Files
LuaCsForBarotraumaEP/Barotrauma/BarotraumaClient/ClientSource/Steam/PublishTab.cs
T
Juan Pablo Arce 4206f6db42 Unstable 0.17.3.0
2022-03-22 14:44:56 -03:00

564 lines
25 KiB
C#

#nullable enable
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net.Mime;
using System.Threading.Tasks;
using Barotrauma.Extensions;
using Barotrauma.IO;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Directory = Barotrauma.IO.Directory;
using ItemOrPackage = Barotrauma.Either<Steamworks.Ugc.Item, Barotrauma.ContentPackage>;
using Path = Barotrauma.IO.Path;
namespace Barotrauma.Steam
{
public partial class WorkshopMenu
{
private class LocalThumbnail : IDisposable
{
public Texture2D? Texture { get; private set; } = null;
public bool Loading = true;
public LocalThumbnail(string path)
{
TaskPool.Add($"LocalThumbnail {path}",
Task.Run(async () =>
{
await Task.Yield();
return TextureLoader.FromFile(path, compress: false, mipmap: false);
}),
(t) =>
{
Loading = false;
Task<Texture2D?> texTask = (t as Task<Texture2D?>)!;
if (disposed)
{
texTask.Result?.Dispose();
}
else
{
Texture = texTask.Result;
}
});
}
~LocalThumbnail()
{
Dispose();
}
private bool disposed = false;
public void Dispose()
{
if (disposed) { return; }
disposed = true;
Texture?.Dispose();
}
}
private LocalThumbnail? localThumbnail = null;
private void CreateLocalThumbnail(string path, GUIFrame thumbnailContainer)
{
thumbnailContainer.ClearChildren();
localThumbnail?.Dispose();
localThumbnail = new LocalThumbnail(path);
CreateAsyncThumbnailComponent(thumbnailContainer, () => localThumbnail?.Texture, () => localThumbnail is { Loading: true });
}
private static async Task<(int FileCount, int ByteCount)> GetModDirInfo(string dir, GUITextBlock label)
{
int fileCount = 0;
int byteCount = 0;
var files = Directory.GetFiles(dir, pattern: "*", option: System.IO.SearchOption.AllDirectories);
foreach (var file in files)
{
await Task.Yield();
fileCount++;
byteCount += (int)(new Barotrauma.IO.FileInfo(file).Length);
label.Text = TextManager.GetWithVariables(
"ModDirInfo",
("[filecount]", fileCount.ToString(CultureInfo.InvariantCulture)),
("[size]", MathUtils.GetBytesReadable(byteCount)));
}
return (fileCount, byteCount);
}
private void DeselectPublishedItem()
{
var deselectCarrier = selfModsList.Parent.FindChild(c => c.UserData is ActionCarrier { Id: var id } && id == "deselect");
Action? deselectAction = deselectCarrier.UserData is ActionCarrier { Action: var action }
? action
: null;
deselectAction?.Invoke();
SelectTab(Tab.Publish);
}
private void PopulatePublishTab(ItemOrPackage itemOrPackage, GUIFrame parentFrame)
{
ContentPackageManager.LocalPackages.Refresh();
ContentPackageManager.WorkshopPackages.Refresh();
parentFrame.ClearChildren();
GUILayoutGroup mainLayout = new GUILayoutGroup(new RectTransform(Vector2.One, parentFrame.RectTransform),
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);
ContentPackage? workshopPackage
= ContentPackageManager.WorkshopPackages.FirstOrDefault(p => p.SteamWorkshopId == workshopItem.Id);
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);
new GUITextBlock(new RectTransform((0.7f, 0.4f), mainLayout.RectTransform),
TextManager.Get(workshopCopyExists ? "LocalCopyRequired" : "ItemInstallRequired"),
wrap: true);
var buttonLayout = new GUILayoutGroup(new RectTransform((0.6f, 0.1f), mainLayout.RectTransform),
isHorizontal: true);
var yesButton = new GUIButton(new RectTransform((0.5f, 1.0f), buttonLayout.RectTransform),
text: TextManager.Get("Yes"))
{
OnClicked = (button, o) =>
{
CoroutineManager.StartCoroutine(MessageBoxCoroutine((currentStepText, messageBox)
=> CreateLocalCopy(currentStepText, workshopItem, parentFrame)),
$"CreateLocalCopy {workshopItem.Id}");
return false;
}
};
var noButton = new GUIButton(new RectTransform((0.5f, 1.0f), buttonLayout.RectTransform),
text: TextManager.Get("No"))
{
OnClicked = (button, o) =>
{
DeselectPublishedItem();
return false;
}
};
}
else
{
if (!ContentPackageManager.LocalPackages.Contains(localPackage))
{
throw new Exception($"Content package \"{localPackage.Name}\" is not a local package!");
}
var selectedTitle =
new GUITextBlock(new RectTransform((1.0f, 0.05f), mainLayout.RectTransform), workshopItem.Title ?? localPackage.Name,
font: GUIStyle.LargeFont);
if (workshopItem.Id != 0)
{
var showInSteamButton = CreateShowInSteamButton(workshopItem, new RectTransform((0.2f, 1.0f), selectedTitle.RectTransform, Anchor.CenterRight));
}
Spacer(mainLayout, height: 0.03f);
var (leftTop, _, rightTop)
= CreateSidebars(mainLayout, leftWidth: 0.2f, centerWidth: 0.01f, rightWidth: 0.79f,
height: 0.4f);
leftTop.Stretch = true;
rightTop.Stretch = true;
Label(leftTop, TextManager.Get("WorkshopItemPreviewImage"), GUIStyle.SubHeadingFont);
string? thumbnailPath = null;
var thumbnailContainer = CreateThumbnailContainer(leftTop, Vector2.One, ScaleBasis.BothWidth);
if (workshopItem.Id != 0)
{
CreateItemThumbnail(workshopItem, taskCancelSrc.Token, thumbnailContainer);
}
var browseThumbnail =
new GUIButton(NewItemRectT(leftTop),
TextManager.Get("WorkshopItemBrowse"), style: "GUIButtonSmall")
{
OnClicked = (button, o) =>
{
FileSelection.ClearFileTypeFilters();
FileSelection.AddFileTypeFilter("PNG", "*.png");
FileSelection.AddFileTypeFilter("JPEG", "*.jpg, *.jpeg");
FileSelection.AddFileTypeFilter("All files", "*.*");
FileSelection.SelectFileTypeFilter("*.png");
FileSelection.CurrentDirectory
= Path.GetFullPath(Path.GetDirectoryName(localPackage.Path)!);
FileSelection.OnFileSelected = (fn) =>
{
thumbnailPath = fn;
CreateLocalThumbnail(thumbnailPath, thumbnailContainer);
};
FileSelection.Open = true;
return false;
}
};
Label(rightTop, TextManager.Get("WorkshopItemTitle"), GUIStyle.SubHeadingFont);
var titleTextBox = new GUITextBox(NewItemRectT(rightTop), workshopItem.Title ?? localPackage.Name);
Label(rightTop, TextManager.Get("WorkshopItemDescription"), GUIStyle.SubHeadingFont);
var descriptionTextBox
= ScrollableTextBox(rightTop, 6.0f, workshopItem.Description ?? string.Empty);
var (leftBottom, _, rightBottom)
= CreateSidebars(mainLayout, leftWidth: 0.49f, centerWidth: 0.01f, rightWidth: 0.5f, height: 0.5f);
leftBottom.Stretch = true;
rightBottom.Stretch = true;
Label(leftBottom, TextManager.Get("WorkshopItemVersion"), GUIStyle.SubHeadingFont);
var modVersion = localPackage.ModVersion;
if (workshopPackage is { ModVersion: { } workshopVersion } &&
modVersion.Equals(workshopVersion, StringComparison.OrdinalIgnoreCase))
{
modVersion = ModProject.IncrementModVersion(modVersion);
}
char[] forbiddenVersionCharacters = { ';', '=' };
var versionTextBox = new GUITextBox(NewItemRectT(leftBottom), modVersion);
versionTextBox.OnTextChanged += (box, text) =>
{
if (text.Any(c => forbiddenVersionCharacters.Contains(c)))
{
foreach (var c in forbiddenVersionCharacters)
{
text = text.Replace($"{c}", "");
}
box.Text = text;
box.Flash(GUIStyle.Red);
}
return true;
};
Label(leftBottom, TextManager.Get("WorkshopItemChangeNote"), GUIStyle.SubHeadingFont);
var changeNoteTextBox = ScrollableTextBox(leftBottom, 5.0f, "");
Label(rightBottom, TextManager.Get("WorkshopItemTags"), GUIStyle.SubHeadingFont);
var tagsList = CreateTagsList(SteamManager.Workshop.Tags, NewItemRectT(rightBottom, heightScale: 4.0f),
canBeFocused: true);
Dictionary<Identifier, GUIButton> tagButtons = tagsList.Content.Children.Cast<GUIButton>()
.Select(b => ((Identifier)b.UserData, b)).ToDictionary();
if (workshopItem.Tags != null)
{
foreach (Identifier tag in workshopItem.Tags.ToIdentifiers())
{
if (tagButtons.TryGetValue(tag, out var button)) { button.Selected = true; }
}
}
GUILayoutGroup visibilityLayout = new GUILayoutGroup(NewItemRectT(rightBottom), isHorizontal: true);
var visibilityLabel = Label(visibilityLayout, TextManager.Get("WorkshopItemVisibility"), GUIStyle.SubHeadingFont);
visibilityLabel.RectTransform.RelativeSize = (0.6f, 1.0f);
visibilityLabel.TextAlignment = Alignment.CenterRight;
Steamworks.Ugc.Visibility visibility = workshopItem.Visibility;
var visibilityDropdown = DropdownEnum(
visibilityLayout,
(v) => TextManager.Get($"WorkshopItemVisibility.{v}"),
visibility,
(v) => visibility = v);
visibilityDropdown.RectTransform.RelativeSize = (0.4f, 1.0f);
var fileInfoLabel = Label(rightBottom, "", GUIStyle.Font, heightScale: 1.0f);
fileInfoLabel.TextAlignment = Alignment.CenterRight;
TaskPool.Add($"FileInfoLabel{workshopItem.Id}", GetModDirInfo(localPackage.Dir, fileInfoLabel), t => { });
GUILayoutGroup buttonLayout = new GUILayoutGroup(NewItemRectT(rightBottom), isHorizontal: true, childAnchor: Anchor.CenterRight);
RectTransform newButtonRectT()
=> new RectTransform((0.4f, 1.0f), buttonLayout.RectTransform);
var publishItemButton = new GUIButton(newButtonRectT(), TextManager.Get("WorkshopItemPublish"))
{
OnClicked = (button, o) =>
{
//Reload the package to force hash recalculation
string packageName = localPackage.Name;
localPackage = ContentPackageManager.ReloadContentPackage(localPackage);
if (localPackage is null)
{
throw new Exception($"\"{packageName}\" was removed upon reload");
}
//Set up the Ugc.Editor object that we'll need to publish
Steamworks.Ugc.Editor ugcEditor =
workshopItem.Id == 0
? Steamworks.Ugc.Editor.NewCommunityFile
: new Steamworks.Ugc.Editor(workshopItem.Id);
ugcEditor = ugcEditor.WithTitle(titleTextBox.Text)
.WithDescription(descriptionTextBox.Text)
.WithTags(tagButtons.Where(kvp => kvp.Value.Selected).Select(kvp => kvp.Key.Value))
.WithChangeLog(changeNoteTextBox.Text)
.WithMetaData($"gameversion={localPackage.GameVersion};modversion={versionTextBox.Text}")
.WithVisibility(visibility)
.WithPreviewFile(thumbnailPath);
CoroutineManager.StartCoroutine(
MessageBoxCoroutine((currentStepText, messageBox)
=> PublishItem(currentStepText, messageBox, versionTextBox.Text, ugcEditor, localPackage)));
return false;
}
};
if (workshopItem.Id != 0)
{
var deleteItemButton = new GUIButton(newButtonRectT(), TextManager.Get("WorkshopItemDelete"), color: GUIStyle.Red)
{
OnClicked = (button, o) =>
{
var confirmDeletion = new GUIMessageBox(
headerText: TextManager.Get("WorkshopItemDelete"),
text: TextManager.GetWithVariable("WorkshopItemDeleteVerification", "[itemname]", workshopItem.Title!),
buttons: new[] { TextManager.Get("Yes"), TextManager.Get("No") });
confirmDeletion.Buttons[0].OnClicked = (yesBuffer, o1) =>
{
TaskPool.Add($"Delete{workshopItem.Id}", Steamworks.SteamUGC.DeleteFileAsync(workshopItem.Id),
t =>
{
confirmDeletion.Close();
DeselectPublishedItem();
});
return false;
};
confirmDeletion.Buttons[1].OnClicked = (noButton, o1) =>
{
confirmDeletion.Close();
return false;
};
return false;
},
HoverColor = Color.Lerp(GUIStyle.Red, Color.White, 0.3f),
PressedColor = Color.Lerp(GUIStyle.Red, Color.Black, 0.3f),
};
deleteItemButton.TextBlock.TextColor = Color.Black;
deleteItemButton.TextBlock.HoverTextColor = Color.Black;
}
}
}
private IEnumerable<CoroutineStatus> MessageBoxCoroutine(Func<GUITextBlock, GUIMessageBox, IEnumerable<CoroutineStatus>> subcoroutine)
{
var messageBox = new GUIMessageBox("", "...", buttons: new [] { TextManager.Get("Cancel") });
messageBox.Buttons[0].OnClicked = (button, o) =>
{
messageBox.Close();
return false;
};
var coroutineEval = subcoroutine(messageBox.Text, messageBox).GetEnumerator();
while (true)
{
var status = coroutineEval.Current;
if (messageBox.Closed)
{
yield return CoroutineStatus.Success;
yield break;
}
else if (status == CoroutineStatus.Failure || status == CoroutineStatus.Success)
{
messageBox.Close();
yield return status;
yield break;
}
else
{
yield return status;
}
bool moveNext = true;
try
{
moveNext = coroutineEval.MoveNext();
}
catch (Exception e)
{
DebugConsole.ThrowError($"{e.Message} {e.StackTrace.CleanupStackTrace()}");
messageBox.Close();
}
if (!moveNext)
{
messageBox.Close();
}
}
}
private IEnumerable<CoroutineStatus> CreateLocalCopy(GUITextBlock currentStepText, Steamworks.Ugc.Item workshopItem, GUIFrame parentFrame)
{
ContentPackage? workshopCopy =
ContentPackageManager.WorkshopPackages.FirstOrDefault(p => p.SteamWorkshopId == workshopItem.Id);
if (workshopCopy is null)
{
if (!SteamManager.Workshop.CanBeInstalled(workshopItem))
{
SteamManager.Workshop.NukeDownload(workshopItem);
}
SteamManager.Workshop.DownloadModThenEnqueueInstall(workshopItem);
TaskPool.Add($"Install {workshopItem.Title}",
SteamManager.Workshop.WaitForInstall(workshopItem),
(t) =>
{
ContentPackageManager.WorkshopPackages.Refresh();
});
while (!ContentPackageManager.WorkshopPackages.Any(p => p.SteamWorkshopId == workshopItem.Id))
{
currentStepText.Text = SteamManager.Workshop.CanBeInstalled(workshopItem)
? TextManager.Get("PublishPopupInstall")
: TextManager.GetWithVariable("PublishPopupDownload", "[percentage]", Percentage(workshopItem.DownloadAmount));
yield return new WaitForSeconds(0.5f);
}
workshopCopy =
ContentPackageManager.WorkshopPackages.First(p => p.SteamWorkshopId == workshopItem.Id);
}
bool localCopyMade = false;
TaskPool.Add($"Create local copy {workshopItem.Title}",
SteamManager.Workshop.CreateLocalCopy(workshopCopy),
(t) =>
{
ContentPackageManager.LocalPackages.Refresh();
localCopyMade = true;
});
while (!localCopyMade)
{
currentStepText.Text = TextManager.Get("PublishPopupCreateLocal");
yield return new WaitForSeconds(0.5f);
}
PopulatePublishTab(workshopItem, parentFrame);
yield return CoroutineStatus.Success;
}
private IEnumerable<CoroutineStatus> PublishItem(
GUITextBlock currentStepText, GUIMessageBox messageBox,
string modVersion, Steamworks.Ugc.Editor editor, ContentPackage localPackage)
{
if (!SteamManager.IsInitialized)
{
yield return CoroutineStatus.Failure;
}
bool stagingReady = false;
Exception? stagingException = null;
TaskPool.Add("CreatePublishStagingCopy",
SteamManager.Workshop.CreatePublishStagingCopy(modVersion, localPackage),
(t) =>
{
stagingReady = true;
stagingException = t.Exception?.GetInnermost();
});
currentStepText.Text = TextManager.Get("PublishPopupStaging");
while (!stagingReady) { yield return new WaitForSeconds(0.5f); }
if (stagingException != null)
{
throw new Exception($"Failed to create staging copy: {stagingException.Message} {stagingException.StackTrace.CleanupStackTrace()}");
}
editor = editor
.WithContent(SteamManager.Workshop.PublishStagingDir)
.ForAppId(SteamManager.AppID);
messageBox.Buttons[0].Enabled = false;
Steamworks.Ugc.PublishResult? result = null;
Exception? resultException = null;
TaskPool.Add($"Publishing {localPackage.Name} ({localPackage.SteamWorkshopId})",
editor.SubmitAsync(),
t =>
{
if (t.TryGetResult(out Steamworks.Ugc.PublishResult publishResult))
{
result = publishResult;
}
resultException = t.Exception?.GetInnermost();
});
currentStepText.Text = TextManager.Get("PublishPopupSubmit");
while (!result.HasValue && resultException is null) { yield return new WaitForSeconds(0.5f); }
if (result is { Success: true })
{
var resultId = result.Value.FileId;
Steamworks.Ugc.Item resultItem = new Steamworks.Ugc.Item(resultId);
Task downloadTask = SteamManager.Workshop.ForceRedownload(resultItem);
while (!resultItem.IsInstalled && !downloadTask.IsCompleted)
{
currentStepText.Text = TextManager.GetWithVariable("PublishPopupDownload", "[percentage]", Percentage(resultItem.DownloadAmount));
yield return new WaitForSeconds(0.5f);
}
if (!resultItem.IsInstalled)
{
throw new Exception($"Failed to install item: download task ended with status {downloadTask.Status}, " +
$"exception was {downloadTask.Exception?.GetInnermost()?.ToString().CleanupStackTrace() ?? "[NULL]"}");
}
ContentPackage? pkgToNuke
= ContentPackageManager.WorkshopPackages.FirstOrDefault(p => p.SteamWorkshopId == resultId);
if (pkgToNuke != null)
{
Directory.Delete(pkgToNuke.Dir, recursive: true);
ContentPackageManager.WorkshopPackages.Refresh();
}
bool installed = false;
TaskPool.Add(
"InstallNewlyPublished",
SteamManager.Workshop.WaitForInstall(resultItem),
(t) =>
{
installed = true;
});
while (!installed)
{
currentStepText.Text = TextManager.Get("PublishPopupInstall");
yield return new WaitForSeconds(0.5f);
}
var localModProject = new ModProject(localPackage)
{
SteamWorkshopId = resultId
};
localModProject.DiscardHashAndInstallTime();
localModProject.Save(localPackage.Path);
ContentPackageManager.ReloadContentPackage(localPackage);
ContentPackageManager.WorkshopPackages.Refresh();
DeselectPublishedItem();
if (result.Value.NeedsWorkshopAgreement)
{
SteamManager.OverlayCustomURL(resultItem.Url);
}
new GUIMessageBox(string.Empty, TextManager.GetWithVariable("workshopitempublished", "[itemname]", localPackage.Name));
}
else if (resultException != null)
{
throw new Exception($"Failed to publish item: {resultException.Message} {resultException.StackTrace.CleanupStackTrace()}");
}
else
{
new GUIMessageBox(TextManager.Get("error"), TextManager.GetWithVariable("workshopitempublishfailed", "[itemname]", localPackage.Name));
}
SteamManager.Workshop.DeletePublishStagingCopy();
messageBox.Close();
}
}
}