diff --git a/README.md b/README.md
index f66b457..b07e9f6 100644
--- a/README.md
+++ b/README.md
@@ -42,20 +42,6 @@ It is recommended to subscribe to the NML item on the Steam Workshop to receive
3. enable experimental mode and start the game
-### Automatic Installation
-
-The installer is available for both Linux and Windows.
-Click the links below to download:
-* [Linux Installer](https://github.com/gmBlahaj/nml-installer/releases/download/nml/nml-setup-linux)
-* [Windows Installer](https://github.com/gmBlahaj/nml-installer/releases/download/nml/nml-setup-win.exe)
-
-
-
-### Downgrade Tool
-
-If you're trying to play mods that aren't compatible with the latest version of Worldbox, you can use **Worldbox Rewind** to downgrade to an older version:
-* [Download Worldbox Rewind](https://gmblahaj.xyz/pages/rewind)
-
Others
diff --git a/api/ModDeclare.cs b/api/ModDeclare.cs
index b4ecfab..89aecf7 100644
--- a/api/ModDeclare.cs
+++ b/api/ModDeclare.cs
@@ -127,6 +127,7 @@ public ModDeclare(string pFilePath)
AICheckTranslation = modDeclare.AICheckTranslation;
AICheckAudio = modDeclare.AICheckAudio;
Tags = modDeclare.Tags ?? new List();
+ SteamId = modDeclare.SteamId;
Dependencies ??= Array.Empty();
OptionalDependencies ??= Array.Empty();
@@ -154,6 +155,10 @@ public ModDeclare(string pFilePath)
Regex regex = new(@"^\d+$");
if (!regex.IsMatch(pathSegments[++currentSearchIndex])) return;
IsWorkshopLoaded = true;
+ if (SteamId == 0 && ulong.TryParse(pathSegments[currentSearchIndex], out ulong parsedId))
+ {
+ SteamId = parsedId;
+ }
}
///
@@ -345,6 +350,9 @@ public string GetAIAttributionDisplay()
};
}
+ [JsonProperty("steam_id")]
+ public ulong SteamId { get; internal set; }
+
private List _tags = new();
[JsonProperty("tags")]
@@ -375,11 +383,9 @@ public void Save()
{
try
{
+ if (string.IsNullOrEmpty(FolderPath) || !Directory.Exists(FolderPath)) return;
string jsonPath = Path.Combine(FolderPath, "mod.json");
- if (File.Exists(jsonPath))
- {
- File.WriteAllText(jsonPath, JsonConvert.SerializeObject(this, Formatting.Indented));
- }
+ File.WriteAllText(jsonPath, JsonConvert.SerializeObject(this, Formatting.Indented));
}
catch (Exception e)
{
diff --git a/resources/commit b/resources/commit
index e38f0b0..630218e 100644
--- a/resources/commit
+++ b/resources/commit
@@ -1 +1 @@
-4bb2ea1fa3af3aa45c8ebb7aefcf096a5bc3b2f0
+98377654d461586e413f2e9bc28ed3fa24514e27
diff --git a/resources/locales/ch.json b/resources/locales/ch.json
index 3278ef7..f542049 100644
--- a/resources/locales/ch.json
+++ b/resources/locales/ch.json
@@ -6,6 +6,23 @@
"NeoModLoader Update Title": "NeoModLoader 更新",
"NeoModLoader Update Complete": "NeoModLoader 更新至{0},\n 重啟遊戲以應用更新。",
"ModUpload Title": "上傳模組",
+ "ModUpdate Title": "更新模組",
+ "ModUploadModeWindow Title": "Steam 創意工坊",
+ "ModUploadMode_Upload": "上傳",
+ "ModUploadMode_Update": "更新",
+ "ModUploadMode_ViewWorkshop": "查看創意工坊",
+ "ModUploadMode_Back": "返回",
+ "ModUploadMode_NewUpload": "新上傳",
+ "ModUploadMode_NoSteamIdPrompt": "未找到此模組的 Steam 創意工坊 ID。\n\n請在上傳視窗中輸入創意工坊 ID 以進行更新。",
+ "ModUploadMode_EnterId": "輸入 ID",
+ "ModUploadMode_WantToChange": "你想要修改任何內容嗎?",
+ "ModUploadMode_Yes": "是",
+ "ModUploadMode_No": "否",
+ "ModUploadMode_AiWarning": "確定嗎?\n\n如果你使用了 AI,在更新前需要修改免責聲明。",
+ "ModUploadMode_ChangeDisclaimer": "修改免責聲明",
+ "ModUploadMode_Cancel": "取消",
+ "Clear Steam ID": "清除 Steam ID",
+ "Clear Steam ID Confirm": "確定要清除綁定的創意工坊 ID 嗎?",
"ModUpload_Categories_Title": "分類標籤 (點擊切換):",
"Information Title": "提示",
"AIConfirmWindow Title": "AI 內容免責聲明",
diff --git a/resources/locales/cz.json b/resources/locales/cz.json
index 1b82704..f4fff90 100644
--- a/resources/locales/cz.json
+++ b/resources/locales/cz.json
@@ -6,6 +6,23 @@
"NeoModLoader Update Title": "NeoModLoader 更新",
"NeoModLoader Update Complete": "NeoModLoader 更新至{0},\n 重启游戏以应用更新。",
"ModUpload Title": "上传模组",
+ "ModUpdate Title": "更新模组",
+ "ModUploadModeWindow Title": "Steam 创意工坊",
+ "ModUploadMode_Upload": "上传",
+ "ModUploadMode_Update": "更新",
+ "ModUploadMode_ViewWorkshop": "查看创意工坊",
+ "ModUploadMode_Back": "返回",
+ "ModUploadMode_NewUpload": "新上传",
+ "ModUploadMode_NoSteamIdPrompt": "未找到此模组的 Steam 创意工坊 ID。\n\n请在上传窗口中输入创意工坊 ID 以进行更新。",
+ "ModUploadMode_EnterId": "输入 ID",
+ "ModUploadMode_WantToChange": "你想要修改任何内容吗?",
+ "ModUploadMode_Yes": "是",
+ "ModUploadMode_No": "否",
+ "ModUploadMode_AiWarning": "确定吗?\n\n如果你使用了 AI,在更新前需要修改免责声明。",
+ "ModUploadMode_ChangeDisclaimer": "修改免责声明",
+ "ModUploadMode_Cancel": "取消",
+ "Clear Steam ID": "清除 Steam ID",
+ "Clear Steam ID Confirm": "确定要清除绑定的创意工坊 ID 吗?",
"ModUpload_Categories_Title": "分类标签 (点击切换):",
"Information Title": "信息",
"AIConfirmWindow Title": "AI 内容免责声明",
diff --git a/resources/locales/en.json b/resources/locales/en.json
index f4a866b..db8a821 100644
--- a/resources/locales/en.json
+++ b/resources/locales/en.json
@@ -6,6 +6,23 @@
"NeoModLoader Update Title": "NeoModLoader Update",
"NeoModLoader Update Complete": "NeoModLoader has been updated to version {0}.\nPlease restart the game to apply the update.",
"ModUpload Title": "Upload Mod",
+ "ModUpdate Title": "Update Mod",
+ "ModUploadModeWindow Title": "Steam Workshop",
+ "ModUploadMode_Upload": "Upload",
+ "ModUploadMode_Update": "Update",
+ "ModUploadMode_ViewWorkshop": "View Workshop",
+ "ModUploadMode_Back": "Back",
+ "ModUploadMode_NewUpload": "New Upload",
+ "ModUploadMode_NoSteamIdPrompt": "No Steam Workshop ID found for this mod.\n\nPlease enter the Workshop ID in the upload window to update it.",
+ "ModUploadMode_EnterId": "Enter ID",
+ "ModUploadMode_WantToChange": "Do you want to change anything?",
+ "ModUploadMode_Yes": "Yes",
+ "ModUploadMode_No": "No",
+ "ModUploadMode_AiWarning": "Are you sure?\n\nIf you used AI, you need to change the disclaimer before updating.",
+ "ModUploadMode_ChangeDisclaimer": "Change Disclaimer",
+ "ModUploadMode_Cancel": "Cancel",
+ "Clear Steam ID": "Clear Steam ID",
+ "Clear Steam ID Confirm": "Are you sure you want to clear the associated Steam Workshop ID?",
"ModUpload_Categories_Title": "Category Tags (Click to toggle):",
"Information Title": "Information",
"AIConfirmWindow Title": "AI Content Disclaimer",
diff --git a/services/IPlatformSpecificModWorkshopService.cs b/services/IPlatformSpecificModWorkshopService.cs
index b3b05ed..54186f0 100644
--- a/services/IPlatformSpecificModWorkshopService.cs
+++ b/services/IPlatformSpecificModWorkshopService.cs
@@ -10,8 +10,7 @@ internal interface IPlatformSpecificModWorkshopService
Promise UploadMod(string name, string description, string previewImagePath, string workshopPath, string changelog,
bool verified, string aiTag = null, List extraTags = null);
- Promise EditMod(ulong fileID, string previewImagePath, string workshopPath, string changelog,
- string aiTag = null, List extraTags = null);
+ Promise EditMod(ulong fileID, string previewImagePath, string workshopPath, string changelog, ModDeclare mod_decl);
void FindSubscribedMods();
ModDeclare GetNextModFromWorkshopItem();
}
\ No newline at end of file
diff --git a/services/ModWorkshopService.cs b/services/ModWorkshopService.cs
index 04df0be..a0b4998 100644
--- a/services/ModWorkshopService.cs
+++ b/services/ModWorkshopService.cs
@@ -1,5 +1,6 @@
using System.Linq;
using System.Reflection;
+using System.Text.RegularExpressions;
using NeoModLoader.api;
using NeoModLoader.api.attributes;
using NeoModLoader.constants;
@@ -38,10 +39,10 @@ private static void UploadModLoader(string changelog)
///
/// Try to Upload a mod to Steam Workshop
///
- public static Promise UploadMod(IMod mod, string changelog, bool verified = false)
+ public static Promise UploadMod(IMod mod, string changelog, bool verified = false, string uploadName = null)
{
ModDeclare mod_decl = mod.GetDeclaration();
- string name = mod_decl.Name;
+ string name = string.IsNullOrEmpty(uploadName) ? mod_decl.Name : uploadName;
string description = BuildModDescription(mod_decl);
string workshopPath = Path.Combine(SaveManager.generateMainPath("workshop_upload_mod") + mod_decl.UID);
@@ -50,7 +51,6 @@ public static Promise UploadMod(IMod mod, string changelog, bool verified = fals
string previewImagePath = PreparePreviewImage(mod_decl, workshopPath);
- // This works for BepInEx mods
File.WriteAllText(Path.Combine(workshopPath, "mod.json"), JsonConvert.SerializeObject(mod_decl, Formatting.Indented));
return workshopServiceBackend.UploadMod(name, description, previewImagePath, workshopPath, changelog, verified, mod_decl.GetAIAttributionDisplay(), mod_decl.Tags);
@@ -68,10 +68,42 @@ public static Promise TryEditMod(ulong fileID, IMod mod, string changelog)
File.WriteAllText(Path.Combine(workshopPath, "mod.json"), JsonConvert.SerializeObject(mod_decl, Formatting.Indented));
- return workshopServiceBackend.EditMod(fileID, previewImagePath, workshopPath, changelog, mod_decl.GetAIAttributionDisplay(), mod_decl.Tags);
+ return workshopServiceBackend.EditMod(fileID, previewImagePath, workshopPath, changelog, mod_decl);
+ }
+
+ public static string UpdateDescriptionDisclaimer(string steamDescription, ModDeclare mod_decl)
+ {
+ string newDisclaimer = BuildAIDisclaimer(mod_decl);
+ if (string.IsNullOrEmpty(steamDescription))
+ {
+ return BuildModDescription(mod_decl);
+ }
+
+ string pattern = @"\[hr\]\[\/hr\](?:(?!\[hr\]\[\/hr\])[\s\S])*?(?:AI Content Disclosure|Authorship:|generative AI|AI Content)[\s\S]*?\[hr\]\[\/hr\]";
+ if (Regex.IsMatch(steamDescription, pattern, RegexOptions.IgnoreCase))
+ {
+ return Regex.Replace(steamDescription, pattern, newDisclaimer, RegexOptions.IgnoreCase);
+ }
+
+ string partialPattern = @"(?:\[hr\]\[\/hr\]\s*)?(?:\[b\])?(?:AI Content Disclosure|Authorship:)[\s\S]*?(?:\[hr\]\[\/hr\]|(?=(?:ModLoader:|模组加载器:|\z)))";
+ if (Regex.IsMatch(steamDescription, partialPattern, RegexOptions.IgnoreCase))
+ {
+ return Regex.Replace(steamDescription, partialPattern, newDisclaimer, RegexOptions.IgnoreCase);
+ }
+
+ string footerPattern = @"(?:\r?\n)+(?:ModLoader:\s*https?:\S+|模组加载器:\s*https?:\S+)[\s\S]*$";
+ Match footerMatch = Regex.Match(steamDescription, footerPattern, RegexOptions.IgnoreCase);
+ if (footerMatch.Success)
+ {
+ string beforeFooter = steamDescription.Substring(0, footerMatch.Index).TrimEnd();
+ string footer = steamDescription.Substring(footerMatch.Index).TrimStart();
+ return beforeFooter + "\n\n" + newDisclaimer + "\n\n" + footer;
+ }
+
+ return steamDescription.TrimEnd() + "\n\n" + newDisclaimer;
}
- private static string BuildModDescription(ModDeclare mod_decl)
+ public static string BuildModDescription(ModDeclare mod_decl)
{
string name = mod_decl.Name;
string aiDisclaimer = BuildAIDisclaimer(mod_decl);
@@ -84,7 +116,7 @@ private static string BuildModDescription(ModDeclare mod_decl)
$"模组加载器: {CoreConstants.RepoURL}";
}
- private static string BuildAIDisclaimer(ModDeclare mod_decl)
+ public static string BuildAIDisclaimer(ModDeclare mod_decl)
{
if (mod_decl.AIAttribution == "ai_made" || mod_decl.AIAttribution == "ai_assisted")
{
@@ -144,7 +176,8 @@ private static void CopyModFiles(string sourceFolder, string targetFolder)
private static string PreparePreviewImage(ModDeclare mod_decl, string workshopPath)
{
string previewImagePath;
- if (string.IsNullOrEmpty(mod_decl.IconPath))
+ if (string.IsNullOrEmpty(mod_decl.IconPath) ||
+ !File.Exists(Path.Combine(mod_decl.FolderPath, mod_decl.IconPath)))
{
using Stream icon_stream = Assembly.GetExecutingAssembly()
.GetManifestResourceStream("NeoModLoader.resources.logo.png");
diff --git a/services/ModWorkshopServiceUnix.cs b/services/ModWorkshopServiceUnix.cs
index 01e3f16..fe26a67 100644
--- a/services/ModWorkshopServiceUnix.cs
+++ b/services/ModWorkshopServiceUnix.cs
@@ -1,5 +1,6 @@
extern alias unixsteamwork;
using System.Reflection;
+using System.Threading.Tasks;
using NeoModLoader.api;
using NeoModLoader.constants;
using NeoModLoader.ui;
@@ -128,23 +129,62 @@ public Promise UploadMod(string name, string description, string previewImagePat
return promise;
}
- public Promise EditMod(ulong fileID, string previewImagePath, string workshopPath, string changelog,
- string aiTag = null, List extraTags = null)
+ public Promise EditMod(ulong fileID, string previewImagePath, string workshopPath, string changelog, ModDeclare mod_decl)
{
Promise promise = new();
- // Create Upload Files Descriptor
+ var progress = ModUploadingProgressWindow.ShowWindow();
+ ExecuteEditMod(fileID, previewImagePath, workshopPath, changelog, mod_decl, promise, progress);
+ return promise;
+ }
+
+ private async Task FetchUpdatedDescriptionAsync(ulong fileID, ModDeclare mod_decl)
+ {
+ try
+ {
+ Query q = Query.Items.WithFileId(fileID).WithLongDescription(true);
+ ResultPage? resultPage = await q.GetPageAsync(1);
+ if (resultPage.HasValue)
+ {
+ foreach (var entry in resultPage.Value.Entries)
+ {
+ if (!string.IsNullOrEmpty(entry.Description))
+ {
+ return ModWorkshopService.UpdateDescriptionDisclaimer(entry.Description, mod_decl);
+ }
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ LogService.LogWarning($"Failed to get existing description: {e.Message}");
+ }
+
+ return ModWorkshopService.BuildModDescription(mod_decl);
+ }
+
+ private Editor ConfigureEditor(ulong fileID, string previewImagePath, string workshopPath, string changelog,
+ string desc, ModDeclare mod_decl)
+ {
Editor editor = new Editor(fileID)
.WithPreviewFile(previewImagePath)
- .WithContent(workshopPath).WithChangeLog(changelog)
+ .WithContent(workshopPath)
+ .WithChangeLog(changelog)
.WithTag("Mod");
+ if (!string.IsNullOrEmpty(desc))
+ {
+ editor = editor.WithDescription(desc);
+ }
+
+ string aiTag = mod_decl.GetAIAttributionDisplay();
if (!string.IsNullOrEmpty(aiTag))
{
editor = editor.WithTag(aiTag);
}
- if (extraTags != null)
+
+ if (mod_decl.Tags != null)
{
- foreach (string tag in extraTags)
+ foreach (string tag in mod_decl.Tags)
{
if (!string.IsNullOrEmpty(tag))
{
@@ -153,34 +193,36 @@ public Promise EditMod(ulong fileID, string previewImagePath, string workshopPat
}
}
- editor.SubmitAsync(ModUploadingProgressWindow.ShowWindow()).ContinueWith(
- delegate(Task taskResult)
- {
- if (taskResult.Status != TaskStatus.RanToCompletion)
- {
- promise.Reject(taskResult.Exception.GetBaseException());
- return;
- }
+ return editor;
+ }
- PublishResult result = taskResult.Result;
- // Result process refer to: https://partner.steamgames.com/doc/api/steam_api#EResult
- if (result.NeedsWorkshopAgreement)
- {
- LogService.LogWarning("Needs Workshop Agreement");
- // TODO: Open Workshop Agreement
- Application.OpenURL("steam://url/CommunityFilePage/" + result.FileId);
- }
+ private async void ExecuteEditMod(ulong fileID, string previewImagePath, string workshopPath, string changelog,
+ ModDeclare mod_decl, Promise promise, ModUploadingProgressWindow.UploadProgress progress)
+ {
+ try
+ {
+ string desc = await FetchUpdatedDescriptionAsync(fileID, mod_decl);
+ Editor editor = ConfigureEditor(fileID, previewImagePath, workshopPath, changelog, desc, mod_decl);
- if (result.Result != Result.OK)
- {
- promise.Reject(new Exception(result.Result.ToString()));
- return;
- }
+ PublishResult result = await editor.SubmitAsync(progress);
+ if (result.NeedsWorkshopAgreement)
+ {
+ Application.OpenURL("steam://url/CommunityFilePage/" + result.FileId);
+ }
- // result.FileId;
- promise.Resolve();
- }, TaskScheduler.FromCurrentSynchronizationContext());
- return promise;
+ if (result.Result != Result.OK)
+ {
+ promise.Reject(new Exception(result.Result.ToString()));
+ return;
+ }
+
+ ModUploadingProgressWindow.Instance.fileId = fileID;
+ promise.Resolve();
+ }
+ catch (Exception ex)
+ {
+ promise.Reject(ex);
+ }
}
public async void FindSubscribedMods()
diff --git a/services/ModWorkshopServiceWindows.cs b/services/ModWorkshopServiceWindows.cs
index bb8e119..bd1070c 100644
--- a/services/ModWorkshopServiceWindows.cs
+++ b/services/ModWorkshopServiceWindows.cs
@@ -1,5 +1,6 @@
extern alias winsteamwork;
using System.Reflection;
+using System.Threading.Tasks;
using NeoModLoader.api;
using NeoModLoader.constants;
using NeoModLoader.ui;
@@ -128,23 +129,62 @@ public Promise UploadMod(string name, string description, string previewImagePat
return promise;
}
- public Promise EditMod(ulong fileID, string previewImagePath, string workshopPath, string changelog,
- string aiTag = null, List extraTags = null)
+ public Promise EditMod(ulong fileID, string previewImagePath, string workshopPath, string changelog, ModDeclare mod_decl)
{
Promise promise = new();
- // Create Upload Files Descriptor
+ var progress = ModUploadingProgressWindow.ShowWindow();
+ ExecuteEditMod(fileID, previewImagePath, workshopPath, changelog, mod_decl, promise, progress);
+ return promise;
+ }
+
+ private async Task FetchUpdatedDescriptionAsync(ulong fileID, ModDeclare mod_decl)
+ {
+ try
+ {
+ Query q = Query.Items.WithFileId(fileID).WithLongDescription(true);
+ ResultPage? resultPage = await q.GetPageAsync(1);
+ if (resultPage.HasValue)
+ {
+ foreach (var entry in resultPage.Value.Entries)
+ {
+ if (!string.IsNullOrEmpty(entry.Description))
+ {
+ return ModWorkshopService.UpdateDescriptionDisclaimer(entry.Description, mod_decl);
+ }
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ LogService.LogWarning($"Failed to get existing description: {e.Message}");
+ }
+
+ return ModWorkshopService.BuildModDescription(mod_decl);
+ }
+
+ private Editor ConfigureEditor(ulong fileID, string previewImagePath, string workshopPath, string changelog,
+ string desc, ModDeclare mod_decl)
+ {
Editor editor = new Editor(fileID)
.WithPreviewFile(previewImagePath)
- .WithContent(workshopPath).WithChangeLog(changelog)
+ .WithContent(workshopPath)
+ .WithChangeLog(changelog)
.WithTag("Mod");
+ if (!string.IsNullOrEmpty(desc))
+ {
+ editor = editor.WithDescription(desc);
+ }
+
+ string aiTag = mod_decl.GetAIAttributionDisplay();
if (!string.IsNullOrEmpty(aiTag))
{
editor = editor.WithTag(aiTag);
}
- if (extraTags != null)
+
+ if (mod_decl.Tags != null)
{
- foreach (string tag in extraTags)
+ foreach (string tag in mod_decl.Tags)
{
if (!string.IsNullOrEmpty(tag))
{
@@ -153,34 +193,36 @@ public Promise EditMod(ulong fileID, string previewImagePath, string workshopPat
}
}
- editor.SubmitAsync(ModUploadingProgressWindow.ShowWindow()).ContinueWith(
- delegate(Task taskResult)
- {
- if (taskResult.Status != TaskStatus.RanToCompletion)
- {
- promise.Reject(taskResult.Exception.GetBaseException());
- return;
- }
+ return editor;
+ }
- PublishResult result = taskResult.Result;
- // Result process refer to: https://partner.steamgames.com/doc/api/steam_api#EResult
- if (result.NeedsWorkshopAgreement)
- {
- LogService.LogWarning("Needs Workshop Agreement");
- // TODO: Open Workshop Agreement
- Application.OpenURL("steam://url/CommunityFilePage/" + result.FileId);
- }
+ private async void ExecuteEditMod(ulong fileID, string previewImagePath, string workshopPath, string changelog,
+ ModDeclare mod_decl, Promise promise, ModUploadingProgressWindow.UploadProgress progress)
+ {
+ try
+ {
+ string desc = await FetchUpdatedDescriptionAsync(fileID, mod_decl);
+ Editor editor = ConfigureEditor(fileID, previewImagePath, workshopPath, changelog, desc, mod_decl);
- if (result.Result != Result.OK)
- {
- promise.Reject(new Exception(result.Result.ToString()));
- return;
- }
+ PublishResult result = await editor.SubmitAsync(progress);
+ if (result.NeedsWorkshopAgreement)
+ {
+ Application.OpenURL("steam://url/CommunityFilePage/" + result.FileId);
+ }
- // result.FileId;
- promise.Resolve();
- }, TaskScheduler.FromCurrentSynchronizationContext());
- return promise;
+ if (result.Result != Result.OK)
+ {
+ promise.Reject(new Exception(result.Result.ToString()));
+ return;
+ }
+
+ ModUploadingProgressWindow.Instance.fileId = fileID;
+ promise.Resolve();
+ }
+ catch (Exception ex)
+ {
+ promise.Reject(ex);
+ }
}
public ModDeclare GetNextModFromWorkshopItem()
diff --git a/ui/AIConfirmWindow.cs b/ui/AIConfirmWindow.cs
index 8405d18..6971d91 100644
--- a/ui/AIConfirmWindow.cs
+++ b/ui/AIConfirmWindow.cs
@@ -9,7 +9,10 @@ namespace NeoModLoader.ui;
internal class AIConfirmWindow : AbstractWindow
{
private Action onConfirmAction;
+ private Action onBackAction;
private Text messageText;
+ private Text backText;
+ private Text continueText;
protected override void Init()
{
@@ -57,7 +60,7 @@ protected override void Init()
backBg.sprite = SpriteTextureLoader.getSprite("ui/special/special_buttonred");
backBg.type = Image.Type.Sliced;
- Text backText = new GameObject("Text", typeof(Text)).GetComponent();
+ backText = new GameObject("Text", typeof(Text)).GetComponent();
backText.transform.SetParent(backButtonObj.transform);
backText.transform.localScale = Vector3.one;
backText.GetComponent().sizeDelta = new Vector2(85, 25);
@@ -67,7 +70,15 @@ protected override void Init()
backButtonObj.GetComponent().onClick.AddListener(() =>
{
- GetComponent().clickHide();
+ ScrollWindow.moveAllToLeftAndRemove(false);
+ if (onBackAction != null)
+ {
+ onBackAction.Invoke();
+ }
+ else
+ {
+ GetComponent().clickHide();
+ }
});
GameObject continueButtonObj = new GameObject("ContinueButton", typeof(Image), typeof(Button));
@@ -78,7 +89,7 @@ protected override void Init()
continueBg.sprite = SpriteTextureLoader.getSprite("ui/special/button2");
continueBg.type = Image.Type.Sliced;
- Text continueText = new GameObject("Text", typeof(Text)).GetComponent();
+ continueText = new GameObject("Text", typeof(Text)).GetComponent();
continueText.transform.SetParent(continueButtonObj.transform);
continueText.transform.localScale = Vector3.one;
continueText.GetComponent().sizeDelta = new Vector2(85, 25);
@@ -94,9 +105,17 @@ protected override void Init()
}
public static void ShowWindow(string pMessage, Action pOnConfirm)
+ {
+ ShowWindow(pMessage, "Continue", pOnConfirm, "Back", null);
+ }
+
+ public static void ShowWindow(string pMessage, string pConfirmText, Action pOnConfirm, string pBackText = "Back", Action pOnBack = null)
{
Instance.messageText.text = pMessage;
+ Instance.continueText.text = pConfirmText;
+ Instance.backText.text = pBackText;
Instance.onConfirmAction = pOnConfirm;
+ Instance.onBackAction = pOnBack;
ScrollWindow.showWindow(WindowId);
}
}
diff --git a/ui/AIDisclosureWindow.cs b/ui/AIDisclosureWindow.cs
index cf7acfa..8098175 100644
--- a/ui/AIDisclosureWindow.cs
+++ b/ui/AIDisclosureWindow.cs
@@ -520,7 +520,8 @@ public static void ShowWindow(IMod pMod, Action pOnConfirm)
Instance.check_translation = decl.AICheckTranslation;
Instance.check_audio = decl.AICheckAudio;
- if (string.IsNullOrEmpty(decl.IconPath))
+ if (string.IsNullOrEmpty(decl.IconPath) ||
+ !File.Exists(Path.Combine(decl.FolderPath, decl.IconPath)))
{
Instance.preview_icon_image.sprite = InternalResourcesGetter.GetIcon();
}
diff --git a/ui/ModListWindow.cs b/ui/ModListWindow.cs
index 7d4c4d2..8183440 100644
--- a/ui/ModListWindow.cs
+++ b/ui/ModListWindow.cs
@@ -232,19 +232,11 @@ public class ModListItem : AbstractListWindowItem
private IEnumerator WaitOpenWindow()
{
yield return new WaitForSeconds(3f);
- if (Instance.clickTimes == 8) ModUploadWindow.ShowWindow(_mod);
+ if (Instance.clickTimes == 8) ModUploadModeWindow.ShowWindow(_mod);
}
- ///
- /// The mod to display
- public override void Setup(IMod mod)
+ private string FormatModDisplayText(ModDeclare mod_declare)
{
- _mod = mod;
- ModDeclare mod_declare = mod.GetDeclaration();
- ModState mod_state = WorldBoxMod.AllRecognizedMods[mod_declare];
-
- Text text = transform.Find("Text").GetComponent();
- var state_text = transform.Find("StateText").GetComponent();
string mod_name = mod_declare.Name;
string mod_author = mod_declare.Author;
string mod_desc = mod_declare.Description;
@@ -267,149 +259,111 @@ public override void Setup(IMod mod)
mod_desc = LM.Get(multilang_mod_desc_key);
}
- switch (mod_declare.ModType)
+ return mod_declare.ModType switch
{
+ ModTypeEnum.NEOMOD or ModTypeEnum.COMPILED_NEOMOD or ModTypeEnum.RESOURCE_PACK =>
+ $"{mod_name}\t{mod_declare.Version}\n{mod_author}\n{mod_desc}",
+ ModTypeEnum.BEPINEX =>
+ $"[BepInEx] {mod_name}\t{mod_declare.Version}\n{mod_author}\n{mod_desc}",
+ _ => throw new ArgumentOutOfRangeException()
+ };
+ }
- case ModTypeEnum.NEOMOD:
- case ModTypeEnum.COMPILED_NEOMOD:
- case ModTypeEnum.RESOURCE_PACK:
- text.text = $"{mod_name}\t{mod_declare.Version}\n{mod_author}\n{mod_desc}";
- break;
- case ModTypeEnum.BEPINEX:
- text.text = $"[BepInEx] {mod_name}\t{mod_declare.Version}\n{mod_author}\n{mod_desc}";
- break;
- default:
- throw new ArgumentOutOfRangeException();
- }
-
- Sprite sprite = null;
+ private Sprite GetModIcon(ModDeclare mod_declare)
+ {
if (!string.IsNullOrEmpty(mod_declare.IconPath) &&
File.Exists(Path.Combine(mod_declare.FolderPath, mod_declare.IconPath)))
{
- sprite = SpriteLoadUtils.LoadSingleSprite(Path.Combine(mod_declare.FolderPath, mod_declare.IconPath));
+ return SpriteLoadUtils.LoadSingleSprite(Path.Combine(mod_declare.FolderPath, mod_declare.IconPath));
}
- if (sprite == null)
+ return InternalResourcesGetter.GetIcon();
+ }
+
+ private void OnIconClicked(ModDeclare mod_declare)
+ {
+ float current_time = Time.time;
+ if (current_time - Instance.lastClickTime > 1)
{
- sprite = InternalResourcesGetter.GetIcon();
+ Instance.clickTimes = 0;
}
- Image icon = transform.Find("Icon").GetComponent();
- Button configure_button = transform.Find("Configure").GetComponent();
- Button website_button = transform.Find("Website").GetComponent();
- Button open_folder_button = transform.Find("OpenFolder").GetComponent();
- TipButton icon_tip_button = icon.GetComponent();
-
- icon.sprite = sprite;
- var configurable = mod.GetGameObject()?.GetComponent();
- configure_button.gameObject.SetActive(configurable != null);
-
- icon.GetComponent().onClick.RemoveAllListeners();
- configure_button.onClick.RemoveAllListeners();
- website_button.onClick.RemoveAllListeners();
- open_folder_button.onClick.RemoveAllListeners();
- open_folder_button.onClick.AddListener(() => { Application.OpenURL(mod_declare.FolderPath); });
+ if (mod_declare != Instance.clickedMod)
+ {
+ Instance.clickedMod = mod_declare;
+ Instance.clickTimes = 0;
+ }
- if (mod_state == ModState.LOADED)
+ Instance.lastClickTime = current_time;
+ Instance.clickTimes++;
+ if (Instance.clickTimes == 8)
{
- icon.GetComponent().onClick.AddListener(() =>
+ StartCoroutine(nameof(WaitOpenWindow));
+ /*
+ new Task(() =>
{
- float current_time = Time.time;
- if (current_time - Instance.lastClickTime > 1)
- {
- Instance.clickTimes = 0;
- }
-
- if (mod_declare != Instance.clickedMod)
- {
- Instance.clickedMod = mod_declare;
- Instance.clickTimes = 0;
- }
-
- Instance.lastClickTime = current_time;
- Instance.clickTimes++;
+ Thread.Sleep(3000);
if (Instance.clickTimes == 8)
{
- StartCoroutine(nameof(WaitOpenWindow));
- /*
- new Task(() =>
- {
- Thread.Sleep(3000);
- if (Instance.clickTimes == 8)
- {
- ModUploadWindow.ShowWindow(mod);
- }
- }).Start();*/
+ ModUploadWindow.ShowWindow(mod);
}
- });
+ }).Start();*/
}
+ }
- void RefreshToggleState()
+ private void RefreshToggleState(ModDeclare mod_declare, Image icon, TipButton icon_tip_button, Text state_text)
+ {
+ ModState mod_state = WorldBoxMod.AllRecognizedMods[mod_declare];
+ string current_state_text = mod_state switch
{
- mod_state = WorldBoxMod.AllRecognizedMods[mod_declare];
- string current_state_text = mod_state switch
- {
- ModState.DISABLED => LM.Get("mod_state_disabled"),
- ModState.LOADED => LM.Get("mod_state_enabled"),
- ModState.FAILED => LM.Get("mod_state_failed")
- };
- string next_state_text = LM.Get(ModInfoUtils.isModDisabled(mod_declare.UID)
- ? "mod_next_state_disabled"
- : "mod_next_state_enabled");
- state_text.text = $"{current_state_text}, {next_state_text}";
-
- if (mod_state == ModState.FAILED)
- {
- icon_tip_button.textOnClick = "ModLoadFailed Title";
- icon_tip_button.textOnClickDescription = "ModLoadFailed Description";
- icon_tip_button.text_description_2 = mod_declare.FailReason.ToString();
- icon.color = ModInfoUtils.isModDisabled(mod_declare.UID) ? Color.yellow : Color.red;
- return;
- }
-
- icon_tip_button.textOnClick = "ToggleMod Title";
- icon_tip_button.textOnClickDescription = ModInfoUtils.isModDisabled(mod_declare.UID)
- ? "ModDisabled Description"
- : "ModEnabled Description";
- icon_tip_button.text_description_2 = "";
- icon.color = ModInfoUtils.isModDisabled(mod_declare.UID) ? Color.gray : Color.white;
- }
-
- icon.GetComponent().onClick.AddListener(() =>
+ ModState.DISABLED => LM.Get("mod_state_disabled"),
+ ModState.LOADED => LM.Get("mod_state_enabled"),
+ ModState.FAILED => LM.Get("mod_state_failed")
+ };
+ string next_state_text = LM.Get(ModInfoUtils.isModDisabled(mod_declare.UID)
+ ? "mod_next_state_disabled"
+ : "mod_next_state_enabled");
+ state_text.text = $"{current_state_text}, {next_state_text}";
+
+ if (mod_state == ModState.FAILED)
{
- if (ModInfoUtils.isModDisabled(mod_declare.UID))
- {
- ModCompileLoadService.TryEnableMod(mod_declare);
- }
- else
- {
- ModCompileLoadService.DisableMod(mod_declare);
- }
+ icon_tip_button.textOnClick = "ModLoadFailed Title";
+ icon_tip_button.textOnClickDescription = "ModLoadFailed Description";
+ icon_tip_button.text_description_2 = mod_declare.FailReason.ToString();
+ icon.color = ModInfoUtils.isModDisabled(mod_declare.UID) ? Color.yellow : Color.red;
+ return;
+ }
- RefreshToggleState();
- });
- RefreshToggleState();
+ icon_tip_button.textOnClick = "ToggleMod Title";
+ icon_tip_button.textOnClickDescription = ModInfoUtils.isModDisabled(mod_declare.UID)
+ ? "ModDisabled Description"
+ : "ModEnabled Description";
+ icon_tip_button.text_description_2 = "";
+ icon.color = ModInfoUtils.isModDisabled(mod_declare.UID) ? Color.gray : Color.white;
+ }
- configure_button.onClick.AddListener(() =>
+ private void ToggleModState(ModDeclare mod_declare)
+ {
+ if (ModInfoUtils.isModDisabled(mod_declare.UID))
{
- // It can be sure that if mod is IConfigurable, then mod is loaded actually.
- ModConfigureWindow.ShowWindow(configurable?.GetConfig());
- });
- website_button.onClick.AddListener(() => { Application.OpenURL(mod.GetUrl()); });
-
- if (!Config.isEditor)
+ ModCompileLoadService.TryEnableMod(mod_declare);
+ }
+ else
{
- transform.Find("Reload").gameObject.SetActive(false);
- return;
+ ModCompileLoadService.DisableMod(mod_declare);
}
+ }
- if (!ModReloadService.CanReload(mod_declare))
+ private void SetupReloadButton(ModDeclare mod_declare)
+ {
+ var reloadObj = transform.Find("Reload").gameObject;
+ if (!Config.isEditor || !ModReloadService.CanReload(mod_declare))
{
- transform.Find("Reload").gameObject.SetActive(false);
+ reloadObj.SetActive(false);
return;
}
- var reload_button = transform.Find("Reload").GetComponent();
+ var reload_button = reloadObj.GetComponent();
reload_button.gameObject.SetActive(true);
reload_button.onClick.RemoveAllListeners();
reload_button.onClick.AddListener(() =>
@@ -420,5 +374,56 @@ void RefreshToggleState()
}
});
}
+
+ ///
+ /// The mod to display
+ public override void Setup(IMod mod)
+ {
+ _mod = mod;
+ ModDeclare mod_declare = mod.GetDeclaration();
+ ModState mod_state = WorldBoxMod.AllRecognizedMods[mod_declare];
+
+ Text text = transform.Find("Text").GetComponent();
+ var state_text = transform.Find("StateText").GetComponent();
+ text.text = FormatModDisplayText(mod_declare);
+
+ Image icon = transform.Find("Icon").GetComponent();
+ icon.sprite = GetModIcon(mod_declare);
+ TipButton icon_tip_button = icon.GetComponent();
+
+ Button configure_button = transform.Find("Configure").GetComponent();
+ Button website_button = transform.Find("Website").GetComponent();
+ Button open_folder_button = transform.Find("OpenFolder").GetComponent();
+
+ var configurable = mod.GetGameObject()?.GetComponent();
+ configure_button.gameObject.SetActive(configurable != null);
+
+ icon.GetComponent().onClick.RemoveAllListeners();
+ configure_button.onClick.RemoveAllListeners();
+ website_button.onClick.RemoveAllListeners();
+ open_folder_button.onClick.RemoveAllListeners();
+
+ open_folder_button.onClick.AddListener(() => { Application.OpenURL(mod_declare.FolderPath); });
+ configure_button.onClick.AddListener(() =>
+ {
+ // It can be sure that if mod is IConfigurable, then mod is loaded actually.
+ ModConfigureWindow.ShowWindow(configurable?.GetConfig());
+ });
+ website_button.onClick.AddListener(() => { Application.OpenURL(mod.GetUrl()); });
+
+ if (mod_state == ModState.LOADED)
+ {
+ icon.GetComponent().onClick.AddListener(() => OnIconClicked(mod_declare));
+ }
+
+ icon.GetComponent().onClick.AddListener(() =>
+ {
+ ToggleModState(mod_declare);
+ RefreshToggleState(mod_declare, icon, icon_tip_button, state_text);
+ });
+
+ RefreshToggleState(mod_declare, icon, icon_tip_button, state_text);
+ SetupReloadButton(mod_declare);
+ }
}
}
diff --git a/ui/ModUploadModeWindow.cs b/ui/ModUploadModeWindow.cs
new file mode 100644
index 0000000..e7813de
--- /dev/null
+++ b/ui/ModUploadModeWindow.cs
@@ -0,0 +1,357 @@
+using System.IO;
+using NeoModLoader.api;
+using NeoModLoader.General;
+using NeoModLoader.utils;
+using UnityEngine;
+using UnityEngine.UI;
+
+namespace NeoModLoader.ui;
+
+internal class ModUploadModeWindow : AbstractWindow
+{
+ private IMod selected_mod;
+ private Image mod_icon_image;
+ private Text mod_name_text;
+ private Text mod_status_text;
+ private Button upload_button;
+ private Button update_button;
+ private GameObject view_workshop_btn_obj;
+ private GameObject clear_id_btn_obj;
+
+ protected override void Init()
+ {
+ ContentTransform.gameObject.AddComponent().verticalFit =
+ ContentSizeFitter.FitMode.PreferredSize;
+ VerticalLayoutGroup layout = ContentTransform.gameObject.AddComponent();
+ layout.childAlignment = TextAnchor.UpperCenter;
+ layout.childControlHeight = false;
+ layout.childControlWidth = false;
+ layout.childForceExpandHeight = false;
+ layout.childForceExpandWidth = false;
+ layout.spacing = 8;
+ layout.padding = new RectOffset(10, 10, 10, 10);
+
+ ScrollWindow scrollWindow = GetComponent();
+ if (scrollWindow != null && scrollWindow.titleText != null)
+ {
+ scrollWindow.titleText.text = LM.Get("ModUploadModeWindow Title");
+ }
+
+ GameObject info_box = new GameObject("ModInfoBox", typeof(RectTransform));
+ info_box.transform.SetParent(ContentTransform);
+ info_box.transform.localScale = Vector3.one;
+ info_box.GetComponent().sizeDelta = new Vector2(190, 50);
+
+ GameObject icon_obj = new GameObject("Icon", typeof(Image));
+ icon_obj.transform.SetParent(info_box.transform);
+ icon_obj.transform.localScale = Vector3.one;
+ icon_obj.transform.localPosition = new Vector3(-65, 0, 0);
+ icon_obj.GetComponent().sizeDelta = new Vector2(40, 40);
+ mod_icon_image = icon_obj.GetComponent();
+
+ GameObject icon_frame = new GameObject("Frame", typeof(Image));
+ icon_frame.transform.SetParent(icon_obj.transform);
+ icon_frame.transform.localScale = Vector3.one;
+ icon_frame.transform.localPosition = Vector3.zero;
+ icon_frame.GetComponent().sizeDelta = new Vector2(44, 44);
+ Image frame_img = icon_frame.GetComponent();
+ frame_img.sprite = InternalResourcesGetter.GetIconFrame();
+ frame_img.type = Image.Type.Sliced;
+
+ GameObject name_obj = new GameObject("Name", typeof(Text));
+ name_obj.transform.SetParent(info_box.transform);
+ name_obj.transform.localScale = Vector3.one;
+ name_obj.transform.localPosition = new Vector3(20, 10, 0);
+ name_obj.GetComponent().sizeDelta = new Vector2(120, 20);
+ mod_name_text = name_obj.GetComponent();
+ OT.InitializeCommonText(mod_name_text);
+ mod_name_text.alignment = TextAnchor.MiddleLeft;
+ mod_name_text.resizeTextForBestFit = true;
+ mod_name_text.resizeTextMinSize = 8;
+ mod_name_text.resizeTextMaxSize = 12;
+
+ GameObject status_obj = new GameObject("Status", typeof(Text));
+ status_obj.transform.SetParent(info_box.transform);
+ status_obj.transform.localScale = Vector3.one;
+ status_obj.transform.localPosition = new Vector3(20, -10, 0);
+ status_obj.GetComponent().sizeDelta = new Vector2(120, 18);
+ mod_status_text = status_obj.GetComponent();
+ OT.InitializeCommonText(mod_status_text);
+ mod_status_text.alignment = TextAnchor.MiddleLeft;
+ mod_status_text.resizeTextForBestFit = true;
+ mod_status_text.resizeTextMinSize = 6;
+ mod_status_text.resizeTextMaxSize = 9;
+
+ GameObject upload_btn_obj = new GameObject("UploadButton", typeof(Image), typeof(Button));
+ upload_btn_obj.transform.SetParent(ContentTransform);
+ upload_btn_obj.transform.localScale = Vector3.one;
+ upload_btn_obj.GetComponent().sizeDelta = new Vector2(190, 28);
+ Image upload_bg = upload_btn_obj.GetComponent();
+ upload_bg.sprite = SpriteTextureLoader.getSprite("ui/special/button2");
+ upload_bg.type = Image.Type.Sliced;
+
+ GameObject upload_txt_obj = new GameObject("Text", typeof(Text), typeof(LocalizedText));
+ upload_txt_obj.transform.SetParent(upload_btn_obj.transform);
+ upload_txt_obj.transform.localScale = Vector3.one;
+ upload_txt_obj.transform.localPosition = Vector3.zero;
+ upload_txt_obj.GetComponent().sizeDelta = new Vector2(190, 28);
+ Text upload_txt = upload_txt_obj.GetComponent();
+ OT.InitializeCommonText(upload_txt);
+ upload_txt.alignment = TextAnchor.MiddleCenter;
+ upload_txt.text = LM.Get("ModUploadMode_Upload");
+ upload_txt.fontSize = 10;
+
+ LocalizedText upload_loc = upload_txt_obj.GetComponent();
+ upload_loc.key = "ModUploadMode_Upload";
+ LocalizedTextManager.addTextField(upload_loc);
+
+ upload_button = upload_btn_obj.GetComponent();
+ upload_button.onClick.AddListener(OnUploadClicked);
+
+ GameObject update_btn_obj = new GameObject("UpdateButton", typeof(Image), typeof(Button));
+ update_btn_obj.transform.SetParent(ContentTransform);
+ update_btn_obj.transform.localScale = Vector3.one;
+ update_btn_obj.GetComponent().sizeDelta = new Vector2(190, 28);
+ Image update_bg = update_btn_obj.GetComponent();
+ update_bg.sprite = SpriteTextureLoader.getSprite("ui/special/button2");
+ update_bg.type = Image.Type.Sliced;
+
+ GameObject update_txt_obj = new GameObject("Text", typeof(Text), typeof(LocalizedText));
+ update_txt_obj.transform.SetParent(update_btn_obj.transform);
+ update_txt_obj.transform.localScale = Vector3.one;
+ update_txt_obj.transform.localPosition = Vector3.zero;
+ update_txt_obj.GetComponent().sizeDelta = new Vector2(190, 28);
+ Text update_txt = update_txt_obj.GetComponent();
+ OT.InitializeCommonText(update_txt);
+ update_txt.alignment = TextAnchor.MiddleCenter;
+ update_txt.text = LM.Get("ModUploadMode_Update");
+ update_txt.fontSize = 10;
+
+ LocalizedText update_loc = update_txt_obj.GetComponent();
+ update_loc.key = "ModUploadMode_Update";
+ LocalizedTextManager.addTextField(update_loc);
+
+ update_button = update_btn_obj.GetComponent();
+ update_button.onClick.AddListener(OnUpdateClicked);
+
+ view_workshop_btn_obj = new GameObject("ViewWorkshopButton", typeof(Image), typeof(Button));
+ view_workshop_btn_obj.transform.SetParent(ContentTransform);
+ view_workshop_btn_obj.transform.localScale = Vector3.one;
+ view_workshop_btn_obj.GetComponent().sizeDelta = new Vector2(190, 28);
+ Image view_workshop_bg = view_workshop_btn_obj.GetComponent();
+ view_workshop_bg.sprite = SpriteTextureLoader.getSprite("ui/special/button2");
+ view_workshop_bg.type = Image.Type.Sliced;
+
+ GameObject view_workshop_txt_obj = new GameObject("Text", typeof(Text), typeof(LocalizedText));
+ view_workshop_txt_obj.transform.SetParent(view_workshop_btn_obj.transform);
+ view_workshop_txt_obj.transform.localScale = Vector3.one;
+ view_workshop_txt_obj.transform.localPosition = Vector3.zero;
+ view_workshop_txt_obj.GetComponent().sizeDelta = new Vector2(190, 28);
+ Text view_workshop_txt = view_workshop_txt_obj.GetComponent();
+ OT.InitializeCommonText(view_workshop_txt);
+ view_workshop_txt.alignment = TextAnchor.MiddleCenter;
+ view_workshop_txt.text = LM.Get("ModUploadMode_ViewWorkshop");
+ view_workshop_txt.fontSize = 10;
+
+ LocalizedText view_workshop_loc = view_workshop_txt_obj.GetComponent();
+ view_workshop_loc.key = "ModUploadMode_ViewWorkshop";
+ LocalizedTextManager.addTextField(view_workshop_loc);
+
+ view_workshop_btn_obj.GetComponent().onClick.AddListener(OnViewWorkshopClicked);
+
+ GameObject back_btn_obj = new GameObject("BackButton", typeof(Image), typeof(Button));
+ back_btn_obj.transform.SetParent(ContentTransform);
+ back_btn_obj.transform.localScale = Vector3.one;
+ back_btn_obj.GetComponent().sizeDelta = new Vector2(85, 24);
+ Image back_bg = back_btn_obj.GetComponent();
+ back_bg.sprite = SpriteTextureLoader.getSprite("ui/special/special_buttonred");
+ back_bg.type = Image.Type.Sliced;
+
+ GameObject back_txt_obj = new GameObject("Text", typeof(Text), typeof(LocalizedText));
+ back_txt_obj.transform.SetParent(back_btn_obj.transform);
+ back_txt_obj.transform.localScale = Vector3.one;
+ back_txt_obj.transform.localPosition = Vector3.zero;
+ back_txt_obj.GetComponent().sizeDelta = new Vector2(85, 24);
+ Text back_txt = back_txt_obj.GetComponent();
+ OT.InitializeCommonText(back_txt);
+ back_txt.alignment = TextAnchor.MiddleCenter;
+ back_txt.text = LM.Get("ModUploadMode_Back");
+
+ LocalizedText back_loc = back_txt_obj.GetComponent();
+ back_loc.key = "ModUploadMode_Back";
+ LocalizedTextManager.addTextField(back_loc);
+
+ back_btn_obj.GetComponent().onClick.AddListener(() =>
+ {
+ ScrollWindow.moveAllToLeftAndRemove(false);
+ });
+
+ clear_id_btn_obj = new GameObject("ClearIdButton", typeof(Image), typeof(Button));
+ clear_id_btn_obj.transform.SetParent(ContentTransform);
+ clear_id_btn_obj.transform.localScale = Vector3.one;
+ clear_id_btn_obj.GetComponent().sizeDelta = new Vector2(110, 18);
+ Image clear_id_bg = clear_id_btn_obj.GetComponent();
+ clear_id_bg.sprite = SpriteTextureLoader.getSprite("ui/special/special_buttonred");
+ clear_id_bg.type = Image.Type.Sliced;
+
+ GameObject clear_id_txt_obj = new GameObject("Text", typeof(Text), typeof(LocalizedText));
+ clear_id_txt_obj.transform.SetParent(clear_id_btn_obj.transform);
+ clear_id_txt_obj.transform.localScale = Vector3.one;
+ clear_id_txt_obj.transform.localPosition = Vector3.zero;
+ clear_id_txt_obj.GetComponent().sizeDelta = new Vector2(110, 18);
+ Text clear_id_txt = clear_id_txt_obj.GetComponent();
+ OT.InitializeCommonText(clear_id_txt);
+ clear_id_txt.alignment = TextAnchor.MiddleCenter;
+ clear_id_txt.text = LM.Get("Clear Steam ID");
+ clear_id_txt.fontSize = 8;
+ clear_id_txt.resizeTextForBestFit = true;
+ clear_id_txt.resizeTextMinSize = 6;
+ clear_id_txt.resizeTextMaxSize = 9;
+
+ LocalizedText clear_id_loc = clear_id_txt_obj.GetComponent();
+ clear_id_loc.key = "Clear Steam ID";
+ LocalizedTextManager.addTextField(clear_id_loc);
+
+ clear_id_btn_obj.GetComponent().onClick.AddListener(OnClearIdClicked);
+ }
+
+ private void OnViewWorkshopClicked()
+ {
+ if (selected_mod == null) return;
+ var decl = selected_mod.GetDeclaration();
+ if (decl.SteamId > 0)
+ {
+ Application.OpenURL("steam://url/CommunityFilePage/" + decl.SteamId);
+ }
+ }
+
+ private void OnUploadClicked()
+ {
+ if (selected_mod == null) return;
+ ScrollWindow.moveAllToLeftAndRemove(false);
+ ModUploadWindow.ShowWindow(selected_mod, false);
+ }
+
+ private void OnUpdateClicked()
+ {
+ if (selected_mod == null) return;
+ var decl = selected_mod.GetDeclaration();
+ if (decl.SteamId == 0)
+ {
+ AIConfirmWindow.ShowWindow(
+ LM.Get("ModUploadMode_NoSteamIdPrompt"),
+ LM.Get("ModUploadMode_EnterId"),
+ () =>
+ {
+ ScrollWindow.moveAllToLeftAndRemove(false);
+ ModUploadWindow.ShowWindow(selected_mod, true);
+ },
+ LM.Get("ModUploadMode_Back"),
+ null
+ );
+ return;
+ }
+ AskIfWantToChange();
+ }
+
+ private void AskIfWantToChange()
+ {
+ AIConfirmWindow.ShowWindow(
+ LM.Get("ModUploadMode_WantToChange"),
+ LM.Get("ModUploadMode_Yes"),
+ () =>
+ {
+ ModUploadWindow.ShowWindow(selected_mod, true);
+ },
+ LM.Get("ModUploadMode_No"),
+ () =>
+ {
+ AskAiDisclaimerWarning();
+ }
+ );
+ }
+
+ private void AskAiDisclaimerWarning()
+ {
+ AIConfirmWindow.ShowWindow(
+ LM.Get("ModUploadMode_AiWarning"),
+ LM.Get("ModUploadMode_ChangeDisclaimer"),
+ () =>
+ {
+ ModUploadWindow.ShowWindow(selected_mod, true);
+ },
+ LM.Get("ModUploadMode_Update"),
+ () =>
+ {
+ ModUploadWindow.ExecuteDirectUpload(selected_mod);
+ }
+ );
+ }
+
+ private void OnClearIdClicked()
+ {
+ if (selected_mod == null) return;
+ var decl = selected_mod.GetDeclaration();
+ if (decl.SteamId == 0) return;
+ AIConfirmWindow.ShowWindow(
+ LM.Get("Clear Steam ID Confirm"),
+ LM.Get("ModUploadMode_Yes"),
+ () =>
+ {
+ decl.SteamId = 0;
+ decl.Save();
+ ShowWindow(selected_mod);
+ },
+ LM.Get("ModUploadMode_Cancel"),
+ null
+ );
+ }
+
+ public static void ShowWindow(IMod mod)
+ {
+ Instance.selected_mod = mod;
+ var decl = mod.GetDeclaration();
+ if (string.IsNullOrEmpty(decl.IconPath) || !File.Exists(Path.Combine(decl.FolderPath, decl.IconPath)))
+ {
+ Instance.mod_icon_image.sprite = InternalResourcesGetter.GetIcon();
+ }
+ else
+ {
+ Instance.mod_icon_image.sprite = SpriteLoadUtils.LoadSingleSprite(Path.Combine(decl.FolderPath, decl.IconPath));
+ }
+
+ Instance.mod_name_text.text = decl.Name;
+ if (decl.SteamId > 0)
+ {
+ Instance.mod_status_text.text = $"ID: {decl.SteamId}";
+ }
+ else
+ {
+ Instance.mod_status_text.text = LM.Get("ModUploadMode_NewUpload");
+ }
+
+ if (Instance.clear_id_btn_obj != null)
+ {
+ Instance.clear_id_btn_obj.SetActive(decl.SteamId > 0);
+ }
+
+ if (Instance.view_workshop_btn_obj != null)
+ {
+ Instance.view_workshop_btn_obj.SetActive(decl.SteamId > 0);
+ }
+
+ ScrollWindow scrollWindow = Instance.GetComponent();
+ if (scrollWindow != null && scrollWindow.titleText != null)
+ {
+ scrollWindow.titleText.text = LM.Get("ModUploadModeWindow Title");
+ var loc = scrollWindow.titleText.GetComponent();
+ if (loc != null)
+ {
+ loc.key = "ModUploadModeWindow Title";
+ loc.updateText();
+ }
+ }
+
+ ScrollWindow.showWindow(WindowId);
+ }
+}
diff --git a/ui/ModUploadWindow.cs b/ui/ModUploadWindow.cs
index 6b8363a..fe7c872 100644
--- a/ui/ModUploadWindow.cs
+++ b/ui/ModUploadWindow.cs
@@ -11,21 +11,27 @@ namespace NeoModLoader.ui;
internal class ModUploadWindow : AbstractWindow
{
+ private static bool is_update_mode;
private Text changelog_text;
private Text mod_author_text;
private Text mod_description_text;
private Text mod_fileid_text;
+ private InputField mod_fileid_inputfield;
+ private NameInput mod_fileid_name_input;
private Image mod_icon_image;
private Text mod_name_text;
private Text mod_version_text;
private IMod selected_mod;
private readonly Dictionary tag_buttons = new();
+ private LocalizedText upload_button_text_localized;
- public static void ShowWindow(IMod mod)
+ public static void ShowWindow(IMod mod, bool isUpdate = false)
{
+ is_update_mode = isUpdate;
Instance.selected_mod = mod;
ModDeclare mod_decl = mod.GetDeclaration();
- if (string.IsNullOrEmpty(mod_decl.IconPath))
+ if (string.IsNullOrEmpty(mod_decl.IconPath) ||
+ !File.Exists(Path.Combine(mod_decl.FolderPath, mod_decl.IconPath)))
{
Instance.mod_icon_image.sprite = InternalResourcesGetter.GetIcon();
}
@@ -41,8 +47,27 @@ public static void ShowWindow(IMod mod)
Instance.mod_version_text.text = mod_decl.Version;
Instance.mod_description_text.text = mod_decl.Description;
+ if (is_update_mode && mod_decl.SteamId == 0)
+ {
+ Instance.mod_fileid_text.text = "";
+ if (Instance.mod_fileid_inputfield != null) Instance.mod_fileid_inputfield.text = "";
+ if (Instance.mod_fileid_name_input != null) Instance.mod_fileid_name_input.setText("");
+ }
+ else
+ {
+ Instance.mod_fileid_text.text = mod_decl.Name;
+ if (Instance.mod_fileid_inputfield != null) Instance.mod_fileid_inputfield.text = mod_decl.Name;
+ if (Instance.mod_fileid_name_input != null) Instance.mod_fileid_name_input.setText(mod_decl.Name);
+ }
+
Instance.updateTagButtonsUI();
+ if (Instance.upload_button_text_localized != null)
+ {
+ Instance.upload_button_text_localized.key = is_update_mode ? "ModUpdate Title" : "ModUpload Title";
+ Instance.upload_button_text_localized.updateText();
+ }
+
ScrollWindow.showWindow(WindowId);
}
@@ -103,7 +128,20 @@ protected override void Init()
NameInput input_fileid_input = input_fileid.AddComponent();
input_fileid_input.inputField = input_fileid_inputfield.GetComponent();
input_fileid_input.textField = input_fileid_inputfield_text;
- input_fileid_input.addListener((fileid) => { });
+ mod_fileid_inputfield = input_fileid_input.inputField;
+ mod_fileid_name_input = input_fileid_input;
+ input_fileid_input.addListener((fileid) =>
+ {
+ if (is_update_mode && ulong.TryParse(fileid, out ulong parsedId) && parsedId > 0)
+ {
+ if (selected_mod != null)
+ {
+ var decl = selected_mod.GetDeclaration();
+ decl.SteamId = parsedId;
+ decl.Save();
+ }
+ }
+ });
RectTransform input_fileid_inputfield_rect = input_fileid_inputfield.GetComponent();
input_fileid_inputfield_rect.sizeDelta = new(170, 15);
@@ -294,7 +332,7 @@ Text create_grid_text(string name)
Text upload_button_text_text = upload_button_text.GetComponent();
OT.InitializeCommonText(upload_button_text_text);
upload_button_text_text.alignment = TextAnchor.MiddleCenter;
- LocalizedText upload_button_text_localized = upload_button_text.GetComponent();
+ upload_button_text_localized = upload_button_text.GetComponent();
upload_button_text_localized.key = "ModUpload Title";
upload_button.GetComponent().onClick.AddListener(uploadSelectedMod);
@@ -307,26 +345,90 @@ private void uploadSelectedMod()
AIDisclosureWindow.ShowWindow(selected_mod, executeUpload);
}
- private void executeUpload()
+ public static void ExecuteDirectUpload(IMod mod)
{
- string fileId = mod_fileid_text.text;
- if (fileId.Any(c => !char.IsDigit(c)))
+ if (mod == null) return;
+ Instance.selected_mod = mod;
+ is_update_mode = true;
+ var decl = mod.GetDeclaration();
+ if (decl.SteamId > 0)
+ {
+ Instance.executeUpload();
+ }
+ else
{
- fileId = null;
+ ShowWindow(mod, true);
}
+ }
- if (string.IsNullOrEmpty(fileId))
+ private string GetInputFileIdText()
+ {
+ if (Instance.mod_fileid_inputfield != null && !string.IsNullOrEmpty(Instance.mod_fileid_inputfield.text))
{
- ModUploadAuthenticationService.Authenticate().Then(
- () => ModWorkshopService.UploadMod(selected_mod, changelog_text.text,
- ModUploadAuthenticationService.Authed)).Then(ModUploadingProgressWindow.FinishUpload,
- ModUploadingProgressWindow.ErrorUpload);
- return;
+ return Instance.mod_fileid_inputfield.text;
}
+ return mod_fileid_text.text;
+ }
- ulong fileIdLong = ulong.Parse(fileId);
+ private ulong ResolveTargetWorkshopId(string inputText)
+ {
+ if (!string.IsNullOrEmpty(inputText) && ulong.TryParse(inputText, out ulong parsedId) && parsedId > 0)
+ {
+ return parsedId;
+ }
+ return selected_mod != null ? selected_mod.GetDeclaration().SteamId : 0;
+ }
+
+ private void SaveModWorkshopId(ulong fileId)
+ {
+ if (selected_mod == null) return;
+ var decl = selected_mod.GetDeclaration();
+ decl.SteamId = fileId;
+ decl.Save();
+ }
+
+ private void ExecuteUpdateMod(ulong fileIdLong)
+ {
ModWorkshopService.TryEditMod(fileIdLong, selected_mod, changelog_text.text)
- .Then(ModUploadingProgressWindow.FinishUpload, ModUploadingProgressWindow.ErrorUpload).Done();
+ .Then(() =>
+ {
+ SaveModWorkshopId(fileIdLong);
+ ModUploadingProgressWindow.FinishUpload();
+ }, ModUploadingProgressWindow.ErrorUpload).Done();
+ }
+
+ private void ExecuteNewUpload(string uploadName)
+ {
+ ModUploadAuthenticationService.Authenticate().Then(
+ () => ModWorkshopService.UploadMod(selected_mod, changelog_text.text,
+ ModUploadAuthenticationService.Authed, uploadName)).Then(() =>
+ {
+ if (ModUploadingProgressWindow.Instance.fileId > 0)
+ {
+ SaveModWorkshopId(ModUploadingProgressWindow.Instance.fileId);
+ }
+ ModUploadingProgressWindow.FinishUpload();
+ },
+ ModUploadingProgressWindow.ErrorUpload);
+ }
+
+ private void executeUpload()
+ {
+ string inputText = GetInputFileIdText();
+ if (is_update_mode)
+ {
+ ulong fileIdLong = ResolveTargetWorkshopId(inputText);
+ if (fileIdLong > 0)
+ {
+ ExecuteUpdateMod(fileIdLong);
+ return;
+ }
+ }
+
+ string uploadName = (!string.IsNullOrEmpty(inputText) && !ulong.TryParse(inputText, out _))
+ ? inputText
+ : null;
+ ExecuteNewUpload(uploadName);
}
private void updateTagButtonsUI()
diff --git a/ui/UIManager.cs b/ui/UIManager.cs
index 9138222..f7f8918 100644
--- a/ui/UIManager.cs
+++ b/ui/UIManager.cs
@@ -18,6 +18,7 @@ public static void init()
ModConfigureWindow.CreateAndInit("ModConfigure");
AIDisclosureWindow.CreateAndInit("AIDisclosureWindow");
AIConfirmWindow.CreateAndInit("AIConfirmWindow");
+ ModUploadModeWindow.CreateAndInit("ModUploadModeWindow");
PowerButtonCreator.AddButtonToTab(
PowerButtonCreator.CreateWindowButton("NML_ModsList", "NeoModList",
InternalResourcesGetter.GetIcon()),
diff --git a/utils/ModInfoUtils.cs b/utils/ModInfoUtils.cs
index 61bf6c8..27dfb9f 100644
--- a/utils/ModInfoUtils.cs
+++ b/utils/ModInfoUtils.cs
@@ -22,7 +22,7 @@ internal static class ModInfoUtils
private static Queue link_request_mods = new();
private static bool to_install_bepinex;
- private static Dictionary mod_compilation_caches;
+ private static ConcurrentDictionary mod_compilation_caches;
private static readonly ConcurrentDictionary mod_last_update_timestamps = new();
@@ -37,17 +37,18 @@ public static void InitializeModCompileCache()
};
try
{
- mod_compilation_caches =
- JsonConvert.DeserializeObject>(json, json_settings) ??
- new Dictionary();
+ var dict = JsonConvert.DeserializeObject>(json, json_settings);
+ mod_compilation_caches = dict != null
+ ? new ConcurrentDictionary(dict)
+ : new ConcurrentDictionary();
}
catch (Exception)
{
- mod_compilation_caches = new Dictionary();
+ mod_compilation_caches = new ConcurrentDictionary();
}
finally
{
- mod_compilation_caches ??= new Dictionary();
+ mod_compilation_caches ??= new ConcurrentDictionary();
}
if (File.Exists(Paths.ModsDisabledRecordPath))