Faction Test 100.6.0.0
This commit is contained in:
@@ -175,11 +175,11 @@ namespace Barotrauma
|
|||||||
position += amount;
|
position += amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ClientWrite(IWriteMessage msg)
|
public void ClientWrite(in SegmentTableWriter<ClientNetSegment> segmentTableWriter, IWriteMessage msg)
|
||||||
{
|
{
|
||||||
if (Character.Controlled != null && !Character.Controlled.IsDead) { return; }
|
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.X);
|
||||||
msg.WriteSingle(position.Y);
|
msg.WriteSingle(position.Y);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -673,15 +673,6 @@ namespace Barotrauma
|
|||||||
|
|
||||||
partial void UpdateProjSpecific(float deltaTime, Camera cam)
|
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)
|
foreach (GUIMessage message in guiMessages)
|
||||||
{
|
{
|
||||||
bool wasPending = message.Timer < 0.0f;
|
bool wasPending = message.Timer < 0.0f;
|
||||||
|
|||||||
@@ -149,6 +149,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public static bool ShouldRecreateHudTexts { get; set; } = true;
|
public static bool ShouldRecreateHudTexts { get; set; } = true;
|
||||||
private static bool heldDownShiftWhenGotHudTexts;
|
private static bool heldDownShiftWhenGotHudTexts;
|
||||||
|
private static float timeHealthWindowClosed;
|
||||||
|
|
||||||
public static bool IsCampaignInterfaceOpen =>
|
public static bool IsCampaignInterfaceOpen =>
|
||||||
GameMain.GameSession?.Campaign != null &&
|
GameMain.GameSession?.Campaign != null &&
|
||||||
@@ -222,7 +223,8 @@ namespace Barotrauma
|
|||||||
if (character.Info != null && !character.ShouldLockHud() && character.SelectedCharacter == null && Screen.Selected != GameMain.SubEditorScreen)
|
if (character.Info != null && !character.ShouldLockHud() && character.SelectedCharacter == null && Screen.Selected != GameMain.SubEditorScreen)
|
||||||
{
|
{
|
||||||
bool mouseOnPortrait = MouseOnCharacterPortrait() && GUI.MouseOn == null;
|
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;
|
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)
|
public static void Draw(SpriteBatch spriteBatch, Character character, Camera cam)
|
||||||
@@ -539,7 +550,17 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
var item = character.Inventory.GetItemAt(i);
|
var item = character.Inventory.GetItemAt(i);
|
||||||
if (item == null || character.Inventory.SlotTypes[i] == InvSlotType.Any) { continue; }
|
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)
|
foreach (ItemComponent ic in item.Components)
|
||||||
{
|
{
|
||||||
if (ic.DrawHudWhenEquipped) { ic.DrawHUD(spriteBatch, character); }
|
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)
|
if (memInput.Count > 60)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ namespace Barotrauma
|
|||||||
Character.Controlled.DeselectCharacter();
|
Character.Controlled.DeselectCharacter();
|
||||||
}
|
}
|
||||||
|
|
||||||
Character.Controlled.ResetInteract = true;
|
Character.Controlled.DisableInteract = true;
|
||||||
if (openHealthWindow != null)
|
if (openHealthWindow != null)
|
||||||
{
|
{
|
||||||
if (value.Character.Info == null || value.Character == Character.Controlled || Character.Controlled.HasEquippedItem("healthscanner".ToIdentifier()))
|
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)
|
&& p.InstallTime.TryUnwrap(out var installTime)
|
||||||
&& item.LatestUpdateTime <= installTime))
|
&& item.LatestUpdateTime <= installTime))
|
||||||
.ToArray();
|
.ToArray();
|
||||||
if (needInstalling.Any())
|
if (!needInstalling.Any()) { return Enumerable.Empty<Steamworks.Ugc.Item>(); }
|
||||||
{
|
|
||||||
await Task.WhenAll(
|
await Task.WhenAll(
|
||||||
needInstalling.Select(SteamManager.Workshop.DownloadModThenEnqueueInstall));
|
needInstalling.Select(SteamManager.Workshop.DownloadModThenEnqueueInstall));
|
||||||
}
|
|
||||||
|
|
||||||
return needInstalling;
|
return needInstalling;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -128,21 +128,17 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public static void Update(float deltaTime)
|
public static void Update(float deltaTime)
|
||||||
{
|
{
|
||||||
lock (queuedMessages)
|
while (queuedMessages.TryDequeue(out var newMsg))
|
||||||
{
|
{
|
||||||
while (queuedMessages.Count > 0)
|
AddMessage(newMsg);
|
||||||
{
|
|
||||||
var newMsg = queuedMessages.Dequeue();
|
|
||||||
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);
|
SaveLogs();
|
||||||
if (unsavedMessages.Count >= messagesPerFile)
|
unsavedMessages.Clear();
|
||||||
{
|
|
||||||
SaveLogs();
|
|
||||||
unsavedMessages.Clear();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -258,25 +254,21 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public static void DequeueMessages()
|
public static void DequeueMessages()
|
||||||
{
|
{
|
||||||
lock (queuedMessages)
|
while (queuedMessages.TryDequeue(out var newMsg))
|
||||||
{
|
{
|
||||||
while (queuedMessages.Count > 0)
|
if (listBox == null)
|
||||||
{
|
{
|
||||||
var newMsg = queuedMessages.Dequeue();
|
//don't attempt to add to the listbox if it hasn't been created yet
|
||||||
if (listBox == null)
|
Messages.Add(newMsg);
|
||||||
{
|
}
|
||||||
//don't attempt to add to the listbox if it hasn't been created yet
|
else
|
||||||
Messages.Add(newMsg);
|
{
|
||||||
}
|
AddMessage(newMsg);
|
||||||
else
|
}
|
||||||
{
|
|
||||||
AddMessage(newMsg);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (GameSettings.CurrentConfig.SaveDebugConsoleLogs || GameSettings.CurrentConfig.VerboseLogging)
|
if (GameSettings.CurrentConfig.SaveDebugConsoleLogs || GameSettings.CurrentConfig.VerboseLogging)
|
||||||
{
|
{
|
||||||
unsavedMessages.Add(newMsg);
|
unsavedMessages.Add(newMsg);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
partial void OnStateChangedProjSpecific()
|
partial void OnStateChangedProjSpecific()
|
||||||
{
|
{
|
||||||
|
SoundPlayer.ForceMusicUpdate();
|
||||||
if (Phase == MissionPhase.NoItemsDestroyed)
|
if (Phase == MissionPhase.NoItemsDestroyed)
|
||||||
{
|
{
|
||||||
CoroutineManager.Invoke(() =>
|
CoroutineManager.Invoke(() =>
|
||||||
@@ -31,6 +32,10 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
else if (Phase == MissionPhase.BossKilled)
|
else if (Phase == MissionPhase.BossKilled)
|
||||||
{
|
{
|
||||||
|
if (!string.IsNullOrEmpty(endCinematicSound))
|
||||||
|
{
|
||||||
|
SoundPlayer.PlaySound(endCinematicSound);
|
||||||
|
}
|
||||||
CoroutineManager.Invoke(() =>
|
CoroutineManager.Invoke(() =>
|
||||||
{
|
{
|
||||||
new CameraTransition(boss, GameMain.GameScreen.Cam, null, Alignment.Center, panDuration: 3, fadeOut: false, endZoom: 0.1f * GUI.yScale)
|
new CameraTransition(boss, GameMain.GameScreen.Cam, null, Alignment.Center, panDuration: 3, fadeOut: false, endZoom: 0.1f * GUI.yScale)
|
||||||
|
|||||||
@@ -115,6 +115,11 @@ namespace Barotrauma
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Identifier GetOverrideMusicType()
|
||||||
|
{
|
||||||
|
return Prefab.GetOverrideMusicType(State);
|
||||||
|
}
|
||||||
|
|
||||||
public virtual void ClientRead(IReadMessage msg)
|
public virtual void ClientRead(IReadMessage msg)
|
||||||
{
|
{
|
||||||
State = msg.ReadInt16();
|
State = msg.ReadInt16();
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using System;
|
using System;
|
||||||
using System.Xml.Linq;
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
partial class MissionPrefab : PrefabWithUintIdentifier
|
partial class MissionPrefab : PrefabWithUintIdentifier
|
||||||
{
|
{
|
||||||
|
private ImmutableArray<Sprite> portraits = new ImmutableArray<Sprite>();
|
||||||
|
|
||||||
|
public bool HasPortraits => portraits.Length > 0;
|
||||||
|
|
||||||
public Sprite Icon
|
public Sprite Icon
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
@@ -49,24 +54,57 @@ namespace Barotrauma
|
|||||||
private Sprite hudIcon;
|
private Sprite hudIcon;
|
||||||
private Color? hudIconColor;
|
private Color? hudIconColor;
|
||||||
|
|
||||||
|
private ImmutableDictionary<int, Identifier> overrideMusicOnState;
|
||||||
|
|
||||||
partial void InitProjSpecific(ContentXElement element)
|
partial void InitProjSpecific(ContentXElement element)
|
||||||
{
|
{
|
||||||
DisplayTargetHudIcons = element.GetAttributeBool("displaytargethudicons", false);
|
DisplayTargetHudIcons = element.GetAttributeBool("displaytargethudicons", false);
|
||||||
HudIconMaxDistance = element.GetAttributeFloat("hudiconmaxdistance", 1000.0f);
|
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())
|
foreach (var subElement in element.Elements())
|
||||||
{
|
{
|
||||||
string name = subElement.Name.ToString();
|
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||||
if (name.Equals("icon", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
{
|
||||||
Icon = new Sprite(subElement);
|
case "icon":
|
||||||
IconColor = subElement.GetAttributeColor("color", Color.White);
|
Icon = new Sprite(subElement);
|
||||||
}
|
IconColor = subElement.GetAttributeColor("color", Color.White);
|
||||||
else if (name.Equals("hudicon", StringComparison.OrdinalIgnoreCase))
|
break;
|
||||||
{
|
case "hudicon":
|
||||||
hudIcon = new Sprite(subElement);
|
hudIcon = new Sprite(subElement);
|
||||||
hudIconColor = subElement.GetAttributeColor("color");
|
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()
|
partial void DisposeProjectSpecific()
|
||||||
|
|||||||
@@ -335,7 +335,7 @@ namespace Barotrauma
|
|||||||
DrawString(spriteBatch, new Vector2(10, y),
|
DrawString(spriteBatch, new Vector2(10, y),
|
||||||
"FPS: " + Math.Round(GameMain.PerformanceCounter.AverageFramesPerSecond),
|
"FPS: " + Math.Round(GameMain.PerformanceCounter.AverageFramesPerSecond),
|
||||||
Color.White, Color.Black * 0.5f, 0, GUIStyle.SmallFont);
|
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;
|
y += yStep;
|
||||||
DrawString(spriteBatch, new Vector2(10, y),
|
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(
|
Rectangle area = drawArea ?? new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight);
|
||||||
(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;
|
|
||||||
|
|
||||||
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);
|
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);
|
pos = new Vector2(pos.X * paddingX, pos.Y * paddingY);
|
||||||
|
|
||||||
spriteBatch.Draw(backgroundSprite.Texture,
|
spriteBatch.Draw(backgroundSprite.Texture,
|
||||||
new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight) / 2 + pos,
|
area.Center.ToVector2() + pos,
|
||||||
null, color, 0.0f, backgroundSprite.size / 2,
|
null, color, 0.0f, backgroundSprite.size / 2,
|
||||||
scale, SpriteEffects.None, 0.0f);
|
scale, spriteEffects, 0.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Update list
|
#region Update list
|
||||||
|
|||||||
@@ -758,7 +758,7 @@ namespace Barotrauma
|
|||||||
toolTipBlock.DrawManually(spriteBatch);
|
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; }
|
if (ObjectiveManager.ContentRunning) { return; }
|
||||||
|
|
||||||
@@ -775,7 +775,10 @@ namespace Barotrauma
|
|||||||
toolTipBlock.UserData = toolTip;
|
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)
|
if (toolTipBlock.Rect.Right > GameMain.GraphicsWidth - 10)
|
||||||
{
|
{
|
||||||
toolTipBlock.RectTransform.AbsoluteOffset -= new Point(toolTipBlock.Rect.Width, 0);
|
toolTipBlock.RectTransform.AbsoluteOffset -= new Point(toolTipBlock.Rect.Width, 0);
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
class LoadingScreen
|
class LoadingScreen
|
||||||
{
|
{
|
||||||
private readonly Texture2D defaultBackgroundTexture, overlay;
|
private readonly Sprite defaultBackgroundTexture, overlay;
|
||||||
private readonly SpriteSheet decorativeGraph, decorativeMap;
|
private readonly SpriteSheet decorativeGraph, decorativeMap;
|
||||||
private Texture2D currentBackgroundTexture;
|
private Sprite currentBackgroundTexture;
|
||||||
private readonly Sprite noiseSprite;
|
private readonly Sprite noiseSprite;
|
||||||
|
|
||||||
private string randText = "";
|
private string randText = "";
|
||||||
@@ -24,6 +24,8 @@ namespace Barotrauma
|
|||||||
private Video currSplashScreen;
|
private Video currSplashScreen;
|
||||||
private DateTime videoStartTime;
|
private DateTime videoStartTime;
|
||||||
|
|
||||||
|
private bool mirrorBackground;
|
||||||
|
|
||||||
public struct PendingSplashScreen
|
public struct PendingSplashScreen
|
||||||
{
|
{
|
||||||
public string Filename;
|
public string Filename;
|
||||||
@@ -112,12 +114,12 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public LoadingScreen(GraphicsDevice graphics)
|
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));
|
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));
|
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);
|
noiseSprite = new Sprite("Content/UI/noise.png", Vector2.Zero);
|
||||||
DrawLoadingText = true;
|
DrawLoadingText = true;
|
||||||
SetSelectedTip(TextManager.Get("LoadingScreenTip"));
|
SetSelectedTip(TextManager.Get("LoadingScreenTip"));
|
||||||
@@ -143,24 +145,19 @@ namespace Barotrauma
|
|||||||
|
|
||||||
currentBackgroundTexture ??= defaultBackgroundTexture;
|
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);
|
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);
|
GUI.DrawBackgroundSprite(spriteBatch, currentBackgroundTexture, Color.White, drawArea,
|
||||||
Vector2 pos = new Vector2((float)PerlinNoise.CalculatePerlin(noiseT, noiseT, 0) - 0.5f, (float)PerlinNoise.CalculatePerlin(noiseT, noiseT, 0.5f) - 0.5f);
|
spriteEffects: mirrorBackground ? SpriteEffects.FlipHorizontally : SpriteEffects.None);
|
||||||
pos = new Vector2(pos.X * paddingX, pos.Y * paddingY);
|
overlay.Draw(spriteBatch, Vector2.Zero, scale: overlayScale);
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
|
double noiseT = Timing.TotalTime * 0.02f;
|
||||||
float noiseStrength = (float)PerlinNoise.CalculatePerlin(noiseT, noiseT, 0);
|
float noiseStrength = (float)PerlinNoise.CalculatePerlin(noiseT, noiseT, 0);
|
||||||
float noiseScale = (float)PerlinNoise.CalculatePerlin(noiseT * 5.0f, noiseT * 2.0f, 0) * 4.0f;
|
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),
|
noiseSprite.DrawTiled(spriteBatch, Vector2.Zero, new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight),
|
||||||
@@ -430,7 +427,12 @@ namespace Barotrauma
|
|||||||
drawn = false;
|
drawn = false;
|
||||||
LoadState = null;
|
LoadState = null;
|
||||||
SetSelectedTip(TextManager.Get("LoadingScreenTip"));
|
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)
|
while (!drawn)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ namespace Barotrauma
|
|||||||
set => hadSellSubPermissions = value;
|
set => hadSellSubPermissions = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool HasPermissionToUseTab(StoreTab tab)
|
private static bool HasPermissionToUseTab(StoreTab tab)
|
||||||
{
|
{
|
||||||
return tab switch
|
return tab switch
|
||||||
{
|
{
|
||||||
@@ -278,6 +278,7 @@ namespace Barotrauma
|
|||||||
RefreshBuying(updateOwned: false);
|
RefreshBuying(updateOwned: false);
|
||||||
RefreshSelling(updateOwned: false);
|
RefreshSelling(updateOwned: false);
|
||||||
RefreshSellingFromSub(updateOwned: false);
|
RefreshSellingFromSub(updateOwned: false);
|
||||||
|
SetConfirmButtonBehavior();
|
||||||
needsRefresh = false;
|
needsRefresh = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -881,18 +882,17 @@ namespace Barotrauma
|
|||||||
float prevBuyListScroll = storeBuyList.BarScroll;
|
float prevBuyListScroll = storeBuyList.BarScroll;
|
||||||
float prevShoppingCrateScroll = shoppingCrateBuyList.BarScroll;
|
float prevShoppingCrateScroll = shoppingCrateBuyList.BarScroll;
|
||||||
|
|
||||||
int dailySpecialCount = ActiveStore.DailySpecials.Count;
|
int dailySpecialCount = ActiveStore?.DailySpecials.Count(s => s.CanCharacterBuy()) ?? 0;
|
||||||
if ((storeDailySpecialsGroup != null) != ActiveStore.DailySpecials.Any() || dailySpecialCount != prevDailySpecialCount)
|
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 = CreateDealsGroup(storeBuyList, dailySpecialCount);
|
||||||
storeDailySpecialsGroup.Parent.SetAsFirstChild();
|
storeDailySpecialsGroup.Parent.SetAsFirstChild();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
storeBuyList.RemoveChild(storeDailySpecialsGroup.Parent);
|
|
||||||
storeDailySpecialsGroup = null;
|
storeDailySpecialsGroup = null;
|
||||||
}
|
}
|
||||||
storeBuyList.RecalculateChildren();
|
storeBuyList.RecalculateChildren();
|
||||||
@@ -901,15 +901,17 @@ namespace Barotrauma
|
|||||||
|
|
||||||
bool hasPermissions = HasTabPermissions(StoreTab.Buy);
|
bool hasPermissions = HasTabPermissions(StoreTab.Buy);
|
||||||
var existingItemFrames = new HashSet<GUIComponent>();
|
var existingItemFrames = new HashSet<GUIComponent>();
|
||||||
foreach (PurchasedItem item in ActiveStore.Stock)
|
if (ActiveStore != null)
|
||||||
{
|
{
|
||||||
CreateOrUpdateItemFrame(item.ItemPrefab, item.Quantity);
|
foreach (PurchasedItem item in ActiveStore.Stock)
|
||||||
}
|
{
|
||||||
|
CreateOrUpdateItemFrame(item.ItemPrefab, item.Quantity);
|
||||||
foreach (ItemPrefab itemPrefab in ActiveStore.DailySpecials)
|
}
|
||||||
{
|
foreach (ItemPrefab itemPrefab in ActiveStore.DailySpecials)
|
||||||
if (ActiveStore.Stock.Any(pi => pi.ItemPrefab == itemPrefab)) { continue; }
|
{
|
||||||
CreateOrUpdateItemFrame(itemPrefab, 0);
|
if (ActiveStore.Stock.Any(pi => pi.ItemPrefab == itemPrefab)) { continue; }
|
||||||
|
CreateOrUpdateItemFrame(itemPrefab, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CreateOrUpdateItemFrame(ItemPrefab itemPrefab, int quantity)
|
void CreateOrUpdateItemFrame(ItemPrefab itemPrefab, int quantity)
|
||||||
@@ -969,11 +971,11 @@ namespace Barotrauma
|
|||||||
float prevSellListScroll = storeSellList.BarScroll;
|
float prevSellListScroll = storeSellList.BarScroll;
|
||||||
float prevShoppingCrateScroll = shoppingCrateSellList.BarScroll;
|
float prevShoppingCrateScroll = shoppingCrateSellList.BarScroll;
|
||||||
|
|
||||||
int requestedGoodsCount = ActiveStore.RequestedGoods.Count;
|
int requestedGoodsCount = ActiveStore?.RequestedGoods.Count ?? 0;
|
||||||
if ((storeRequestedGoodGroup != null) != ActiveStore.RequestedGoods.Any() || requestedGoodsCount != prevRequestedGoodsCount)
|
if ((ActiveStore == null && storeRequestedGoodGroup != null) || (storeRequestedGoodGroup != null) != ActiveStore.RequestedGoods.Any() || requestedGoodsCount != prevRequestedGoodsCount)
|
||||||
{
|
{
|
||||||
storeSellList.RemoveChild(storeRequestedGoodGroup?.Parent);
|
storeSellList.RemoveChild(storeRequestedGoodGroup?.Parent);
|
||||||
if (storeRequestedGoodGroup == null || requestedGoodsCount != prevRequestedGoodsCount)
|
if (ActiveStore != null && (storeRequestedGoodGroup == null || requestedGoodsCount != prevRequestedGoodsCount))
|
||||||
{
|
{
|
||||||
storeRequestedGoodGroup = CreateDealsGroup(storeSellList, requestedGoodsCount);
|
storeRequestedGoodGroup = CreateDealsGroup(storeSellList, requestedGoodsCount);
|
||||||
storeRequestedGoodGroup.Parent.SetAsFirstChild();
|
storeRequestedGoodGroup.Parent.SetAsFirstChild();
|
||||||
@@ -988,14 +990,17 @@ namespace Barotrauma
|
|||||||
|
|
||||||
bool hasPermissions = HasTabPermissions(StoreTab.Sell);
|
bool hasPermissions = HasTabPermissions(StoreTab.Sell);
|
||||||
var existingItemFrames = new HashSet<GUIComponent>();
|
var existingItemFrames = new HashSet<GUIComponent>();
|
||||||
foreach (PurchasedItem item in itemsToSell)
|
if (ActiveStore != null)
|
||||||
{
|
{
|
||||||
CreateOrUpdateItemFrame(item.ItemPrefab, item.Quantity);
|
foreach (PurchasedItem item in itemsToSell)
|
||||||
}
|
{
|
||||||
foreach (var requestedGood in ActiveStore.RequestedGoods)
|
CreateOrUpdateItemFrame(item.ItemPrefab, item.Quantity);
|
||||||
{
|
}
|
||||||
if (itemsToSell.Any(pi => pi.ItemPrefab == requestedGood)) { continue; }
|
foreach (var requestedGood in ActiveStore.RequestedGoods)
|
||||||
CreateOrUpdateItemFrame(requestedGood, 0);
|
{
|
||||||
|
if (itemsToSell.Any(pi => pi.ItemPrefab == requestedGood)) { continue; }
|
||||||
|
CreateOrUpdateItemFrame(requestedGood, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CreateOrUpdateItemFrame(ItemPrefab itemPrefab, int itemQuantity)
|
void CreateOrUpdateItemFrame(ItemPrefab itemPrefab, int itemQuantity)
|
||||||
@@ -1053,11 +1058,11 @@ namespace Barotrauma
|
|||||||
float prevSellListScroll = storeSellFromSubList.BarScroll;
|
float prevSellListScroll = storeSellFromSubList.BarScroll;
|
||||||
float prevShoppingCrateScroll = shoppingCrateSellFromSubList.BarScroll;
|
float prevShoppingCrateScroll = shoppingCrateSellFromSubList.BarScroll;
|
||||||
|
|
||||||
int requestedGoodsCount = ActiveStore.RequestedGoods.Count;
|
int requestedGoodsCount = ActiveStore?.RequestedGoods.Count ?? 0;
|
||||||
if ((storeRequestedSubGoodGroup != null) != ActiveStore.RequestedGoods.Any() || requestedGoodsCount != prevSubRequestedGoodsCount)
|
if ((ActiveStore == null && storeRequestedSubGoodGroup != null) || (storeRequestedSubGoodGroup != null) != ActiveStore.RequestedGoods.Any() || requestedGoodsCount != prevSubRequestedGoodsCount)
|
||||||
{
|
{
|
||||||
storeSellFromSubList.RemoveChild(storeRequestedSubGoodGroup?.Parent);
|
storeSellFromSubList.RemoveChild(storeRequestedSubGoodGroup?.Parent);
|
||||||
if (storeRequestedSubGoodGroup == null || requestedGoodsCount != prevSubRequestedGoodsCount)
|
if (ActiveStore != null && (storeRequestedSubGoodGroup == null || requestedGoodsCount != prevSubRequestedGoodsCount))
|
||||||
{
|
{
|
||||||
storeRequestedSubGoodGroup = CreateDealsGroup(storeSellFromSubList, requestedGoodsCount);
|
storeRequestedSubGoodGroup = CreateDealsGroup(storeSellFromSubList, requestedGoodsCount);
|
||||||
storeRequestedSubGoodGroup.Parent.SetAsFirstChild();
|
storeRequestedSubGoodGroup.Parent.SetAsFirstChild();
|
||||||
@@ -1072,14 +1077,17 @@ namespace Barotrauma
|
|||||||
|
|
||||||
bool hasPermissions = HasSellSubPermissions;
|
bool hasPermissions = HasSellSubPermissions;
|
||||||
var existingItemFrames = new HashSet<GUIComponent>();
|
var existingItemFrames = new HashSet<GUIComponent>();
|
||||||
foreach (PurchasedItem item in itemsToSellFromSub)
|
if (ActiveStore != null)
|
||||||
{
|
{
|
||||||
CreateOrUpdateItemFrame(item.ItemPrefab, item.Quantity);
|
foreach (PurchasedItem item in itemsToSellFromSub)
|
||||||
}
|
{
|
||||||
foreach (var requestedGood in ActiveStore.RequestedGoods)
|
CreateOrUpdateItemFrame(item.ItemPrefab, item.Quantity);
|
||||||
{
|
}
|
||||||
if (itemsToSellFromSub.Any(pi => pi.ItemPrefab == requestedGood)) { continue; }
|
foreach (var requestedGood in ActiveStore.RequestedGoods)
|
||||||
CreateOrUpdateItemFrame(requestedGood, 0);
|
{
|
||||||
|
if (itemsToSellFromSub.Any(pi => pi.ItemPrefab == requestedGood)) { continue; }
|
||||||
|
CreateOrUpdateItemFrame(requestedGood, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CreateOrUpdateItemFrame(ItemPrefab itemPrefab, int itemQuantity)
|
void CreateOrUpdateItemFrame(ItemPrefab itemPrefab, int itemQuantity)
|
||||||
@@ -1166,6 +1174,7 @@ namespace Barotrauma
|
|||||||
public void RefreshItemsToSell()
|
public void RefreshItemsToSell()
|
||||||
{
|
{
|
||||||
itemsToSell.Clear();
|
itemsToSell.Clear();
|
||||||
|
if (ActiveStore == null) { return; }
|
||||||
var playerItems = CargoManager.GetSellableItems(Character.Controlled);
|
var playerItems = CargoManager.GetSellableItems(Character.Controlled);
|
||||||
foreach (Item playerItem in playerItems)
|
foreach (Item playerItem in playerItems)
|
||||||
{
|
{
|
||||||
@@ -1196,6 +1205,7 @@ namespace Barotrauma
|
|||||||
public void RefreshItemsToSellFromSub()
|
public void RefreshItemsToSellFromSub()
|
||||||
{
|
{
|
||||||
itemsToSellFromSub.Clear();
|
itemsToSellFromSub.Clear();
|
||||||
|
if (ActiveStore == null) { return; }
|
||||||
var subItems = CargoManager.GetSellableItemsFromSub();
|
var subItems = CargoManager.GetSellableItemsFromSub();
|
||||||
foreach (Item subItem in subItems)
|
foreach (Item subItem in subItems)
|
||||||
{
|
{
|
||||||
@@ -1229,52 +1239,55 @@ namespace Barotrauma
|
|||||||
bool hasPermissions = HasTabPermissions(tab);
|
bool hasPermissions = HasTabPermissions(tab);
|
||||||
HashSet<GUIComponent> existingItemFrames = new HashSet<GUIComponent>();
|
HashSet<GUIComponent> existingItemFrames = new HashSet<GUIComponent>();
|
||||||
int totalPrice = 0;
|
int totalPrice = 0;
|
||||||
foreach (PurchasedItem item in items)
|
if (ActiveStore != null)
|
||||||
{
|
{
|
||||||
if (!(item.ItemPrefab.GetPriceInfo(ActiveStore) is { } priceInfo)) { continue; }
|
foreach (PurchasedItem item in items)
|
||||||
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);
|
if (!(item.ItemPrefab.GetPriceInfo(ActiveStore) is { } priceInfo)) { continue; }
|
||||||
numInput = itemFrame.FindChild(c => c is GUINumberInput, recursive: true) as GUINumberInput;
|
GUINumberInput numInput = null;
|
||||||
}
|
if (!(listBox.Content.FindChild(c => c.UserData is PurchasedItem pi && pi.ItemPrefab.Identifier == item.ItemPrefab.Identifier) is { } itemFrame))
|
||||||
else
|
{
|
||||||
{
|
itemFrame = CreateItemFrame(item, listBox, tab, forceDisable: !hasPermissions);
|
||||||
itemFrame.UserData = item;
|
numInput = itemFrame.FindChild(c => c is GUINumberInput, recursive: true) as GUINumberInput;
|
||||||
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)
|
if (numInput != null)
|
||||||
{
|
{
|
||||||
numInput.UserData = item;
|
if (numInput.IntValue != item.Quantity) { itemFrame.Flash(GUIStyle.Green); }
|
||||||
numInput.Enabled = hasPermissions;
|
numInput.IntValue = item.Quantity;
|
||||||
numInput.MaxValueInt = GetMaxAvailable(item.ItemPrefab, tab);
|
|
||||||
}
|
}
|
||||||
SetOwnedText(itemFrame);
|
suppressBuySell = false;
|
||||||
SetItemFrameStatus(itemFrame, hasPermissions);
|
|
||||||
}
|
|
||||||
existingItemFrames.Add(itemFrame);
|
|
||||||
|
|
||||||
suppressBuySell = true;
|
try
|
||||||
if (numInput != null)
|
|
||||||
{
|
|
||||||
if (numInput.IntValue != item.Quantity) { itemFrame.Flash(GUIStyle.Green); }
|
|
||||||
numInput.IntValue = item.Quantity;
|
|
||||||
}
|
|
||||||
suppressBuySell = false;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
int price = tab switch
|
|
||||||
{
|
{
|
||||||
StoreTab.Buy => ActiveStore.GetAdjustedItemBuyPrice(item.ItemPrefab, priceInfo: priceInfo),
|
int price = tab switch
|
||||||
StoreTab.Sell => ActiveStore.GetAdjustedItemSellPrice(item.ItemPrefab, priceInfo: priceInfo),
|
{
|
||||||
StoreTab.SellSub => ActiveStore.GetAdjustedItemSellPrice(item.ItemPrefab, priceInfo: priceInfo),
|
StoreTab.Buy => ActiveStore.GetAdjustedItemBuyPrice(item.ItemPrefab, priceInfo: priceInfo),
|
||||||
_ => throw new NotImplementedException()
|
StoreTab.Sell => ActiveStore.GetAdjustedItemSellPrice(item.ItemPrefab, priceInfo: priceInfo),
|
||||||
};
|
StoreTab.SellSub => ActiveStore.GetAdjustedItemSellPrice(item.ItemPrefab, priceInfo: priceInfo),
|
||||||
totalPrice += item.Quantity * price;
|
_ => throw new NotImplementedException()
|
||||||
}
|
};
|
||||||
catch (NotImplementedException e)
|
totalPrice += item.Quantity * price;
|
||||||
{
|
}
|
||||||
DebugConsole.LogError($"Error getting item price: Uknown store tab type. {e.StackTrace.CleanupStackTrace()}");
|
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)
|
private void SortItems(GUIListBox list, SortingMethod sortingMethod)
|
||||||
{
|
{
|
||||||
if (CurrentLocation == null) { return; }
|
if (CurrentLocation == null || ActiveStore == null) { return; }
|
||||||
|
|
||||||
if (sortingMethod == SortingMethod.AlphabeticalAsc || sortingMethod == SortingMethod.AlphabeticalDesc)
|
if (sortingMethod == SortingMethod.AlphabeticalAsc || sortingMethod == SortingMethod.AlphabeticalDesc)
|
||||||
{
|
{
|
||||||
@@ -1707,6 +1720,8 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
OwnedItems.Clear();
|
OwnedItems.Clear();
|
||||||
|
|
||||||
|
if (ActiveStore == null) { return; }
|
||||||
|
|
||||||
// Add items on the sub(s)
|
// Add items on the sub(s)
|
||||||
if (Submarine.MainSub?.GetItems(true) is List<Item> subItems)
|
if (Submarine.MainSub?.GetItems(true) is List<Item> subItems)
|
||||||
{
|
{
|
||||||
@@ -2124,7 +2139,12 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private void SetShoppingCrateTotalText()
|
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.Text = TextManager.FormatCurrency(buyTotal);
|
||||||
shoppingCrateTotal.TextColor = Balance < buyTotal ? Color.Red : Color.White;
|
shoppingCrateTotal.TextColor = Balance < buyTotal ? Color.Red : Color.White;
|
||||||
@@ -2144,7 +2164,11 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private void SetConfirmButtonBehavior()
|
private void SetConfirmButtonBehavior()
|
||||||
{
|
{
|
||||||
if (IsBuying)
|
if (ActiveStore == null)
|
||||||
|
{
|
||||||
|
confirmButton.OnClicked = null;
|
||||||
|
}
|
||||||
|
else if (IsBuying)
|
||||||
{
|
{
|
||||||
confirmButton.ClickSound = GUISoundType.ConfirmTransaction;
|
confirmButton.ClickSound = GUISoundType.ConfirmTransaction;
|
||||||
confirmButton.Text = TextManager.Get("CampaignStore.Purchase");
|
confirmButton.Text = TextManager.Get("CampaignStore.Purchase");
|
||||||
@@ -2172,6 +2196,7 @@ namespace Barotrauma
|
|||||||
private void SetConfirmButtonStatus()
|
private void SetConfirmButtonStatus()
|
||||||
{
|
{
|
||||||
confirmButton.Enabled =
|
confirmButton.Enabled =
|
||||||
|
ActiveStore != null &&
|
||||||
HasActiveTabPermissions() &&
|
HasActiveTabPermissions() &&
|
||||||
ActiveShoppingCrateList.Content.RectTransform.Children.Any() &&
|
ActiveShoppingCrateList.Content.RectTransform.Children.Any() &&
|
||||||
activeTab switch
|
activeTab switch
|
||||||
@@ -2181,6 +2206,7 @@ namespace Barotrauma
|
|||||||
StoreTab.SellSub => CurrentLocation != null && sellFromSubTotal <= ActiveStore.Balance,
|
StoreTab.SellSub => CurrentLocation != null && sellFromSubTotal <= ActiveStore.Balance,
|
||||||
_ => false
|
_ => false
|
||||||
};
|
};
|
||||||
|
confirmButton.Visible = ActiveStore != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetClearAllButtonStatus()
|
private void SetClearAllButtonStatus()
|
||||||
@@ -2254,29 +2280,32 @@ namespace Barotrauma
|
|||||||
prevBalance = currBalance;
|
prevBalance = currBalance;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (needsItemsToSellRefresh)
|
if (ActiveStore != null)
|
||||||
{
|
{
|
||||||
RefreshItemsToSell();
|
if (needsItemsToSellRefresh)
|
||||||
}
|
{
|
||||||
if (needsItemsToSellFromSubRefresh)
|
RefreshItemsToSell();
|
||||||
{
|
}
|
||||||
RefreshItemsToSellFromSub();
|
if (needsItemsToSellFromSubRefresh)
|
||||||
}
|
{
|
||||||
if (needsRefresh)
|
RefreshItemsToSellFromSub();
|
||||||
{
|
}
|
||||||
Refresh(updateOwned: ownedItemsUpdateTimer > 0.0f);
|
if (needsRefresh)
|
||||||
}
|
{
|
||||||
if (needsBuyingRefresh || HavePermissionsChanged(StoreTab.Buy))
|
Refresh(updateOwned: ownedItemsUpdateTimer > 0.0f);
|
||||||
{
|
}
|
||||||
RefreshBuying(updateOwned: ownedItemsUpdateTimer > 0.0f);
|
if (needsBuyingRefresh || HavePermissionsChanged(StoreTab.Buy))
|
||||||
}
|
{
|
||||||
if (needsSellingRefresh || HavePermissionsChanged(StoreTab.Sell))
|
RefreshBuying(updateOwned: ownedItemsUpdateTimer > 0.0f);
|
||||||
{
|
}
|
||||||
RefreshSelling(updateOwned: ownedItemsUpdateTimer > 0.0f);
|
if (needsSellingRefresh || HavePermissionsChanged(StoreTab.Sell))
|
||||||
}
|
{
|
||||||
if (needsSellingFromSubRefresh || HavePermissionsChanged(StoreTab.SellSub))
|
RefreshSelling(updateOwned: ownedItemsUpdateTimer > 0.0f);
|
||||||
{
|
}
|
||||||
RefreshSellingFromSub(updateOwned: ownedItemsUpdateTimer > 0.0f, updateItemsToSellFromSub: sellableItemsFromSubUpdateTimer > 0.0f);
|
if (needsSellingFromSubRefresh || HavePermissionsChanged(StoreTab.SellSub))
|
||||||
|
{
|
||||||
|
RefreshSellingFromSub(updateOwned: ownedItemsUpdateTimer > 0.0f, updateItemsToSellFromSub: sellableItemsFromSubUpdateTimer > 0.0f);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateStopwatch.Stop();
|
updateStopwatch.Stop();
|
||||||
|
|||||||
@@ -1491,7 +1491,11 @@ namespace Barotrauma
|
|||||||
GUIFrame missionFrame = new GUIFrame(new RectTransform(Vector2.One, infoFrame.RectTransform, Anchor.TopCenter), style: "GUIFrameListBox");
|
GUIFrame missionFrame = new GUIFrame(new RectTransform(Vector2.One, infoFrame.RectTransform, Anchor.TopCenter), style: "GUIFrameListBox");
|
||||||
int padding = (int)(0.0245f * missionFrame.Rect.Height);
|
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);
|
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))
|
GUILayoutGroup locationInfoContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.3f), missionFrameContent.RectTransform))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -283,7 +283,7 @@ namespace Barotrauma
|
|||||||
break;
|
break;
|
||||||
case TalentTreeType.Specialization:
|
case TalentTreeType.Specialization:
|
||||||
talentList = GetSpecializationList();
|
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;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new ArgumentOutOfRangeException($"Invalid TalentTreeType \"{subTree.Type}\"");
|
throw new ArgumentOutOfRangeException($"Invalid TalentTreeType \"{subTree.Type}\"");
|
||||||
@@ -325,7 +325,9 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
|
|
||||||
var specializationList = GetSpecializationList();
|
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);
|
GUITextBlock.AutoScaleAndNormalize(subTreeNames);
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ using System.Diagnostics;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using Barotrauma.Extensions;
|
||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
@@ -465,10 +466,11 @@ namespace Barotrauma
|
|||||||
|
|
||||||
LegacySteamUgcTransition.Prepare();
|
LegacySteamUgcTransition.Prepare();
|
||||||
var contentPackageLoadRoutine = ContentPackageManager.Init();
|
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;
|
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;
|
yield return CoroutineStatus.Running;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1078,10 +1080,9 @@ namespace Barotrauma
|
|||||||
|
|
||||||
if (GameSession != null)
|
if (GameSession != null)
|
||||||
{
|
{
|
||||||
double roundDuration = Timing.TotalTime - GameSession.RoundStartTime;
|
|
||||||
GameAnalyticsManager.AddProgressionEvent(GameAnalyticsManager.ProgressionStatus.Fail,
|
GameAnalyticsManager.AddProgressionEvent(GameAnalyticsManager.ProgressionStatus.Fail,
|
||||||
GameSession.GameMode?.Preset.Identifier.Value ?? "none",
|
GameSession.GameMode?.Preset.Identifier.Value ?? "none",
|
||||||
roundDuration);
|
GameSession.RoundDuration);
|
||||||
string eventId = "QuitRound:" + (GameSession.GameMode?.Preset.Identifier.Value ?? "none") + ":";
|
string eventId = "QuitRound:" + (GameSession.GameMode?.Preset.Identifier.Value ?? "none") + ":";
|
||||||
GameAnalyticsManager.AddDesignEvent(eventId + "EventManager:CurrentIntensity", GameSession.EventManager.CurrentIntensity);
|
GameAnalyticsManager.AddDesignEvent(eventId + "EventManager:CurrentIntensity", GameSession.EventManager.CurrentIntensity);
|
||||||
foreach (var activeEvent in GameSession.EventManager.ActiveEvents)
|
foreach (var activeEvent in GameSession.EventManager.ActiveEvents)
|
||||||
|
|||||||
@@ -11,7 +11,15 @@ namespace Barotrauma
|
|||||||
private List<SoldEntity> SoldEntities { get; } = new List<SoldEntity>();
|
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
|
// 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)
|
public IEnumerable<Item> GetSellableItems(Character character)
|
||||||
{
|
{
|
||||||
|
|||||||
+1
-1
@@ -432,7 +432,7 @@ namespace Barotrauma
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
Map.ProgressWorld(this, transitionType, (float)(Timing.TotalTime - GameMain.GameSession.RoundStartTime));
|
Map.ProgressWorld(this, transitionType, GameMain.GameSession.RoundDuration);
|
||||||
|
|
||||||
GUI.ClearMessages();
|
GUI.ClearMessages();
|
||||||
|
|
||||||
|
|||||||
@@ -296,7 +296,7 @@ static class ObjectiveManager
|
|||||||
};
|
};
|
||||||
for (int i = 0; i < activeObjectives.Count; i++)
|
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);
|
screenSettings = new ScreenSettings(new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight), GUI.Scale, GameSettings.CurrentConfig.Graphics.DisplayMode);
|
||||||
}
|
}
|
||||||
@@ -318,7 +318,7 @@ static class ObjectiveManager
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds the segment to the objective list
|
/// Adds the segment to the objective list
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static void AddToObjectiveList(Segment segment, bool connectExisting = false)
|
private static void AddToObjectiveList(Segment segment, bool connectExisting = false, bool useExistingIndex = false)
|
||||||
{
|
{
|
||||||
if (connectExisting)
|
if (connectExisting)
|
||||||
{
|
{
|
||||||
@@ -338,8 +338,8 @@ static class ObjectiveManager
|
|||||||
if (parentSegment is not null)
|
if (parentSegment is not null)
|
||||||
{
|
{
|
||||||
// Add this child as the last child in case there are other existing children already
|
// 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 = useExistingIndex ? activeObjectives.IndexOf(segment) :
|
||||||
int childIndex = activeObjectives.IndexOf(parentSegment) + totalChildren;
|
activeObjectives.IndexOf(parentSegment) + activeObjectives.Count(s => s.ParentId == segment.ParentId);
|
||||||
if (objectiveGroup.RectTransform.GetChildIndex(frameRt) != childIndex)
|
if (objectiveGroup.RectTransform.GetChildIndex(frameRt) != childIndex)
|
||||||
{
|
{
|
||||||
frameRt.RepositionChildInHierarchy(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
|
//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)
|
//(in that case, the quick action should just fill up the stack)
|
||||||
bool disallowSwapping =
|
bool disallowSwapping =
|
||||||
heldItem.OwnInventory.Capacity == 1 &&
|
(heldItem.OwnInventory.Capacity == 1 || heldItem.OwnInventory.Container.HasSubContainers) &&
|
||||||
heldItem.OwnInventory.GetItemAt(0)?.Prefab == item.Prefab &&
|
heldItem.OwnInventory.GetItemAt(0)?.Prefab == item.Prefab &&
|
||||||
heldItem.OwnInventory.GetItemsAt(0).Count() > 1;
|
heldItem.OwnInventory.GetItemsAt(0).Count() > 1;
|
||||||
if (heldItem.OwnInventory.TryPutItem(item, Character.Controlled) ||
|
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);
|
GUI.DrawRectangle(spriteBatch, inventoryArea, new Color(30,30,30,100), isFilled: true);
|
||||||
var lockIcon = GUIStyle.GetComponentStyle("LockIcon")?.GetDefaultSprite();
|
var lockIcon = GUIStyle.GetComponentStyle("LockIcon")?.GetDefaultSprite();
|
||||||
lockIcon?.Draw(spriteBatch, inventoryArea.Center.ToVector2(), scale: Math.Min(inventoryArea.Height / lockIcon.size.Y * 0.7f, 1.0f));
|
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)));
|
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>();
|
var wire = it.GetComponent<Wire>();
|
||||||
if (wire != null && wire.Connections.Any(c => c != null)) { return false; }
|
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; }
|
if (it.HasTag("traitormissionitem")) { return false; }
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -29,7 +29,11 @@ namespace Barotrauma.Items.Components
|
|||||||
private Sprite tempRangeIndicator;
|
private Sprite tempRangeIndicator;
|
||||||
|
|
||||||
private Sprite graphLine;
|
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 optimalRangeColor = new Color(74,238,104,255);
|
||||||
private Color offRangeColor = Color.Orange;
|
private Color offRangeColor = Color.Orange;
|
||||||
@@ -67,6 +71,8 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
public override bool RecreateGUIOnResolutionChange => true;
|
public override bool RecreateGUIOnResolutionChange => true;
|
||||||
|
|
||||||
|
public bool TriggerInfographic { get; set; }
|
||||||
|
|
||||||
partial void InitProjSpecific(ContentXElement element)
|
partial void InitProjSpecific(ContentXElement element)
|
||||||
{
|
{
|
||||||
CreateGUI();
|
CreateGUI();
|
||||||
@@ -122,7 +128,7 @@ namespace Barotrauma.Items.Components
|
|||||||
//left column
|
//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),
|
MinSize = new Point(85, 220),
|
||||||
RelativeOffset = new Vector2(-0.02f, 0)
|
RelativeOffset = new Vector2(-0.02f, 0)
|
||||||
@@ -255,7 +261,7 @@ namespace Barotrauma.Items.Components
|
|||||||
};
|
};
|
||||||
TurbineOutputScrollBar.Frame.UserData = UIHighlightAction.ElementId.TurbineOutputSlider;
|
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,
|
Stretch = true,
|
||||||
RelativeSpacing = 0.02f
|
RelativeSpacing = 0.02f
|
||||||
@@ -436,8 +442,8 @@ namespace Barotrauma.Items.Components
|
|||||||
LocalizedString kW = TextManager.Get("kilowatt");
|
LocalizedString kW = TextManager.Get("kilowatt");
|
||||||
loadText.TextGetter += () => $"{loadStr.Replace("[kw]", ((int)Load).ToString())} {kW}";
|
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");
|
var graphFrame = 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);
|
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),
|
var outputText = new GUITextBlock(new RectTransform(relativeTextSize, graphArea.RectTransform),
|
||||||
"Output", textColor: outputColor, font: GUIStyle.SubHeadingFont, textAlignment: Alignment.CenterLeft)
|
"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}";
|
outputText.TextGetter += () => $"{outputStr.Replace("[kw]", ((int)-currPowerConsumption).ToString())} {kW}";
|
||||||
|
|
||||||
InitInventoryUI();
|
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()
|
private void InitInventoryUI()
|
||||||
@@ -469,7 +491,6 @@ namespace Barotrauma.Items.Components
|
|||||||
InitInventoryUI();
|
InitInventoryUI();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void DrawTempMeter(SpriteBatch spriteBatch, GUICustomComponent container)
|
private void DrawTempMeter(SpriteBatch spriteBatch, GUICustomComponent container)
|
||||||
{
|
{
|
||||||
Vector2 meterPos = new Vector2(container.Rect.X, container.Rect.Y);
|
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);
|
DrawGraph(loadGraph, spriteBatch, graphRect, Math.Max(10000.0f, maxLoad), xOffset, loadColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void UpdateGraph(float deltaTime)
|
private void UpdateGraph(float deltaTime)
|
||||||
{
|
{
|
||||||
graphTimer += deltaTime * 1000.0f;
|
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)
|
private void DrawMeter(SpriteBatch spriteBatch, Rectangle rect, Sprite meterSprite, float value, Vector2 range, Vector2 optimalRange, Vector2 allowedRange)
|
||||||
@@ -761,6 +787,95 @@ namespace Barotrauma.Items.Components
|
|||||||
spriteBatch.Begin(SpriteSortMode.Deferred);
|
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()
|
protected override void RemoveComponentSpecific()
|
||||||
{
|
{
|
||||||
base.RemoveComponentSpecific();
|
base.RemoveComponentSpecific();
|
||||||
|
|||||||
@@ -817,7 +817,7 @@ namespace Barotrauma.Items.Components
|
|||||||
if (distSqr > t.SoundRange * t.SoundRange * 2) { continue; }
|
if (distSqr > t.SoundRange * t.SoundRange * 2) { continue; }
|
||||||
|
|
||||||
float dist = (float)Math.Sqrt(distSqr);
|
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;
|
int prevBlipCount = sonarBlips.Count;
|
||||||
Ping(t.WorldPosition, transducerCenter,
|
Ping(t.WorldPosition, transducerCenter,
|
||||||
|
|||||||
@@ -35,6 +35,13 @@ namespace Barotrauma.Items.Components
|
|||||||
if (GuiFrame == null) { return; }
|
if (GuiFrame == null) { return; }
|
||||||
originalMaxSize = GuiFrame.RectTransform.MaxSize;
|
originalMaxSize = GuiFrame.RectTransform.MaxSize;
|
||||||
originalRelativeSize = GuiFrame.RectTransform.RelativeSize;
|
originalRelativeSize = GuiFrame.RectTransform.RelativeSize;
|
||||||
|
CreateGUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void CreateGUI()
|
||||||
|
{
|
||||||
|
if (GuiFrame == null) { return; }
|
||||||
|
|
||||||
CheckForLabelOverlap();
|
CheckForLabelOverlap();
|
||||||
var content = new GUICustomComponent(new RectTransform(Vector2.One, GuiFrame.RectTransform), DrawConnections, null)
|
var content = new GUICustomComponent(new RectTransform(Vector2.One, GuiFrame.RectTransform), DrawConnections, null)
|
||||||
{
|
{
|
||||||
@@ -44,7 +51,7 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
//prevents inputs from going through the GUICustomComponent to the drag handle
|
//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)
|
dragArea = new GUIFrame(new RectTransform(GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin, GuiFrame.RectTransform, Anchor.Center)
|
||||||
{ AbsoluteOffset = GUIStyle.ItemFrameOffset }, style: null);
|
{ AbsoluteOffset = GUIStyle.ItemFrameOffset }, style: null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void TriggerRewiringSound()
|
public void TriggerRewiringSound()
|
||||||
@@ -121,12 +128,6 @@ namespace Barotrauma.Items.Components
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnResolutionChanged()
|
|
||||||
{
|
|
||||||
if (GuiFrame == null) { return; }
|
|
||||||
CheckForLabelOverlap();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CheckForLabelOverlap()
|
private void CheckForLabelOverlap()
|
||||||
{
|
{
|
||||||
GuiFrame.RectTransform.MaxSize = originalMaxSize;
|
GuiFrame.RectTransform.MaxSize = originalMaxSize;
|
||||||
|
|||||||
@@ -324,7 +324,7 @@ namespace Barotrauma.Items.Components
|
|||||||
if (Character.Controlled != null)
|
if (Character.Controlled != null)
|
||||||
{
|
{
|
||||||
Character.Controlled.FocusedItem = null;
|
Character.Controlled.FocusedItem = null;
|
||||||
Character.Controlled.ResetInteract = true;
|
Character.Controlled.DisableInteract = true;
|
||||||
Character.Controlled.ClearInputs();
|
Character.Controlled.ClearInputs();
|
||||||
}
|
}
|
||||||
//cancel dragging
|
//cancel dragging
|
||||||
@@ -401,7 +401,7 @@ namespace Barotrauma.Items.Components
|
|||||||
{
|
{
|
||||||
if (Character.Controlled != null)
|
if (Character.Controlled != null)
|
||||||
{
|
{
|
||||||
Character.Controlled.ResetInteract = true;
|
Character.Controlled.DisableInteract = true;
|
||||||
Character.Controlled.ClearInputs();
|
Character.Controlled.ClearInputs();
|
||||||
}
|
}
|
||||||
int closestSectionIndex = selectedWire.GetClosestSectionIndex(mousePos, sectionSelectDist, out _);
|
int closestSectionIndex = selectedWire.GetClosestSectionIndex(mousePos, sectionSelectDist, out _);
|
||||||
@@ -431,7 +431,7 @@ namespace Barotrauma.Items.Components
|
|||||||
{
|
{
|
||||||
if (Character.Controlled != null)
|
if (Character.Controlled != null)
|
||||||
{
|
{
|
||||||
Character.Controlled.ResetInteract = true;
|
Character.Controlled.DisableInteract = true;
|
||||||
Character.Controlled.ClearInputs();
|
Character.Controlled.ClearInputs();
|
||||||
}
|
}
|
||||||
draggingWire = selectedWire;
|
draggingWire = selectedWire;
|
||||||
|
|||||||
@@ -1287,7 +1287,8 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
foreach (ItemComponent ic in activeHUDs)
|
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;
|
ic.GuiFrame.RectTransform.ScreenSpaceOffset = Point.Zero;
|
||||||
if (ic.UseAlternativeLayout)
|
if (ic.UseAlternativeLayout)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -73,6 +73,8 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private RichString beaconStationActiveText, beaconStationInactiveText;
|
private RichString beaconStationActiveText, beaconStationInactiveText;
|
||||||
|
|
||||||
|
private GUIComponent locationInfoOverlay;
|
||||||
|
|
||||||
/*private (Rectangle targetArea, string tip)? connectionTooltip;
|
/*private (Rectangle targetArea, string tip)? connectionTooltip;
|
||||||
private string sanitizedConnectionTooltip;
|
private string sanitizedConnectionTooltip;
|
||||||
private List<RichTextData> connectionTooltipRichTextData;
|
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()
|
partial void ClearAnimQueue()
|
||||||
{
|
{
|
||||||
mapAnimQueue.Clear();
|
mapAnimQueue.Clear();
|
||||||
@@ -425,39 +545,82 @@ namespace Barotrauma
|
|||||||
Vector2 rectCenter = new Vector2(rect.Center.X, rect.Center.Y);
|
Vector2 rectCenter = new Vector2(rect.Center.X, rect.Center.Y);
|
||||||
Vector2 viewOffset = DrawOffset + drawOffsetNoise;
|
Vector2 viewOffset = DrawOffset + drawOffsetNoise;
|
||||||
|
|
||||||
float closestDist = 0.0f;
|
|
||||||
HighlightedLocation = null;
|
bool cursorOnOverlay = false;
|
||||||
if (GUI.MouseOn == null || GUI.MouseOn == mapContainer)
|
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];
|
CreateLocationInfoOverlay(HighlightedLocation);
|
||||||
if (IsInFogOfWar(location) && !(currentDisplayLocation?.Connections.Any(c => c.Locations.Contains(location)) ?? false) && !GameMain.DebugDraw) { continue; }
|
}
|
||||||
|
|
||||||
Vector2 pos = rectCenter + (location.MapPosition + viewOffset) * zoom;
|
Point offsetFromLocationIcon = new Point(GUI.IntScale(25));
|
||||||
if (!rect.Contains(pos)) { continue; }
|
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;
|
Rectangle highlightedLocationRect = new Rectangle(highlightedLocationDrawPos.ToPoint(), new Point(GUI.IntScale(25)));
|
||||||
float iconScale = generationParams.LocationIconSize / locationSprite.size.X;
|
Rectangle overlayRect = Rectangle.Union(highlightedLocationRect, locationInfoRt.Rect);
|
||||||
if (location == currentDisplayLocation) { iconScale *= 1.2f; }
|
if (overlayRect.Contains(PlayerInput.MousePosition))
|
||||||
|
{
|
||||||
|
cursorOnOverlay = true;
|
||||||
|
}
|
||||||
|
locationInfoOverlay?.AddToGUIUpdateList(order: 1);
|
||||||
|
}
|
||||||
|
|
||||||
Rectangle drawRect = locationSprite.SourceRect;
|
if (!cursorOnOverlay)
|
||||||
drawRect.Width = (int)(drawRect.Width * iconScale * zoom * 1.4f);
|
{
|
||||||
drawRect.Height = (int)(drawRect.Height * iconScale * zoom * 1.4f);
|
float closestDist = 0.0f;
|
||||||
drawRect.X = (int)pos.X - drawRect.Width / 2;
|
HighlightedLocation = null;
|
||||||
drawRect.Y = (int)pos.Y - drawRect.Width / 2;
|
if ((GUI.MouseOn == null || GUI.MouseOn == mapContainer))
|
||||||
|
{
|
||||||
if (!drawRect.Contains(PlayerInput.MousePosition)) { continue; }
|
for (int i = 0; i < Locations.Count; i++)
|
||||||
|
|
||||||
float dist = Vector2.Distance(PlayerInput.MousePosition, pos);
|
|
||||||
if (HighlightedLocation == null || dist < closestDist)
|
|
||||||
{
|
{
|
||||||
closestDist = dist;
|
Location location = Locations[i];
|
||||||
HighlightedLocation = location;
|
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)
|
if (SelectedConnection != null)
|
||||||
{
|
{
|
||||||
connectionHighlightState = Math.Min(connectionHighlightState + deltaTime, 1.0f);
|
connectionHighlightState = Math.Min(connectionHighlightState + deltaTime, 1.0f);
|
||||||
@@ -779,106 +942,6 @@ namespace Barotrauma
|
|||||||
GUIComponent.DrawToolTip(spriteBatch, tooltip.Value.tip, tooltip.Value.targetArea);
|
GUIComponent.DrawToolTip(spriteBatch, tooltip.Value.tip, tooltip.Value.targetArea);
|
||||||
drawRadiationTooltip = false;
|
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)
|
if (drawRadiationTooltip)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1055,19 +1055,11 @@ namespace Barotrauma
|
|||||||
|
|
||||||
protected static void PositionEditingHUD()
|
protected static void PositionEditingHUD()
|
||||||
{
|
{
|
||||||
int maxHeight = 100;
|
int maxHeight =
|
||||||
if (Screen.Selected == GameMain.SubEditorScreen)
|
Screen.Selected == GameMain.SubEditorScreen ?
|
||||||
{
|
GameMain.GraphicsHeight - GameMain.SubEditorScreen.EntityMenu.Rect.Height - GameMain.SubEditorScreen.TopPanel.Rect.Bottom * 2 - 20 :
|
||||||
editingHUD.RectTransform.SetPosition(Anchor.TopRight);
|
HUDLayoutSettings.InventoryAreaLower.Y - HUDLayoutSettings.CrewArea.Bottom - 10;
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
var listBox = editingHUD.GetChild<GUIListBox>();
|
var listBox = editingHUD.GetChild<GUIListBox>();
|
||||||
if (listBox != null)
|
if (listBox != null)
|
||||||
@@ -1087,6 +1079,17 @@ namespace Barotrauma
|
|||||||
MathHelper.Clamp(contentHeight + padding * 2, 50, maxHeight)), resizeChildren: false);
|
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);
|
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) { }
|
public virtual void DrawEditing(SpriteBatch spriteBatch, Camera cam) { }
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ namespace Barotrauma.Networking
|
|||||||
{
|
{
|
||||||
partial class ChatMessage
|
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.WriteUInt16(NetStateID);
|
||||||
msg.WriteRangedInteger((int)Type, 0, Enum.GetValues(typeof(ChatMessageType)).Length - 1);
|
msg.WriteRangedInteger((int)Type, 0, Enum.GetValues(typeof(ChatMessageType)).Length - 1);
|
||||||
msg.WriteRangedInteger((int)ChatMode, 0, Enum.GetValues(typeof(ChatMode)).Length - 1);
|
msg.WriteRangedInteger((int)ChatMode, 0, Enum.GetValues(typeof(ChatMode)).Length - 1);
|
||||||
|
|||||||
@@ -485,15 +485,11 @@ namespace Barotrauma.Networking
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
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."; }
|
if (GameMain.Client == null) { errorMsg += "Client disposed."; }
|
||||||
errorMsg += "\n" + e.StackTrace.CleanupStackTrace();
|
AppendExceptionInfo(ref errorMsg, e);
|
||||||
if (e.InnerException != null)
|
|
||||||
{
|
|
||||||
errorMsg += "\nInner exception: " + e.InnerException.Message + "\n" + e.InnerException.StackTrace.CleanupStackTrace();
|
|
||||||
}
|
|
||||||
GameAnalyticsManager.AddErrorEventOnce("GameClient.Update:CheckServerMessagesException" + e.TargetSite.ToString(), GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
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())))
|
new GUIMessageBox(TextManager.Get("Error"), TextManager.GetWithVariables("MessageReadError", ("[message]", e.Message), ("[targetsite]", e.TargetSite.ToString())))
|
||||||
{
|
{
|
||||||
DisplayInLoadingScreens = true
|
DisplayInLoadingScreens = true
|
||||||
@@ -636,14 +632,8 @@ namespace Barotrauma.Networking
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
string errorMsg = "Error while reading an ingame update message from server. {" + e + "}\n" + e.StackTrace.CleanupStackTrace();
|
string errorMsg = "Error while reading an ingame update message from server.";
|
||||||
if (e.InnerException != null)
|
AppendExceptionInfo(ref errorMsg, e);
|
||||||
{
|
|
||||||
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
|
|
||||||
GameAnalyticsManager.AddErrorEventOnce("GameClient.ReadDataMessage:ReadIngameUpdate", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
GameAnalyticsManager.AddErrorEventOnce("GameClient.ReadDataMessage:ReadIngameUpdate", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
@@ -872,22 +862,31 @@ namespace Barotrauma.Networking
|
|||||||
}
|
}
|
||||||
|
|
||||||
byte missionCount = inc.ReadByte();
|
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>();
|
List<Identifier> serverMissionIdentifiers = new List<Identifier>();
|
||||||
for (int i = 0; i < missionCount; i++)
|
for (int i = 0; i < missionCount; i++)
|
||||||
{
|
{
|
||||||
serverMissionIdentifiers.Add(inc.ReadIdentifier());
|
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 (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);
|
GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:MissionsDontMatch" + Level.Loaded.Seed, GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
||||||
throw new Exception(errorMsg);
|
throw new Exception(errorMsg);
|
||||||
}
|
}
|
||||||
@@ -1883,12 +1882,11 @@ namespace Barotrauma.Networking
|
|||||||
|
|
||||||
private void ReadLobbyUpdate(IReadMessage inc)
|
private void ReadLobbyUpdate(IReadMessage inc)
|
||||||
{
|
{
|
||||||
ServerNetObject objHeader;
|
SegmentTableReader<ServerNetSegment>.Read(inc, (segment, inc) =>
|
||||||
while ((objHeader = (ServerNetObject)inc.ReadByte()) != ServerNetObject.END_OF_MESSAGE)
|
|
||||||
{
|
{
|
||||||
switch (objHeader)
|
switch (segment)
|
||||||
{
|
{
|
||||||
case ServerNetObject.SYNC_IDS:
|
case ServerNetSegment.SyncIds:
|
||||||
bool lobbyUpdated = inc.ReadBoolean();
|
bool lobbyUpdated = inc.ReadBoolean();
|
||||||
inc.ReadPadBits();
|
inc.ReadPadBits();
|
||||||
|
|
||||||
@@ -2020,17 +2018,19 @@ namespace Barotrauma.Networking
|
|||||||
|
|
||||||
lastSentChatMsgID = inc.ReadUInt16();
|
lastSentChatMsgID = inc.ReadUInt16();
|
||||||
break;
|
break;
|
||||||
case ServerNetObject.CLIENT_LIST:
|
case ServerNetSegment.ClientList:
|
||||||
ReadClientList(inc);
|
ReadClientList(inc);
|
||||||
break;
|
break;
|
||||||
case ServerNetObject.CHAT_MESSAGE:
|
case ServerNetSegment.ChatMessage:
|
||||||
ChatMessage.ClientRead(inc);
|
ChatMessage.ClientRead(inc);
|
||||||
break;
|
break;
|
||||||
case ServerNetObject.VOTE:
|
case ServerNetSegment.Vote:
|
||||||
Voting.ClientRead(inc);
|
Voting.ClientRead(inc);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
return SegmentTableReader<ServerNetSegment>.BreakSegmentReading.No;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly List<IServerSerializable> debugEntityList = new List<IServerSerializable>();
|
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;
|
float sendingTime = inc.ReadSingle() - 0.0f;//TODO: reimplement inc.SenderConnection.RemoteTimeOffset;
|
||||||
|
|
||||||
ServerNetObject? prevObjHeader = null;
|
SegmentTableReader<ServerNetSegment>.Read(inc,
|
||||||
long prevBitPos = 0;
|
segmentDataReader: (segment, inc) =>
|
||||||
long prevBytePos = 0;
|
|
||||||
|
|
||||||
long prevBitLength = 0;
|
|
||||||
long prevByteLength = 0;
|
|
||||||
|
|
||||||
ServerNetObject? objHeader = null;
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
while ((objHeader = (ServerNetObject)inc.ReadByte()) != ServerNetObject.END_OF_MESSAGE)
|
switch (segment)
|
||||||
{
|
{
|
||||||
switch (objHeader)
|
case ServerNetSegment.SyncIds:
|
||||||
{
|
lastSentChatMsgID = inc.ReadUInt16();
|
||||||
case ServerNetObject.SYNC_IDS:
|
LastSentEntityEventID = inc.ReadUInt16();
|
||||||
lastSentChatMsgID = inc.ReadUInt16();
|
|
||||||
LastSentEntityEventID = inc.ReadUInt16();
|
|
||||||
|
|
||||||
bool campaignUpdated = inc.ReadBoolean();
|
bool campaignUpdated = inc.ReadBoolean();
|
||||||
inc.ReadPadBits();
|
inc.ReadPadBits();
|
||||||
if (campaignUpdated)
|
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 SegmentTableReader<ServerNetSegment>.BreakSegmentReading.Yes;
|
||||||
|
}
|
||||||
|
|
||||||
|
debugEntityList.Add(entity);
|
||||||
|
if (entity != null)
|
||||||
|
{
|
||||||
|
if (entity is Item != isItem)
|
||||||
{
|
{
|
||||||
MultiPlayerCampaign.ClientRead(inc);
|
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 (GameMain.NetLobbyScreen.SelectedMode != GameModePreset.MultiPlayerCampaign)
|
else if (entity is MapEntity { Prefab: { UintIdentifier: { } uintIdentifier } } me &&
|
||||||
|
uintIdentifier != incomingUintIdentifier)
|
||||||
{
|
{
|
||||||
GameMain.NetLobbyScreen.SetCampaignCharacterInfo(null);
|
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;
|
else
|
||||||
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);
|
|
||||||
|
|
||||||
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.");
|
entity.ClientReadPosition(inc, sendingTime);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
debugEntityList.Add(entity);
|
//force to the correct position in case the entity doesn't exist
|
||||||
if (entity != null)
|
//or the message wasn't read correctly for whatever reason
|
||||||
{
|
inc.BitPosition = msgEndPos;
|
||||||
if (entity is Item != isItem)
|
inc.ReadPadBits();
|
||||||
{
|
break;
|
||||||
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...");
|
case ServerNetSegment.ClientList:
|
||||||
}
|
ReadClientList(inc);
|
||||||
else if (entity is MapEntity { Prefab: { UintIdentifier: { } uintIdentifier } } me &&
|
break;
|
||||||
uintIdentifier != incomingUintIdentifier)
|
case ServerNetSegment.EntityEvent:
|
||||||
{
|
case ServerNetSegment.EntityEventInitial:
|
||||||
DebugConsole.AddWarning($"Received a potentially invalid ENTITY_POSITION message."
|
if (!EntityEventManager.Read(segment, inc, sendingTime, debugEntityList))
|
||||||
+$"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...");
|
return SegmentTableReader<ServerNetSegment>.BreakSegmentReading.Yes;
|
||||||
}
|
}
|
||||||
else
|
break;
|
||||||
{
|
case ServerNetSegment.ChatMessage:
|
||||||
entity.ClientReadPosition(inc, sendingTime);
|
ChatMessage.ClientRead(inc);
|
||||||
}
|
break;
|
||||||
}
|
default:
|
||||||
|
throw new Exception($"Unknown segment \"{segment}\"!)");
|
||||||
//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))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
|
|
||||||
prevObjHeader = objHeader;
|
|
||||||
prevBitPos = inc.BitPosition;
|
|
||||||
prevBytePos = inc.BytePosition;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
catch (Exception ex)
|
return SegmentTableReader<ServerNetSegment>.BreakSegmentReading.No;
|
||||||
|
},
|
||||||
|
exceptionHandler: (segment, prevSegments, ex) =>
|
||||||
{
|
{
|
||||||
List<string> errorLines = new List<string>
|
List<string> errorLines = new List<string>
|
||||||
{
|
{
|
||||||
ex.Message,
|
ex.Message,
|
||||||
"Message length: " + inc.LengthBits + " (" + inc.LengthBytes + " bytes)",
|
"Message length: " + inc.LengthBits + " (" + inc.LengthBytes + " bytes)",
|
||||||
"Read position: " + inc.BitPosition,
|
"Read position: " + inc.BitPosition,
|
||||||
"Header: " + (objHeader != null ? objHeader.Value.ToString() : "Error occurred on the very first header!"),
|
$"Segment with error: {segment}"
|
||||||
prevObjHeader != null ? "Previous header: " + prevObjHeader : "Error occurred on the very first header!",
|
|
||||||
"Previous object was " + (prevBitLength) + " bits long (" + (prevByteLength) + " bytes)",
|
|
||||||
" "
|
|
||||||
};
|
};
|
||||||
|
if (prevSegments.Any())
|
||||||
|
{
|
||||||
|
errorLines.Add("Prev segments: " + string.Join(", ", prevSegments));
|
||||||
|
errorLines.Add(" ");
|
||||||
|
}
|
||||||
errorLines.Add(ex.StackTrace.CleanupStackTrace());
|
errorLines.Add(ex.StackTrace.CleanupStackTrace());
|
||||||
errorLines.Add(" ");
|
errorLines.Add(" ");
|
||||||
if (prevObjHeader == ServerNetObject.ENTITY_EVENT || prevObjHeader == ServerNetObject.ENTITY_EVENT_INITIAL ||
|
if (prevSegments.Concat(segment.ToEnumerable()).Any(s => s.Identifier
|
||||||
objHeader == ServerNetObject.ENTITY_EVENT || objHeader == ServerNetObject.ENTITY_EVENT_INITIAL ||
|
is ServerNetSegment.EntityPosition
|
||||||
objHeader == ServerNetObject.ENTITY_POSITION || prevObjHeader == ServerNetObject.ENTITY_POSITION)
|
or ServerNetSegment.EntityEvent
|
||||||
|
or ServerNetSegment.EntityEventInitial))
|
||||||
{
|
{
|
||||||
foreach (IServerSerializable ent in debugEntityList)
|
foreach (IServerSerializable ent in debugEntityList)
|
||||||
{
|
{
|
||||||
@@ -2164,10 +2153,6 @@ namespace Barotrauma.Networking
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (string line in errorLines)
|
|
||||||
{
|
|
||||||
DebugConsole.ThrowError(line);
|
|
||||||
}
|
|
||||||
errorLines.Add("Last console messages:");
|
errorLines.Add("Last console messages:");
|
||||||
for (int i = DebugConsole.Messages.Count - 1; i > Math.Max(0, DebugConsole.Messages.Count - 20); i--)
|
for (int i = DebugConsole.Messages.Count - 1; i > Math.Max(0, DebugConsole.Messages.Count - 20); i--)
|
||||||
{
|
{
|
||||||
@@ -2175,23 +2160,11 @@ namespace Barotrauma.Networking
|
|||||||
}
|
}
|
||||||
GameAnalyticsManager.AddErrorEventOnce("GameClient.ReadInGameUpdate", GameAnalyticsManager.ErrorSeverity.Critical, string.Join("\n", errorLines));
|
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");
|
throw new Exception(
|
||||||
|
$"Exception thrown while reading segment {segment.Identifier} at position {segment.Pointer}." +
|
||||||
using (FileStream fl = File.Open("networkerror_data.log", System.IO.FileMode.Create))
|
(prevSegments.Any() ? $" Previous segments: {string.Join(", ", prevSegments)}" : ""),
|
||||||
{
|
ex);
|
||||||
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\"!");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SendLobbyUpdate()
|
private void SendLobbyUpdate()
|
||||||
@@ -2199,50 +2172,51 @@ namespace Barotrauma.Networking
|
|||||||
IWriteMessage outmsg = new WriteOnlyMessage();
|
IWriteMessage outmsg = new WriteOnlyMessage();
|
||||||
outmsg.WriteByte((byte)ClientPacketHeader.UPDATE_LOBBY);
|
outmsg.WriteByte((byte)ClientPacketHeader.UPDATE_LOBBY);
|
||||||
|
|
||||||
outmsg.WriteByte((byte)ClientNetObject.SYNC_IDS);
|
using (var segmentTable = SegmentTableWriter<ClientNetSegment>.StartWriting(outmsg))
|
||||||
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.WriteIdentifier(jobPreferences[0].Prefab.Identifier);
|
segmentTable.StartNewSegment(ClientNetSegment.SyncIds);
|
||||||
}
|
outmsg.WriteUInt16(GameMain.NetLobbyScreen.LastUpdateID);
|
||||||
else
|
outmsg.WriteUInt16(ChatMessage.LastID);
|
||||||
{
|
outmsg.WriteUInt16(LastClientListUpdateID);
|
||||||
outmsg.WriteIdentifier(Identifier.Empty);
|
outmsg.WriteUInt16(nameId);
|
||||||
}
|
outmsg.WriteString(Name);
|
||||||
outmsg.WriteByte((byte)MultiplayerPreferences.Instance.TeamPreference);
|
var jobPreferences = GameMain.NetLobbyScreen.JobPreferences;
|
||||||
|
if (jobPreferences.Count > 0)
|
||||||
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.WriteIdentifier(jobPreferences[0].Prefab.Identifier);
|
||||||
}
|
}
|
||||||
outmsg.WriteBoolean(GameMain.NetLobbyScreen.CampaignCharacterDiscarded);
|
else
|
||||||
}
|
|
||||||
|
|
||||||
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
|
outmsg.WriteIdentifier(Identifier.Empty);
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
chatMsgQueue[i].ClientWrite(outmsg);
|
outmsg.WriteByte((byte)MultiplayerPreferences.Instance.TeamPreference);
|
||||||
}
|
|
||||||
outmsg.WriteByte((byte)ClientNetObject.END_OF_MESSAGE);
|
|
||||||
|
|
||||||
|
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)
|
if (outmsg.LengthBytes > MsgConstants.MTU)
|
||||||
{
|
{
|
||||||
DebugConsole.ThrowError($"Maximum packet size exceeded ({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.WriteBoolean(EntityEventManager.MidRoundSyncingDone);
|
||||||
outmsg.WritePadBits();
|
outmsg.WritePadBits();
|
||||||
|
|
||||||
outmsg.WriteByte((byte)ClientNetObject.SYNC_IDS);
|
using (var segmentTable = SegmentTableWriter<ClientNetSegment>.StartWriting(outmsg))
|
||||||
//outmsg.Write(GameMain.NetLobbyScreen.LastUpdateID);
|
{
|
||||||
outmsg.WriteUInt16(ChatMessage.LastID);
|
segmentTable.StartNewSegment(ClientNetSegment.SyncIds);
|
||||||
outmsg.WriteUInt16(EntityEventManager.LastReceivedID);
|
//outmsg.Write(GameMain.NetLobbyScreen.LastUpdateID);
|
||||||
outmsg.WriteUInt16(LastClientListUpdateID);
|
outmsg.WriteUInt16(ChatMessage.LastID);
|
||||||
|
outmsg.WriteUInt16(EntityEventManager.LastReceivedID);
|
||||||
|
outmsg.WriteUInt16(LastClientListUpdateID);
|
||||||
|
|
||||||
if (!(GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign) || campaign.LastSaveID == 0)
|
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)))
|
|
||||||
{
|
{
|
||||||
outmsg.WriteUInt16(campaign.GetLastUpdateIdForFlag(flag));
|
outmsg.WriteUInt16((UInt16)0);
|
||||||
}
|
}
|
||||||
outmsg.WriteBoolean(GameMain.NetLobbyScreen.CampaignCharacterDiscarded);
|
else
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
//not enough room in this packet
|
outmsg.WriteUInt16(campaign.LastSaveID);
|
||||||
break;
|
outmsg.WriteByte(campaign.CampaignID);
|
||||||
}
|
foreach (MultiPlayerCampaign.NetFlags flag in Enum.GetValues(typeof(MultiPlayerCampaign.NetFlags)))
|
||||||
chatMsgQueue[i].ClientWrite(outmsg);
|
{
|
||||||
}
|
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)
|
if (outmsg.LengthBytes > MsgConstants.MTU)
|
||||||
{
|
{
|
||||||
@@ -2608,7 +2585,6 @@ namespace Barotrauma.Networking
|
|||||||
IWriteMessage msg = new WriteOnlyMessage();
|
IWriteMessage msg = new WriteOnlyMessage();
|
||||||
msg.WriteByte((byte)ClientPacketHeader.UPDATE_CHARACTERINFO);
|
msg.WriteByte((byte)ClientPacketHeader.UPDATE_CHARACTERINFO);
|
||||||
WriteCharacterInfo(msg, newName);
|
WriteCharacterInfo(msg, newName);
|
||||||
msg.WriteByte((byte)ServerNetObject.END_OF_MESSAGE);
|
|
||||||
ClientPeer?.Send(msg, DeliveryMethod.Reliable);
|
ClientPeer?.Send(msg, DeliveryMethod.Reliable);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2648,9 +2624,11 @@ namespace Barotrauma.Networking
|
|||||||
|
|
||||||
IWriteMessage msg = new WriteOnlyMessage();
|
IWriteMessage msg = new WriteOnlyMessage();
|
||||||
msg.WriteByte((byte)ClientPacketHeader.UPDATE_LOBBY);
|
msg.WriteByte((byte)ClientPacketHeader.UPDATE_LOBBY);
|
||||||
msg.WriteByte((byte)ClientNetObject.VOTE);
|
using (var segmentTable = SegmentTableWriter<ClientNetSegment>.StartWriting(msg))
|
||||||
Voting.ClientWrite(msg, voteType, data);
|
{
|
||||||
msg.WriteByte((byte)ServerNetObject.END_OF_MESSAGE);
|
segmentTable.StartNewSegment(ClientNetSegment.Vote);
|
||||||
|
Voting.ClientWrite(msg, voteType, data);
|
||||||
|
}
|
||||||
|
|
||||||
ClientPeer.Send(msg, DeliveryMethod.Reliable);
|
ClientPeer.Send(msg, DeliveryMethod.Reliable);
|
||||||
}
|
}
|
||||||
@@ -2763,7 +2741,6 @@ namespace Barotrauma.Networking
|
|||||||
msg.WriteByte((byte)ClientPacketHeader.SERVER_COMMAND);
|
msg.WriteByte((byte)ClientPacketHeader.SERVER_COMMAND);
|
||||||
msg.WriteUInt16((UInt16)ClientPermissions.ManageCampaign);
|
msg.WriteUInt16((UInt16)ClientPermissions.ManageCampaign);
|
||||||
campaign.ClientWrite(msg);
|
campaign.ClientWrite(msg);
|
||||||
msg.WriteByte((byte)ServerNetObject.END_OF_MESSAGE);
|
|
||||||
ClientPeer.Send(msg, DeliveryMethod.Reliable);
|
ClientPeer.Send(msg, DeliveryMethod.Reliable);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2812,7 +2789,6 @@ namespace Barotrauma.Networking
|
|||||||
msg.WriteUInt16((UInt16)ClientPermissions.SelectSub);
|
msg.WriteUInt16((UInt16)ClientPermissions.SelectSub);
|
||||||
msg.WriteBoolean(isShuttle); msg.WritePadBits();
|
msg.WriteBoolean(isShuttle); msg.WritePadBits();
|
||||||
msg.WriteString(sub.MD5Hash.StringRepresentation);
|
msg.WriteString(sub.MD5Hash.StringRepresentation);
|
||||||
msg.WriteByte((byte)ServerNetObject.END_OF_MESSAGE);
|
|
||||||
ClientPeer.Send(msg, DeliveryMethod.Reliable);
|
ClientPeer.Send(msg, DeliveryMethod.Reliable);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2832,7 +2808,6 @@ namespace Barotrauma.Networking
|
|||||||
msg.WriteByte((byte)ClientPacketHeader.SERVER_COMMAND);
|
msg.WriteByte((byte)ClientPacketHeader.SERVER_COMMAND);
|
||||||
msg.WriteUInt16((UInt16)ClientPermissions.SelectMode);
|
msg.WriteUInt16((UInt16)ClientPermissions.SelectMode);
|
||||||
msg.WriteUInt16((UInt16)modeIndex);
|
msg.WriteUInt16((UInt16)modeIndex);
|
||||||
msg.WriteByte((byte)ServerNetObject.END_OF_MESSAGE);
|
|
||||||
|
|
||||||
ClientPeer.Send(msg, DeliveryMethod.Reliable);
|
ClientPeer.Send(msg, DeliveryMethod.Reliable);
|
||||||
}
|
}
|
||||||
@@ -3538,6 +3513,23 @@ namespace Barotrauma.Networking
|
|||||||
eventErrorWritten = true;
|
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
|
#if DEBUG
|
||||||
public void ForceTimeOut()
|
public void ForceTimeOut()
|
||||||
{
|
{
|
||||||
|
|||||||
+15
-38
@@ -66,7 +66,7 @@ namespace Barotrauma.Networking
|
|||||||
events.Add(newEvent);
|
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;
|
if (events.Count == 0 || serverConnection == null) return;
|
||||||
|
|
||||||
@@ -103,7 +103,7 @@ namespace Barotrauma.Networking
|
|||||||
eventLastSent[entityEvent.ID] = (float)Lidgren.Network.NetTime.Now;
|
eventLastSent[entityEvent.ID] = (float)Lidgren.Network.NetTime.Now;
|
||||||
}
|
}
|
||||||
|
|
||||||
msg.WriteByte((byte)ClientNetObject.ENTITY_STATE);
|
segmentTable.StartNewSegment(ClientNetSegment.EntityState);
|
||||||
Write(msg, eventsToSync, out _);
|
Write(msg, eventsToSync, out _);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,11 +112,11 @@ namespace Barotrauma.Networking
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Read the events from the message, ignoring ones we've already received. Returns false if reading the events fails.
|
/// Read the events from the message, ignoring ones we've already received. Returns false if reading the events fails.
|
||||||
/// </summary>
|
/// </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;
|
UInt16 unreceivedEntityEventCount = 0;
|
||||||
|
|
||||||
if (type == ServerNetObject.ENTITY_EVENT_INITIAL)
|
if (type == ServerNetSegment.EntityEventInitial)
|
||||||
{
|
{
|
||||||
unreceivedEntityEventCount = msg.ReadUInt16();
|
unreceivedEntityEventCount = msg.ReadUInt16();
|
||||||
firstNewID = msg.ReadUInt16();
|
firstNewID = msg.ReadUInt16();
|
||||||
@@ -218,43 +218,20 @@ namespace Barotrauma.Networking
|
|||||||
Microsoft.Xna.Framework.Color.Green);
|
Microsoft.Xna.Framework.Color.Green);
|
||||||
}
|
}
|
||||||
lastReceivedID++;
|
lastReceivedID++;
|
||||||
try
|
ReadEvent(msg, entity, sendingTime);
|
||||||
|
msg.ReadPadBits();
|
||||||
|
|
||||||
|
if (msg.BitPosition != msgPosition + msgLength * 8)
|
||||||
{
|
{
|
||||||
ReadEvent(msg, entity, sendingTime);
|
var prevEntity = entities.Count >= 2 ? entities[entities.Count - 2] : null;
|
||||||
msg.ReadPadBits();
|
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)
|
GameAnalyticsManager.AddErrorEventOnce("ClientEntityEventManager.Read:BitPosMismatch", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
||||||
{
|
|
||||||
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);
|
throw new Exception(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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ namespace Barotrauma.Networking
|
|||||||
{
|
{
|
||||||
partial class OrderChatMessage : ChatMessage
|
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.WriteUInt16(NetStateID);
|
||||||
msg.WriteRangedInteger((int)ChatMessageType.Order, 0, Enum.GetValues(typeof(ChatMessageType)).Length - 1);
|
msg.WriteRangedInteger((int)ChatMessageType.Order, 0, Enum.GetValues(typeof(ChatMessageType)).Length - 1);
|
||||||
msg.WriteRangedInteger((int)ChatMode.None, 0, Enum.GetValues(typeof(ChatMode)).Length - 1);
|
msg.WriteRangedInteger((int)ChatMode.None, 0, Enum.GetValues(typeof(ChatMode)).Length - 1);
|
||||||
|
|||||||
+8
-2
@@ -1766,9 +1766,15 @@ namespace Barotrauma.CharacterEditor
|
|||||||
var modProject = new ModProject(contentPackage);
|
var modProject = new ModProject(contentPackage);
|
||||||
var newFile = ModProject.File.FromPath<CharacterFile>(configFilePath);
|
var newFile = ModProject.File.FromPath<CharacterFile>(configFilePath);
|
||||||
modProject.AddFile(newFile);
|
modProject.AddFile(newFile);
|
||||||
|
|
||||||
modProject.Save(contentPackage.Path);
|
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));
|
DebugConsole.NewMessage(GetCharacterEditorTranslation("ContentPackageSaved").Replace("[path]", contentPackage.Path));
|
||||||
|
|
||||||
|
|||||||
@@ -956,16 +956,21 @@ namespace Barotrauma
|
|||||||
backgroundSprite = new Sprite("Content/UnstableBackground.png", sourceRectangle: null);
|
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)
|
if (vignette != null)
|
||||||
{
|
{
|
||||||
vignette.Draw(spriteBatch, Vector2.Zero, Color.White, Vector2.Zero, 0.0f,
|
vignette.Draw(spriteBatch, Vector2.Zero, Color.White, Vector2.Zero, 0.0f, vignetteScale);
|
||||||
new Vector2(Math.Min(GameMain.GraphicsWidth / vignette.size.X, GameMain.GraphicsHeight / vignette.size.Y)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#nullable enable
|
#nullable enable
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Barotrauma.Extensions;
|
using Barotrauma.Extensions;
|
||||||
using Barotrauma.IO;
|
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()
|
public override void Select()
|
||||||
{
|
{
|
||||||
base.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)
|
LogAndThrowException("Error in ModDownloadScreen: the list of mods the server has enabled was empty. "
|
||||||
{
|
+$"Content package list received: {GameMain.Client.ClientPeer.ContentPackageOrderReceived}",
|
||||||
string errorMsg = $"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");
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var missingPackages = GameMain.Client.ClientPeer.ServerContentPackages
|
var missingPackages = GameMain.Client.ClientPeer.ServerContentPackages
|
||||||
@@ -96,11 +95,18 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (!GameMain.Client.IsServerOwner)
|
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.BackUp();
|
||||||
ContentPackageManager.EnabledPackages.SetCore(
|
ContentPackageManager.EnabledPackages.SetCore(corePackage);
|
||||||
GameMain.Client.ClientPeer.ServerContentPackages
|
|
||||||
.Select(p => p.CorePackage)
|
|
||||||
.OfType<CorePackage>().First());
|
|
||||||
List<RegularPackage> regularPackages =
|
List<RegularPackage> regularPackages =
|
||||||
GameMain.Client.ClientPeer.ServerContentPackages
|
GameMain.Client.ClientPeer.ServerContentPackages
|
||||||
.Select(p => p.RegularPackage)
|
.Select(p => p.RegularPackage)
|
||||||
@@ -113,6 +119,15 @@ namespace Barotrauma
|
|||||||
return;
|
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(
|
GUIMessageBox msgBox = new GUIMessageBox(
|
||||||
TextManager.Get("ModDownloadTitle"),
|
TextManager.Get("ModDownloadTitle"),
|
||||||
"",
|
"",
|
||||||
@@ -291,14 +306,23 @@ namespace Barotrauma
|
|||||||
var serverPackages = GameMain.Client.ClientPeer.ServerContentPackages;
|
var serverPackages = GameMain.Client.ClientPeer.ServerContentPackages;
|
||||||
CorePackage corePackage
|
CorePackage corePackage
|
||||||
= downloadedPackages.FirstOrDefault(p => p is CorePackage) as CorePackage
|
= downloadedPackages.FirstOrDefault(p => p is CorePackage) as CorePackage
|
||||||
?? serverPackages.FirstOrDefault(p => p.CorePackage != null)
|
?? serverPackages.FirstOrDefault(p => p.CorePackage != null)?.CorePackage
|
||||||
?.CorePackage
|
|
||||||
?? throw new Exception($"Failed to find core package to enable");
|
?? throw new Exception($"Failed to find core package to enable");
|
||||||
|
|
||||||
List<RegularPackage> regularPackages = new List<RegularPackage>();
|
List<RegularPackage> regularPackages = new List<RegularPackage>();
|
||||||
foreach (var p in serverPackages)
|
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 =
|
RegularPackage? matchingPackage =
|
||||||
p.RegularPackage ?? downloadedPackages.FirstOrDefault(d => d is RegularPackage && d.Hash.Equals(p.Hash)) as RegularPackage;
|
p.RegularPackage ?? downloadedPackages.FirstOrDefault(d => d is RegularPackage && d.Hash.Equals(p.Hash)) as RegularPackage;
|
||||||
if (matchingPackage is null)
|
if (matchingPackage is null)
|
||||||
@@ -355,9 +379,13 @@ namespace Barotrauma
|
|||||||
string dir = path.RemoveFromEnd(ModReceiver.Extension, StringComparison.OrdinalIgnoreCase);
|
string dir = path.RemoveFromEnd(ModReceiver.Extension, StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
SaveUtil.DecompressToDirectory(path, dir, file => { });
|
SaveUtil.DecompressToDirectory(path, dir, file => { });
|
||||||
ContentPackage newPackage
|
var result = ContentPackage.TryLoad(Path.Combine(dir, ContentPackage.FileListFileName));
|
||||||
= ContentPackage.TryLoad($"{dir}/{ContentPackage.FileListFileName}")
|
|
||||||
?? throw new Exception($"Failed to load downloaded mod \"{currentDownload.Name}\"");
|
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))
|
if (!currentDownload.Hash.Equals(newPackage.Hash))
|
||||||
{
|
{
|
||||||
throw new Exception($"Hash mismatch for downloaded mod \"{currentDownload.Name}\" (expected {currentDownload.Hash}, got {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)
|
public override void Draw(double deltaTime, GraphicsDevice graphics, SpriteBatch spriteBatch)
|
||||||
{
|
{
|
||||||
|
if (backgroundSprite?.Texture == null) { return; }
|
||||||
graphics.Clear(Color.Black);
|
graphics.Clear(Color.Black);
|
||||||
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
|
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
|
||||||
GUI.DrawBackgroundSprite(spriteBatch, backgroundSprite, Color.White);
|
GUI.DrawBackgroundSprite(spriteBatch, backgroundSprite, Color.White);
|
||||||
|
|||||||
@@ -1352,6 +1352,10 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private void AddToServerList(ServerInfo serverInfo, bool skipPing = false)
|
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.RefreshingServerList);
|
||||||
RemoveMsgFromServerList(MsgUserData.NoServers);
|
RemoveMsgFromServerList(MsgUserData.NoServers);
|
||||||
var serverFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.06f), serverList.Content.RectTransform) { MinSize = new Point(0, 35) },
|
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)
|
void DrawOverlay(Sprite sprite, Color color)
|
||||||
{
|
{
|
||||||
|
if (sprite.Texture == null) { return; }
|
||||||
GUI.DrawBackgroundSprite(spriteBatch, sprite, color);
|
GUI.DrawBackgroundSprite(spriteBatch, sprite, color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3087,10 +3087,16 @@ namespace Barotrauma
|
|||||||
XDocument doc = new XDocument(ItemAssemblyPrefab.Save(MapEntity.SelectedList.ToList(), nameBox.Text, descriptionBox.Text, hideInMenus));
|
XDocument doc = new XDocument(ItemAssemblyPrefab.Save(MapEntity.SelectedList.ToList(), nameBox.Text, descriptionBox.Text, hideInMenus));
|
||||||
doc.SaveSafe(filePath);
|
doc.SaveSafe(filePath);
|
||||||
|
|
||||||
var resultPackage = ContentPackageManager.ReloadContentPackage(existingContentPackage) as RegularPackage;
|
var result = ContentPackageManager.ReloadContentPackage(existingContentPackage);
|
||||||
if (!ContentPackageManager.EnabledPackages.Regular.Contains(resultPackage))
|
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();
|
GameSettings.SaveCurrentConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -518,6 +518,11 @@ namespace Barotrauma
|
|||||||
if (musicDisposed) { Thread.Sleep(60); }
|
if (musicDisposed) { Thread.Sleep(60); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void ForceMusicUpdate()
|
||||||
|
{
|
||||||
|
updateMusicTimer = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
private static void UpdateMusic(float deltaTime)
|
private static void UpdateMusic(float deltaTime)
|
||||||
{
|
{
|
||||||
if (musicClips == null || (GameMain.SoundManager?.Disabled ?? true)) { return; }
|
if (musicClips == null || (GameMain.SoundManager?.Disabled ?? true)) { return; }
|
||||||
@@ -544,7 +549,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
IEnumerable<BackgroundMusic> suitableMusic = GetSuitableMusicClips(currentMusicType, currentIntensity);
|
IEnumerable<BackgroundMusic> suitableMusic = GetSuitableMusicClips(currentMusicType, currentIntensity);
|
||||||
int mainTrackIndex = 0;
|
int mainTrackIndex = 0;
|
||||||
if (suitableMusic.Count() == 0)
|
if (suitableMusic.None())
|
||||||
{
|
{
|
||||||
targetMusic[mainTrackIndex] = null;
|
targetMusic[mainTrackIndex] = null;
|
||||||
}
|
}
|
||||||
@@ -611,10 +616,17 @@ namespace Barotrauma
|
|||||||
targetMusic[typeAmbienceTrackIndex] = suitableTypeAmbiences.GetRandomUnsynced();
|
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
|
//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;
|
int intensityTrackStartIndex = 3;
|
||||||
for (int i = intensityTrackStartIndex; i < MaxMusicChannels; i++)
|
for (int i = intensityTrackStartIndex; i < MaxMusicChannels; i++)
|
||||||
{
|
{
|
||||||
@@ -744,6 +756,14 @@ namespace Barotrauma
|
|||||||
|
|
||||||
firstTimeInMainMenu = false;
|
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)
|
if (Character.Controlled != null)
|
||||||
{
|
{
|
||||||
@@ -833,7 +853,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
return "levelend".ToIdentifier();
|
return "levelend".ToIdentifier();
|
||||||
}
|
}
|
||||||
if (Timing.TotalTime < GameMain.GameSession.RoundStartTime + 120.0 &&
|
if (GameMain.GameSession.RoundDuration > 120.0 &&
|
||||||
Level.Loaded?.Type == LevelData.LevelType.LocationConnection)
|
Level.Loaded?.Type == LevelData.LevelType.LocationConnection)
|
||||||
{
|
{
|
||||||
return "start".ToIdentifier();
|
return "start".ToIdentifier();
|
||||||
|
|||||||
@@ -238,17 +238,24 @@ namespace Barotrauma
|
|||||||
public readonly float Volume;
|
public readonly float Volume;
|
||||||
|
|
||||||
public readonly Vector2 IntensityRange;
|
public readonly Vector2 IntensityRange;
|
||||||
|
public readonly bool MuteIntensityTracks;
|
||||||
|
public readonly float? ForceIntensityTrack;
|
||||||
|
|
||||||
public readonly bool ContinueFromPreviousTime;
|
public readonly bool ContinueFromPreviousTime;
|
||||||
public int PreviousTime;
|
public int PreviousTime;
|
||||||
|
|
||||||
public BackgroundMusic(ContentXElement element, SoundsFile file) : base(element, file, stream: true)
|
public BackgroundMusic(ContentXElement element, SoundsFile file) : base(element, file, stream: true)
|
||||||
{
|
{
|
||||||
Type = element.GetAttributeIdentifier("type", "");
|
Type = element.GetAttributeIdentifier(nameof(Type), "");
|
||||||
IntensityRange = element.GetAttributeVector2("intensityrange", new Vector2(0.0f, 100.0f));
|
IntensityRange = element.GetAttributeVector2(nameof(IntensityRange), new Vector2(0.0f, 100.0f));
|
||||||
DuckVolume = element.GetAttributeBool("duckvolume", false);
|
DuckVolume = element.GetAttributeBool(nameof(DuckVolume), false);
|
||||||
this.Volume = element.GetAttributeFloat("volume", 1.0f);
|
MuteIntensityTracks = element.GetAttributeBool(nameof(MuteIntensityTracks), false);
|
||||||
ContinueFromPreviousTime = element.GetAttributeBool("continuefromprevioustime", 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
|
//This callback seems to take place when the item has been downloaded recently and an update
|
||||||
//or a redownload has taken place
|
//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!
|
//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);
|
await CopyDirectory(contentPackage.Dir, contentPackage.Name, Path.GetDirectoryName(contentPackage.Path)!, PublishStagingDir, ShouldCorrectPaths.No);
|
||||||
|
|
||||||
var stagingFileListPath = Path.Combine(PublishStagingDir, ContentPackage.FileListFileName);
|
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
|
//Load filelist.xml and write the hash into it so anyone downloading this mod knows what it should be
|
||||||
ModProject modProject = new ModProject(tempPkg)
|
ModProject modProject = new ModProject(tempPkg)
|
||||||
|
|||||||
+1
-1
@@ -40,7 +40,7 @@ namespace Barotrauma.Steam
|
|||||||
CanBeFocused = false,
|
CanBeFocused = false,
|
||||||
UserData = p
|
UserData = p
|
||||||
};
|
};
|
||||||
if (p.Errors.Any())
|
if (p.FatalLoadErrors.Any())
|
||||||
{
|
{
|
||||||
CreateModErrorInfo(p, regularBox, regularBox);
|
CreateModErrorInfo(p, regularBox, regularBox);
|
||||||
regularBox.CanBeFocused = true;
|
regularBox.CanBeFocused = true;
|
||||||
|
|||||||
+20
-20
@@ -50,20 +50,9 @@ namespace Barotrauma.Steam
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TaskPool.Add("RemoveUnsubscribedItems", SteamManager.Workshop.GetPublishedItems(), t =>
|
SteamManager.Workshop.DeleteUnsubscribedMods(removedPackages =>
|
||||||
{
|
{
|
||||||
if (!t.TryGetResult(out ISet<Steamworks.Ugc.Item> publishedItems)) { return; }
|
if (removedPackages.Any())
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
PopulateInstalledModLists();
|
PopulateInstalledModLists();
|
||||||
}
|
}
|
||||||
@@ -487,8 +476,9 @@ namespace Barotrauma.Steam
|
|||||||
{
|
{
|
||||||
string str = modsListFilter.Text;
|
string str = modsListFilter.Text;
|
||||||
enabledRegularModsList.Content.Children.Concat(disabledRegularModsList.Content.Children)
|
enabledRegularModsList.Content.Children.Concat(disabledRegularModsList.Content.Children)
|
||||||
.ForEach(c => c.Visible = c.UserData is not ContentPackage p
|
.ForEach(c
|
||||||
|| ModNameMatches(p, str) && ModMatchesTickboxes(p, c));
|
=> c.Visible = c.UserData is not ContentPackage p
|
||||||
|
|| ModNameMatches(p, str) && ModMatchesTickboxes(p, c));
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool ModMatchesTickboxes(ContentPackage p, GUIComponent guiItem)
|
private bool ModMatchesTickboxes(ContentPackage p, GUIComponent guiItem)
|
||||||
@@ -550,7 +540,20 @@ namespace Barotrauma.Steam
|
|||||||
(p) => p.Name,
|
(p) => p.Name,
|
||||||
ContentPackageManager.CorePackages.ToArray(),
|
ContentPackageManager.CorePackages.ToArray(),
|
||||||
ContentPackageManager.EnabledPackages.Core!,
|
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)
|
void addRegularModToList(RegularPackage mod, GUIListBox list)
|
||||||
{
|
{
|
||||||
@@ -658,10 +661,7 @@ namespace Barotrauma.Steam
|
|||||||
{
|
{
|
||||||
CanBeFocused = false
|
CanBeFocused = false
|
||||||
};
|
};
|
||||||
if (mod.Errors.Any())
|
CreateModErrorInfo(mod, modFrame, modName);
|
||||||
{
|
|
||||||
CreateModErrorInfo(mod, modFrame, modName);
|
|
||||||
}
|
|
||||||
if (ContentPackageManager.LocalPackages.Contains(mod))
|
if (ContentPackageManager.LocalPackages.Contains(mod))
|
||||||
{
|
{
|
||||||
var editButton = new GUIButton(new RectTransform(Vector2.One, frameContent.RectTransform, scaleBasis: ScaleBasis.Smallest), "",
|
var editButton = new GUIButton(new RectTransform(Vector2.One, frameContent.RectTransform, scaleBasis: ScaleBasis.Smallest), "",
|
||||||
|
|||||||
+4
@@ -165,6 +165,10 @@ namespace Barotrauma.Steam
|
|||||||
.Select(c => c.UserData as RegularPackage).OfType<RegularPackage>().ToArray());
|
.Select(c => c.UserData as RegularPackage).OfType<RegularPackage>().ToArray());
|
||||||
PopulateInstalledModLists(forceRefreshEnabled: true, refreshDisabled: true);
|
PopulateInstalledModLists(forceRefreshEnabled: true, refreshDisabled: true);
|
||||||
ContentPackageManager.LogEnabledRegularPackageErrors();
|
ContentPackageManager.LogEnabledRegularPackageErrors();
|
||||||
|
enabledCoreDropdown.ButtonTextColor =
|
||||||
|
EnabledCorePackage.HasAnyErrors
|
||||||
|
? GUIStyle.Red
|
||||||
|
: GUIStyle.TextColorNormal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -294,10 +294,11 @@ namespace Barotrauma.Steam
|
|||||||
{
|
{
|
||||||
//Reload the package to force hash recalculation
|
//Reload the package to force hash recalculation
|
||||||
string packageName = localPackage.Name;
|
string packageName = localPackage.Name;
|
||||||
localPackage = ContentPackageManager.ReloadContentPackage(localPackage);
|
var result = ContentPackageManager.ReloadContentPackage(localPackage);
|
||||||
if (localPackage is null)
|
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
|
//Set up the Ugc.Editor object that we'll need to publish
|
||||||
|
|||||||
@@ -133,20 +133,29 @@ namespace Barotrauma.Steam
|
|||||||
return searchBox;
|
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;
|
const int maxErrorsToShow = 5;
|
||||||
nameText.TextColor = GUIStyle.Red;
|
nameText.TextColor = GUIStyle.Red;
|
||||||
uiElement.ToolTip =
|
uiElement.ToolTip =
|
||||||
TextManager.GetWithVariable("contentpackagehaserrors", "[packagename]", mod.Name)
|
TextManager.GetWithVariable("ContentPackageHasFatalErrors", "[packagename]", mod.Name)
|
||||||
+ '\n' + string.Join('\n', mod.Errors.Take(maxErrorsToShow).Select(e => e.Message));
|
+ '\n' + string.Join('\n', mod.FatalLoadErrors.Take(maxErrorsToShow).Select(e => e.Message));
|
||||||
if (mod.Errors.Count() > maxErrorsToShow)
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<RootNamespace>Barotrauma</RootNamespace>
|
<RootNamespace>Barotrauma</RootNamespace>
|
||||||
<Authors>FakeFish, Undertow Games</Authors>
|
<Authors>FakeFish, Undertow Games</Authors>
|
||||||
<Product>Barotrauma</Product>
|
<Product>Barotrauma</Product>
|
||||||
<Version>100.5.0.0</Version>
|
<Version>100.6.0.0</Version>
|
||||||
<Copyright>Copyright © FakeFish 2018-2022</Copyright>
|
<Copyright>Copyright © FakeFish 2018-2022</Copyright>
|
||||||
<Platforms>AnyCPU;x64</Platforms>
|
<Platforms>AnyCPU;x64</Platforms>
|
||||||
<AssemblyName>Barotrauma</AssemblyName>
|
<AssemblyName>Barotrauma</AssemblyName>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<RootNamespace>Barotrauma</RootNamespace>
|
<RootNamespace>Barotrauma</RootNamespace>
|
||||||
<Authors>FakeFish, Undertow Games</Authors>
|
<Authors>FakeFish, Undertow Games</Authors>
|
||||||
<Product>Barotrauma</Product>
|
<Product>Barotrauma</Product>
|
||||||
<Version>100.5.0.0</Version>
|
<Version>100.6.0.0</Version>
|
||||||
<Copyright>Copyright © FakeFish 2018-2022</Copyright>
|
<Copyright>Copyright © FakeFish 2018-2022</Copyright>
|
||||||
<Platforms>AnyCPU;x64</Platforms>
|
<Platforms>AnyCPU;x64</Platforms>
|
||||||
<AssemblyName>Barotrauma</AssemblyName>
|
<AssemblyName>Barotrauma</AssemblyName>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<RootNamespace>Barotrauma</RootNamespace>
|
<RootNamespace>Barotrauma</RootNamespace>
|
||||||
<Authors>FakeFish, Undertow Games</Authors>
|
<Authors>FakeFish, Undertow Games</Authors>
|
||||||
<Product>Barotrauma</Product>
|
<Product>Barotrauma</Product>
|
||||||
<Version>100.5.0.0</Version>
|
<Version>100.6.0.0</Version>
|
||||||
<Copyright>Copyright © FakeFish 2018-2022</Copyright>
|
<Copyright>Copyright © FakeFish 2018-2022</Copyright>
|
||||||
<Platforms>AnyCPU;x64</Platforms>
|
<Platforms>AnyCPU;x64</Platforms>
|
||||||
<AssemblyName>Barotrauma</AssemblyName>
|
<AssemblyName>Barotrauma</AssemblyName>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<RootNamespace>Barotrauma</RootNamespace>
|
<RootNamespace>Barotrauma</RootNamespace>
|
||||||
<Authors>FakeFish, Undertow Games</Authors>
|
<Authors>FakeFish, Undertow Games</Authors>
|
||||||
<Product>Barotrauma Dedicated Server</Product>
|
<Product>Barotrauma Dedicated Server</Product>
|
||||||
<Version>100.5.0.0</Version>
|
<Version>100.6.0.0</Version>
|
||||||
<Copyright>Copyright © FakeFish 2018-2022</Copyright>
|
<Copyright>Copyright © FakeFish 2018-2022</Copyright>
|
||||||
<Platforms>AnyCPU;x64</Platforms>
|
<Platforms>AnyCPU;x64</Platforms>
|
||||||
<AssemblyName>DedicatedServer</AssemblyName>
|
<AssemblyName>DedicatedServer</AssemblyName>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<RootNamespace>Barotrauma</RootNamespace>
|
<RootNamespace>Barotrauma</RootNamespace>
|
||||||
<Authors>FakeFish, Undertow Games</Authors>
|
<Authors>FakeFish, Undertow Games</Authors>
|
||||||
<Product>Barotrauma Dedicated Server</Product>
|
<Product>Barotrauma Dedicated Server</Product>
|
||||||
<Version>100.5.0.0</Version>
|
<Version>100.6.0.0</Version>
|
||||||
<Copyright>Copyright © FakeFish 2018-2022</Copyright>
|
<Copyright>Copyright © FakeFish 2018-2022</Copyright>
|
||||||
<Platforms>AnyCPU;x64</Platforms>
|
<Platforms>AnyCPU;x64</Platforms>
|
||||||
<AssemblyName>DedicatedServer</AssemblyName>
|
<AssemblyName>DedicatedServer</AssemblyName>
|
||||||
|
|||||||
@@ -82,54 +82,50 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
|
|
||||||
//dequeue messages
|
//dequeue messages
|
||||||
lock (queuedMessages)
|
if (queuedMessages.Count > 0)
|
||||||
{
|
{
|
||||||
if (queuedMessages.Count > 0)
|
|
||||||
|
if (!Console.IsOutputRedirected)
|
||||||
{
|
{
|
||||||
|
Console.CursorLeft = 0;
|
||||||
if (!Console.IsOutputRedirected)
|
}
|
||||||
|
while (queuedMessages.TryDequeue(out var msg))
|
||||||
|
{
|
||||||
|
Messages.Add(msg);
|
||||||
|
if (GameSettings.CurrentConfig.SaveDebugConsoleLogs || GameSettings.CurrentConfig.VerboseLogging)
|
||||||
{
|
{
|
||||||
Console.CursorLeft = 0;
|
unsavedMessages.Add(msg);
|
||||||
}
|
if (unsavedMessages.Count >= messagesPerFile)
|
||||||
while (queuedMessages.Count > 0)
|
|
||||||
{
|
|
||||||
ColoredText msg = queuedMessages.Dequeue();
|
|
||||||
Messages.Add(msg);
|
|
||||||
if (GameSettings.CurrentConfig.SaveDebugConsoleLogs || GameSettings.CurrentConfig.VerboseLogging)
|
|
||||||
{
|
{
|
||||||
unsavedMessages.Add(msg);
|
SaveLogs();
|
||||||
if (unsavedMessages.Count >= messagesPerFile)
|
unsavedMessages.Clear();
|
||||||
{
|
|
||||||
SaveLogs();
|
|
||||||
unsavedMessages.Clear();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
string msgTxt = msg.Text;
|
|
||||||
|
|
||||||
if (msg.IsCommand) commandMemory.Add(msgTxt);
|
|
||||||
|
|
||||||
if(!Console.IsOutputRedirected)
|
|
||||||
{
|
|
||||||
int paddingLen = consoleWidth - (msg.Text.Length % consoleWidth) - 1;
|
|
||||||
msgTxt += new string(' ', paddingLen > 0 ? paddingLen : 0);
|
|
||||||
|
|
||||||
Console.ForegroundColor = XnaToConsoleColor.Convert(msg.Color);
|
|
||||||
}
|
|
||||||
Console.WriteLine(msgTxt);
|
|
||||||
|
|
||||||
if (sw.ElapsedMilliseconds >= maxTime) { break; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string msgTxt = msg.Text;
|
||||||
|
|
||||||
|
if (msg.IsCommand) commandMemory.Add(msgTxt);
|
||||||
|
|
||||||
if(!Console.IsOutputRedirected)
|
if(!Console.IsOutputRedirected)
|
||||||
{
|
{
|
||||||
RewriteInputToCommandLine(input);
|
int paddingLen = consoleWidth - (msg.Text.Length % consoleWidth) - 1;
|
||||||
|
msgTxt += new string(' ', paddingLen > 0 ? paddingLen : 0);
|
||||||
|
|
||||||
|
Console.ForegroundColor = XnaToConsoleColor.Convert(msg.Color);
|
||||||
}
|
}
|
||||||
|
Console.WriteLine(msgTxt);
|
||||||
|
|
||||||
|
if (sw.ElapsedMilliseconds >= maxTime) { break; }
|
||||||
}
|
}
|
||||||
if (Messages.Count > MaxMessages)
|
if (!Console.IsOutputRedirected)
|
||||||
{
|
{
|
||||||
Messages.RemoveRange(0, Messages.Count - MaxMessages);
|
RewriteInputToCommandLine(input);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (Messages.Count > MaxMessages)
|
||||||
|
{
|
||||||
|
Messages.RemoveRange(0, Messages.Count - MaxMessages);
|
||||||
|
}
|
||||||
|
|
||||||
// No good way to display input when console output is redirected, and can't read from redirected input using KeyAvailable.
|
// No good way to display input when console output is redirected, and can't read from redirected input using KeyAvailable.
|
||||||
if(!Console.IsOutputRedirected && !Console.IsInputRedirected)
|
if(!Console.IsOutputRedirected && !Console.IsInputRedirected)
|
||||||
@@ -272,26 +268,22 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public static void Clear()
|
public static void Clear()
|
||||||
{
|
{
|
||||||
lock (queuedMessages)
|
while (queuedMessages.TryDequeue(out var msg))
|
||||||
{
|
{
|
||||||
while (queuedMessages.Count > 0)
|
Messages.Add(msg);
|
||||||
|
if (GameSettings.CurrentConfig.SaveDebugConsoleLogs || GameSettings.CurrentConfig.VerboseLogging)
|
||||||
{
|
{
|
||||||
var msg = queuedMessages.Dequeue();
|
unsavedMessages.Add(msg);
|
||||||
Messages.Add(msg);
|
if (unsavedMessages.Count >= messagesPerFile)
|
||||||
if (GameSettings.CurrentConfig.SaveDebugConsoleLogs || GameSettings.CurrentConfig.VerboseLogging)
|
|
||||||
{
|
{
|
||||||
unsavedMessages.Add(msg);
|
SaveLogs();
|
||||||
if (unsavedMessages.Count >= messagesPerFile)
|
unsavedMessages.Clear();
|
||||||
{
|
|
||||||
SaveLogs();
|
|
||||||
unsavedMessages.Clear();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (Messages.Count > MaxMessages)
|
}
|
||||||
{
|
if (Messages.Count > MaxMessages)
|
||||||
Messages.RemoveRange(0, Messages.Count - MaxMessages);
|
{
|
||||||
}
|
Messages.RemoveRange(0, Messages.Count - MaxMessages);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -162,13 +162,13 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
name = doc.Root.GetAttributeString("name", "Server");
|
name = doc.Root.GetAttributeString(nameof(ServerSettings.Name), "Server");
|
||||||
port = doc.Root.GetAttributeInt("port", NetConfig.DefaultPort);
|
port = doc.Root.GetAttributeInt(nameof(ServerSettings.Port), NetConfig.DefaultPort);
|
||||||
queryPort = doc.Root.GetAttributeInt("queryport", NetConfig.DefaultQueryPort);
|
queryPort = doc.Root.GetAttributeInt(nameof(ServerSettings.QueryPort), NetConfig.DefaultQueryPort);
|
||||||
publiclyVisible = doc.Root.GetAttributeBool("public", false);
|
publiclyVisible = doc.Root.GetAttributeBool(nameof(ServerSettings.IsPublic), false);
|
||||||
|
enableUpnp = doc.Root.GetAttributeBool(nameof(ServerSettings.EnableUPnP), false);
|
||||||
|
maxPlayers = doc.Root.GetAttributeInt(nameof(ServerSettings.MaxPlayers), 10);
|
||||||
password = doc.Root.GetAttributeString("password", "");
|
password = doc.Root.GetAttributeString("password", "");
|
||||||
enableUpnp = doc.Root.GetAttributeBool("enableupnp", false);
|
|
||||||
maxPlayers = doc.Root.GetAttributeInt("maxplayers", 10);
|
|
||||||
ownerKey = Option<int>.None();
|
ownerKey = Option<int>.None();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -361,7 +361,7 @@ namespace Barotrauma
|
|||||||
if (prevUpdateRates.Count >= 10)
|
if (prevUpdateRates.Count >= 10)
|
||||||
{
|
{
|
||||||
int avgUpdateRate = (int)prevUpdateRates.Average();
|
int avgUpdateRate = (int)prevUpdateRates.Average();
|
||||||
if (avgUpdateRate < Timing.FixedUpdateRate * 0.98 && GameSession != null && Timing.TotalTime > GameSession.RoundStartTime + 1.0)
|
if (avgUpdateRate < Timing.FixedUpdateRate * 0.98 && GameSession != null && GameSession.RoundDuration > 1.0)
|
||||||
{
|
{
|
||||||
DebugConsole.AddWarning($"Running slowly ({avgUpdateRate} updates/s)!");
|
DebugConsole.AddWarning($"Running slowly ({avgUpdateRate} updates/s)!");
|
||||||
if (Server != null)
|
if (Server != null)
|
||||||
|
|||||||
+1
-1
@@ -335,7 +335,7 @@ namespace Barotrauma
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
Map.ProgressWorld(this, transitionType, (float)(Timing.TotalTime - GameMain.GameSession.RoundStartTime));
|
Map.ProgressWorld(this, transitionType, GameMain.GameSession.RoundDuration);
|
||||||
|
|
||||||
bool success = GameMain.Server.ConnectedClients.Any(c => c.InGame && c.Character != null && !c.Character.IsDead);
|
bool success = GameMain.Server.ConnectedClients.Any(c => c.InGame && c.Character != null && !c.Character.IsDead);
|
||||||
if (success)
|
if (success)
|
||||||
|
|||||||
@@ -200,9 +200,9 @@ namespace Barotrauma.Networking
|
|||||||
return length;
|
return length;
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void ServerWrite(IWriteMessage msg, Client c)
|
public virtual void ServerWrite(in SegmentTableWriter<ServerNetSegment> segmentTable, IWriteMessage msg, Client c)
|
||||||
{
|
{
|
||||||
msg.WriteByte((byte)ServerNetObject.CHAT_MESSAGE);
|
segmentTable.StartNewSegment(ServerNetSegment.ChatMessage);
|
||||||
msg.WriteUInt16(NetStateID);
|
msg.WriteUInt16(NetStateID);
|
||||||
msg.WriteRangedInteger((int)Type, 0, Enum.GetValues(typeof(ChatMessageType)).Length - 1);
|
msg.WriteRangedInteger((int)Type, 0, Enum.GetValues(typeof(ChatMessageType)).Length - 1);
|
||||||
msg.WriteByte((byte)ChangeType);
|
msg.WriteByte((byte)ChangeType);
|
||||||
|
|||||||
@@ -1028,12 +1028,11 @@ namespace Barotrauma.Networking
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ClientNetObject objHeader;
|
SegmentTableReader<ClientNetSegment>.Read(inc, (segment, inc) =>
|
||||||
while ((objHeader = (ClientNetObject)inc.ReadByte()) != ClientNetObject.END_OF_MESSAGE)
|
|
||||||
{
|
{
|
||||||
switch (objHeader)
|
switch (segment)
|
||||||
{
|
{
|
||||||
case ClientNetObject.SYNC_IDS:
|
case ClientNetSegment.SyncIds:
|
||||||
//TODO: might want to use a clever class for this
|
//TODO: might want to use a clever class for this
|
||||||
c.LastRecvLobbyUpdate = NetIdUtils.Clamp(inc.ReadUInt16(), c.LastRecvLobbyUpdate, GameMain.NetLobbyScreen.LastUpdateID);
|
c.LastRecvLobbyUpdate = NetIdUtils.Clamp(inc.ReadUInt16(), c.LastRecvLobbyUpdate, GameMain.NetLobbyScreen.LastUpdateID);
|
||||||
if (c.HasPermission(ClientPermissions.ManageSettings) &&
|
if (c.HasPermission(ClientPermissions.ManageSettings) &&
|
||||||
@@ -1072,19 +1071,21 @@ namespace Barotrauma.Networking
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case ClientNetObject.CHAT_MESSAGE:
|
case ClientNetSegment.ChatMessage:
|
||||||
ChatMessage.ServerRead(inc, c);
|
ChatMessage.ServerRead(inc, c);
|
||||||
break;
|
break;
|
||||||
case ClientNetObject.VOTE:
|
case ClientNetSegment.Vote:
|
||||||
Voting.ServerRead(inc, c);
|
Voting.ServerRead(inc, c);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return;
|
return SegmentTableReader<ClientNetSegment>.BreakSegmentReading.Yes;
|
||||||
}
|
}
|
||||||
|
|
||||||
//don't read further messages if the client has been disconnected (kicked due to spam for example)
|
//don't read further messages if the client has been disconnected (kicked due to spam for example)
|
||||||
if (!connectedClients.Contains(c)) break;
|
return connectedClients.Contains(c)
|
||||||
}
|
? SegmentTableReader<ClientNetSegment>.BreakSegmentReading.No
|
||||||
|
: SegmentTableReader<ClientNetSegment>.BreakSegmentReading.Yes;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ClientReadIngame(IReadMessage inc)
|
private void ClientReadIngame(IReadMessage inc)
|
||||||
@@ -1109,13 +1110,12 @@ namespace Barotrauma.Networking
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ClientNetObject objHeader;
|
SegmentTableReader<ClientNetSegment>.Read(inc, (segment, inc) =>
|
||||||
while ((objHeader = (ClientNetObject)inc.ReadByte()) != ClientNetObject.END_OF_MESSAGE)
|
|
||||||
{
|
{
|
||||||
switch (objHeader)
|
switch (segment)
|
||||||
{
|
{
|
||||||
case ClientNetObject.SYNC_IDS:
|
case ClientNetSegment.SyncIds:
|
||||||
//TODO: might want to use a clever class for this
|
//TODO: switch this to INetSerializableStruct
|
||||||
|
|
||||||
UInt16 lastRecvChatMsgID = inc.ReadUInt16();
|
UInt16 lastRecvChatMsgID = inc.ReadUInt16();
|
||||||
UInt16 lastRecvEntityEventID = inc.ReadUInt16();
|
UInt16 lastRecvEntityEventID = inc.ReadUInt16();
|
||||||
@@ -1218,10 +1218,10 @@ namespace Barotrauma.Networking
|
|||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case ClientNetObject.CHAT_MESSAGE:
|
case ClientNetSegment.ChatMessage:
|
||||||
ChatMessage.ServerRead(inc, c);
|
ChatMessage.ServerRead(inc, c);
|
||||||
break;
|
break;
|
||||||
case ClientNetObject.CHARACTER_INPUT:
|
case ClientNetSegment.CharacterInput:
|
||||||
if (c.Character != null)
|
if (c.Character != null)
|
||||||
{
|
{
|
||||||
c.Character.ServerReadInput(inc, c);
|
c.Character.ServerReadInput(inc, c);
|
||||||
@@ -1231,22 +1231,24 @@ namespace Barotrauma.Networking
|
|||||||
DebugConsole.AddWarning($"Received character inputs from a client who's not controlling a character ({c.Name}).");
|
DebugConsole.AddWarning($"Received character inputs from a client who's not controlling a character ({c.Name}).");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case ClientNetObject.ENTITY_STATE:
|
case ClientNetSegment.EntityState:
|
||||||
entityEventManager.Read(inc, c);
|
entityEventManager.Read(inc, c);
|
||||||
break;
|
break;
|
||||||
case ClientNetObject.VOTE:
|
case ClientNetSegment.Vote:
|
||||||
Voting.ServerRead(inc, c);
|
Voting.ServerRead(inc, c);
|
||||||
break;
|
break;
|
||||||
case ClientNetObject.SPECTATING_POS:
|
case ClientNetSegment.SpectatingPos:
|
||||||
c.SpectatePos = new Vector2(inc.ReadSingle(), inc.ReadSingle());
|
c.SpectatePos = new Vector2(inc.ReadSingle(), inc.ReadSingle());
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return;
|
return SegmentTableReader<ClientNetSegment>.BreakSegmentReading.Yes;
|
||||||
}
|
}
|
||||||
|
|
||||||
//don't read further messages if the client has been disconnected (kicked due to spam for example)
|
//don't read further messages if the client has been disconnected (kicked due to spam for example)
|
||||||
if (!connectedClients.Contains(c)) { break; }
|
return connectedClients.Contains(c)
|
||||||
}
|
? SegmentTableReader<ClientNetSegment>.BreakSegmentReading.No
|
||||||
|
: SegmentTableReader<ClientNetSegment>.BreakSegmentReading.Yes;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ReadCrewMessage(IReadMessage inc, Client sender)
|
private void ReadCrewMessage(IReadMessage inc, Client sender)
|
||||||
@@ -1701,78 +1703,78 @@ namespace Barotrauma.Networking
|
|||||||
|
|
||||||
IWriteMessage outmsg = new WriteOnlyMessage();
|
IWriteMessage outmsg = new WriteOnlyMessage();
|
||||||
outmsg.WriteByte((byte)ServerPacketHeader.UPDATE_INGAME);
|
outmsg.WriteByte((byte)ServerPacketHeader.UPDATE_INGAME);
|
||||||
|
|
||||||
outmsg.WriteSingle((float)NetTime.Now);
|
outmsg.WriteSingle((float)NetTime.Now);
|
||||||
|
|
||||||
outmsg.WriteByte((byte)ServerNetObject.SYNC_IDS);
|
using (var segmentTable = SegmentTableWriter<ServerNetSegment>.StartWriting(outmsg))
|
||||||
outmsg.WriteUInt16(c.LastSentChatMsgID); //send this to client so they know which chat messages weren't received by the server
|
|
||||||
outmsg.WriteUInt16(c.LastSentEntityEventID);
|
|
||||||
|
|
||||||
if (GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign && campaign.Preset == GameMain.NetLobbyScreen.SelectedMode)
|
|
||||||
{
|
{
|
||||||
outmsg.WriteBoolean(true);
|
segmentTable.StartNewSegment(ServerNetSegment.SyncIds);
|
||||||
outmsg.WritePadBits();
|
outmsg.WriteUInt16(c.LastSentChatMsgID); //send this to client so they know which chat messages weren't received by the server
|
||||||
campaign.ServerWrite(outmsg, c);
|
outmsg.WriteUInt16(c.LastSentEntityEventID);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
outmsg.WriteBoolean(false);
|
|
||||||
outmsg.WritePadBits();
|
|
||||||
}
|
|
||||||
|
|
||||||
int clientListBytes = outmsg.LengthBytes;
|
if (GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign && campaign.Preset == GameMain.NetLobbyScreen.SelectedMode)
|
||||||
WriteClientList(c, outmsg);
|
|
||||||
clientListBytes = outmsg.LengthBytes - clientListBytes;
|
|
||||||
|
|
||||||
int chatMessageBytes = outmsg.LengthBytes;
|
|
||||||
WriteChatMessages(outmsg, c);
|
|
||||||
chatMessageBytes = outmsg.LengthBytes - chatMessageBytes;
|
|
||||||
|
|
||||||
//write as many position updates as the message can fit (only after midround syncing is done)
|
|
||||||
int positionUpdateBytes = outmsg.LengthBytes;
|
|
||||||
while (!c.NeedsMidRoundSync && c.PendingPositionUpdates.Count > 0)
|
|
||||||
{
|
|
||||||
var entity = c.PendingPositionUpdates.Peek();
|
|
||||||
if (!(entity is IServerPositionSync entityPositionSync) ||
|
|
||||||
entity.Removed ||
|
|
||||||
(entity is Item item && float.IsInfinity(item.PositionUpdateInterval)))
|
|
||||||
{
|
{
|
||||||
|
outmsg.WriteBoolean(true);
|
||||||
|
outmsg.WritePadBits();
|
||||||
|
campaign.ServerWrite(outmsg, c);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
outmsg.WriteBoolean(false);
|
||||||
|
outmsg.WritePadBits();
|
||||||
|
}
|
||||||
|
|
||||||
|
int clientListBytes = outmsg.LengthBytes;
|
||||||
|
WriteClientList(segmentTable, c, outmsg);
|
||||||
|
clientListBytes = outmsg.LengthBytes - clientListBytes;
|
||||||
|
|
||||||
|
int chatMessageBytes = outmsg.LengthBytes;
|
||||||
|
WriteChatMessages(segmentTable, outmsg, c);
|
||||||
|
chatMessageBytes = outmsg.LengthBytes - chatMessageBytes;
|
||||||
|
|
||||||
|
//write as many position updates as the message can fit (only after midround syncing is done)
|
||||||
|
int positionUpdateBytes = outmsg.LengthBytes;
|
||||||
|
while (!c.NeedsMidRoundSync && c.PendingPositionUpdates.Count > 0)
|
||||||
|
{
|
||||||
|
var entity = c.PendingPositionUpdates.Peek();
|
||||||
|
if (!(entity is IServerPositionSync entityPositionSync) ||
|
||||||
|
entity.Removed ||
|
||||||
|
(entity is Item item && float.IsInfinity(item.PositionUpdateInterval)))
|
||||||
|
{
|
||||||
|
c.PendingPositionUpdates.Dequeue();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
IWriteMessage tempBuffer = new ReadWriteMessage();
|
||||||
|
tempBuffer.WriteBoolean(entity is Item); tempBuffer.WritePadBits();
|
||||||
|
tempBuffer.WriteUInt32(entity is MapEntity me ? me.Prefab.UintIdentifier : (UInt32)0);
|
||||||
|
entityPositionSync.ServerWritePosition(tempBuffer, c);
|
||||||
|
|
||||||
|
//no more room in this packet
|
||||||
|
if (outmsg.LengthBytes + tempBuffer.LengthBytes > MsgConstants.MTU - 100)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
segmentTable.StartNewSegment(ServerNetSegment.EntityPosition);
|
||||||
|
outmsg.WritePadBits(); //padding is required here to make sure any padding bits within tempBuffer are read correctly
|
||||||
|
outmsg.WriteBytes(tempBuffer.Buffer, 0, tempBuffer.LengthBytes);
|
||||||
|
outmsg.WritePadBits();
|
||||||
|
|
||||||
|
c.PositionUpdateLastSent[entity] = (float)NetTime.Now;
|
||||||
c.PendingPositionUpdates.Dequeue();
|
c.PendingPositionUpdates.Dequeue();
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
positionUpdateBytes = outmsg.LengthBytes - positionUpdateBytes;
|
||||||
|
|
||||||
IWriteMessage tempBuffer = new ReadWriteMessage();
|
if (outmsg.LengthBytes > MsgConstants.MTU)
|
||||||
tempBuffer.WriteBoolean(entity is Item); tempBuffer.WritePadBits();
|
|
||||||
tempBuffer.WriteUInt32(entity is MapEntity me ? me.Prefab.UintIdentifier : (UInt32)0);
|
|
||||||
entityPositionSync.ServerWritePosition(tempBuffer, c);
|
|
||||||
|
|
||||||
//no more room in this packet
|
|
||||||
if (outmsg.LengthBytes + tempBuffer.LengthBytes > MsgConstants.MTU - 100)
|
|
||||||
{
|
{
|
||||||
break;
|
string errorMsg = "Maximum packet size exceeded (" + outmsg.LengthBytes + " > " + MsgConstants.MTU + ")\n";
|
||||||
|
errorMsg +=
|
||||||
|
" Client list size: " + clientListBytes + " bytes\n" +
|
||||||
|
" Chat message size: " + chatMessageBytes + " bytes\n" +
|
||||||
|
" Position update size: " + positionUpdateBytes + " bytes\n\n";
|
||||||
|
DebugConsole.ThrowError(errorMsg);
|
||||||
|
GameAnalyticsManager.AddErrorEventOnce("GameServer.ClientWriteIngame1:PacketSizeExceeded" + outmsg.LengthBytes, GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
outmsg.WriteByte((byte)ServerNetObject.ENTITY_POSITION);
|
|
||||||
outmsg.WritePadBits(); //padding is required here to make sure any padding bits within tempBuffer are read correctly
|
|
||||||
outmsg.WriteBytes(tempBuffer.Buffer, 0, tempBuffer.LengthBytes);
|
|
||||||
outmsg.WritePadBits();
|
|
||||||
|
|
||||||
c.PositionUpdateLastSent[entity] = (float)NetTime.Now;
|
|
||||||
c.PendingPositionUpdates.Dequeue();
|
|
||||||
}
|
|
||||||
positionUpdateBytes = outmsg.LengthBytes - positionUpdateBytes;
|
|
||||||
|
|
||||||
outmsg.WriteByte((byte)ServerNetObject.END_OF_MESSAGE);
|
|
||||||
|
|
||||||
if (outmsg.LengthBytes > MsgConstants.MTU)
|
|
||||||
{
|
|
||||||
string errorMsg = "Maximum packet size exceeded (" + outmsg.LengthBytes + " > " + MsgConstants.MTU + ")\n";
|
|
||||||
errorMsg +=
|
|
||||||
" Client list size: " + clientListBytes + " bytes\n" +
|
|
||||||
" Chat message size: " + chatMessageBytes + " bytes\n" +
|
|
||||||
" Position update size: " + positionUpdateBytes + " bytes\n\n";
|
|
||||||
DebugConsole.ThrowError(errorMsg);
|
|
||||||
GameAnalyticsManager.AddErrorEventOnce("GameServer.ClientWriteIngame1:PacketSizeExceeded" + outmsg.LengthBytes, GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
serverPeer.Send(outmsg, c.Connection, DeliveryMethod.Unreliable);
|
serverPeer.Send(outmsg, c.Connection, DeliveryMethod.Unreliable);
|
||||||
@@ -1785,46 +1787,50 @@ namespace Barotrauma.Networking
|
|||||||
outmsg.WriteByte((byte)ServerPacketHeader.UPDATE_INGAME);
|
outmsg.WriteByte((byte)ServerPacketHeader.UPDATE_INGAME);
|
||||||
outmsg.WriteSingle((float)Lidgren.Network.NetTime.Now);
|
outmsg.WriteSingle((float)Lidgren.Network.NetTime.Now);
|
||||||
|
|
||||||
int eventManagerBytes = outmsg.LengthBytes;
|
using (var segmentTable = SegmentTableWriter<ServerNetSegment>.StartWriting(outmsg))
|
||||||
entityEventManager.Write(c, outmsg, out List<NetEntityEvent> sentEvents);
|
|
||||||
eventManagerBytes = outmsg.LengthBytes - eventManagerBytes;
|
|
||||||
|
|
||||||
if (sentEvents.Count == 0)
|
|
||||||
{
|
{
|
||||||
break;
|
int eventManagerBytes = outmsg.LengthBytes;
|
||||||
}
|
entityEventManager.Write(segmentTable, c, outmsg, out List<NetEntityEvent> sentEvents);
|
||||||
|
eventManagerBytes = outmsg.LengthBytes - eventManagerBytes;
|
||||||
|
|
||||||
outmsg.WriteByte((byte)ServerNetObject.END_OF_MESSAGE);
|
if (sentEvents.Count == 0)
|
||||||
|
|
||||||
if (outmsg.LengthBytes > MsgConstants.MTU)
|
|
||||||
{
|
|
||||||
string errorMsg = "Maximum packet size exceeded (" + outmsg.LengthBytes + " > " + MsgConstants.MTU + ")\n";
|
|
||||||
errorMsg +=
|
|
||||||
" Event size: " + eventManagerBytes + " bytes\n";
|
|
||||||
|
|
||||||
if (sentEvents != null && sentEvents.Count > 0)
|
|
||||||
{
|
{
|
||||||
errorMsg += "Sent events: \n";
|
break;
|
||||||
foreach (var entityEvent in sentEvents)
|
|
||||||
{
|
|
||||||
errorMsg += " - " + (entityEvent.Entity?.ToString() ?? "null") + "\n";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DebugConsole.ThrowError(errorMsg);
|
if (outmsg.LengthBytes > MsgConstants.MTU)
|
||||||
GameAnalyticsManager.AddErrorEventOnce("GameServer.ClientWriteIngame2:PacketSizeExceeded" + outmsg.LengthBytes, GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
{
|
||||||
|
string errorMsg = "Maximum packet size exceeded (" + outmsg.LengthBytes + " > " +
|
||||||
|
MsgConstants.MTU + ")\n";
|
||||||
|
errorMsg +=
|
||||||
|
" Event size: " + eventManagerBytes + " bytes\n";
|
||||||
|
|
||||||
|
if (sentEvents != null && sentEvents.Count > 0)
|
||||||
|
{
|
||||||
|
errorMsg += "Sent events: \n";
|
||||||
|
foreach (var entityEvent in sentEvents)
|
||||||
|
{
|
||||||
|
errorMsg += " - " + (entityEvent.Entity?.ToString() ?? "null") + "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DebugConsole.ThrowError(errorMsg);
|
||||||
|
GameAnalyticsManager.AddErrorEventOnce(
|
||||||
|
"GameServer.ClientWriteIngame2:PacketSizeExceeded" + outmsg.LengthBytes,
|
||||||
|
GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
serverPeer.Send(outmsg, c.Connection, DeliveryMethod.Unreliable);
|
serverPeer.Send(outmsg, c.Connection, DeliveryMethod.Unreliable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void WriteClientList(Client c, IWriteMessage outmsg)
|
private void WriteClientList(in SegmentTableWriter<ServerNetSegment> segmentTable, Client c, IWriteMessage outmsg)
|
||||||
{
|
{
|
||||||
bool hasChanged = NetIdUtils.IdMoreRecent(LastClientListUpdateID, c.LastRecvClientListUpdate);
|
bool hasChanged = NetIdUtils.IdMoreRecent(LastClientListUpdateID, c.LastRecvClientListUpdate);
|
||||||
if (!hasChanged) { return; }
|
if (!hasChanged) { return; }
|
||||||
|
|
||||||
outmsg.WriteByte((byte)ServerNetObject.CLIENT_LIST);
|
segmentTable.StartNewSegment(ServerNetSegment.ClientList);
|
||||||
outmsg.WriteUInt16(LastClientListUpdateID);
|
outmsg.WriteUInt16(LastClientListUpdateID);
|
||||||
|
|
||||||
outmsg.WriteByte((byte)connectedClients.Count);
|
outmsg.WriteByte((byte)connectedClients.Count);
|
||||||
@@ -1861,133 +1867,135 @@ namespace Barotrauma.Networking
|
|||||||
IWriteMessage outmsg = new WriteOnlyMessage();
|
IWriteMessage outmsg = new WriteOnlyMessage();
|
||||||
outmsg.WriteByte((byte)ServerPacketHeader.UPDATE_LOBBY);
|
outmsg.WriteByte((byte)ServerPacketHeader.UPDATE_LOBBY);
|
||||||
|
|
||||||
outmsg.WriteByte((byte)ServerNetObject.SYNC_IDS);
|
bool messageTooLarge;
|
||||||
|
using (var segmentTable = SegmentTableWriter<ServerNetSegment>.StartWriting(outmsg))
|
||||||
int settingsBytes = outmsg.LengthBytes;
|
|
||||||
int initialUpdateBytes = 0;
|
|
||||||
|
|
||||||
if (ServerSettings.UnsentFlags() != ServerSettings.NetFlags.None)
|
|
||||||
{
|
{
|
||||||
GameMain.NetLobbyScreen.LastUpdateID++;
|
segmentTable.StartNewSegment(ServerNetSegment.SyncIds);
|
||||||
}
|
|
||||||
|
|
||||||
IWriteMessage settingsBuf = null;
|
int settingsBytes = outmsg.LengthBytes;
|
||||||
if (NetIdUtils.IdMoreRecent(GameMain.NetLobbyScreen.LastUpdateID, c.LastRecvLobbyUpdate))
|
int initialUpdateBytes = 0;
|
||||||
{
|
|
||||||
outmsg.WriteBoolean(true);
|
|
||||||
outmsg.WritePadBits();
|
|
||||||
|
|
||||||
outmsg.WriteUInt16(GameMain.NetLobbyScreen.LastUpdateID);
|
if (ServerSettings.UnsentFlags() != ServerSettings.NetFlags.None)
|
||||||
|
|
||||||
settingsBuf = new ReadWriteMessage();
|
|
||||||
ServerSettings.ServerWrite(settingsBuf, c);
|
|
||||||
outmsg.WriteUInt16((UInt16)settingsBuf.LengthBytes);
|
|
||||||
outmsg.WriteBytes(settingsBuf.Buffer, 0, settingsBuf.LengthBytes);
|
|
||||||
|
|
||||||
outmsg.WriteBoolean(c.LastRecvLobbyUpdate < 1);
|
|
||||||
if (c.LastRecvLobbyUpdate < 1)
|
|
||||||
{
|
{
|
||||||
isInitialUpdate = true;
|
GameMain.NetLobbyScreen.LastUpdateID++;
|
||||||
initialUpdateBytes = outmsg.LengthBytes;
|
|
||||||
ClientWriteInitial(c, outmsg);
|
|
||||||
initialUpdateBytes = outmsg.LengthBytes - initialUpdateBytes;
|
|
||||||
}
|
}
|
||||||
outmsg.WriteString(GameMain.NetLobbyScreen.SelectedSub.Name);
|
|
||||||
outmsg.WriteString(GameMain.NetLobbyScreen.SelectedSub.MD5Hash.ToString());
|
|
||||||
outmsg.WriteBoolean(IsUsingRespawnShuttle());
|
|
||||||
var selectedShuttle = GameStarted && RespawnManager != null && RespawnManager.UsingShuttle ?
|
|
||||||
RespawnManager.RespawnShuttle.Info :
|
|
||||||
GameMain.NetLobbyScreen.SelectedShuttle;
|
|
||||||
outmsg.WriteString(selectedShuttle.Name);
|
|
||||||
outmsg.WriteString(selectedShuttle.MD5Hash.ToString());
|
|
||||||
|
|
||||||
outmsg.WriteBoolean(ServerSettings.AllowSubVoting);
|
IWriteMessage settingsBuf = null;
|
||||||
outmsg.WriteBoolean(ServerSettings.AllowModeVoting);
|
if (NetIdUtils.IdMoreRecent(GameMain.NetLobbyScreen.LastUpdateID, c.LastRecvLobbyUpdate))
|
||||||
|
|
||||||
outmsg.WriteBoolean(ServerSettings.VoiceChatEnabled);
|
|
||||||
|
|
||||||
outmsg.WriteBoolean(ServerSettings.AllowSpectating);
|
|
||||||
|
|
||||||
outmsg.WriteRangedInteger((int)ServerSettings.TraitorsEnabled, 0, 2);
|
|
||||||
|
|
||||||
outmsg.WriteRangedInteger((int)GameMain.NetLobbyScreen.MissionType, 0, (int)MissionType.All);
|
|
||||||
|
|
||||||
outmsg.WriteByte((byte)GameMain.NetLobbyScreen.SelectedModeIndex);
|
|
||||||
outmsg.WriteString(GameMain.NetLobbyScreen.LevelSeed);
|
|
||||||
outmsg.WriteSingle(ServerSettings.SelectedLevelDifficulty);
|
|
||||||
|
|
||||||
outmsg.WriteByte((byte)ServerSettings.BotCount);
|
|
||||||
outmsg.WriteBoolean(ServerSettings.BotSpawnMode == BotSpawnMode.Fill);
|
|
||||||
|
|
||||||
outmsg.WriteBoolean(ServerSettings.AutoRestart);
|
|
||||||
if (ServerSettings.AutoRestart)
|
|
||||||
{
|
{
|
||||||
outmsg.WriteSingle(autoRestartTimerRunning ? ServerSettings.AutoRestartTimer : 0.0f);
|
outmsg.WriteBoolean(true);
|
||||||
|
outmsg.WritePadBits();
|
||||||
|
|
||||||
|
outmsg.WriteUInt16(GameMain.NetLobbyScreen.LastUpdateID);
|
||||||
|
|
||||||
|
settingsBuf = new ReadWriteMessage();
|
||||||
|
ServerSettings.ServerWrite(settingsBuf, c);
|
||||||
|
outmsg.WriteUInt16((UInt16)settingsBuf.LengthBytes);
|
||||||
|
outmsg.WriteBytes(settingsBuf.Buffer, 0, settingsBuf.LengthBytes);
|
||||||
|
|
||||||
|
outmsg.WriteBoolean(c.LastRecvLobbyUpdate < 1);
|
||||||
|
if (c.LastRecvLobbyUpdate < 1)
|
||||||
|
{
|
||||||
|
isInitialUpdate = true;
|
||||||
|
initialUpdateBytes = outmsg.LengthBytes;
|
||||||
|
ClientWriteInitial(c, outmsg);
|
||||||
|
initialUpdateBytes = outmsg.LengthBytes - initialUpdateBytes;
|
||||||
|
}
|
||||||
|
outmsg.WriteString(GameMain.NetLobbyScreen.SelectedSub.Name);
|
||||||
|
outmsg.WriteString(GameMain.NetLobbyScreen.SelectedSub.MD5Hash.ToString());
|
||||||
|
outmsg.WriteBoolean(IsUsingRespawnShuttle());
|
||||||
|
var selectedShuttle = GameStarted && RespawnManager != null && RespawnManager.UsingShuttle ?
|
||||||
|
RespawnManager.RespawnShuttle.Info :
|
||||||
|
GameMain.NetLobbyScreen.SelectedShuttle;
|
||||||
|
outmsg.WriteString(selectedShuttle.Name);
|
||||||
|
outmsg.WriteString(selectedShuttle.MD5Hash.ToString());
|
||||||
|
|
||||||
|
outmsg.WriteBoolean(ServerSettings.AllowSubVoting);
|
||||||
|
outmsg.WriteBoolean(ServerSettings.AllowModeVoting);
|
||||||
|
|
||||||
|
outmsg.WriteBoolean(ServerSettings.VoiceChatEnabled);
|
||||||
|
|
||||||
|
outmsg.WriteBoolean(ServerSettings.AllowSpectating);
|
||||||
|
|
||||||
|
outmsg.WriteRangedInteger((int)ServerSettings.TraitorsEnabled, 0, 2);
|
||||||
|
|
||||||
|
outmsg.WriteRangedInteger((int)GameMain.NetLobbyScreen.MissionType, 0, (int)MissionType.All);
|
||||||
|
|
||||||
|
outmsg.WriteByte((byte)GameMain.NetLobbyScreen.SelectedModeIndex);
|
||||||
|
outmsg.WriteString(GameMain.NetLobbyScreen.LevelSeed);
|
||||||
|
outmsg.WriteSingle(ServerSettings.SelectedLevelDifficulty);
|
||||||
|
|
||||||
|
outmsg.WriteByte((byte)ServerSettings.BotCount);
|
||||||
|
outmsg.WriteBoolean(ServerSettings.BotSpawnMode == BotSpawnMode.Fill);
|
||||||
|
|
||||||
|
outmsg.WriteBoolean(ServerSettings.AutoRestart);
|
||||||
|
if (ServerSettings.AutoRestart)
|
||||||
|
{
|
||||||
|
outmsg.WriteSingle(autoRestartTimerRunning ? ServerSettings.AutoRestartTimer : 0.0f);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
else
|
||||||
else
|
|
||||||
{
|
|
||||||
outmsg.WriteBoolean(false);
|
|
||||||
outmsg.WritePadBits();
|
|
||||||
}
|
|
||||||
settingsBytes = outmsg.LengthBytes - settingsBytes;
|
|
||||||
|
|
||||||
int campaignBytes = outmsg.LengthBytes;
|
|
||||||
var campaign = GameMain.GameSession?.GameMode as MultiPlayerCampaign;
|
|
||||||
if (outmsg.LengthBytes < MsgConstants.MTU - 500 &&
|
|
||||||
campaign != null && campaign.Preset == GameMain.NetLobbyScreen.SelectedMode)
|
|
||||||
{
|
|
||||||
outmsg.WriteBoolean(true);
|
|
||||||
outmsg.WritePadBits();
|
|
||||||
campaign.ServerWrite(outmsg, c);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
outmsg.WriteBoolean(false);
|
|
||||||
outmsg.WritePadBits();
|
|
||||||
}
|
|
||||||
campaignBytes = outmsg.LengthBytes - campaignBytes;
|
|
||||||
|
|
||||||
outmsg.WriteUInt16(c.LastSentChatMsgID); //send this to client so they know which chat messages weren't received by the server
|
|
||||||
|
|
||||||
int clientListBytes = outmsg.LengthBytes;
|
|
||||||
if (outmsg.LengthBytes < MsgConstants.MTU - 500)
|
|
||||||
{
|
|
||||||
WriteClientList(c, outmsg);
|
|
||||||
}
|
|
||||||
clientListBytes = outmsg.LengthBytes - clientListBytes;
|
|
||||||
|
|
||||||
int chatMessageBytes = outmsg.LengthBytes;
|
|
||||||
WriteChatMessages(outmsg, c);
|
|
||||||
chatMessageBytes = outmsg.LengthBytes - chatMessageBytes;
|
|
||||||
|
|
||||||
outmsg.WriteByte((byte)ServerNetObject.END_OF_MESSAGE);
|
|
||||||
|
|
||||||
bool messageTooLarge = outmsg.LengthBytes > MsgConstants.MTU;
|
|
||||||
if (messageTooLarge && !isInitialUpdate)
|
|
||||||
{
|
|
||||||
string warningMsg = "Maximum packet size exceeded, will send using reliable mode (" + outmsg.LengthBytes + " > " + MsgConstants.MTU + ")\n";
|
|
||||||
warningMsg +=
|
|
||||||
" Client list size: " + clientListBytes + " bytes\n" +
|
|
||||||
" Chat message size: " + chatMessageBytes + " bytes\n" +
|
|
||||||
" Campaign size: " + campaignBytes + " bytes\n" +
|
|
||||||
" Settings size: " + settingsBytes + " bytes\n";
|
|
||||||
if (initialUpdateBytes > 0)
|
|
||||||
{
|
{
|
||||||
|
outmsg.WriteBoolean(false);
|
||||||
|
outmsg.WritePadBits();
|
||||||
|
}
|
||||||
|
settingsBytes = outmsg.LengthBytes - settingsBytes;
|
||||||
|
|
||||||
|
int campaignBytes = outmsg.LengthBytes;
|
||||||
|
var campaign = GameMain.GameSession?.GameMode as MultiPlayerCampaign;
|
||||||
|
if (outmsg.LengthBytes < MsgConstants.MTU - 500 &&
|
||||||
|
campaign != null && campaign.Preset == GameMain.NetLobbyScreen.SelectedMode)
|
||||||
|
{
|
||||||
|
outmsg.WriteBoolean(true);
|
||||||
|
outmsg.WritePadBits();
|
||||||
|
campaign.ServerWrite(outmsg, c);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
outmsg.WriteBoolean(false);
|
||||||
|
outmsg.WritePadBits();
|
||||||
|
}
|
||||||
|
campaignBytes = outmsg.LengthBytes - campaignBytes;
|
||||||
|
|
||||||
|
outmsg.WriteUInt16(c.LastSentChatMsgID); //send this to client so they know which chat messages weren't received by the server
|
||||||
|
|
||||||
|
int clientListBytes = outmsg.LengthBytes;
|
||||||
|
if (outmsg.LengthBytes < MsgConstants.MTU - 500)
|
||||||
|
{
|
||||||
|
WriteClientList(segmentTable, c, outmsg);
|
||||||
|
}
|
||||||
|
clientListBytes = outmsg.LengthBytes - clientListBytes;
|
||||||
|
|
||||||
|
int chatMessageBytes = outmsg.LengthBytes;
|
||||||
|
WriteChatMessages(segmentTable, outmsg, c);
|
||||||
|
chatMessageBytes = outmsg.LengthBytes - chatMessageBytes;
|
||||||
|
|
||||||
|
messageTooLarge = outmsg.LengthBytes > MsgConstants.MTU;
|
||||||
|
if (messageTooLarge && !isInitialUpdate)
|
||||||
|
{
|
||||||
|
string warningMsg = "Maximum packet size exceeded, will send using reliable mode (" + outmsg.LengthBytes + " > " + MsgConstants.MTU + ")\n";
|
||||||
warningMsg +=
|
warningMsg +=
|
||||||
" Initial update size: " + settingsBuf.LengthBytes + " bytes\n";
|
" Client list size: " + clientListBytes + " bytes\n" +
|
||||||
}
|
" Chat message size: " + chatMessageBytes + " bytes\n" +
|
||||||
if (settingsBuf != null)
|
" Campaign size: " + campaignBytes + " bytes\n" +
|
||||||
{
|
" Settings size: " + settingsBytes + " bytes\n";
|
||||||
warningMsg +=
|
if (initialUpdateBytes > 0)
|
||||||
" Settings buffer size: " + settingsBuf.LengthBytes + " bytes\n";
|
{
|
||||||
}
|
warningMsg +=
|
||||||
|
" Initial update size: " + initialUpdateBytes + " bytes\n";
|
||||||
|
}
|
||||||
|
if (settingsBuf != null)
|
||||||
|
{
|
||||||
|
warningMsg +=
|
||||||
|
" Settings buffer size: " + settingsBuf.LengthBytes + " bytes\n";
|
||||||
|
}
|
||||||
#if DEBUG || UNSTABLE
|
#if DEBUG || UNSTABLE
|
||||||
DebugConsole.ThrowError(warningMsg);
|
DebugConsole.ThrowError(warningMsg);
|
||||||
#else
|
#else
|
||||||
if (GameSettings.CurrentConfig.VerboseLogging) { DebugConsole.AddWarning(warningMsg); }
|
if (GameSettings.CurrentConfig.VerboseLogging) { DebugConsole.AddWarning(warningMsg); }
|
||||||
#endif
|
#endif
|
||||||
GameAnalyticsManager.AddErrorEventOnce("GameServer.ClientWriteIngame1:ClientWriteLobby" + outmsg.LengthBytes, GameAnalyticsManager.ErrorSeverity.Warning, warningMsg);
|
GameAnalyticsManager.AddErrorEventOnce("GameServer.ClientWriteIngame1:ClientWriteLobby" + outmsg.LengthBytes, GameAnalyticsManager.ErrorSeverity.Warning, warningMsg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isInitialUpdate || messageTooLarge)
|
if (isInitialUpdate || messageTooLarge)
|
||||||
@@ -2014,7 +2022,7 @@ namespace Barotrauma.Networking
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void WriteChatMessages(IWriteMessage outmsg, Client c)
|
private void WriteChatMessages(in SegmentTableWriter<ServerNetSegment> segmentTable, IWriteMessage outmsg, Client c)
|
||||||
{
|
{
|
||||||
c.ChatMsgQueue.RemoveAll(cMsg => !NetIdUtils.IdMoreRecent(cMsg.NetStateID, c.LastRecvChatMsgID));
|
c.ChatMsgQueue.RemoveAll(cMsg => !NetIdUtils.IdMoreRecent(cMsg.NetStateID, c.LastRecvChatMsgID));
|
||||||
for (int i = 0; i < c.ChatMsgQueue.Count && i < ChatMessage.MaxMessagesPerPacket; i++)
|
for (int i = 0; i < c.ChatMsgQueue.Count && i < ChatMessage.MaxMessagesPerPacket; i++)
|
||||||
@@ -2024,7 +2032,7 @@ namespace Barotrauma.Networking
|
|||||||
//not enough room in this packet
|
//not enough room in this packet
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
c.ChatMsgQueue[i].ServerWrite(outmsg, c);
|
c.ChatMsgQueue[i].ServerWrite(segmentTable, outmsg, c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2417,7 +2425,6 @@ namespace Barotrauma.Networking
|
|||||||
spawnedCharacter.Info.InventoryData = new XElement("inventory");
|
spawnedCharacter.Info.InventoryData = new XElement("inventory");
|
||||||
spawnedCharacter.Info.StartItemsGiven = true;
|
spawnedCharacter.Info.StartItemsGiven = true;
|
||||||
spawnedCharacter.SaveInventory();
|
spawnedCharacter.SaveInventory();
|
||||||
// talents are only avilable for players in online sessions, but modders or someone else might want to have them loaded anyway
|
|
||||||
spawnedCharacter.LoadTalents();
|
spawnedCharacter.LoadTalents();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3327,9 +3334,11 @@ namespace Barotrauma.Networking
|
|||||||
|
|
||||||
IWriteMessage msg = new WriteOnlyMessage();
|
IWriteMessage msg = new WriteOnlyMessage();
|
||||||
msg.WriteByte((byte)ServerPacketHeader.UPDATE_LOBBY);
|
msg.WriteByte((byte)ServerPacketHeader.UPDATE_LOBBY);
|
||||||
msg.WriteByte((byte)ServerNetObject.VOTE);
|
using (var segmentTable = SegmentTableWriter<ServerNetSegment>.StartWriting(msg))
|
||||||
Voting.ServerWrite(msg);
|
{
|
||||||
msg.WriteByte((byte)ServerNetObject.END_OF_MESSAGE);
|
segmentTable.StartNewSegment(ServerNetSegment.Vote);
|
||||||
|
Voting.ServerWrite(msg);
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var c in recipients)
|
foreach (var c in recipients)
|
||||||
{
|
{
|
||||||
|
|||||||
+9
-9
@@ -123,7 +123,7 @@ namespace Barotrauma.Networking
|
|||||||
//remove old events that have been sent to all clients, they are redundant now
|
//remove old events that have been sent to all clients, they are redundant now
|
||||||
// keep at least one event in the list (lastSentToAll == e.ID) so we can use it to keep track of the latest ID
|
// keep at least one event in the list (lastSentToAll == e.ID) so we can use it to keep track of the latest ID
|
||||||
// and events less than 15 seconds old to give disconnected clients a bit of time to reconnect without getting desynced
|
// and events less than 15 seconds old to give disconnected clients a bit of time to reconnect without getting desynced
|
||||||
if (Timing.TotalTime > GameMain.GameSession.RoundStartTime + NetConfig.RoundStartSyncDuration)
|
if (GameMain.GameSession.RoundDuration > NetConfig.RoundStartSyncDuration)
|
||||||
{
|
{
|
||||||
events.RemoveAll(e =>
|
events.RemoveAll(e =>
|
||||||
(NetIdUtils.IdMoreRecent(lastSentToAll, e.ID) || !inGameClientsPresent) &&
|
(NetIdUtils.IdMoreRecent(lastSentToAll, e.ID) || !inGameClientsPresent) &&
|
||||||
@@ -217,7 +217,7 @@ namespace Barotrauma.Networking
|
|||||||
|
|
||||||
if (Timing.TotalTime - lastWarningTime > 5.0 &&
|
if (Timing.TotalTime - lastWarningTime > 5.0 &&
|
||||||
Timing.TotalTime - lastSentToAnyoneTime > 10.0 &&
|
Timing.TotalTime - lastSentToAnyoneTime > 10.0 &&
|
||||||
Timing.TotalTime > GameMain.GameSession.RoundStartTime + NetConfig.RoundStartSyncDuration)
|
GameMain.GameSession.RoundDuration > NetConfig.RoundStartSyncDuration)
|
||||||
{
|
{
|
||||||
lastWarningTime = Timing.TotalTime;
|
lastWarningTime = Timing.TotalTime;
|
||||||
GameServer.Log("WARNING: ServerEntityEventManager is lagging behind! Last sent id: " + lastSentToAnyone.ToString() + ", latest create id: " + ID.ToString(), ServerLog.MessageType.ServerMessage);
|
GameServer.Log("WARNING: ServerEntityEventManager is lagging behind! Last sent id: " + lastSentToAnyone.ToString() + ", latest create id: " + ID.ToString(), ServerLog.MessageType.ServerMessage);
|
||||||
@@ -229,7 +229,7 @@ namespace Barotrauma.Networking
|
|||||||
|
|
||||||
ServerEntityEvent firstEventToResend = events.Find(e => e.ID == (ushort)(lastSentToAll + 1));
|
ServerEntityEvent firstEventToResend = events.Find(e => e.ID == (ushort)(lastSentToAll + 1));
|
||||||
if (firstEventToResend != null &&
|
if (firstEventToResend != null &&
|
||||||
Timing.TotalTime > GameMain.GameSession.RoundStartTime + NetConfig.RoundStartSyncDuration &&
|
GameMain.GameSession.RoundDuration > NetConfig.RoundStartSyncDuration &&
|
||||||
((lastSentToAnyoneTime - firstEventToResend.CreateTime) > NetConfig.OldReceivedEventKickTime || (Timing.TotalTime - firstEventToResend.CreateTime) > NetConfig.OldEventKickTime))
|
((lastSentToAnyoneTime - firstEventToResend.CreateTime) > NetConfig.OldReceivedEventKickTime || (Timing.TotalTime - firstEventToResend.CreateTime) > NetConfig.OldEventKickTime))
|
||||||
{
|
{
|
||||||
// This event is 10 seconds older than the last one we've successfully sent,
|
// This event is 10 seconds older than the last one we've successfully sent,
|
||||||
@@ -295,15 +295,15 @@ namespace Barotrauma.Networking
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Writes all the events that the client hasn't received yet into the outgoing message
|
/// Writes all the events that the client hasn't received yet into the outgoing message
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Write(Client client, IWriteMessage msg)
|
public void Write(in SegmentTableWriter<ServerNetSegment> segmentTable, Client client, IWriteMessage msg)
|
||||||
{
|
{
|
||||||
Write(client, msg, out _);
|
Write(segmentTable, client, msg, out _);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Writes all the events that the client hasn't received yet into the outgoing message
|
/// Writes all the events that the client hasn't received yet into the outgoing message
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Write(Client client, IWriteMessage msg, out List<NetEntityEvent> sentEvents)
|
public void Write(in SegmentTableWriter<ServerNetSegment> segmentTable, Client client, IWriteMessage msg, out List<NetEntityEvent> sentEvents)
|
||||||
{
|
{
|
||||||
List<NetEntityEvent> eventsToSync = GetEventsToSync(client);
|
List<NetEntityEvent> eventsToSync = GetEventsToSync(client);
|
||||||
|
|
||||||
@@ -315,7 +315,7 @@ namespace Barotrauma.Networking
|
|||||||
|
|
||||||
//too many events for one packet
|
//too many events for one packet
|
||||||
//(normal right after a round has just started, don't show a warning if it's been less than 10 seconds)
|
//(normal right after a round has just started, don't show a warning if it's been less than 10 seconds)
|
||||||
if (eventsToSync.Count > 200 && GameMain.GameSession != null && Timing.TotalTime > GameMain.GameSession.RoundStartTime + 10.0)
|
if (eventsToSync.Count > 200 && GameMain.GameSession != null && GameMain.GameSession.RoundDuration > 10.0)
|
||||||
{
|
{
|
||||||
if (eventsToSync.Count > 200 && !client.NeedsMidRoundSync && Timing.TotalTime > lastEventCountHighWarning + 2.0)
|
if (eventsToSync.Count > 200 && !client.NeedsMidRoundSync && Timing.TotalTime > lastEventCountHighWarning + 2.0)
|
||||||
{
|
{
|
||||||
@@ -345,7 +345,7 @@ namespace Barotrauma.Networking
|
|||||||
|
|
||||||
if (client.NeedsMidRoundSync)
|
if (client.NeedsMidRoundSync)
|
||||||
{
|
{
|
||||||
msg.WriteByte((byte)ServerNetObject.ENTITY_EVENT_INITIAL);
|
segmentTable.StartNewSegment(ServerNetSegment.EntityEventInitial);
|
||||||
msg.WriteUInt16(client.UnreceivedEntityEventCount);
|
msg.WriteUInt16(client.UnreceivedEntityEventCount);
|
||||||
msg.WriteUInt16(client.FirstNewEventID);
|
msg.WriteUInt16(client.FirstNewEventID);
|
||||||
|
|
||||||
@@ -353,7 +353,7 @@ namespace Barotrauma.Networking
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
msg.WriteByte((byte)ServerNetObject.ENTITY_EVENT);
|
segmentTable.StartNewSegment(ServerNetSegment.EntityEvent);
|
||||||
Write(msg, eventsToSync, out sentEvents, client);
|
Write(msg, eventsToSync, out sentEvents, client);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
using Barotrauma.Steam;
|
using Barotrauma.Steam;
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Barotrauma.Networking
|
namespace Barotrauma.Networking
|
||||||
{
|
{
|
||||||
partial class OrderChatMessage : ChatMessage
|
partial class OrderChatMessage : ChatMessage
|
||||||
{
|
{
|
||||||
public override void ServerWrite(IWriteMessage msg, Client c)
|
public override void ServerWrite(in SegmentTableWriter<ServerNetSegment> segmentTable, IWriteMessage msg, Client c)
|
||||||
{
|
{
|
||||||
msg.WriteByte((byte)ServerNetObject.CHAT_MESSAGE);
|
segmentTable.StartNewSegment(ServerNetSegment.ChatMessage);
|
||||||
msg.WriteUInt16(NetStateID);
|
msg.WriteUInt16(NetStateID);
|
||||||
msg.WriteRangedInteger((int)ChatMessageType.Order, 0, Enum.GetValues(typeof(ChatMessageType)).Length - 1);
|
msg.WriteRangedInteger((int)ChatMessageType.Order, 0, Enum.GetValues(typeof(ChatMessageType)).Length - 1);
|
||||||
msg.WriteString(SenderName);
|
msg.WriteString(SenderName);
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ namespace Barotrauma.Networking
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsRespawnPromptPendingForClient(Client c)
|
private static bool IsRespawnPromptPendingForClient(Client c)
|
||||||
{
|
{
|
||||||
if (!UseRespawnPrompt || !(GameMain.GameSession.GameMode is MultiPlayerCampaign campaign)) { return false; }
|
if (!UseRespawnPrompt || !(GameMain.GameSession.GameMode is MultiPlayerCampaign campaign)) { return false; }
|
||||||
|
|
||||||
@@ -67,7 +67,7 @@ namespace Barotrauma.Networking
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<CharacterInfo> GetBotsToRespawn()
|
private static List<CharacterInfo> GetBotsToRespawn()
|
||||||
{
|
{
|
||||||
if (GameMain.Server.ServerSettings.BotSpawnMode == BotSpawnMode.Normal)
|
if (GameMain.Server.ServerSettings.BotSpawnMode == BotSpawnMode.Normal)
|
||||||
{
|
{
|
||||||
@@ -110,7 +110,7 @@ namespace Barotrauma.Networking
|
|||||||
return ShouldStartRespawnCountdown(characterToRespawnCount);
|
return ShouldStartRespawnCountdown(characterToRespawnCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int GetMinCharactersToRespawn()
|
private static int GetMinCharactersToRespawn()
|
||||||
{
|
{
|
||||||
return Math.Max((int)(GameMain.Server.ConnectedClients.Count * GameMain.Server.ServerSettings.MinRespawnRatio), 1);
|
return Math.Max((int)(GameMain.Server.ConnectedClients.Count * GameMain.Server.ServerSettings.MinRespawnRatio), 1);
|
||||||
}
|
}
|
||||||
@@ -464,7 +464,7 @@ namespace Barotrauma.Networking
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(GameMain.GameSession.GameMode is CampaignMode))
|
if (GameMain.GameSession.GameMode is not CampaignMode)
|
||||||
{
|
{
|
||||||
if (scooterPrefab != null)
|
if (scooterPrefab != null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<RootNamespace>Barotrauma</RootNamespace>
|
<RootNamespace>Barotrauma</RootNamespace>
|
||||||
<Authors>FakeFish, Undertow Games</Authors>
|
<Authors>FakeFish, Undertow Games</Authors>
|
||||||
<Product>Barotrauma Dedicated Server</Product>
|
<Product>Barotrauma Dedicated Server</Product>
|
||||||
<Version>100.5.0.0</Version>
|
<Version>100.6.0.0</Version>
|
||||||
<Copyright>Copyright © FakeFish 2018-2022</Copyright>
|
<Copyright>Copyright © FakeFish 2018-2022</Copyright>
|
||||||
<Platforms>AnyCPU;x64</Platforms>
|
<Platforms>AnyCPU;x64</Platforms>
|
||||||
<AssemblyName>DedicatedServer</AssemblyName>
|
<AssemblyName>DedicatedServer</AssemblyName>
|
||||||
|
|||||||
@@ -1625,7 +1625,7 @@ namespace Barotrauma
|
|||||||
float accumulatedDamage = Math.Max(otherHumanAI.structureDamageAccumulator[character], maxAccumulatedDamage);
|
float accumulatedDamage = Math.Max(otherHumanAI.structureDamageAccumulator[character], maxAccumulatedDamage);
|
||||||
maxAccumulatedDamage = Math.Max(accumulatedDamage, maxAccumulatedDamage);
|
maxAccumulatedDamage = Math.Max(accumulatedDamage, maxAccumulatedDamage);
|
||||||
|
|
||||||
if (GameMain.GameSession?.Campaign?.Map?.CurrentLocation?.Reputation != null)
|
if (GameMain.GameSession?.Campaign?.Map?.CurrentLocation?.Reputation != null && character.IsPlayer)
|
||||||
{
|
{
|
||||||
var reputationLoss = damageAmount * Reputation.ReputationLossPerWallDamage;
|
var reputationLoss = damageAmount * Reputation.ReputationLossPerWallDamage;
|
||||||
GameMain.GameSession.Campaign.Map.CurrentLocation.Reputation.AddReputation(-reputationLoss);
|
GameMain.GameSession.Campaign.Map.CurrentLocation.Reputation.AddReputation(-reputationLoss);
|
||||||
|
|||||||
@@ -77,11 +77,11 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (Level.Loaded.Type == LevelData.LevelType.LocationConnection)
|
if (Level.Loaded.Type == LevelData.LevelType.LocationConnection)
|
||||||
{
|
{
|
||||||
if (Timing.TotalTime < GameMain.GameSession.RoundStartTime + 30.0f) { currentFlags.Add("Initial".ToIdentifier()); }
|
if (GameMain.GameSession.RoundDuration > 30.0f) { currentFlags.Add("Initial".ToIdentifier()); }
|
||||||
}
|
}
|
||||||
else if (Level.Loaded.Type == LevelData.LevelType.Outpost)
|
else if (Level.Loaded.Type == LevelData.LevelType.Outpost)
|
||||||
{
|
{
|
||||||
if (Timing.TotalTime < GameMain.GameSession.RoundStartTime + 120.0f &&
|
if (GameMain.GameSession.RoundDuration > 120.0f &&
|
||||||
speaker?.CurrentHull != null &&
|
speaker?.CurrentHull != null &&
|
||||||
(speaker.TeamID == CharacterTeamType.FriendlyNPC || speaker.TeamID == CharacterTeamType.None) &&
|
(speaker.TeamID == CharacterTeamType.FriendlyNPC || speaker.TeamID == CharacterTeamType.None) &&
|
||||||
Character.CharacterList.Any(c => c.TeamID != speaker.TeamID && c.CurrentHull == speaker.CurrentHull))
|
Character.CharacterList.Any(c => c.TeamID != speaker.TeamID && c.CurrentHull == speaker.CurrentHull))
|
||||||
|
|||||||
@@ -212,7 +212,7 @@ namespace Barotrauma
|
|||||||
public float Stun { get; private set; }
|
public float Stun { get; private set; }
|
||||||
|
|
||||||
[Serialize(false, IsPropertySaveable.Yes, description: "Can damage only Humans."), Editable]
|
[Serialize(false, IsPropertySaveable.Yes, description: "Can damage only Humans."), Editable]
|
||||||
public bool OnlyHumans { get; private set; }
|
public bool OnlyHumans { get; set; }
|
||||||
|
|
||||||
[Serialize("", IsPropertySaveable.Yes), Editable]
|
[Serialize("", IsPropertySaveable.Yes), Editable]
|
||||||
public string ApplyForceOnLimbs
|
public string ApplyForceOnLimbs
|
||||||
@@ -328,7 +328,7 @@ namespace Barotrauma
|
|||||||
List<Affliction> multipliedAfflictions = new List<Affliction>();
|
List<Affliction> multipliedAfflictions = new List<Affliction>();
|
||||||
foreach (Affliction affliction in Afflictions.Keys)
|
foreach (Affliction affliction in Afflictions.Keys)
|
||||||
{
|
{
|
||||||
multipliedAfflictions.Add(affliction.CreateMultiplied(multiplier, affliction.Probability));
|
multipliedAfflictions.Add(affliction.CreateMultiplied(multiplier, affliction));
|
||||||
}
|
}
|
||||||
return multipliedAfflictions;
|
return multipliedAfflictions;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -540,7 +540,7 @@ namespace Barotrauma
|
|||||||
private Color speechBubbleColor;
|
private Color speechBubbleColor;
|
||||||
private float speechBubbleTimer;
|
private float speechBubbleTimer;
|
||||||
|
|
||||||
public bool ResetInteract;
|
public bool DisableInteract { get; set; }
|
||||||
|
|
||||||
//text displayed when the character is highlighted if custom interact is set
|
//text displayed when the character is highlighted if custom interact is set
|
||||||
public LocalizedString CustomInteractHUDText { get; private set; }
|
public LocalizedString CustomInteractHUDText { get; private set; }
|
||||||
@@ -809,6 +809,7 @@ namespace Barotrauma
|
|||||||
public float MaxHealth => MaxVitality;
|
public float MaxHealth => MaxVitality;
|
||||||
public AIState AIState => AIController is EnemyAIController enemyAI ? enemyAI.State : AIState.Idle;
|
public AIState AIState => AIController is EnemyAIController enemyAI ? enemyAI.State : AIState.Idle;
|
||||||
public bool IsLatched => AIController is EnemyAIController enemyAI && enemyAI.LatchOntoAI != null && enemyAI.LatchOntoAI.IsAttached;
|
public bool IsLatched => AIController is EnemyAIController enemyAI && enemyAI.LatchOntoAI != null && enemyAI.LatchOntoAI.IsAttached;
|
||||||
|
public float EmpVulnerability => Params.Health.EmpVulnerability;
|
||||||
|
|
||||||
public float Bloodloss
|
public float Bloodloss
|
||||||
{
|
{
|
||||||
@@ -856,6 +857,12 @@ namespace Barotrauma
|
|||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IgnoreMeleeWeapons
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Current speed of the character's collider. Can be used by status effects to check if the character is moving.
|
/// Current speed of the character's collider. Can be used by status effects to check if the character is moving.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -906,6 +913,10 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
itemSelectedTime = Timing.TotalTime;
|
itemSelectedTime = Timing.TotalTime;
|
||||||
}
|
}
|
||||||
|
if (prevSelectedItem != _selectedItem && prevSelectedItem?.OnDeselect != null)
|
||||||
|
{
|
||||||
|
prevSelectedItem.OnDeselect(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -1592,7 +1603,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
DebugConsole.ThrowError($"Failed to give job items for the character \"{Name}\" - could not find human prefab with the id \"{info.HumanPrefabIds.NpcIdentifier}\" from \"{info.HumanPrefabIds.NpcSetIdentifier}\".");
|
DebugConsole.ThrowError($"Failed to give job items for the character \"{Name}\" - could not find human prefab with the id \"{info.HumanPrefabIds.NpcIdentifier}\" from \"{info.HumanPrefabIds.NpcSetIdentifier}\".");
|
||||||
}
|
}
|
||||||
else if (humanPrefab.GiveItems(this, Submarine))
|
else if (humanPrefab.GiveItems(this, Submarine, spawnPoint))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -2681,9 +2692,9 @@ namespace Barotrauma
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ResetInteract)
|
if (DisableInteract)
|
||||||
{
|
{
|
||||||
ResetInteract = false;
|
DisableInteract = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2704,25 +2715,31 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (!IsMouseOnUI && (ViewTarget == null || ViewTarget == this))
|
if (!IsMouseOnUI && (ViewTarget == null || ViewTarget == this))
|
||||||
{
|
{
|
||||||
if ((findFocusedTimer <= 0.0f || Screen.Selected == GameMain.SubEditorScreen) && (!PlayerInput.PrimaryMouseButtonHeld() || Barotrauma.Inventory.DraggingItemToWorld))
|
if (findFocusedTimer <= 0.0f || Screen.Selected == GameMain.SubEditorScreen)
|
||||||
{
|
{
|
||||||
FocusedCharacter = CanInteract || CanEat ? FindCharacterAtPosition(mouseSimPos) : null;
|
if (!PlayerInput.PrimaryMouseButtonHeld() || Barotrauma.Inventory.DraggingItemToWorld)
|
||||||
if (FocusedCharacter != null && !CanSeeCharacter(FocusedCharacter)) { FocusedCharacter = null; }
|
|
||||||
float aimAssist = GameSettings.CurrentConfig.AimAssistAmount * (AnimController.InWater ? 1.5f : 1.0f);
|
|
||||||
if (HeldItems.Any(it => it?.GetComponent<Wire>()?.IsActive ?? false))
|
|
||||||
{
|
{
|
||||||
//disable aim assist when rewiring to make it harder to accidentally select items when adding wire nodes
|
FocusedCharacter = CanInteract || CanEat ? FindCharacterAtPosition(mouseSimPos) : null;
|
||||||
aimAssist = 0.0f;
|
if (FocusedCharacter != null && !CanSeeCharacter(FocusedCharacter)) { FocusedCharacter = null; }
|
||||||
|
float aimAssist = GameSettings.CurrentConfig.AimAssistAmount * (AnimController.InWater ? 1.5f : 1.0f);
|
||||||
|
if (HeldItems.Any(it => it?.GetComponent<Wire>()?.IsActive ?? false))
|
||||||
|
{
|
||||||
|
//disable aim assist when rewiring to make it harder to accidentally select items when adding wire nodes
|
||||||
|
aimAssist = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
focusedItem = CanInteract ? FindItemAtPosition(mouseSimPos, aimAssist) : null;
|
||||||
|
if (focusedItem != null && focusedItem.CampaignInteractionType != CampaignMode.InteractionType.None)
|
||||||
|
{
|
||||||
|
FocusedCharacter = null;
|
||||||
|
}
|
||||||
|
findFocusedTimer = 0.05f;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
var item = FindItemAtPosition(mouseSimPos, aimAssist);
|
|
||||||
|
|
||||||
focusedItem = CanInteract ? item : null;
|
|
||||||
if (focusedItem != null && focusedItem.CampaignInteractionType != CampaignMode.InteractionType.None)
|
|
||||||
{
|
{
|
||||||
FocusedCharacter = null;
|
if (focusedItem != null && !CanInteractWith(focusedItem)) { focusedItem = null; }
|
||||||
|
if (FocusedCharacter != null && !CanInteractWith(FocusedCharacter)) { FocusedCharacter = null; }
|
||||||
}
|
}
|
||||||
findFocusedTimer = 0.05f;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -2953,6 +2970,15 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
UpdateProjSpecific(deltaTime, cam);
|
UpdateProjSpecific(deltaTime, 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;
|
||||||
|
}
|
||||||
|
|
||||||
KnockbackCooldownTimer -= deltaTime;
|
KnockbackCooldownTimer -= deltaTime;
|
||||||
|
|
||||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient && this == Controlled && !isSynced) { return; }
|
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient && this == Controlled && !isSynced) { return; }
|
||||||
@@ -2992,6 +3018,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
|
|
||||||
HideFace = false;
|
HideFace = false;
|
||||||
|
IgnoreMeleeWeapons = false;
|
||||||
|
|
||||||
UpdateSightRange(deltaTime);
|
UpdateSightRange(deltaTime);
|
||||||
UpdateSoundRange(deltaTime);
|
UpdateSoundRange(deltaTime);
|
||||||
@@ -4096,7 +4123,13 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient && !isNetworkMessage) { return; }
|
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient && !isNetworkMessage) { return; }
|
||||||
if (Screen.Selected != GameMain.GameScreen) { return; }
|
if (Screen.Selected != GameMain.GameScreen) { return; }
|
||||||
if (newStun > 0 && Params.Health.StunImmunity) { return; }
|
if (newStun > 0 && Params.Health.StunImmunity)
|
||||||
|
{
|
||||||
|
if (EmpVulnerability <= 0 || CharacterHealth.GetAfflictionStrength("emp", allowLimbAfflictions: false) <= 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
if ((newStun <= Stun && !allowStunDecrease) || !MathUtils.IsValid(newStun)) { return; }
|
if ((newStun <= Stun && !allowStunDecrease) || !MathUtils.IsValid(newStun)) { return; }
|
||||||
if (Math.Sign(newStun) != Math.Sign(Stun))
|
if (Math.Sign(newStun) != Math.Sign(Stun))
|
||||||
{
|
{
|
||||||
@@ -4345,6 +4378,9 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
foreach (Item heldItem in HeldItems.ToList())
|
foreach (Item heldItem in HeldItems.ToList())
|
||||||
{
|
{
|
||||||
|
//if the item is both wearable and holdable, and currently worn, don't drop the item
|
||||||
|
var wearable = heldItem.GetComponent<Wearable>();
|
||||||
|
if (wearable is { IsActive: true }) { continue; }
|
||||||
heldItem.Drop(this);
|
heldItem.Drop(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1241,7 +1241,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
partial void OnSkillChanged(Identifier skillIdentifier, float prevLevel, float newLevel);
|
partial void OnSkillChanged(Identifier skillIdentifier, float prevLevel, float newLevel);
|
||||||
|
|
||||||
public void GiveExperience(int amount, bool isMissionExperience = false)
|
public void GiveExperience(int amount)
|
||||||
{
|
{
|
||||||
int prevAmount = ExperiencePoints;
|
int prevAmount = ExperiencePoints;
|
||||||
|
|
||||||
@@ -1295,6 +1295,18 @@ namespace Barotrauma
|
|||||||
return experienceRequired + ExperienceRequiredPerLevel(level);
|
return experienceRequired + ExperienceRequiredPerLevel(level);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int GetExperienceRequiredForLevel(int level)
|
||||||
|
{
|
||||||
|
int currentLevel = GetCurrentLevel(out int experienceRequired);
|
||||||
|
if (currentLevel >= level) { return 0; }
|
||||||
|
int required = experienceRequired;
|
||||||
|
for (int i = currentLevel + 1; i <= level; i++)
|
||||||
|
{
|
||||||
|
required += ExperienceRequiredPerLevel(i);
|
||||||
|
}
|
||||||
|
return required;
|
||||||
|
}
|
||||||
|
|
||||||
public int GetCurrentLevel()
|
public int GetCurrentLevel()
|
||||||
{
|
{
|
||||||
return GetCurrentLevel(out _);
|
return GetCurrentLevel(out _);
|
||||||
@@ -1312,7 +1324,7 @@ namespace Barotrauma
|
|||||||
return level;
|
return level;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int ExperienceRequiredPerLevel(int level)
|
private static int ExperienceRequiredPerLevel(int level)
|
||||||
{
|
{
|
||||||
return BaseExperienceRequired + AddedExperienceRequiredPerLevel * level;
|
return BaseExperienceRequired + AddedExperienceRequiredPerLevel * level;
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-2
@@ -61,6 +61,9 @@ namespace Barotrauma
|
|||||||
[Serialize(true, IsPropertySaveable.Yes, description: "Explosion damage is applied per each affected limb. Should this affliction damage be divided by the count of affected limbs (1-15) or applied in full? Default: true. Only affects explosions."), Editable]
|
[Serialize(true, IsPropertySaveable.Yes, description: "Explosion damage is applied per each affected limb. Should this affliction damage be divided by the count of affected limbs (1-15) or applied in full? Default: true. Only affects explosions."), Editable]
|
||||||
public bool DivideByLimbCount { get; set; }
|
public bool DivideByLimbCount { get; set; }
|
||||||
|
|
||||||
|
[Serialize(false, IsPropertySaveable.Yes, description: "Is the damage relative to the max vitality (percentage) or absolute (normal)"), Editable]
|
||||||
|
public bool MultiplyByMaxVitality { get; private set; }
|
||||||
|
|
||||||
public float DamagePerSecond;
|
public float DamagePerSecond;
|
||||||
public float DamagePerSecondTimer;
|
public float DamagePerSecondTimer;
|
||||||
public float PreviousVitalityDecrease;
|
public float PreviousVitalityDecrease;
|
||||||
@@ -104,6 +107,15 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Copy properties here instead of using SerializableProperties (with reflection).
|
||||||
|
/// </summary>
|
||||||
|
public void CopyProperties(Affliction source)
|
||||||
|
{
|
||||||
|
Probability = source.Probability;
|
||||||
|
DivideByLimbCount = source.DivideByLimbCount;
|
||||||
|
MultiplyByMaxVitality = source.MultiplyByMaxVitality;
|
||||||
|
}
|
||||||
|
|
||||||
public void Serialize(XElement element)
|
public void Serialize(XElement element)
|
||||||
{
|
{
|
||||||
@@ -115,10 +127,10 @@ namespace Barotrauma
|
|||||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
|
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Affliction CreateMultiplied(float multiplier, float probability)
|
public Affliction CreateMultiplied(float multiplier, Affliction affliction)
|
||||||
{
|
{
|
||||||
var instance = Prefab.Instantiate(NonClampedStrength * multiplier, Source);
|
var instance = Prefab.Instantiate(NonClampedStrength * multiplier, Source);
|
||||||
instance.Probability = probability;
|
instance.CopyProperties(affliction);
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -693,8 +693,15 @@ namespace Barotrauma
|
|||||||
if (Character.Params.IsMachine && !newAffliction.Prefab.AffectMachines) { return; }
|
if (Character.Params.IsMachine && !newAffliction.Prefab.AffectMachines) { return; }
|
||||||
if (!DoesBleed && newAffliction is AfflictionBleeding) { return; }
|
if (!DoesBleed && newAffliction is AfflictionBleeding) { return; }
|
||||||
if (!Character.NeedsOxygen && newAffliction.Prefab == AfflictionPrefab.OxygenLow) { return; }
|
if (!Character.NeedsOxygen && newAffliction.Prefab == AfflictionPrefab.OxygenLow) { return; }
|
||||||
if (Character.Params.Health.StunImmunity && newAffliction.Prefab.AfflictionType == "stun") { return; }
|
if (Character.Params.Health.StunImmunity && newAffliction.Prefab.AfflictionType == "stun")
|
||||||
|
{
|
||||||
|
if (Character.EmpVulnerability <= 0 || GetAfflictionStrength("emp", allowLimbAfflictions: false) <= 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (Character.Params.Health.PoisonImmunity && newAffliction.Prefab.AfflictionType == "poison") { return; }
|
if (Character.Params.Health.PoisonImmunity && newAffliction.Prefab.AfflictionType == "poison") { return; }
|
||||||
|
if (Character.EmpVulnerability <= 0 && newAffliction.Prefab.AfflictionType == "emp") { return; }
|
||||||
if (newAffliction.Prefab.TargetSpecies.Any() && newAffliction.Prefab.TargetSpecies.None(s => s == Character.SpeciesName)) { return; }
|
if (newAffliction.Prefab.TargetSpecies.Any() && newAffliction.Prefab.TargetSpecies.None(s => s == Character.SpeciesName)) { return; }
|
||||||
|
|
||||||
Affliction existingAffliction = null;
|
Affliction existingAffliction = null;
|
||||||
|
|||||||
@@ -185,7 +185,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool GiveItems(Character character, Submarine submarine, Rand.RandSync randSync = Rand.RandSync.Unsynced, bool createNetworkEvents = true)
|
public bool GiveItems(Character character, Submarine submarine, WayPoint spawnPoint, Rand.RandSync randSync = Rand.RandSync.Unsynced, bool createNetworkEvents = true)
|
||||||
{
|
{
|
||||||
if (ItemSets == null || !ItemSets.Any()) { return false; }
|
if (ItemSets == null || !ItemSets.Any()) { return false; }
|
||||||
var spawnItems = ToolBox.SelectWeightedRandom(ItemSets, it => it.commonness, randSync).element;
|
var spawnItems = ToolBox.SelectWeightedRandom(ItemSets, it => it.commonness, randSync).element;
|
||||||
@@ -196,7 +196,7 @@ namespace Barotrauma
|
|||||||
int amount = itemElement.GetAttributeInt("amount", 1);
|
int amount = itemElement.GetAttributeInt("amount", 1);
|
||||||
for (int i = 0; i < amount; i++)
|
for (int i = 0; i < amount; i++)
|
||||||
{
|
{
|
||||||
InitializeItem(character, itemElement, submarine, this, createNetworkEvents: createNetworkEvents);
|
InitializeItem(character, itemElement, submarine, this, spawnPoint, createNetworkEvents: createNetworkEvents);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -234,7 +234,7 @@ namespace Barotrauma
|
|||||||
return characterInfo;
|
return characterInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void InitializeItem(Character character, XElement itemElement, Submarine submarine, HumanPrefab humanPrefab, Item parentItem = null, bool createNetworkEvents = true)
|
public static void InitializeItem(Character character, XElement itemElement, Submarine submarine, HumanPrefab humanPrefab, WayPoint spawnPoint = null, Item parentItem = null, bool createNetworkEvents = true)
|
||||||
{
|
{
|
||||||
ItemPrefab itemPrefab;
|
ItemPrefab itemPrefab;
|
||||||
string itemIdentifier = itemElement.GetAttributeString("identifier", "");
|
string itemIdentifier = itemElement.GetAttributeString("identifier", "");
|
||||||
@@ -278,7 +278,7 @@ namespace Barotrauma
|
|||||||
IdCard idCardComponent = item.GetComponent<IdCard>();
|
IdCard idCardComponent = item.GetComponent<IdCard>();
|
||||||
if (idCardComponent != null)
|
if (idCardComponent != null)
|
||||||
{
|
{
|
||||||
idCardComponent.Initialize(null, character);
|
idCardComponent.Initialize(spawnPoint, character);
|
||||||
if (submarine != null && (submarine.Info.IsWreck || submarine.Info.IsOutpost))
|
if (submarine != null && (submarine.Info.IsWreck || submarine.Info.IsOutpost))
|
||||||
{
|
{
|
||||||
idCardComponent.SubmarineSpecificID = submarine.SubmarineSpecificIDTag;
|
idCardComponent.SubmarineSpecificID = submarine.SubmarineSpecificIDTag;
|
||||||
@@ -301,7 +301,7 @@ namespace Barotrauma
|
|||||||
int amount = childItemElement.GetAttributeInt("amount", 1);
|
int amount = childItemElement.GetAttributeInt("amount", 1);
|
||||||
for (int i = 0; i < amount; i++)
|
for (int i = 0; i < amount; i++)
|
||||||
{
|
{
|
||||||
InitializeItem(character, childItemElement, submarine, humanPrefab, item, createNetworkEvents);
|
InitializeItem(character, childItemElement, submarine, humanPrefab, spawnPoint, item, createNetworkEvents);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -757,6 +757,10 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
if (!foundMatchingModifier && random > affliction.Probability) { continue; }
|
if (!foundMatchingModifier && random > affliction.Probability) { continue; }
|
||||||
float finalDamageModifier = damageMultiplier;
|
float finalDamageModifier = damageMultiplier;
|
||||||
|
if (affliction.Prefab.AfflictionType == "emp" && character.EmpVulnerability > 0)
|
||||||
|
{
|
||||||
|
finalDamageModifier *= character.EmpVulnerability;
|
||||||
|
}
|
||||||
foreach (DamageModifier damageModifier in tempModifiers)
|
foreach (DamageModifier damageModifier in tempModifiers)
|
||||||
{
|
{
|
||||||
float damageModifierValue = damageModifier.DamageMultiplier;
|
float damageModifierValue = damageModifier.DamageMultiplier;
|
||||||
@@ -766,9 +770,13 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
finalDamageModifier *= damageModifierValue;
|
finalDamageModifier *= damageModifierValue;
|
||||||
}
|
}
|
||||||
|
if (affliction.MultiplyByMaxVitality)
|
||||||
|
{
|
||||||
|
finalDamageModifier *= character.MaxVitality / 100f;
|
||||||
|
}
|
||||||
if (!MathUtils.NearlyEqual(finalDamageModifier, 1.0f))
|
if (!MathUtils.NearlyEqual(finalDamageModifier, 1.0f))
|
||||||
{
|
{
|
||||||
newAffliction = affliction.CreateMultiplied(finalDamageModifier, affliction.Probability);
|
newAffliction = affliction.CreateMultiplied(finalDamageModifier, affliction);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -504,6 +504,9 @@ namespace Barotrauma
|
|||||||
[Serialize(false, IsPropertySaveable.Yes), Editable]
|
[Serialize(false, IsPropertySaveable.Yes), Editable]
|
||||||
public bool PoisonImmunity { get; set; }
|
public bool PoisonImmunity { get; set; }
|
||||||
|
|
||||||
|
[Serialize(0f, IsPropertySaveable.Yes), Editable]
|
||||||
|
public float EmpVulnerability { get; set; }
|
||||||
|
|
||||||
[Serialize(false, IsPropertySaveable.Yes, description: "Can afflictions affect the face/body tint of the character."), Editable]
|
[Serialize(false, IsPropertySaveable.Yes, description: "Can afflictions affect the face/body tint of the character."), Editable]
|
||||||
public bool ApplyAfflictionColors { get; private set; }
|
public bool ApplyAfflictionColors { get; private set; }
|
||||||
|
|
||||||
|
|||||||
+12
-2
@@ -39,8 +39,18 @@ namespace Barotrauma.Abilities
|
|||||||
{
|
{
|
||||||
if (factions.Any())
|
if (factions.Any())
|
||||||
{
|
{
|
||||||
// FIXME there's probably a better way to check the faction affiliated with the mission later
|
if (GameMain.GameSession?.Campaign?.Factions is not { } factions) { return false; }
|
||||||
return mission.ReputationRewards.Keys.Any(factionIdentifier => factions.Contains(factionIdentifier));
|
|
||||||
|
foreach (var (factionIdentifier, amount) in mission.ReputationRewards)
|
||||||
|
{
|
||||||
|
if (amount <= 0) { continue; }
|
||||||
|
if (factions.FirstOrDefault(faction => factionIdentifier == faction.Prefab.Identifier) is Faction faction &&
|
||||||
|
Faction.GetPlayerAffiliationStatus(faction) is FactionAffiliation.Positive)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return missionType.Contains(mission.Prefab.Type);
|
return missionType.Contains(mission.Prefab.Type);
|
||||||
|
|||||||
-1
@@ -23,7 +23,6 @@
|
|||||||
protected override bool MatchesConditionSpecific()
|
protected override bool MatchesConditionSpecific()
|
||||||
{
|
{
|
||||||
Identifier identifier = CharacterAbilityGivePermanentStat.HandlePlaceholders(placeholder, statIdentifier);
|
Identifier identifier = CharacterAbilityGivePermanentStat.HandlePlaceholders(placeholder, statIdentifier);
|
||||||
|
|
||||||
return character.Info.GetSavedStatValue(statType, identifier) >= min;
|
return character.Info.GetSavedStatValue(statType, identifier) >= min;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-1
@@ -5,15 +5,33 @@ internal sealed class CharacterAbilityGiveExperience : CharacterAbility
|
|||||||
public override bool AppliesEffectOnIntervalUpdate => true;
|
public override bool AppliesEffectOnIntervalUpdate => true;
|
||||||
|
|
||||||
private readonly int amount;
|
private readonly int amount;
|
||||||
|
private readonly int level;
|
||||||
|
|
||||||
public CharacterAbilityGiveExperience(CharacterAbilityGroup characterAbilityGroup, ContentXElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
public CharacterAbilityGiveExperience(CharacterAbilityGroup characterAbilityGroup, ContentXElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||||
{
|
{
|
||||||
amount = abilityElement.GetAttributeInt("amount", 0);
|
amount = abilityElement.GetAttributeInt("amount", 0);
|
||||||
|
level = abilityElement.GetAttributeInt("level", 0);
|
||||||
|
|
||||||
|
if (amount == 0 && level == 0)
|
||||||
|
{
|
||||||
|
DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier} - no exp amount or level defined in {nameof(CharacterAbilityGiveExperience)}.");
|
||||||
|
}
|
||||||
|
if (amount > 0 && level > 0)
|
||||||
|
{
|
||||||
|
DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier} - {nameof(CharacterAbilityGiveExperience)} defines both an exp amount and a level.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ApplyEffectSpecific(Character targetCharacter)
|
private void ApplyEffectSpecific(Character targetCharacter)
|
||||||
{
|
{
|
||||||
targetCharacter.Info?.GiveExperience(amount);
|
if (amount != 0)
|
||||||
|
{
|
||||||
|
targetCharacter.Info?.GiveExperience(amount);
|
||||||
|
}
|
||||||
|
if (level > 0)
|
||||||
|
{
|
||||||
|
targetCharacter.Info?.GiveExperience(targetCharacter.Info.GetExperienceRequiredForLevel(level));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void ApplyEffect(AbilityObject abilityObject)
|
protected override void ApplyEffect(AbilityObject abilityObject)
|
||||||
|
|||||||
-26
@@ -1,26 +0,0 @@
|
|||||||
namespace Barotrauma.Abilities
|
|
||||||
{
|
|
||||||
class CharacterAbilityRevive : CharacterAbility
|
|
||||||
{
|
|
||||||
public override bool AppliesEffectOnIntervalUpdate => true;
|
|
||||||
|
|
||||||
public CharacterAbilityRevive(CharacterAbilityGroup characterAbilityGroup, ContentXElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ApplyEffectSpecific()
|
|
||||||
{
|
|
||||||
Character.Revive(removeAllAfflictions: false);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void ApplyEffect()
|
|
||||||
{
|
|
||||||
ApplyEffectSpecific();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void ApplyEffect(AbilityObject abilityObject)
|
|
||||||
{
|
|
||||||
ApplyEffectSpecific();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+2
-4
@@ -58,10 +58,8 @@ namespace Barotrauma.Abilities
|
|||||||
foreach (Identifier identifier in selectedTalentTree)
|
foreach (Identifier identifier in selectedTalentTree)
|
||||||
{
|
{
|
||||||
if (Character.HasTalent(identifier)) { continue; }
|
if (Character.HasTalent(identifier)) { continue; }
|
||||||
if (Character.GiveTalent(identifier))
|
|
||||||
{
|
Character.GiveTalent(identifier);
|
||||||
Character.Info.AdditionalTalentPoints++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool IsShowCaseTalent(Identifier identifier, TalentOption option)
|
static bool IsShowCaseTalent(Identifier identifier, TalentOption option)
|
||||||
|
|||||||
@@ -141,23 +141,13 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (talentOptionStage.TalentIdentifiers.Contains(talentIdentifier))
|
if (talentOptionStage.TalentIdentifiers.Contains(talentIdentifier))
|
||||||
{
|
{
|
||||||
return TalentTreeMeetsRequirements(talentTree, subTree, selectedTalents);
|
return !talentOptionStage.HasMaxTalents(selectedTalents) && TalentTreeMeetsRequirements(talentTree, subTree, selectedTalents);
|
||||||
}
|
}
|
||||||
bool optionStageCompleted = talentOptionStage.HasEnoughTalents(selectedTalents);
|
bool optionStageCompleted = talentOptionStage.HasEnoughTalents(selectedTalents);
|
||||||
if (!optionStageCompleted)
|
if (!optionStageCompleted)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*bool hasTalentInThisTier = talentOptionStage.HasMaxTalents(selectedTalents);
|
|
||||||
if (!hasTalentInThisTier)
|
|
||||||
{
|
|
||||||
if (talentOptionStage.TalentIdentifiers.Contains(talentIdentifier))
|
|
||||||
{
|
|
||||||
return TalentTreeMeetsRequirements(talentTree, subTree, selectedTalents);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}*/
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+14
-24
@@ -67,10 +67,10 @@ namespace Barotrauma
|
|||||||
.ToImmutableHashSet();
|
.ToImmutableHashSet();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Result<ContentFile, LoadError> CreateFromXElement(ContentPackage contentPackage, XElement element)
|
public static Result<ContentFile, ContentPackage.LoadError> CreateFromXElement(ContentPackage contentPackage, XElement element)
|
||||||
{
|
{
|
||||||
static Result<ContentFile, LoadError> fail(string error, Exception? exception = null)
|
static Result<ContentFile, ContentPackage.LoadError> fail(string error, Exception? exception = null)
|
||||||
=> Result<ContentFile, LoadError>.Failure(new LoadError(error, exception));
|
=> Result<ContentFile, ContentPackage.LoadError>.Failure(new ContentPackage.LoadError(error, exception));
|
||||||
|
|
||||||
Identifier elemName = element.NameAsIdentifier();
|
Identifier elemName = element.NameAsIdentifier();
|
||||||
var type = Types.FirstOrDefault(t => t.Names.Contains(elemName));
|
var type = Types.FirstOrDefault(t => t.Names.Contains(elemName));
|
||||||
@@ -83,6 +83,8 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
return fail($"No content path defined for file of type \"{elemName}\"");
|
return fail($"No content path defined for file of type \"{elemName}\"");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
using var errorCatcher = DebugConsole.ErrorCatcher.Create();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
filePath = type.MutateContentPath(filePath);
|
filePath = type.MutateContentPath(filePath);
|
||||||
@@ -90,10 +92,16 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
return fail($"Failed to load file \"{filePath}\" of type \"{elemName}\": file not found.");
|
return fail($"Failed to load file \"{filePath}\" of type \"{elemName}\": file not found.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var file = type.CreateInstance(contentPackage, filePath);
|
var file = type.CreateInstance(contentPackage, filePath);
|
||||||
return file is null
|
if (file is null) { return fail($"Content type {type.Type.Name} is not implemented correctly"); }
|
||||||
? throw new Exception($"Content type is not implemented correctly")
|
|
||||||
: Result<ContentFile, LoadError>.Success(file);
|
if (errorCatcher.Errors.Any())
|
||||||
|
{
|
||||||
|
return fail(
|
||||||
|
$"Errors were issued to the debug console when loading \"{filePath}\" of type \"{elemName}\"");
|
||||||
|
}
|
||||||
|
return Result<ContentFile, ContentPackage.LoadError>.Success(file);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@@ -123,23 +131,5 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
|
|
||||||
public bool NotSyncedInMultiplayer => Types.Any(t => t.Type == GetType() && t.NotSyncedInMultiplayer);
|
public bool NotSyncedInMultiplayer => Types.Any(t => t.Type == GetType() && t.NotSyncedInMultiplayer);
|
||||||
|
|
||||||
public readonly struct LoadError
|
|
||||||
{
|
|
||||||
public readonly string Message;
|
|
||||||
public readonly Exception? Exception;
|
|
||||||
|
|
||||||
public LoadError(string message, Exception? exception)
|
|
||||||
{
|
|
||||||
Message = message;
|
|
||||||
Exception = exception;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
=> Message
|
|
||||||
+ (Exception is { StackTrace: var stackTrace }
|
|
||||||
? '\n' + stackTrace.CleanupStackTrace()
|
|
||||||
: string.Empty);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+97
-49
@@ -6,7 +6,6 @@ using System.Collections.Generic;
|
|||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
|
|
||||||
@@ -14,6 +13,15 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
public abstract class ContentPackage
|
public abstract class ContentPackage
|
||||||
{
|
{
|
||||||
|
public readonly record struct LoadError(string Message, Exception? Exception)
|
||||||
|
{
|
||||||
|
public override string ToString()
|
||||||
|
=> Message
|
||||||
|
+ (Exception is { StackTrace: var stackTrace }
|
||||||
|
? '\n' + stackTrace.CleanupStackTrace()
|
||||||
|
: string.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
public static readonly Version MinimumHashCompatibleVersion = new Version(0, 18, 13, 0);
|
public static readonly Version MinimumHashCompatibleVersion = new Version(0, 18, 13, 0);
|
||||||
|
|
||||||
public const string LocalModsDir = "LocalMods";
|
public const string LocalModsDir = "LocalMods";
|
||||||
@@ -37,12 +45,30 @@ namespace Barotrauma
|
|||||||
public readonly Option<DateTime> InstallTime;
|
public readonly Option<DateTime> InstallTime;
|
||||||
|
|
||||||
public ImmutableArray<ContentFile> Files { get; private set; }
|
public ImmutableArray<ContentFile> Files { get; private set; }
|
||||||
public ImmutableArray<ContentFile.LoadError> Errors { get; private set; }
|
|
||||||
|
/// <summary>
|
||||||
|
/// Errors that occurred when loading this content package.
|
||||||
|
/// Currently, all errors are considered fatal and the game
|
||||||
|
/// will refuse to load a content package that has any errors.
|
||||||
|
/// </summary>
|
||||||
|
public ImmutableArray<LoadError> FatalLoadErrors { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An error that occurred when trying to enable this mod.
|
||||||
|
/// This field doesn't directly affect whether or not this mod
|
||||||
|
/// can be enabled, but if it's been set to anything other than
|
||||||
|
/// Option.None then the game has already refused to enable it
|
||||||
|
/// at least once.
|
||||||
|
/// </summary>
|
||||||
|
public Option<ContentPackageManager.LoadProgress.Error> EnableError { get; private set; }
|
||||||
|
= Option.None;
|
||||||
|
|
||||||
|
public bool HasAnyErrors => FatalLoadErrors.Length > 0 || EnableError.IsSome();
|
||||||
|
|
||||||
public async Task<bool> IsUpToDate()
|
public async Task<bool> IsUpToDate()
|
||||||
{
|
{
|
||||||
if (!UgcId.TryUnwrap(out var ugcId)) { return true; }
|
if (!UgcId.TryUnwrap(out var ugcId)) { return true; }
|
||||||
if (!(ugcId is SteamWorkshopId steamWorkshopId)) { return true; }
|
if (ugcId is not SteamWorkshopId steamWorkshopId) { return true; }
|
||||||
if (!InstallTime.TryUnwrap(out var installTime)) { return true; }
|
if (!InstallTime.TryUnwrap(out var installTime)) { return true; }
|
||||||
|
|
||||||
Steamworks.Ugc.Item? item = await SteamManager.Workshop.GetItem(steamWorkshopId.Value);
|
Steamworks.Ugc.Item? item = await SteamManager.Workshop.GetItem(steamWorkshopId.Value);
|
||||||
@@ -55,20 +81,25 @@ namespace Barotrauma
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Does the content package include some content that needs to match between all players in multiplayer.
|
/// Does the content package include some content that needs to match between all players in multiplayer.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool HasMultiplayerSyncedContent { get; private set; }
|
public bool HasMultiplayerSyncedContent { get; }
|
||||||
|
|
||||||
protected ContentPackage(XDocument doc, string path)
|
protected ContentPackage(XDocument doc, string path)
|
||||||
{
|
{
|
||||||
|
using var errorCatcher = DebugConsole.ErrorCatcher.Create();
|
||||||
|
|
||||||
Path = path.CleanUpPathCrossPlatform();
|
Path = path.CleanUpPathCrossPlatform();
|
||||||
XElement rootElement = doc.Root ?? throw new NullReferenceException("XML document is invalid: root element is null.");
|
XElement rootElement = doc.Root ?? throw new NullReferenceException("XML document is invalid: root element is null.");
|
||||||
|
|
||||||
Name = rootElement.GetAttributeString("name", "").Trim();
|
Name = rootElement.GetAttributeString("name", "").Trim();
|
||||||
AltNames = rootElement.GetAttributeStringArray("altnames", Array.Empty<string>())
|
AltNames = rootElement.GetAttributeStringArray("altnames", Array.Empty<string>())
|
||||||
.Select(n => n.Trim()).ToImmutableArray();
|
.Select(n => n.Trim()).ToImmutableArray();
|
||||||
AssertCondition(!string.IsNullOrEmpty(Name), "Name is null or empty");
|
|
||||||
|
|
||||||
UInt64 steamWorkshopId = rootElement.GetAttributeUInt64("steamworkshopid", 0);
|
UInt64 steamWorkshopId = rootElement.GetAttributeUInt64("steamworkshopid", 0);
|
||||||
|
|
||||||
|
if (Name.IsNullOrWhiteSpace() && AltNames.Any())
|
||||||
|
{
|
||||||
|
Name = AltNames.First();
|
||||||
|
}
|
||||||
|
|
||||||
UgcId = steamWorkshopId != 0
|
UgcId = steamWorkshopId != 0
|
||||||
? Option<ContentPackageId>.Some(new SteamWorkshopId(steamWorkshopId))
|
? Option<ContentPackageId>.Some(new SteamWorkshopId(steamWorkshopId))
|
||||||
: Option<ContentPackageId>.None();
|
: Option<ContentPackageId>.None();
|
||||||
@@ -85,23 +116,31 @@ namespace Barotrauma
|
|||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
Files = fileResults
|
Files = fileResults
|
||||||
.OfType<Success<ContentFile, ContentFile.LoadError>>()
|
.Successes()
|
||||||
.Select(f => f.Value)
|
|
||||||
.ToImmutableArray();
|
.ToImmutableArray();
|
||||||
|
|
||||||
Errors = fileResults
|
FatalLoadErrors = fileResults
|
||||||
.OfType<Failure<ContentFile, ContentFile.LoadError>>()
|
.Failures()
|
||||||
.Select(f => f.Error)
|
|
||||||
.ToImmutableArray();
|
.ToImmutableArray();
|
||||||
|
|
||||||
|
AssertCondition(!string.IsNullOrEmpty(Name), $"{nameof(Name)} is null or empty");
|
||||||
|
|
||||||
HasMultiplayerSyncedContent = Files.Any(f => !f.NotSyncedInMultiplayer);
|
HasMultiplayerSyncedContent = Files.Any(f => !f.NotSyncedInMultiplayer);
|
||||||
|
|
||||||
Hash = CalculateHash();
|
Hash = CalculateHash();
|
||||||
var expectedHash = rootElement.GetAttributeString("expectedhash", "");
|
var expectedHash = rootElement.GetAttributeString("expectedhash", "");
|
||||||
if (HashMismatches(expectedHash))
|
if (HashMismatches(expectedHash))
|
||||||
{
|
{
|
||||||
DebugConsole.ThrowError($"Hash calculation for content package \"{Name}\" didn't match expected hash ({Hash.StringRepresentation} != {expectedHash})");
|
FatalLoadErrors = FatalLoadErrors.Add(
|
||||||
|
new LoadError(
|
||||||
|
Message: $"Hash calculation returned {Hash.StringRepresentation}, expected {expectedHash}",
|
||||||
|
Exception: null
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FatalLoadErrors = FatalLoadErrors
|
||||||
|
.Concat(errorCatcher.Errors.Select(err => new LoadError(err.Text, null)))
|
||||||
|
.ToImmutableArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool HashMismatches(string expectedHash)
|
public bool HashMismatches(string expectedHash)
|
||||||
@@ -122,21 +161,21 @@ namespace Barotrauma
|
|||||||
public bool NameMatches(string name)
|
public bool NameMatches(string name)
|
||||||
=> NameMatches(name.ToIdentifier());
|
=> NameMatches(name.ToIdentifier());
|
||||||
|
|
||||||
public static ContentPackage? TryLoad(string path)
|
public static Result<ContentPackage, Exception> TryLoad(string path)
|
||||||
{
|
{
|
||||||
|
var (success, failure) = Result<ContentPackage, Exception>.GetFactoryMethods();
|
||||||
|
|
||||||
XDocument doc = XMLExtensions.TryLoadXml(path);
|
XDocument doc = XMLExtensions.TryLoadXml(path);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return doc.Root.GetAttributeBool("corepackage", false)
|
return success(doc.Root.GetAttributeBool("corepackage", false)
|
||||||
? (ContentPackage)new CorePackage(doc, path)
|
? new CorePackage(doc, path)
|
||||||
: new RegularPackage(doc, path);
|
: new RegularPackage(doc, path));
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
e = e.GetInnermost();
|
return failure(e.GetInnermost());
|
||||||
DebugConsole.ThrowError($"{e.Message}: {e.StackTrace}");
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,7 +220,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (!condition)
|
if (!condition)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException($"Failed to load \"{Name}\" at {Path}: {errorMsg}");
|
FatalLoadErrors = FatalLoadErrors.Add(new LoadError(errorMsg, null));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,17 +240,19 @@ namespace Barotrauma
|
|||||||
Failure
|
Failure
|
||||||
}
|
}
|
||||||
|
|
||||||
public LoadResult LoadPackage()
|
public LoadResult LoadContent()
|
||||||
{
|
{
|
||||||
foreach (var p in LoadPackageEnumerable())
|
foreach (var p in LoadContentEnumerable())
|
||||||
{
|
{
|
||||||
if (p.Exception != null) { return LoadResult.Failure; }
|
if (p.Result.IsFailure) { return LoadResult.Failure; }
|
||||||
}
|
}
|
||||||
return LoadResult.Success;
|
return LoadResult.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<ContentPackageManager.LoadProgress> LoadPackageEnumerable()
|
public IEnumerable<ContentPackageManager.LoadProgress> LoadContentEnumerable()
|
||||||
{
|
{
|
||||||
|
using var errorCatcher = DebugConsole.ErrorCatcher.Create();
|
||||||
|
|
||||||
ContentFile[] getFilesToLoad(Predicate<ContentFile> predicate)
|
ContentFile[] getFilesToLoad(Predicate<ContentFile> predicate)
|
||||||
=> Files.Where(predicate.Invoke).ToArray()
|
=> Files.Where(predicate.Invoke).ToArray()
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
@@ -227,6 +268,7 @@ namespace Barotrauma
|
|||||||
for (int i = 0; i < filesToLoad.Length; i++)
|
for (int i = 0; i < filesToLoad.Length; i++)
|
||||||
{
|
{
|
||||||
Exception? exception = null;
|
Exception? exception = null;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
//do not allow exceptions thrown here to crash the game
|
//do not allow exceptions thrown here to crash the game
|
||||||
@@ -234,20 +276,30 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
var innermost = e.GetInnermost();
|
||||||
|
DebugConsole.LogError($"Failed to load \"{filesToLoad[i].Path}\": {innermost.Message}\n{innermost.StackTrace}");
|
||||||
exception = e;
|
exception = e;
|
||||||
}
|
}
|
||||||
if (exception != null)
|
if (exception != null)
|
||||||
{
|
{
|
||||||
yield return ContentPackageManager.LoadProgress.Failure(exception);
|
yield return ContentPackageManager.LoadProgress.Failure(exception);
|
||||||
break;
|
yield break;
|
||||||
}
|
}
|
||||||
yield return new ContentPackageManager.LoadProgress((i + indexOffset) / (float)Files.Length);
|
|
||||||
|
if (errorCatcher.Errors.Any())
|
||||||
|
{
|
||||||
|
yield return ContentPackageManager.LoadProgress.Failure(
|
||||||
|
ContentPackageManager.LoadProgress.Error
|
||||||
|
.Reason.ConsoleErrorsThrown);
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
yield return ContentPackageManager.LoadProgress.Progress((i + indexOffset) / (float)Files.Length);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Load the UI and text files first. This is to allow the game
|
//Load the UI and text files first. This is to allow the game
|
||||||
//to render the text in the loading screen as soon as possible.
|
//to render the text in the loading screen as soon as possible.
|
||||||
var priorityFiles = getFilesToLoad(f => f is UIStyleFile || f is TextFile);
|
var priorityFiles = getFilesToLoad(f => f is UIStyleFile or TextFile);
|
||||||
|
|
||||||
var remainder = getFilesToLoad(f => !priorityFiles.Contains(f));
|
var remainder = getFilesToLoad(f => !priorityFiles.Contains(f));
|
||||||
|
|
||||||
@@ -257,19 +309,20 @@ namespace Barotrauma
|
|||||||
|
|
||||||
foreach (var p in loadEnumerable)
|
foreach (var p in loadEnumerable)
|
||||||
{
|
{
|
||||||
if (p.Exception != null)
|
if (p.Result.TryUnwrapFailure(out var failure))
|
||||||
{
|
{
|
||||||
HandleLoadException(p.Exception);
|
errorCatcher.Dispose();
|
||||||
|
UnloadContent();
|
||||||
|
EnableError = Option.Some(failure);
|
||||||
yield return p;
|
yield return p;
|
||||||
break;
|
yield break;
|
||||||
}
|
}
|
||||||
yield return p;
|
yield return p;
|
||||||
}
|
}
|
||||||
|
errorCatcher.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract void HandleLoadException(Exception e);
|
public void UnloadContent()
|
||||||
|
|
||||||
public void UnloadPackage()
|
|
||||||
{
|
{
|
||||||
Files.ForEach(f => f.UnloadFile());
|
Files.ForEach(f => f.UnloadFile());
|
||||||
}
|
}
|
||||||
@@ -284,21 +337,16 @@ namespace Barotrauma
|
|||||||
.Select(e => ContentFile.CreateFromXElement(this, e))
|
.Select(e => ContentFile.CreateFromXElement(this, e))
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
foreach (var result in fileResults)
|
foreach (var file in fileResults.Successes())
|
||||||
{
|
{
|
||||||
switch (result)
|
if (file is BaseSubFile or ItemAssemblyFile)
|
||||||
{
|
{
|
||||||
case Success<ContentFile, ContentFile.LoadError> { Value: var file }:
|
newFileList.Add(file);
|
||||||
if (file is BaseSubFile || file is ItemAssemblyFile)
|
}
|
||||||
{
|
else
|
||||||
newFileList.Add(file);
|
{
|
||||||
}
|
var existingFile = Files.FirstOrDefault(f => f.Path == file.Path);
|
||||||
else
|
newFileList.Add(existingFile ?? file);
|
||||||
{
|
|
||||||
var existingFile = Files.FirstOrDefault(f => f.Path == file.Path);
|
|
||||||
newFileList.Add(existingFile ?? file);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -331,16 +379,16 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public void LogErrors()
|
public void LogErrors()
|
||||||
{
|
{
|
||||||
if (!Errors.Any())
|
if (!FatalLoadErrors.Any())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
DebugConsole.AddWarning(
|
DebugConsole.AddWarning(
|
||||||
$"The following errors occurred while loading the content package \"{Name}\". The package might not work correctly.\n" +
|
$"The following errors occurred while loading the content package \"{Name}\". The package might not work correctly.\n" +
|
||||||
string.Join('\n', Errors.Select(errorToStr)));
|
string.Join('\n', FatalLoadErrors.Select(errorToStr)));
|
||||||
|
|
||||||
static string errorToStr(ContentFile.LoadError error)
|
static string errorToStr(LoadError error)
|
||||||
=> error.ToString();
|
=> error.ToString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
-5
@@ -43,10 +43,5 @@ namespace Barotrauma
|
|||||||
"Core package requires at least one of the following content types: " +
|
"Core package requires at least one of the following content types: " +
|
||||||
string.Join(", ", missingFileTypes.Select(t => t.Type.Name)));
|
string.Join(", ", missingFileTypes.Select(t => t.Type.Name)));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void HandleLoadException(Exception e)
|
|
||||||
{
|
|
||||||
throw new Exception($"An exception was thrown while loading \"{Name}\"", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
-7
@@ -1,4 +1,3 @@
|
|||||||
using System;
|
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
@@ -9,11 +8,5 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
AssertCondition(!doc.Root.GetAttributeBool("corepackage", false), "Expected a regular package, got a core package");
|
AssertCondition(!doc.Root.GetAttributeBool("corepackage", false), "Expected a regular package, got a core package");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void HandleLoadException(Exception e)
|
|
||||||
{
|
|
||||||
UnloadPackage();
|
|
||||||
DebugConsole.ThrowError($"Failed to load package \"{Name}\"", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+106
-66
@@ -47,18 +47,19 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
var oldCore = Core;
|
var oldCore = Core;
|
||||||
if (newCore == oldCore) { yield break; }
|
if (newCore == oldCore) { yield break; }
|
||||||
Core?.UnloadPackage();
|
if (newCore.FatalLoadErrors.Any()) { yield break; }
|
||||||
|
Core?.UnloadContent();
|
||||||
Core = newCore;
|
Core = newCore;
|
||||||
foreach (var p in newCore.LoadPackageEnumerable()) { yield return p; }
|
foreach (var p in newCore.LoadContentEnumerable()) { yield return p; }
|
||||||
SortContent();
|
SortContent();
|
||||||
yield return new LoadProgress(1.0f);
|
yield return LoadProgress.Progress(1.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void ReloadCore()
|
public static void ReloadCore()
|
||||||
{
|
{
|
||||||
if (Core == null) { return; }
|
if (Core == null) { return; }
|
||||||
Core.UnloadPackage();
|
Core.UnloadContent();
|
||||||
Core.LoadPackage();
|
Core.LoadContent();
|
||||||
SortContent();
|
SortContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,10 +80,14 @@ namespace Barotrauma
|
|||||||
if (ReferenceEquals(inNewRegular, regular)) { yield break; }
|
if (ReferenceEquals(inNewRegular, regular)) { yield break; }
|
||||||
if (inNewRegular.SequenceEqual(regular)) { yield break; }
|
if (inNewRegular.SequenceEqual(regular)) { yield break; }
|
||||||
ThrowIfDuplicates(inNewRegular);
|
ThrowIfDuplicates(inNewRegular);
|
||||||
var newRegular = inNewRegular.ToList();
|
var newRegular = inNewRegular
|
||||||
|
// Refuse to enable packages with load errors
|
||||||
|
// so people are forced away from broken mods
|
||||||
|
.Where(r => !r.FatalLoadErrors.Any())
|
||||||
|
.ToList();
|
||||||
IEnumerable<RegularPackage> toUnload = regular.Where(r => !newRegular.Contains(r));
|
IEnumerable<RegularPackage> toUnload = regular.Where(r => !newRegular.Contains(r));
|
||||||
RegularPackage[] toLoad = newRegular.Where(r => !regular.Contains(r)).ToArray();
|
RegularPackage[] toLoad = newRegular.Where(r => !regular.Contains(r)).ToArray();
|
||||||
toUnload.ForEach(r => r.UnloadPackage());
|
toUnload.ForEach(r => r.UnloadContent());
|
||||||
|
|
||||||
Range<float> loadingRange = new Range<float>(0.0f, 1.0f);
|
Range<float> loadingRange = new Range<float>(0.0f, 1.0f);
|
||||||
|
|
||||||
@@ -90,9 +95,9 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
var package = toLoad[i];
|
var package = toLoad[i];
|
||||||
loadingRange = new Range<float>(i / (float)toLoad.Length, (i + 1) / (float)toLoad.Length);
|
loadingRange = new Range<float>(i / (float)toLoad.Length, (i + 1) / (float)toLoad.Length);
|
||||||
foreach (var progress in package.LoadPackageEnumerable())
|
foreach (var progress in package.LoadContentEnumerable())
|
||||||
{
|
{
|
||||||
if (progress.Exception != null)
|
if (progress.Result.IsFailure)
|
||||||
{
|
{
|
||||||
//If an exception was thrown while loading this package, refuse to add it to the list of enabled packages
|
//If an exception was thrown while loading this package, refuse to add it to the list of enabled packages
|
||||||
newRegular.Remove(package);
|
newRegular.Remove(package);
|
||||||
@@ -103,7 +108,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
regular.Clear(); regular.AddRange(newRegular);
|
regular.Clear(); regular.AddRange(newRegular);
|
||||||
SortContent();
|
SortContent();
|
||||||
yield return new LoadProgress(1.0f);
|
yield return LoadProgress.Progress(1.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void ThrowIfDuplicates(IEnumerable<ContentPackage> pkgs)
|
public static void ThrowIfDuplicates(IEnumerable<ContentPackage> pkgs)
|
||||||
@@ -231,10 +236,12 @@ namespace Barotrauma
|
|||||||
public sealed partial class PackageSource : ICollection<ContentPackage>
|
public sealed partial class PackageSource : ICollection<ContentPackage>
|
||||||
{
|
{
|
||||||
private readonly Predicate<string>? skipPredicate;
|
private readonly Predicate<string>? skipPredicate;
|
||||||
|
private readonly Action<string, Exception>? onLoadFail;
|
||||||
|
|
||||||
public PackageSource(string dir, Predicate<string>? skipPredicate)
|
public PackageSource(string dir, Predicate<string>? skipPredicate, Action<string, Exception>? onLoadFail)
|
||||||
{
|
{
|
||||||
this.skipPredicate = skipPredicate;
|
this.skipPredicate = skipPredicate;
|
||||||
|
this.onLoadFail = onLoadFail;
|
||||||
directory = dir;
|
directory = dir;
|
||||||
Directory.CreateDirectory(directory);
|
Directory.CreateDirectory(directory);
|
||||||
}
|
}
|
||||||
@@ -278,25 +285,30 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
var fileListPath = Path.Combine(subDir, ContentPackage.FileListFileName).CleanUpPathCrossPlatform();
|
var fileListPath = Path.Combine(subDir, ContentPackage.FileListFileName).CleanUpPathCrossPlatform();
|
||||||
if (this.Any(p => p.Path.Equals(fileListPath, StringComparison.OrdinalIgnoreCase))) { continue; }
|
if (this.Any(p => p.Path.Equals(fileListPath, StringComparison.OrdinalIgnoreCase))) { continue; }
|
||||||
if (File.Exists(fileListPath))
|
|
||||||
|
if (!File.Exists(fileListPath)) { continue; }
|
||||||
|
if (skipPredicate?.Invoke(fileListPath) is true) { continue; }
|
||||||
|
|
||||||
|
var result = ContentPackage.TryLoad(fileListPath);
|
||||||
|
if (!result.TryUnwrapSuccess(out var newPackage))
|
||||||
{
|
{
|
||||||
if (skipPredicate?.Invoke(fileListPath) is true) { continue; }
|
onLoadFail?.Invoke(
|
||||||
|
fileListPath,
|
||||||
ContentPackage? newPackage = ContentPackage.TryLoad(fileListPath);
|
result.TryUnwrapFailure(out var exception) ? exception : throw new Exception("unreachable"));
|
||||||
if (newPackage is CorePackage corePackage)
|
continue;
|
||||||
{
|
|
||||||
corePackages.Add(corePackage);
|
|
||||||
}
|
|
||||||
else if (newPackage is RegularPackage regularPackage)
|
|
||||||
{
|
|
||||||
regularPackages.Add(regularPackage);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(newPackage is null))
|
|
||||||
{
|
|
||||||
Debug.WriteLine($"Loaded \"{newPackage.Name}\"");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch (newPackage)
|
||||||
|
{
|
||||||
|
case CorePackage corePackage:
|
||||||
|
corePackages.Add(corePackage);
|
||||||
|
break;
|
||||||
|
case RegularPackage regularPackage:
|
||||||
|
regularPackages.Add(regularPackage);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.WriteLine($"Loaded \"{newPackage.Name}\"");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -348,8 +360,20 @@ namespace Barotrauma
|
|||||||
public bool IsReadOnly => true;
|
public bool IsReadOnly => true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static readonly PackageSource LocalPackages = new PackageSource(ContentPackage.LocalModsDir, skipPredicate: null);
|
public static readonly PackageSource LocalPackages
|
||||||
public static readonly PackageSource WorkshopPackages = new PackageSource(ContentPackage.WorkshopModsDir, skipPredicate: SteamManager.Workshop.IsInstallingToPath);
|
= new PackageSource(
|
||||||
|
ContentPackage.LocalModsDir,
|
||||||
|
skipPredicate: null,
|
||||||
|
onLoadFail: null);
|
||||||
|
public static readonly PackageSource WorkshopPackages = new PackageSource(
|
||||||
|
ContentPackage.WorkshopModsDir,
|
||||||
|
skipPredicate: SteamManager.Workshop.IsInstallingToPath,
|
||||||
|
onLoadFail: (fileListPath, exception) =>
|
||||||
|
{
|
||||||
|
// Delete Workshop mods that fail to load to
|
||||||
|
// force a reinstall on next launch if necessary
|
||||||
|
Directory.TryDelete(Path.GetDirectoryName(fileListPath)!);
|
||||||
|
});
|
||||||
|
|
||||||
public static CorePackage? VanillaCorePackage { get; private set; } = null;
|
public static CorePackage? VanillaCorePackage { get; private set; } = null;
|
||||||
|
|
||||||
@@ -373,63 +397,77 @@ namespace Barotrauma
|
|||||||
EnabledPackages.DisableRemovedMods();
|
EnabledPackages.DisableRemovedMods();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ContentPackage? ReloadContentPackage(ContentPackage p)
|
public static Result<ContentPackage, Exception> ReloadContentPackage(ContentPackage p)
|
||||||
{
|
{
|
||||||
ContentPackage? newPackage = ContentPackage.TryLoad(p.Path);
|
var result = ContentPackage.TryLoad(p.Path);
|
||||||
if (newPackage is CorePackage core)
|
|
||||||
{
|
|
||||||
if (EnabledPackages.Core == p) { EnabledPackages.SetCore(core); }
|
|
||||||
}
|
|
||||||
else if (newPackage is RegularPackage regular)
|
|
||||||
{
|
|
||||||
int index = EnabledPackages.Regular.IndexOf(p);
|
|
||||||
if (index >= 0)
|
|
||||||
{
|
|
||||||
var newRegular = EnabledPackages.Regular.ToArray();
|
|
||||||
newRegular[index] = regular;
|
|
||||||
EnabledPackages.SetRegular(newRegular);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newPackage != null)
|
if (result.TryUnwrapSuccess(out var newPackage))
|
||||||
{
|
{
|
||||||
|
switch (newPackage)
|
||||||
|
{
|
||||||
|
case CorePackage core:
|
||||||
|
{
|
||||||
|
if (EnabledPackages.Core == p) { EnabledPackages.SetCore(core); }
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case RegularPackage regular:
|
||||||
|
{
|
||||||
|
int index = EnabledPackages.Regular.IndexOf(p);
|
||||||
|
if (index >= 0)
|
||||||
|
{
|
||||||
|
var newRegular = EnabledPackages.Regular.ToArray();
|
||||||
|
newRegular[index] = regular;
|
||||||
|
EnabledPackages.SetRegular(newRegular);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LocalPackages.SwapPackage(p, newPackage);
|
LocalPackages.SwapPackage(p, newPackage);
|
||||||
WorkshopPackages.SwapPackage(p, newPackage);
|
WorkshopPackages.SwapPackage(p, newPackage);
|
||||||
}
|
}
|
||||||
EnabledPackages.DisableRemovedMods();
|
EnabledPackages.DisableRemovedMods();
|
||||||
return newPackage;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly struct LoadProgress
|
public readonly record struct LoadProgress(Result<float, LoadProgress.Error> Result)
|
||||||
{
|
{
|
||||||
public readonly float Value;
|
public readonly record struct Error(
|
||||||
public readonly Exception? Exception;
|
Error.Reason ErrorReason,
|
||||||
|
Option<Exception> Exception)
|
||||||
public LoadProgress(float value)
|
|
||||||
{
|
{
|
||||||
Value = value;
|
public enum Reason { Exception, ConsoleErrorsThrown }
|
||||||
Exception = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private LoadProgress(Exception exception)
|
public Error(Reason reason) : this(reason, Option.None) { }
|
||||||
{
|
public Error(Exception exception) : this(Reason.Exception, Option.Some(exception)) { }
|
||||||
Value = -1f;
|
|
||||||
Exception = exception;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LoadProgress Failure(Exception exception)
|
public static LoadProgress Failure(Exception exception)
|
||||||
=> new LoadProgress(exception);
|
=> new LoadProgress(
|
||||||
|
Result<float, Error>.Failure(new Error(exception)));
|
||||||
|
|
||||||
|
public static LoadProgress Failure(Error.Reason reason)
|
||||||
|
=> new LoadProgress(
|
||||||
|
Result<float, Error>.Failure(new Error(reason)));
|
||||||
|
|
||||||
|
public static LoadProgress Progress(float value)
|
||||||
|
=> new LoadProgress(
|
||||||
|
Result<float, Error>.Success(value));
|
||||||
|
|
||||||
public LoadProgress Transform(Range<float> range)
|
public LoadProgress Transform(Range<float> range)
|
||||||
=> Exception != null
|
=> Result.TryUnwrapSuccess(out var value)
|
||||||
? this
|
? new LoadProgress(
|
||||||
: new LoadProgress(MathHelper.Lerp(range.Start, range.End, Value));
|
Result<float, Error>.Success(
|
||||||
|
MathHelper.Lerp(range.Start, range.End, value)))
|
||||||
|
: this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void LoadVanillaFileList()
|
public static void LoadVanillaFileList()
|
||||||
{
|
{
|
||||||
VanillaCorePackage = new CorePackage(XDocument.Load(VanillaFileList), VanillaFileList);
|
VanillaCorePackage = new CorePackage(XDocument.Load(VanillaFileList), VanillaFileList);
|
||||||
foreach (ContentFile.LoadError error in VanillaCorePackage.Errors)
|
foreach (ContentPackage.LoadError error in VanillaCorePackage.FatalLoadErrors)
|
||||||
{
|
{
|
||||||
DebugConsole.ThrowError(error.ToString());
|
DebugConsole.ThrowError(error.ToString());
|
||||||
}
|
}
|
||||||
@@ -444,6 +482,8 @@ namespace Barotrauma
|
|||||||
|
|
||||||
if (VanillaCorePackage is null) { LoadVanillaFileList(); }
|
if (VanillaCorePackage is null) { LoadVanillaFileList(); }
|
||||||
|
|
||||||
|
SteamManager.Workshop.DeleteUnsubscribedMods();
|
||||||
|
|
||||||
CorePackage enabledCorePackage = VanillaCorePackage!;
|
CorePackage enabledCorePackage = VanillaCorePackage!;
|
||||||
List<RegularPackage> enabledRegularPackages = new List<RegularPackage>();
|
List<RegularPackage> enabledRegularPackages = new List<RegularPackage>();
|
||||||
|
|
||||||
@@ -512,7 +552,7 @@ namespace Barotrauma
|
|||||||
yield return p.Transform(loadingRange);
|
yield return p.Transform(loadingRange);
|
||||||
}
|
}
|
||||||
|
|
||||||
yield return new LoadProgress(1.0f);
|
yield return LoadProgress.Progress(1.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void LogEnabledRegularPackageErrors()
|
public static void LogEnabledRegularPackageErrors()
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using Barotrauma.Steam;
|
|||||||
using FarseerPhysics;
|
using FarseerPhysics;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
@@ -15,12 +16,12 @@ using Barotrauma.MapCreatures.Behavior;
|
|||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
struct ColoredText
|
readonly struct ColoredText
|
||||||
{
|
{
|
||||||
public string Text;
|
public readonly string Text;
|
||||||
public Color Color;
|
public readonly Color Color;
|
||||||
public bool IsCommand;
|
public readonly bool IsCommand;
|
||||||
public bool IsError;
|
public readonly bool IsError;
|
||||||
|
|
||||||
public readonly string Time;
|
public readonly string Time;
|
||||||
|
|
||||||
@@ -31,7 +32,7 @@ namespace Barotrauma
|
|||||||
this.IsCommand = isCommand;
|
this.IsCommand = isCommand;
|
||||||
this.IsError = isError;
|
this.IsError = isError;
|
||||||
|
|
||||||
Time = DateTime.Now.ToString();
|
Time = DateTime.Now.ToString(CultureInfo.InvariantCulture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,13 +92,57 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly Queue<ColoredText> queuedMessages = new Queue<ColoredText>();
|
private static readonly ConcurrentQueue<ColoredText> queuedMessages
|
||||||
|
= new ConcurrentQueue<ColoredText>();
|
||||||
|
|
||||||
|
public static readonly NamedEvent<ColoredText> MessageHandler = new NamedEvent<ColoredText>();
|
||||||
|
|
||||||
|
public struct ErrorCatcher : IDisposable
|
||||||
|
{
|
||||||
|
private readonly List<ColoredText> errors;
|
||||||
|
private readonly bool wasConsoleOpen;
|
||||||
|
private Identifier handlerId;
|
||||||
|
public IReadOnlyList<ColoredText> Errors => errors;
|
||||||
|
|
||||||
|
private ErrorCatcher(Identifier handlerId)
|
||||||
|
{
|
||||||
|
this.handlerId = handlerId;
|
||||||
|
#if CLIENT
|
||||||
|
this.wasConsoleOpen = IsOpen;
|
||||||
|
#else
|
||||||
|
this.wasConsoleOpen = false;
|
||||||
|
#endif
|
||||||
|
this.errors = new List<ColoredText>();
|
||||||
|
|
||||||
|
//create a local variable that can be captured by lambdas
|
||||||
|
var errs = this.errors;
|
||||||
|
|
||||||
|
MessageHandler.Register(handlerId, msg =>
|
||||||
|
{
|
||||||
|
if (!msg.IsError) { return; }
|
||||||
|
errs.Add(msg);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ErrorCatcher Create()
|
||||||
|
=> new ErrorCatcher(ToolBox.RandomSeed(25).ToIdentifier());
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (handlerId.IsEmpty) { return; }
|
||||||
|
MessageHandler.Deregister(handlerId);
|
||||||
|
handlerId = Identifier.Empty;
|
||||||
|
#if CLIENT
|
||||||
|
DebugConsole.IsOpen = wasConsoleOpen;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static partial void ShowHelpMessage(Command command);
|
static partial void ShowHelpMessage(Command command);
|
||||||
|
|
||||||
const int MaxMessages = 300;
|
const int MaxMessages = 300;
|
||||||
|
|
||||||
public static List<ColoredText> Messages = new List<ColoredText>();
|
public static readonly List<ColoredText> Messages = new List<ColoredText>();
|
||||||
|
|
||||||
public delegate void QuestionCallback(string answer);
|
public delegate void QuestionCallback(string answer);
|
||||||
private static QuestionCallback activeQuestionCallback;
|
private static QuestionCallback activeQuestionCallback;
|
||||||
@@ -2291,10 +2336,9 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(msg)) { return; }
|
if (string.IsNullOrEmpty(msg)) { return; }
|
||||||
|
|
||||||
lock (queuedMessages)
|
var newMsg = new ColoredText(msg, color, isCommand, isError);
|
||||||
{
|
queuedMessages.Enqueue(newMsg);
|
||||||
queuedMessages.Enqueue(new ColoredText(msg, color, isCommand, isError));
|
MessageHandler.Invoke(newMsg);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void ShowQuestionPrompt(string question, QuestionCallback onAnswered, string[] args = null, int argCount = -1)
|
public static void ShowQuestionPrompt(string question, QuestionCallback onAnswered, string[] args = null, int argCount = -1)
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ namespace Barotrauma
|
|||||||
newCharacter.HumanPrefab = humanPrefab;
|
newCharacter.HumanPrefab = humanPrefab;
|
||||||
newCharacter.TeamID = TeamID;
|
newCharacter.TeamID = TeamID;
|
||||||
newCharacter.EnableDespawn = false;
|
newCharacter.EnableDespawn = false;
|
||||||
humanPrefab.GiveItems(newCharacter, newCharacter.Submarine);
|
humanPrefab.GiveItems(newCharacter, newCharacter.Submarine, spawnPos as WayPoint);
|
||||||
if (LootingIsStealing)
|
if (LootingIsStealing)
|
||||||
{
|
{
|
||||||
foreach (Item item in newCharacter.Inventory.FindAllItems(recursive: true))
|
foreach (Item item in newCharacter.Inventory.FindAllItems(recursive: true))
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ namespace Barotrauma
|
|||||||
private readonly Identifier spawnPointTag;
|
private readonly Identifier spawnPointTag;
|
||||||
private readonly Identifier destructibleItemTag;
|
private readonly Identifier destructibleItemTag;
|
||||||
|
|
||||||
|
private readonly string endCinematicSound;
|
||||||
|
|
||||||
private ImmutableArray<Character> minions;
|
private ImmutableArray<Character> minions;
|
||||||
private readonly int minionCount;
|
private readonly int minionCount;
|
||||||
private readonly float minionScatter;
|
private readonly float minionScatter;
|
||||||
@@ -134,7 +136,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
spawnPointTag = prefab.ConfigElement.GetAttributeIdentifier(nameof(spawnPointTag), Identifier.Empty);
|
spawnPointTag = prefab.ConfigElement.GetAttributeIdentifier(nameof(spawnPointTag), Identifier.Empty);
|
||||||
destructibleItemTag = prefab.ConfigElement.GetAttributeIdentifier(nameof(destructibleItemTag), Identifier.Empty);
|
destructibleItemTag = prefab.ConfigElement.GetAttributeIdentifier(nameof(destructibleItemTag), Identifier.Empty);
|
||||||
|
endCinematicSound = prefab.ConfigElement.GetAttributeString(nameof(endCinematicSound), string.Empty);
|
||||||
startCinematicDistance = prefab.ConfigElement.GetAttributeFloat(nameof(startCinematicDistance), 0);
|
startCinematicDistance = prefab.ConfigElement.GetAttributeFloat(nameof(startCinematicDistance), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -407,7 +407,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
var experienceGainMultiplierIndividual = new AbilityMissionExperienceGainMultiplier(this, 1f);
|
var experienceGainMultiplierIndividual = new AbilityMissionExperienceGainMultiplier(this, 1f);
|
||||||
info?.Character?.CheckTalents(AbilityEffectType.OnGainMissionExperience, experienceGainMultiplierIndividual);
|
info?.Character?.CheckTalents(AbilityEffectType.OnGainMissionExperience, experienceGainMultiplierIndividual);
|
||||||
info?.GiveExperience((int)(experienceGain * experienceGainMultiplier.Value), isMissionExperience: true);
|
info?.GiveExperience((int)(experienceGain * experienceGainMultiplier.Value));
|
||||||
}
|
}
|
||||||
|
|
||||||
// apply money gains afterwards to prevent them from affecting XP gains
|
// apply money gains afterwards to prevent them from affecting XP gains
|
||||||
@@ -561,8 +561,7 @@ namespace Barotrauma
|
|||||||
Character spawnedCharacter = Character.Create(characterInfo.SpeciesName, positionToStayIn.WorldPosition, ToolBox.RandomSeed(8), characterInfo, createNetworkEvent: false);
|
Character spawnedCharacter = Character.Create(characterInfo.SpeciesName, positionToStayIn.WorldPosition, ToolBox.RandomSeed(8), characterInfo, createNetworkEvent: false);
|
||||||
spawnedCharacter.HumanPrefab = humanPrefab;
|
spawnedCharacter.HumanPrefab = humanPrefab;
|
||||||
humanPrefab.InitializeCharacter(spawnedCharacter, positionToStayIn);
|
humanPrefab.InitializeCharacter(spawnedCharacter, positionToStayIn);
|
||||||
humanPrefab.GiveItems(spawnedCharacter, submarine, Rand.RandSync.ServerAndClient, createNetworkEvents: false);
|
humanPrefab.GiveItems(spawnedCharacter, submarine, positionToStayIn as WayPoint, Rand.RandSync.ServerAndClient, createNetworkEvents: false);
|
||||||
|
|
||||||
characters.Add(spawnedCharacter);
|
characters.Add(spawnedCharacter);
|
||||||
characterItems.Add(spawnedCharacter, spawnedCharacter.Inventory.FindAllItems(recursive: true));
|
characterItems.Add(spawnedCharacter, spawnedCharacter.Inventory.FindAllItems(recursive: true));
|
||||||
|
|
||||||
|
|||||||
@@ -15,11 +15,17 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
public Item Item;
|
public Item Item;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Note that the integer values matter here: the state of the target can't go back to a smaller value,
|
||||||
|
/// and a larger or equal value than the <see href="RequiredRetrievalState">RequiredRetrievalState</see> means the item counts as retrieved
|
||||||
|
/// (if the item needs to be picked up to be considered retrieved, it's also considered retrieved if it's in the sub)
|
||||||
|
/// </summary>
|
||||||
public enum RetrievalState
|
public enum RetrievalState
|
||||||
{
|
{
|
||||||
None = 0,
|
None = 0,
|
||||||
PickedUp = 1,
|
Interact = 1,
|
||||||
RetrievedToSub = 2
|
PickedUp = 2,
|
||||||
|
RetrievedToSub = 3
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly ItemPrefab ItemPrefab;
|
public readonly ItemPrefab ItemPrefab;
|
||||||
@@ -41,10 +47,19 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public readonly bool HideLabelAfterRetrieved;
|
public readonly bool HideLabelAfterRetrieved;
|
||||||
|
|
||||||
public bool Retrieved =>
|
public bool Retrieved
|
||||||
RequiredRetrievalState == RetrievalState.RetrievedToSub ?
|
{
|
||||||
State == RetrievalState.RetrievedToSub :
|
get
|
||||||
State != RetrievalState.None;
|
{
|
||||||
|
return RequiredRetrievalState switch
|
||||||
|
{
|
||||||
|
RetrievalState.None => true,
|
||||||
|
RetrievalState.Interact or RetrievalState.PickedUp => State >= RequiredRetrievalState,
|
||||||
|
RetrievalState.RetrievedToSub => State == RetrievalState.RetrievedToSub,
|
||||||
|
_ => throw new NotImplementedException(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private RetrievalState state;
|
private RetrievalState state;
|
||||||
public RetrievalState State
|
public RetrievalState State
|
||||||
@@ -60,6 +75,8 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool Interacted;
|
||||||
|
|
||||||
private readonly SalvageMission mission;
|
private readonly SalvageMission mission;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -268,6 +285,13 @@ namespace Barotrauma
|
|||||||
target.Item.body.SetTransformIgnoreContacts(target.Item.body.SimPosition, target.Item.body.Rotation);
|
target.Item.body.SetTransformIgnoreContacts(target.Item.body.SimPosition, target.Item.body.Rotation);
|
||||||
target.Item.body.FarseerBody.BodyType = BodyType.Kinematic;
|
target.Item.body.FarseerBody.BodyType = BodyType.Kinematic;
|
||||||
}
|
}
|
||||||
|
else if (target.RequiredRetrievalState == Target.RetrievalState.Interact)
|
||||||
|
{
|
||||||
|
target.Item.OnInteract += () =>
|
||||||
|
{
|
||||||
|
target.Interacted = true;
|
||||||
|
};
|
||||||
|
}
|
||||||
for (int i = 0; i < target.StatusEffects.Count; i++)
|
for (int i = 0; i < target.StatusEffects.Count; i++)
|
||||||
{
|
{
|
||||||
List<StatusEffect> effectList = target.StatusEffects[i];
|
List<StatusEffect> effectList = target.StatusEffects[i];
|
||||||
@@ -352,22 +376,33 @@ namespace Barotrauma
|
|||||||
switch (target.State)
|
switch (target.State)
|
||||||
{
|
{
|
||||||
case Target.RetrievalState.None:
|
case Target.RetrievalState.None:
|
||||||
|
if (target.Interacted)
|
||||||
|
{
|
||||||
|
TrySetRetrievalState(Target.RetrievalState.Interact);
|
||||||
|
}
|
||||||
var root = target.Item?.GetRootContainer() ?? target.Item;
|
var root = target.Item?.GetRootContainer() ?? target.Item;
|
||||||
if (root.ParentInventory?.Owner is Character character && character.TeamID == CharacterTeamType.Team1)
|
if (root.ParentInventory?.Owner is Character character && character.TeamID == CharacterTeamType.Team1)
|
||||||
{
|
{
|
||||||
target.State = Target.RetrievalState.PickedUp;
|
TrySetRetrievalState(Target.RetrievalState.PickedUp);
|
||||||
if (target.Retrieved) { State = i + 1 ; }
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Target.RetrievalState.PickedUp:
|
case Target.RetrievalState.PickedUp:
|
||||||
Submarine parentSub = target.Item.CurrentHull?.Submarine ?? target.Item.GetRootInventoryOwner()?.Submarine;
|
Submarine parentSub = target.Item.CurrentHull?.Submarine ?? target.Item.GetRootInventoryOwner()?.Submarine;
|
||||||
if (parentSub != null && parentSub.Info.Type == SubmarineType.Player)
|
if (parentSub != null && parentSub.Info.Type == SubmarineType.Player)
|
||||||
{
|
{
|
||||||
target.State = Target.RetrievalState.RetrievedToSub;
|
TrySetRetrievalState(Target.RetrievalState.RetrievedToSub);
|
||||||
if (target.Retrieved) { State = i + 1; }
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TrySetRetrievalState(Target.RetrievalState retrievalState)
|
||||||
|
{
|
||||||
|
if (retrievalState < target.State) { return; }
|
||||||
|
bool wasRetrieved = false;
|
||||||
|
target.State = retrievalState;
|
||||||
|
//increment the mission state if the target became retrieved
|
||||||
|
if (!wasRetrieved && target.Retrieved) { State = i + 1; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (targets.All(t => t.Retrieved))
|
if (targets.All(t => t.Retrieved))
|
||||||
{
|
{
|
||||||
@@ -383,7 +418,7 @@ namespace Barotrauma
|
|||||||
protected override void EndMissionSpecific(bool completed)
|
protected override void EndMissionSpecific(bool completed)
|
||||||
{
|
{
|
||||||
//consider failed (can't attempt again) if we picked up any of the items but failed to bring them out of the level
|
//consider failed (can't attempt again) if we picked up any of the items but failed to bring them out of the level
|
||||||
failed = !completed && targets.Any(t => t.State != Target.RetrievalState.None);
|
failed = !completed && targets.Any(t => t.State >= Target.RetrievalState.PickedUp);
|
||||||
foreach (var target in targets)
|
foreach (var target in targets)
|
||||||
{
|
{
|
||||||
if (target.RemoveItem)
|
if (target.RemoveItem)
|
||||||
|
|||||||
@@ -594,7 +594,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
GameAnalyticsManager.AddDesignEvent(
|
GameAnalyticsManager.AddDesignEvent(
|
||||||
$"MonsterSpawn:{GameMain.GameSession.GameMode?.Preset?.Identifier.Value ?? "none"}:{Level.Loaded?.LevelData?.Biome?.Identifier.Value ?? "none"}:{SpawnPosType}:{SpeciesName}",
|
$"MonsterSpawn:{GameMain.GameSession.GameMode?.Preset?.Identifier.Value ?? "none"}:{Level.Loaded?.LevelData?.Biome?.Identifier.Value ?? "none"}:{SpawnPosType}:{SpeciesName}",
|
||||||
value: Timing.TotalTime - GameMain.GameSession.RoundStartTime);
|
value: GameMain.GameSession.RoundDuration);
|
||||||
}
|
}
|
||||||
}, delayBetweenSpawns * i);
|
}, delayBetweenSpawns * i);
|
||||||
i++;
|
i++;
|
||||||
|
|||||||
@@ -316,5 +316,17 @@ namespace Barotrauma.Extensions
|
|||||||
=> source
|
=> source
|
||||||
.OfType<Some<T>>()
|
.OfType<Some<T>>()
|
||||||
.Select(some => some.Value);
|
.Select(some => some.Value);
|
||||||
|
|
||||||
|
public static IEnumerable<TSuccess> Successes<TSuccess, TFailure>(
|
||||||
|
this IEnumerable<Result<TSuccess, TFailure>> source)
|
||||||
|
=> source
|
||||||
|
.OfType<Success<TSuccess, TFailure>>()
|
||||||
|
.Select(s => s.Value);
|
||||||
|
|
||||||
|
public static IEnumerable<TFailure> Failures<TSuccess, TFailure>(
|
||||||
|
this IEnumerable<Result<TSuccess, TFailure>> source)
|
||||||
|
=> source
|
||||||
|
.OfType<Failure<TSuccess, TFailure>>()
|
||||||
|
.Select(f => f.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ namespace Barotrauma
|
|||||||
if (!data.ContainsKey(identifier))
|
if (!data.ContainsKey(identifier))
|
||||||
{
|
{
|
||||||
data.Add(identifier, value);
|
data.Add(identifier, value);
|
||||||
|
SteamAchievementManager.OnCampaignMetadataSet(identifier, value);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ namespace Barotrauma
|
|||||||
float reputationGainMultiplier = 1f;
|
float reputationGainMultiplier = 1f;
|
||||||
foreach (Character character in GameSession.GetSessionCrewCharacters(CharacterType.Both))
|
foreach (Character character in GameSession.GetSessionCrewCharacters(CharacterType.Both))
|
||||||
{
|
{
|
||||||
reputationGainMultiplier *= 1f + character.GetStatValue(StatTypes.ReputationGainMultiplier);
|
reputationGainMultiplier *= 1f + character.GetStatValue(StatTypes.ReputationGainMultiplier, includeSaved: false);
|
||||||
reputationGainMultiplier *= 1f + character.Info?.GetSavedStatValue(StatTypes.ReputationGainMultiplier, Identifier) ?? 0;
|
reputationGainMultiplier *= 1f + character.Info?.GetSavedStatValue(StatTypes.ReputationGainMultiplier, Identifier) ?? 0;
|
||||||
}
|
}
|
||||||
reputationChange *= reputationGainMultiplier;
|
reputationChange *= reputationGainMultiplier;
|
||||||
@@ -75,7 +75,7 @@ namespace Barotrauma
|
|||||||
float reputationLossMultiplier = 1f;
|
float reputationLossMultiplier = 1f;
|
||||||
foreach (Character character in GameSession.GetSessionCrewCharacters(CharacterType.Both))
|
foreach (Character character in GameSession.GetSessionCrewCharacters(CharacterType.Both))
|
||||||
{
|
{
|
||||||
reputationLossMultiplier *= 1f + character.GetStatValue(StatTypes.ReputationLossMultiplier);
|
reputationLossMultiplier *= 1f + character.GetStatValue(StatTypes.ReputationLossMultiplier, includeSaved: false);
|
||||||
reputationLossMultiplier *= 1f + character.Info?.GetSavedStatValue(StatTypes.ReputationLossMultiplier, Identifier) ?? 0;
|
reputationLossMultiplier *= 1f + character.Info?.GetSavedStatValue(StatTypes.ReputationLossMultiplier, Identifier) ?? 0;
|
||||||
}
|
}
|
||||||
reputationChange *= reputationLossMultiplier;
|
reputationChange *= reputationLossMultiplier;
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public bool DisableEvents
|
public bool DisableEvents
|
||||||
{
|
{
|
||||||
get { return IsFirstRound && Timing.TotalTime < GameMain.GameSession.RoundStartTime + FirstRoundEventDelay; }
|
get { return IsFirstRound && GameMain.GameSession.RoundDuration > FirstRoundEventDelay; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool CheatsEnabled;
|
public bool CheatsEnabled;
|
||||||
@@ -248,7 +248,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
for (int i = 0; i < wall.SectionCount; i++)
|
for (int i = 0; i < wall.SectionCount; i++)
|
||||||
{
|
{
|
||||||
wall.SetDamage(i, 0, createNetworkEvent: false);
|
wall.SetDamage(i, 0, createNetworkEvent: false, createExplosionEffect: false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1260,6 +1260,7 @@ namespace Barotrauma
|
|||||||
if (item.Components.None(c => c is Pickable)) { continue; }
|
if (item.Components.None(c => c is Pickable)) { continue; }
|
||||||
if (item.Components.Any(c => c is Pickable p && p.IsAttached)) { continue; }
|
if (item.Components.Any(c => c is Pickable p && p.IsAttached)) { continue; }
|
||||||
if (item.Components.Any(c => c is Wire w && w.Connections.Any(c => c != null))) { continue; }
|
if (item.Components.Any(c => c is Wire w && w.Connections.Any(c => c != null))) { continue; }
|
||||||
|
if (item.Container?.GetComponent<ItemContainer>() is { DrawInventory: false }) { continue; }
|
||||||
itemsToTransfer.Add((item, item.Container));
|
itemsToTransfer.Add((item, item.Container));
|
||||||
item.Submarine = null;
|
item.Submarine = null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,10 @@ namespace Barotrauma
|
|||||||
private Location[]? dummyLocations;
|
private Location[]? dummyLocations;
|
||||||
public CrewManager? CrewManager;
|
public CrewManager? CrewManager;
|
||||||
|
|
||||||
public double RoundStartTime;
|
public float RoundDuration
|
||||||
|
{
|
||||||
|
get; private set;
|
||||||
|
}
|
||||||
|
|
||||||
public double TimeSpentCleaning, TimeSpentPainting;
|
public double TimeSpentCleaning, TimeSpentPainting;
|
||||||
|
|
||||||
@@ -392,6 +395,7 @@ namespace Barotrauma
|
|||||||
#if DEBUG
|
#if DEBUG
|
||||||
DateTime startTime = DateTime.Now;
|
DateTime startTime = DateTime.Now;
|
||||||
#endif
|
#endif
|
||||||
|
RoundDuration = 0.0f;
|
||||||
AfflictionPrefab.LoadAllEffects();
|
AfflictionPrefab.LoadAllEffects();
|
||||||
|
|
||||||
MirrorLevel = mirrorLevel;
|
MirrorLevel = mirrorLevel;
|
||||||
@@ -543,7 +547,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
RoundSummary = new RoundSummary(GameMode, Missions, StartLocation, EndLocation);
|
RoundSummary = new RoundSummary(GameMode, Missions, StartLocation, EndLocation);
|
||||||
|
|
||||||
if (!(GameMode is TutorialMode) && !(GameMode is TestGameMode))
|
if (GameMode is not TutorialMode && GameMode is not TestGameMode)
|
||||||
{
|
{
|
||||||
GUI.AddMessage("", Color.Transparent, 3.0f, playSound: false);
|
GUI.AddMessage("", Color.Transparent, 3.0f, playSound: false);
|
||||||
if (EndLocation != null && levelData != null)
|
if (EndLocation != null && levelData != null)
|
||||||
@@ -610,9 +614,9 @@ namespace Barotrauma
|
|||||||
GameMode.Start();
|
GameMode.Start();
|
||||||
foreach (Mission mission in missions)
|
foreach (Mission mission in missions)
|
||||||
{
|
{
|
||||||
int prevEntityCount = Entity.GetEntities().Count();
|
int prevEntityCount = Entity.GetEntities().Count;
|
||||||
mission.Start(Level.Loaded);
|
mission.Start(Level.Loaded);
|
||||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient && Entity.GetEntities().Count() != prevEntityCount)
|
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient && Entity.GetEntities().Count != prevEntityCount)
|
||||||
{
|
{
|
||||||
DebugConsole.ThrowError(
|
DebugConsole.ThrowError(
|
||||||
$"Entity count has changed after starting a mission ({mission.Prefab.Identifier}) as a client. " +
|
$"Entity count has changed after starting a mission ({mission.Prefab.Identifier}) as a client. " +
|
||||||
@@ -651,7 +655,7 @@ namespace Barotrauma
|
|||||||
CreatureMetrics.Instance.RecentlyEncountered.Clear();
|
CreatureMetrics.Instance.RecentlyEncountered.Clear();
|
||||||
|
|
||||||
GameMain.GameScreen.Cam.Position = Character.Controlled?.WorldPosition ?? Submarine.MainSub.WorldPosition;
|
GameMain.GameScreen.Cam.Position = Character.Controlled?.WorldPosition ?? Submarine.MainSub.WorldPosition;
|
||||||
RoundStartTime = Timing.TotalTime;
|
RoundDuration = 0.0f;
|
||||||
GameMain.ResetFrameTime();
|
GameMain.ResetFrameTime();
|
||||||
IsRunning = true;
|
IsRunning = true;
|
||||||
}
|
}
|
||||||
@@ -762,6 +766,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public void Update(float deltaTime)
|
public void Update(float deltaTime)
|
||||||
{
|
{
|
||||||
|
RoundDuration += deltaTime;
|
||||||
EventManager?.Update(deltaTime);
|
EventManager?.Update(deltaTime);
|
||||||
GameMode?.Update(deltaTime);
|
GameMode?.Update(deltaTime);
|
||||||
//backwards for loop because the missions may get completed and removed from the list in Update()
|
//backwards for loop because the missions may get completed and removed from the list in Update()
|
||||||
@@ -914,17 +919,16 @@ namespace Barotrauma
|
|||||||
#else
|
#else
|
||||||
bool success = GameMain.Server.ConnectedClients.Any(c => c.InGame && c.Character != null && !c.Character.IsDead);
|
bool success = GameMain.Server.ConnectedClients.Any(c => c.InGame && c.Character != null && !c.Character.IsDead);
|
||||||
#endif
|
#endif
|
||||||
double roundDuration = Timing.TotalTime - RoundStartTime;
|
|
||||||
GameAnalyticsManager.AddProgressionEvent(
|
GameAnalyticsManager.AddProgressionEvent(
|
||||||
success ? GameAnalyticsManager.ProgressionStatus.Complete : GameAnalyticsManager.ProgressionStatus.Fail,
|
success ? GameAnalyticsManager.ProgressionStatus.Complete : GameAnalyticsManager.ProgressionStatus.Fail,
|
||||||
GameMode?.Preset.Identifier.Value ?? "none",
|
GameMode?.Preset.Identifier.Value ?? "none",
|
||||||
roundDuration);
|
RoundDuration);
|
||||||
string eventId = "EndRound:" + (GameMode?.Preset?.Identifier.Value ?? "none") + ":";
|
string eventId = "EndRound:" + (GameMode?.Preset?.Identifier.Value ?? "none") + ":";
|
||||||
LogEndRoundStats(eventId);
|
LogEndRoundStats(eventId);
|
||||||
if (GameMode is CampaignMode campaignMode)
|
if (GameMode is CampaignMode campaignMode)
|
||||||
{
|
{
|
||||||
GameAnalyticsManager.AddDesignEvent(eventId + "MoneyEarned", GetAmountOfMoney(crewCharacters) - prevMoney);
|
GameAnalyticsManager.AddDesignEvent(eventId + "MoneyEarned", GetAmountOfMoney(crewCharacters) - prevMoney);
|
||||||
campaignMode.TotalPlayTime += roundDuration;
|
campaignMode.TotalPlayTime += RoundDuration;
|
||||||
}
|
}
|
||||||
#if CLIENT
|
#if CLIENT
|
||||||
HintManager.OnRoundEnded();
|
HintManager.OnRoundEnded();
|
||||||
@@ -950,21 +954,20 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public void LogEndRoundStats(string eventId)
|
public void LogEndRoundStats(string eventId)
|
||||||
{
|
{
|
||||||
double roundDuration = Timing.TotalTime - RoundStartTime;
|
GameAnalyticsManager.AddDesignEvent(eventId + "Submarine:" + (Submarine.MainSub?.Info?.Name ?? "none"), RoundDuration);
|
||||||
GameAnalyticsManager.AddDesignEvent(eventId + "Submarine:" + (Submarine.MainSub?.Info?.Name ?? "none"), roundDuration);
|
GameAnalyticsManager.AddDesignEvent(eventId + "GameMode:" + (GameMode?.Name.Value ?? "none"), RoundDuration);
|
||||||
GameAnalyticsManager.AddDesignEvent(eventId + "GameMode:" + (GameMode?.Name.Value ?? "none"), roundDuration);
|
GameAnalyticsManager.AddDesignEvent(eventId + "CrewSize:" + (CrewManager?.CharacterInfos?.Count() ?? 0), RoundDuration);
|
||||||
GameAnalyticsManager.AddDesignEvent(eventId + "CrewSize:" + (CrewManager?.CharacterInfos?.Count() ?? 0), roundDuration);
|
|
||||||
foreach (Mission mission in missions)
|
foreach (Mission mission in missions)
|
||||||
{
|
{
|
||||||
GameAnalyticsManager.AddDesignEvent(eventId + "MissionType:" + (mission.Prefab.Type.ToString() ?? "none") + ":" + mission.Prefab.Identifier + ":" + (mission.Completed ? "Completed" : "Failed"), roundDuration);
|
GameAnalyticsManager.AddDesignEvent(eventId + "MissionType:" + (mission.Prefab.Type.ToString() ?? "none") + ":" + mission.Prefab.Identifier + ":" + (mission.Completed ? "Completed" : "Failed"), RoundDuration);
|
||||||
}
|
}
|
||||||
if (Level.Loaded != null)
|
if (Level.Loaded != null)
|
||||||
{
|
{
|
||||||
Identifier levelId = (Level.Loaded.Type == LevelData.LevelType.Outpost ?
|
Identifier levelId = (Level.Loaded.Type == LevelData.LevelType.Outpost ?
|
||||||
Level.Loaded.StartOutpost?.Info?.OutpostGenerationParams?.Identifier :
|
Level.Loaded.StartOutpost?.Info?.OutpostGenerationParams?.Identifier :
|
||||||
Level.Loaded.GenerationParams?.Identifier) ?? "null".ToIdentifier();
|
Level.Loaded.GenerationParams?.Identifier) ?? "null".ToIdentifier();
|
||||||
GameAnalyticsManager.AddDesignEvent(eventId + "LevelType:" + (Level.Loaded?.Type.ToString() ?? "none" + ":" + levelId), roundDuration);
|
GameAnalyticsManager.AddDesignEvent(eventId + "LevelType:" + (Level.Loaded?.Type.ToString() ?? "none" + ":" + levelId), RoundDuration);
|
||||||
GameAnalyticsManager.AddDesignEvent(eventId + "Biome:" + (Level.Loaded?.LevelData?.Biome?.Identifier.Value ?? "none"), roundDuration);
|
GameAnalyticsManager.AddDesignEvent(eventId + "Biome:" + (Level.Loaded?.LevelData?.Biome?.Identifier.Value ?? "none"), RoundDuration);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Submarine.MainSub != null)
|
if (Submarine.MainSub != null)
|
||||||
|
|||||||
@@ -315,6 +315,7 @@ namespace Barotrauma.Items.Components
|
|||||||
if (f2.Body.UserData is Limb targetLimb)
|
if (f2.Body.UserData is Limb targetLimb)
|
||||||
{
|
{
|
||||||
if (targetLimb.IsSevered || targetLimb.character == null || targetLimb.character == User) { return false; }
|
if (targetLimb.IsSevered || targetLimb.character == null || targetLimb.character == User) { return false; }
|
||||||
|
if (targetLimb.character.IgnoreMeleeWeapons) { return false; }
|
||||||
var targetCharacter = targetLimb.character;
|
var targetCharacter = targetLimb.character;
|
||||||
if (targetCharacter == picker) { return false; }
|
if (targetCharacter == picker) { return false; }
|
||||||
if (AllowHitMultiple)
|
if (AllowHitMultiple)
|
||||||
@@ -330,6 +331,7 @@ namespace Barotrauma.Items.Components
|
|||||||
else if (f2.Body.UserData is Character targetCharacter)
|
else if (f2.Body.UserData is Character targetCharacter)
|
||||||
{
|
{
|
||||||
if (targetCharacter == picker || targetCharacter == User) { return false; }
|
if (targetCharacter == picker || targetCharacter == User) { return false; }
|
||||||
|
if (targetCharacter.IgnoreMeleeWeapons) { return false; }
|
||||||
targetLimb = targetCharacter.AnimController.GetLimb(LimbType.Torso); //Otherwise armor can be bypassed in strange ways
|
targetLimb = targetCharacter.AnimController.GetLimb(LimbType.Torso); //Otherwise armor can be bypassed in strange ways
|
||||||
if (AllowHitMultiple)
|
if (AllowHitMultiple)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -809,7 +809,14 @@ namespace Barotrauma.Items.Components
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
hasRequiredItems = itemList.Any(Predicate);
|
if (itemList.Any(Predicate))
|
||||||
|
{
|
||||||
|
hasRequiredItems = !relatedItem.RequireEmpty;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
hasRequiredItems = relatedItem.MatchOnEmpty || relatedItem.RequireEmpty;
|
||||||
|
}
|
||||||
if (!hasRequiredItems)
|
if (!hasRequiredItems)
|
||||||
{
|
{
|
||||||
shouldBreak = true;
|
shouldBreak = true;
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ namespace Barotrauma.Items.Components
|
|||||||
[Serialize(100, IsPropertySaveable.No, description: "How many items are placed in a row before starting a new row.")]
|
[Serialize(100, IsPropertySaveable.No, description: "How many items are placed in a row before starting a new row.")]
|
||||||
public int ItemsPerRow { get; set; }
|
public int ItemsPerRow { get; set; }
|
||||||
|
|
||||||
[Serialize(true, IsPropertySaveable.No, description: "Should the inventory of this item be visible when the item is selected.")]
|
[Serialize(true, IsPropertySaveable.No, description: "Should the contents in the item's inventory be visible? Disabled on items like magazines that spawn the contents as needed.")]
|
||||||
public bool DrawInventory
|
public bool DrawInventory
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
@@ -135,9 +135,6 @@ namespace Barotrauma.Items.Components
|
|||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Serialize(true, IsPropertySaveable.No)]
|
|
||||||
public bool AllowAccess { get; set; }
|
|
||||||
|
|
||||||
[Serialize(false, IsPropertySaveable.No)]
|
[Serialize(false, IsPropertySaveable.No)]
|
||||||
public bool AccessOnlyWhenBroken { get; set; }
|
public bool AccessOnlyWhenBroken { get; set; }
|
||||||
|
|
||||||
@@ -530,12 +527,12 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
public override bool HasRequiredItems(Character character, bool addMessage, LocalizedString msg = null)
|
public override bool HasRequiredItems(Character character, bool addMessage, LocalizedString msg = null)
|
||||||
{
|
{
|
||||||
return AllowAccess && (!AccessOnlyWhenBroken || Item.Condition <= 0) && base.HasRequiredItems(character, addMessage, msg);
|
return DrawInventory && (!AccessOnlyWhenBroken || Item.Condition <= 0) && base.HasRequiredItems(character, addMessage, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool Select(Character character)
|
public override bool Select(Character character)
|
||||||
{
|
{
|
||||||
if (!AllowAccess) { return false; }
|
if (!DrawInventory) { return false; }
|
||||||
if (item.Container != null) { return false; }
|
if (item.Container != null) { return false; }
|
||||||
if (AccessOnlyWhenBroken)
|
if (AccessOnlyWhenBroken)
|
||||||
{
|
{
|
||||||
@@ -571,7 +568,7 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
public override bool Pick(Character picker)
|
public override bool Pick(Character picker)
|
||||||
{
|
{
|
||||||
if (!AllowAccess) { return false; }
|
if (!DrawInventory) { return false; }
|
||||||
if (AccessOnlyWhenBroken)
|
if (AccessOnlyWhenBroken)
|
||||||
{
|
{
|
||||||
if (item.Condition > 0)
|
if (item.Condition > 0)
|
||||||
|
|||||||
@@ -745,7 +745,7 @@ namespace Barotrauma.Items.Components
|
|||||||
itemList.AddRange(container.Inventory.AllItems);
|
itemList.AddRange(container.Inventory.AllItems);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (user?.Inventory != null)
|
if (user?.Inventory != null && user.SelectedItem == item)
|
||||||
{
|
{
|
||||||
itemList.AddRange(user.Inventory.AllItems);
|
itemList.AddRange(user.Inventory.AllItems);
|
||||||
linkedInventories.Add(user.Inventory);
|
linkedInventories.Add(user.Inventory);
|
||||||
|
|||||||
@@ -65,14 +65,7 @@ namespace Barotrauma.Items.Components
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (GameMain.GameSession != null)
|
return GameMain.GameSession?.RoundDuration ?? 0.0f;
|
||||||
{
|
|
||||||
return (float)(Timing.TotalTime - GameMain.GameSession.RoundStartTime);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return 0.0f;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user