Unstable v0.19.5.0
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Tutorials;
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
@@ -398,36 +399,58 @@ namespace Barotrauma
|
||||
progressBar.Draw(spriteBatch, cam);
|
||||
}
|
||||
|
||||
foreach (Character npc in Character.CharacterList)
|
||||
void DrawInteractionIcon(Entity entity, string iconStyle)
|
||||
{
|
||||
if (npc.CampaignInteractionType == CampaignMode.InteractionType.None || npc.Submarine != character.Submarine || npc.IsDead || npc.IsIncapacitated) { continue; }
|
||||
if (entity == null || entity.Removed) { return; }
|
||||
var characterEntity = entity as Character;
|
||||
if (characterEntity is not null && (characterEntity.IsDead || characterEntity.IsIncapacitated)) { return; }
|
||||
if (GUIStyle.GetComponentStyle(iconStyle) is not GUIComponentStyle style) { return; }
|
||||
|
||||
var iconStyle = GUIStyle.GetComponentStyle("CampaignInteractionIcon." + npc.CampaignInteractionType);
|
||||
if (iconStyle == null) { continue; }
|
||||
Range<float> visibleRange = new Range<float>(npc.CurrentHull == Character.Controlled.CurrentHull ? 500.0f : 100.0f, float.PositiveInfinity);
|
||||
if (npc.CampaignInteractionType == CampaignMode.InteractionType.Examine)
|
||||
Hull currentHull = entity switch
|
||||
{
|
||||
Character character => character.CurrentHull,
|
||||
Item item => item.CurrentHull,
|
||||
_ => null
|
||||
};
|
||||
|
||||
Range<float> visibleRange = new Range<float>(currentHull == Character.Controlled.CurrentHull ? 500.0f : 100.0f, float.PositiveInfinity);
|
||||
if (characterEntity?.CampaignInteractionType == CampaignMode.InteractionType.Examine)
|
||||
{
|
||||
//TODO: we could probably do better than just hardcoding
|
||||
//a check for InteractionType.Examine here.
|
||||
|
||||
if (Vector2.DistanceSquared(character.Position, npc.Position) > 500f * 500f) { continue; }
|
||||
if (Vector2.DistanceSquared(character.Position, entity.Position) > 500f * 500f) { return; }
|
||||
|
||||
var body = Submarine.CheckVisibility(character.SimPosition, npc.SimPosition, ignoreLevel: true);
|
||||
if (body != null && body.UserData as Character != npc) { continue; }
|
||||
var body = Submarine.CheckVisibility(character.SimPosition, entity.SimPosition, ignoreLevel: true);
|
||||
if (body != null && body.UserData != entity) { return; }
|
||||
|
||||
visibleRange = new Range<float>(-100f, 500f);
|
||||
}
|
||||
float dist = Vector2.Distance(character.WorldPosition, npc.WorldPosition);
|
||||
float dist = Vector2.Distance(character.WorldPosition, entity.WorldPosition);
|
||||
float distFactor = 1.0f - MathUtils.InverseLerp(1000.0f, 3000.0f, dist);
|
||||
float alpha = MathHelper.Lerp(0.3f, 1.0f, distFactor);
|
||||
GUI.DrawIndicator(
|
||||
spriteBatch,
|
||||
npc.WorldPosition,
|
||||
entity.WorldPosition,
|
||||
cam,
|
||||
visibleRange,
|
||||
iconStyle.GetDefaultSprite(),
|
||||
iconStyle.Color * alpha,
|
||||
label: npc.Info?.Title);
|
||||
style.GetDefaultSprite(),
|
||||
style.Color * alpha,
|
||||
label: characterEntity?.Info?.Title);
|
||||
}
|
||||
|
||||
foreach (Character npc in Character.CharacterList)
|
||||
{
|
||||
if (npc.CampaignInteractionType == CampaignMode.InteractionType.None) { continue; }
|
||||
DrawInteractionIcon(npc, "CampaignInteractionIcon." + npc.CampaignInteractionType);
|
||||
}
|
||||
|
||||
if (GameMain.GameSession?.GameMode is TutorialMode tutorialMode && tutorialMode.Tutorial is not null)
|
||||
{
|
||||
foreach (var (entity, iconStyle) in tutorialMode.Tutorial.Icons)
|
||||
{
|
||||
DrawInteractionIcon(entity, iconStyle);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Item item in Item.ItemList)
|
||||
|
||||
@@ -521,8 +521,9 @@ namespace Barotrauma
|
||||
Color skinColor = inc.ReadColorR8G8B8();
|
||||
Color hairColor = inc.ReadColorR8G8B8();
|
||||
Color facialHairColor = inc.ReadColorR8G8B8();
|
||||
string ragdollFile = inc.ReadString();
|
||||
|
||||
string ragdollFile = inc.ReadString();
|
||||
Identifier npcId = inc.ReadIdentifier();
|
||||
uint jobIdentifier = inc.ReadUInt32();
|
||||
int variant = inc.ReadByte();
|
||||
|
||||
@@ -539,9 +540,9 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
// TODO: animations
|
||||
CharacterInfo ch = new CharacterInfo(speciesName, newName, originalName, jobPrefab, ragdollFile, variant)
|
||||
CharacterInfo ch = new CharacterInfo(speciesName, newName, originalName, jobPrefab, ragdollFile, variant, npcIdentifier: npcId)
|
||||
{
|
||||
ID = infoID,
|
||||
ID = infoID
|
||||
};
|
||||
ch.RecreateHead(tagSet.ToImmutableHashSet(), hairIndex, beardIndex, moustacheIndex, faceAttachmentIndex);
|
||||
ch.Head.SkinColor = skinColor;
|
||||
|
||||
@@ -63,7 +63,7 @@ namespace Barotrauma
|
||||
files = contentPackage.Files.Select(File.FromContentFile).ToList();
|
||||
ModVersion = IncrementModVersion(contentPackage.ModVersion);
|
||||
IsCore = contentPackage is CorePackage;
|
||||
SteamWorkshopId = contentPackage.SteamWorkshopId;
|
||||
UgcId = contentPackage.UgcId;
|
||||
ExpectedHash = contentPackage.Hash;
|
||||
InstallTime = contentPackage.InstallTime;
|
||||
}
|
||||
@@ -90,9 +90,9 @@ namespace Barotrauma
|
||||
|
||||
public bool IsCore = false;
|
||||
|
||||
public UInt64 SteamWorkshopId = 0;
|
||||
public Option<ContentPackageId> UgcId = Option<ContentPackageId>.None();
|
||||
|
||||
public DateTime? InstallTime = null;
|
||||
public Option<DateTime> InstallTime = Option<DateTime>.None();
|
||||
|
||||
public bool HasFile(File file)
|
||||
=> Files.Any(f =>
|
||||
@@ -120,7 +120,7 @@ namespace Barotrauma
|
||||
public void DiscardHashAndInstallTime()
|
||||
{
|
||||
ExpectedHash = null;
|
||||
InstallTime = null;
|
||||
InstallTime = Option<DateTime>.None();
|
||||
}
|
||||
|
||||
public static string IncrementModVersion(string modVersion)
|
||||
@@ -155,11 +155,11 @@ namespace Barotrauma
|
||||
addRootAttribute("name", Name);
|
||||
if (!ModVersion.IsNullOrEmpty()) { addRootAttribute("modversion", ModVersion); }
|
||||
addRootAttribute("corepackage", IsCore);
|
||||
if (SteamWorkshopId != 0) { addRootAttribute("steamworkshopid", SteamWorkshopId); }
|
||||
if (UgcId.TryUnwrap(out var ugcId) && ugcId is SteamWorkshopId steamWorkshopId) { addRootAttribute("steamworkshopid", steamWorkshopId.Value); }
|
||||
addRootAttribute("gameversion", GameMain.Version);
|
||||
if (AltNames.Any()) { addRootAttribute("altnames", string.Join(",", AltNames)); }
|
||||
if (ExpectedHash != null) { addRootAttribute("expectedhash", ExpectedHash.StringRepresentation); }
|
||||
if (InstallTime != null) { addRootAttribute("installtime", ToolBox.Epoch.FromDateTime(InstallTime.Value)); }
|
||||
if (InstallTime.TryUnwrap(out var installTime)) { addRootAttribute("installtime", ToolBox.Epoch.FromDateTime(installTime)); }
|
||||
|
||||
files.ForEach(f => rootElement.Add(f.ToXElement()));
|
||||
|
||||
|
||||
@@ -45,9 +45,11 @@ namespace Barotrauma
|
||||
|
||||
var needInstalling = subscribedItems.Where(item
|
||||
=> !WorkshopPackages.Any(p
|
||||
=> item.Id == p.SteamWorkshopId
|
||||
&& p.InstallTime.HasValue
|
||||
&& item.LatestUpdateTime <= p.InstallTime))
|
||||
=> p.UgcId.TryUnwrap(out var ugcId)
|
||||
&& ugcId is SteamWorkshopId workshopId
|
||||
&& item.Id == workshopId.Value
|
||||
&& p.InstallTime.TryUnwrap(out var installTime)
|
||||
&& item.LatestUpdateTime <= installTime))
|
||||
.ToArray();
|
||||
if (needInstalling.Any())
|
||||
{
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace Barotrauma.Transition
|
||||
/// Class dedicated to transitioning away from the old, shitty
|
||||
/// Mods + Submarines folders to the new LocalMods folder
|
||||
/// </summary>
|
||||
public static class UgcTransition
|
||||
public static class LegacySteamUgcTransition
|
||||
{
|
||||
private const string readmeName = "LOCALMODS_README.txt";
|
||||
|
||||
@@ -168,7 +168,11 @@ namespace Barotrauma.Transition
|
||||
addHeader(TextManager.Get("SubscribedMods"));
|
||||
foreach (var mod in mods.Mods)
|
||||
{
|
||||
addTickbox(mod.Dir, mod.Name, ticked: !ContentPackageManager.LocalPackages.Any(p => p.SteamWorkshopId != 0 && p.SteamWorkshopId == mod.Item?.Id));
|
||||
addTickbox(mod.Dir, mod.Name,
|
||||
ticked: !(mod.Item is { } item && ContentPackageManager.LocalPackages.Any(p =>
|
||||
p.UgcId.TryUnwrap(out var ugcId)
|
||||
&& ugcId is SteamWorkshopId workshopId
|
||||
&& workshopId.Value == item.Id)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2489,26 +2489,8 @@ namespace Barotrauma
|
||||
Barotrauma.IO.Validation.SkipValidationInDebugBuilds = false;
|
||||
ToolBox.OpenFileWithShell(Path.GetFullPath(filePath));
|
||||
}));
|
||||
|
||||
#if DEBUG
|
||||
commands.Add(new Command("playovervc", "Plays a sound over voice chat.", (args) =>
|
||||
{
|
||||
VoipCapture.Instance?.SetOverrideSound(args.Length > 0 ? args[0] : null);
|
||||
}));
|
||||
|
||||
commands.Add(new Command("querylobbies", "Queries all SteamP2P lobbies", (args) =>
|
||||
{
|
||||
TaskPool.Add("DebugQueryLobbies",
|
||||
SteamManager.LobbyQueryRequest(), (t) =>
|
||||
{
|
||||
t.TryGetResult(out List<Steamworks.Data.Lobby> lobbies);
|
||||
foreach (var lobby in lobbies)
|
||||
{
|
||||
NewMessage(lobby.GetData("name") + ", " + lobby.GetData("lobbyowner"), Color.Yellow);
|
||||
}
|
||||
NewMessage($"Retrieved a total of {lobbies.Count} lobbies", Color.Lime);
|
||||
});
|
||||
}));
|
||||
|
||||
commands.Add(new Command("checkduplicates", "Checks the given language for duplicate translation keys and writes to file.", (string[] args) =>
|
||||
{
|
||||
if (args.Length != 1) { return; }
|
||||
@@ -3056,11 +3038,6 @@ namespace Barotrauma
|
||||
|
||||
commands.Add(new Command("reloadwearables", "Reloads the sprites of all limbs and wearable sprites (clothing) of the controlled character. Provide id or name if you want to target another character.", args =>
|
||||
{
|
||||
if (GameMain.GameSession != null)
|
||||
{
|
||||
ThrowError("Using the command is not allowed during an active game session: the command is intended to be used in the character editor or in the main menu.");
|
||||
return;
|
||||
}
|
||||
var character = (args.Length == 0) ? Character.Controlled : FindMatchingCharacter(args, true);
|
||||
if (character == null)
|
||||
{
|
||||
@@ -3072,11 +3049,6 @@ namespace Barotrauma
|
||||
|
||||
commands.Add(new Command("loadwearable", "Force select certain variant for the selected character.", args =>
|
||||
{
|
||||
if (GameMain.GameSession != null)
|
||||
{
|
||||
ThrowError("Using the command is not allowed during an active game session: the command is intended to be used in the character editor or in the main menu.");
|
||||
return;
|
||||
}
|
||||
var character = Character.Controlled;
|
||||
if (character == null)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
using Barotrauma.Tutorials;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma;
|
||||
|
||||
partial class MessageBoxAction : EventAction
|
||||
{
|
||||
partial void UpdateProjSpecific()
|
||||
{
|
||||
if (Type == ActionType.Create)
|
||||
{
|
||||
CreateMessageBox();
|
||||
if (!ObjectiveTag.IsEmpty && GameMain.GameSession?.GameMode is TutorialMode tutorialMode)
|
||||
{
|
||||
Identifier id = Identifier.IsEmpty ? Text : Identifier;
|
||||
var segment = Tutorial.Segment.CreateMessageBoxSegment(id, ObjectiveTag, CreateMessageBox);
|
||||
tutorialMode.Tutorial?.TriggerTutorialSegment(segment);
|
||||
}
|
||||
}
|
||||
else if (Type == ActionType.Close)
|
||||
{
|
||||
GUIMessageBox.Close(Tag);
|
||||
}
|
||||
}
|
||||
|
||||
public void CreateMessageBox()
|
||||
{
|
||||
new GUIMessageBox(
|
||||
headerText: TextManager.Get(Header),
|
||||
text: RichString.Rich(TextManager.ParseInputTypes(TextManager.Get(Text).Fallback(Text.ToString()), useColorHighlight: true)),
|
||||
buttons: Array.Empty<LocalizedString>(),
|
||||
type: GUIMessageBox.Type.Tutorial,
|
||||
tag: Tag,
|
||||
iconStyle: IconStyle,
|
||||
autoCloseCondition: GetAutoCloseCondition(),
|
||||
hideCloseButton: HideCloseButton)
|
||||
{
|
||||
FlashOnAutoCloseCondition = true
|
||||
};
|
||||
}
|
||||
|
||||
private Func<bool> GetAutoCloseCondition()
|
||||
{
|
||||
var character = ParentEvent.GetTargets(TargetTag).FirstOrDefault() as Character;
|
||||
Func<bool> autoCloseCondition = null;
|
||||
if (!string.IsNullOrEmpty(CloseOnInput) && Enum.TryParse(CloseOnInput, true, out InputType closeOnInput))
|
||||
{
|
||||
autoCloseCondition = () => PlayerInput.KeyDown(closeOnInput);
|
||||
}
|
||||
else if (!CloseOnSelectTag.IsEmpty)
|
||||
{
|
||||
autoCloseCondition = () => character?.SelectedItem != null && character.SelectedItem.HasTag(CloseOnSelectTag);
|
||||
}
|
||||
else if (!CloseOnPickUpTag.IsEmpty)
|
||||
{
|
||||
autoCloseCondition = () => character?.Inventory != null && character.Inventory.FindItemByTag(CloseOnPickUpTag, recursive: true) != null;
|
||||
}
|
||||
else if (!CloseOnEquipTag.IsEmpty)
|
||||
{
|
||||
autoCloseCondition = () => character != null && character.HasEquippedItem(CloseOnEquipTag);
|
||||
}
|
||||
else if (!CloseOnExitRoomName.IsEmpty)
|
||||
{
|
||||
autoCloseCondition = () => character?.CurrentHull == null || character.CurrentHull.RoomName.ToIdentifier() != CloseOnExitRoomName;
|
||||
}
|
||||
else if (!CloseOnInRoomName.IsEmpty)
|
||||
{
|
||||
autoCloseCondition = () => character?.CurrentHull != null && character.CurrentHull.RoomName.ToIdentifier() == CloseOnInRoomName;
|
||||
}
|
||||
return autoCloseCondition;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma;
|
||||
|
||||
partial class TutorialHighlightAction : EventAction
|
||||
{
|
||||
private static readonly Color highlightColor = Color.OrangeRed;
|
||||
|
||||
partial void UpdateProjSpecific()
|
||||
{
|
||||
if (GameMain.GameSession?.GameMode is not TutorialMode) { return; }
|
||||
foreach (var target in ParentEvent.GetTargets(TargetTag))
|
||||
{
|
||||
SetHighlight(target);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetHighlight(Entity entity)
|
||||
{
|
||||
if (entity is Item i)
|
||||
{
|
||||
SetHighlight(i);
|
||||
}
|
||||
else if (entity is Structure s)
|
||||
{
|
||||
SetHighlight(s);
|
||||
}
|
||||
else if (entity is Character c)
|
||||
{
|
||||
SetHighlight(c);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetHighlight(Item item)
|
||||
{
|
||||
if (item.ExternalHighlight == State) { return; }
|
||||
item.SpriteColor = (State) ? highlightColor : Color.White;
|
||||
item.ExternalHighlight = State;
|
||||
}
|
||||
|
||||
private void SetHighlight(Structure structure)
|
||||
{
|
||||
structure.SpriteColor = (State) ? highlightColor : Color.White;
|
||||
structure.ExternalHighlight = State;
|
||||
}
|
||||
|
||||
private void SetHighlight(Character character)
|
||||
{
|
||||
character.ExternalHighlight = State;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
using Barotrauma.Tutorials;
|
||||
|
||||
namespace Barotrauma;
|
||||
|
||||
partial class TutorialSegmentAction : EventAction
|
||||
{
|
||||
private Tutorial.Segment segment;
|
||||
|
||||
partial void UpdateProjSpecific()
|
||||
{
|
||||
// Only need to create the segment when it's being triggered (otherwise the tutorial already has the segment instance)
|
||||
if (Type == SegmentActionType.Trigger)
|
||||
{
|
||||
segment = Tutorial.Segment.CreateInfoBoxSegment(Id, ObjectiveTag, AutoPlayVideo ? Tutorials.AutoPlayVideo.Yes : Tutorials.AutoPlayVideo.No,
|
||||
new Tutorial.Segment.Text(TextTag, Width, Height, Anchor.Center),
|
||||
new Tutorial.Segment.Video(VideoFile, TextTag, Width, Height));
|
||||
}
|
||||
else if (Type == SegmentActionType.Add)
|
||||
{
|
||||
segment = Tutorial.Segment.CreateObjectiveSegment(Id, !ObjectiveTag.IsEmpty ? ObjectiveTag : Id);
|
||||
}
|
||||
if (GameMain.GameSession?.GameMode is TutorialMode tutorialMode)
|
||||
{
|
||||
if (tutorialMode.Tutorial is Tutorial tutorial)
|
||||
{
|
||||
switch (Type)
|
||||
{
|
||||
case SegmentActionType.Trigger:
|
||||
case SegmentActionType.Add:
|
||||
tutorial.TriggerTutorialSegment(segment);
|
||||
break;
|
||||
case SegmentActionType.Complete:
|
||||
tutorial.CompleteTutorialSegment(Id);
|
||||
break;
|
||||
case SegmentActionType.Remove:
|
||||
tutorial.RemoveTutorialSegment(Id);
|
||||
break;
|
||||
case SegmentActionType.CompleteAndRemove:
|
||||
tutorial.CompleteTutorialSegment(Id);
|
||||
tutorial.RemoveTutorialSegment(Id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ShowError($"Error in event \"{ParentEvent.Prefab.Identifier}\": attempting to use TutorialSegmentAction during a non-Tutorial game mode!");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,23 +6,26 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using System.Threading;
|
||||
using Barotrauma.Threading;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class ScalableFont : IDisposable
|
||||
{
|
||||
private static List<ScalableFont> FontList = new List<ScalableFont>();
|
||||
private static readonly List<ScalableFont> FontList = new List<ScalableFont>();
|
||||
private static Library Lib = null;
|
||||
private readonly object mutex = new object();
|
||||
private static readonly object globalMutex = new object();
|
||||
|
||||
private readonly ReaderWriterLockSlim rwl = new ReaderWriterLockSlim();
|
||||
|
||||
private string filename;
|
||||
private Face face;
|
||||
private readonly string filename;
|
||||
private readonly Face face;
|
||||
private uint size;
|
||||
private int baseHeight;
|
||||
private Dictionary<uint, GlyphData> texCoords;
|
||||
private List<Texture2D> textures;
|
||||
private GraphicsDevice graphicsDevice;
|
||||
private readonly Dictionary<uint, GlyphData> texCoords;
|
||||
private readonly List<Texture2D> textures;
|
||||
private readonly GraphicsDevice graphicsDevice;
|
||||
|
||||
private Vector2 currentDynamicAtlasCoords;
|
||||
private int currentDynamicAtlasNextY;
|
||||
@@ -49,7 +52,7 @@ namespace Barotrauma
|
||||
set
|
||||
{
|
||||
size = value;
|
||||
if (graphicsDevice != null) RenderAtlas(graphicsDevice, charRanges, texDims, baseChar);
|
||||
if (graphicsDevice != null) { RenderAtlas(graphicsDevice, charRanges, texDims, baseChar); }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,11 +96,15 @@ namespace Barotrauma
|
||||
|
||||
public ScalableFont(string filename, uint size, GraphicsDevice gd = null, bool dynamicLoading = false, bool isCJK = false)
|
||||
{
|
||||
lock (mutex)
|
||||
lock (globalMutex)
|
||||
{
|
||||
Lib ??= new Library();
|
||||
}
|
||||
|
||||
this.filename = filename;
|
||||
this.face = null;
|
||||
using (new ReadLock(rwl))
|
||||
{
|
||||
if (Lib == null) Lib = new Library();
|
||||
this.filename = filename;
|
||||
this.face = null;
|
||||
foreach (ScalableFont font in FontList)
|
||||
{
|
||||
if (font.filename == filename)
|
||||
@@ -106,19 +113,23 @@ namespace Barotrauma
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.face ??= new Face(Lib, filename);
|
||||
this.size = size;
|
||||
this.textures = new List<Texture2D>();
|
||||
this.texCoords = new Dictionary<uint, GlyphData>();
|
||||
this.DynamicLoading = dynamicLoading;
|
||||
this.IsCJK = isCJK;
|
||||
this.graphicsDevice = gd;
|
||||
}
|
||||
|
||||
if (gd != null && !dynamicLoading)
|
||||
{
|
||||
RenderAtlas(gd);
|
||||
}
|
||||
this.face ??= new Face(Lib, filename);
|
||||
this.size = size;
|
||||
this.textures = new List<Texture2D>();
|
||||
this.texCoords = new Dictionary<uint, GlyphData>();
|
||||
this.DynamicLoading = dynamicLoading;
|
||||
this.IsCJK = isCJK;
|
||||
this.graphicsDevice = gd;
|
||||
|
||||
if (gd != null && !dynamicLoading)
|
||||
{
|
||||
RenderAtlas(gd);
|
||||
}
|
||||
|
||||
lock (globalMutex)
|
||||
{
|
||||
FontList.Add(this);
|
||||
}
|
||||
}
|
||||
@@ -162,7 +173,7 @@ namespace Barotrauma
|
||||
Vector2 currentCoords = Vector2.Zero;
|
||||
int nextY = 0;
|
||||
|
||||
lock (mutex)
|
||||
using (new WriteLock(rwl))
|
||||
{
|
||||
face.SetPixelSizes(0, size);
|
||||
face.LoadGlyph(face.GetCharIndex(baseChar), LoadFlags.Default, LoadTarget.Normal);
|
||||
@@ -175,19 +186,22 @@ namespace Barotrauma
|
||||
for (uint j = start; j <= end; j++)
|
||||
{
|
||||
uint glyphIndex = face.GetCharIndex(j);
|
||||
if (glyphIndex == 0) continue;
|
||||
if (glyphIndex == 0)
|
||||
{
|
||||
texCoords.Add(j, new GlyphData(
|
||||
advance: 0,
|
||||
texIndex: -1));
|
||||
continue;
|
||||
}
|
||||
face.LoadGlyph(glyphIndex, LoadFlags.Default, LoadTarget.Normal);
|
||||
if (face.Glyph.Metrics.Width == 0 || face.Glyph.Metrics.Height == 0)
|
||||
{
|
||||
if (face.Glyph.Metrics.HorizontalAdvance > 0)
|
||||
{
|
||||
//glyph is empty, but char still applies advance
|
||||
GlyphData blankData = new GlyphData(
|
||||
advance: (float)face.Glyph.Metrics.HorizontalAdvance,
|
||||
texIndex: -1); //indicates no texture because the glyph is empty
|
||||
//glyph is empty, but char might still apply advance
|
||||
GlyphData blankData = new GlyphData(
|
||||
advance: Math.Max((float)face.Glyph.Metrics.HorizontalAdvance, 0f),
|
||||
texIndex: -1); //indicates no texture because the glyph is empty
|
||||
|
||||
texCoords.Add(j, blankData);
|
||||
}
|
||||
texCoords.Add(j, blankData);
|
||||
continue;
|
||||
}
|
||||
//stacktrace doesn't really work that well when RenderGlyph throws an exception
|
||||
@@ -257,7 +271,7 @@ namespace Barotrauma
|
||||
private void DynamicRenderAtlas(GraphicsDevice gd, uint character, int texDims = 1024, uint baseChar = 0x54)
|
||||
{
|
||||
bool missingCharacterFound = false;
|
||||
lock (mutex)
|
||||
using (new ReadLock(rwl))
|
||||
{
|
||||
missingCharacterFound = !texCoords.ContainsKey(character);
|
||||
}
|
||||
@@ -268,10 +282,9 @@ namespace Barotrauma
|
||||
private void DynamicRenderAtlas(GraphicsDevice gd, string str, int texDims = 1024, uint baseChar = 0x54)
|
||||
{
|
||||
bool missingCharacterFound = false;
|
||||
var distinctChrs = str.Distinct().Select(c => (uint)c).ToArray();
|
||||
lock (mutex)
|
||||
using (new ReadLock(rwl))
|
||||
{
|
||||
foreach (var character in distinctChrs)
|
||||
foreach (var character in str)
|
||||
{
|
||||
if (texCoords.ContainsKey(character)) { continue; }
|
||||
|
||||
@@ -280,7 +293,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
if (!missingCharacterFound) { return; }
|
||||
DynamicRenderAtlas(gd, distinctChrs, texDims, baseChar);
|
||||
DynamicRenderAtlas(gd, str.Select(c => (uint)c), texDims, baseChar);
|
||||
}
|
||||
|
||||
private void DynamicRenderAtlas(GraphicsDevice gd, IEnumerable<uint> characters, int texDims = 1024, uint baseChar = 0x54)
|
||||
@@ -299,7 +312,7 @@ namespace Barotrauma
|
||||
Fixed26Dot6 horizontalAdvance;
|
||||
Vector2 drawOffset;
|
||||
|
||||
lock (mutex)
|
||||
using (new WriteLock(rwl))
|
||||
{
|
||||
if (textures.Count == 0)
|
||||
{
|
||||
@@ -318,20 +331,23 @@ namespace Barotrauma
|
||||
if (texCoords.ContainsKey(character)) { continue; }
|
||||
|
||||
uint glyphIndex = face.GetCharIndex(character);
|
||||
if (glyphIndex == 0) { continue; }
|
||||
if (glyphIndex == 0)
|
||||
{
|
||||
texCoords.Add(character, new GlyphData(
|
||||
advance: 0,
|
||||
texIndex: -1));
|
||||
continue;
|
||||
}
|
||||
|
||||
face.SetPixelSizes(0, size);
|
||||
face.LoadGlyph(glyphIndex, LoadFlags.Default, LoadTarget.Normal);
|
||||
if (face.Glyph.Metrics.Width == 0 || face.Glyph.Metrics.Height == 0)
|
||||
{
|
||||
if (face.Glyph.Metrics.HorizontalAdvance > 0)
|
||||
{
|
||||
//glyph is empty, but char still applies advance
|
||||
GlyphData blankData = new GlyphData(
|
||||
advance: (float)face.Glyph.Metrics.HorizontalAdvance,
|
||||
texIndex: -1); //indicates no texture because the glyph is empty
|
||||
texCoords.Add(character, blankData);
|
||||
}
|
||||
//glyph is empty, but char might still apply advance
|
||||
GlyphData blankData = new GlyphData(
|
||||
advance: Math.Max((float)face.Glyph.Metrics.HorizontalAdvance, 0f),
|
||||
texIndex: -1); //indicates no texture because the glyph is empty
|
||||
texCoords.Add(character, blankData);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -730,7 +730,7 @@ namespace Barotrauma
|
||||
public void DrawToolTip(SpriteBatch spriteBatch)
|
||||
{
|
||||
if (!Visible) { return; }
|
||||
DrawToolTip(spriteBatch, ToolTip, GUI.MouseOn.Rect);
|
||||
DrawToolTip(spriteBatch, ToolTip, Rect);
|
||||
}
|
||||
|
||||
public static void DrawToolTip(SpriteBatch spriteBatch, RichString toolTip, Vector2 pos)
|
||||
@@ -781,7 +781,7 @@ namespace Barotrauma
|
||||
if (toolTipBlock.Rect.Bottom > GameMain.GraphicsHeight - 10)
|
||||
{
|
||||
toolTipBlock.RectTransform.AbsoluteOffset -= new Point(
|
||||
(targetElement.Width / 2) * Math.Sign(targetElement.Center.X - toolTipBlock.Center.X),
|
||||
0,
|
||||
toolTipBlock.Rect.Bottom - (GameMain.GraphicsHeight - 10));
|
||||
}
|
||||
toolTipBlock.SetTextPos();
|
||||
|
||||
@@ -204,15 +204,7 @@ namespace Barotrauma
|
||||
|
||||
currentHighestParent = FindHighestParent();
|
||||
currentHighestParent.GUIComponent.OnAddedToGUIUpdateList += AddListBoxToGUIUpdateList;
|
||||
rectT.ParentChanged += (RectTransform newParent) =>
|
||||
{
|
||||
currentHighestParent.GUIComponent.OnAddedToGUIUpdateList -= AddListBoxToGUIUpdateList;
|
||||
if (newParent != null)
|
||||
{
|
||||
currentHighestParent = FindHighestParent();
|
||||
currentHighestParent.GUIComponent.OnAddedToGUIUpdateList += AddListBoxToGUIUpdateList;
|
||||
}
|
||||
};
|
||||
rectT.ParentChanged += _ => RefreshListBoxParent();
|
||||
}
|
||||
|
||||
|
||||
@@ -396,6 +388,15 @@ namespace Barotrauma
|
||||
return true;
|
||||
}
|
||||
|
||||
public void RefreshListBoxParent()
|
||||
{
|
||||
currentHighestParent.GUIComponent.OnAddedToGUIUpdateList -= AddListBoxToGUIUpdateList;
|
||||
if (RectTransform.Parent == null) { return; }
|
||||
|
||||
currentHighestParent = FindHighestParent();
|
||||
currentHighestParent.GUIComponent.OnAddedToGUIUpdateList += AddListBoxToGUIUpdateList;
|
||||
}
|
||||
|
||||
private void AddListBoxToGUIUpdateList(GUIComponent parent)
|
||||
{
|
||||
//the parent is not our parent anymore :(
|
||||
@@ -403,11 +404,13 @@ namespace Barotrauma
|
||||
//and somewhere between this component and the higher parent a component was removed
|
||||
for (int i = 1; i < parentHierarchy.Count; i++)
|
||||
{
|
||||
if (!parentHierarchy[i].IsParentOf(parentHierarchy[i - 1], recursive: false))
|
||||
if (parentHierarchy[i].IsParentOf(parentHierarchy[i - 1], recursive: false))
|
||||
{
|
||||
parent.OnAddedToGUIUpdateList -= AddListBoxToGUIUpdateList;
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
|
||||
parent.OnAddedToGUIUpdateList -= AddListBoxToGUIUpdateList;
|
||||
return;
|
||||
}
|
||||
|
||||
if (Dropped)
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace Barotrauma
|
||||
public GUIFrame InnerFrame { get; private set; }
|
||||
public GUITextBlock Header { get; private set; }
|
||||
public GUITextBlock Text { get; private set; }
|
||||
public string Tag { get; private set; }
|
||||
public Identifier Tag { get; private set; }
|
||||
public bool Closed { get; private set; }
|
||||
|
||||
public bool DisplayInLoadingScreens;
|
||||
@@ -78,6 +78,8 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
private readonly Func<bool> autoCloseCondition;
|
||||
|
||||
public bool FlashOnAutoCloseCondition { get; set; }
|
||||
|
||||
public Type MessageBoxType => type;
|
||||
|
||||
public static GUIComponent VisibleBox => MessageBoxes.LastOrDefault();
|
||||
@@ -142,7 +144,7 @@ namespace Barotrauma
|
||||
}
|
||||
GUIStyle.Apply(InnerFrame, "", this);
|
||||
this.type = type;
|
||||
Tag = tag;
|
||||
Tag = tag.ToIdentifier();
|
||||
|
||||
#warning TODO: These should be broken into separate methods at least
|
||||
if (type == Type.Default || type == Type.Vote)
|
||||
@@ -199,6 +201,7 @@ namespace Barotrauma
|
||||
var button = new GUIButton(new RectTransform(new Vector2(0.6f, 1.0f / buttons.Length), buttonContainer.RectTransform), buttons[i]);
|
||||
Buttons.Add(button);
|
||||
}
|
||||
GUITextBlock.AutoScaleAndNormalize(Buttons.Select(btn => btn.TextBlock));
|
||||
}
|
||||
else if (type == Type.InGame || type == Type.Tutorial)
|
||||
{
|
||||
@@ -540,9 +543,7 @@ namespace Barotrauma
|
||||
if (type == Type.InGame || type == Type.Tutorial)
|
||||
{
|
||||
initialPos = new Vector2(0.0f, GameMain.GraphicsHeight);
|
||||
defaultPos = type == Type.InGame ?
|
||||
new Vector2(0.0f, HUDLayoutSettings.InventoryAreaLower.Y - InnerFrame.Rect.Height - 20 * GUI.Scale) :
|
||||
new Vector2(0.0f, GameMain.GraphicsHeight / 2);
|
||||
defaultPos = new Vector2(0.0f, HUDLayoutSettings.InventoryAreaLower.Y - InnerFrame.Rect.Height - 20 * GUI.Scale);
|
||||
endPos = new Vector2(GameMain.GraphicsWidth, defaultPos.Y);
|
||||
}
|
||||
else
|
||||
@@ -574,10 +575,18 @@ namespace Barotrauma
|
||||
inGameCloseTimer += deltaTime;
|
||||
}
|
||||
|
||||
if (inGameCloseTimer >= inGameCloseTime || (autoCloseCondition != null && autoCloseCondition()))
|
||||
if (inGameCloseTimer >= inGameCloseTime)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
else if (autoCloseCondition != null && autoCloseCondition())
|
||||
{
|
||||
Close();
|
||||
if (FlashOnAutoCloseCondition)
|
||||
{
|
||||
InnerFrame.Flash(GUIStyle.Green);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -634,7 +643,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void Close()
|
||||
{
|
||||
if (IsAnimated)
|
||||
@@ -662,6 +670,19 @@ namespace Barotrauma
|
||||
MessageBoxes.Clear();
|
||||
}
|
||||
|
||||
public static void Close(Identifier tag)
|
||||
{
|
||||
foreach (var messageBox in MessageBoxes)
|
||||
{
|
||||
if (messageBox is GUIMessageBox mb && mb.Tag == tag)
|
||||
{
|
||||
mb.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void Close(string tag) => Close(tag.ToIdentifier());
|
||||
|
||||
/// <summary>
|
||||
/// Parent does not matter. It's overridden.
|
||||
/// </summary>
|
||||
|
||||
@@ -178,19 +178,19 @@ namespace Barotrauma
|
||||
{
|
||||
public GUIFont(string identifier) : base(identifier) { }
|
||||
|
||||
public bool HasValue => Prefabs.Any();
|
||||
public bool HasValue => !Prefabs.IsEmpty;
|
||||
|
||||
public ScalableFont Value => Prefabs.ActivePrefab.Font;
|
||||
|
||||
public static implicit operator ScalableFont(GUIFont reference) => reference.Value;
|
||||
|
||||
public bool ForceUpperCase => HasValue && Value.ForceUpperCase;
|
||||
public bool ForceUpperCase => Prefabs.ActivePrefab?.Font is { ForceUpperCase: true };
|
||||
|
||||
public uint Size => HasValue ? Value.Size : 0;
|
||||
|
||||
private ScalableFont GetFontForStr(LocalizedString str) => GetFontForStr(str.Value);
|
||||
|
||||
private ScalableFont GetFontForStr(string str) =>
|
||||
public ScalableFont GetFontForStr(string str) =>
|
||||
TextManager.IsCJK(str) ? Prefabs.ActivePrefab.CjkFont : Prefabs.ActivePrefab.Font;
|
||||
|
||||
public void DrawString(SpriteBatch sb, LocalizedString text, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects se, float layerDepth)
|
||||
|
||||
@@ -161,7 +161,7 @@ namespace Barotrauma
|
||||
public static Point ItemFrameOffset => new Point(0, 3).Multiply(GUI.SlicedSpriteScale);
|
||||
|
||||
public static GUIComponentStyle GetComponentStyle(string name)
|
||||
=> ComponentStyles.ContainsKey(name) ? ComponentStyles[name] : null;
|
||||
=> ComponentStyles.TryGet(name, out var style) ? style : null;
|
||||
|
||||
public static void Apply(GUIComponent targetComponent, string styleName = "", GUIComponent parent = null)
|
||||
{
|
||||
|
||||
@@ -177,7 +177,7 @@ namespace Barotrauma
|
||||
radioButtonGroup = rbg;
|
||||
}
|
||||
|
||||
private void ResizeBox()
|
||||
public void ResizeBox()
|
||||
{
|
||||
Vector2 textBlockScale = new Vector2(Math.Max(Rect.Width - box.Rect.Width, 0.0f) / Math.Max(Rect.Width, 1.0f), 1.0f);
|
||||
text.RectTransform.RelativeSize = textBlockScale;
|
||||
|
||||
@@ -233,7 +233,9 @@ namespace Barotrauma
|
||||
get
|
||||
{
|
||||
Point absoluteOffset = ConvertOffsetRelativeToAnchor(AbsoluteOffset, Anchor);
|
||||
Point relativeOffset = NonScaledParentRect.MultiplySize(RelativeOffset);
|
||||
Point relativeOffset = new Point(
|
||||
(int)(NonScaledParentSize.X * RelativeOffset.X),
|
||||
(int)(NonScaledParentSize.Y * RelativeOffset.Y));
|
||||
relativeOffset = ConvertOffsetRelativeToAnchor(relativeOffset, Anchor);
|
||||
return AnchorPoint + PivotOffset + absoluteOffset + relativeOffset + ScreenSpaceOffset;
|
||||
}
|
||||
@@ -256,6 +258,7 @@ namespace Barotrauma
|
||||
public Rectangle ParentRect => Parent != null ? Parent.Rect : UIRect;
|
||||
protected Rectangle NonScaledRect => new Rectangle(NonScaledTopLeft, NonScaledSize);
|
||||
protected virtual Rectangle NonScaledUIRect => NonScaledRect;
|
||||
protected Point NonScaledParentSize => parent?.NonScaledSize ?? new Point(GUI.UIWidth, GameMain.GraphicsHeight);
|
||||
protected Rectangle NonScaledParentRect => parent != null ? Parent.NonScaledRect : UIRect;
|
||||
protected Rectangle NonScaledParentUIRect => parent != null ? Parent.NonScaledUIRect : UIRect;
|
||||
protected Rectangle UIRect => new Rectangle(0, 0, GUI.UIWidth, GameMain.GraphicsHeight);
|
||||
@@ -336,6 +339,11 @@ namespace Barotrauma
|
||||
public event Action<RectTransform> ChildrenChanged;
|
||||
public event Action ScaleChanged;
|
||||
public event Action SizeChanged;
|
||||
|
||||
public void ResetSizeChanged()
|
||||
{
|
||||
SizeChanged = null;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Initialization
|
||||
@@ -730,17 +738,17 @@ namespace Barotrauma
|
||||
get { return animTargetPos ?? AbsoluteOffset; }
|
||||
}
|
||||
|
||||
public void MoveOverTime(Point targetPos, float duration)
|
||||
public void MoveOverTime(Point targetPos, float duration, Action onDoneMoving = null)
|
||||
{
|
||||
animTargetPos = targetPos;
|
||||
CoroutineManager.StartCoroutine(DoMoveAnimation(targetPos, duration));
|
||||
CoroutineManager.StartCoroutine(DoMoveAnimation(targetPos, duration, onDoneMoving));
|
||||
}
|
||||
public void ScaleOverTime(Point targetSize, float duration)
|
||||
{
|
||||
CoroutineManager.StartCoroutine(DoScaleAnimation(targetSize, duration));
|
||||
}
|
||||
|
||||
private IEnumerable<CoroutineStatus> DoMoveAnimation(Point targetPos, float duration)
|
||||
private IEnumerable<CoroutineStatus> DoMoveAnimation(Point targetPos, float duration, Action onDoneMoving = null)
|
||||
{
|
||||
Vector2 startPos = AbsoluteOffset.ToVector2();
|
||||
float t = 0.0f;
|
||||
@@ -752,6 +760,7 @@ namespace Barotrauma
|
||||
}
|
||||
AbsoluteOffset = targetPos;
|
||||
animTargetPos = null;
|
||||
onDoneMoving?.Invoke();
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
private IEnumerable<CoroutineStatus> DoScaleAnimation(Point targetSize, float duration)
|
||||
|
||||
@@ -226,17 +226,18 @@ namespace Barotrauma
|
||||
};
|
||||
submarineDisplayElement.submarineImage = new GUIImage(new RectTransform(new Vector2(0.8f, 1f), submarineDisplayElement.background.RectTransform, Anchor.Center), null, true);
|
||||
submarineDisplayElement.middleTextBlock = new GUITextBlock(new RectTransform(new Vector2(0.8f, 1f), submarineDisplayElement.background.RectTransform, Anchor.Center), string.Empty, textAlignment: Alignment.Center);
|
||||
submarineDisplayElement.submarineName = new GUITextBlock(new RectTransform(new Vector2(1f, 0.1f), submarineDisplayElement.background.RectTransform, Anchor.TopCenter, Pivot.TopCenter) { AbsoluteOffset = new Point(0, HUDLayoutSettings.Padding) }, string.Empty, textAlignment: Alignment.Center, font: GUIStyle.SubHeadingFont);
|
||||
submarineDisplayElement.submarineClass = new GUITextBlock(new RectTransform(new Vector2(1f, 0.1f), submarineDisplayElement.background.RectTransform, Anchor.TopCenter, Pivot.TopCenter) { AbsoluteOffset = new Point(0, HUDLayoutSettings.Padding + (int)GUIStyle.Font.MeasureString(submarineDisplayElement.submarineName.Text).Y) }, string.Empty, textAlignment: Alignment.Left);
|
||||
submarineDisplayElement.submarineTier = new GUITextBlock(new RectTransform(new Vector2(1f, 0.1f), submarineDisplayElement.background.RectTransform, Anchor.TopCenter, Pivot.TopCenter) { AbsoluteOffset = new Point(0, HUDLayoutSettings.Padding + (int)GUIStyle.Font.MeasureString(submarineDisplayElement.submarineName.Text).Y) }, string.Empty, textAlignment: Alignment.Right);
|
||||
submarineDisplayElement.submarineFee = new GUITextBlock(new RectTransform(new Vector2(1f, 0.1f), submarineDisplayElement.background.RectTransform, Anchor.BottomCenter, Pivot.BottomCenter) { AbsoluteOffset = new Point(0, HUDLayoutSettings.Padding) }, string.Empty, textAlignment: Alignment.Center, font: GUIStyle.SubHeadingFont);
|
||||
submarineDisplayElement.submarineName = new GUITextBlock(new RectTransform(new Vector2(1f, 0.1f), submarineDisplayElement.background.RectTransform, Anchor.TopCenter) { AbsoluteOffset = new Point(0, HUDLayoutSettings.Padding) }, string.Empty, textAlignment: Alignment.Center, font: GUIStyle.SubHeadingFont);
|
||||
submarineDisplayElement.submarineFee = new GUITextBlock(new RectTransform(new Vector2(1f, 0.1f), submarineDisplayElement.background.RectTransform, Anchor.BottomCenter) { AbsoluteOffset = new Point(0, HUDLayoutSettings.Padding) }, string.Empty, textAlignment: Alignment.Center, font: GUIStyle.SubHeadingFont);
|
||||
submarineDisplayElement.selectSubmarineButton = new GUIButton(new RectTransform(Vector2.One, submarineDisplayElement.background.RectTransform), style: null);
|
||||
submarineDisplayElement.previewButton = new GUIButton(new RectTransform(Vector2.One * 0.12f, submarineDisplayElement.background.RectTransform, anchor: Anchor.BottomRight, pivot: Pivot.BottomRight, scaleBasis: ScaleBasis.BothHeight) { AbsoluteOffset = new Point((int)(0.03f * background.Rect.Height)) }, style: "ExpandButton")
|
||||
submarineDisplayElement.previewButton = new GUIButton(new RectTransform(Vector2.One * 0.12f, submarineDisplayElement.background.RectTransform, anchor: Anchor.BottomRight, scaleBasis: ScaleBasis.BothHeight) { AbsoluteOffset = new Point((int)(0.03f * background.Rect.Height)) }, style: "ExpandButton")
|
||||
{
|
||||
Color = Color.White,
|
||||
HoverColor = Color.White,
|
||||
PressedColor = Color.White
|
||||
};
|
||||
submarineDisplayElement.submarineClass = new GUITextBlock(new RectTransform(new Vector2(1f, 0.1f), submarineDisplayElement.background.RectTransform, Anchor.TopCenter) { AbsoluteOffset = new Point(0, HUDLayoutSettings.Padding + (int)GUIStyle.Font.MeasureString(submarineDisplayElement.submarineName.Text).Y) }, string.Empty, textAlignment: Alignment.Left);
|
||||
submarineDisplayElement.submarineTier = new GUITextBlock(new RectTransform(new Vector2(0.5f, 0.1f), submarineDisplayElement.background.RectTransform, Anchor.TopRight) { AbsoluteOffset = new Point(0, HUDLayoutSettings.Padding + (int)GUIStyle.Font.MeasureString(submarineDisplayElement.submarineName.Text).Y) }, string.Empty, textAlignment: Alignment.Right);
|
||||
|
||||
submarineDisplays[i] = submarineDisplayElement;
|
||||
}
|
||||
|
||||
@@ -395,9 +396,13 @@ namespace Barotrauma
|
||||
return true;
|
||||
};
|
||||
|
||||
submarineDisplays[i].submarineName.Text = subToDisplay.DisplayName;
|
||||
submarineDisplays[i].submarineName.Text = subToDisplay.DisplayName;
|
||||
|
||||
submarineDisplays[i].submarineClass.Text = TextManager.GetWithVariable("submarineclass.classsuffixformat", "[type]", TextManager.Get($"submarineclass.{subToDisplay.SubmarineClass}"));
|
||||
submarineDisplays[i].submarineClass.ToolTip = TextManager.Get("submarineclass.description") + "\n\n" + TextManager.Get($"submarineclass.{subToDisplay.SubmarineClass}.description");
|
||||
|
||||
submarineDisplays[i].submarineTier.Text = TextManager.Get($"submarinetier.{subToDisplay.Tier}");
|
||||
submarineDisplays[i].submarineTier.ToolTip = TextManager.Get("submarinetier.description");
|
||||
|
||||
if (!GameMain.GameSession.IsSubmarineOwned(subToDisplay))
|
||||
{
|
||||
|
||||
@@ -1722,7 +1722,11 @@ namespace Barotrauma
|
||||
|
||||
var subInfoTextLayout = new GUILayoutGroup(new RectTransform(Vector2.One, paddedFrame.RectTransform));
|
||||
|
||||
LocalizedString className = !sub.Info.HasTag(SubmarineTag.Shuttle) ? $"{TextManager.Get($"submarineclass.{sub.Info.SubmarineClass}")} ({TextManager.Get($"submarinetier.{sub.Info.Tier}")})" : TextManager.Get("shuttle");
|
||||
LocalizedString className = !sub.Info.HasTag(SubmarineTag.Shuttle) ?
|
||||
TextManager.GetWithVariables("submarine.classandtier",
|
||||
("[class]", TextManager.Get($"submarineclass.{sub.Info.SubmarineClass}")),
|
||||
("[tier]", TextManager.Get($"submarinetier.{sub.Info.Tier}"))) :
|
||||
TextManager.Get("shuttle");
|
||||
|
||||
int nameHeight = (int)GUIStyle.LargeFont.MeasureString(sub.Info.DisplayName, true).Y;
|
||||
int classHeight = (int)GUIStyle.SubHeadingFont.MeasureString(className).Y;
|
||||
@@ -2140,18 +2144,17 @@ namespace Barotrauma
|
||||
skillNames.Add(skillName);
|
||||
skillName.RectTransform.MinSize = new Point(0, skillName.Rect.Height);
|
||||
skillContainer.RectTransform.MinSize = new Point(0, skillName.Rect.Height);
|
||||
|
||||
|
||||
new GUITextBlock(new RectTransform(new Vector2(0.15f, 1.0f), skillContainer.RectTransform), Math.Floor(skill.Level).ToString("F0"), textAlignment: Alignment.TopRight);
|
||||
|
||||
float modifiedSkillLevel = character?.GetSkillLevel(skill.Identifier) ?? skill.Level;
|
||||
if (!MathUtils.NearlyEqual(MathF.Floor(modifiedSkillLevel), MathF.Floor(skill.Level)))
|
||||
{
|
||||
int skillChange = (int)MathF.Floor(modifiedSkillLevel - skill.Level);
|
||||
//TODO: if/when we upgrade to C# 9, do neater pattern matching here
|
||||
string stringColor = true switch
|
||||
string stringColor = skillChange switch
|
||||
{
|
||||
true when skillChange > 0 => XMLExtensions.ToStringHex(GUIStyle.Green),
|
||||
true when skillChange < 0 => XMLExtensions.ToStringHex(GUIStyle.Red),
|
||||
> 0 => XMLExtensions.ToStringHex(GUIStyle.Green),
|
||||
< 0 => XMLExtensions.ToStringHex(GUIStyle.Red),
|
||||
_ => XMLExtensions.ToStringHex(GUIStyle.TextColorNormal)
|
||||
};
|
||||
|
||||
|
||||
@@ -1257,12 +1257,17 @@ namespace Barotrauma
|
||||
List<Upgrade> upgrades = entity.GetUpgrades();
|
||||
int upgradesCount = upgrades.Count;
|
||||
const int maxUpgrades = 4;
|
||||
|
||||
itemName.Text = entity is Item ? entity.Name : TextManager.Get("upgradecategory.walls");
|
||||
|
||||
Item? item = entity as Item;
|
||||
itemName.Text = item?.Name ?? TextManager.Get("upgradecategory.walls");
|
||||
if (slotIndex > -1)
|
||||
{
|
||||
itemName.Text = TextManager.GetWithVariables("weaponslotwithname", ("[number]", slotIndex.ToString()), ("[weaponname]", itemName.Text));
|
||||
}
|
||||
if (item?.PendingItemSwap != null)
|
||||
{
|
||||
itemName.Text = RichString.Rich(itemName.Text + "\n" + TextManager.GetWithVariable("upgrades.pendingitem", "[itemname]", item.PendingItemSwap.Name));
|
||||
}
|
||||
upgradeList.Content.ClearChildren();
|
||||
for (var i = 0; i < upgrades.Count && i < maxUpgrades; i++)
|
||||
{
|
||||
@@ -1275,7 +1280,7 @@ namespace Barotrauma
|
||||
// include pending upgrades into the tooltip
|
||||
foreach (var (prefab, category, level) in upgradeManager.PendingUpgrades)
|
||||
{
|
||||
if (entity is Item item && category.CanBeApplied(item, prefab) || entity is Structure && category.IsWallUpgrade)
|
||||
if (item != null && category.CanBeApplied(item, prefab) || entity is Structure && category.IsWallUpgrade)
|
||||
{
|
||||
bool found = false;
|
||||
foreach (GUITextBlock textBlock in upgradeList.Content.Children.Where(c => c is GUITextBlock).Cast<GUITextBlock>())
|
||||
@@ -1392,6 +1397,10 @@ namespace Barotrauma
|
||||
if (!itemElement.Selected) { itemElement.OnClicked(itemElement, itemElement.UserData); }
|
||||
(itemElement.Parent?.Parent?.Parent as GUIListBox)?.ScrollToElement(itemElement);
|
||||
}
|
||||
else
|
||||
{
|
||||
ScrollToCategory(data => data.Category.CanBeApplied(item, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -1463,23 +1472,16 @@ namespace Barotrauma
|
||||
// submarine name
|
||||
new GUITextBlock(rectT(1, 0, submarineInfoFrame), submarine.Info.DisplayName, textAlignment: Alignment.Right, font: GUIStyle.LargeFont);
|
||||
|
||||
GUILayoutGroup classLayout = new GUILayoutGroup(rectT(1, 0.15f, submarineInfoFrame), isHorizontal: true) { Stretch = true };
|
||||
LocalizedString classText = $"{TextManager.GetWithVariable("submarineclass.classsuffixformat", "[type]", TextManager.Get($"submarineclass.{submarine.Info.SubmarineClass}"))}";
|
||||
// submarine class + tier
|
||||
new GUITextBlock(rectT(0.8f, 1, classLayout), classText, textAlignment: Alignment.Right, font: GUIStyle.Font)
|
||||
new GUITextBlock(rectT(1.0f, 0.15f, submarineInfoFrame), classText, textAlignment: Alignment.Right, font: GUIStyle.Font)
|
||||
{
|
||||
ToolTip = TextManager.Get("submarineclass.description") + '\n' + TextManager.Get($"submarineclass.{submarine.Info.SubmarineClass}.description")
|
||||
ToolTip = TextManager.Get("submarineclass.description") + "\n\n" + TextManager.Get($"submarineclass.{submarine.Info.SubmarineClass}.description")
|
||||
};
|
||||
int tier = submarine.Info.Tier;
|
||||
string tierStyle = $"SubmarineTier.{tier}";
|
||||
if (GUIStyle.GetComponentStyle(tierStyle) != null)
|
||||
new GUITextBlock(rectT(1.0f, 0.15f, submarineInfoFrame), TextManager.Get($"submarinetier.{submarine.Info.Tier}"), textAlignment: Alignment.Right, font: GUIStyle.Font)
|
||||
{
|
||||
LocalizedString tooltip = TextManager.Get("submarinetier.description").Fallback(string.Empty);
|
||||
new GUIImage(rectT(0.15f, 1, classLayout), style: tierStyle, scaleToFit: false)
|
||||
{
|
||||
ToolTip = tooltip
|
||||
};
|
||||
}
|
||||
ToolTip = TextManager.Get("submarinetier.description")
|
||||
};
|
||||
|
||||
var description = new GUITextBlock(rectT(1, 0, submarineInfoFrame), submarine.Info.Description, textAlignment: Alignment.Right, wrap: true);
|
||||
submarineInfoFrame.RectTransform.ScreenSpaceOffset = new Point(0, (int)(16 * GUI.Scale));
|
||||
@@ -1750,14 +1752,26 @@ namespace Barotrauma
|
||||
{
|
||||
if (currentStoreLayout == null) { return; }
|
||||
|
||||
CategoryData? mostAppropriateCategory = null;
|
||||
GUIComponent? mostAppropriateChild = null;
|
||||
foreach (GUIComponent child in currentStoreLayout.Content.Children)
|
||||
{
|
||||
if (child.UserData is CategoryData data && predicate(data))
|
||||
{
|
||||
currentStoreLayout.ScrollToElement(child, playSelectSound);
|
||||
break;
|
||||
//choose the category with least items in it as the "most appropriate"
|
||||
//e.g. when selecting junction boxes, we want to select the "junction boxes" category instead of "electrical repairs" which contains many electrical devices
|
||||
if (mostAppropriateCategory == null ||
|
||||
data.Category.ItemTags.Count() < mostAppropriateCategory.Value.Category.ItemTags.Count())
|
||||
{
|
||||
mostAppropriateCategory = data;
|
||||
mostAppropriateChild = child;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mostAppropriateChild != null)
|
||||
{
|
||||
currentStoreLayout.ScrollToElement(mostAppropriateChild, playSelectSound);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -461,7 +461,7 @@ namespace Barotrauma
|
||||
|
||||
yield return CoroutineStatus.Running;
|
||||
|
||||
UgcTransition.Prepare();
|
||||
LegacySteamUgcTransition.Prepare();
|
||||
var contentPackageLoadRoutine = ContentPackageManager.Init();
|
||||
foreach (var progress in contentPackageLoadRoutine)
|
||||
{
|
||||
@@ -715,7 +715,7 @@ namespace Barotrauma
|
||||
}
|
||||
#endif
|
||||
|
||||
NetworkMember?.Update((float)Timing.Step);
|
||||
Client?.Update((float)Timing.Step);
|
||||
|
||||
if (!HasLoaded && !CoroutineManager.IsCoroutineRunning(loadingCoroutine))
|
||||
{
|
||||
@@ -874,7 +874,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
NetworkMember?.Update((float)Timing.Step);
|
||||
Client?.Update((float)Timing.Step);
|
||||
|
||||
GUI.Update((float)Timing.Step);
|
||||
|
||||
@@ -1181,7 +1181,7 @@ namespace Barotrauma
|
||||
{
|
||||
exiting = true;
|
||||
DebugConsole.NewMessage("Exiting...");
|
||||
NetworkMember?.Quit();
|
||||
Client?.Quit();
|
||||
SteamManager.ShutDown();
|
||||
|
||||
try
|
||||
|
||||
@@ -3516,9 +3516,9 @@ namespace Barotrauma
|
||||
if (node == null || characterContext != null) { return false; }
|
||||
if (node.UserData is Order nodeOrder)
|
||||
{
|
||||
return !nodeOrder.TargetAllCharacters && !nodeOrder.Prefab.HasOptions &&
|
||||
(!nodeOrder.MustSetTarget || itemContext != null ||
|
||||
nodeOrder.GetMatchingItems(GetTargetSubmarine(), true, interactableFor: Character.Controlled).Count < 2);
|
||||
return !nodeOrder.TargetAllCharacters &&
|
||||
(!nodeOrder.Prefab.HasOptions || !nodeOrder.Option.IsEmpty) &&
|
||||
(!nodeOrder.MustSetTarget || itemContext != null || nodeOrder.GetMatchingItems(GetTargetSubmarine(), true, interactableFor: Character.Controlled).Count < 2);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,22 +1,21 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.IO;
|
||||
using Barotrauma.Items.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma.Tutorials
|
||||
{
|
||||
enum AutoPlayVideo { Yes, No };
|
||||
|
||||
enum TutorialSegmentType { MessageBox, InfoBox };
|
||||
enum TutorialSegmentType { MessageBox, InfoBox, Objective };
|
||||
|
||||
class Tutorial
|
||||
sealed class Tutorial
|
||||
{
|
||||
#region Constants
|
||||
|
||||
private const string PlayableContentPath = "Content/Tutorials/TutorialVideos/";
|
||||
private const SpawnType SpawnPointType = SpawnType.Human;
|
||||
private const float FadeOutTime = 3f;
|
||||
private const float WaitBeforeFade = 4f;
|
||||
@@ -25,19 +24,11 @@ namespace Barotrauma.Tutorials
|
||||
|
||||
#region Tutorial variables
|
||||
|
||||
public static ImmutableHashSet<Type> Types;
|
||||
|
||||
static Tutorial()
|
||||
{
|
||||
Types = ReflectionUtils.GetDerivedNonAbstract<Tutorial>()
|
||||
.ToImmutableHashSet();
|
||||
}
|
||||
|
||||
public readonly Identifier Identifier;
|
||||
|
||||
public LocalizedString DisplayName { get; }
|
||||
|
||||
public bool ContentRunning { get; protected set; }
|
||||
public bool ContentRunning { get; private set; }
|
||||
|
||||
private GUIComponent infoBox;
|
||||
private Action infoBoxClosedCallback;
|
||||
@@ -51,47 +42,25 @@ namespace Barotrauma.Tutorials
|
||||
private readonly LocalizedString objectiveTextTranslated;
|
||||
|
||||
private readonly List<Segment> ActiveObjectives = new List<Segment>();
|
||||
private const float ObjectiveComponentRemovalTime = 1.5f;
|
||||
private const float ObjectiveComponentAnimationTime = 1.5f;
|
||||
private Segment ActiveContentSegment { get; set; }
|
||||
|
||||
public class Segment
|
||||
{
|
||||
public struct Text
|
||||
public readonly record struct Text(
|
||||
Identifier Tag,
|
||||
int Width = DefaultWidth,
|
||||
int Height = DefaultHeight,
|
||||
Anchor Anchor = Anchor.Center);
|
||||
|
||||
public readonly record struct Video(
|
||||
string FullPath,
|
||||
Identifier TextTag,
|
||||
int Width = DefaultWidth,
|
||||
int Height = DefaultHeight)
|
||||
{
|
||||
private const Anchor DefaultAnchor = Anchor.Center;
|
||||
|
||||
public Identifier Tag;
|
||||
public int Width;
|
||||
public int Height;
|
||||
public Anchor Anchor;
|
||||
|
||||
public Text(Identifier tag, int? width = null, int? height = null, Anchor? anchor = null)
|
||||
{
|
||||
Tag = tag;
|
||||
Width = width ?? DefaultWidth;
|
||||
Height = height ?? DefaultHeight;
|
||||
Anchor = anchor ?? DefaultAnchor;
|
||||
}
|
||||
|
||||
public Text(string tag, int? width = null, int? height = null, Anchor? anchor = null) : this(tag.ToIdentifier(), width, height, anchor) { }
|
||||
}
|
||||
|
||||
public struct Video
|
||||
{
|
||||
public string File;
|
||||
public Identifier TextTag;
|
||||
public int Width;
|
||||
public int Height;
|
||||
|
||||
public Video(string file, Identifier textTag, int? width = null, int? height = null)
|
||||
{
|
||||
File = file;
|
||||
TextTag = textTag;
|
||||
Width = width ?? DefaultWidth;
|
||||
Height = height ?? DefaultHeight;
|
||||
}
|
||||
|
||||
public Video(string file, string textTag, int? width = null, int? height = null) : this(file, textTag.ToIdentifier(), width, height) { }
|
||||
public string FileName => Path.GetFileName(FullPath.CleanUpPath());
|
||||
public string ContentPath => Path.GetDirectoryName(FullPath.CleanUpPath());
|
||||
}
|
||||
|
||||
private const int DefaultWidth = 450;
|
||||
@@ -103,15 +72,30 @@ namespace Barotrauma.Tutorials
|
||||
public LocalizedString ObjectiveText;
|
||||
|
||||
public readonly Identifier Id;
|
||||
public readonly Text? TextContent;
|
||||
public readonly Video? VideoContent;
|
||||
public readonly Text TextContent;
|
||||
public readonly Video VideoContent;
|
||||
public readonly AutoPlayVideo AutoPlayVideo;
|
||||
|
||||
public Action OnClickToDisplayMessage;
|
||||
public Action OnClickObjective;
|
||||
|
||||
public readonly TutorialSegmentType SegmentType;
|
||||
|
||||
public Segment(Identifier id, Identifier objectiveTextTag, AutoPlayVideo autoPlayVideo, Text? textContent = null, Video? videoContent = null)
|
||||
public static Segment CreateInfoBoxSegment(Identifier id, Identifier objectiveTextTag, AutoPlayVideo autoPlayVideo, Text textContent = default, Video videoContent = default)
|
||||
{
|
||||
return new Segment(id, objectiveTextTag, autoPlayVideo, textContent, videoContent);
|
||||
}
|
||||
|
||||
public static Segment CreateMessageBoxSegment(Identifier id, Identifier objectiveTextTag, Action onClickObjective)
|
||||
{
|
||||
return new Segment(id, objectiveTextTag, onClickObjective);
|
||||
}
|
||||
|
||||
public static Segment CreateObjectiveSegment(Identifier id, Identifier objectiveTextTag)
|
||||
{
|
||||
return new Segment(id, objectiveTextTag);
|
||||
}
|
||||
|
||||
private Segment(Identifier id, Identifier objectiveTextTag, AutoPlayVideo autoPlayVideo, Text textContent = default, Video videoContent = default)
|
||||
{
|
||||
Id = id;
|
||||
ObjectiveText = TextManager.ParseInputTypes(TextManager.Get(objectiveTextTag));
|
||||
@@ -121,15 +105,21 @@ namespace Barotrauma.Tutorials
|
||||
SegmentType = TutorialSegmentType.InfoBox;
|
||||
}
|
||||
|
||||
public Segment(Identifier id, Action onClickToDisplayMessage)
|
||||
private Segment(Identifier id, Identifier objectiveTextTag, Action onClickObjective)
|
||||
{
|
||||
Id = id;
|
||||
var objetiveTextTag = $"{id}.objective".ToIdentifier();
|
||||
ObjectiveText = TextManager.ParseInputTypes(TextManager.Get(objetiveTextTag));
|
||||
OnClickToDisplayMessage = onClickToDisplayMessage;
|
||||
ObjectiveText = TextManager.ParseInputTypes(TextManager.Get(objectiveTextTag));
|
||||
OnClickObjective = onClickObjective;
|
||||
SegmentType = TutorialSegmentType.MessageBox;
|
||||
}
|
||||
}
|
||||
|
||||
private Segment(Identifier id, Identifier objectiveTextTag)
|
||||
{
|
||||
Id = id;
|
||||
ObjectiveText = TextManager.ParseInputTypes(TextManager.Get(objectiveTextTag));
|
||||
SegmentType = TutorialSegmentType.Objective;
|
||||
}
|
||||
}
|
||||
|
||||
private bool completed;
|
||||
public bool Completed
|
||||
@@ -157,60 +147,57 @@ namespace Barotrauma.Tutorials
|
||||
|
||||
private Character character;
|
||||
|
||||
private readonly string submarinePath = "Content/Tutorials/Dugong_Tutorial.sub";
|
||||
private readonly string startOutpostPath = "Content/Tutorials/TutorialOutpost.sub";
|
||||
|
||||
private readonly string levelSeed = "nLoZLLtza";
|
||||
private readonly string levelParams = "ColdCavernsTutorial";
|
||||
private string SubmarinePath => TutorialPrefab.SubmarinePath.Value;
|
||||
private string StartOutpostPath => TutorialPrefab.OutpostPath.Value;
|
||||
private string LevelSeed => TutorialPrefab.LevelSeed;
|
||||
private string LevelParams => TutorialPrefab.LevelParams;
|
||||
|
||||
private SubmarineInfo startOutpost = null;
|
||||
|
||||
public readonly List<(Entity entity, string iconStyle)> Icons = new List<(Entity entity, string iconStyle)>();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Tutorial Controls
|
||||
|
||||
public Tutorial(TutorialPrefab prefab)
|
||||
{
|
||||
Identifier = $"tutorial.{prefab?.Identifier ?? Identifier.Empty}".ToIdentifier();
|
||||
Identifier = $"tutorial.{prefab.Identifier}".ToIdentifier();
|
||||
DisplayName = TextManager.Get(Identifier);
|
||||
objectiveTextTranslated = TextManager.Get("Tutorial.Objective");
|
||||
|
||||
TutorialPrefab = prefab;
|
||||
submarinePath = prefab.SubmarinePath.Value;
|
||||
startOutpostPath = prefab.OutpostPath.Value;
|
||||
levelSeed = prefab.LevelSeed;
|
||||
levelParams = prefab.LevelParams;
|
||||
eventPrefab = EventSet.GetEventPrefab(prefab.EventIdentifier);
|
||||
}
|
||||
|
||||
private IEnumerable<CoroutineStatus> Loading()
|
||||
{
|
||||
SubmarineInfo subInfo = new SubmarineInfo(submarinePath);
|
||||
SubmarineInfo subInfo = new SubmarineInfo(SubmarinePath);
|
||||
|
||||
LevelGenerationParams generationParams = LevelGenerationParams.LevelParams.Find(p => p.Identifier == levelParams);
|
||||
LevelGenerationParams.LevelParams.TryGet(LevelParams, out LevelGenerationParams generationParams);
|
||||
|
||||
yield return CoroutineStatus.Running;
|
||||
|
||||
GameMain.GameSession = new GameSession(subInfo, GameModePreset.Tutorial, missionPrefabs: null);
|
||||
(GameMain.GameSession.GameMode as TutorialMode).Tutorial = this;
|
||||
|
||||
if (generationParams != null)
|
||||
if (generationParams is not null)
|
||||
{
|
||||
Biome biome =
|
||||
Biome.Prefabs.FirstOrDefault(b => generationParams.AllowedBiomeIdentifiers.Contains(b.Identifier)) ??
|
||||
Biome.Prefabs.First();
|
||||
|
||||
if (!string.IsNullOrEmpty(startOutpostPath))
|
||||
if (!string.IsNullOrEmpty(StartOutpostPath))
|
||||
{
|
||||
startOutpost = new SubmarineInfo(startOutpostPath);
|
||||
startOutpost = new SubmarineInfo(StartOutpostPath);
|
||||
}
|
||||
|
||||
LevelData tutorialLevel = new LevelData(levelSeed, 0, 0, generationParams, biome);
|
||||
LevelData tutorialLevel = new LevelData(LevelSeed, 0, 0, generationParams, biome);
|
||||
GameMain.GameSession.StartRound(tutorialLevel, startOutpost: startOutpost);
|
||||
}
|
||||
else
|
||||
{
|
||||
GameMain.GameSession.StartRound(levelSeed);
|
||||
GameMain.GameSession.StartRound(LevelSeed);
|
||||
}
|
||||
|
||||
GameMain.GameSession.EventManager.ActiveEvents.Clear();
|
||||
@@ -274,6 +261,7 @@ namespace Barotrauma.Tutorials
|
||||
private void Initialize()
|
||||
{
|
||||
GameMain.GameSession.CrewManager.AllowCharacterSwitch = TutorialPrefab.AllowCharacterSwitch;
|
||||
GameMain.GameSession.CrewManager.AutoHideCrewList();
|
||||
|
||||
if (Character.Controlled is Character character)
|
||||
{
|
||||
@@ -450,7 +438,7 @@ namespace Barotrauma.Tutorials
|
||||
|
||||
public void TriggerTutorialSegment(Segment segment)
|
||||
{
|
||||
if (segment.SegmentType == TutorialSegmentType.MessageBox)
|
||||
if (segment.SegmentType != TutorialSegmentType.InfoBox)
|
||||
{
|
||||
ActiveObjectives.Add(segment);
|
||||
AddToObjectiveList(segment);
|
||||
@@ -462,7 +450,7 @@ namespace Barotrauma.Tutorials
|
||||
ActiveContentSegment = segment;
|
||||
|
||||
var title = TextManager.Get(segment.Id);
|
||||
LocalizedString tutorialText = TextManager.GetFormatted(segment.TextContent.Value.Tag);
|
||||
LocalizedString tutorialText = TextManager.GetFormatted(segment.TextContent.Tag);
|
||||
tutorialText = TextManager.ParseInputTypes(tutorialText);
|
||||
|
||||
switch (segment.AutoPlayVideo)
|
||||
@@ -471,9 +459,9 @@ namespace Barotrauma.Tutorials
|
||||
infoBox = CreateInfoFrame(
|
||||
title,
|
||||
tutorialText,
|
||||
segment.TextContent.Value.Width,
|
||||
segment.TextContent.Value.Height,
|
||||
segment.TextContent.Value.Anchor,
|
||||
segment.TextContent.Width,
|
||||
segment.TextContent.Height,
|
||||
segment.TextContent.Anchor,
|
||||
hasButton: true,
|
||||
onInfoBoxClosed: LoadActiveContentVideo);
|
||||
break;
|
||||
@@ -481,9 +469,9 @@ namespace Barotrauma.Tutorials
|
||||
infoBox = CreateInfoFrame(
|
||||
title,
|
||||
tutorialText,
|
||||
segment.TextContent.Value.Width,
|
||||
segment.TextContent.Value.Height,
|
||||
segment.TextContent.Value.Anchor,
|
||||
segment.TextContent.Width,
|
||||
segment.TextContent.Height,
|
||||
segment.TextContent.Anchor,
|
||||
hasButton: true,
|
||||
onInfoBoxClosed: StopCurrentContentSegment,
|
||||
onVideoButtonClicked: LoadActiveContentVideo);
|
||||
@@ -493,7 +481,7 @@ namespace Barotrauma.Tutorials
|
||||
|
||||
public void CompleteTutorialSegment(Identifier segmentId)
|
||||
{
|
||||
if (!(GetActiveObjective(segmentId) is Segment segment))
|
||||
if (GetActiveObjective(segmentId) is not Segment segment)
|
||||
{
|
||||
DebugConsole.AddWarning($"Warning: tried to complete the tutorial segment \"{segmentId}\" in tutorial \"{Identifier}\" but it isn't active!");
|
||||
return;
|
||||
@@ -501,29 +489,28 @@ namespace Barotrauma.Tutorials
|
||||
if (GUIStyle.GetComponentStyle("ObjectiveIndicatorCompleted") is GUIComponentStyle style)
|
||||
{
|
||||
segment.ObjectiveStateIndicator.ApplyStyle(style);
|
||||
segment.ObjectiveStateIndicator.Flash(color: GUIStyle.Green);
|
||||
}
|
||||
segment.ObjectiveStateIndicator.Parent.Flash(color: GUIStyle.Green, flashDuration: 0.35f, useRectangleFlash: true);
|
||||
segment.ObjectiveButton.OnClicked = null;
|
||||
segment.ObjectiveButton.CanBeFocused = false;
|
||||
}
|
||||
|
||||
public void RemoveTutorialSegment(Identifier segmentId)
|
||||
{
|
||||
if (!(GetActiveObjective(segmentId) is Segment segment))
|
||||
if (GetActiveObjective(segmentId) is not Segment segment)
|
||||
{
|
||||
DebugConsole.AddWarning($"Warning: tried to remove the tutorial segment \"{segmentId}\" in tutorial \"{Identifier}\" but it isn't active!");
|
||||
return;
|
||||
}
|
||||
segment.ObjectiveStateIndicator.FadeOut(ObjectiveComponentRemovalTime, false);
|
||||
segment.LinkedTextBlock.FadeOut(ObjectiveComponentRemovalTime, false);
|
||||
segment.ObjectiveStateIndicator.FadeOut(ObjectiveComponentAnimationTime, false);
|
||||
segment.LinkedTextBlock.FadeOut(ObjectiveComponentAnimationTime, false);
|
||||
var parent = segment.LinkedTextBlock.Parent;
|
||||
parent.FadeOut(ObjectiveComponentRemovalTime, true, onRemove: () =>
|
||||
parent.FadeOut(ObjectiveComponentAnimationTime, true, onRemove: () =>
|
||||
{
|
||||
ActiveObjectives.Remove(segment);
|
||||
objectiveGroup?.Recalculate();
|
||||
});
|
||||
var targetPos = new Point(GameMain.GraphicsWidth - parent.Rect.X, 0);
|
||||
parent.RectTransform.MoveOverTime(targetPos, ObjectiveComponentRemovalTime);
|
||||
parent.RectTransform.MoveOverTime(GetObjectiveHiddenPosition(parent.RectTransform), ObjectiveComponentAnimationTime);
|
||||
segment.ObjectiveButton.OnClicked = null;
|
||||
segment.ObjectiveButton.CanBeFocused = false;
|
||||
}
|
||||
@@ -585,12 +572,14 @@ namespace Barotrauma.Tutorials
|
||||
{
|
||||
var frameRt = new RectTransform(new Vector2(1.0f, 0.1f), objectiveGroup.RectTransform)
|
||||
{
|
||||
AbsoluteOffset = GetObjectiveHiddenPosition(),
|
||||
MinSize = new Point(0, objectiveGroup.AbsoluteSpacing)
|
||||
};
|
||||
var frame = new GUIFrame(frameRt, style: null)
|
||||
{
|
||||
CanBeFocused = true
|
||||
};
|
||||
objectiveGroup.Recalculate();
|
||||
|
||||
segment.LinkedTextBlock = new GUITextBlock(
|
||||
new RectTransform(new Point(frameRt.Rect.Width - objectiveGroup.AbsoluteSpacing, 0), frame.RectTransform, anchor: Anchor.TopRight),
|
||||
@@ -612,6 +601,7 @@ namespace Barotrauma.Tutorials
|
||||
|
||||
segment.ObjectiveButton = new GUIButton(new RectTransform(Vector2.One, segment.LinkedTextBlock.RectTransform, Anchor.TopLeft, Pivot.TopLeft), style: null)
|
||||
{
|
||||
CanBeFocused = segment.SegmentType != TutorialSegmentType.Objective,
|
||||
ToolTip = objectiveTextTranslated,
|
||||
OnClicked = (GUIButton btn, object userdata) =>
|
||||
{
|
||||
@@ -628,13 +618,15 @@ namespace Barotrauma.Tutorials
|
||||
}
|
||||
else if (segment.SegmentType == TutorialSegmentType.MessageBox)
|
||||
{
|
||||
segment.OnClickToDisplayMessage?.Invoke();
|
||||
segment.OnClickObjective?.Invoke();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
SetTransparent(segment.ObjectiveButton);
|
||||
|
||||
frameRt.MoveOverTime(new Point(0, frameRt.AbsoluteOffset.Y), ObjectiveComponentAnimationTime, onDoneMoving: () => objectiveGroup?.Recalculate());
|
||||
|
||||
static void SetTransparent(GUIComponent component) => component.Color = component.HoverColor = component.PressedColor = component.SelectedColor = Color.Transparent;
|
||||
}
|
||||
|
||||
@@ -654,15 +646,20 @@ namespace Barotrauma.Tutorials
|
||||
ActiveContentSegment = segment;
|
||||
infoBox = CreateInfoFrame(
|
||||
TextManager.Get(segment.Id),
|
||||
TextManager.Get(segment.TextContent.Value.Tag),
|
||||
segment.TextContent.Value.Width,
|
||||
segment.TextContent.Value.Height,
|
||||
segment.TextContent.Value.Anchor,
|
||||
TextManager.Get(segment.TextContent.Tag),
|
||||
segment.TextContent.Width,
|
||||
segment.TextContent.Height,
|
||||
segment.TextContent.Anchor,
|
||||
hasButton: true,
|
||||
onInfoBoxClosed: () => ContentRunning = false,
|
||||
onVideoButtonClicked: () => LoadVideo(segment));
|
||||
}
|
||||
|
||||
private Point GetObjectiveHiddenPosition(RectTransform rt = null)
|
||||
{
|
||||
return new Point(GameMain.GraphicsWidth - objectiveGroup.Rect.X, rt?.AbsoluteOffset.Y ?? 0);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region InfoFrame
|
||||
@@ -772,9 +769,9 @@ namespace Barotrauma.Tutorials
|
||||
if (segment.AutoPlayVideo == AutoPlayVideo.Yes)
|
||||
{
|
||||
videoPlayer.LoadContent(
|
||||
contentPath: PlayableContentPath,
|
||||
videoSettings: new VideoPlayer.VideoSettings(segment.VideoContent.Value.File),
|
||||
textSettings: new VideoPlayer.TextSettings(segment.VideoContent.Value.TextTag, segment.VideoContent.Value.Width),
|
||||
contentPath: segment.VideoContent.ContentPath,
|
||||
videoSettings: new VideoPlayer.VideoSettings(segment.VideoContent.FileName),
|
||||
textSettings: new VideoPlayer.TextSettings(segment.VideoContent.TextTag, segment.VideoContent.Width),
|
||||
contentId: segment.Id,
|
||||
startPlayback: true,
|
||||
objective: segment.ObjectiveText,
|
||||
@@ -783,8 +780,8 @@ namespace Barotrauma.Tutorials
|
||||
else
|
||||
{
|
||||
videoPlayer.LoadContent(
|
||||
contentPath: PlayableContentPath,
|
||||
videoSettings: new VideoPlayer.VideoSettings(segment.VideoContent.Value.File),
|
||||
contentPath: segment.VideoContent.ContentPath,
|
||||
videoSettings: new VideoPlayer.VideoSettings(segment.VideoContent.FileName),
|
||||
textSettings: null,
|
||||
contentId: segment.Id,
|
||||
startPlayback: true,
|
||||
|
||||
@@ -153,7 +153,7 @@ namespace Barotrauma.Items.Components
|
||||
GUI.MouseOn == null && !Inventory.IsMouseOnInventory && !GameMain.Instance.Paused;
|
||||
if (GUI.HideCursor)
|
||||
{
|
||||
crosshairSprite?.Draw(spriteBatch, crosshairPos, Color.White, 0, currentCrossHairScale);
|
||||
crosshairSprite?.Draw(spriteBatch, crosshairPos, ReloadTimer <= 0.0f ? Color.White : Color.White * 0.2f, 0, currentCrossHairScale);
|
||||
crosshairPointerSprite?.Draw(spriteBatch, crosshairPointerPos, 0, currentCrossHairPointerScale);
|
||||
}
|
||||
|
||||
|
||||
@@ -369,6 +369,7 @@ namespace Barotrauma.Items.Components
|
||||
loopingSoundChannel = loopingSound.RoundSound.Sound.Play(
|
||||
new Vector3(position.X, position.Y, 0.0f),
|
||||
0.01f,
|
||||
freqMult: itemSound.RoundSound.GetRandomFrequencyMultiplier(),
|
||||
muffle: SoundPlayer.ShouldMuffleSound(Character.Controlled, position, loopingSound.Range, Character.Controlled?.CurrentHull));
|
||||
loopingSoundChannel.Looping = true;
|
||||
//TODO: tweak
|
||||
|
||||
@@ -1013,6 +1013,19 @@ namespace Barotrauma.Items.Components
|
||||
if (!CheckResourceMarkerVisibility(c.center, transducerCenter)) { continue; }
|
||||
var i = unobtainedMinerals.FirstOrDefault();
|
||||
if (i == null) { continue; }
|
||||
|
||||
bool disrupted = false;
|
||||
foreach ((Vector2 disruptPos, float disruptStrength) in disruptedDirections)
|
||||
{
|
||||
float dot = Vector2.Dot(Vector2.Normalize(c.center - transducerCenter), disruptPos);
|
||||
if (dot > 1.0f - disruptStrength)
|
||||
{
|
||||
disrupted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (disrupted) { continue; }
|
||||
|
||||
DrawMarker(spriteBatch,
|
||||
i.Name, "mineral".ToIdentifier(), "mineralcluster" + i,
|
||||
c.center, transducerCenter,
|
||||
|
||||
@@ -22,6 +22,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private GUILayoutGroup extraButtonContainer;
|
||||
|
||||
private GUIComponent skillTextContainer;
|
||||
|
||||
private readonly List<ParticleEmitter> particleEmitters = new List<ParticleEmitter>();
|
||||
//the corresponding particle emitter is active when the condition is within this range
|
||||
private readonly List<Vector2> particleEmitterConditionRanges = new List<Vector2>();
|
||||
@@ -132,9 +134,10 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedFrame.RectTransform),
|
||||
TextManager.Get("RequiredRepairSkills"), font: GUIStyle.SubHeadingFont);
|
||||
skillTextContainer = paddedFrame;
|
||||
for (int i = 0; i < requiredSkills.Count; i++)
|
||||
{
|
||||
var skillText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedFrame.RectTransform),
|
||||
var skillText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), skillTextContainer.RectTransform),
|
||||
" - " + TextManager.AddPunctuation(':', TextManager.Get("SkillName." + requiredSkills[i].Identifier), ((int) Math.Round(requiredSkills[i].Level * SkillRequirementMultiplier)).ToString()),
|
||||
font: GUIStyle.SmallFont)
|
||||
{
|
||||
@@ -359,19 +362,11 @@ namespace Barotrauma.Items.Components
|
||||
extraButtonContainer.Visible = SabotageButton.Visible || TinkerButton.Visible;
|
||||
extraButtonContainer.IgnoreLayoutGroups = !extraButtonContainer.Visible;
|
||||
|
||||
foreach (GUIComponent c in GuiFrame.GetChild(0).Children)
|
||||
foreach (GUIComponent c in skillTextContainer.Children)
|
||||
{
|
||||
if (!(c.UserData is Skill skill)) continue;
|
||||
|
||||
if (c.UserData is not Skill skill) { continue; }
|
||||
GUITextBlock textBlock = (GUITextBlock)c;
|
||||
if (character.GetSkillLevel(skill.Identifier) < (skill.Level * SkillRequirementMultiplier))
|
||||
{
|
||||
textBlock.TextColor = GUIStyle.Red;
|
||||
}
|
||||
else
|
||||
{
|
||||
textBlock.TextColor = Color.White;
|
||||
}
|
||||
textBlock.TextColor = character.GetSkillLevel(skill.Identifier) < (skill.Level * SkillRequirementMultiplier) ? GUIStyle.Red : GUIStyle.TextColorNormal;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -823,7 +823,7 @@ namespace Barotrauma
|
||||
reloadTextureButton.OnClicked += (button, data) =>
|
||||
{
|
||||
Sprite.ReloadXML();
|
||||
Sprite.ReloadTexture(updateAllSprites: true);
|
||||
Sprite.ReloadTexture();
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -163,7 +163,7 @@ namespace Barotrauma
|
||||
OnClicked = (button, data) =>
|
||||
{
|
||||
Sprite.ReloadXML();
|
||||
Sprite.ReloadTexture(updateAllSprites: true);
|
||||
Sprite.ReloadTexture();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -93,10 +93,11 @@ namespace Barotrauma
|
||||
{
|
||||
float leftPanelWidth = 0.6f;
|
||||
float rightPanelWidth = 0.4f / leftPanelWidth;
|
||||
LocalizedString className = !HasTag(SubmarineTag.Shuttle)
|
||||
? $"{TextManager.Get($"submarineclass.{SubmarineClass}")} ({TextManager.Get($"submarinetier.{Tier}")})"
|
||||
: TextManager.Get("shuttle");
|
||||
|
||||
LocalizedString className = !HasTag(SubmarineTag.Shuttle) ?
|
||||
TextManager.GetWithVariables("submarine.classandtier",
|
||||
("[class]", TextManager.Get($"submarineclass.{SubmarineClass}")),
|
||||
("[tier]", TextManager.Get($"submarinetier.{Tier}"))) :
|
||||
TextManager.Get("shuttle");
|
||||
|
||||
int classHeight = (int)GUIStyle.SubHeadingFont.MeasureString(className).Y;
|
||||
int leftPanelWidthInt = (int)(parent.Rect.Width * leftPanelWidth);
|
||||
|
||||
@@ -134,6 +134,14 @@ namespace Barotrauma.Networking
|
||||
return Permissions.HasFlag(permission);
|
||||
}
|
||||
|
||||
public void ResetVotes()
|
||||
{
|
||||
for (int i = 0; i < votes.Length; i++)
|
||||
{
|
||||
votes[i] = null;
|
||||
}
|
||||
}
|
||||
|
||||
partial void DisposeProjSpecific()
|
||||
{
|
||||
if (VoipQueue != null)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,6 +2,8 @@
|
||||
using Barotrauma.Steam;
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
@@ -10,37 +12,14 @@ namespace Barotrauma.Networking
|
||||
public ImmutableArray<ServerContentPackage> ServerContentPackages { get; set; } =
|
||||
ImmutableArray<ServerContentPackage>.Empty;
|
||||
|
||||
public delegate void MessageCallback(IReadMessage message);
|
||||
|
||||
public delegate void DisconnectCallback(bool disableReconnect);
|
||||
|
||||
public delegate void DisconnectMessageCallback(string message);
|
||||
|
||||
public delegate void PasswordCallback(int salt, int retries);
|
||||
|
||||
public delegate void InitializationCompleteCallback();
|
||||
|
||||
[Obsolete("TODO: delete in nr3-layer-1-2-cleanup")]
|
||||
public readonly struct Callbacks
|
||||
public readonly record struct Callbacks(
|
||||
Callbacks.MessageCallback OnMessageReceived,
|
||||
Callbacks.DisconnectCallback OnDisconnect,
|
||||
Callbacks.InitializationCompleteCallback OnInitializationComplete)
|
||||
{
|
||||
public readonly MessageCallback OnMessageReceived;
|
||||
public readonly DisconnectCallback OnDisconnect;
|
||||
public readonly DisconnectMessageCallback OnDisconnectMessageReceived;
|
||||
public readonly PasswordCallback OnRequestPassword;
|
||||
public readonly InitializationCompleteCallback OnInitializationComplete;
|
||||
|
||||
public Callbacks(MessageCallback onMessageReceived,
|
||||
DisconnectCallback onDisconnect,
|
||||
DisconnectMessageCallback onDisconnectMessageReceived,
|
||||
PasswordCallback onRequestPassword,
|
||||
InitializationCompleteCallback onInitializationComplete)
|
||||
{
|
||||
OnMessageReceived = onMessageReceived;
|
||||
OnDisconnect = onDisconnect;
|
||||
OnDisconnectMessageReceived = onDisconnectMessageReceived;
|
||||
OnRequestPassword = onRequestPassword;
|
||||
OnInitializationComplete = onInitializationComplete;
|
||||
}
|
||||
public delegate void MessageCallback(IReadMessage message);
|
||||
public delegate void DisconnectCallback(PeerDisconnectPacket disconnectPacket);
|
||||
public delegate void InitializationCompleteCallback();
|
||||
}
|
||||
|
||||
protected readonly Callbacks callbacks;
|
||||
@@ -51,6 +30,8 @@ namespace Barotrauma.Networking
|
||||
protected readonly bool isOwner;
|
||||
protected readonly Option<int> ownerKey;
|
||||
|
||||
protected bool isActive;
|
||||
|
||||
public ClientPeer(Endpoint serverEndpoint, Callbacks callbacks, Option<int> ownerKey)
|
||||
{
|
||||
ServerEndpoint = serverEndpoint;
|
||||
@@ -60,7 +41,7 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
|
||||
public abstract void Start();
|
||||
public abstract void Close(string? msg = null, bool disableReconnect = false);
|
||||
public abstract void Close(PeerDisconnectPacket peerDisconnectPacket);
|
||||
public abstract void Update(float deltaTime);
|
||||
public abstract void Send(IWriteMessage msg, DeliveryMethod deliveryMethod, bool compressPastThreshold = true);
|
||||
public abstract void SendPassword(string password);
|
||||
@@ -71,6 +52,12 @@ namespace Barotrauma.Networking
|
||||
public bool ContentPackageOrderReceived { get; protected set; }
|
||||
protected int passwordSalt;
|
||||
protected Steamworks.AuthTicket? steamAuthTicket;
|
||||
private GUIMessageBox? passwordMsgBox;
|
||||
|
||||
public bool WaitingForPassword
|
||||
=> isActive && initializationStep == ConnectionInitialization.Password
|
||||
&& passwordMsgBox != null
|
||||
&& GUIMessageBox.MessageBoxes.Contains(passwordMsgBox);
|
||||
|
||||
public struct IncomingInitializationMessage
|
||||
{
|
||||
@@ -112,8 +99,9 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
case ConnectionInitialization.ContentPackageOrder:
|
||||
{
|
||||
if (initializationStep == ConnectionInitialization.SteamTicketAndVersion ||
|
||||
initializationStep == ConnectionInitialization.Password)
|
||||
if (initializationStep
|
||||
is ConnectionInitialization.SteamTicketAndVersion
|
||||
or ConnectionInitialization.Password)
|
||||
{
|
||||
initializationStep = ConnectionInitialization.ContentPackageOrder;
|
||||
}
|
||||
@@ -155,10 +143,57 @@ namespace Barotrauma.Networking
|
||||
|
||||
var passwordPacket = INetSerializableStruct.Read<ServerPeerPasswordPacket>(inc.Message);
|
||||
|
||||
if (WaitingForPassword) { return; }
|
||||
|
||||
passwordPacket.Salt.TryUnwrap(out passwordSalt);
|
||||
passwordPacket.RetriesLeft.TryUnwrap(out var retries);
|
||||
|
||||
callbacks.OnRequestPassword.Invoke(passwordSalt, retries);
|
||||
LocalizedString pwMsg = TextManager.Get("PasswordRequired");
|
||||
|
||||
passwordMsgBox = new GUIMessageBox(pwMsg, "", new LocalizedString[] { TextManager.Get("OK"), TextManager.Get("Cancel") },
|
||||
relativeSize: new Vector2(0.25f, 0.1f), minSize: new Point(400, GUI.IntScale(170)));
|
||||
var passwordHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.5f), passwordMsgBox.Content.RectTransform), childAnchor: Anchor.TopCenter);
|
||||
var passwordBox = new GUITextBox(new RectTransform(new Vector2(0.8f, 1f), passwordHolder.RectTransform) { MinSize = new Point(0, 20) })
|
||||
{
|
||||
Censor = true
|
||||
};
|
||||
|
||||
if (retries > 0)
|
||||
{
|
||||
var incorrectPasswordText = new GUITextBlock(new RectTransform(new Vector2(1f, 0.0f), passwordHolder.RectTransform), TextManager.Get("incorrectpassword"), GUIStyle.Red, GUIStyle.Font, textAlignment: Alignment.Center);
|
||||
incorrectPasswordText.RectTransform.MinSize = new Point(0, (int)incorrectPasswordText.TextSize.Y);
|
||||
passwordHolder.Recalculate();
|
||||
}
|
||||
|
||||
passwordMsgBox.Content.Recalculate();
|
||||
passwordMsgBox.Content.RectTransform.MinSize = new Point(0, passwordMsgBox.Content.RectTransform.Children.Sum(c => c.Rect.Height));
|
||||
passwordMsgBox.Content.Parent.RectTransform.MinSize = new Point(0, (int)(passwordMsgBox.Content.RectTransform.MinSize.Y / passwordMsgBox.Content.RectTransform.RelativeSize.Y));
|
||||
|
||||
var okButton = passwordMsgBox.Buttons[0];
|
||||
okButton.OnClicked += (_, __) =>
|
||||
{
|
||||
SendPassword(passwordBox.Text);
|
||||
return true;
|
||||
};
|
||||
okButton.OnClicked += passwordMsgBox.Close;
|
||||
|
||||
var cancelButton = passwordMsgBox.Buttons[1];
|
||||
cancelButton.OnClicked = (_, __) =>
|
||||
{
|
||||
Close(PeerDisconnectPacket.WithReason(DisconnectReason.Disconnected));
|
||||
passwordMsgBox?.Close(); passwordMsgBox = null;
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
passwordBox.OnEnterPressed += (_, __) =>
|
||||
{
|
||||
okButton.OnClicked.Invoke(okButton, okButton.UserData);
|
||||
return true;
|
||||
};
|
||||
|
||||
passwordBox.Select();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
internal sealed class LidgrenClientPeer : ClientPeer
|
||||
{
|
||||
private bool isActive;
|
||||
private NetClient? netClient;
|
||||
private readonly NetPeerConfiguration netPeerConfiguration;
|
||||
|
||||
@@ -94,7 +93,7 @@ namespace Barotrauma.Networking
|
||||
|
||||
if (isOwner && !(ChildServerRelay.Process is { HasExited: false }))
|
||||
{
|
||||
Close();
|
||||
Close(PeerDisconnectPacket.WithReason(DisconnectReason.ServerCrashed));
|
||||
var msgBox = new GUIMessageBox(TextManager.Get("ConnectionLost"), ChildServerRelay.CrashMessage);
|
||||
msgBox.Buttons[0].OnClicked += (btn, obj) =>
|
||||
{
|
||||
@@ -140,8 +139,10 @@ namespace Barotrauma.Networking
|
||||
|
||||
var (_, packetHeader, initialization) = INetSerializableStruct.Read<PeerPacketHeaders>(inc);
|
||||
|
||||
if (packetHeader.IsConnectionInitializationStep() && initializationStep != ConnectionInitialization.Success)
|
||||
if (packetHeader.IsConnectionInitializationStep())
|
||||
{
|
||||
if (initializationStep == ConnectionInitialization.Success) { return; }
|
||||
|
||||
ReadConnectionInitializationStep(new IncomingInitializationMessage
|
||||
{
|
||||
InitializationStep = initialization ?? throw new Exception("Initialization step missing"),
|
||||
@@ -170,8 +171,9 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
case NetConnectionStatus.Disconnected:
|
||||
string disconnectMsg = inc.ReadString();
|
||||
Close(disconnectMsg);
|
||||
callbacks.OnDisconnectMessageReceived.Invoke(disconnectMsg);
|
||||
var peerDisconnectPacket =
|
||||
PeerDisconnectPacket.FromLidgrenStringRepresentation(disconnectMsg);
|
||||
Close(peerDisconnectPacket.Fallback(PeerDisconnectPacket.WithReason(DisconnectReason.Unknown)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -198,7 +200,7 @@ namespace Barotrauma.Networking
|
||||
SendMsgInternal(headers, body);
|
||||
}
|
||||
|
||||
public override void Close(string? msg = null, bool disableReconnect = false)
|
||||
public override void Close(PeerDisconnectPacket peerDisconnectPacket)
|
||||
{
|
||||
if (!isActive) { return; }
|
||||
|
||||
@@ -206,13 +208,13 @@ namespace Barotrauma.Networking
|
||||
|
||||
isActive = false;
|
||||
|
||||
netClient.Shutdown(msg ?? TextManager.Get("Disconnecting").Value);
|
||||
netClient.Shutdown(peerDisconnectPacket.ToLidgrenStringRepresentation());
|
||||
netClient = null;
|
||||
|
||||
steamAuthTicket?.Cancel();
|
||||
steamAuthTicket = null;
|
||||
|
||||
callbacks.OnDisconnect.Invoke(disableReconnect);
|
||||
callbacks.OnDisconnect.Invoke(peerDisconnectPacket);
|
||||
}
|
||||
|
||||
public override void Send(IWriteMessage msg, DeliveryMethod deliveryMethod, bool compressPastThreshold = true)
|
||||
|
||||
@@ -10,7 +10,6 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
internal sealed class SteamP2PClientPeer : ClientPeer
|
||||
{
|
||||
private bool isActive;
|
||||
private readonly SteamId hostSteamId;
|
||||
private double timeout;
|
||||
private double heartbeatTimer;
|
||||
@@ -97,8 +96,7 @@ namespace Barotrauma.Networking
|
||||
|
||||
if (steamId != hostSteamId.Value) { return; }
|
||||
|
||||
Close($"SteamP2P connection failed: {error}");
|
||||
callbacks.OnDisconnectMessageReceived.Invoke($"{DisconnectReason.SteamP2PError}/SteamP2P connection failed: {error}");
|
||||
Close(PeerDisconnectPacket.SteamP2PError(error));
|
||||
}
|
||||
|
||||
private void OnP2PData(ulong steamId, byte[] data, int dataLength)
|
||||
@@ -117,8 +115,10 @@ namespace Barotrauma.Networking
|
||||
|
||||
if (!packetHeader.IsServerMessage()) { return; }
|
||||
|
||||
if (packetHeader.IsConnectionInitializationStep() && initialization.HasValue)
|
||||
if (packetHeader.IsConnectionInitializationStep())
|
||||
{
|
||||
if (!initialization.HasValue) { return; }
|
||||
|
||||
var relayPacket = INetSerializableStruct.Read<SteamP2PInitializationRelayPacket>(inc);
|
||||
|
||||
SteamManager.JoinLobby(relayPacket.LobbyID, false);
|
||||
@@ -127,7 +127,7 @@ namespace Barotrauma.Networking
|
||||
incomingInitializationMessages.Add(new IncomingInitializationMessage
|
||||
{
|
||||
InitializationStep = initialization.Value,
|
||||
Message = relayPacket.Message.GetReadMessage()
|
||||
Message = relayPacket.Message.GetReadMessageUncompressed()
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -138,13 +138,12 @@ namespace Barotrauma.Networking
|
||||
else if (packetHeader.IsDisconnectMessage())
|
||||
{
|
||||
PeerDisconnectPacket packet = INetSerializableStruct.Read<PeerDisconnectPacket>(inc);
|
||||
Close(packet.Message);
|
||||
callbacks.OnDisconnectMessageReceived.Invoke(packet.Message);
|
||||
Close(packet);
|
||||
}
|
||||
else
|
||||
{
|
||||
var packet = INetSerializableStruct.Read<PeerPacketMessage>(inc);
|
||||
incomingDataMessages.Add(packet.GetReadMessage());
|
||||
incomingDataMessages.Add(packet.GetReadMessage(packetHeader.IsCompressed(), ServerConnection!));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,14 +169,12 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
if (state.P2PSessionError != Steamworks.P2PSessionError.None)
|
||||
{
|
||||
Close($"SteamP2P error code: {state.P2PSessionError}");
|
||||
callbacks.OnDisconnectMessageReceived.Invoke($"{DisconnectReason.SteamP2PError}/SteamP2P error code: {state.P2PSessionError}");
|
||||
Close(PeerDisconnectPacket.SteamP2PError(state.P2PSessionError));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Close("SteamP2P connection could not be established");
|
||||
callbacks.OnDisconnectMessageReceived.Invoke(DisconnectReason.SteamP2PError.ToString());
|
||||
Close(PeerDisconnectPacket.WithReason(DisconnectReason.Timeout));
|
||||
}
|
||||
|
||||
connectionStatusTimer = 1.0f;
|
||||
@@ -212,8 +209,7 @@ namespace Barotrauma.Networking
|
||||
|
||||
if (timeout < 0.0)
|
||||
{
|
||||
Close("Timed out");
|
||||
callbacks.OnDisconnectMessageReceived.Invoke(DisconnectReason.SteamP2PTimeOut.ToString());
|
||||
Close(PeerDisconnectPacket.WithReason(DisconnectReason.SteamP2PTimeOut));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -221,20 +217,26 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
if (incomingDataMessages.Count > 0)
|
||||
{
|
||||
var incomingMessage = incomingDataMessages.First();
|
||||
byte incomingHeader = incomingMessage.LengthBytes > 0 ? incomingMessage.PeekByte() : (byte)0;
|
||||
if (ContentPackageOrderReceived)
|
||||
void initializationError(string errorMsg, string analyticsTag)
|
||||
{
|
||||
#warning: TODO: do not allow completing initialization until content package order has been received?
|
||||
string errorMsg = $"Error during connection initialization: completed initialization before receiving content package order. Incoming header: {incomingHeader}";
|
||||
GameAnalyticsManager.AddErrorEventOnce("SteamP2PClientPeer.OnInitializationComplete:ContentPackageOrderNotReceived", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
||||
GameAnalyticsManager.AddErrorEventOnce($"SteamP2PClientPeer.OnInitializationComplete:{analyticsTag}", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
||||
DebugConsole.ThrowError(errorMsg);
|
||||
Close(PeerDisconnectPacket.WithReason(DisconnectReason.Disconnected));
|
||||
}
|
||||
|
||||
if (!ContentPackageOrderReceived)
|
||||
{
|
||||
initializationError(
|
||||
errorMsg: "Error during connection initialization: completed initialization before receiving content package order.",
|
||||
analyticsTag: "ContentPackageOrderNotReceived");
|
||||
return;
|
||||
}
|
||||
if (ServerContentPackages.Length == 0)
|
||||
{
|
||||
string errorMsg = $"Error during connection initialization: list of content packages enabled on the server was empty when completing initialization. Incoming header: {incomingHeader}";
|
||||
GameAnalyticsManager.AddErrorEventOnce("SteamP2PClientPeer.OnInitializationComplete:NoContentPackages", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
||||
DebugConsole.ThrowError(errorMsg);
|
||||
initializationError(
|
||||
errorMsg: "Error during connection initialization: list of content packages enabled on the server was empty when completing initialization.",
|
||||
analyticsTag: "NoContentPackages");
|
||||
return;
|
||||
}
|
||||
callbacks.OnInitializationComplete.Invoke();
|
||||
initializationStep = ConnectionInitialization.Success;
|
||||
@@ -320,7 +322,7 @@ namespace Barotrauma.Networking
|
||||
SendMsgInternal(headers, body);
|
||||
}
|
||||
|
||||
public override void Close(string? msg = null, bool disableReconnect = false)
|
||||
public override void Close(PeerDisconnectPacket peerDisconnectPacket)
|
||||
{
|
||||
if (!isActive) { return; }
|
||||
|
||||
@@ -334,12 +336,7 @@ namespace Barotrauma.Networking
|
||||
PacketHeader = PacketHeader.IsDisconnectMessage,
|
||||
Initialization = null
|
||||
};
|
||||
var body = new PeerDisconnectPacket
|
||||
{
|
||||
Message = msg ?? "Disconnected"
|
||||
};
|
||||
|
||||
SendMsgInternal(headers, body);
|
||||
SendMsgInternal(headers, peerDisconnectPacket);
|
||||
|
||||
Thread.Sleep(100);
|
||||
|
||||
@@ -349,7 +346,7 @@ namespace Barotrauma.Networking
|
||||
steamAuthTicket?.Cancel();
|
||||
steamAuthTicket = null;
|
||||
|
||||
callbacks.OnDisconnect.Invoke(disableReconnect);
|
||||
callbacks.OnDisconnect.Invoke(peerDisconnectPacket);
|
||||
}
|
||||
|
||||
protected override void SendMsgInternal(PeerPacketHeaders headers, INetSerializableStruct? body)
|
||||
|
||||
@@ -9,8 +9,6 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
sealed class SteamP2POwnerPeer : ClientPeer
|
||||
{
|
||||
private bool isActive;
|
||||
|
||||
private readonly SteamId selfSteamID;
|
||||
private UInt64 ownerKey64 => unchecked((UInt64)ownerKey.Fallback(0));
|
||||
|
||||
@@ -68,6 +66,7 @@ namespace Barotrauma.Networking
|
||||
: throw new InvalidOperationException("Steamworks not initialized");
|
||||
}
|
||||
|
||||
|
||||
public override void Start()
|
||||
{
|
||||
if (isActive) { return; }
|
||||
@@ -93,22 +92,13 @@ namespace Barotrauma.Networking
|
||||
private void OnAuthChange(Steamworks.SteamId steamId, Steamworks.SteamId ownerId, Steamworks.AuthResponse status)
|
||||
{
|
||||
RemotePeer? remotePeer = remotePeers.Find(p => p.SteamId.Value == steamId);
|
||||
DebugConsole.Log($"{steamId} validation: {status}, {remotePeer != null}");
|
||||
|
||||
if (remotePeer == null) { return; }
|
||||
|
||||
if (remotePeer.Authenticated)
|
||||
{
|
||||
if (status != Steamworks.AuthResponse.OK)
|
||||
{
|
||||
DisconnectPeer(remotePeer, $"{DisconnectReason.SteamAuthenticationFailed}/ Steam authentication status changed: {status}");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (status == Steamworks.AuthResponse.OK)
|
||||
{
|
||||
if (remotePeer.Authenticated) { return; }
|
||||
|
||||
SteamId ownerSteamId = new SteamId(ownerId);
|
||||
remotePeer.OwnerSteamId = Option<SteamId>.Some(ownerSteamId);
|
||||
remotePeer.Authenticated = true;
|
||||
@@ -126,7 +116,7 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
else
|
||||
{
|
||||
DisconnectPeer(remotePeer, $"{DisconnectReason.SteamAuthenticationFailed}/ Steam authentication failed: {status}");
|
||||
DisconnectPeer(remotePeer, PeerDisconnectPacket.SteamAuthError(status));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,7 +161,7 @@ namespace Barotrauma.Networking
|
||||
Steamworks.BeginAuthResult authSessionStartState = SteamManager.StartAuthSession(ticket, steamId);
|
||||
if (authSessionStartState != Steamworks.BeginAuthResult.OK)
|
||||
{
|
||||
DisconnectPeer(remotePeer, $"{DisconnectReason.SteamAuthenticationFailed}/ Steam auth session failed to start: {authSessionStartState}");
|
||||
DisconnectPeer(remotePeer, PeerDisconnectPacket.SteamAuthError(authSessionStartState));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -197,9 +187,9 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
if (!isActive) { return; }
|
||||
|
||||
if (ChildServerRelay.HasShutDown || (ChildServerRelay.Process?.HasExited ?? true))
|
||||
if (ChildServerRelay.HasShutDown || !(ChildServerRelay.Process is { HasExited: false }))
|
||||
{
|
||||
Close();
|
||||
Close(PeerDisconnectPacket.WithReason(DisconnectReason.ServerCrashed));
|
||||
var msgBox = new GUIMessageBox(TextManager.Get("ConnectionLost"), ChildServerRelay.CrashMessage);
|
||||
msgBox.Buttons[0].OnClicked += (btn, obj) =>
|
||||
{
|
||||
@@ -279,7 +269,7 @@ namespace Barotrauma.Networking
|
||||
if (packetHeader.IsDisconnectMessage())
|
||||
{
|
||||
var packet = INetSerializableStruct.Read<PeerDisconnectPacket>(inc);
|
||||
DisconnectPeer(peer, packet.Message);
|
||||
DisconnectPeer(peer, packet);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -366,30 +356,20 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
}
|
||||
|
||||
private void DisconnectPeer(RemotePeer peer, string msg)
|
||||
private void DisconnectPeer(RemotePeer peer, PeerDisconnectPacket peerDisconnectPacket)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(msg))
|
||||
{
|
||||
peer.DisconnectTime ??= Timing.TotalTime + 1.0;
|
||||
peer.DisconnectTime ??= Timing.TotalTime + 1.0;
|
||||
|
||||
IWriteMessage outMsg = new WriteOnlyMessage();
|
||||
outMsg.WriteNetSerializableStruct(new PeerPacketHeaders
|
||||
{
|
||||
DeliveryMethod = DeliveryMethod.Reliable,
|
||||
PacketHeader = PacketHeader.IsServerMessage | PacketHeader.IsDisconnectMessage
|
||||
});
|
||||
outMsg.WriteNetSerializableStruct(new PeerDisconnectPacket
|
||||
{
|
||||
Message = msg
|
||||
});
|
||||
|
||||
Steamworks.SteamNetworking.SendP2PPacket(peer.SteamId.Value, outMsg.Buffer, outMsg.LengthBytes, 0, Steamworks.P2PSend.Reliable);
|
||||
sentBytes += outMsg.LengthBytes;
|
||||
}
|
||||
else
|
||||
IWriteMessage outMsg = new WriteOnlyMessage();
|
||||
outMsg.WriteNetSerializableStruct(new PeerPacketHeaders
|
||||
{
|
||||
ClosePeerSession(peer);
|
||||
}
|
||||
DeliveryMethod = DeliveryMethod.Reliable,
|
||||
PacketHeader = PacketHeader.IsServerMessage | PacketHeader.IsDisconnectMessage
|
||||
});
|
||||
outMsg.WriteNetSerializableStruct(peerDisconnectPacket);
|
||||
|
||||
Steamworks.SteamNetworking.SendP2PPacket(peer.SteamId.Value, outMsg.Buffer, outMsg.LengthBytes, 0, Steamworks.P2PSend.Reliable);
|
||||
sentBytes += outMsg.LengthBytes;
|
||||
}
|
||||
|
||||
private void ClosePeerSession(RemotePeer peer)
|
||||
@@ -403,7 +383,7 @@ namespace Barotrauma.Networking
|
||||
//owner doesn't send passwords
|
||||
}
|
||||
|
||||
public override void Close(string? msg = null, bool disableReconnect = false)
|
||||
public override void Close(PeerDisconnectPacket peerDisconnectPacket)
|
||||
{
|
||||
if (!isActive) { return; }
|
||||
|
||||
@@ -411,7 +391,7 @@ namespace Barotrauma.Networking
|
||||
|
||||
for (int i = remotePeers.Count - 1; i >= 0; i--)
|
||||
{
|
||||
DisconnectPeer(remotePeers[i], msg ?? DisconnectReason.ServerShutdown.ToString());
|
||||
DisconnectPeer(remotePeers[i], peerDisconnectPacket);
|
||||
}
|
||||
|
||||
Thread.Sleep(100);
|
||||
@@ -423,7 +403,7 @@ namespace Barotrauma.Networking
|
||||
|
||||
ChildServerRelay.ClosePipes();
|
||||
|
||||
callbacks.OnDisconnect.Invoke(disableReconnect);
|
||||
callbacks.OnDisconnect.Invoke(peerDisconnectPacket);
|
||||
|
||||
SteamManager.LeaveLobby();
|
||||
Steamworks.SteamNetworking.ResetActions();
|
||||
|
||||
@@ -1,517 +0,0 @@
|
||||
using Barotrauma.Steam;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
class ServerInfo
|
||||
{
|
||||
public Endpoint Endpoint;
|
||||
|
||||
#region TODO: genericize
|
||||
public int QueryPort;
|
||||
public UInt64 LobbyID;
|
||||
public Steamworks.Data.NetPingLocation? PingLocation;
|
||||
#endregion
|
||||
|
||||
public bool OwnerVerified;
|
||||
|
||||
private string serverName;
|
||||
public string ServerName
|
||||
{
|
||||
get { return serverName; }
|
||||
set
|
||||
{
|
||||
serverName = value;
|
||||
if (serverName.Length > NetConfig.ServerNameMaxLength) { ServerName = ServerName.Substring(0, NetConfig.ServerNameMaxLength); }
|
||||
}
|
||||
}
|
||||
|
||||
public string ServerMessage;
|
||||
public bool GameStarted;
|
||||
public int PlayerCount;
|
||||
public int MaxPlayers;
|
||||
public bool HasPassword;
|
||||
|
||||
public bool PingChecked;
|
||||
public int Ping = -1;
|
||||
|
||||
//null value means that the value isn't known (the server may be using
|
||||
//an old version of the game that didn't report these values or the FetchRules query to Steam may not have finished yet)
|
||||
// TODO: death to Nullable<T>!!!!
|
||||
public SelectionMode? ModeSelectionMode;
|
||||
public SelectionMode? SubSelectionMode;
|
||||
public bool? AllowSpectating;
|
||||
public bool? VoipEnabled;
|
||||
public bool? KarmaEnabled;
|
||||
public bool? FriendlyFireEnabled;
|
||||
public bool? AllowRespawn;
|
||||
public YesNoMaybe? TraitorsEnabled;
|
||||
public Identifier GameMode;
|
||||
public PlayStyle? PlayStyle;
|
||||
|
||||
public bool Recent;
|
||||
public bool Favorite;
|
||||
|
||||
public bool? RespondedToSteamQuery = null;
|
||||
|
||||
public Steamworks.Friend? SteamFriend;
|
||||
public Steamworks.SteamMatchmakingPingResponse MatchmakingPingResponse;
|
||||
|
||||
public string GameVersion;
|
||||
public List<string> ContentPackageNames
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
} = new List<string>();
|
||||
public List<string> ContentPackageHashes
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
} = new List<string>();
|
||||
public List<ulong> ContentPackageWorkshopIds
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
} = new List<ulong>();
|
||||
|
||||
public void CreatePreviewWindow(GUIFrame frame)
|
||||
{
|
||||
if (frame == null) { return; }
|
||||
|
||||
frame.ClearChildren();
|
||||
|
||||
var title = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), frame.RectTransform), ServerName, font: GUIStyle.LargeFont)
|
||||
{
|
||||
ToolTip = ServerName,
|
||||
CanBeFocused = false
|
||||
};
|
||||
title.Text = ToolBox.LimitString(title.Text, title.Font, (int)(title.Rect.Width * 0.85f));
|
||||
|
||||
GUITickBox favoriteTickBox = new GUITickBox(new RectTransform(new Vector2(0.15f, 0.8f), title.RectTransform, Anchor.CenterRight),
|
||||
"", null, "GUIServerListFavoriteTickBox")
|
||||
{
|
||||
Selected = Favorite,
|
||||
ToolTip = TextManager.Get(Favorite ? "removefromfavorites" : "addtofavorites"),
|
||||
OnSelected = (tickbox) =>
|
||||
{
|
||||
if (tickbox.Selected)
|
||||
{
|
||||
GameMain.ServerListScreen.AddToFavoriteServers(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
GameMain.ServerListScreen.RemoveFromFavoriteServers(this);
|
||||
}
|
||||
tickbox.ToolTip = TextManager.Get(tickbox.Selected ? "removefromfavorites" : "addtofavorites");
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), frame.RectTransform),
|
||||
TextManager.AddPunctuation(':', TextManager.Get("ServerListVersion"),
|
||||
string.IsNullOrEmpty(GameVersion) ? TextManager.Get("Unknown") : GameVersion))
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
|
||||
bool hidePlaystyleBanner = !PlayStyle.HasValue;
|
||||
if (!hidePlaystyleBanner)
|
||||
{
|
||||
PlayStyle playStyle = PlayStyle ?? Networking.PlayStyle.Serious;
|
||||
Sprite playStyleBannerSprite = ServerListScreen.PlayStyleBanners[(int)playStyle];
|
||||
float playStyleBannerAspectRatio = playStyleBannerSprite.SourceRect.Width / playStyleBannerSprite.SourceRect.Height;
|
||||
var playStyleBanner = new GUIImage(new RectTransform(new Point(frame.Rect.Width, (int)(frame.Rect.Width / playStyleBannerAspectRatio)), frame.RectTransform),
|
||||
playStyleBannerSprite, null, true);
|
||||
|
||||
var playStyleName = new GUITextBlock(
|
||||
new RectTransform(new Vector2(0.15f, 0.0f), playStyleBanner.RectTransform)
|
||||
{ RelativeOffset = new Vector2(0.0f, 0.06f) },
|
||||
TextManager.AddPunctuation(':', TextManager.Get("serverplaystyle"),
|
||||
TextManager.Get("servertag." + playStyle)), textColor: Color.White,
|
||||
font: GUIStyle.SmallFont, textAlignment: Alignment.Center,
|
||||
color: ServerListScreen.PlayStyleColors[(int)playStyle], style: "GUISlopedHeader");
|
||||
playStyleName.RectTransform.NonScaledSize = (playStyleName.Font.MeasureString(playStyleName.Text) + new Vector2(20, 5) * GUI.Scale).ToPoint();
|
||||
playStyleName.RectTransform.IsFixedSize = true;
|
||||
}
|
||||
|
||||
var serverType = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), frame.RectTransform),
|
||||
Endpoint.ServerTypeString,
|
||||
textAlignment: Alignment.TopLeft)
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
serverType.RectTransform.MinSize = new Point(0, (int)(serverType.Rect.Height * 1.5f));
|
||||
|
||||
var content = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.6f), frame.RectTransform))
|
||||
{
|
||||
Stretch = true
|
||||
};
|
||||
// playstyle tags -----------------------------------------------------------------------------
|
||||
|
||||
var playStyleContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.15f), content.RectTransform), isHorizontal: true)
|
||||
{
|
||||
Stretch = true,
|
||||
RelativeSpacing = 0.01f,
|
||||
CanBeFocused = true
|
||||
};
|
||||
|
||||
var playStyleTags = GetPlayStyleTags();
|
||||
foreach (string tag in playStyleTags)
|
||||
{
|
||||
if (!ServerListScreen.PlayStyleIcons.ContainsKey(tag)) { continue; }
|
||||
|
||||
new GUIImage(new RectTransform(Vector2.One, playStyleContainer.RectTransform),
|
||||
ServerListScreen.PlayStyleIcons[tag], scaleToFit: true)
|
||||
{
|
||||
ToolTip = TextManager.Get("servertagdescription." + tag),
|
||||
Color = ServerListScreen.PlayStyleIconColors[tag]
|
||||
};
|
||||
}
|
||||
|
||||
playStyleContainer.Recalculate();
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
float elementHeight = 0.075f;
|
||||
|
||||
// Spacing
|
||||
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.025f), content.RectTransform), style: null);
|
||||
|
||||
var serverMsg = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.3f), content.RectTransform)) { ScrollBarVisible = true };
|
||||
var msgText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), serverMsg.Content.RectTransform), ServerMessage, font: GUIStyle.SmallFont, wrap: true)
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
serverMsg.Content.RectTransform.SizeChanged += () => { msgText.CalculateHeightFromText(); };
|
||||
msgText.RectTransform.SizeChanged += () => { serverMsg.UpdateScrollBarSize(); };
|
||||
|
||||
var gameMode = new GUITextBlock(new RectTransform(new Vector2(1.0f, elementHeight), content.RectTransform), TextManager.Get("GameMode"));
|
||||
new GUITextBlock(new RectTransform(Vector2.One, gameMode.RectTransform),
|
||||
TextManager.Get(GameMode.IsEmpty ? "Unknown" : "GameMode." + GameMode).Fallback(GameMode.Value),
|
||||
textAlignment: Alignment.Right);
|
||||
|
||||
GUITextBlock playStyleText = null;
|
||||
if (hidePlaystyleBanner && PlayStyle.HasValue)
|
||||
{
|
||||
PlayStyle playStyle = PlayStyle.Value;
|
||||
playStyleText = new GUITextBlock(new RectTransform(new Vector2(1.0f, elementHeight), content.RectTransform), TextManager.Get("serverplaystyle"));
|
||||
new GUITextBlock(new RectTransform(Vector2.One, playStyleText.RectTransform), TextManager.Get("servertag." + playStyle), textAlignment: Alignment.Right);
|
||||
}
|
||||
|
||||
var subSelection = new GUITextBlock(new RectTransform(new Vector2(1.0f, elementHeight), content.RectTransform), TextManager.Get("ServerListSubSelection"));
|
||||
new GUITextBlock(new RectTransform(Vector2.One, subSelection.RectTransform), TextManager.Get(!SubSelectionMode.HasValue ? "Unknown" : SubSelectionMode.Value.ToString()), textAlignment: Alignment.Right);
|
||||
|
||||
var modeSelection = new GUITextBlock(new RectTransform(new Vector2(1.0f, elementHeight), content.RectTransform), TextManager.Get("ServerListModeSelection"));
|
||||
new GUITextBlock(new RectTransform(Vector2.One, modeSelection.RectTransform), TextManager.Get(!ModeSelectionMode.HasValue ? "Unknown" : ModeSelectionMode.Value.ToString()), textAlignment: Alignment.Right);
|
||||
|
||||
if (gameMode.TextSize.X + gameMode.GetChild<GUITextBlock>().TextSize.X > gameMode.Rect.Width ||
|
||||
subSelection.TextSize.X + subSelection.GetChild<GUITextBlock>().TextSize.X > subSelection.Rect.Width ||
|
||||
modeSelection.TextSize.X + modeSelection.GetChild<GUITextBlock>().TextSize.X > modeSelection.Rect.Width)
|
||||
{
|
||||
gameMode.Font = subSelection.Font = modeSelection.Font = GUIStyle.SmallFont;
|
||||
gameMode.GetChild<GUITextBlock>().Font = subSelection.GetChild<GUITextBlock>().Font = modeSelection.GetChild<GUITextBlock>().Font = GUIStyle.SmallFont;
|
||||
if (playStyleText != null)
|
||||
{
|
||||
playStyleText.Font = playStyleText.GetChild<GUITextBlock>().Font = GUIStyle.SmallFont;
|
||||
}
|
||||
}
|
||||
|
||||
var allowSpectating = new GUITickBox(new RectTransform(new Vector2(1, elementHeight), content.RectTransform), TextManager.Get("ServerListAllowSpectating"))
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
if (!AllowSpectating.HasValue)
|
||||
new GUITextBlock(new RectTransform(new Vector2(0.8f, 0.8f), allowSpectating.Box.RectTransform, Anchor.Center), "?", textAlignment: Alignment.Center);
|
||||
else
|
||||
allowSpectating.Selected = AllowSpectating.Value;
|
||||
|
||||
var allowRespawn = new GUITickBox(new RectTransform(new Vector2(1, elementHeight), content.RectTransform), TextManager.Get("ServerSettingsAllowRespawning"))
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
if (!AllowRespawn.HasValue)
|
||||
new GUITextBlock(new RectTransform(new Vector2(0.8f, 0.8f), allowRespawn.Box.RectTransform, Anchor.Center), "?", textAlignment: Alignment.Center);
|
||||
else
|
||||
allowRespawn.Selected = AllowRespawn.Value;
|
||||
|
||||
/*var voipEnabledTickBox = new GUITickBox(new RectTransform(new Vector2(1.0f, elementHeight), bodyContainer.RectTransform), TextManager.Get("serversettingsvoicechatenabled"))
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
if (!VoipEnabled.HasValue)
|
||||
new GUITextBlock(new RectTransform(new Vector2(0.8f, 0.8f), voipEnabledTickBox.Box.RectTransform, Anchor.Center), "?", textAlignment: Alignment.Center);
|
||||
else
|
||||
voipEnabledTickBox.Selected = VoipEnabled.Value;*/
|
||||
|
||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform),
|
||||
TextManager.Get("ServerListContentPackages"), textAlignment: Alignment.Center, font: GUIStyle.SubHeadingFont);
|
||||
|
||||
var contentPackageList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.3f), frame.RectTransform))
|
||||
{
|
||||
ScrollBarVisible = true,
|
||||
OnSelected = (component, o) => false
|
||||
};
|
||||
if (ContentPackageNames.Count == 0)
|
||||
{
|
||||
new GUITextBlock(new RectTransform(Vector2.One, contentPackageList.Content.RectTransform), TextManager.Get("Unknown"), textAlignment: Alignment.Center)
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < ContentPackageNames.Count; i++)
|
||||
{
|
||||
var packageText = new GUITickBox(
|
||||
new RectTransform(new Vector2(1.0f, 0.15f), contentPackageList.Content.RectTransform)
|
||||
{ MinSize = new Point(0, 15) },
|
||||
ContentPackageNames[i])
|
||||
{
|
||||
Enabled = false
|
||||
};
|
||||
packageText.Box.Enabled = true;
|
||||
packageText.TextBlock.Enabled = true;
|
||||
if (i < ContentPackageHashes.Count)
|
||||
{
|
||||
if (ContentPackageManager.AllPackages.Any(contentPackage => contentPackage.Hash.StringRepresentation == ContentPackageHashes[i]))
|
||||
{
|
||||
packageText.TextColor = GUIStyle.Green;
|
||||
packageText.Selected = true;
|
||||
}
|
||||
//workshop download link found
|
||||
else if (i < ContentPackageWorkshopIds.Count && ContentPackageWorkshopIds[i] != 0)
|
||||
{
|
||||
packageText.ToolTip = TextManager.GetWithVariable("ServerListIncompatibleContentPackageWorkshopAvailable", "[contentpackage]", ContentPackageNames[i]);
|
||||
}
|
||||
else //no package or workshop download link found (TODO: update text to say that they could be downloaded through the server)
|
||||
{
|
||||
packageText.TextColor = GameMain.VanillaContent.NameMatches(ContentPackageNames[i]) ? GUIStyle.Red : GUIStyle.Yellow;
|
||||
packageText.ToolTip = TextManager.GetWithVariables("ServerListIncompatibleContentPackage",
|
||||
("[contentpackage]", ContentPackageNames[i]), ("[hash]", ContentPackageHashes[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
foreach (GUIComponent c in content.Children)
|
||||
{
|
||||
if (c is GUITextBlock textBlock) { textBlock.Padding = Vector4.Zero; }
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetPlayStyleTags()
|
||||
{
|
||||
List<string> tags = new List<string>();
|
||||
if (KarmaEnabled.HasValue)
|
||||
{
|
||||
tags.Add(KarmaEnabled.Value ? "karma.true" : "karma.false");
|
||||
}
|
||||
if (TraitorsEnabled.HasValue)
|
||||
{
|
||||
tags.Add(TraitorsEnabled.Value == YesNoMaybe.Maybe ?
|
||||
"traitors.maybe" :
|
||||
(TraitorsEnabled.Value == YesNoMaybe.Yes ? "traitors.true" : "traitors.false"));
|
||||
}
|
||||
if (VoipEnabled.HasValue)
|
||||
{
|
||||
tags.Add(VoipEnabled.Value ? "voip.true" : "voip.false");
|
||||
}
|
||||
if (FriendlyFireEnabled.HasValue)
|
||||
{
|
||||
tags.Add(FriendlyFireEnabled.Value ? "friendlyfire.true" : "friendlyfire.false");
|
||||
}
|
||||
if (ContentPackageNames.Count > 0)
|
||||
{
|
||||
tags.Add(ContentPackageNames.Count > 1 || !GameMain.VanillaContent.NameMatches(ContentPackageNames[0]) ? "modded.true" : "modded.false");
|
||||
}
|
||||
return tags;
|
||||
}
|
||||
|
||||
public static ServerInfo FromXElement(XElement element)
|
||||
{
|
||||
string endpointStr
|
||||
= element.GetAttributeString("Endpoint", null)
|
||||
?? element.GetAttributeString("OwnerID", null)
|
||||
?? $"{element.GetAttributeString("IP", "")}:{element.GetAttributeInt("Port", 0)}";
|
||||
|
||||
if (!(Endpoint.Parse(endpointStr).TryUnwrap(out var endpoint))) { return null; }
|
||||
|
||||
ServerInfo info = new ServerInfo
|
||||
{
|
||||
ServerName = element.GetAttributeString("ServerName", ""),
|
||||
ServerMessage = element.GetAttributeString("ServerMessage", ""),
|
||||
Endpoint = endpoint,
|
||||
QueryPort = !string.IsNullOrEmpty(element.GetAttributeString("QueryPort", string.Empty)) ? element.GetAttributeInt("QueryPort", 0) : 0,
|
||||
GameMode = element.GetAttributeIdentifier("GameMode", Identifier.Empty),
|
||||
GameVersion = element.GetAttributeString("GameVersion", ""),
|
||||
MaxPlayers = Math.Min(element.GetAttributeInt("MaxPlayers", 0), NetConfig.MaxPlayers),
|
||||
HasPassword = element.GetAttributeBool("HasPassword", false),
|
||||
RespondedToSteamQuery = null
|
||||
};
|
||||
|
||||
if (Enum.TryParse(element.GetAttributeString("PlayStyle", ""), out PlayStyle playStyleTemp)) { info.PlayStyle = playStyleTemp; }
|
||||
if (Enum.TryParse(element.GetAttributeString("TraitorsEnabled", ""), out YesNoMaybe traitorsTemp)) { info.TraitorsEnabled = traitorsTemp; }
|
||||
if (Enum.TryParse(element.GetAttributeString("SubSelectionMode", ""), out SelectionMode subSelectionTemp)) { info.SubSelectionMode = subSelectionTemp; }
|
||||
if (Enum.TryParse(element.GetAttributeString("ModeSelectionMode", ""), out SelectionMode modeSelectionTemp)) { info.ModeSelectionMode = modeSelectionTemp; }
|
||||
if (bool.TryParse(element.GetAttributeString("VoipEnabled", ""), out bool voipTemp)) { info.VoipEnabled = voipTemp; }
|
||||
if (bool.TryParse(element.GetAttributeString("KarmaEnabled", ""), out bool karmaTemp)) { info.KarmaEnabled = karmaTemp; }
|
||||
if (bool.TryParse(element.GetAttributeString("FriendlyFireEnabled", ""), out bool friendlyFireTemp)) { info.FriendlyFireEnabled = friendlyFireTemp; }
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
public void QueryLiveInfo(Action<Networking.ServerInfo> onServerRulesReceived, Action<Networking.ServerInfo> onQueryDone)
|
||||
{
|
||||
if (!SteamManager.IsInitialized) { return; }
|
||||
|
||||
if (QueryPort != 0 && Endpoint is LidgrenEndpoint { NetEndpoint: { Address: var ipAddress } })
|
||||
{
|
||||
if (MatchmakingPingResponse is { QueryActive: true })
|
||||
{
|
||||
MatchmakingPingResponse.Cancel();
|
||||
}
|
||||
|
||||
MatchmakingPingResponse = new Steamworks.SteamMatchmakingPingResponse(
|
||||
(server) =>
|
||||
{
|
||||
ServerName = server.Name;
|
||||
RespondedToSteamQuery = true;
|
||||
PlayerCount = server.Players;
|
||||
MaxPlayers = server.MaxPlayers;
|
||||
HasPassword = server.Passworded;
|
||||
PingChecked = true;
|
||||
Ping = server.Ping;
|
||||
LobbyID = 0;
|
||||
TaskPool.Add("QueryServerRules (QueryLiveInfo)", server.QueryRulesAsync(),
|
||||
(t) =>
|
||||
{
|
||||
onQueryDone(this);
|
||||
if (t.Status == TaskStatus.Faulted)
|
||||
{
|
||||
TaskPool.PrintTaskExceptions(t, "Failed to retrieve rules for " + ServerName);
|
||||
return;
|
||||
}
|
||||
|
||||
t.TryGetResult(out Dictionary<string, string> rules);
|
||||
SteamManager.AssignServerRulesToServerInfo(rules, this);
|
||||
|
||||
onServerRulesReceived(this);
|
||||
});
|
||||
},
|
||||
() =>
|
||||
{
|
||||
RespondedToSteamQuery = false;
|
||||
});
|
||||
|
||||
MatchmakingPingResponse.HQueryPing(ipAddress, QueryPort);
|
||||
}
|
||||
else if (Endpoint is SteamP2PEndpoint { SteamId: var ownerId })
|
||||
{
|
||||
SteamFriend ??= new Steamworks.Friend(ownerId.Value);
|
||||
if (LobbyID == 0)
|
||||
{
|
||||
TaskPool.Add("RequestSteamP2POwnerInfo", SteamFriend?.RequestInfoAsync(),
|
||||
(t) =>
|
||||
{
|
||||
onQueryDone(this);
|
||||
if ((SteamFriend?.IsPlayingThisGame ?? false) && ((SteamFriend?.GameInfo?.Lobby?.Id ?? 0) != 0))
|
||||
{
|
||||
LobbyID = SteamFriend?.GameInfo?.Lobby?.Id.Value ?? 0;
|
||||
Steamworks.SteamMatchmaking.OnLobbyDataChanged += UpdateInfoFromSteamworksLobby;
|
||||
SteamFriend?.GameInfo?.Lobby?.Refresh();
|
||||
}
|
||||
else
|
||||
{
|
||||
RespondedToSteamQuery = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
onQueryDone(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateInfoFromSteamworksLobby(Steamworks.Data.Lobby lobby)
|
||||
{
|
||||
if (lobby.Id != LobbyID) { return; }
|
||||
Steamworks.SteamMatchmaking.OnLobbyDataChanged -= UpdateInfoFromSteamworksLobby;
|
||||
if (string.IsNullOrWhiteSpace(lobby.GetData("haspassword"))) { return; }
|
||||
bool.TryParse(lobby.GetData("haspassword"), out bool hasPassword);
|
||||
int.TryParse(lobby.GetData("playercount"), out int currPlayers);
|
||||
int.TryParse(lobby.GetData("maxplayernum"), out int maxPlayers);
|
||||
|
||||
if (!SteamId.Parse(lobby.GetData("lobbyowner")).TryUnwrap(out var ownerId)) { return; }
|
||||
if (!(Endpoint is SteamP2PEndpoint { SteamId: var id }) || id != ownerId) { return; }
|
||||
|
||||
ServerName = lobby.GetData("name");
|
||||
PlayerCount = currPlayers;
|
||||
MaxPlayers = maxPlayers;
|
||||
HasPassword = hasPassword;
|
||||
RespondedToSteamQuery = true;
|
||||
PingChecked = false;
|
||||
OwnerVerified = true;
|
||||
|
||||
SteamManager.AssignLobbyDataToServerInfo(lobby, this);
|
||||
}
|
||||
|
||||
public XElement ToXElement()
|
||||
{
|
||||
if (Endpoint is null)
|
||||
{
|
||||
return null; //can't save this one since it's not set up correctly
|
||||
}
|
||||
|
||||
XElement element = new XElement("ServerInfo");
|
||||
|
||||
element.SetAttributeValue("ServerName", ServerName);
|
||||
element.SetAttributeValue("ServerMessage", ServerMessage);
|
||||
element.SetAttributeValue("Endpoint", Endpoint.ToString());
|
||||
|
||||
element.SetAttributeValue("GameMode", GameMode);
|
||||
element.SetAttributeValue("GameVersion", GameVersion ?? "");
|
||||
element.SetAttributeValue("MaxPlayers", MaxPlayers);
|
||||
if (PlayStyle.HasValue) { element.SetAttributeValue("PlayStyle", PlayStyle.Value.ToString()); }
|
||||
if (TraitorsEnabled.HasValue) { element.SetAttributeValue("TraitorsEnabled", TraitorsEnabled.Value.ToString()); }
|
||||
if (SubSelectionMode.HasValue) { element.SetAttributeValue("SubSelectionMode", SubSelectionMode.Value.ToString()); }
|
||||
if (ModeSelectionMode.HasValue) { element.SetAttributeValue("ModeSelectionMode", ModeSelectionMode.Value.ToString()); }
|
||||
if (VoipEnabled.HasValue) { element.SetAttributeValue("VoipEnabled", VoipEnabled.Value.ToString()); }
|
||||
if (KarmaEnabled.HasValue) { element.SetAttributeValue("KarmaEnabled", KarmaEnabled.Value.ToString()); }
|
||||
if (FriendlyFireEnabled.HasValue) { element.SetAttributeValue("FriendlyFireEnabled", FriendlyFireEnabled.Value.ToString()); }
|
||||
element.SetAttributeValue("HasPassword", HasPassword.ToString());
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is ServerInfo other ? Equals(other) : base.Equals(obj);
|
||||
}
|
||||
|
||||
public bool Equals(ServerInfo other)
|
||||
{
|
||||
return
|
||||
other.Endpoint == Endpoint &&
|
||||
(other.LobbyID == LobbyID || other.LobbyID == 0 || LobbyID == 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This class is trash, so punish its use by making it horribly inefficient in hashsets
|
||||
/// Doing anything else here would make it cause even more bugs
|
||||
/// </summary>
|
||||
public override int GetHashCode() => 0;
|
||||
|
||||
public bool MatchesByEndpoint(ServerInfo other)
|
||||
{
|
||||
return other.Endpoint == Endpoint;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#nullable enable
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
abstract class FriendProvider
|
||||
{
|
||||
public abstract ServerListScreen.FriendInfo[] RetrieveFriends();
|
||||
public abstract void RetrieveAvatar(ServerListScreen.FriendInfo friend, ServerListScreen.AvatarSize avatarSize);
|
||||
public abstract string GetUserName();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Barotrauma.Networking;
|
||||
using Barotrauma.Steam;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class SteamFriendProvider : FriendProvider
|
||||
{
|
||||
private static ServerListScreen.FriendInfo FromSteamFriend(Steamworks.Friend steamFriend)
|
||||
=> new ServerListScreen.FriendInfo(
|
||||
steamFriend.Name,
|
||||
new SteamId(steamFriend.Id),
|
||||
steamFriend.State switch
|
||||
{
|
||||
Steamworks.FriendState.Offline => ServerListScreen.FriendInfo.Status.Offline,
|
||||
Steamworks.FriendState.Invisible => ServerListScreen.FriendInfo.Status.Offline,
|
||||
_ when steamFriend.IsPlayingThisGame => ServerListScreen.FriendInfo.Status.PlayingBarotrauma,
|
||||
_ when steamFriend.GameInfo is { GameID: var gameId } && gameId > 0 => ServerListScreen.FriendInfo.Status.PlayingAnotherGame,
|
||||
_ => ServerListScreen.FriendInfo.Status.NotPlaying
|
||||
})
|
||||
{
|
||||
ServerName = steamFriend.GetRichPresence("servername"),
|
||||
ConnectCommand = steamFriend.GetRichPresence("connect") is { } connectCmd
|
||||
? ToolBox.ParseConnectCommand(ToolBox.SplitCommand(connectCmd))
|
||||
: Option<ConnectCommand>.None()
|
||||
};
|
||||
|
||||
public override ServerListScreen.FriendInfo[] RetrieveFriends()
|
||||
=> SteamManager.IsInitialized
|
||||
? Steamworks.SteamFriends.GetFriends().Select(FromSteamFriend).ToArray()
|
||||
: Array.Empty<ServerListScreen.FriendInfo>();
|
||||
|
||||
public override void RetrieveAvatar(ServerListScreen.FriendInfo friend, ServerListScreen.AvatarSize avatarSize)
|
||||
{
|
||||
if (!(friend.Id is SteamId steamId)) { return; }
|
||||
|
||||
Func<Steamworks.SteamId, Task<Steamworks.Data.Image?>> avatarFunc = avatarSize switch
|
||||
{
|
||||
ServerListScreen.AvatarSize.Small => Steamworks.SteamFriends.GetSmallAvatarAsync,
|
||||
ServerListScreen.AvatarSize.Medium => Steamworks.SteamFriends.GetMediumAvatarAsync,
|
||||
ServerListScreen.AvatarSize.Large => Steamworks.SteamFriends.GetLargeAvatarAsync,
|
||||
};
|
||||
TaskPool.Add($"Get{avatarSize}AvatarAsync", avatarFunc(steamId.Value), task =>
|
||||
{
|
||||
if (!task.TryGetResult(out Steamworks.Data.Image? img)) { return; }
|
||||
if (!(img is { } avatarImage)) { return; }
|
||||
|
||||
if (friend.Avatar.TryUnwrap(out var prevAvatar))
|
||||
{
|
||||
prevAvatar.Remove();
|
||||
}
|
||||
|
||||
#warning TODO: create an avatar atlas?
|
||||
var avatarTexture = new Texture2D(GameMain.Instance.GraphicsDevice, (int)avatarImage.Width, (int)avatarImage.Height);
|
||||
avatarTexture.SetData(avatarImage.Data);
|
||||
friend.Avatar = Option<Sprite>.Some(new Sprite(avatarTexture, null, null));
|
||||
});
|
||||
}
|
||||
|
||||
public override string GetUserName()
|
||||
=> SteamManager.GetUsername();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
using Barotrauma.Steam;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading.Tasks;
|
||||
using Steamworks.Data;
|
||||
using Color = Microsoft.Xna.Framework.Color;
|
||||
using Socket = System.Net.Sockets.Socket;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
static class PingUtils
|
||||
{
|
||||
private static readonly Dictionary<IPAddress, int> activePings = new Dictionary<IPAddress, int>();
|
||||
|
||||
private static bool steamPingInfoReady;
|
||||
|
||||
public static void QueryPingData()
|
||||
{
|
||||
steamPingInfoReady = false;
|
||||
if (SteamManager.IsInitialized)
|
||||
{
|
||||
TaskPool.Add("WaitForPingDataAsync (serverlist)", Steamworks.SteamNetworkingUtils.WaitForPingDataAsync(), task =>
|
||||
{
|
||||
steamPingInfoReady = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static void GetServerPing(ServerInfo serverInfo, Action<ServerInfo> onPingDiscovered)
|
||||
{
|
||||
if (CoroutineManager.IsCoroutineRunning("ConnectToServer")) { return; }
|
||||
|
||||
switch (serverInfo.Endpoint)
|
||||
{
|
||||
case LidgrenEndpoint { NetEndpoint: { Address: var address } }:
|
||||
GetIPAddressPing(serverInfo, address, onPingDiscovered);
|
||||
break;
|
||||
case SteamP2PEndpoint steamP2PEndpoint:
|
||||
TaskPool.Add($"EstimateSteamLobbyPing ({steamP2PEndpoint.StringRepresentation})",
|
||||
EstimateSteamLobbyPing(serverInfo),
|
||||
t =>
|
||||
{
|
||||
if (!t.TryGetResult(out Option<int> ping)) { return; }
|
||||
serverInfo.Ping = ping;
|
||||
onPingDiscovered(serverInfo);
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly ref struct LobbyDataChangedEventHandler
|
||||
{
|
||||
private readonly Action<Lobby> action;
|
||||
|
||||
public LobbyDataChangedEventHandler(Action<Lobby> action)
|
||||
{
|
||||
this.action = action;
|
||||
Steamworks.SteamMatchmaking.OnLobbyDataChanged += action;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Steamworks.SteamMatchmaking.OnLobbyDataChanged -= action;
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<Lobby?> GetSteamLobbyForUser(SteamId steamId)
|
||||
{
|
||||
var steamFriend = new Steamworks.Friend(steamId.Value);
|
||||
await steamFriend.RequestInfoAsync();
|
||||
|
||||
var friendLobby = steamFriend.GameInfo?.Lobby;
|
||||
if (!(friendLobby is { } lobby)) { return null; }
|
||||
|
||||
bool waiting = true;
|
||||
Lobby loadedLobby = default;
|
||||
|
||||
void finishWaiting(Steamworks.Data.Lobby l)
|
||||
{
|
||||
loadedLobby = l;
|
||||
waiting = false;
|
||||
}
|
||||
|
||||
using (new LobbyDataChangedEventHandler(finishWaiting))
|
||||
{
|
||||
lobby.Refresh();
|
||||
|
||||
for (int i = 0;; i++)
|
||||
{
|
||||
if (!waiting) { break; }
|
||||
if (i >= 100) { return null; }
|
||||
}
|
||||
}
|
||||
|
||||
return loadedLobby;
|
||||
}
|
||||
|
||||
private static async Task<Option<int>> EstimateSteamLobbyPing(ServerInfo serverInfo)
|
||||
{
|
||||
if (!(serverInfo.Endpoint is SteamP2PEndpoint { SteamId: var ownerId })) { return Option<int>.None(); }
|
||||
while (!steamPingInfoReady) { await Task.Delay(50); }
|
||||
|
||||
Lobby lobby;
|
||||
|
||||
if (serverInfo.MetadataSource.TryUnwrap(out SteamP2PServerProvider.DataSource src))
|
||||
{
|
||||
lobby = src.Lobby;
|
||||
}
|
||||
else
|
||||
{
|
||||
var friendLobby = await GetSteamLobbyForUser(ownerId);
|
||||
if (friendLobby is null) { return Option<int>.None(); }
|
||||
lobby = friendLobby.Value;
|
||||
}
|
||||
|
||||
var pingLocation = NetPingLocation.TryParseFromString(lobby.GetData("pinglocation"));
|
||||
|
||||
if (pingLocation.HasValue && Steamworks.SteamNetworkingUtils.LocalPingLocation.HasValue)
|
||||
{
|
||||
int ping = Steamworks.SteamNetworkingUtils.LocalPingLocation.Value.EstimatePingTo(pingLocation.Value);
|
||||
return ping >= 0 ? Option<int>.Some(ping) : Option<int>.None();
|
||||
}
|
||||
else
|
||||
{
|
||||
return Option<int>.None();
|
||||
}
|
||||
}
|
||||
|
||||
private static void GetIPAddressPing(ServerInfo serverInfo, IPAddress address, Action<ServerInfo> onPingDiscovered)
|
||||
{
|
||||
lock (activePings)
|
||||
{
|
||||
if (activePings.ContainsKey(address)) { return; }
|
||||
activePings.Add(address, activePings.Any() ? activePings.Values.Max() + 1 : 0);
|
||||
}
|
||||
|
||||
serverInfo.Ping = Option<int>.None();
|
||||
|
||||
TaskPool.Add($"PingServerAsync ({address})", PingServerAsync(address, 1000),
|
||||
rtt =>
|
||||
{
|
||||
if (!rtt.TryGetResult(out serverInfo.Ping)) { serverInfo.Ping = Option<int>.None(); }
|
||||
onPingDiscovered(serverInfo);
|
||||
lock (activePings)
|
||||
{
|
||||
activePings.Remove(address);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static async Task<Option<int>> PingServerAsync(IPAddress ipAddress, int timeOut)
|
||||
{
|
||||
await Task.Yield();
|
||||
bool shouldGo = false;
|
||||
while (!shouldGo)
|
||||
{
|
||||
lock (activePings)
|
||||
{
|
||||
shouldGo = activePings.Count(kvp => kvp.Value < activePings[ipAddress]) < 25;
|
||||
}
|
||||
await Task.Delay(25);
|
||||
}
|
||||
|
||||
if (ipAddress == null) { return Option<int>.None(); }
|
||||
|
||||
//don't attempt to ping if the address is IPv6 and it's not supported
|
||||
if (ipAddress.AddressFamily == AddressFamily.InterNetworkV6 && !Socket.OSSupportsIPv6) { return Option<int>.None(); }
|
||||
|
||||
Ping ping = new Ping();
|
||||
byte[] buffer = new byte[32];
|
||||
try
|
||||
{
|
||||
PingReply pingReply = await ping.SendPingAsync(ipAddress, timeOut, buffer, new PingOptions(128, true));
|
||||
|
||||
return pingReply.Status switch
|
||||
{
|
||||
IPStatus.Success => Option<int>.Some((int)pingReply.RoundtripTime),
|
||||
_ => Option<int>.None(),
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
GameAnalyticsManager.AddErrorEventOnce("ServerListScreen.PingServer:PingException" + ipAddress, GameAnalyticsManager.ErrorSeverity.Warning, "Failed to ping a server - " + (ex?.InnerException?.Message ?? ex.Message));
|
||||
#if DEBUG
|
||||
DebugConsole.NewMessage("Failed to ping a server (" + ipAddress + ") - " + (ex?.InnerException?.Message ?? ex.Message), Color.Red);
|
||||
#endif
|
||||
|
||||
return Option<int>.None();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,509 @@
|
||||
#nullable enable
|
||||
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Steam;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
sealed class ServerInfo : ISerializableEntity
|
||||
{
|
||||
public abstract class DataSource
|
||||
{
|
||||
public static Option<DataSource> Parse(XElement element)
|
||||
=> ReflectionUtils.ParseDerived<DataSource, XElement>(element);
|
||||
public abstract void Write(XElement element);
|
||||
}
|
||||
|
||||
public Endpoint Endpoint { get; private set; }
|
||||
|
||||
public Option<DataSource> MetadataSource = Option<DataSource>.None();
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public string ServerName { get; set; } = "";
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public string ServerMessage { get; set; } = "";
|
||||
|
||||
public int PlayerCount { get; set; }
|
||||
|
||||
[Serialize(0, IsPropertySaveable.Yes)]
|
||||
public int MaxPlayers { get; set; }
|
||||
|
||||
public bool GameStarted { get; set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes)]
|
||||
public bool HasPassword { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier GameMode { get; set; }
|
||||
|
||||
[Serialize(SelectionMode.Manual, IsPropertySaveable.Yes)]
|
||||
public SelectionMode ModeSelectionMode { get; set; }
|
||||
|
||||
[Serialize(SelectionMode.Manual, IsPropertySaveable.Yes)]
|
||||
public SelectionMode SubSelectionMode { get; set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes)]
|
||||
public bool AllowSpectating { get; set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes)]
|
||||
public bool VoipEnabled { get; set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes)]
|
||||
public bool KarmaEnabled { get; set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes)]
|
||||
public bool FriendlyFireEnabled { get; set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes)]
|
||||
public bool AllowRespawn { get; set; }
|
||||
|
||||
[Serialize(YesNoMaybe.No, IsPropertySaveable.Yes)]
|
||||
public YesNoMaybe TraitorsEnabled { get; set; }
|
||||
|
||||
[Serialize(PlayStyle.Casual, IsPropertySaveable.Yes)]
|
||||
public PlayStyle PlayStyle { get; set; }
|
||||
|
||||
public Version GameVersion { get; set; } = new Version(0,0,0,0);
|
||||
|
||||
public Option<int> Ping = Option<int>.None();
|
||||
|
||||
public bool Checked = false;
|
||||
|
||||
public readonly struct ContentPackageInfo
|
||||
{
|
||||
public readonly string Name;
|
||||
public readonly string Hash;
|
||||
public readonly Option<ContentPackageId> Id;
|
||||
|
||||
public ContentPackageInfo(string name, string hash, Option<ContentPackageId> id)
|
||||
{
|
||||
Name = name;
|
||||
Hash = hash;
|
||||
Id = id;
|
||||
}
|
||||
|
||||
public ContentPackageInfo(ContentPackage pkg)
|
||||
{
|
||||
Name = pkg.Name;
|
||||
Hash = pkg.Hash.StringRepresentation;
|
||||
Id = pkg.UgcId;
|
||||
}
|
||||
}
|
||||
|
||||
public ImmutableArray<ContentPackageInfo> ContentPackages;
|
||||
|
||||
public bool IsModded => ContentPackages.Any(p => !GameMain.VanillaContent.NameMatches(p.Name));
|
||||
|
||||
public ServerInfo(Endpoint endpoint)
|
||||
{
|
||||
SerializableProperties = SerializableProperty.GetProperties(this);
|
||||
Endpoint = endpoint;
|
||||
ContentPackages = ImmutableArray<ContentPackageInfo>.Empty;
|
||||
}
|
||||
|
||||
public static ServerInfo FromServerConnection(NetworkConnection connection, ServerSettings serverSettings)
|
||||
{
|
||||
var serverInfo = new ServerInfo(connection.Endpoint)
|
||||
{
|
||||
GameMode = GameMain.NetLobbyScreen.SelectedMode?.Identifier ?? Identifier.Empty,
|
||||
GameStarted = Screen.Selected != GameMain.NetLobbyScreen,
|
||||
GameVersion = GameMain.Version,
|
||||
PlayerCount = GameMain.Client.ConnectedClients.Count,
|
||||
ContentPackages = ContentPackageManager.EnabledPackages.All.Select(p => new ContentPackageInfo(p)).ToImmutableArray(),
|
||||
Ping = GameMain.Client.Ping,
|
||||
|
||||
// -------------------------------------
|
||||
// Settings that cannot be copied via
|
||||
// SerializableProperty because they do
|
||||
// not implement the attribute
|
||||
ServerName = serverSettings.ServerName,
|
||||
ServerMessage = serverSettings.ServerMessageText,
|
||||
// -------------------------------------
|
||||
// Settings that cannot be copied via
|
||||
// SerializableProperty due to name mismatch
|
||||
HasPassword = serverSettings.HasPassword,
|
||||
VoipEnabled = serverSettings.VoiceChatEnabled,
|
||||
FriendlyFireEnabled = serverSettings.AllowFriendlyFire,
|
||||
// -------------------------------------
|
||||
|
||||
Checked = true
|
||||
};
|
||||
|
||||
var serverInfoSerializableProperties
|
||||
= SerializableProperty.GetProperties(serverInfo);
|
||||
var serverSettingsSerializableProperties
|
||||
= SerializableProperty.GetProperties(serverSettings);
|
||||
|
||||
var intersection = serverInfoSerializableProperties.Keys
|
||||
.Where(serverSettingsSerializableProperties.ContainsKey);
|
||||
|
||||
foreach (var key in intersection)
|
||||
{
|
||||
var propToGet = serverSettingsSerializableProperties[key];
|
||||
var propToSet = serverInfoSerializableProperties[key];
|
||||
if (!propToGet.PropertyInfo.CanRead) { continue; }
|
||||
if (!propToSet.PropertyInfo.CanWrite) { continue; }
|
||||
propToSet.SetValue(
|
||||
serverInfo,
|
||||
propToGet.GetValue(serverSettings));
|
||||
}
|
||||
|
||||
return serverInfo;
|
||||
}
|
||||
|
||||
public void CreatePreviewWindow(GUIFrame frame)
|
||||
{
|
||||
frame.ClearChildren();
|
||||
|
||||
var serverListScreen = GameMain.ServerListScreen;
|
||||
|
||||
var title = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), frame.RectTransform), ServerName, font: GUIStyle.LargeFont)
|
||||
{
|
||||
ToolTip = ServerName,
|
||||
CanBeFocused = false
|
||||
};
|
||||
title.Text = ToolBox.LimitString(title.Text, title.Font, (int)(title.Rect.Width * 0.85f));
|
||||
|
||||
bool isFavorite = serverListScreen.IsFavorite(this);
|
||||
|
||||
static LocalizedString favoriteTickBoxToolTip(bool isFavorite)
|
||||
=> TextManager.Get(isFavorite ? "RemoveFromFavorites" : "AddToFavorites");
|
||||
|
||||
GUITickBox favoriteTickBox = new GUITickBox(new RectTransform(new Vector2(0.15f, 0.8f), title.RectTransform, Anchor.CenterRight),
|
||||
"", null, "GUIServerListFavoriteTickBox")
|
||||
{
|
||||
UserData = this,
|
||||
Selected = isFavorite,
|
||||
ToolTip = favoriteTickBoxToolTip(isFavorite),
|
||||
OnSelected = tickbox =>
|
||||
{
|
||||
ServerInfo info = (ServerInfo)tickbox.UserData;
|
||||
if (tickbox.Selected)
|
||||
{
|
||||
GameMain.ServerListScreen.AddToFavoriteServers(info);
|
||||
}
|
||||
else
|
||||
{
|
||||
GameMain.ServerListScreen.RemoveFromFavoriteServers(info);
|
||||
}
|
||||
tickbox.ToolTip = favoriteTickBoxToolTip(tickbox.Selected);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), frame.RectTransform),
|
||||
TextManager.AddPunctuation(':', TextManager.Get("ServerListVersion"),
|
||||
GameVersion.ToString()))
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
|
||||
PlayStyle playStyle = PlayStyle;
|
||||
Sprite? playStyleBannerSprite = GUIStyle.GetComponentStyle($"PlayStyleBanner.{playStyle}")?.GetSprite(GUIComponent.ComponentState.None);
|
||||
|
||||
GUIComponent playStyleBanner;
|
||||
Color playStyleBannerColor;
|
||||
if (playStyleBannerSprite != null)
|
||||
{
|
||||
float playStyleBannerAspectRatio = (float)playStyleBannerSprite.SourceRect.Width / (float)playStyleBannerSprite.SourceRect.Height;
|
||||
playStyleBanner = new GUIImage(new RectTransform(new Vector2(1.0f, 1.0f / playStyleBannerAspectRatio), frame.RectTransform, scaleBasis: ScaleBasis.BothWidth),
|
||||
playStyleBannerSprite, null, true);
|
||||
playStyleBannerColor = playStyleBannerSprite.SourceElement.GetAttributeColor("bannercolor", Color.Black);
|
||||
}
|
||||
else
|
||||
{
|
||||
playStyleBanner = new GUIFrame(new RectTransform((1.0f, 0.2f), frame.RectTransform), style: null)
|
||||
{
|
||||
Color = Color.Black,
|
||||
DisabledColor = Color.Black,
|
||||
OutlineColor = Color.Black,
|
||||
PressedColor = Color.Black,
|
||||
SelectedColor = Color.Black,
|
||||
HoverColor = Color.Black
|
||||
};
|
||||
playStyleBannerColor = Color.Black;
|
||||
}
|
||||
|
||||
var playStyleName = new GUITextBlock(
|
||||
new RectTransform(new Vector2(0.15f, 0.0f), playStyleBanner.RectTransform)
|
||||
{ RelativeOffset = new Vector2(0.0f, 0.06f) },
|
||||
TextManager.AddPunctuation(':', TextManager.Get("serverplaystyle"),
|
||||
TextManager.Get($"servertag.{playStyle}")), textColor: Color.White,
|
||||
font: GUIStyle.SmallFont, textAlignment: Alignment.Center,
|
||||
color: playStyleBannerColor, style: "GUISlopedHeader");
|
||||
playStyleName.RectTransform.NonScaledSize = (playStyleName.Font.MeasureString(playStyleName.Text) + new Vector2(20, 5) * GUI.Scale).ToPoint();
|
||||
playStyleName.RectTransform.IsFixedSize = true;
|
||||
|
||||
var serverType = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), frame.RectTransform),
|
||||
Endpoint?.ServerTypeString ?? string.Empty,
|
||||
textAlignment: Alignment.TopLeft)
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
serverType.RectTransform.MinSize = new Point(0, (int)(serverType.Rect.Height * 1.5f));
|
||||
|
||||
var content = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.6f), frame.RectTransform))
|
||||
{
|
||||
Stretch = true
|
||||
};
|
||||
// playstyle tags -----------------------------------------------------------------------------
|
||||
|
||||
var playStyleContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.15f), content.RectTransform), isHorizontal: true)
|
||||
{
|
||||
Stretch = true,
|
||||
RelativeSpacing = 0.01f,
|
||||
CanBeFocused = true
|
||||
};
|
||||
|
||||
var playStyleTags = GetPlayStyleTags();
|
||||
foreach (var tag in playStyleTags)
|
||||
{
|
||||
var playStyleIcon = GUIStyle.GetComponentStyle($"PlayStyleIcon.{tag}")
|
||||
?.GetSprite(GUIComponent.ComponentState.None);
|
||||
if (playStyleIcon is null) { continue; }
|
||||
|
||||
new GUIImage(new RectTransform(Vector2.One, playStyleContainer.RectTransform),
|
||||
playStyleIcon, scaleToFit: true)
|
||||
{
|
||||
ToolTip = TextManager.Get($"servertagdescription.{tag}"),
|
||||
Color = Color.White
|
||||
};
|
||||
}
|
||||
|
||||
playStyleContainer.Recalculate();
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
float elementHeight = 0.075f;
|
||||
|
||||
// Spacing
|
||||
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.025f), content.RectTransform), style: null);
|
||||
|
||||
var serverMsg = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.3f), content.RectTransform)) { ScrollBarVisible = true };
|
||||
var msgText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), serverMsg.Content.RectTransform), ServerMessage ?? string.Empty, font: GUIStyle.SmallFont, wrap: true)
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
serverMsg.Content.RectTransform.SizeChanged += () => { msgText.CalculateHeightFromText(); };
|
||||
msgText.RectTransform.SizeChanged += () => { serverMsg.UpdateScrollBarSize(); };
|
||||
|
||||
var gameMode = new GUITextBlock(new RectTransform(new Vector2(1.0f, elementHeight), content.RectTransform), TextManager.Get("GameMode"));
|
||||
new GUITextBlock(new RectTransform(Vector2.One, gameMode.RectTransform),
|
||||
TextManager.Get(GameMode.IsEmpty ? "Unknown" : "GameMode." + GameMode).Fallback(GameMode.Value),
|
||||
textAlignment: Alignment.Right);
|
||||
|
||||
GUITextBlock playStyleText = new GUITextBlock(new RectTransform(new Vector2(1.0f, elementHeight), content.RectTransform), TextManager.Get("serverplaystyle"));
|
||||
new GUITextBlock(new RectTransform(Vector2.One, playStyleText.RectTransform), TextManager.Get("servertag." + playStyle), textAlignment: Alignment.Right);
|
||||
|
||||
var subSelection = new GUITextBlock(new RectTransform(new Vector2(1.0f, elementHeight), content.RectTransform), TextManager.Get("ServerListSubSelection"));
|
||||
new GUITextBlock(new RectTransform(Vector2.One, subSelection.RectTransform), TextManager.Get(SubSelectionMode.ToString()), textAlignment: Alignment.Right);
|
||||
|
||||
var modeSelection = new GUITextBlock(new RectTransform(new Vector2(1.0f, elementHeight), content.RectTransform), TextManager.Get("ServerListModeSelection"));
|
||||
new GUITextBlock(new RectTransform(Vector2.One, modeSelection.RectTransform), TextManager.Get(ModeSelectionMode.ToString()), textAlignment: Alignment.Right);
|
||||
|
||||
if (gameMode.TextSize.X + gameMode.GetChild<GUITextBlock>().TextSize.X > gameMode.Rect.Width ||
|
||||
subSelection.TextSize.X + subSelection.GetChild<GUITextBlock>().TextSize.X > subSelection.Rect.Width ||
|
||||
modeSelection.TextSize.X + modeSelection.GetChild<GUITextBlock>().TextSize.X > modeSelection.Rect.Width)
|
||||
{
|
||||
gameMode.Font = subSelection.Font = modeSelection.Font = GUIStyle.SmallFont;
|
||||
gameMode.GetChild<GUITextBlock>().Font = subSelection.GetChild<GUITextBlock>().Font = modeSelection.GetChild<GUITextBlock>().Font = GUIStyle.SmallFont;
|
||||
playStyleText.Font = playStyleText.GetChild<GUITextBlock>().Font = GUIStyle.SmallFont;
|
||||
}
|
||||
|
||||
var allowSpectating = new GUITickBox(new RectTransform(new Vector2(1, elementHeight), content.RectTransform), TextManager.Get("ServerListAllowSpectating"))
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
allowSpectating.Selected = AllowSpectating;
|
||||
|
||||
var allowRespawn = new GUITickBox(new RectTransform(new Vector2(1, elementHeight), content.RectTransform), TextManager.Get("ServerSettingsAllowRespawning"))
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
allowRespawn.Selected = AllowRespawn;
|
||||
|
||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform),
|
||||
TextManager.Get("ServerListContentPackages"), textAlignment: Alignment.Center, font: GUIStyle.SubHeadingFont);
|
||||
|
||||
var contentPackageList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.3f), frame.RectTransform))
|
||||
{
|
||||
ScrollBarVisible = true,
|
||||
OnSelected = (component, o) => false
|
||||
};
|
||||
if (ContentPackages.Length == 0)
|
||||
{
|
||||
new GUITextBlock(new RectTransform(Vector2.One, contentPackageList.Content.RectTransform), TextManager.Get("Unknown"), textAlignment: Alignment.Center)
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var package in ContentPackages)
|
||||
{
|
||||
var packageText = new GUITickBox(
|
||||
new RectTransform(new Vector2(1.0f, 0.15f), contentPackageList.Content.RectTransform)
|
||||
{ MinSize = new Point(0, 15) },
|
||||
package.Name)
|
||||
{
|
||||
CanBeFocused = false
|
||||
};
|
||||
if (!string.IsNullOrEmpty(package.Hash))
|
||||
{
|
||||
if (ContentPackageManager.AllPackages.Any(contentPackage => contentPackage.Hash.StringRepresentation == package.Hash))
|
||||
{
|
||||
packageText.TextColor = GUIStyle.Green;
|
||||
packageText.Selected = true;
|
||||
}
|
||||
//workshop download link found
|
||||
else if (package.Id is Some<ContentPackageId> { Value: var ugcId } && ugcId is SteamWorkshopId)
|
||||
{
|
||||
packageText.ToolTip = TextManager.GetWithVariable("ServerListIncompatibleContentPackageWorkshopAvailable", "[contentpackage]", package.Name);
|
||||
}
|
||||
else //no package or workshop download link found
|
||||
{
|
||||
packageText.TextColor = GameMain.VanillaContent.NameMatches(package.Name) ? GUIStyle.Red : GUIStyle.Yellow;
|
||||
packageText.ToolTip = TextManager.GetWithVariables("ServerListIncompatibleContentPackage",
|
||||
("[contentpackage]", package.Name), ("[hash]", package.Hash));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
foreach (GUIComponent c in content.Children)
|
||||
{
|
||||
if (c is GUITextBlock textBlock) { textBlock.Padding = Vector4.Zero; }
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<Identifier> GetPlayStyleTags()
|
||||
{
|
||||
yield return $"Karma.{KarmaEnabled}".ToIdentifier();
|
||||
yield return (TraitorsEnabled == YesNoMaybe.Yes ? $"Traitors.True" : $"Traitors.False").ToIdentifier();
|
||||
yield return $"VoIP.{VoipEnabled}".ToIdentifier();
|
||||
yield return $"FriendlyFire.{FriendlyFireEnabled}".ToIdentifier();
|
||||
yield return $"Modded.{ContentPackages.Any()}".ToIdentifier();
|
||||
}
|
||||
|
||||
public void UpdateInfo(Func<string, string?> valueGetter)
|
||||
{
|
||||
ServerMessage = valueGetter("message") ?? "";
|
||||
GameVersion = Version.TryParse(valueGetter("version"), out var version)
|
||||
? version
|
||||
: GameMain.Version;
|
||||
|
||||
if (int.TryParse(valueGetter("playercount"), out int playerCount)) { PlayerCount = playerCount; }
|
||||
if (int.TryParse(valueGetter("maxplayernum"), out int maxPlayers)) { MaxPlayers = maxPlayers; }
|
||||
if (Enum.TryParse(valueGetter("modeselectionmode"), out SelectionMode modeSelectionMode)) { ModeSelectionMode = modeSelectionMode; }
|
||||
if (Enum.TryParse(valueGetter("subselectionmode"), out SelectionMode subSelectionMode)) { SubSelectionMode = subSelectionMode; }
|
||||
|
||||
HasPassword = getBool("haspassword");
|
||||
GameStarted = getBool("gamestarted");
|
||||
KarmaEnabled = getBool("karmaenabled");
|
||||
FriendlyFireEnabled = getBool("friendlyfireenabled");
|
||||
AllowSpectating = getBool("allowspectating");
|
||||
AllowRespawn = getBool("allowrespawn");
|
||||
VoipEnabled = getBool("voicechatenabled");
|
||||
|
||||
GameMode = valueGetter("gamemode")?.ToIdentifier() ?? Identifier.Empty;
|
||||
if (Enum.TryParse(valueGetter("traitors"), out YesNoMaybe traitorsEnabled)) { TraitorsEnabled = traitorsEnabled; }
|
||||
if (Enum.TryParse(valueGetter("playstyle"), out PlayStyle playStyle)) { PlayStyle = playStyle; }
|
||||
|
||||
ContentPackages = ExtractContentPackageInfo(valueGetter).ToImmutableArray();
|
||||
|
||||
bool getBool(string key)
|
||||
{
|
||||
string? data = valueGetter(key);
|
||||
return bool.TryParse(data, out var result) && result;
|
||||
}
|
||||
}
|
||||
|
||||
private static ContentPackageInfo[] ExtractContentPackageInfo(Func<string, string?> valueGetter)
|
||||
{
|
||||
string? joinedNames = valueGetter("contentpackage");
|
||||
string? joinedHashes = valueGetter("contentpackagehash");
|
||||
string? joinedWorkshopIds = valueGetter("contentpackageid");
|
||||
|
||||
string[] contentPackageNames = joinedNames.IsNullOrEmpty() ? Array.Empty<string>() : joinedNames.Split(',');
|
||||
string[] contentPackageHashes = joinedHashes.IsNullOrEmpty() ? Array.Empty<string>() : joinedHashes.Split(',');
|
||||
#warning TODO: genericize
|
||||
ulong[] contentPackageIds = joinedWorkshopIds.IsNullOrEmpty() ? new ulong[1] : SteamManager.ParseWorkshopIds(joinedWorkshopIds).ToArray();
|
||||
|
||||
if (contentPackageNames.Length != contentPackageHashes.Length
|
||||
|| contentPackageHashes.Length != contentPackageIds.Length)
|
||||
{
|
||||
return Array.Empty<ContentPackageInfo>();
|
||||
}
|
||||
|
||||
return contentPackageNames
|
||||
.Zip(contentPackageHashes, (name, hash) => (name, hash))
|
||||
.Zip(contentPackageIds, (t1, id) =>
|
||||
new ContentPackageInfo(
|
||||
t1.name,
|
||||
t1.hash,
|
||||
Option<ContentPackageId>.Some(new SteamWorkshopId(id))))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
public static Option<ServerInfo> FromXElement(XElement element)
|
||||
{
|
||||
string endpointStr
|
||||
= element.GetAttributeString("Endpoint", null)
|
||||
?? element.GetAttributeString("OwnerID", null)
|
||||
?? $"{element.GetAttributeString("IP", "")}:{element.GetAttributeInt("Port", 0)}";
|
||||
|
||||
if (!Endpoint.Parse(endpointStr).TryUnwrap(out var endpoint)) { return Option<ServerInfo>.None(); }
|
||||
|
||||
var gameVersionStr = element.GetAttributeString("GameVersion", "");
|
||||
if (!Version.TryParse(gameVersionStr, out var gameVersion)) { gameVersion = GameMain.Version; }
|
||||
var info = new ServerInfo(endpoint)
|
||||
{
|
||||
GameVersion = gameVersion
|
||||
};
|
||||
SerializableProperty.DeserializeProperties(info, element);
|
||||
|
||||
info.MetadataSource = DataSource.Parse(element);
|
||||
|
||||
return Option<ServerInfo>.Some(info);
|
||||
}
|
||||
|
||||
public XElement ToXElement()
|
||||
{
|
||||
XElement element = new XElement(GetType().Name);
|
||||
|
||||
element.SetAttributeValue("Endpoint", Endpoint.ToString());
|
||||
element.SetAttributeValue("GameVersion", GameVersion.ToString());
|
||||
|
||||
SerializableProperty.SerializeProperties(this, element, saveIfDefault: true);
|
||||
|
||||
if (MetadataSource.TryUnwrap(out var dataSource))
|
||||
{
|
||||
dataSource.Write(element);
|
||||
}
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is ServerInfo other && Equals(other);
|
||||
}
|
||||
|
||||
public bool Equals(ServerInfo other)
|
||||
=> other.Endpoint == Endpoint;
|
||||
|
||||
public override int GetHashCode() => Endpoint.GetHashCode();
|
||||
|
||||
string ISerializableEntity.Name => "ServerInfo";
|
||||
public Dictionary<Identifier, SerializableProperty> SerializableProperties { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class CompositeServerProvider : ServerProvider
|
||||
{
|
||||
private readonly ImmutableArray<ServerProvider> providers;
|
||||
|
||||
public CompositeServerProvider(params ServerProvider[] providers)
|
||||
{
|
||||
this.providers = providers.ToImmutableArray();
|
||||
}
|
||||
|
||||
protected override void RetrieveServersImpl(Action<ServerInfo> onServerDataReceived, Action onQueryCompleted)
|
||||
{
|
||||
int providersFinished = 0;
|
||||
void ackFinishedProvider()
|
||||
{
|
||||
providersFinished++;
|
||||
if (providersFinished == providers.Length)
|
||||
{
|
||||
onQueryCompleted();
|
||||
}
|
||||
}
|
||||
providers.ForEach(p => p.RetrieveServers(onServerDataReceived, ackFinishedProvider));
|
||||
}
|
||||
|
||||
public override void Cancel()
|
||||
=> providers.ForEach(p => p.Cancel());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
abstract class ServerProvider
|
||||
{
|
||||
public void RetrieveServers(Action<ServerInfo> onServerDataReceived, Action onQueryCompleted)
|
||||
{
|
||||
Cancel();
|
||||
RetrieveServersImpl(onServerDataReceived, onQueryCompleted);
|
||||
}
|
||||
protected abstract void RetrieveServersImpl(Action<ServerInfo> onServerDataReceived, Action onQueryCompleted);
|
||||
public abstract void Cancel();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Networking;
|
||||
using Barotrauma.Steam;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
sealed class SteamDedicatedServerProvider : ServerProvider
|
||||
{
|
||||
public class DataSource : ServerInfo.DataSource
|
||||
{
|
||||
public readonly UInt16 QueryPort;
|
||||
|
||||
public DataSource(UInt16 queryPort)
|
||||
{
|
||||
QueryPort = queryPort;
|
||||
}
|
||||
|
||||
/// Method is invoked via reflection,
|
||||
/// see <see cref="ServerInfo.DataSource.Parse" />
|
||||
public new static Option<DataSource> Parse(XElement element)
|
||||
=> element.TryGetAttributeInt("QueryPort", out var result)
|
||||
? result switch
|
||||
{
|
||||
var invalidPort when invalidPort <= 0 || invalidPort > UInt16.MaxValue => Option<DataSource>.None(),
|
||||
var queryPort => Option<DataSource>.Some(new DataSource((UInt16)queryPort))
|
||||
}
|
||||
: Option<DataSource>.None();
|
||||
|
||||
public override void Write(XElement element) => element.SetAttributeValue("QueryPort", QueryPort);
|
||||
}
|
||||
|
||||
private static Option<ServerInfo> InfoFromListEntry(Steamworks.Data.ServerInfo entry) =>
|
||||
entry.Name.IsNullOrEmpty()
|
||||
? Option<ServerInfo>.None()
|
||||
: Option<ServerInfo>.Some(new ServerInfo(new LidgrenEndpoint(entry.Address, entry.ConnectionPort))
|
||||
{
|
||||
ServerName = entry.Name,
|
||||
HasPassword = entry.Passworded,
|
||||
PlayerCount = entry.Players,
|
||||
MaxPlayers = entry.MaxPlayers,
|
||||
MetadataSource = Option<ServerInfo.DataSource>.Some(new DataSource((UInt16)entry.QueryPort))
|
||||
});
|
||||
|
||||
private static void HandleResponsiveServer(Steamworks.Data.ServerInfo entry, Action<ServerInfo> onServerDataReceived)
|
||||
{
|
||||
TaskPool.Add($"QueryServerRules (GetServers, {entry.Name}, {entry.Address})", entry.QueryRulesAsync(),
|
||||
t =>
|
||||
{
|
||||
if (t.Status == TaskStatus.Faulted)
|
||||
{
|
||||
TaskPool.PrintTaskExceptions(t, $"Failed to retrieve rules for {entry.Name}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!t.TryGetResult(out Dictionary<string, string> rules)) { return; }
|
||||
if (rules is null) { return; }
|
||||
if (!InfoFromListEntry(entry).TryUnwrap(out var serverInfo)) { return; }
|
||||
serverInfo.UpdateInfo(key =>
|
||||
{
|
||||
if (rules.TryGetValue(key, out var val)) { return val; }
|
||||
return null;
|
||||
});
|
||||
serverInfo.Checked = true; //rules != null;
|
||||
|
||||
onServerDataReceived(serverInfo);
|
||||
});
|
||||
}
|
||||
|
||||
private static void HandleUnresponsiveServer(Steamworks.Data.ServerInfo entry, Action<ServerInfo> onServerDataReceived)
|
||||
{
|
||||
//TODO: do we still want to list unresponsive servers?
|
||||
if (!InfoFromListEntry(entry).TryUnwrap(out var serverInfo)) { return; }
|
||||
onServerDataReceived(serverInfo);
|
||||
}
|
||||
|
||||
private Steamworks.ServerList.Internet? serverQuery;
|
||||
private CoroutineHandle? queryCoroutine;
|
||||
|
||||
protected override void RetrieveServersImpl(Action<ServerInfo> onServerDataReceived, Action onQueryCompleted)
|
||||
{
|
||||
if (!SteamManager.IsInitialized)
|
||||
{
|
||||
onQueryCompleted();
|
||||
return;
|
||||
}
|
||||
|
||||
// All lambdas in here must only capture this call's
|
||||
// query, not the provider's latest query
|
||||
var selfServerQuery = new Steamworks.ServerList.Internet();
|
||||
serverQuery = selfServerQuery;
|
||||
|
||||
ConcurrentQueue<Steamworks.Data.ServerInfo> responsiveServers =
|
||||
new ConcurrentQueue<Steamworks.Data.ServerInfo>();
|
||||
ConcurrentQueue<Steamworks.Data.ServerInfo> unresponsiveServers =
|
||||
new ConcurrentQueue<Steamworks.Data.ServerInfo>();
|
||||
|
||||
selfServerQuery.OnResponsiveServer = responsiveServers.Enqueue;
|
||||
selfServerQuery.OnUnresponsiveServer = unresponsiveServers.Enqueue;
|
||||
|
||||
void dequeue(int? limit = null)
|
||||
{
|
||||
for (int i = 0; (!limit.HasValue || i < limit) && responsiveServers.TryDequeue(out var serverInfo); i++)
|
||||
{
|
||||
HandleResponsiveServer(serverInfo, onServerDataReceived);
|
||||
}
|
||||
|
||||
for (int i = 0; (!limit.HasValue || i < limit) && unresponsiveServers.TryDequeue(out var serverInfo); i++)
|
||||
{
|
||||
HandleUnresponsiveServer(serverInfo, onServerDataReceived);
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerable<CoroutineStatus> dequeueCoroutine()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
dequeue(limit: 20);
|
||||
yield return new WaitForSeconds(0.1f, ignorePause: true);
|
||||
}
|
||||
}
|
||||
var selfQueryCoroutine = CoroutineManager.StartCoroutine(dequeueCoroutine(),
|
||||
$"{nameof(SteamDedicatedServerProvider)}.{nameof(RetrieveServers)}.{nameof(dequeueCoroutine)}");
|
||||
queryCoroutine = selfQueryCoroutine;
|
||||
|
||||
TaskPool.Add("RunServerQuery", selfServerQuery.RunQueryAsync(timeoutSeconds: 30f),
|
||||
t =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// Clear the callbacks because it's too late now, we want to get this over with
|
||||
selfServerQuery.OnResponsiveServer = null;
|
||||
selfServerQuery.OnUnresponsiveServer = null;
|
||||
|
||||
CoroutineManager.StopCoroutines(selfQueryCoroutine);
|
||||
dequeue();
|
||||
|
||||
if (t.Status == TaskStatus.Faulted) { TaskPool.PrintTaskExceptions(t, "Failed to retrieve servers"); }
|
||||
|
||||
selfServerQuery.Dispose();
|
||||
}
|
||||
finally
|
||||
{
|
||||
onQueryCompleted();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public override void Cancel()
|
||||
{
|
||||
if (queryCoroutine != null) { CoroutineManager.StopCoroutines(queryCoroutine); }
|
||||
serverQuery?.Dispose();
|
||||
serverQuery = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Networking;
|
||||
using Barotrauma.Steam;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
sealed class SteamP2PServerProvider : ServerProvider
|
||||
{
|
||||
public class DataSource : ServerInfo.DataSource
|
||||
{
|
||||
public readonly Steamworks.Data.Lobby Lobby;
|
||||
|
||||
public override void Write(XElement element) { /* do nothing */ }
|
||||
|
||||
public DataSource(Steamworks.Data.Lobby lobby)
|
||||
{
|
||||
Lobby = lobby;
|
||||
}
|
||||
}
|
||||
|
||||
private object? queryRef = null;
|
||||
|
||||
protected override void RetrieveServersImpl(Action<ServerInfo> onServerDataReceived, Action onQueryCompleted)
|
||||
{
|
||||
if (!SteamManager.IsInitialized)
|
||||
{
|
||||
onQueryCompleted();
|
||||
return;
|
||||
}
|
||||
|
||||
// All lambdas and local methods in here must only capture
|
||||
// this call's query, not the provider's latest query
|
||||
var selfQueryRef = new object();
|
||||
queryRef = selfQueryRef;
|
||||
|
||||
Steamworks.Data.LobbyQuery lobbyQuery = Steamworks.SteamMatchmaking.CreateLobbyQuery()
|
||||
.FilterDistanceWorldwide()
|
||||
.WithMaxResults(50);
|
||||
// Steamworks is unable to retrieve more than 50 lobbies per request
|
||||
// (see https://partner.steamgames.com/doc/features/multiplayer/matchmaking#3)
|
||||
// To work around this, we'll make up to 10 requests, asking to ignore
|
||||
// all previous results in each subsequent request.
|
||||
#warning TODO: do something less horrible here?
|
||||
|
||||
int requestCount = 0;
|
||||
HashSet<SteamId> retrieved = new HashSet<SteamId>();
|
||||
|
||||
void startQuery()
|
||||
{
|
||||
if (requestCount >= 10) { return; }
|
||||
requestCount++;
|
||||
TaskPool.Add($"LobbyQuery.RequestAsync ({requestCount})", lobbyQuery.RequestAsync(), onRequestComplete);
|
||||
}
|
||||
|
||||
void onRequestComplete(Task t)
|
||||
{
|
||||
// If queryRef != selfQueryRef, this query was cancelled
|
||||
if (!ReferenceEquals(selfQueryRef, queryRef)) { return; }
|
||||
|
||||
if (!t.TryGetResult(out Steamworks.Data.Lobby[] lobbies)
|
||||
|| lobbies is null
|
||||
|| lobbies.Length == 0)
|
||||
{
|
||||
onQueryCompleted();
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var lobby in lobbies)
|
||||
{
|
||||
string lobbyOwnerStr = lobby.GetData("lobbyowner");
|
||||
lobbyQuery = lobbyQuery.WithoutKeyValue("lobbyowner", lobbyOwnerStr);
|
||||
|
||||
string serverName = lobby.GetData("name");
|
||||
if (string.IsNullOrEmpty(serverName)) { continue; }
|
||||
|
||||
var ownerId = SteamId.Parse(lobbyOwnerStr);
|
||||
if (!ownerId.TryUnwrap(out var lobbyOwnerId)) { continue; }
|
||||
|
||||
if (retrieved.Contains(lobbyOwnerId)) { continue; }
|
||||
retrieved.Add(lobbyOwnerId);
|
||||
|
||||
var serverInfo = new ServerInfo(new SteamP2PEndpoint(lobbyOwnerId))
|
||||
{
|
||||
ServerName = serverName,
|
||||
MetadataSource = Option<ServerInfo.DataSource>.Some(new DataSource(lobby))
|
||||
};
|
||||
serverInfo.UpdateInfo(key => lobby.GetData(key));
|
||||
serverInfo.Checked = true;
|
||||
|
||||
onServerDataReceived(serverInfo);
|
||||
}
|
||||
startQuery();
|
||||
}
|
||||
|
||||
startQuery();
|
||||
}
|
||||
|
||||
public override void Cancel()
|
||||
{
|
||||
queryRef = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -125,8 +125,6 @@ namespace Barotrauma.Networking
|
||||
|
||||
public void ClientRead(IReadMessage incMsg)
|
||||
{
|
||||
cachedServerListInfo = null;
|
||||
|
||||
NetFlags requiredFlags = (NetFlags)incMsg.ReadByte();
|
||||
|
||||
if (requiredFlags.HasFlag(NetFlags.Name))
|
||||
@@ -146,7 +144,6 @@ namespace Barotrauma.Networking
|
||||
AllowFileTransfers = incMsg.ReadBoolean();
|
||||
incMsg.ReadPadBits();
|
||||
TickRate = incMsg.ReadRangedInteger(1, 60);
|
||||
GameMain.NetworkMember.TickRate = TickRate;
|
||||
|
||||
if (requiredFlags.HasFlag(NetFlags.Properties))
|
||||
{
|
||||
@@ -241,7 +238,8 @@ namespace Barotrauma.Networking
|
||||
|
||||
outMsg.WriteSingle(levelDifficulty ?? -1000.0f);
|
||||
|
||||
outMsg.WriteBoolean(useRespawnShuttle ?? UseRespawnShuttle);
|
||||
outMsg.WriteBoolean(useRespawnShuttle != null);
|
||||
outMsg.WriteBoolean(useRespawnShuttle ?? false);
|
||||
|
||||
outMsg.WriteBoolean(autoRestart != null);
|
||||
outMsg.WriteBoolean(autoRestart ?? false);
|
||||
@@ -1057,15 +1055,7 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
settingsFrame = null;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private ServerInfo cachedServerListInfo = null;
|
||||
public ServerInfo GetServerListInfo()
|
||||
{
|
||||
cachedServerListInfo ??= GameMain.ServerListScreen.UpdateServerInfoWithServerSettings(GameMain.Client.ClientPeer.ServerConnection, this);
|
||||
return cachedServerListInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
using System;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -111,6 +112,23 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetVotes(IEnumerable<Client> connectedClients)
|
||||
{
|
||||
foreach (Client client in connectedClients)
|
||||
{
|
||||
client.ResetVotes();
|
||||
}
|
||||
|
||||
foreach (VoteType voteType in Enum.GetValues(typeof(VoteType)))
|
||||
{
|
||||
SetVoteCountYes(voteType, 0);
|
||||
SetVoteCountNo(voteType, 0);
|
||||
SetVoteCountMax(voteType, 0);
|
||||
}
|
||||
UpdateVoteTexts(connectedClients, VoteType.Mode);
|
||||
UpdateVoteTexts(connectedClients, VoteType.Sub);
|
||||
}
|
||||
|
||||
public void ClientWrite(IWriteMessage msg, VoteType voteType, object data)
|
||||
{
|
||||
msg.WriteByte((byte)voteType);
|
||||
|
||||
@@ -67,6 +67,8 @@ namespace Barotrauma
|
||||
|
||||
public static readonly Queue<ulong> WorkshopItemsToUpdate = new Queue<ulong>();
|
||||
|
||||
private readonly GUIListBox tutorialList;
|
||||
|
||||
#region Creation
|
||||
public MainMenuScreen(GameMain game)
|
||||
{
|
||||
@@ -429,26 +431,19 @@ namespace Barotrauma
|
||||
menuTabs[Tab.Tutorials] = new GUIFrame(new RectTransform(relativeSize, GUI.Canvas, anchor, pivot, minSize, maxSize) { RelativeOffset = relativeSpacing });
|
||||
|
||||
//PLACEHOLDER
|
||||
var tutorialList = new GUIListBox(
|
||||
tutorialList = new GUIListBox(
|
||||
new RectTransform(new Vector2(0.95f, 0.85f), menuTabs[Tab.Tutorials].RectTransform, Anchor.TopCenter) { RelativeOffset = new Vector2(0.0f, 0.1f) })
|
||||
{
|
||||
PlaySoundOnSelect = true,
|
||||
};
|
||||
foreach (var tutorialPrefab in TutorialPrefab.Prefabs.OrderBy(p => p.Order))
|
||||
{
|
||||
var tutorial = new Tutorial(tutorialPrefab);
|
||||
var tutorialText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.15f), tutorialList.Content.RectTransform), tutorial.DisplayName, textAlignment: Alignment.Center, font: GUIStyle.LargeFont)
|
||||
{
|
||||
TextColor = GUIStyle.Green,
|
||||
UserData = tutorial
|
||||
};
|
||||
}
|
||||
tutorialList.OnSelected += (component, obj) =>
|
||||
{
|
||||
(obj as Tutorial)?.Start();
|
||||
return true;
|
||||
};
|
||||
|
||||
CreateTutorialButtons();
|
||||
|
||||
this.game = game;
|
||||
|
||||
menuTabs[Tab.Credits] = new GUIFrame(new RectTransform(Vector2.One, GUI.Canvas, Anchor.Center), style: null)
|
||||
@@ -463,7 +458,28 @@ namespace Barotrauma
|
||||
var creditsContainer = new GUIFrame(new RectTransform(new Vector2(0.75f, 1.5f), menuTabs[Tab.Credits].RectTransform, Anchor.CenterRight), style: "OuterGlow", color: Color.Black * 0.8f);
|
||||
creditsPlayer = new CreditsPlayer(new RectTransform(Vector2.One, creditsContainer.RectTransform), "Content/Texts/Credits.xml");
|
||||
}
|
||||
#endregion
|
||||
|
||||
private void CreateTutorialButtons()
|
||||
{
|
||||
foreach (var tutorialPrefab in TutorialPrefab.Prefabs.OrderBy(p => p.Order))
|
||||
{
|
||||
var tutorial = new Tutorial(tutorialPrefab);
|
||||
var tutorialText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.15f), tutorialList.Content.RectTransform), tutorial.DisplayName, textAlignment: Alignment.Center, font: GUIStyle.LargeFont)
|
||||
{
|
||||
TextColor = GUIStyle.Green,
|
||||
UserData = tutorial
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static void UpdateInstanceTutorialButtons()
|
||||
{
|
||||
if (GameMain.MainMenuScreen is not MainMenuScreen menuScreen) { return; }
|
||||
menuScreen.tutorialList.ClearChildren();
|
||||
menuScreen.CreateTutorialButtons();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Selection
|
||||
public override void Select()
|
||||
@@ -1117,7 +1133,7 @@ namespace Barotrauma
|
||||
var playstyleContainer = new GUIFrame(new RectTransform(new Vector2(1.35f, 0.1f), parent.RectTransform), style: null, color: Color.Black);
|
||||
|
||||
playstyleBanner = new GUIImage(new RectTransform(new Vector2(1.0f, 0.1f), playstyleContainer.RectTransform),
|
||||
ServerListScreen.PlayStyleBanners[0], scaleToFit: true)
|
||||
GUIStyle.GetComponentStyle($"PlayStyleBanner.{PlayStyle.Serious}").GetSprite(GUIComponent.ComponentState.None), scaleToFit: true)
|
||||
{
|
||||
UserData = PlayStyle.Serious
|
||||
};
|
||||
@@ -1336,12 +1352,15 @@ namespace Barotrauma
|
||||
|
||||
private void SetServerPlayStyle(PlayStyle playStyle)
|
||||
{
|
||||
playstyleBanner.Sprite = ServerListScreen.PlayStyleBanners[(int)playStyle];
|
||||
playstyleBanner.Sprite = GUIStyle
|
||||
.GetComponentStyle($"PlayStyleBanner.{playStyle}")
|
||||
.GetSprite(GUIComponent.ComponentState.None);
|
||||
playstyleBanner.UserData = playStyle;
|
||||
|
||||
var nameText = playstyleBanner.GetChild<GUITextBlock>();
|
||||
nameText.Text = TextManager.AddPunctuation(':', TextManager.Get("serverplaystyle"), TextManager.Get("servertag." + playStyle));
|
||||
nameText.Color = ServerListScreen.PlayStyleColors[(int)playStyle];
|
||||
nameText.Color = playstyleBanner.Sprite
|
||||
.SourceElement.GetAttributeColor("BannerColor") ?? Color.White;
|
||||
nameText.RectTransform.NonScaledSize = (nameText.Font.MeasureString(nameText.Text) + new Vector2(25, 10) * GUI.Scale).ToPoint();
|
||||
|
||||
playstyleDescription.Text = TextManager.Get("servertagdescription." + playStyle);
|
||||
|
||||
@@ -92,7 +92,7 @@ namespace Barotrauma
|
||||
|
||||
var missingPackages = GameMain.Client.ClientPeer.ServerContentPackages
|
||||
.Where(sp => sp.ContentPackage is null).ToArray();
|
||||
if (!missingPackages.Any())
|
||||
if (!missingPackages.Any(p => p.IsMandatory))
|
||||
{
|
||||
if (!GameMain.Client.IsServerOwner)
|
||||
{
|
||||
@@ -106,7 +106,7 @@ namespace Barotrauma
|
||||
.Select(p => p.RegularPackage)
|
||||
.OfType<RegularPackage>().ToList();
|
||||
//keep enabled client-side-only mods enabled
|
||||
regularPackages.AddRange(ContentPackageManager.EnabledPackages.Regular.Where(p => !p.HasMultiplayerSyncedContent));
|
||||
regularPackages.AddRange(ContentPackageManager.EnabledPackages.Regular.Where(p => !p.HasMultiplayerSyncedContent && !regularPackages.Contains(p)));
|
||||
ContentPackageManager.EnabledPackages.SetRegular(regularPackages);
|
||||
}
|
||||
GameMain.NetLobbyScreen.Select();
|
||||
@@ -177,11 +177,11 @@ namespace Barotrauma
|
||||
});
|
||||
buttonContainerSpacing(0.1f);
|
||||
|
||||
var missingIds = missingPackages.Where(
|
||||
mp => mp.WorkshopId != 0
|
||||
&& ContentPackageManager.WorkshopPackages.All(wp
|
||||
=> wp.SteamWorkshopId != mp.WorkshopId))
|
||||
.Select(mp => mp.WorkshopId)
|
||||
var missingIds = missingPackages
|
||||
.Where(p => p.IsMandatory)
|
||||
.Select(mp => ContentPackageId.Parse(mp.UgcId))
|
||||
.NotNone()
|
||||
.Where(id => ContentPackageManager.WorkshopPackages.All(wp => !wp.UgcId.Equals(id)))
|
||||
.ToArray();
|
||||
if (missingIds.Any() && SteamManager.IsInitialized)
|
||||
{
|
||||
@@ -191,7 +191,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (GameMain.Client != null)
|
||||
{
|
||||
BulkDownloader.SubscribeToServerMods(missingIds,
|
||||
BulkDownloader.SubscribeToServerMods(missingIds.OfType<SteamWorkshopId>().Select(id => id.Value),
|
||||
new ConnectCommand(
|
||||
serverName: GameMain.Client.ServerName,
|
||||
endpoint: GameMain.Client.ClientPeer.ServerEndpoint));
|
||||
@@ -202,7 +202,7 @@ namespace Barotrauma
|
||||
buttonContainerSpacing(0.15f);
|
||||
}
|
||||
|
||||
foreach (var p in missingPackages)
|
||||
foreach (var p in missingPackages.Where(p => p.IsMandatory))
|
||||
{
|
||||
pendingDownloads.Enqueue(p);
|
||||
|
||||
@@ -294,26 +294,50 @@ namespace Barotrauma
|
||||
?? serverPackages.FirstOrDefault(p => p.CorePackage != null)
|
||||
?.CorePackage
|
||||
?? throw new Exception($"Failed to find core package to enable");
|
||||
List<RegularPackage> regularPackages
|
||||
= serverPackages.Where(p => p.CorePackage is null)
|
||||
.Select(p =>
|
||||
p.RegularPackage
|
||||
?? downloadedPackages.FirstOrDefault(d => d is RegularPackage && d.Hash.Equals(p.Hash))
|
||||
?? throw new Exception($"Could not find regular package \"{p.Name}\""))
|
||||
.Cast<RegularPackage>()
|
||||
.ToList();
|
||||
|
||||
List<RegularPackage> regularPackages = new List<RegularPackage>();
|
||||
foreach (var p in serverPackages)
|
||||
{
|
||||
if (p.CorePackage != null) { continue; }
|
||||
RegularPackage? matchingPackage =
|
||||
p.RegularPackage ?? downloadedPackages.FirstOrDefault(d => d is RegularPackage && d.Hash.Equals(p.Hash)) as RegularPackage;
|
||||
if (matchingPackage is null)
|
||||
{
|
||||
if (!p.IsMandatory)
|
||||
{
|
||||
//we don't need to care about missing non-mandatory (= submarine) mods
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"Could not find regular package \"{p.Name}\"");
|
||||
}
|
||||
}
|
||||
regularPackages.Add(matchingPackage);
|
||||
}
|
||||
foreach (var regularPackage in regularPackages)
|
||||
{
|
||||
DebugConsole.NewMessage($"Enabling \"{regularPackage.Name}\" ({regularPackage.Dir})", Color.Lime);
|
||||
}
|
||||
|
||||
//keep enabled client-side-only mods enabled
|
||||
regularPackages.AddRange(ContentPackageManager.EnabledPackages.Regular.Where(p => !p.HasMultiplayerSyncedContent));
|
||||
regularPackages.AddRange(ContentPackageManager.EnabledPackages.Regular.Where(p => !p.HasMultiplayerSyncedContent && !regularPackages.Contains(p)));
|
||||
|
||||
ContentPackageManager.EnabledPackages.BackUp();
|
||||
ContentPackageManager.EnabledPackages.SetCore(corePackage);
|
||||
ContentPackageManager.EnabledPackages.SetRegular(regularPackages);
|
||||
|
||||
//see if any of the packages we enabled contain subs that we were missing previously, and update their paths
|
||||
foreach (var serverSub in GameMain.Client.ServerSubmarines)
|
||||
{
|
||||
if (File.Exists(serverSub.FilePath)) { continue; }
|
||||
var matchingSub = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == serverSub.Name && s.MD5Hash == serverSub.MD5Hash);
|
||||
if (matchingSub != null)
|
||||
{
|
||||
serverSub.FilePath = matchingSub.FilePath;
|
||||
}
|
||||
}
|
||||
GameMain.NetLobbyScreen.UpdateSubList(GameMain.NetLobbyScreen.SubList, GameMain.Client.ServerSubmarines);
|
||||
GameMain.NetLobbyScreen.Select();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -666,7 +666,7 @@ namespace Barotrauma
|
||||
OnSelected = (tickbox) =>
|
||||
{
|
||||
if (GameMain.Client == null) { return true; }
|
||||
ServerInfo info = GameMain.Client.ServerSettings.GetServerListInfo();
|
||||
ServerInfo info = GameMain.Client.CreateServerInfoFromSettings();
|
||||
if (tickbox.Selected)
|
||||
{
|
||||
GameMain.ServerListScreen.AddToFavoriteServers(info);
|
||||
@@ -1432,10 +1432,6 @@ namespace Barotrauma
|
||||
bool nameChangePending = isGameRunning && GameMain.Client.PendingName != string.Empty && GameMain.Client?.Character?.Name != GameMain.Client.PendingName;
|
||||
changesPendingText = null;
|
||||
|
||||
if (isGameRunning)
|
||||
{
|
||||
infoContainer.RectTransform.AbsoluteOffset = new Point(0, (int)(parent.Rect.Height * 0.025f));
|
||||
}
|
||||
|
||||
if (TabMenu.PendingChanges)
|
||||
{
|
||||
@@ -1454,7 +1450,6 @@ namespace Barotrauma
|
||||
{
|
||||
if (GameMain.Client == null) { return; }
|
||||
string newName = Client.SanitizeName(tb.Text);
|
||||
newName = newName.Replace(":", "").Replace(";", "");
|
||||
if (newName == GameMain.Client.Name) return;
|
||||
if (string.IsNullOrWhiteSpace(newName))
|
||||
{
|
||||
@@ -1782,6 +1777,10 @@ namespace Barotrauma
|
||||
|
||||
// Hide spectate tickbox if spectating is not allowed
|
||||
spectateBox.Visible = allowSpectating;
|
||||
if (infoContainer != null)
|
||||
{
|
||||
infoContainer.RectTransform.RelativeSize = new Vector2(infoContainer.RectTransform.RelativeSize.X, spectateBox.Visible ? 0.92f : 0.97f);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetAutoRestart(bool enabled, float timer = 0.0f)
|
||||
@@ -2753,25 +2752,24 @@ namespace Barotrauma
|
||||
if (GameMain.NetworkMember?.ServerSettings == null) { return; }
|
||||
|
||||
PlayStyle playStyle = GameMain.NetworkMember.ServerSettings.PlayStyle;
|
||||
if ((int)playStyle < 0 ||
|
||||
(int)playStyle >= ServerListScreen.PlayStyleBanners.Length)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Sprite sprite = ServerListScreen.PlayStyleBanners[(int)playStyle];
|
||||
Sprite sprite = GUIStyle
|
||||
.GetComponentStyle($"PlayStyleBanner.{playStyle}")?
|
||||
.GetSprite(GUIComponent.ComponentState.None);
|
||||
if (sprite is null) { return; }
|
||||
|
||||
float scale = component.Rect.Width / sprite.size.X;
|
||||
sprite.Draw(spriteBatch, component.Center, scale: scale);
|
||||
|
||||
if (!prevPlayStyle.HasValue || playStyle != prevPlayStyle.Value)
|
||||
{
|
||||
var nameText = component.GetChild<GUITextBlock>();
|
||||
nameText.Text = TextManager.Get("servertag." + playStyle);
|
||||
nameText.Color = ServerListScreen.PlayStyleColors[(int)playStyle];
|
||||
nameText.Text = TextManager.Get($"ServerTag.{playStyle}");
|
||||
nameText.Color = sprite.SourceElement.GetAttributeColor("BannerColor") ?? Color.White;
|
||||
nameText.RectTransform.NonScaledSize = (nameText.Font.MeasureString(nameText.Text) + new Vector2(25, 10) * GUI.Scale).ToPoint();
|
||||
prevPlayStyle = playStyle;
|
||||
|
||||
component.ToolTip = TextManager.Get("servertagdescription." + playStyle);
|
||||
component.ToolTip = TextManager.Get($"ServerTagDescription.{playStyle}");
|
||||
}
|
||||
|
||||
publicOrPrivate.RectTransform.NonScaledSize = (publicOrPrivate.Font.MeasureString(publicOrPrivate.Text) + new Vector2(25, 8) * GUI.Scale).ToPoint();
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,112 @@
|
||||
using System;
|
||||
using Barotrauma.Extensions;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class PanelAnimator
|
||||
{
|
||||
private readonly GUIScissorComponent container;
|
||||
|
||||
private readonly GUIFrame leftFrame;
|
||||
private readonly GUIComponent middleFrame;
|
||||
private readonly GUIFrame rightFrame;
|
||||
|
||||
private readonly GUIButton leftButton;
|
||||
private readonly GUIButton rightButton;
|
||||
|
||||
private float leftAnimState = 1.0f;
|
||||
private float rightAnimState = 0.0f;
|
||||
|
||||
public bool LeftEnabled
|
||||
{
|
||||
get => leftButton.Enabled;
|
||||
set => leftButton.Enabled = value;
|
||||
}
|
||||
public bool RightEnabled
|
||||
{
|
||||
get => rightButton.Enabled;
|
||||
set => rightButton.Enabled = value;
|
||||
}
|
||||
|
||||
public bool LeftVisible = true;
|
||||
public bool RightVisible = false;
|
||||
|
||||
public PanelAnimator(RectTransform rectTransform, GUIFrame leftFrame, GUIComponent middleFrame, GUIFrame rightFrame)
|
||||
{
|
||||
container = new GUIScissorComponent(rectTransform);
|
||||
|
||||
this.leftFrame = leftFrame;
|
||||
this.middleFrame = middleFrame;
|
||||
this.rightFrame = rightFrame;
|
||||
|
||||
void own(GUIComponent component)
|
||||
{
|
||||
component.RectTransform.Parent = container.Content.RectTransform;
|
||||
component.RectTransform.Anchor = Anchor.TopLeft;
|
||||
component.RectTransform.Pivot = Pivot.TopLeft;
|
||||
|
||||
component.GetAllChildren<GUIDropDown>().ForEach(dd => dd.RefreshListBoxParent());
|
||||
}
|
||||
|
||||
GUIButton makeButton(Action action)
|
||||
=> new GUIButton(new RectTransform(new Vector2(0.01f, 1.0f), container.Content.RectTransform)
|
||||
{ MinSize = new Point(20, 0), MaxSize = new Point(int.MaxValue, (int)(150 * GUI.Scale)) },
|
||||
style: "UIToggleButton")
|
||||
{
|
||||
OnClicked = (_, __) =>
|
||||
{
|
||||
action();
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
own(leftFrame);
|
||||
this.leftButton = makeButton(() => LeftVisible = !LeftVisible);
|
||||
|
||||
own(middleFrame);
|
||||
|
||||
this.rightButton = makeButton(() => RightVisible = !RightVisible);
|
||||
own(rightFrame);
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
if (!LeftEnabled) { LeftVisible = false; }
|
||||
if (!RightEnabled) { RightVisible = false; }
|
||||
|
||||
static void updateState(ref float state, bool visible)
|
||||
=> state = MathHelper.Lerp(state, visible ? 0.0f : 1.0f, 0.5f);
|
||||
updateState(ref leftAnimState, LeftVisible);
|
||||
updateState(ref rightAnimState, RightVisible);
|
||||
|
||||
static int width(GUIComponent c)
|
||||
=> c.RectTransform.NonScaledSize.X;
|
||||
|
||||
int height = container.RectTransform.NonScaledSize.Y;
|
||||
int buttonY = height/2 - leftButton.RectTransform.NonScaledSize.Y/2;
|
||||
|
||||
leftFrame.RectTransform.AbsoluteOffset = new Point((int)(-width(leftFrame) * leftAnimState), 0);
|
||||
leftButton.RectTransform.AbsoluteOffset = leftFrame.RectTransform.AbsoluteOffset
|
||||
+ new Point(width(leftFrame), buttonY);
|
||||
leftButton.Children.ForEach(c => c.SpriteEffects = LeftVisible
|
||||
? SpriteEffects.FlipHorizontally
|
||||
: SpriteEffects.None);
|
||||
|
||||
rightFrame.RectTransform.AbsoluteOffset = new Point((int)(width(container) + width(rightFrame) * (rightAnimState-1f)), 0);
|
||||
rightButton.RectTransform.AbsoluteOffset = rightFrame.RectTransform.AbsoluteOffset
|
||||
+ new Point(-width(rightButton), buttonY);
|
||||
rightButton.Children.ForEach(c => c.SpriteEffects = RightVisible
|
||||
? SpriteEffects.None
|
||||
: SpriteEffects.FlipHorizontally);
|
||||
|
||||
middleFrame.RectTransform.AbsoluteOffset = new Point(
|
||||
leftButton.RectTransform.AbsoluteOffset.X + width(leftButton),
|
||||
0);
|
||||
middleFrame.RectTransform.NonScaledSize = new Point(
|
||||
rightButton.RectTransform.AbsoluteOffset.X - middleFrame.RectTransform.AbsoluteOffset.X,
|
||||
height);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1547,6 +1547,8 @@ namespace Barotrauma
|
||||
|
||||
GUI.ForceMouseOn(null);
|
||||
|
||||
if (ImageManager.EditorMode) { GameSettings.SaveCurrentConfig(); }
|
||||
|
||||
MapEntityPrefab.Selected = null;
|
||||
|
||||
saveFrame = null;
|
||||
@@ -1797,25 +1799,14 @@ namespace Barotrauma
|
||||
{
|
||||
Type subFileType = DetermineSubFileType(MainSub?.Info.Type ?? SubmarineType.Player);
|
||||
|
||||
void addSubAndSaveModProject(ModProject modProject, string filePath, string packagePath)
|
||||
static string getExistingFilePath(ContentPackage package, string fileName)
|
||||
{
|
||||
filePath = filePath.CleanUpPath();
|
||||
packagePath = packagePath.CleanUpPath();
|
||||
string packageDir = Path.GetDirectoryName(packagePath).CleanUpPathCrossPlatform(correctFilenameCase: false);
|
||||
if (filePath.StartsWith(packageDir))
|
||||
if (Submarine.MainSub?.Info == null) { return null; }
|
||||
if (package.Files.Any(f => f.Path == MainSub.Info.FilePath && Path.GetFileName(f.Path.Value) == fileName))
|
||||
{
|
||||
filePath = $"{ContentPath.ModDirStr}/{filePath[packageDir.Length..]}";
|
||||
return MainSub.Info.FilePath;
|
||||
}
|
||||
if (!modProject.Files.Any(f => f.Type == subFileType &&
|
||||
f.Path == filePath))
|
||||
{
|
||||
var newFile = ModProject.File.FromPath(filePath, subFileType);
|
||||
modProject.AddFile(newFile);
|
||||
}
|
||||
|
||||
using var _ = Validation.SkipInDebugBuilds();
|
||||
modProject.DiscardHashAndInstallTime();
|
||||
modProject.Save(packagePath);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!GameMain.DebugDraw)
|
||||
@@ -1861,101 +1852,139 @@ namespace Barotrauma
|
||||
#if !DEBUG
|
||||
throw new InvalidOperationException("Cannot save to Vanilla package");
|
||||
#endif
|
||||
savePath = string.Format((MainSub?.Info.Type ?? SubmarineType.Player) switch
|
||||
{
|
||||
SubmarineType.Player => "Content/Submarines/{0}",
|
||||
SubmarineType.Outpost => "Content/Map/Outposts/{0}",
|
||||
SubmarineType.Ruin => "Content/Submarines/{0}", //we don't seem to use this anymore...
|
||||
SubmarineType.Wreck => "Content/Map/Wrecks/{0}",
|
||||
SubmarineType.BeaconStation => "Content/Map/BeaconStations/{0}",
|
||||
SubmarineType.EnemySubmarine => "Content/Map/EnemySubmarines/{0}",
|
||||
SubmarineType.OutpostModule => "Content/Map/Outposts/{0}",
|
||||
_ => throw new InvalidOperationException()
|
||||
}, savePath);
|
||||
savePath =
|
||||
getExistingFilePath(packageToSaveTo, savePath) ??
|
||||
string.Format((MainSub?.Info.Type ?? SubmarineType.Player) switch
|
||||
{
|
||||
SubmarineType.Player => "Content/Submarines/{0}",
|
||||
SubmarineType.Outpost => "Content/Map/Outposts/{0}",
|
||||
SubmarineType.Ruin => "Content/Submarines/{0}", //we don't seem to use this anymore...
|
||||
SubmarineType.Wreck => "Content/Map/Wrecks/{0}",
|
||||
SubmarineType.BeaconStation => "Content/Map/BeaconStations/{0}",
|
||||
SubmarineType.EnemySubmarine => "Content/Map/EnemySubmarines/{0}",
|
||||
SubmarineType.OutpostModule => "Content/Map/Outposts/{0}",
|
||||
_ => throw new InvalidOperationException()
|
||||
}, savePath);
|
||||
modProject.ModVersion = "";
|
||||
}
|
||||
else
|
||||
{
|
||||
savePath = Path.Combine(packageToSaveTo.Dir, savePath);
|
||||
string existingFilePath = getExistingFilePath(packageToSaveTo, savePath);
|
||||
//if we're trying to save a sub that's already included in the package with the same name as before, save directly in the same path
|
||||
if (existingFilePath != null)
|
||||
{
|
||||
savePath = existingFilePath;
|
||||
}
|
||||
//otherwise make sure we're not trying to overwrite another sub in the same package
|
||||
else
|
||||
{
|
||||
savePath = Path.Combine(packageToSaveTo.Dir, savePath);
|
||||
if (File.Exists(savePath))
|
||||
{
|
||||
var verification = new GUIMessageBox(TextManager.Get("warning"), TextManager.Get("subeditor.duplicatesubinpackage"),
|
||||
new LocalizedString[] { TextManager.Get("yes"), TextManager.Get("no") });
|
||||
verification.Buttons[0].OnClicked = (_, _) =>
|
||||
{
|
||||
addSubAndSave(modProject, savePath, fileListPath);
|
||||
verification.Close();
|
||||
return true;
|
||||
};
|
||||
verification.Buttons[1].OnClicked = verification.Close;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
addSubAndSaveModProject(modProject, savePath, fileListPath);
|
||||
}
|
||||
else if (MainSub?.Info?.FilePath != null
|
||||
&& MainSub.Info.Name != null
|
||||
&& MainSub.Info.FilePath.StartsWith(ContentPackage.LocalModsDir)
|
||||
&& MainSub.Info.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
prevSavePath = MainSub.Info.FilePath.CleanUpPath();
|
||||
ContentPackage contentPackage = GetLocalPackageThatOwnsSub(MainSub.Info);
|
||||
if (contentPackage == null)
|
||||
{
|
||||
throw new InvalidOperationException($"Tried to overwrite a submarine ({name}) that's not in a local package!");
|
||||
}
|
||||
ModProject modProject = new ModProject(contentPackage);
|
||||
packageToSaveTo = contentPackage;
|
||||
savePath = prevSavePath;
|
||||
addSubAndSaveModProject(modProject, savePath, contentPackage.Path);
|
||||
addSubAndSave(modProject, savePath, fileListPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
savePath = Path.Combine(newLocalModDir, savePath);
|
||||
ModProject modProject = new ModProject { Name = name };
|
||||
addSubAndSaveModProject(modProject, savePath, Path.Combine(Path.GetDirectoryName(savePath), ContentPackage.FileListFileName));
|
||||
}
|
||||
savePath = savePath.CleanUpPathCrossPlatform(correctFilenameCase: false);
|
||||
|
||||
if (MainSub != null)
|
||||
{
|
||||
Barotrauma.IO.Validation.SkipValidationInDebugBuilds = true;
|
||||
if (previewImage?.Sprite?.Texture != null && !previewImage.Sprite.Texture.IsDisposed && MainSub.Info.Type != SubmarineType.OutpostModule)
|
||||
if (File.Exists(savePath))
|
||||
{
|
||||
bool savePreviewImage = true;
|
||||
using System.IO.MemoryStream imgStream = new System.IO.MemoryStream();
|
||||
try
|
||||
{
|
||||
previewImage.Sprite.Texture.SaveAsPng(imgStream, previewImage.Sprite.Texture.Width, previewImage.Sprite.Texture.Height);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError($"Saving the preview image of the submarine \"{MainSub.Info.Name}\" failed.", e);
|
||||
savePreviewImage = false;
|
||||
}
|
||||
MainSub.TrySaveAs(savePath, savePreviewImage ? imgStream : null);
|
||||
new GUIMessageBox(TextManager.Get("warning"), TextManager.GetWithVariable("subeditor.packagealreadyexists", "[name]", name));
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
MainSub.TrySaveAs(savePath);
|
||||
ModProject modProject = new ModProject { Name = name };
|
||||
addSubAndSave(modProject, savePath, Path.Combine(Path.GetDirectoryName(savePath), ContentPackage.FileListFileName));
|
||||
}
|
||||
Barotrauma.IO.Validation.SkipValidationInDebugBuilds = false;
|
||||
}
|
||||
|
||||
MainSub.CheckForErrors();
|
||||
|
||||
GUI.AddMessage(TextManager.GetWithVariable("SubSavedNotification", "[filepath]", savePath), GUIStyle.Green);
|
||||
|
||||
if (savePath.StartsWith(newLocalModDir))
|
||||
void addSubAndSave(ModProject modProject, string filePath, string packagePath)
|
||||
{
|
||||
filePath = filePath.CleanUpPath();
|
||||
packagePath = packagePath.CleanUpPath();
|
||||
string packageDir = Path.GetDirectoryName(packagePath).CleanUpPathCrossPlatform(correctFilenameCase: false);
|
||||
if (filePath.StartsWith(packageDir))
|
||||
{
|
||||
ContentPackageManager.LocalPackages.Refresh();
|
||||
var newPackage = ContentPackageManager.LocalPackages.FirstOrDefault(p => p.Path.StartsWith(newLocalModDir));
|
||||
if (newPackage is RegularPackage regular)
|
||||
filePath = $"{ContentPath.ModDirStr}/{filePath[packageDir.Length..]}";
|
||||
}
|
||||
if (!modProject.Files.Any(f => f.Type == subFileType &&
|
||||
f.Path == filePath))
|
||||
{
|
||||
var newFile = ModProject.File.FromPath(filePath, subFileType);
|
||||
modProject.AddFile(newFile);
|
||||
}
|
||||
|
||||
using var _ = Validation.SkipInDebugBuilds();
|
||||
modProject.DiscardHashAndInstallTime();
|
||||
modProject.Save(packagePath);
|
||||
|
||||
savePath = savePath.CleanUpPathCrossPlatform(correctFilenameCase: false);
|
||||
if (MainSub != null)
|
||||
{
|
||||
Barotrauma.IO.Validation.SkipValidationInDebugBuilds = true;
|
||||
if (previewImage?.Sprite?.Texture != null && !previewImage.Sprite.Texture.IsDisposed && MainSub.Info.Type != SubmarineType.OutpostModule)
|
||||
{
|
||||
ContentPackageManager.EnabledPackages.EnableRegular(regular);
|
||||
GameSettings.SaveCurrentConfig();
|
||||
bool savePreviewImage = true;
|
||||
using System.IO.MemoryStream imgStream = new System.IO.MemoryStream();
|
||||
try
|
||||
{
|
||||
previewImage.Sprite.Texture.SaveAsPng(imgStream, previewImage.Sprite.Texture.Width, previewImage.Sprite.Texture.Height);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError($"Saving the preview image of the submarine \"{MainSub.Info.Name}\" failed.", e);
|
||||
savePreviewImage = false;
|
||||
}
|
||||
MainSub.TrySaveAs(savePath, savePreviewImage ? imgStream : null);
|
||||
}
|
||||
}
|
||||
if (packageToSaveTo != null) { ReloadModifiedPackage(packageToSaveTo); }
|
||||
SubmarineInfo.RefreshSavedSub(savePath);
|
||||
if (prevSavePath != null && prevSavePath != savePath) { SubmarineInfo.RefreshSavedSub(prevSavePath); }
|
||||
MainSub.Info.PreviewImage = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.FilePath == savePath)?.PreviewImage;
|
||||
else
|
||||
{
|
||||
MainSub.TrySaveAs(savePath);
|
||||
}
|
||||
Barotrauma.IO.Validation.SkipValidationInDebugBuilds = false;
|
||||
|
||||
string downloadFolder = Path.GetFullPath(SaveUtil.SubmarineDownloadFolder);
|
||||
linkedSubBox.ClearChildren();
|
||||
foreach (SubmarineInfo sub in SubmarineInfo.SavedSubmarines)
|
||||
{
|
||||
if (sub.Type != SubmarineType.Player) { continue; }
|
||||
if (Path.GetDirectoryName(Path.GetFullPath(sub.FilePath)) == downloadFolder) { continue; }
|
||||
linkedSubBox.AddItem(sub.Name, sub);
|
||||
MainSub.CheckForErrors();
|
||||
|
||||
GUI.AddMessage(TextManager.GetWithVariable("SubSavedNotification", "[filepath]", savePath), GUIStyle.Green);
|
||||
|
||||
if (savePath.StartsWith(newLocalModDir))
|
||||
{
|
||||
ContentPackageManager.LocalPackages.Refresh();
|
||||
var newPackage = ContentPackageManager.LocalPackages.FirstOrDefault(p => p.Path.StartsWith(newLocalModDir));
|
||||
if (newPackage is RegularPackage regular)
|
||||
{
|
||||
ContentPackageManager.EnabledPackages.EnableRegular(regular);
|
||||
GameSettings.SaveCurrentConfig();
|
||||
}
|
||||
}
|
||||
if (packageToSaveTo != null) { ReloadModifiedPackage(packageToSaveTo); }
|
||||
SubmarineInfo.RefreshSavedSub(savePath);
|
||||
if (prevSavePath != null && prevSavePath != savePath) { SubmarineInfo.RefreshSavedSub(prevSavePath); }
|
||||
MainSub.Info.PreviewImage = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.FilePath == savePath)?.PreviewImage;
|
||||
|
||||
string downloadFolder = Path.GetFullPath(SaveUtil.SubmarineDownloadFolder);
|
||||
linkedSubBox.ClearChildren();
|
||||
foreach (SubmarineInfo sub in SubmarineInfo.SavedSubmarines)
|
||||
{
|
||||
if (sub.Type != SubmarineType.Player) { continue; }
|
||||
if (Path.GetDirectoryName(Path.GetFullPath(sub.FilePath)) == downloadFolder) { continue; }
|
||||
linkedSubBox.AddItem(sub.Name, sub);
|
||||
}
|
||||
subNameLabel.Text = ToolBox.LimitString(MainSub.Info.Name, subNameLabel.Font, subNameLabel.Rect.Width);
|
||||
}
|
||||
subNameLabel.Text = ToolBox.LimitString(MainSub.Info.Name, subNameLabel.Font, subNameLabel.Rect.Width);
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -2735,40 +2764,31 @@ namespace Barotrauma
|
||||
new GUICustomComponent(new RectTransform(Vector2.Zero, saveInPackageLayout.RectTransform),
|
||||
onUpdate: (f, component) =>
|
||||
{
|
||||
bool canCreateNewPackage = true;
|
||||
foreach (GUIComponent contentChild in packageToSaveInList.Content.Children)
|
||||
{
|
||||
contentChild.Visible = !(contentChild.UserData is ContentPackage p)
|
||||
|| !string.Equals(p.Name, nameBox.Text, StringComparison.OrdinalIgnoreCase);
|
||||
canCreateNewPackage &= contentChild.Visible;
|
||||
contentChild.Visible &= !(contentChild.GetChild<GUILayoutGroup>()?.GetChild<GUITextBlock>() is GUITextBlock tb &&
|
||||
!tb.Text.Contains(packToSaveInFilter.Text, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
if (newPackageListIcon.Style.Identifier != "NewContentPackageIcon" && canCreateNewPackage)
|
||||
{
|
||||
GUIStyle.Apply(newPackageListIcon, "NewContentPackageIcon");
|
||||
newPackageListText.Text = TextManager.Get("CreateNewLocalPackage");
|
||||
}
|
||||
if (newPackageListIcon.Style.Identifier != "WorkshopMenu.EditButton" && !canCreateNewPackage)
|
||||
{
|
||||
GUIStyle.Apply(newPackageListIcon, "WorkshopMenu.EditButton");
|
||||
newPackageListText.Text = TextManager.GetWithVariable("UpdateExistingLocalPackage", "[mod]", nameBox.Text);
|
||||
}
|
||||
});
|
||||
packageToSaveInList.Select(0);
|
||||
ContentPackage ownerPkg = null;
|
||||
if (MainSub?.Info != null) { ownerPkg = GetLocalPackageThatOwnsSub(MainSub.Info); }
|
||||
foreach (var p in ContentPackageManager.LocalPackages)
|
||||
{
|
||||
addItemToPackageToSaveList(p.Name, p);
|
||||
var packageListItem = addItemToPackageToSaveList(p.Name, p);
|
||||
if (p == ownerPkg)
|
||||
{
|
||||
var packageListIcon = packageListItem.GetChild<GUIFrame>();
|
||||
var packageListText = packageListItem.GetChild<GUITextBlock>();
|
||||
GUIStyle.Apply(packageListIcon, "WorkshopMenu.EditButton");
|
||||
packageListText.Text = TextManager.GetWithVariable("UpdateExistingLocalPackage", "[mod]", p.Name);
|
||||
}
|
||||
}
|
||||
|
||||
if (ownerPkg != null && !string.Equals(ownerPkg.Name, nameBox.Text, StringComparison.OrdinalIgnoreCase))
|
||||
if (ownerPkg != null)
|
||||
{
|
||||
packageToSaveInList.Select(ownerPkg);
|
||||
packageToSaveInList.ScrollToElement(packageToSaveInList.SelectedComponent);
|
||||
var element = packageToSaveInList.Content.FindChild(ownerPkg);
|
||||
element?.RectTransform.SetAsFirstChild();
|
||||
}
|
||||
packageToSaveInList.Select(0);
|
||||
|
||||
var requiredContentPackagesLayout = new GUILayoutGroup(new RectTransform(Vector2.One,
|
||||
horizontalArea.RectTransform, Anchor.BottomRight))
|
||||
@@ -3424,7 +3444,8 @@ namespace Barotrauma
|
||||
{
|
||||
if (GetWorkshopPackageThatOwnsSub(selectedSubInfo) is ContentPackage workshopPackage)
|
||||
{
|
||||
if (publishedWorkshopItemIds.Contains(workshopPackage.SteamWorkshopId))
|
||||
if (workshopPackage.TryExtractSteamWorkshopId(out var workshopId)
|
||||
&& publishedWorkshopItemIds.Contains(workshopId.Value))
|
||||
{
|
||||
AskLoadPublishedSub(selectedSubInfo, workshopPackage);
|
||||
}
|
||||
|
||||
@@ -380,6 +380,11 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
LocalizedString toolTip = TextManager.Get($"sp.{propertyTag}.description");
|
||||
if (toolTip.IsNullOrEmpty() && entity.GetType() != property.PropertyInfo.DeclaringType)
|
||||
{
|
||||
Identifier propertyTagForDerivedClass = $"{entity.GetType().Name}.{property.PropertyInfo.Name}".ToIdentifier();
|
||||
toolTip = TextManager.Get($"{propertyTagForDerivedClass}.description", $"sp.{propertyTagForDerivedClass}.description");
|
||||
}
|
||||
if (toolTip.IsNullOrEmpty())
|
||||
{
|
||||
toolTip = TextManager.Get($"{propertyTag}.description", $"sp.{fallbackTag}.description");
|
||||
|
||||
@@ -127,15 +127,22 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public void ReloadTexture(bool updateAllSprites = false) => ReloadTexture(updateAllSprites ? LoadedSprites.Where(s => s.texture == texture).ToList() : new List<Sprite>() { this });
|
||||
|
||||
public void ReloadTexture(IEnumerable<Sprite> spritesToUpdate)
|
||||
public void ReloadTexture()
|
||||
{
|
||||
var oldTexture = texture;
|
||||
texture.Dispose();
|
||||
texture = TextureLoader.FromFile(FilePath.Value, Compress);
|
||||
foreach (Sprite sprite in spritesToUpdate)
|
||||
Identifier pathKey = FullPath.ToIdentifier();
|
||||
if (textureRefCounts.ContainsKey(pathKey))
|
||||
{
|
||||
sprite.texture = texture;
|
||||
textureRefCounts[pathKey].Texture = texture;
|
||||
}
|
||||
foreach (Sprite sprite in LoadedSprites)
|
||||
{
|
||||
if (sprite.texture == oldTexture)
|
||||
{
|
||||
sprite.texture = texture;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -70,7 +70,9 @@ namespace Barotrauma.Steam
|
||||
(ContentPackage Package, bool IsUpToDate)[] outOfDatePackages = await Task.WhenAll(determiningTasks);
|
||||
|
||||
return (await Task.WhenAll(outOfDatePackages.Where(p => !p.IsUpToDate)
|
||||
.Select(async p => await SteamManager.Workshop.GetItem(p.Package.SteamWorkshopId))))
|
||||
.Select(p => p.Package.UgcId)
|
||||
.OfType<SteamWorkshopId>()
|
||||
.Select(async id => await SteamManager.Workshop.GetItem(id.Value))))
|
||||
.Where(p => p.HasValue).Select(p => p ?? default).ToArray();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -102,7 +100,7 @@ namespace Barotrauma.Steam
|
||||
|
||||
currentLobby?.SetData("contentpackage", string.Join(",", contentPackages.Select(cp => cp.Name)));
|
||||
currentLobby?.SetData("contentpackagehash", string.Join(",", contentPackages.Select(cp => cp.Hash.StringRepresentation)));
|
||||
currentLobby?.SetData("contentpackageid", string.Join(",", contentPackages.Select(cp => cp.SteamWorkshopId)));
|
||||
currentLobby?.SetData("contentpackageid", string.Join(",", contentPackages.Select(cp => cp.UgcId)));
|
||||
currentLobby?.SetData("modeselectionmode", serverSettings.ModeSelectionMode.ToString());
|
||||
currentLobby?.SetData("subselectionmode", serverSettings.SubSelectionMode.ToString());
|
||||
currentLobby?.SetData("voicechatenabled", serverSettings.VoiceChatEnabled.ToString());
|
||||
@@ -152,275 +150,5 @@ namespace Barotrauma.Steam
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static bool GetServers(Action<ServerInfo> addToServerList, Action serverQueryFinished)
|
||||
{
|
||||
if (!IsInitialized) { return false; }
|
||||
|
||||
int doneTasks = 0;
|
||||
void taskDone()
|
||||
{
|
||||
doneTasks++;
|
||||
if (doneTasks >= 2)
|
||||
{
|
||||
serverQueryFinished?.Invoke();
|
||||
serverQueryFinished = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Steamworks.Dispatch.OnDebugCallback = (callbackType, contents, isServer) =>
|
||||
{
|
||||
DebugConsole.NewMessage($"{callbackType}: " + contents, Color.Yellow);
|
||||
};
|
||||
|
||||
TaskPool.Add("LobbyQueryRequest", LobbyQueryRequest(),
|
||||
(t) =>
|
||||
{
|
||||
Steamworks.Dispatch.OnDebugCallback = null;
|
||||
if (t.Status == TaskStatus.Faulted)
|
||||
{
|
||||
TaskPool.PrintTaskExceptions(t, "Failed to retrieve SteamP2P lobbies");
|
||||
taskDone();
|
||||
return;
|
||||
}
|
||||
var lobbies = ((Task<List<Steamworks.Data.Lobby>>)t).Result;
|
||||
if (lobbies != null)
|
||||
{
|
||||
foreach (var lobby in lobbies)
|
||||
{
|
||||
if (string.IsNullOrEmpty(lobby.GetData("name"))) { continue; }
|
||||
|
||||
ServerInfo serverInfo = new ServerInfo
|
||||
{
|
||||
ServerName = lobby.GetData("name"),
|
||||
Endpoint = new SteamP2PEndpoint(SteamId.Parse(lobby.GetData("lobbyowner")).Fallback(default(SteamId))),
|
||||
LobbyID = lobby.Id,
|
||||
RespondedToSteamQuery = true
|
||||
};
|
||||
bool.TryParse(lobby.GetData("haspassword"), out serverInfo.HasPassword);
|
||||
serverInfo.PlayerCount = int.TryParse(lobby.GetData("playercount"), out int playerCount) ? playerCount : 0;
|
||||
serverInfo.MaxPlayers = int.TryParse(lobby.GetData("maxplayernum"), out int maxPlayers) ? maxPlayers : 1;
|
||||
|
||||
AssignLobbyDataToServerInfo(lobby, serverInfo);
|
||||
|
||||
addToServerList(serverInfo);
|
||||
}
|
||||
}
|
||||
taskDone();
|
||||
});
|
||||
|
||||
Steamworks.ServerList.Internet serverQuery = new Steamworks.ServerList.Internet();
|
||||
void onServer(Steamworks.Data.ServerInfo info, bool responsive)
|
||||
{
|
||||
if (string.IsNullOrEmpty(info.Name)) { return; }
|
||||
|
||||
ServerInfo serverInfo = new ServerInfo
|
||||
{
|
||||
ServerName = info.Name,
|
||||
HasPassword = info.Passworded,
|
||||
Endpoint = new LidgrenEndpoint(info.Address, info.ConnectionPort),
|
||||
PlayerCount = info.Players,
|
||||
MaxPlayers = info.MaxPlayers,
|
||||
RespondedToSteamQuery = responsive
|
||||
};
|
||||
|
||||
if (responsive)
|
||||
{
|
||||
TaskPool.Add($"QueryServerRules (GetServers, {info.Name}, {info.Address})", info.QueryRulesAsync(),
|
||||
(t) =>
|
||||
{
|
||||
if (t.Status == TaskStatus.Faulted)
|
||||
{
|
||||
TaskPool.PrintTaskExceptions(t, "Failed to retrieve rules for " + info.Name);
|
||||
return;
|
||||
}
|
||||
|
||||
var rules = ((Task<Dictionary<string, string>>)t).Result;
|
||||
AssignServerRulesToServerInfo(rules, serverInfo);
|
||||
|
||||
CrossThread.RequestExecutionOnMainThread(() =>
|
||||
{
|
||||
addToServerList(serverInfo);
|
||||
});
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
CrossThread.RequestExecutionOnMainThread(() =>
|
||||
{
|
||||
addToServerList(serverInfo);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
serverQuery.OnResponsiveServer += (info) => onServer(info, true);
|
||||
serverQuery.OnUnresponsiveServer += (info) => onServer(info, false);
|
||||
|
||||
TaskPool.Add("RunServerQuery", serverQuery.RunQueryAsync(),
|
||||
(t) =>
|
||||
{
|
||||
serverQuery.Dispose();
|
||||
taskDone();
|
||||
if (t.Status == TaskStatus.Faulted)
|
||||
{
|
||||
TaskPool.PrintTaskExceptions(t, "Failed to retrieve servers");
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static async Task<List<Steamworks.Data.Lobby>> LobbyQueryRequest()
|
||||
{
|
||||
List<Steamworks.Data.Lobby> allLobbies = new List<Steamworks.Data.Lobby>();
|
||||
Steamworks.Data.LobbyQuery lobbyQuery = Steamworks.SteamMatchmaking.CreateLobbyQuery()
|
||||
.FilterDistanceWorldwide()
|
||||
.WithMaxResults(50);
|
||||
//steamworks seems to unable to retrieve more than 50
|
||||
//lobbies per request; to work around this, we'll make
|
||||
//up to 10 requests, asking to ignore all previous results
|
||||
//in each subsequent request
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
Steamworks.Data.Lobby[] lobbies = await lobbyQuery.RequestAsync();
|
||||
if (lobbies == null) { break; }
|
||||
foreach (var l in lobbies)
|
||||
{
|
||||
lobbyQuery = lobbyQuery
|
||||
.WithoutKeyValue("lobbyowner", l.GetData("lobbyowner"));
|
||||
}
|
||||
allLobbies.AddRange(lobbies);
|
||||
}
|
||||
|
||||
//make sure all returned lobbies are distinct, don't want any duplicates here
|
||||
return allLobbies.Select(l => l.Id).Distinct().Select(i => allLobbies.Find(l => l.Id == i)).ToList();
|
||||
}
|
||||
|
||||
public static void AssignLobbyDataToServerInfo(Steamworks.Data.Lobby lobby, ServerInfo serverInfo)
|
||||
{
|
||||
serverInfo.OwnerVerified = true;
|
||||
|
||||
serverInfo.ServerMessage = lobby.GetData("message");
|
||||
serverInfo.GameVersion = lobby.GetData("version");
|
||||
|
||||
serverInfo.ContentPackageNames.AddRange(lobby.GetData("contentpackage").Split(','));
|
||||
serverInfo.ContentPackageHashes.AddRange(lobby.GetData("contentpackagehash").Split(','));
|
||||
|
||||
string workshopIdData = lobby.GetData("contentpackageid");
|
||||
if (!string.IsNullOrEmpty(workshopIdData))
|
||||
{
|
||||
serverInfo.ContentPackageWorkshopIds.AddRange(ParseWorkshopIds(workshopIdData));
|
||||
}
|
||||
else
|
||||
{
|
||||
string[] workshopUrls = lobby.GetData("contentpackageurl").Split(',');
|
||||
serverInfo.ContentPackageWorkshopIds.AddRange(WorkshopUrlsToIds(workshopUrls));
|
||||
}
|
||||
|
||||
if (Enum.TryParse(lobby.GetData("modeselectionmode"), out SelectionMode selectionMode)) { serverInfo.ModeSelectionMode = selectionMode; }
|
||||
if (Enum.TryParse(lobby.GetData("subselectionmode"), out selectionMode)) { serverInfo.SubSelectionMode = selectionMode; }
|
||||
|
||||
serverInfo.AllowSpectating = getLobbyBool("allowspectating");
|
||||
serverInfo.AllowRespawn = getLobbyBool("allowrespawn");
|
||||
serverInfo.VoipEnabled = getLobbyBool("voicechatenabled");
|
||||
serverInfo.KarmaEnabled = getLobbyBool("karmaenabled");
|
||||
serverInfo.FriendlyFireEnabled = getLobbyBool("friendlyfireenabled");
|
||||
if (Enum.TryParse(lobby.GetData("traitors"), out YesNoMaybe traitorsEnabled)) { serverInfo.TraitorsEnabled = traitorsEnabled; }
|
||||
|
||||
serverInfo.GameStarted = lobby.GetData("gamestarted") == "True";
|
||||
serverInfo.GameMode = (lobby.GetData("gamemode") ?? "").ToIdentifier();
|
||||
if (Enum.TryParse(lobby.GetData("playstyle"), out PlayStyle playStyle)) serverInfo.PlayStyle = playStyle;
|
||||
|
||||
if (serverInfo.ContentPackageNames.Count != serverInfo.ContentPackageHashes.Count ||
|
||||
serverInfo.ContentPackageHashes.Count != serverInfo.ContentPackageWorkshopIds.Count)
|
||||
{
|
||||
//invalid contentpackage info
|
||||
serverInfo.ContentPackageNames.Clear();
|
||||
serverInfo.ContentPackageHashes.Clear();
|
||||
serverInfo.ContentPackageWorkshopIds.Clear();
|
||||
}
|
||||
|
||||
string pingLocation = lobby.GetData("pinglocation");
|
||||
if (!string.IsNullOrEmpty(pingLocation))
|
||||
{
|
||||
serverInfo.PingLocation = Steamworks.Data.NetPingLocation.TryParseFromString(pingLocation);
|
||||
}
|
||||
|
||||
bool? getLobbyBool(string key)
|
||||
{
|
||||
string data = lobby.GetData(key);
|
||||
if (string.IsNullOrEmpty(data)) { return null; }
|
||||
return data == "True" || data == "true";
|
||||
}
|
||||
}
|
||||
|
||||
public static void AssignServerRulesToServerInfo(Dictionary<string, string> rules, ServerInfo serverInfo)
|
||||
{
|
||||
serverInfo.OwnerVerified = true;
|
||||
|
||||
if (rules == null) { return; }
|
||||
|
||||
if (rules.ContainsKey("message")) { serverInfo.ServerMessage = rules["message"]; }
|
||||
if (rules.ContainsKey("version")) { serverInfo.GameVersion = rules["version"]; }
|
||||
|
||||
if (rules.ContainsKey("playercount"))
|
||||
{
|
||||
if (int.TryParse(rules["playercount"], out int playerCount)) { serverInfo.PlayerCount = playerCount; }
|
||||
}
|
||||
|
||||
serverInfo.ContentPackageNames.Clear();
|
||||
serverInfo.ContentPackageHashes.Clear();
|
||||
serverInfo.ContentPackageWorkshopIds.Clear();
|
||||
if (rules.ContainsKey("contentpackage")) { serverInfo.ContentPackageNames.AddRange(rules["contentpackage"].Split(',')); }
|
||||
if (rules.ContainsKey("contentpackagehash")) { serverInfo.ContentPackageHashes.AddRange(rules["contentpackagehash"].Split(',')); }
|
||||
if (rules.ContainsKey("contentpackageid"))
|
||||
{
|
||||
serverInfo.ContentPackageWorkshopIds.AddRange(ParseWorkshopIds(rules["contentpackageid"]));
|
||||
}
|
||||
else if (rules.ContainsKey("contentpackageurl"))
|
||||
{
|
||||
string[] workshopUrls = rules["contentpackageurl"].Split(',');
|
||||
serverInfo.ContentPackageWorkshopIds.AddRange(WorkshopUrlsToIds(workshopUrls));
|
||||
}
|
||||
|
||||
if (rules.ContainsKey("modeselectionmode"))
|
||||
{
|
||||
if (Enum.TryParse(rules["modeselectionmode"], out SelectionMode selectionMode)) { serverInfo.ModeSelectionMode = selectionMode; }
|
||||
}
|
||||
if (rules.ContainsKey("subselectionmode"))
|
||||
{
|
||||
if (Enum.TryParse(rules["subselectionmode"], out SelectionMode selectionMode)) { serverInfo.SubSelectionMode = selectionMode; }
|
||||
}
|
||||
if (rules.ContainsKey("allowspectating")) { serverInfo.AllowSpectating = rules["allowspectating"] == "True"; }
|
||||
if (rules.ContainsKey("allowrespawn")) { serverInfo.AllowRespawn = rules["allowrespawn"] == "True"; }
|
||||
if (rules.ContainsKey("voicechatenabled")) { serverInfo.VoipEnabled = rules["voicechatenabled"] == "True"; }
|
||||
if (rules.ContainsKey("friendlyfireenabled")) { serverInfo.FriendlyFireEnabled = rules["friendlyfireenabled"] == "True"; }
|
||||
if (rules.ContainsKey("karmaenabled")) { serverInfo.KarmaEnabled = rules["karmaenabled"] == "True"; }
|
||||
if (rules.ContainsKey("traitors"))
|
||||
{
|
||||
if (Enum.TryParse(rules["traitors"], out YesNoMaybe traitorsEnabled)) { serverInfo.TraitorsEnabled = traitorsEnabled; }
|
||||
}
|
||||
|
||||
if (rules.ContainsKey("gamestarted")) { serverInfo.GameStarted = rules["gamestarted"] == "True"; }
|
||||
if (rules.ContainsKey("gamemode"))
|
||||
{
|
||||
serverInfo.GameMode = rules["gamemode"].ToIdentifier();
|
||||
}
|
||||
if (rules.ContainsKey("playstyle") && Enum.TryParse(rules["playstyle"], out PlayStyle playStyle))
|
||||
{
|
||||
serverInfo.PlayStyle = playStyle;
|
||||
}
|
||||
|
||||
if (serverInfo.ContentPackageNames.Count != serverInfo.ContentPackageHashes.Count ||
|
||||
serverInfo.ContentPackageHashes.Count != serverInfo.ContentPackageWorkshopIds.Count)
|
||||
{
|
||||
//invalid contentpackage info
|
||||
serverInfo.ContentPackageNames.Clear();
|
||||
serverInfo.ContentPackageHashes.Clear();
|
||||
serverInfo.ContentPackageWorkshopIds.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,7 +196,7 @@ namespace Barotrauma.Steam
|
||||
throw new Exception("Expected Workshop package");
|
||||
}
|
||||
|
||||
if (contentPackage.SteamWorkshopId == 0)
|
||||
if (!contentPackage.UgcId.TryUnwrap(out var ugcId) || !(ugcId is SteamWorkshopId workshopId))
|
||||
{
|
||||
throw new Exception($"Steam Workshop ID not set for {contentPackage.Name}");
|
||||
}
|
||||
@@ -210,7 +210,7 @@ namespace Barotrauma.Steam
|
||||
string newPath = $"{ContentPackage.LocalModsDir}/{sanitizedName}";
|
||||
if (File.Exists(newPath) || Directory.Exists(newPath))
|
||||
{
|
||||
newPath += $"_{contentPackage.SteamWorkshopId}";
|
||||
newPath += $"_{workshopId.Value}";
|
||||
}
|
||||
|
||||
if (File.Exists(newPath) || Directory.Exists(newPath))
|
||||
@@ -226,7 +226,7 @@ namespace Barotrauma.Steam
|
||||
|
||||
RefreshLocalMods();
|
||||
|
||||
return ContentPackageManager.LocalPackages.FirstOrDefault(p => p.SteamWorkshopId == contentPackage.SteamWorkshopId);
|
||||
return ContentPackageManager.LocalPackages.FirstOrDefault(p => p.UgcId == contentPackage.UgcId);
|
||||
}
|
||||
|
||||
private struct InstallWaiter
|
||||
@@ -266,7 +266,10 @@ namespace Barotrauma.Steam
|
||||
{
|
||||
NukeDownload(workshopItem);
|
||||
var toUninstall
|
||||
= ContentPackageManager.WorkshopPackages.Where(p => p.SteamWorkshopId == workshopItem.Id)
|
||||
= ContentPackageManager.WorkshopPackages.Where(p =>
|
||||
p.UgcId.TryUnwrap(out var ugcId)
|
||||
&& ugcId is SteamWorkshopId workshopId
|
||||
&& workshopId.Value == workshopItem.Id)
|
||||
.ToHashSet();
|
||||
toUninstall.Select(p => p.Dir).ForEach(d => Directory.Delete(d));
|
||||
CrossThread.RequestExecutionOnMainThread(() => ContentPackageManager.WorkshopPackages.Refresh());
|
||||
@@ -296,7 +299,10 @@ namespace Barotrauma.Steam
|
||||
return;
|
||||
}
|
||||
else if (CanBeInstalled(id)
|
||||
&& !ContentPackageManager.WorkshopPackages.Any(p => p.SteamWorkshopId == id)
|
||||
&& !ContentPackageManager.WorkshopPackages.Any(p =>
|
||||
p.UgcId.TryUnwrap(out var ugcId)
|
||||
&& ugcId is SteamWorkshopId workshopId
|
||||
&& workshopId.Value == id)
|
||||
&& !InstallTaskCounter.IsInstalling(id))
|
||||
{
|
||||
TaskPool.Add($"InstallItem{id}", InstallMod(id), t => InstallWaiter.StopWaiting(id));
|
||||
|
||||
@@ -35,7 +35,12 @@ namespace Barotrauma.Steam
|
||||
memSubscribedModCount = numSubscribedMods;
|
||||
|
||||
var subscribedIds = SteamManager.GetSubscribedItems().ToHashSet();
|
||||
var installedIds = ContentPackageManager.WorkshopPackages.Select(p => p.SteamWorkshopId).ToHashSet();
|
||||
var installedIds = ContentPackageManager.WorkshopPackages
|
||||
.Select(p => p.UgcId)
|
||||
.NotNone()
|
||||
.OfType<SteamWorkshopId>()
|
||||
.Select(id => id.Value)
|
||||
.ToHashSet();
|
||||
foreach (var id in subscribedIds.Where(id2 => !installedIds.Contains(id2)))
|
||||
{
|
||||
Steamworks.Ugc.Item item = new Steamworks.Ugc.Item(id);
|
||||
@@ -514,7 +519,9 @@ namespace Barotrauma.Steam
|
||||
|
||||
private void PrepareToShowModInfo(ContentPackage mod)
|
||||
{
|
||||
TaskPool.Add($"PrepareToShow{mod.SteamWorkshopId}Info", SteamManager.Workshop.GetItem(mod.SteamWorkshopId),
|
||||
if (!mod.UgcId.TryUnwrap(out var ugcId)
|
||||
|| !(ugcId is SteamWorkshopId workshopId)) { return; }
|
||||
TaskPool.Add($"PrepareToShow{mod.UgcId}Info", SteamManager.Workshop.GetItem(workshopId.Value),
|
||||
t =>
|
||||
{
|
||||
if (!t.TryGetResult(out Steamworks.Ugc.Item? item)) { return; }
|
||||
@@ -592,7 +599,12 @@ namespace Barotrauma.Steam
|
||||
isEnabled: true,
|
||||
onSelected: () =>
|
||||
{
|
||||
TaskPool.AddIfNotFound($"UnsubFromSelected", Task.WhenAll(selectedMods.Select(m => SteamManager.Workshop.GetItem(m.SteamWorkshopId))),
|
||||
var workshopIds = selectedMods
|
||||
.Select(m => m.UgcId)
|
||||
.NotNone()
|
||||
.OfType<SteamWorkshopId>()
|
||||
.Select(id => id.Value);
|
||||
TaskPool.AddIfNotFound($"UnsubFromSelected", Task.WhenAll(workshopIds.Select(SteamManager.Workshop.GetItem)),
|
||||
t =>
|
||||
{
|
||||
if (!t.TryGetResult(out Steamworks.Ugc.Item?[] items)) { return; }
|
||||
@@ -672,7 +684,7 @@ namespace Barotrauma.Steam
|
||||
infoButton.Enabled = false;
|
||||
}
|
||||
TaskPool.AddIfNotFound(
|
||||
$"DetermineUpdateRequired{mod.SteamWorkshopId}",
|
||||
$"DetermineUpdateRequired{mod.UgcId}",
|
||||
mod.IsUpToDate(),
|
||||
t =>
|
||||
{
|
||||
@@ -725,6 +737,8 @@ namespace Barotrauma.Steam
|
||||
{
|
||||
var mod = child.UserData as RegularPackage;
|
||||
if (mod is null || !ContentPackageManager.WorkshopPackages.Contains(mod)) { continue; }
|
||||
if (!mod.UgcId.TryUnwrap(out var ugcId)) { continue; }
|
||||
if (!(ugcId is SteamWorkshopId workshopId)) { continue; }
|
||||
|
||||
var btn = child.GetChild<GUILayoutGroup>()?.GetAllChildren<GUIButton>().Last();
|
||||
if (btn is null) { continue; }
|
||||
@@ -732,11 +746,11 @@ namespace Barotrauma.Steam
|
||||
|
||||
btn.ApplyStyle(
|
||||
GUIStyle.GetComponentStyle(
|
||||
ids.Contains(mod.SteamWorkshopId)
|
||||
ids.Contains(workshopId.Value)
|
||||
? "WorkshopMenu.PublishedIcon"
|
||||
: "WorkshopMenu.DownloadedIcon"));
|
||||
btn.ToolTip = TextManager.Get(
|
||||
ids.Contains(mod.SteamWorkshopId)
|
||||
ids.Contains(workshopId.Value)
|
||||
? "PublishedWorkshopMod"
|
||||
: "DownloadedWorkshopMod");
|
||||
btn.HoverCursor = CursorState.Default;
|
||||
|
||||
@@ -226,7 +226,7 @@ namespace Barotrauma.Steam
|
||||
(Steamworks.Ugc.Item WorkshopItem, ContentPackage? LocalPackage)[] publishedItems = workshopItems
|
||||
.Select(item => (item,
|
||||
(ContentPackage?)ContentPackageManager.LocalPackages.FirstOrDefault(p
|
||||
=> p.SteamWorkshopId != 0 && p.SteamWorkshopId == item.Id)))
|
||||
=> p.TryExtractSteamWorkshopId(out var workshopId) && workshopId.Value == item.Id)))
|
||||
//Sort the pairs by last local edit time if available
|
||||
.OrderBy(t => t.Item2 == null)
|
||||
.ThenByDescending(t => t.Item2 is { } p ? getEditTime(p) : t.Item1.LatestUpdateTime)
|
||||
@@ -241,7 +241,9 @@ namespace Barotrauma.Steam
|
||||
|
||||
//Get mods that haven't been published and add them to the list
|
||||
var unpublishedMods = ContentPackageManager.LocalPackages
|
||||
.Where(p => p.SteamWorkshopId == 0 || !publishedItems.Any(item => item.WorkshopItem.Id == p.SteamWorkshopId))
|
||||
.Where(p =>
|
||||
!p.TryExtractSteamWorkshopId(out var workshopId)
|
||||
|| !publishedItems.Any(item => item.WorkshopItem.Id == workshopId.Value))
|
||||
.OrderByDescending(getEditTime).ToArray();
|
||||
|
||||
if (unpublishedMods.Any())
|
||||
@@ -556,7 +558,9 @@ namespace Barotrauma.Steam
|
||||
taskCancelSrc = taskCancelSrc.IsCancellationRequested ? new CancellationTokenSource() : taskCancelSrc;
|
||||
|
||||
var contentPackage
|
||||
= ContentPackageManager.WorkshopPackages.FirstOrDefault(p => p.SteamWorkshopId == workshopItem.Id);
|
||||
= ContentPackageManager.WorkshopPackages.FirstOrDefault(p =>
|
||||
p.TryExtractSteamWorkshopId(out var workshopId)
|
||||
&& workshopId.Value == workshopItem.Id);
|
||||
|
||||
var verticalLayout = new GUILayoutGroup(new RectTransform(Vector2.One, parentFrame.RectTransform));
|
||||
|
||||
@@ -619,7 +623,7 @@ namespace Barotrauma.Steam
|
||||
if (contentPackage != null)
|
||||
{
|
||||
TaskPool.AddIfNotFound(
|
||||
$"DetermineUpdateRequired{contentPackage.SteamWorkshopId}",
|
||||
$"DetermineUpdateRequired{contentPackage.UgcId}",
|
||||
contentPackage.IsUpToDate(),
|
||||
t =>
|
||||
{
|
||||
@@ -652,7 +656,9 @@ namespace Barotrauma.Steam
|
||||
|
||||
if (contentPackage != null
|
||||
&& !ContentPackageManager.WorkshopPackages.Contains(contentPackage)
|
||||
&& ContentPackageManager.WorkshopPackages.Any(p => p.SteamWorkshopId == workshopItem.Id))
|
||||
&& ContentPackageManager.WorkshopPackages.Any(p =>
|
||||
p.TryExtractSteamWorkshopId(out var workshopId)
|
||||
&& workshopId.Value == workshopItem.Id))
|
||||
{
|
||||
updateButton.Visible = false;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.IO;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Steam;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma
|
||||
@@ -49,21 +50,18 @@ namespace Barotrauma
|
||||
case ModType.Workshop:
|
||||
{
|
||||
var id = element.GetAttributeUInt64("id", 0);
|
||||
var pkg = ContentPackageManager.WorkshopPackages.FirstOrDefault(p => p.SteamWorkshopId == id);
|
||||
if (id != 0 && pkg != null)
|
||||
{
|
||||
addPkg(pkg);
|
||||
}
|
||||
if (id == 0) { continue; }
|
||||
var pkg = ContentPackageManager.WorkshopPackages.FirstOrDefault(p =>
|
||||
p.TryExtractSteamWorkshopId(out var workshopId) && workshopId.Value == id);
|
||||
if (pkg != null) { addPkg(pkg); }
|
||||
}
|
||||
break;
|
||||
case ModType.Local:
|
||||
{
|
||||
var name = element.GetAttributeString("name", "");
|
||||
if (name.IsNullOrEmpty()) { continue; }
|
||||
var pkg = ContentPackageManager.LocalPackages.FirstOrDefault(p => p.NameMatches(name));
|
||||
if (!name.IsNullOrEmpty() && pkg != null)
|
||||
{
|
||||
addPkg(pkg);
|
||||
}
|
||||
if (pkg != null) { addPkg(pkg); }
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -115,7 +113,7 @@ namespace Barotrauma
|
||||
{
|
||||
case ModType.Workshop:
|
||||
pkgElem.SetAttributeValue("name", pkg.Name);
|
||||
pkgElem.SetAttributeValue("id", pkg.SteamWorkshopId.ToString());
|
||||
pkgElem.SetAttributeValue("id", pkg.UgcId.ToString());
|
||||
break;
|
||||
case ModType.Local:
|
||||
pkgElem.SetAttributeValue("name", pkg.Name);
|
||||
|
||||
@@ -95,6 +95,9 @@ namespace Barotrauma.Steam
|
||||
SelectTab(Tab.Publish);
|
||||
}
|
||||
|
||||
private static bool PackageMatchesItem(ContentPackage p, Steamworks.Ugc.Item workshopItem)
|
||||
=> p.TryExtractSteamWorkshopId(out var workshopId) && workshopId.Value == workshopItem.Id;
|
||||
|
||||
private void PopulatePublishTab(ItemOrPackage itemOrPackage, GUIFrame parentFrame)
|
||||
{
|
||||
ContentPackageManager.LocalPackages.Refresh();
|
||||
@@ -105,18 +108,19 @@ namespace Barotrauma.Steam
|
||||
childAnchor: Anchor.TopCenter);
|
||||
|
||||
Steamworks.Ugc.Item workshopItem = itemOrPackage.TryGet(out Steamworks.Ugc.Item item) ? item : default;
|
||||
|
||||
ContentPackage? localPackage = itemOrPackage.TryGet(out ContentPackage package)
|
||||
? package
|
||||
: ContentPackageManager.LocalPackages.FirstOrDefault(p => p.SteamWorkshopId == workshopItem.Id);
|
||||
: ContentPackageManager.LocalPackages.FirstOrDefault(p => PackageMatchesItem(p, workshopItem));
|
||||
ContentPackage? workshopPackage
|
||||
= ContentPackageManager.WorkshopPackages.FirstOrDefault(p => p.SteamWorkshopId == workshopItem.Id);
|
||||
= ContentPackageManager.WorkshopPackages.FirstOrDefault(p => PackageMatchesItem(p, workshopItem));
|
||||
if (localPackage is null)
|
||||
{
|
||||
new GUIFrame(new RectTransform((1.0f, 0.15f), mainLayout.RectTransform), style: null);
|
||||
|
||||
//Local copy does not exist; check for Workshop copy
|
||||
bool workshopCopyExists =
|
||||
ContentPackageManager.WorkshopPackages.Any(p => p.SteamWorkshopId == workshopItem.Id);
|
||||
ContentPackageManager.WorkshopPackages.Any(p => PackageMatchesItem(p, workshopItem));
|
||||
|
||||
new GUITextBlock(new RectTransform((0.7f, 0.4f), mainLayout.RectTransform),
|
||||
TextManager.Get(workshopCopyExists ? "LocalCopyRequired" : "ItemInstallRequired"),
|
||||
@@ -403,7 +407,7 @@ namespace Barotrauma.Steam
|
||||
private IEnumerable<CoroutineStatus> CreateLocalCopy(GUITextBlock currentStepText, Steamworks.Ugc.Item workshopItem, GUIFrame parentFrame)
|
||||
{
|
||||
ContentPackage? workshopCopy =
|
||||
ContentPackageManager.WorkshopPackages.FirstOrDefault(p => p.SteamWorkshopId == workshopItem.Id);
|
||||
ContentPackageManager.WorkshopPackages.FirstOrDefault(p => PackageMatchesItem(p, workshopItem));
|
||||
if (workshopCopy is null)
|
||||
{
|
||||
if (!SteamManager.Workshop.CanBeInstalled(workshopItem))
|
||||
@@ -417,7 +421,7 @@ namespace Barotrauma.Steam
|
||||
{
|
||||
ContentPackageManager.WorkshopPackages.Refresh();
|
||||
});
|
||||
while (!ContentPackageManager.WorkshopPackages.Any(p => p.SteamWorkshopId == workshopItem.Id))
|
||||
while (!ContentPackageManager.WorkshopPackages.Any(p => PackageMatchesItem(p, workshopItem)))
|
||||
{
|
||||
currentStepText.Text = SteamManager.Workshop.CanBeInstalled(workshopItem)
|
||||
? TextManager.Get("PublishPopupInstall")
|
||||
@@ -426,7 +430,7 @@ namespace Barotrauma.Steam
|
||||
}
|
||||
|
||||
workshopCopy =
|
||||
ContentPackageManager.WorkshopPackages.First(p => p.SteamWorkshopId == workshopItem.Id);
|
||||
ContentPackageManager.WorkshopPackages.First(p => PackageMatchesItem(p, workshopItem));
|
||||
}
|
||||
|
||||
bool localCopyMade = false;
|
||||
@@ -480,7 +484,7 @@ namespace Barotrauma.Steam
|
||||
messageBox.Buttons[0].Enabled = false;
|
||||
Steamworks.Ugc.PublishResult? result = null;
|
||||
Exception? resultException = null;
|
||||
TaskPool.Add($"Publishing {localPackage.Name} ({localPackage.SteamWorkshopId})",
|
||||
TaskPool.Add($"Publishing {localPackage.Name} ({localPackage.UgcId})",
|
||||
editor.SubmitAsync(),
|
||||
t =>
|
||||
{
|
||||
@@ -496,6 +500,8 @@ namespace Barotrauma.Steam
|
||||
if (result is { Success: true })
|
||||
{
|
||||
var resultId = result.Value.FileId;
|
||||
bool packageMatchesResult(ContentPackage p)
|
||||
=> p.TryExtractSteamWorkshopId(out var workshopId) && workshopId.Value == resultId;
|
||||
Steamworks.Ugc.Item resultItem = new Steamworks.Ugc.Item(resultId);
|
||||
Task downloadTask = SteamManager.Workshop.ForceRedownload(resultItem);
|
||||
while (!resultItem.IsInstalled && !downloadTask.IsCompleted)
|
||||
@@ -511,7 +517,7 @@ namespace Barotrauma.Steam
|
||||
}
|
||||
|
||||
ContentPackage? pkgToNuke
|
||||
= ContentPackageManager.WorkshopPackages.FirstOrDefault(p => p.SteamWorkshopId == resultId);
|
||||
= ContentPackageManager.WorkshopPackages.FirstOrDefault(packageMatchesResult);
|
||||
if (pkgToNuke != null)
|
||||
{
|
||||
Directory.Delete(pkgToNuke.Dir, recursive: true);
|
||||
@@ -537,7 +543,7 @@ namespace Barotrauma.Steam
|
||||
|
||||
var localModProject = new ModProject(localPackage)
|
||||
{
|
||||
SteamWorkshopId = resultId
|
||||
UgcId = Option<ContentPackageId>.Some(new SteamWorkshopId(resultId))
|
||||
};
|
||||
localModProject.DiscardHashAndInstallTime();
|
||||
localModProject.Save(localPackage.Path);
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace Barotrauma
|
||||
public override bool Loaded => nestedStr.Loaded;
|
||||
public override void RetrieveValue()
|
||||
{
|
||||
cachedValue = ToolBox.WrapText(nestedStr.Value, lineLength, font.Value, textScale);
|
||||
cachedValue = ToolBox.WrapText(nestedStr.Value, lineLength, font.GetFontForStr(nestedStr.Value), textScale);
|
||||
UpdateLanguage();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
<Authors>FakeFish, Undertow Games</Authors>
|
||||
<Product>Barotrauma</Product>
|
||||
<Version>0.19.3.0</Version>
|
||||
<Version>0.19.5.0</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2022</Copyright>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AssemblyName>Barotrauma</AssemblyName>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
<Authors>FakeFish, Undertow Games</Authors>
|
||||
<Product>Barotrauma</Product>
|
||||
<Version>0.19.3.0</Version>
|
||||
<Version>0.19.5.0</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2022</Copyright>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AssemblyName>Barotrauma</AssemblyName>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
<Authors>FakeFish, Undertow Games</Authors>
|
||||
<Product>Barotrauma</Product>
|
||||
<Version>0.19.3.0</Version>
|
||||
<Version>0.19.5.0</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2022</Copyright>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AssemblyName>Barotrauma</AssemblyName>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
<Authors>FakeFish, Undertow Games</Authors>
|
||||
<Product>Barotrauma Dedicated Server</Product>
|
||||
<Version>0.19.3.0</Version>
|
||||
<Version>0.19.5.0</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2022</Copyright>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AssemblyName>DedicatedServer</AssemblyName>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
<Authors>FakeFish, Undertow Games</Authors>
|
||||
<Product>Barotrauma Dedicated Server</Product>
|
||||
<Version>0.19.3.0</Version>
|
||||
<Version>0.19.5.0</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2022</Copyright>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AssemblyName>DedicatedServer</AssemblyName>
|
||||
|
||||
@@ -61,8 +61,9 @@ namespace Barotrauma
|
||||
msg.WriteColorR8G8B8(Head.SkinColor);
|
||||
msg.WriteColorR8G8B8(Head.HairColor);
|
||||
msg.WriteColorR8G8B8(Head.FacialHairColor);
|
||||
msg.WriteString(ragdollFileName);
|
||||
|
||||
msg.WriteString(ragdollFileName);
|
||||
msg.WriteIdentifier(HumanPrefabIds.NpcIdentifier);
|
||||
if (Job != null)
|
||||
{
|
||||
msg.WriteUInt32(Job.Prefab.UintIdentifier);
|
||||
|
||||
@@ -243,6 +243,7 @@ namespace Barotrauma
|
||||
maxPlayers,
|
||||
ownerKey,
|
||||
steamId);
|
||||
Server.StartServer();
|
||||
|
||||
for (int i = 0; i < CommandLineArgs.Length; i++)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Barotrauma.Networking;
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
@@ -20,19 +21,22 @@ namespace Barotrauma
|
||||
CharacterInfo = client.CharacterInfo;
|
||||
|
||||
healthData = new XElement("health");
|
||||
client.Character?.CharacterHealth?.Save(healthData);
|
||||
if (client.Character?.Inventory != null)
|
||||
|
||||
//the character may not be controlled by the client atm, but still exist
|
||||
Character character = client.Character ?? CharacterInfo?.Character;
|
||||
|
||||
character?.CharacterHealth?.Save(healthData);
|
||||
if (character?.Inventory != null)
|
||||
{
|
||||
itemData = new XElement("inventory");
|
||||
Character.SaveInventory(client.Character.Inventory, itemData);
|
||||
Character.SaveInventory(character.Inventory, itemData);
|
||||
}
|
||||
OrderData = new XElement("orders");
|
||||
if (client.CharacterInfo != null)
|
||||
if (CharacterInfo != null)
|
||||
{
|
||||
CharacterInfo.SaveOrderData(client.CharacterInfo, OrderData);
|
||||
CharacterInfo.SaveOrderData(CharacterInfo, OrderData);
|
||||
}
|
||||
|
||||
if (client.Character?.Wallet.Save() is { } walletSave)
|
||||
if (character?.Wallet.Save() is { } walletSave)
|
||||
{
|
||||
WalletData = walletSave;
|
||||
}
|
||||
@@ -118,9 +122,9 @@ namespace Barotrauma
|
||||
character.SpawnInventoryItems(inventory, itemData.FromPackage(null));
|
||||
}
|
||||
|
||||
public void ApplyHealthData(Character character)
|
||||
public void ApplyHealthData(Character character, Func<AfflictionPrefab, bool> afflictionPredicate = null)
|
||||
{
|
||||
CharacterInfo.ApplyHealthData(character, healthData);
|
||||
CharacterInfo.ApplyHealthData(character, healthData, afflictionPredicate);
|
||||
}
|
||||
|
||||
public void ApplyOrderData(Character character)
|
||||
|
||||
@@ -917,15 +917,14 @@ namespace Barotrauma
|
||||
|
||||
foreach (var kvp in purchasedItems)
|
||||
{
|
||||
foreach (var purchasedItemList in purchasedItems.Values)
|
||||
var storeId = kvp.Key;
|
||||
var purchasedItemList = kvp.Value;
|
||||
foreach (var purchasedItem in purchasedItemList)
|
||||
{
|
||||
foreach (var purchasedItem in purchasedItemList)
|
||||
{
|
||||
int availableQuantity = map.CurrentLocation.Stores[kvp.Key].Stock.Find(s => s.ItemPrefab == purchasedItem.ItemPrefab)?.Quantity ?? 0;
|
||||
purchasedItem.Quantity = Math.Min(purchasedItem.Quantity, availableQuantity);
|
||||
}
|
||||
}
|
||||
CargoManager.PurchaseItems(kvp.Key, kvp.Value, false, sender);
|
||||
int availableQuantity = map.CurrentLocation.Stores[storeId].Stock.Find(s => s.ItemPrefab == purchasedItem.ItemPrefab)?.Quantity ?? 0;
|
||||
purchasedItem.Quantity = Math.Min(purchasedItem.Quantity, availableQuantity);
|
||||
}
|
||||
CargoManager.PurchaseItems(storeId, purchasedItemList, false, sender);
|
||||
}
|
||||
|
||||
foreach (var (storeIdentifier, items) in CargoManager.PurchasedItems)
|
||||
|
||||
@@ -71,6 +71,12 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
expirationTime = parsedTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
string error = $"Failed to parse the ban duration of \"{name}\" ({separatedLine[2]}) from the legacy ban list file (text file which has now been changed to XML). Considering the ban permanent.";
|
||||
DebugConsole.ThrowError(error);
|
||||
GameServer.AddPendingMessageToOwner(error, ChatMessageType.Error);
|
||||
}
|
||||
}
|
||||
string reason = separatedLine.Length > 3 ? string.Join(",", separatedLine.Skip(3)) : "";
|
||||
|
||||
@@ -156,6 +162,8 @@ namespace Barotrauma.Networking
|
||||
|
||||
public void BanPlayer(string name, Either<Address, AccountId> addressOrAccountId, string reason, TimeSpan? duration)
|
||||
{
|
||||
if (addressOrAccountId.TryGet(out Address address) && address.IsLocalHost) { return; }
|
||||
|
||||
var existingBan = bannedPlayers.Find(bp => bp.AddressOrAccountId == addressOrAccountId);
|
||||
if (existingBan != null) { bannedPlayers.Remove(existingBan); }
|
||||
|
||||
|
||||
@@ -224,27 +224,55 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reset what this client has voted for and the kick votes given to this client
|
||||
/// </summary>
|
||||
public void ResetVotes(bool resetKickVotes)
|
||||
{
|
||||
for (int i = 0; i < votes.Length; i++)
|
||||
{
|
||||
votes[i] = null;
|
||||
}
|
||||
if (resetKickVotes)
|
||||
{
|
||||
kickVoters.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void SetPermissions(ClientPermissions permissions, IEnumerable<DebugConsole.Command> permittedConsoleCommands)
|
||||
{
|
||||
this.Permissions = permissions;
|
||||
this.PermittedConsoleCommands.Clear();
|
||||
this.PermittedConsoleCommands.UnionWith(permittedConsoleCommands);
|
||||
Permissions = permissions;
|
||||
PermittedConsoleCommands.Clear();
|
||||
PermittedConsoleCommands.UnionWith(permittedConsoleCommands);
|
||||
if (Permissions.HasFlag(ClientPermissions.ManageSettings))
|
||||
{
|
||||
//ensure the client has the up-to-date server settings
|
||||
GameMain.Server?.ServerSettings?.ForcePropertyUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
public void GivePermission(ClientPermissions permission)
|
||||
{
|
||||
if (!this.Permissions.HasFlag(permission)) this.Permissions |= permission;
|
||||
if (!Permissions.HasFlag(permission))
|
||||
{
|
||||
Permissions |= permission;
|
||||
if (permission.HasFlag(ClientPermissions.ManageSettings))
|
||||
{
|
||||
//ensure the client has the up-to-date server settings
|
||||
GameMain.Server?.ServerSettings?.ForcePropertyUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void RemovePermission(ClientPermissions permission)
|
||||
{
|
||||
this.Permissions &= ~permission;
|
||||
Permissions &= ~permission;
|
||||
}
|
||||
|
||||
public bool HasPermission(ClientPermissions permission)
|
||||
{
|
||||
return this.Permissions.HasFlag(permission);
|
||||
return Permissions.HasFlag(permission);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ namespace Barotrauma.Networking
|
||||
string resultFileName
|
||||
= dir.StartsWith(ContentPackage.LocalModsDir)
|
||||
? $"Local_{mod.Name}"
|
||||
: $"Workshop_{mod.Name}_{mod.SteamWorkshopId}";
|
||||
: $"Workshop_{mod.Name}_{(mod.UgcId.TryUnwrap(out var ugcId) ? ugcId.ToString() : "NULL")}";
|
||||
resultFileName = ToolBox.RemoveInvalidFileNameChars(resultFileName.Replace('\\', '_').Replace('/', '_'));
|
||||
resultFileName = $"{resultFileName}{Extension}";
|
||||
return Path.Combine(UploadFolder, resultFileName);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -246,7 +246,7 @@ namespace Barotrauma.Networking
|
||||
" (created " + (Timing.TotalTime - firstEventToResend.CreateTime).ToString("0.##") + " s ago, " +
|
||||
(lastSentToAnyoneTime - firstEventToResend.CreateTime).ToString("0.##") + " s older than last event sent to anyone)" +
|
||||
" Events queued: " + events.Count + ", last sent to all: " + lastSentToAll, ServerLog.MessageType.Error);
|
||||
server.DisconnectClient(c, "", DisconnectReason.ExcessiveDesyncOldEvent + "/ServerMessage.ExcessiveDesyncOldEvent");
|
||||
server.DisconnectClient(c, PeerDisconnectPacket.WithReason(DisconnectReason.ExcessiveDesyncOldEvent));
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -260,7 +260,7 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
DebugConsole.NewMessage(c.Name + " was kicked because they were expecting a removed network event (" + (c.LastRecvEntityEventID + 1).ToString() + ", last available is " + events[0].ID.ToString() + ")", Color.Red);
|
||||
GameServer.Log(GameServer.ClientLogName(c) + " was kicked because they were expecting a removed network event (" + (c.LastRecvEntityEventID + 1).ToString() + ", last available is " + events[0].ID.ToString() + ")", ServerLog.MessageType.Error);
|
||||
server.DisconnectClient(c, "", DisconnectReason.ExcessiveDesyncRemovedEvent + "/ServerMessage.ExcessiveDesyncRemovedEvent");
|
||||
server.DisconnectClient(c, PeerDisconnectPacket.WithReason(DisconnectReason.ExcessiveDesyncRemovedEvent));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -269,7 +269,7 @@ namespace Barotrauma.Networking
|
||||
foreach (Client timedOutClient in timedOutClients)
|
||||
{
|
||||
GameServer.Log("Disconnecting client " + GameServer.ClientLogName(timedOutClient) + ". Syncing the client with the server took too long.", ServerLog.MessageType.Error);
|
||||
GameMain.Server.DisconnectClient(timedOutClient, "", DisconnectReason.SyncTimeout + "/ServerMessage.SyncTimeout");
|
||||
GameMain.Server.DisconnectClient(timedOutClient, PeerDisconnectPacket.WithReason(DisconnectReason.SyncTimeout));
|
||||
}
|
||||
|
||||
bufferedEvents.RemoveAll(b => b.IsProcessed);
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace Barotrauma.Networking
|
||||
if (Sender != null && c.InGame)
|
||||
{
|
||||
msg.WriteUInt16(Sender.ID);
|
||||
}
|
||||
}
|
||||
msg.WriteBoolean(false); //text color (no custom text colors for order messages)
|
||||
msg.WritePadBits();
|
||||
WriteOrder(msg);
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Barotrauma.Networking
|
||||
|
||||
private readonly List<NetIncomingMessage> incomingLidgrenMessages;
|
||||
|
||||
public LidgrenServerPeer(Option<int> ownKey, ServerSettings settings)
|
||||
public LidgrenServerPeer(Option<int> ownKey, ServerSettings settings, Callbacks callbacks) : base(callbacks)
|
||||
{
|
||||
serverSettings = settings;
|
||||
|
||||
@@ -68,21 +68,21 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
}
|
||||
|
||||
public override void Close(string? msg = null)
|
||||
public override void Close()
|
||||
{
|
||||
if (netServer == null) { return; }
|
||||
|
||||
for (int i = pendingClients.Count - 1; i >= 0; i--)
|
||||
{
|
||||
RemovePendingClient(pendingClients[i], DisconnectReason.ServerShutdown, msg);
|
||||
RemovePendingClient(pendingClients[i], PeerDisconnectPacket.WithReason(DisconnectReason.ServerShutdown));
|
||||
}
|
||||
|
||||
for (int i = connectedClients.Count - 1; i >= 0; i--)
|
||||
{
|
||||
Disconnect(connectedClients[i], msg ?? DisconnectReason.ServerShutdown.ToString());
|
||||
Disconnect(connectedClients[i], PeerDisconnectPacket.WithReason(DisconnectReason.ServerShutdown));
|
||||
}
|
||||
|
||||
netServer.Shutdown(msg ?? DisconnectReason.ServerShutdown.ToString());
|
||||
netServer.Shutdown(PeerDisconnectPacket.WithReason(DisconnectReason.ServerShutdown).ToLidgrenStringRepresentation());
|
||||
|
||||
pendingClients.Clear();
|
||||
connectedClients.Clear();
|
||||
@@ -91,7 +91,7 @@ namespace Barotrauma.Networking
|
||||
|
||||
Steamworks.SteamServer.OnValidateAuthTicketResponse -= OnAuthChange;
|
||||
|
||||
OnShutdown?.Invoke();
|
||||
callbacks.OnShutdown.Invoke();
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
@@ -100,12 +100,6 @@ namespace Barotrauma.Networking
|
||||
|
||||
ToolBox.ThrowIfNull(incomingLidgrenMessages);
|
||||
|
||||
if (OnOwnerDetermined != null && OwnerConnection != null)
|
||||
{
|
||||
OnOwnerDetermined?.Invoke(OwnerConnection);
|
||||
OnOwnerDetermined = null;
|
||||
}
|
||||
|
||||
netServer.ReadMessages(incomingLidgrenMessages);
|
||||
|
||||
//process incoming connections first
|
||||
@@ -193,14 +187,14 @@ namespace Barotrauma.Networking
|
||||
|
||||
if (connectedClients.Count >= serverSettings.MaxPlayers)
|
||||
{
|
||||
inc.SenderConnection.Deny(DisconnectReason.ServerFull.ToString());
|
||||
inc.SenderConnection.Deny(PeerDisconnectPacket.WithReason(DisconnectReason.ServerFull).ToLidgrenStringRepresentation());
|
||||
return;
|
||||
}
|
||||
|
||||
if (serverSettings.BanList.IsBanned(new LidgrenEndpoint(inc.SenderConnection.RemoteEndPoint), out string banReason))
|
||||
{
|
||||
//IP banned: deny immediately
|
||||
inc.SenderConnection.Deny($"{DisconnectReason.Banned}/ {banReason}");
|
||||
inc.SenderConnection.Deny(PeerDisconnectPacket.Banned(banReason).ToLidgrenStringRepresentation());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -235,12 +229,12 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
if (pendingClient != null)
|
||||
{
|
||||
RemovePendingClient(pendingClient, DisconnectReason.AuthenticationRequired, "Received data message from unauthenticated client");
|
||||
RemovePendingClient(pendingClient, PeerDisconnectPacket.WithReason(DisconnectReason.AuthenticationRequired));
|
||||
}
|
||||
else if (lidgrenMsg.SenderConnection.Status != NetConnectionStatus.Disconnected &&
|
||||
lidgrenMsg.SenderConnection.Status != NetConnectionStatus.Disconnecting)
|
||||
{
|
||||
lidgrenMsg.SenderConnection.Disconnect($"{DisconnectReason.AuthenticationRequired}/ Received data message from unauthenticated client");
|
||||
lidgrenMsg.SenderConnection.Disconnect(PeerDisconnectPacket.WithReason(DisconnectReason.AuthenticationRequired).ToLidgrenStringRepresentation());
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -252,12 +246,12 @@ namespace Barotrauma.Networking
|
||||
|| (conn.AccountInfo.AccountId.TryUnwrap(out var accountId) && serverSettings.BanList.IsBanned(accountId, out banReason))
|
||||
|| conn.AccountInfo.OtherMatchingIds.Any(id => serverSettings.BanList.IsBanned(id, out banReason)))
|
||||
{
|
||||
Disconnect(conn, $"{DisconnectReason.Banned}/ {banReason}");
|
||||
Disconnect(conn, PeerDisconnectPacket.Banned(banReason));
|
||||
return;
|
||||
}
|
||||
|
||||
var packet = INetSerializableStruct.Read<PeerPacketMessage>(inc);
|
||||
OnMessageReceived?.Invoke(conn, packet.GetReadMessage(packetHeader.IsCompressed(), conn));
|
||||
callbacks.OnMessageReceived.Invoke(conn, packet.GetReadMessage(packetHeader.IsCompressed(), conn));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,12 +269,11 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
DebugConsole.NewMessage("Owner disconnected: closing the server...");
|
||||
GameServer.Log("Owner disconnected: closing the server...", ServerLog.MessageType.ServerMessage);
|
||||
Close($"{DisconnectReason.ServerShutdown}/ Owner disconnected");
|
||||
Close();
|
||||
}
|
||||
else
|
||||
{
|
||||
#warning TODO: kill off disconnect in layer 1
|
||||
Disconnect(conn, $"ServerMessage.HasDisconnected~[client]={GameMain.Server.ConnectedClients.First(c => c.Connection == conn).Name}");
|
||||
Disconnect(conn, PeerDisconnectPacket.WithReason(DisconnectReason.Disconnected));
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -288,7 +281,7 @@ namespace Barotrauma.Networking
|
||||
PendingClient? pendingClient = pendingClients.Find(c => c.Connection is LidgrenConnection l && l.NetConnection == inc.SenderConnection);
|
||||
if (pendingClient != null)
|
||||
{
|
||||
RemovePendingClient(pendingClient, DisconnectReason.Unknown, $"ServerMessage.HasDisconnected~[client]={pendingClient.Name}");
|
||||
RemovePendingClient(pendingClient, PeerDisconnectPacket.WithReason(DisconnectReason.Disconnected));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -312,9 +305,11 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
if (status == Steamworks.AuthResponse.OK) { return; }
|
||||
|
||||
if (connectedClients.Find(c => c.AccountInfo.AccountId is Some<AccountId> { Value: SteamId id } && id.Value == steamId) is LidgrenConnection connection)
|
||||
if (connectedClients.Find(c
|
||||
=> c.AccountInfo.AccountId is Some<AccountId> { Value: SteamId id } && id.Value == steamId)
|
||||
is LidgrenConnection connection)
|
||||
{
|
||||
Disconnect(connection, $"{DisconnectReason.SteamAuthenticationFailed}/ Steam authentication status changed: {status}");
|
||||
Disconnect(connection, PeerDisconnectPacket.SteamAuthError(status));
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -325,7 +320,7 @@ namespace Barotrauma.Networking
|
||||
|| serverSettings.BanList.IsBanned(new SteamId(steamId), out banReason)
|
||||
|| serverSettings.BanList.IsBanned(new SteamId(ownerId), out banReason))
|
||||
{
|
||||
RemovePendingClient(pendingClient, DisconnectReason.Banned, banReason);
|
||||
RemovePendingClient(pendingClient, PeerDisconnectPacket.Banned(banReason));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -337,7 +332,7 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
else
|
||||
{
|
||||
RemovePendingClient(pendingClient, DisconnectReason.SteamAuthenticationFailed, $"Steam authentication failed: {status}");
|
||||
RemovePendingClient(pendingClient, PeerDisconnectPacket.SteamAuthError(status));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -374,7 +369,7 @@ namespace Barotrauma.Networking
|
||||
SendMsgInternal(conn, headers, body);
|
||||
}
|
||||
|
||||
public override void Disconnect(NetworkConnection conn, string? msg = null)
|
||||
public override void Disconnect(NetworkConnection conn, PeerDisconnectPacket peerDisconnectPacket)
|
||||
{
|
||||
if (netServer == null) { return; }
|
||||
|
||||
@@ -384,11 +379,11 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
lidgrenConn.Status = NetworkConnectionStatus.Disconnected;
|
||||
connectedClients.Remove(lidgrenConn);
|
||||
OnDisconnect?.Invoke(conn, msg);
|
||||
callbacks.OnDisconnect.Invoke(conn, peerDisconnectPacket);
|
||||
if (conn.AccountInfo.AccountId is Some<AccountId> { Value: SteamId steamId }) { SteamManager.StopAuthSession(steamId); }
|
||||
}
|
||||
|
||||
lidgrenConn.NetConnection.Disconnect(msg ?? "Disconnected");
|
||||
lidgrenConn.NetConnection.Disconnect(peerDisconnectPacket.ToLidgrenStringRepresentation());
|
||||
}
|
||||
|
||||
protected override void SendMsgInternal(NetworkConnection conn, PeerPacketHeaders headers, INetSerializableStruct? body)
|
||||
@@ -413,6 +408,7 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
ownerKey = Option<int>.None();
|
||||
OwnerConnection = pendingClient.Connection;
|
||||
callbacks.OnOwnerDetermined.Invoke(OwnerConnection);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -440,7 +436,7 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
if (requireSteamAuth)
|
||||
{
|
||||
RemovePendingClient(pendingClient, DisconnectReason.SteamAuthenticationFailed, "Steam auth session failed to start: Steam ID not provided");
|
||||
RemovePendingClient(pendingClient, PeerDisconnectPacket.WithReason(DisconnectReason.SteamAuthenticationFailed));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -451,7 +447,7 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
if (requireSteamAuth)
|
||||
{
|
||||
RemovePendingClient(pendingClient, DisconnectReason.SteamAuthenticationFailed, $"Steam auth session failed to start: {authSessionStartState}");
|
||||
RemovePendingClient(pendingClient, PeerDisconnectPacket.SteamAuthError(authSessionStartState));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -471,7 +467,7 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
if (pendingClient.AccountInfo.AccountId != packet.SteamId.Select(uid => (AccountId)uid))
|
||||
{
|
||||
RemovePendingClient(pendingClient, DisconnectReason.SteamAuthenticationFailed, "SteamID mismatch");
|
||||
RemovePendingClient(pendingClient, PeerDisconnectPacket.WithReason(DisconnectReason.SteamAuthenticationFailed));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,26 +9,31 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
internal abstract class ServerPeer
|
||||
{
|
||||
public delegate void MessageCallback(NetworkConnection connection, IReadMessage message);
|
||||
public readonly record struct Callbacks(
|
||||
Callbacks.MessageCallback OnMessageReceived,
|
||||
Callbacks.DisconnectCallback OnDisconnect,
|
||||
Callbacks.InitializationCompleteCallback OnInitializationComplete,
|
||||
Callbacks.ShutdownCallback OnShutdown,
|
||||
Callbacks.OwnerDeterminedCallback OnOwnerDetermined)
|
||||
{
|
||||
public delegate void MessageCallback(NetworkConnection connection, IReadMessage message);
|
||||
public delegate void DisconnectCallback(NetworkConnection connection, PeerDisconnectPacket peerDisconnectPacket);
|
||||
public delegate void InitializationCompleteCallback(NetworkConnection connection, string? clientName);
|
||||
public delegate void ShutdownCallback();
|
||||
public delegate void OwnerDeterminedCallback(NetworkConnection connection);
|
||||
}
|
||||
|
||||
public delegate void DisconnectCallback(NetworkConnection connection, string? reason);
|
||||
|
||||
public delegate void InitializationCompleteCallback(NetworkConnection connection, string? clientName);
|
||||
|
||||
public delegate void ShutdownCallback();
|
||||
|
||||
public delegate void OwnerDeterminedCallback(NetworkConnection connection);
|
||||
|
||||
public MessageCallback? OnMessageReceived;
|
||||
public DisconnectCallback? OnDisconnect;
|
||||
public InitializationCompleteCallback? OnInitializationComplete;
|
||||
public ShutdownCallback? OnShutdown;
|
||||
public OwnerDeterminedCallback? OnOwnerDetermined;
|
||||
protected readonly Callbacks callbacks;
|
||||
|
||||
protected ServerPeer(Callbacks callbacks)
|
||||
{
|
||||
this.callbacks = callbacks;
|
||||
}
|
||||
|
||||
public abstract void InitializeSteamServerCallbacks();
|
||||
|
||||
public abstract void Start();
|
||||
public abstract void Close(string? msg = null);
|
||||
public abstract void Close();
|
||||
public abstract void Update(float deltaTime);
|
||||
|
||||
protected sealed class PendingClient
|
||||
@@ -84,15 +89,16 @@ namespace Barotrauma.Networking
|
||||
|
||||
if (!Client.IsValidName(authPacket.Name, serverSettings))
|
||||
{
|
||||
RemovePendingClient(pendingClient, DisconnectReason.InvalidName, "");
|
||||
RemovePendingClient(pendingClient, PeerDisconnectPacket.WithReason(DisconnectReason.InvalidName));
|
||||
return;
|
||||
}
|
||||
|
||||
bool isCompatibleVersion = NetworkMember.IsCompatible(authPacket.GameVersion, GameMain.Version.ToString()) ?? false;
|
||||
bool isCompatibleVersion =
|
||||
Version.TryParse(authPacket.GameVersion, out var remoteVersion)
|
||||
&& NetworkMember.IsCompatible(remoteVersion, GameMain.Version);
|
||||
if (!isCompatibleVersion)
|
||||
{
|
||||
RemovePendingClient(pendingClient, DisconnectReason.InvalidVersion,
|
||||
$"DisconnectMessage.InvalidVersion~[version]={GameMain.Version}~[clientversion]={authPacket.GameVersion}");
|
||||
RemovePendingClient(pendingClient, PeerDisconnectPacket.InvalidVersion());
|
||||
|
||||
GameServer.Log($"{authPacket.Name} ({authPacket.SteamId}) couldn't join the server (incompatible game version)", ServerLog.MessageType.Error);
|
||||
DebugConsole.NewMessage($"{authPacket.Name} ({authPacket.SteamId}) couldn't join the server (incompatible game version)", Microsoft.Xna.Framework.Color.Red);
|
||||
@@ -104,7 +110,7 @@ namespace Barotrauma.Networking
|
||||
Client nameTaken = GameMain.Server.ConnectedClients.Find(c => Homoglyphs.Compare(c.Name.ToLower(), authPacket.Name.ToLower()));
|
||||
if (nameTaken != null)
|
||||
{
|
||||
RemovePendingClient(pendingClient, DisconnectReason.NameTaken, "");
|
||||
RemovePendingClient(pendingClient, PeerDisconnectPacket.WithReason(DisconnectReason.NameTaken));
|
||||
GameServer.Log($"{authPacket.Name} ({authPacket.SteamId}) couldn't join the server (name too similar to the name of the client \"" + nameTaken.Name + "\").", ServerLog.MessageType.Error);
|
||||
return;
|
||||
}
|
||||
@@ -135,7 +141,7 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
const string banMsg = "Failed to enter correct password too many times";
|
||||
BanPendingClient(pendingClient, banMsg, null);
|
||||
RemovePendingClient(pendingClient, DisconnectReason.Banned, banMsg);
|
||||
RemovePendingClient(pendingClient, PeerDisconnectPacket.Banned(banMsg));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -190,13 +196,13 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
if (IsPendingClientBanned(pendingClient, out string? banReason))
|
||||
{
|
||||
RemovePendingClient(pendingClient, DisconnectReason.Banned, banReason);
|
||||
RemovePendingClient(pendingClient, PeerDisconnectPacket.Banned(banReason));
|
||||
return;
|
||||
}
|
||||
|
||||
if (connectedClients.Count >= serverSettings.MaxPlayers)
|
||||
{
|
||||
RemovePendingClient(pendingClient, DisconnectReason.ServerFull, "");
|
||||
RemovePendingClient(pendingClient, PeerDisconnectPacket.WithReason(DisconnectReason.ServerFull));
|
||||
}
|
||||
|
||||
if (pendingClient.InitializationStep == ConnectionInitialization.Success)
|
||||
@@ -205,15 +211,15 @@ namespace Barotrauma.Networking
|
||||
connectedClients.Add(newConnection);
|
||||
pendingClients.Remove(pendingClient);
|
||||
|
||||
CheckOwnership(pendingClient);
|
||||
callbacks.OnInitializationComplete.Invoke(newConnection, pendingClient.Name);
|
||||
|
||||
OnInitializationComplete?.Invoke(newConnection, pendingClient.Name);
|
||||
CheckOwnership(pendingClient);
|
||||
}
|
||||
|
||||
pendingClient.TimeOut -= Timing.Step;
|
||||
if (pendingClient.TimeOut < 0.0)
|
||||
{
|
||||
RemovePendingClient(pendingClient, DisconnectReason.Unknown, Lidgren.Network.NetConnection.NoResponseMessage);
|
||||
RemovePendingClient(pendingClient, PeerDisconnectPacket.WithReason(DisconnectReason.Timeout));
|
||||
}
|
||||
|
||||
if (Timing.TotalTime < pendingClient.UpdateTime) { return; }
|
||||
@@ -237,7 +243,7 @@ namespace Barotrauma.Networking
|
||||
structToSend = new ServerPeerContentPackageOrderPacket
|
||||
{
|
||||
ServerName = GameMain.Server.ServerName,
|
||||
ContentPackages = ContentPackageManager.EnabledPackages.All.Where(cp => cp.HasMultiplayerSyncedContent)
|
||||
ContentPackages = ContentPackageManager.EnabledPackages.All.Where(cp => cp.HasMultiplayerSyncedContent || cp.Files.All(f => f is SubmarineFile))
|
||||
.Select(contentPackage => new ServerContentPackage(contentPackage, timeNow))
|
||||
.ToImmutableArray()
|
||||
};
|
||||
@@ -267,11 +273,11 @@ namespace Barotrauma.Networking
|
||||
|
||||
protected virtual void CheckOwnership(PendingClient pendingClient) { }
|
||||
|
||||
protected void RemovePendingClient(PendingClient pendingClient, DisconnectReason reason, string? msg)
|
||||
protected void RemovePendingClient(PendingClient pendingClient, PeerDisconnectPacket peerDisconnectPacket)
|
||||
{
|
||||
if (pendingClients.Contains(pendingClient))
|
||||
{
|
||||
Disconnect(pendingClient.Connection, $"{reason}/{msg}");
|
||||
Disconnect(pendingClient.Connection, peerDisconnectPacket);
|
||||
|
||||
pendingClients.Remove(pendingClient);
|
||||
|
||||
@@ -285,6 +291,6 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
|
||||
public abstract void Send(IWriteMessage msg, NetworkConnection conn, DeliveryMethod deliveryMethod, bool compressPastThreshold = true);
|
||||
public abstract void Disconnect(NetworkConnection conn, string? msg = null);
|
||||
public abstract void Disconnect(NetworkConnection conn, PeerDisconnectPacket peerDisconnectPacket);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
@@ -16,7 +17,7 @@ namespace Barotrauma.Networking
|
||||
private SteamId ReadSteamId(IReadMessage inc) => new SteamId(inc.ReadUInt64() ^ ownerKey64);
|
||||
private void WriteSteamId(IWriteMessage msg, SteamId val) => msg.WriteUInt64(val.Value ^ ownerKey64);
|
||||
|
||||
public SteamP2PServerPeer(SteamId steamId, int ownerKey, ServerSettings settings)
|
||||
public SteamP2PServerPeer(SteamId steamId, int ownerKey, ServerSettings settings, Callbacks callbacks) : base(callbacks)
|
||||
{
|
||||
serverSettings = settings;
|
||||
|
||||
@@ -43,7 +44,7 @@ namespace Barotrauma.Networking
|
||||
started = true;
|
||||
}
|
||||
|
||||
public override void Close(string? msg = null)
|
||||
public override void Close()
|
||||
{
|
||||
if (!started) { return; }
|
||||
|
||||
@@ -51,12 +52,12 @@ namespace Barotrauma.Networking
|
||||
|
||||
for (int i = pendingClients.Count - 1; i >= 0; i--)
|
||||
{
|
||||
RemovePendingClient(pendingClients[i], DisconnectReason.ServerShutdown, msg);
|
||||
RemovePendingClient(pendingClients[i], PeerDisconnectPacket.WithReason(DisconnectReason.ServerShutdown));
|
||||
}
|
||||
|
||||
for (int i = connectedClients.Count - 1; i >= 0; i--)
|
||||
{
|
||||
Disconnect(connectedClients[i], msg ?? DisconnectReason.ServerShutdown.ToString());
|
||||
Disconnect(connectedClients[i], PeerDisconnectPacket.WithReason(DisconnectReason.ServerShutdown));
|
||||
}
|
||||
|
||||
pendingClients.Clear();
|
||||
@@ -64,19 +65,13 @@ namespace Barotrauma.Networking
|
||||
|
||||
ChildServerRelay.ShutDown();
|
||||
|
||||
OnShutdown?.Invoke();
|
||||
callbacks.OnShutdown.Invoke();
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (!started) { return; }
|
||||
|
||||
if (OnOwnerDetermined != null && OwnerConnection != null)
|
||||
{
|
||||
OnOwnerDetermined?.Invoke(OwnerConnection);
|
||||
OnOwnerDetermined = null;
|
||||
}
|
||||
|
||||
//backwards for loop so we can remove elements while iterating
|
||||
for (int i = connectedClients.Count - 1; i >= 0; i--)
|
||||
{
|
||||
@@ -84,7 +79,7 @@ namespace Barotrauma.Networking
|
||||
conn.Decay(deltaTime);
|
||||
if (conn.Timeout < 0.0)
|
||||
{
|
||||
Disconnect(conn, "Timed out");
|
||||
Disconnect(conn, PeerDisconnectPacket.WithReason(DisconnectReason.Timeout));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,24 +144,22 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
if (pendingClient != null)
|
||||
{
|
||||
RemovePendingClient(pendingClient, DisconnectReason.Banned, banReason);
|
||||
RemovePendingClient(pendingClient, PeerDisconnectPacket.Banned(banReason));
|
||||
}
|
||||
else if (connectedClient != null)
|
||||
{
|
||||
Disconnect(connectedClient, $"{DisconnectReason.Banned}/ {banReason}");
|
||||
Disconnect(connectedClient, PeerDisconnectPacket.Banned(banReason));
|
||||
}
|
||||
}
|
||||
else if (packetHeader.IsDisconnectMessage())
|
||||
{
|
||||
if (pendingClient != null)
|
||||
{
|
||||
string disconnectMsg = $"ServerMessage.HasDisconnected~[client]={pendingClient.Name}";
|
||||
RemovePendingClient(pendingClient, DisconnectReason.Unknown, disconnectMsg);
|
||||
RemovePendingClient(pendingClient, PeerDisconnectPacket.WithReason(DisconnectReason.Disconnected));
|
||||
}
|
||||
else if (connectedClient != null)
|
||||
{
|
||||
string disconnectMsg = $"ServerMessage.HasDisconnected~[client]={GameMain.Server.ConnectedClients.First(c => c.Connection == connectedClient).Name}";
|
||||
Disconnect(connectedClient, disconnectMsg, false);
|
||||
Disconnect(connectedClient, PeerDisconnectPacket.WithReason(DisconnectReason.Disconnected));
|
||||
}
|
||||
}
|
||||
else if (packetHeader.IsHeartbeatMessage())
|
||||
@@ -176,28 +169,27 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
else if (packetHeader.IsConnectionInitializationStep())
|
||||
{
|
||||
if (!initialization.HasValue) { return; }
|
||||
ConnectionInitialization initializationStep = initialization.Value;
|
||||
|
||||
if (pendingClient != null)
|
||||
{
|
||||
pendingClient.Connection.SetAccountInfo(new AccountInfo(senderSteamId, sentOwnerSteamId));
|
||||
ReadConnectionInitializationStep(
|
||||
pendingClient,
|
||||
new ReadWriteMessage(inc.Buffer, inc.BitPosition, inc.LengthBits, false),
|
||||
initialization ?? throw new Exception("Initialization step missing"));
|
||||
initializationStep);
|
||||
}
|
||||
else if (initialization.HasValue)
|
||||
else if (initializationStep == ConnectionInitialization.ConnectionStarted)
|
||||
{
|
||||
ConnectionInitialization initializationStep = initialization.Value;
|
||||
if (initializationStep == ConnectionInitialization.ConnectionStarted)
|
||||
{
|
||||
pendingClients.Add(new PendingClient(new SteamP2PConnection(senderSteamId)));
|
||||
}
|
||||
pendingClients.Add(new PendingClient(new SteamP2PConnection(senderSteamId)));
|
||||
}
|
||||
}
|
||||
else if (connectedClient != null)
|
||||
{
|
||||
var packet = INetSerializableStruct.Read<PeerPacketMessage>(inc);
|
||||
IReadMessage msg = new ReadOnlyMessage(packet.Buffer, packetHeader.IsCompressed(), 0, packet.Length, connectedClient);
|
||||
OnMessageReceived?.Invoke(connectedClient, msg);
|
||||
callbacks.OnMessageReceived.Invoke(connectedClient, msg);
|
||||
}
|
||||
}
|
||||
else //sender is owner
|
||||
@@ -227,7 +219,8 @@ namespace Barotrauma.Networking
|
||||
};
|
||||
OwnerConnection.SetAccountInfo(new AccountInfo(ownerSteamId, ownerSteamId));
|
||||
|
||||
OnInitializationComplete?.Invoke(OwnerConnection, packet.OwnerName);
|
||||
callbacks.OnInitializationComplete.Invoke(OwnerConnection, packet.OwnerName);
|
||||
callbacks.OnOwnerDetermined.Invoke(OwnerConnection);
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -241,7 +234,7 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
var packet = INetSerializableStruct.Read<PeerPacketMessage>(inc);
|
||||
IReadMessage msg = new ReadOnlyMessage(packet.Buffer, packetHeader.IsCompressed(), 0, packet.Length, OwnerConnection);
|
||||
OnMessageReceived?.Invoke(OwnerConnection!, msg);
|
||||
callbacks.OnMessageReceived.Invoke(OwnerConnection!, msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -281,27 +274,21 @@ namespace Barotrauma.Networking
|
||||
SendMsgInternal(steamP2PConn, headers, body);
|
||||
}
|
||||
|
||||
private void SendDisconnectMessage(SteamId steamId, string? msg)
|
||||
private void SendDisconnectMessage(SteamId steamId, PeerDisconnectPacket peerDisconnectPacket)
|
||||
{
|
||||
if (!started) { return; }
|
||||
|
||||
if (string.IsNullOrWhiteSpace(msg)) { return; }
|
||||
|
||||
var headers = new PeerPacketHeaders
|
||||
{
|
||||
DeliveryMethod = DeliveryMethod.Reliable,
|
||||
PacketHeader = PacketHeader.IsDisconnectMessage | PacketHeader.IsServerMessage,
|
||||
Initialization = null
|
||||
};
|
||||
var packet = new PeerDisconnectPacket
|
||||
{
|
||||
Message = msg
|
||||
};
|
||||
|
||||
SendMsgInternal(steamId, headers, packet);
|
||||
SendMsgInternal(steamId, headers, peerDisconnectPacket);
|
||||
}
|
||||
|
||||
private void Disconnect(NetworkConnection conn, string? msg, bool sendDisconnectMessage)
|
||||
public override void Disconnect(NetworkConnection conn, PeerDisconnectPacket peerDisconnectPacket)
|
||||
{
|
||||
if (!started) { return; }
|
||||
|
||||
@@ -309,26 +296,21 @@ namespace Barotrauma.Networking
|
||||
|
||||
if (!conn.AccountInfo.AccountId.TryUnwrap(out var connAccountId) || !(connAccountId is SteamId connSteamId)) { return; }
|
||||
|
||||
if (sendDisconnectMessage) { SendDisconnectMessage(connSteamId, msg); }
|
||||
SendDisconnectMessage(connSteamId, peerDisconnectPacket);
|
||||
|
||||
if (connectedClients.Contains(steamp2pConn))
|
||||
{
|
||||
steamp2pConn.Status = NetworkConnectionStatus.Disconnected;
|
||||
connectedClients.Remove(steamp2pConn);
|
||||
OnDisconnect?.Invoke(conn, msg);
|
||||
callbacks.OnDisconnect.Invoke(conn, peerDisconnectPacket);
|
||||
Steam.SteamManager.StopAuthSession(connSteamId);
|
||||
}
|
||||
else if (steamp2pConn == OwnerConnection)
|
||||
{
|
||||
//TODO: fix?
|
||||
throw new InvalidOperationException("Cannot disconnect owner peer");
|
||||
}
|
||||
}
|
||||
|
||||
public override void Disconnect(NetworkConnection conn, string? msg = null)
|
||||
{
|
||||
Disconnect(conn, msg, true);
|
||||
}
|
||||
|
||||
protected override void SendMsgInternal(NetworkConnection conn, PeerPacketHeaders headers, INetSerializableStruct? body)
|
||||
{
|
||||
var connSteamId = conn is SteamP2PConnection { Endpoint: SteamP2PEndpoint { SteamId: var id } } ? id : null;
|
||||
|
||||
@@ -505,9 +505,10 @@ namespace Barotrauma.Networking
|
||||
var characterData = campaign?.GetClientCharacterData(clients[i]);
|
||||
if (characterData != null && Level.Loaded?.Type != LevelData.LevelType.Outpost && characterData.HasSpawned)
|
||||
{
|
||||
//we need to reapply the previous respawn penalty affliction or successive deaths won't make it stack
|
||||
characterData.ApplyHealthData(character, (AfflictionPrefab ap) => ap == GetRespawnPenaltyAfflictionPrefab());
|
||||
GiveRespawnPenaltyAffliction(character);
|
||||
}
|
||||
|
||||
if (characterData == null || characterData.HasSpawned)
|
||||
{
|
||||
//give the character the items they would've gotten if they had spawned in the main sub
|
||||
@@ -555,7 +556,7 @@ namespace Barotrauma.Networking
|
||||
foreach (Skill skill in characterInfo.Job.GetSkills())
|
||||
{
|
||||
var skillPrefab = characterInfo.Job.Prefab.Skills.Find(s => skill.Identifier == s.Identifier);
|
||||
if (skillPrefab == null) { continue; }
|
||||
if (skillPrefab == null || skill.Level < skillPrefab.LevelRange.End) { continue; }
|
||||
skill.Level = MathHelper.Lerp(skill.Level, skillPrefab.LevelRange.End, SkillReductionOnDeath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,10 +18,14 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
if (!PropEquals(lastSyncedValue, Value))
|
||||
{
|
||||
LastUpdateID = (UInt16)(GameMain.NetLobbyScreen.LastUpdateID);
|
||||
LastUpdateID = GameMain.NetLobbyScreen.LastUpdateID;
|
||||
lastSyncedValue = Value;
|
||||
}
|
||||
}
|
||||
public void ForceUpdate()
|
||||
{
|
||||
LastUpdateID = GameMain.NetLobbyScreen.LastUpdateID++;
|
||||
}
|
||||
}
|
||||
|
||||
public static readonly string ClientPermissionsFile = "Data" + Path.DirectorySeparatorChar + "clientpermissions.xml";
|
||||
@@ -54,6 +58,15 @@ namespace Barotrauma.Networking
|
||||
LoadClientPermissions();
|
||||
}
|
||||
|
||||
public void ForcePropertyUpdate()
|
||||
{
|
||||
UpdateFlag(NetFlags.Properties);
|
||||
foreach (NetPropertyData property in netProperties.Values)
|
||||
{
|
||||
property.ForceUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteNetProperties(IWriteMessage outMsg, Client c)
|
||||
{
|
||||
foreach (UInt32 key in netProperties.Keys)
|
||||
@@ -197,27 +210,32 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
int orBits = incMsg.ReadRangedInteger(0, (int)Barotrauma.MissionType.All) & (int)Barotrauma.MissionType.All;
|
||||
int andBits = incMsg.ReadRangedInteger(0, (int)Barotrauma.MissionType.All) & (int)Barotrauma.MissionType.All;
|
||||
GameMain.NetLobbyScreen.MissionType = (Barotrauma.MissionType)(((int)GameMain.NetLobbyScreen.MissionType | orBits) & andBits);
|
||||
GameMain.NetLobbyScreen.MissionType = (MissionType)(((int)GameMain.NetLobbyScreen.MissionType | orBits) & andBits);
|
||||
|
||||
int traitorSetting = (int)TraitorsEnabled + incMsg.ReadByte() - 1;
|
||||
if (traitorSetting < 0) traitorSetting = 2;
|
||||
if (traitorSetting > 2) traitorSetting = 0;
|
||||
if (traitorSetting < 0) { traitorSetting = 2; }
|
||||
if (traitorSetting > 2) { traitorSetting = 0; }
|
||||
TraitorsEnabled = (YesNoMaybe)traitorSetting;
|
||||
|
||||
int botCount = BotCount + incMsg.ReadByte() - 1;
|
||||
if (botCount < 0) botCount = MaxBotCount;
|
||||
if (botCount > MaxBotCount) botCount = 0;
|
||||
if (botCount < 0) { botCount = MaxBotCount; }
|
||||
if (botCount > MaxBotCount) { botCount = 0; }
|
||||
BotCount = botCount;
|
||||
|
||||
int botSpawnMode = (int)BotSpawnMode + incMsg.ReadByte() - 1;
|
||||
if (botSpawnMode < 0) botSpawnMode = 1;
|
||||
if (botSpawnMode > 1) botSpawnMode = 0;
|
||||
if (botSpawnMode < 0) { botSpawnMode = 1; }
|
||||
if (botSpawnMode > 1) { botSpawnMode = 0; }
|
||||
BotSpawnMode = (BotSpawnMode)botSpawnMode;
|
||||
|
||||
float levelDifficulty = incMsg.ReadSingle();
|
||||
if (levelDifficulty >= 0.0f) SelectedLevelDifficulty = levelDifficulty;
|
||||
if (levelDifficulty >= 0.0f) { SelectedLevelDifficulty = levelDifficulty; }
|
||||
|
||||
UseRespawnShuttle = incMsg.ReadBoolean();
|
||||
bool changedUseRespawnShuttle = incMsg.ReadBoolean();
|
||||
bool useRespawnShuttle = incMsg.ReadBoolean();
|
||||
if (changedUseRespawnShuttle)
|
||||
{
|
||||
UseRespawnShuttle = useRespawnShuttle;
|
||||
}
|
||||
|
||||
bool changedAutoRestart = incMsg.ReadBoolean();
|
||||
bool autoRestart = incMsg.ReadBoolean();
|
||||
|
||||
@@ -216,6 +216,14 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetVotes(IEnumerable<Client> connectedClients, bool resetKickVotes)
|
||||
{
|
||||
foreach (Client client in connectedClients)
|
||||
{
|
||||
client.ResetVotes(resetKickVotes);
|
||||
}
|
||||
}
|
||||
|
||||
public void ServerRead(IReadMessage inc, Client sender)
|
||||
{
|
||||
if (GameMain.Server == null || sender == null) { return; }
|
||||
|
||||
@@ -192,7 +192,7 @@ namespace Barotrauma
|
||||
public override void Select()
|
||||
{
|
||||
base.Select();
|
||||
GameMain.Server.Voting.ResetVotes(GameMain.Server.ConnectedClients);
|
||||
GameMain.Server.Voting.ResetVotes(GameMain.Server.ConnectedClients, resetKickVotes: false);
|
||||
if (SelectedMode != GameModePreset.MultiPlayerCampaign && GameMain.GameSession?.GameMode is CampaignMode && Selected == this)
|
||||
{
|
||||
GameMain.GameSession = null;
|
||||
|
||||
@@ -53,12 +53,13 @@ namespace Barotrauma.Steam
|
||||
Steamworks.SteamServer.Passworded = server.ServerSettings.HasPassword;
|
||||
Steamworks.SteamServer.MapName = GameMain.NetLobbyScreen?.SelectedSub?.DisplayName?.Value ?? "";
|
||||
Steamworks.SteamServer.SetKey("haspassword", server.ServerSettings.HasPassword.ToString());
|
||||
Steamworks.SteamServer.SetKey("message", GameMain.Server.ServerSettings.ServerMessageText);
|
||||
Steamworks.SteamServer.SetKey("message", server.ServerSettings.ServerMessageText);
|
||||
Steamworks.SteamServer.SetKey("version", GameMain.Version.ToString());
|
||||
Steamworks.SteamServer.SetKey("playercount", GameMain.Server.ConnectedClients.Count.ToString());
|
||||
Steamworks.SteamServer.SetKey("playercount", server.ConnectedClients.Count.ToString());
|
||||
Steamworks.SteamServer.SetKey("contentpackage", string.Join(",", contentPackages.Select(cp => cp.Name)));
|
||||
Steamworks.SteamServer.SetKey("contentpackagehash", string.Join(",", contentPackages.Select(cp => cp.Hash.StringRepresentation)));
|
||||
Steamworks.SteamServer.SetKey("contentpackageid", string.Join(",", contentPackages.Select(cp => cp.SteamWorkshopId)));
|
||||
Steamworks.SteamServer.SetKey("contentpackageid", string.Join(",", contentPackages.Select(cp
|
||||
=> cp.UgcId.TryUnwrap(out var ugcId) ? ugcId.StringRepresentation : "")));
|
||||
Steamworks.SteamServer.SetKey("modeselectionmode", server.ServerSettings.ModeSelectionMode.ToString());
|
||||
Steamworks.SteamServer.SetKey("subselectionmode", server.ServerSettings.SubSelectionMode.ToString());
|
||||
Steamworks.SteamServer.SetKey("voicechatenabled", server.ServerSettings.VoiceChatEnabled.ToString());
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
<Authors>FakeFish, Undertow Games</Authors>
|
||||
<Product>Barotrauma Dedicated Server</Product>
|
||||
<Version>0.19.3.0</Version>
|
||||
<Version>0.19.5.0</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2022</Copyright>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AssemblyName>DedicatedServer</AssemblyName>
|
||||
|
||||
@@ -527,7 +527,7 @@ namespace Barotrauma
|
||||
{
|
||||
// We'll want this to run each time, because the delegate is used to find a valid button component.
|
||||
bool canAccessButtons = false;
|
||||
foreach (var button in door.Item.GetConnectedComponents<Controller>(true))
|
||||
foreach (var button in door.Item.GetConnectedComponents<Controller>(true, connectionFilter: c => c.Name == "toggle" || c.Name == "set_state"))
|
||||
{
|
||||
if (button.HasAccess(character) && (buttonFilter == null || buttonFilter(button)))
|
||||
{
|
||||
@@ -675,6 +675,8 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
float distance = Vector2.DistanceSquared(button.Item.WorldPosition, character.WorldPosition);
|
||||
//heavily prefer buttons linked to the door, so sub builders can help the bots figure out which button to use by linking them
|
||||
if (door.Item.linkedTo.Contains(button.Item)) { distance *= 0.1f; }
|
||||
if (closestButton == null || distance < closestDist && character.CanSeeTarget(button.Item))
|
||||
{
|
||||
closestButton = button;
|
||||
|
||||
@@ -1634,9 +1634,9 @@ namespace Barotrauma
|
||||
return id;
|
||||
}
|
||||
|
||||
public static void ApplyHealthData(Character character, XElement healthData)
|
||||
public static void ApplyHealthData(Character character, XElement healthData, Func<AfflictionPrefab, bool> afflictionPredicate = null)
|
||||
{
|
||||
if (healthData != null) { character?.CharacterHealth.Load(healthData); }
|
||||
if (healthData != null) { character?.CharacterHealth.Load(healthData, afflictionPredicate); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -12,12 +12,8 @@ namespace Barotrauma
|
||||
{
|
||||
public readonly static PrefabCollection<CharacterPrefab> Prefabs = new PrefabCollection<CharacterPrefab>();
|
||||
|
||||
private bool disposed = false;
|
||||
public override void Dispose()
|
||||
{
|
||||
if (disposed) { return; }
|
||||
disposed = true;
|
||||
Prefabs.Remove(this);
|
||||
Character.RemoveByPrefab(this);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,13 +11,7 @@ namespace Barotrauma
|
||||
{
|
||||
public static readonly PrefabCollection<CorpsePrefab> Prefabs = new PrefabCollection<CorpsePrefab>();
|
||||
|
||||
private bool disposed = false;
|
||||
public override void Dispose()
|
||||
{
|
||||
if (disposed) { return; }
|
||||
disposed = true;
|
||||
Prefabs.Remove(this);
|
||||
}
|
||||
public override void Dispose() { }
|
||||
|
||||
public static CorpsePrefab Get(Identifier identifier)
|
||||
{
|
||||
|
||||
@@ -1227,7 +1227,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public void Load(XElement element)
|
||||
public void Load(XElement element, Func<AfflictionPrefab, bool> afflictionPredicate = null)
|
||||
{
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
@@ -1260,6 +1260,7 @@ namespace Barotrauma
|
||||
DebugConsole.ThrowError($"Error while loading character health: affliction \"{id}\" not found.");
|
||||
return;
|
||||
}
|
||||
if (afflictionPredicate != null && !afflictionPredicate.Invoke(afflictionPrefab)) { return; }
|
||||
float strength = afflictionElement.GetAttributeFloat("strength", 0.0f);
|
||||
var irremovableAffliction = irremovableAfflictions.FirstOrDefault(a => a.Prefab == afflictionPrefab);
|
||||
if (irremovableAffliction != null)
|
||||
|
||||
@@ -65,13 +65,7 @@ namespace Barotrauma
|
||||
{
|
||||
public static readonly PrefabCollection<JobPrefab> Prefabs = new PrefabCollection<JobPrefab>();
|
||||
|
||||
private bool disposed = false;
|
||||
public override void Dispose()
|
||||
{
|
||||
if (disposed) { return; }
|
||||
disposed = true;
|
||||
Prefabs.Remove(this);
|
||||
}
|
||||
public override void Dispose() { }
|
||||
|
||||
private static readonly Dictionary<Identifier, float> _itemRepairPriorities = new Dictionary<Identifier, float>();
|
||||
/// <summary>
|
||||
|
||||
@@ -56,11 +56,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private bool disposed = false;
|
||||
public override void Dispose()
|
||||
{
|
||||
if (disposed) { return; }
|
||||
disposed = true;
|
||||
}
|
||||
public override void Dispose() { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,9 @@ namespace Barotrauma
|
||||
}
|
||||
catch
|
||||
{
|
||||
prefab.Dispose(); //clean up before rethrowing, since some prefab types might lock resources
|
||||
//clean up before rethrowing, since some prefab types might lock resources
|
||||
prefab.Dispose();
|
||||
Prefabs.Remove(prefab);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,25 +29,25 @@ namespace Barotrauma
|
||||
public readonly ImmutableArray<string> AltNames;
|
||||
public readonly string Path;
|
||||
public string Dir => Barotrauma.IO.Path.GetDirectoryName(Path) ?? "";
|
||||
public readonly UInt64 SteamWorkshopId;
|
||||
public readonly Option<ContentPackageId> UgcId;
|
||||
|
||||
public readonly Version GameVersion;
|
||||
public readonly string ModVersion;
|
||||
public Md5Hash Hash { get; private set; }
|
||||
public readonly DateTime? InstallTime;
|
||||
public readonly Option<DateTime> InstallTime;
|
||||
|
||||
public ImmutableArray<ContentFile> Files { get; private set; }
|
||||
public ImmutableArray<ContentFile.LoadError> Errors { get; private set; }
|
||||
|
||||
public async Task<bool> IsUpToDate()
|
||||
{
|
||||
if (SteamWorkshopId != 0 && InstallTime.HasValue)
|
||||
{
|
||||
Steamworks.Ugc.Item? item = await SteamManager.Workshop.GetItem(SteamWorkshopId);
|
||||
if (item is null) { return true; }
|
||||
return item.Value.LatestUpdateTime <= InstallTime;
|
||||
}
|
||||
return true;
|
||||
if (!UgcId.TryUnwrap(out var ugcId)) { return true; }
|
||||
if (!(ugcId is SteamWorkshopId steamWorkshopId)) { return true; }
|
||||
if (!InstallTime.TryUnwrap(out var installTime)) { return true; }
|
||||
|
||||
Steamworks.Ugc.Item? item = await SteamManager.Workshop.GetItem(steamWorkshopId.Value);
|
||||
if (item is null) { return true; }
|
||||
return item.Value.LatestUpdateTime <= installTime;
|
||||
}
|
||||
|
||||
public int Index => ContentPackageManager.EnabledPackages.IndexOf(this);
|
||||
@@ -66,18 +66,19 @@ namespace Barotrauma
|
||||
AltNames = rootElement.GetAttributeStringArray("altnames", Array.Empty<string>())
|
||||
.Select(n => n.Trim()).ToImmutableArray();
|
||||
AssertCondition(!string.IsNullOrEmpty(Name), "Name is null or empty");
|
||||
SteamWorkshopId = rootElement.GetAttributeUInt64("steamworkshopid", 0);
|
||||
|
||||
UInt64 steamWorkshopId = rootElement.GetAttributeUInt64("steamworkshopid", 0);
|
||||
|
||||
UgcId = steamWorkshopId != 0
|
||||
? Option<ContentPackageId>.Some(new SteamWorkshopId(steamWorkshopId))
|
||||
: Option<ContentPackageId>.None();
|
||||
|
||||
GameVersion = rootElement.GetAttributeVersion("gameversion", GameMain.Version);
|
||||
ModVersion = rootElement.GetAttributeString("modversion", DefaultModVersion);
|
||||
if (rootElement.Attribute("installtime") != null)
|
||||
{
|
||||
InstallTime = ToolBox.Epoch.ToDateTime(rootElement.GetAttributeUInt("installtime", 0));
|
||||
}
|
||||
else
|
||||
{
|
||||
InstallTime = null;
|
||||
}
|
||||
UInt64 installTimeUnix = rootElement.GetAttributeUInt64("installtime", 0);
|
||||
InstallTime = installTimeUnix != 0
|
||||
? Option<DateTime>.Some(ToolBox.Epoch.ToDateTime(installTimeUnix))
|
||||
: Option<DateTime>.None();
|
||||
|
||||
var fileResults = rootElement.Elements()
|
||||
.Select(e => ContentFile.CreateFromXElement(this, e))
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
#nullable enable
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public abstract class ContentPackageId
|
||||
{
|
||||
public abstract string StringRepresentation { get; }
|
||||
|
||||
public override string ToString()
|
||||
=> StringRepresentation;
|
||||
|
||||
public abstract override bool Equals(object? obj);
|
||||
|
||||
public abstract override int GetHashCode();
|
||||
|
||||
public static Option<ContentPackageId> Parse(string s)
|
||||
=> ReflectionUtils.ParseDerived<ContentPackageId, string>(s);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
sealed class SteamWorkshopId : ContentPackageId
|
||||
{
|
||||
public readonly UInt64 Value;
|
||||
|
||||
public SteamWorkshopId(UInt64 value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
private const string Prefix = "STEAM_WORKSHOP_";
|
||||
|
||||
public override string StringRepresentation => Value.ToString(CultureInfo.InvariantCulture);
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
=> obj is SteamWorkshopId otherWorkshopId && otherWorkshopId.Value == Value;
|
||||
|
||||
public override int GetHashCode() => Value.GetHashCode();
|
||||
|
||||
public new static Option<SteamWorkshopId> Parse(string s)
|
||||
{
|
||||
if (s.StartsWith(Prefix)) { s = s[Prefix.Length..]; }
|
||||
if (!UInt64.TryParse(s, out var id) || id == 0) { return Option<SteamWorkshopId>.None(); }
|
||||
return Option<SteamWorkshopId>.Some(new SteamWorkshopId(id));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -181,7 +181,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (Core != null && !ContentPackageManager.CorePackages.Contains(Core))
|
||||
{
|
||||
SetCore(ContentPackageManager.WorkshopPackages.Core.FirstOrDefault(p => p.SteamWorkshopId == Core.SteamWorkshopId) ??
|
||||
SetCore(ContentPackageManager.WorkshopPackages.Core.FirstOrDefault(p => p.UgcId == Core.UgcId) ??
|
||||
ContentPackageManager.CorePackages.First());
|
||||
}
|
||||
|
||||
@@ -193,7 +193,7 @@ namespace Barotrauma
|
||||
newRegular.Add(p);
|
||||
}
|
||||
else if (ContentPackageManager.WorkshopPackages.Regular.FirstOrDefault(p2
|
||||
=> p2.SteamWorkshopId == p.SteamWorkshopId) is { } newP)
|
||||
=> p2.UgcId == p.UgcId) is { } newP)
|
||||
{
|
||||
newRegular.Add(newP);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user