mirror of
https://github.com/gwdevhub/Daybreak.git
synced 2026-07-15 15:19:57 +00:00
* Mod selection for each launch configuration (Closes #1225) * Store enabled mods with the launched application
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
using Daybreak.Shared.Attributes;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Daybreak.Configuration.Options;
|
||||
|
||||
[OptionsName(Name = "Daybreak API")]
|
||||
[OptionsIgnore]
|
||||
public sealed class DaybreakApiOptions
|
||||
{
|
||||
[JsonPropertyName(nameof(Enabled))]
|
||||
[OptionName(Name = "Enabled", Description = "If true, the Daybreak API is injected into Guild Wars, enabling extended functionality such as loading builds, character switching, and the focus view")]
|
||||
public bool Enabled { get; set; } = false;
|
||||
}
|
||||
@@ -8,10 +8,6 @@ namespace Daybreak.Configuration.Options;
|
||||
[OptionsIgnore]
|
||||
public sealed class FocusViewOptions
|
||||
{
|
||||
[JsonPropertyName(nameof(Enabled))]
|
||||
[OptionName(Name = "Enabled", Description = "If true, the focus view is enabled, showing live information from the game")]
|
||||
public bool Enabled { get; set; } = false;
|
||||
|
||||
[JsonPropertyName(nameof(ExperienceDisplay))]
|
||||
[OptionName(Name = "Experience Display Mode", Description = "Sets how should the experience display show the information")]
|
||||
public ExperienceDisplay ExperienceDisplay { get; set; }
|
||||
|
||||
@@ -334,6 +334,7 @@ public class ProjectConfiguration : PluginConfigurationBase
|
||||
|
||||
optionsProducer.RegisterOptions<SynchronizationOptions>();
|
||||
optionsProducer.RegisterOptions<FocusViewOptions>();
|
||||
optionsProducer.RegisterOptions<DaybreakApiOptions>();
|
||||
|
||||
optionsProducer.RegisterOptions<ToolboxOptions>();
|
||||
optionsProducer.RegisterOptions<UModOptions>();
|
||||
|
||||
@@ -10,6 +10,7 @@ public sealed class ModListEntry
|
||||
public required bool IsVisible { get; init; }
|
||||
public required bool CanManage { get; init; }
|
||||
public required bool CanUninstall { get; init; }
|
||||
public required bool CanDisable { get; init; }
|
||||
|
||||
public bool IsEnabled { get; set; }
|
||||
public bool IsInstalled { get; set; }
|
||||
|
||||
@@ -24,7 +24,7 @@ public sealed class DaybreakApiService(
|
||||
IStubInjector stubInjector,
|
||||
INotificationService notificationService,
|
||||
IHttpClient<ScopedApiContext> scopedApiClient,
|
||||
IOptionsMonitor<FocusViewOptions> liveUpdateableOptions,
|
||||
IOptionsMonitor<DaybreakApiOptions> daybreakApiOptions,
|
||||
ILogger<DaybreakApiService> logger,
|
||||
ILogger<ScopedApiContext> scopedApiLogger)
|
||||
: IDaybreakApiService
|
||||
@@ -42,7 +42,7 @@ public sealed class DaybreakApiService(
|
||||
private readonly IStubInjector stubInjector = stubInjector.ThrowIfNull();
|
||||
private readonly INotificationService notificationService = notificationService.ThrowIfNull();
|
||||
private readonly IHttpClient<ScopedApiContext> scopedApiClient = scopedApiClient.ThrowIfNull();
|
||||
private readonly IOptionsMonitor<FocusViewOptions> liveUpdateableOptions = liveUpdateableOptions.ThrowIfNull();
|
||||
private readonly IOptionsMonitor<DaybreakApiOptions> daybreakApiOptions = daybreakApiOptions.ThrowIfNull();
|
||||
private readonly ILogger<DaybreakApiService> logger = logger.ThrowIfNull();
|
||||
private readonly ILogger<ScopedApiContext> scopedApiLogger = scopedApiLogger.ThrowIfNull();
|
||||
|
||||
@@ -52,10 +52,10 @@ public sealed class DaybreakApiService(
|
||||
|
||||
public bool IsEnabled
|
||||
{
|
||||
get => this.liveUpdateableOptions.CurrentValue.Enabled;
|
||||
get => this.daybreakApiOptions.CurrentValue.Enabled;
|
||||
set
|
||||
{
|
||||
var options = this.liveUpdateableOptions.CurrentValue;
|
||||
var options = this.daybreakApiOptions.CurrentValue;
|
||||
options.Enabled = value;
|
||||
this.optionsProvider.SaveOption(options);
|
||||
}
|
||||
@@ -65,6 +65,7 @@ public sealed class DaybreakApiService(
|
||||
public bool IsVisible => true;
|
||||
public bool CanCustomManage => false;
|
||||
public bool CanUninstall => false;
|
||||
public bool CanDisable => true;
|
||||
|
||||
public IProgressAsyncOperation<bool> PerformUninstallation(CancellationToken cancellationToken)
|
||||
{
|
||||
|
||||
@@ -17,12 +17,10 @@ using Daybreak.Shared.Services.Privilege;
|
||||
using Daybreak.Views;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Photino.NET;
|
||||
|
||||
namespace Daybreak.Services.ApplicationLauncher;
|
||||
|
||||
internal sealed class ApplicationLauncher(
|
||||
PhotinoWindow photinoWindow,
|
||||
IDaybreakInjector daybreakInjector,
|
||||
IGuildWarsExecutableManager guildWarsExecutableManager,
|
||||
INotificationService notificationService,
|
||||
@@ -40,7 +38,6 @@ internal sealed class ApplicationLauncher(
|
||||
|
||||
private static readonly TimeSpan LaunchTimeout = TimeSpan.FromMinutes(1);
|
||||
|
||||
private readonly PhotinoWindow photinoWindow = photinoWindow.ThrowIfNull();
|
||||
private readonly IDaybreakInjector daybreakInjector = daybreakInjector.ThrowIfNull();
|
||||
private readonly IGuildWarsExecutableManager guildWarsExecutableManager =
|
||||
guildWarsExecutableManager.ThrowIfNull();
|
||||
@@ -71,7 +68,7 @@ internal sealed class ApplicationLauncher(
|
||||
cancellationToken,
|
||||
timeout.Token
|
||||
);
|
||||
var gwProcess = await this.LaunchGuildwarsProcess(
|
||||
var (gwProcess, enabledMods) = await this.LaunchGuildwarsProcess(
|
||||
launchConfigurationWithCredentials,
|
||||
cancellation.Token
|
||||
);
|
||||
@@ -85,6 +82,7 @@ internal sealed class ApplicationLauncher(
|
||||
LaunchConfiguration = launchConfigurationWithCredentials,
|
||||
GuildWarsProcess = gwProcess,
|
||||
ProcessId = (uint)gwProcess.Id,
|
||||
EnabledMods = enabledMods,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -103,7 +101,7 @@ internal sealed class ApplicationLauncher(
|
||||
this.daybreakRestartingService.RestartDaybreakAsNormalUser();
|
||||
}
|
||||
|
||||
private async Task<Process?> LaunchGuildwarsProcess(
|
||||
private async Task<(Process? Process, IReadOnlyList<IModService> EnabledMods)> LaunchGuildwarsProcess(
|
||||
LaunchConfigurationWithCredentials launchConfigurationWithCredentials,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
@@ -137,7 +135,7 @@ internal sealed class ApplicationLauncher(
|
||||
title: "Can not launch Guild Wars",
|
||||
description: "No available executables found. All executables are currently in use"
|
||||
);
|
||||
return default;
|
||||
return (default, []);
|
||||
}
|
||||
|
||||
executable = firstAvailableExecutable;
|
||||
@@ -178,11 +176,15 @@ internal sealed class ApplicationLauncher(
|
||||
|
||||
var enabledMods = this
|
||||
.modsManager.GetMods()
|
||||
.Where(m => m.IsEnabled && m.IsInstalled)
|
||||
.Where(m => launchConfigurationWithCredentials.CustomModLoadoutEnabled
|
||||
? m.IsInstalled && (!m.CanDisable || launchConfigurationWithCredentials.EnabledMods?.Contains(m.Name) is true)
|
||||
: m.IsInstalled && m.IsEnabled)
|
||||
.ToList();
|
||||
var disabledMods = this
|
||||
.modsManager.GetMods()
|
||||
.Where(m => !m.IsEnabled && m.IsInstalled)
|
||||
.Where(m => launchConfigurationWithCredentials.CustomModLoadoutEnabled
|
||||
? m.IsInstalled && (!m.CanDisable || launchConfigurationWithCredentials.EnabledMods?.Contains(m.Name) is not true)
|
||||
: m.IsInstalled && !m.IsEnabled)
|
||||
.ToList();
|
||||
|
||||
if (this.launcherOptions.CurrentValue.CancelLaunchOutOfDateMods)
|
||||
@@ -269,7 +271,7 @@ internal sealed class ApplicationLauncher(
|
||||
title: $"{mod.Name} exception",
|
||||
description: $"Mod encountered exception of type {e.GetType().Name} while processing {nameof(mod.OnGuildWarsStartingDisabled)}"
|
||||
);
|
||||
return default;
|
||||
return (default, []);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,7 +295,7 @@ internal sealed class ApplicationLauncher(
|
||||
title: $"{mod.Name} canceled startup",
|
||||
description: $"Mod canceled the startup during {nameof(mod.OnGuildWarsStarting)}"
|
||||
);
|
||||
return default;
|
||||
return (default, []);
|
||||
}
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
@@ -311,7 +313,7 @@ internal sealed class ApplicationLauncher(
|
||||
title: $"{mod.Name} exception",
|
||||
description: $"Mod encountered exception of type {e.GetType().Name} while processing {nameof(mod.OnGuildWarsStarting)}"
|
||||
);
|
||||
return default;
|
||||
return (default, []);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -331,7 +333,7 @@ internal sealed class ApplicationLauncher(
|
||||
title: "Failed to launch Guild Wars",
|
||||
description: $"Injector failed to launch Guild Wars with launch result {launchResult}. Check logs for details"
|
||||
);
|
||||
return default;
|
||||
return (default, []);
|
||||
}
|
||||
|
||||
var threadHandle = launchResult.ThreadHandle;
|
||||
@@ -347,7 +349,7 @@ internal sealed class ApplicationLauncher(
|
||||
title: "Failed to launch Guild Wars",
|
||||
description: "Injector returned invalid process or thread handle. Check logs for details"
|
||||
);
|
||||
return default;
|
||||
return (default, []);
|
||||
}
|
||||
|
||||
// Reset launch context with the launched process
|
||||
@@ -386,7 +388,7 @@ internal sealed class ApplicationLauncher(
|
||||
ProcessId = (uint)pId,
|
||||
}
|
||||
);
|
||||
return default;
|
||||
return (default, []);
|
||||
}
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
@@ -412,7 +414,7 @@ internal sealed class ApplicationLauncher(
|
||||
title: $"{mod.Name} exception",
|
||||
description: $"Mod encountered exception of type {e.GetType().Name} while processing {nameof(mod.OnGuildWarsCreated)}"
|
||||
);
|
||||
return default;
|
||||
return (default, []);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -427,7 +429,7 @@ internal sealed class ApplicationLauncher(
|
||||
title: "Failed to launch Guild Wars",
|
||||
description: $"Injector failed to resume Guild Wars with resume result {resumeResult}. Check logs for details"
|
||||
);
|
||||
return default;
|
||||
return (default, []);
|
||||
}
|
||||
|
||||
var sw = Stopwatch.StartNew();
|
||||
@@ -443,7 +445,7 @@ internal sealed class ApplicationLauncher(
|
||||
title: "Guild Wars process not ready",
|
||||
description: "Guild Wars process exited or timed out before becoming ready. Please check logs for details"
|
||||
);
|
||||
return default;
|
||||
return (default, []);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -485,11 +487,11 @@ internal sealed class ApplicationLauncher(
|
||||
title: $"{mod.Name} exception",
|
||||
description: $"Mod encountered exception of type {e.GetType().Name} while processing {nameof(mod.OnGuildWarsStarted)}"
|
||||
);
|
||||
return default;
|
||||
return (default, []);
|
||||
}
|
||||
}
|
||||
|
||||
return process;
|
||||
return (process, enabledMods);
|
||||
}
|
||||
|
||||
public GuildWarsApplicationLaunchContext? GetGuildwarsProcess(
|
||||
|
||||
@@ -57,6 +57,7 @@ internal sealed class DirectSongService(
|
||||
public bool IsVisible => true;
|
||||
public bool CanCustomManage => false;
|
||||
public bool CanUninstall => true;
|
||||
public bool CanDisable => true;
|
||||
public bool IsEnabled
|
||||
{
|
||||
get => this.options.CurrentValue.Enabled;
|
||||
|
||||
@@ -47,6 +47,7 @@ internal sealed class GuildWarsVersionChecker(
|
||||
private readonly ILogger<GuildWarsVersionChecker> logger = logger.ThrowIfNull();
|
||||
|
||||
public bool CanUninstall => false;
|
||||
public bool CanDisable => true;
|
||||
|
||||
public IProgressAsyncOperation<bool> PerformUninstallation(CancellationToken cancellationToken)
|
||||
{
|
||||
|
||||
@@ -111,6 +111,8 @@ internal sealed class LaunchConfigurationService(
|
||||
config.Executable = launchConfigurationWithCredentials.ExecutablePath;
|
||||
config.Arguments = launchConfigurationWithCredentials.Arguments;
|
||||
config.SteamSupport = launchConfigurationWithCredentials.SteamSupport;
|
||||
config.EnabledMods = launchConfigurationWithCredentials.EnabledMods;
|
||||
config.CustomModLoadoutEnabled = launchConfigurationWithCredentials.CustomModLoadoutEnabled;
|
||||
var options = this.liveUpdateableOptions.CurrentValue;
|
||||
options.LaunchConfigurations = configs;
|
||||
this.optionsProvider.SaveOption(options);
|
||||
@@ -124,7 +126,9 @@ internal sealed class LaunchConfigurationService(
|
||||
Executable = launchConfigurationWithCredentials.ExecutablePath,
|
||||
Identifier = launchConfigurationWithCredentials.Identifier,
|
||||
Arguments = launchConfigurationWithCredentials.Arguments,
|
||||
SteamSupport = launchConfigurationWithCredentials.SteamSupport
|
||||
SteamSupport = launchConfigurationWithCredentials.SteamSupport,
|
||||
EnabledMods = launchConfigurationWithCredentials.EnabledMods,
|
||||
CustomModLoadoutEnabled = launchConfigurationWithCredentials.CustomModLoadoutEnabled
|
||||
});
|
||||
var optionsNew = this.liveUpdateableOptions.CurrentValue;
|
||||
optionsNew.LaunchConfigurations = configs;
|
||||
@@ -152,7 +156,9 @@ internal sealed class LaunchConfigurationService(
|
||||
ExecutablePath = launchConfiguration.Executable,
|
||||
Arguments = launchConfiguration.Arguments,
|
||||
Name = launchConfiguration.Name,
|
||||
SteamSupport = launchConfiguration.SteamSupport ?? true
|
||||
SteamSupport = launchConfiguration.SteamSupport ?? true,
|
||||
EnabledMods = launchConfiguration.EnabledMods,
|
||||
CustomModLoadoutEnabled = launchConfiguration.CustomModLoadoutEnabled ?? false
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,6 +99,7 @@ internal sealed class ReShadeService(
|
||||
File.Exists(ReShadeLogPath) &&
|
||||
File.Exists(ConfigIniPath);
|
||||
public bool CanUninstall => true;
|
||||
public bool CanDisable => true;
|
||||
|
||||
public IProgressAsyncOperation<bool> PerformUninstallation(CancellationToken cancellationToken)
|
||||
{
|
||||
|
||||
@@ -57,6 +57,7 @@ internal sealed class ToolboxService(
|
||||
public bool IsVisible => true;
|
||||
public bool CanCustomManage => false;
|
||||
public bool CanUninstall => true;
|
||||
public bool CanDisable => true;
|
||||
public bool IsEnabled
|
||||
{
|
||||
get => this.toolboxOptions.CurrentValue.Enabled;
|
||||
|
||||
@@ -64,6 +64,7 @@ internal sealed class UModService(
|
||||
public bool IsVisible => true;
|
||||
public bool CanCustomManage => true;
|
||||
public bool CanUninstall => true;
|
||||
public bool CanDisable => true;
|
||||
public bool IsEnabled
|
||||
{
|
||||
get => this.uModOptions.CurrentValue.Enabled;
|
||||
|
||||
@@ -1,117 +1,114 @@
|
||||
<div class="container">
|
||||
<div class="title-bar-additional-btns-left">
|
||||
<button class="title-bar-btn" @onclick="this.ViewModel.CreateNewLaunchConfiguration" title="Add new launch configuration">
|
||||
<FluentIcon Value="new Microsoft.FluentUI.AspNetCore.Components.Icons.Regular.Size16.Add()" />
|
||||
</button>
|
||||
<div class="title-bar-additional-btns-left">
|
||||
<button class="title-bar-btn" @onclick="this.ViewModel.CreateNewLaunchConfiguration"
|
||||
title="Add new launch configuration">
|
||||
<FluentIcon Value="new Microsoft.FluentUI.AspNetCore.Components.Icons.Regular.Size16.Add()" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="content backdrop-panel">
|
||||
<div class="option-view-title">
|
||||
<h3>Launch Configurations</h3>
|
||||
</div>
|
||||
<div class="content backdrop-panel">
|
||||
<div class="option-view-title">
|
||||
<h3>Launch Configurations</h3>
|
||||
</div>
|
||||
<div class="launch-configs-list">
|
||||
@foreach (var config in this.ViewModel.LaunchConfigurations)
|
||||
{
|
||||
<div class="launch-configs-row">
|
||||
<div class="launch-config-identifier">
|
||||
<div class="launch-config-identifier-label">
|
||||
<FluentLabel>Identifier</FluentLabel>
|
||||
</div>
|
||||
<div class="launch-config-identifier-field">
|
||||
<FluentTextField Value="@config.Identifier"
|
||||
ReadOnly="true"
|
||||
Spellcheck="false"
|
||||
Placeholder="Launch config identifier">
|
||||
<FluentIcon Value="@(new Microsoft.FluentUI.AspNetCore.Components.Icons.Regular.Size16.Delete())"
|
||||
@onclick="@(() => this.ViewModel.DeleteLaunchConfiguration(config))"
|
||||
slot="end"
|
||||
Color="Color.Neutral"
|
||||
Title="Delete launch configuration" />
|
||||
</FluentTextField>
|
||||
</div>
|
||||
</div>
|
||||
<div class="launch-config-name">
|
||||
<div class="launch-config-name-label">
|
||||
<FluentLabel>Name</FluentLabel>
|
||||
</div>
|
||||
<div class="launch-config-name-field">
|
||||
<FluentTextField Value="@config.Name"
|
||||
ValueChanged="@((newValue) => this.ViewModel.CustomNameChanged(config, newValue))"
|
||||
Spellcheck="false"
|
||||
Immediate="true"
|
||||
ImmediateDelay="500"
|
||||
InputMode="InputMode.Text"
|
||||
AutoComplete="false"
|
||||
Placeholder="Custom name" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="launch-config-executable">
|
||||
<div class="launch-config-executable-label">
|
||||
<FluentLabel>Executable</FluentLabel>
|
||||
</div>
|
||||
<div class="launch-config-executable-field">
|
||||
<FluentSelect TOption="string"
|
||||
Multiple="false"
|
||||
Width="100%"
|
||||
Position="SelectPosition.Below"
|
||||
Items="@this.ViewModel.Executables"
|
||||
SelectedOption="@config.ExecutablePath"
|
||||
SelectedOptionChanged="@((newValue) => this.ViewModel.ExecutableChanged(config, newValue))">
|
||||
<OptionTemplate>
|
||||
<div title="@(context)">
|
||||
@(string.IsNullOrWhiteSpace(context) ? "Any Executable" : context)
|
||||
</div>
|
||||
</OptionTemplate>
|
||||
</FluentSelect>
|
||||
</div>
|
||||
</div>
|
||||
<div class="launch-config-credentials">
|
||||
<div class="launch-config-credentials-label">
|
||||
<FluentLabel>Credentials</FluentLabel>
|
||||
</div>
|
||||
<div class="launch-config-credentials-field">
|
||||
<FluentSelect TOption="string"
|
||||
Multiple="false"
|
||||
Width="100%"
|
||||
Position="SelectPosition.Below"
|
||||
Items="@this.ViewModel.Credentials.Select(c => c.Identifier ?? string.Empty)"
|
||||
SelectedOption="@(config.Credentials?.Identifier ?? string.Empty)"
|
||||
SelectedOptionChanged="@((newValue) => this.ViewModel.CredentialsChanged(config, newValue))">
|
||||
<OptionTemplate>
|
||||
@(this.ViewModel.Credentials.FirstOrDefault(c => c.Identifier == context)?.Username ?? "Unknown")
|
||||
</OptionTemplate>
|
||||
</FluentSelect>
|
||||
</div>
|
||||
</div>
|
||||
<div class="launch-config-args">
|
||||
<div class="launch-config-args-label">
|
||||
<FluentLabel>Custom Arguments</FluentLabel>
|
||||
</div>
|
||||
<div class="launch-config-args-field">
|
||||
<FluentTextField Value="@config.Arguments"
|
||||
ValueChanged="@((newValue) => this.ViewModel.CustomArgsChanged(config, newValue))"
|
||||
Spellcheck="false"
|
||||
Immediate="true"
|
||||
ImmediateDelay="500"
|
||||
InputMode="InputMode.Text"
|
||||
AutoComplete="false"
|
||||
Placeholder="Launch arguments"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="launch-config-steam">
|
||||
<div class="launch-config-steam-label">
|
||||
<FluentLabel>Steam support</FluentLabel>
|
||||
</div>
|
||||
<div class="launch-config-steam-field">
|
||||
<FluentCheckbox Value="@config.SteamSupport"
|
||||
ValueChanged="@((newValue) => this.ViewModel.SteamSupportChanged(config, newValue))" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="launch-configs-list">
|
||||
@foreach (var config in this.ViewModel.LaunchConfigurations)
|
||||
{
|
||||
<div class="launch-configs-row">
|
||||
<div class="launch-config-identifier">
|
||||
<div class="launch-config-identifier-label">
|
||||
<FluentLabel>Identifier</FluentLabel>
|
||||
</div>
|
||||
<div class="launch-config-identifier-field">
|
||||
<FluentTextField Value="@config.Identifier" ReadOnly="true" Spellcheck="false"
|
||||
Placeholder="Launch config identifier">
|
||||
<FluentIcon Value="@(new Microsoft.FluentUI.AspNetCore.Components.Icons.Regular.Size16.Delete())"
|
||||
@onclick="@(() => this.ViewModel.DeleteLaunchConfiguration(config))" slot="end" Color="Color.Neutral"
|
||||
Title="Delete launch configuration" />
|
||||
</FluentTextField>
|
||||
</div>
|
||||
</div>
|
||||
<div class="launch-config-name">
|
||||
<div class="launch-config-name-label">
|
||||
<FluentLabel>Name</FluentLabel>
|
||||
</div>
|
||||
<div class="launch-config-name-field">
|
||||
<FluentTextField Value="@config.Name"
|
||||
ValueChanged="@((newValue) => this.ViewModel.CustomNameChanged(config, newValue))" Spellcheck="false"
|
||||
Immediate="true" ImmediateDelay="500" InputMode="InputMode.Text" AutoComplete="false"
|
||||
Placeholder="Custom name" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="launch-config-executable">
|
||||
<div class="launch-config-executable-label">
|
||||
<FluentLabel>Executable</FluentLabel>
|
||||
</div>
|
||||
<div class="launch-config-executable-field">
|
||||
<FluentSelect TOption="string" Multiple="false" Width="100%" Position="SelectPosition.Below"
|
||||
Items="@this.ViewModel.Executables" SelectedOption="@config.ExecutablePath"
|
||||
SelectedOptionChanged="@((newValue) => this.ViewModel.ExecutableChanged(config, newValue))">
|
||||
<OptionTemplate>
|
||||
<div title="@(context)">
|
||||
@(string.IsNullOrWhiteSpace(context) ? "Any Executable" : context)
|
||||
</div>
|
||||
</OptionTemplate>
|
||||
</FluentSelect>
|
||||
</div>
|
||||
</div>
|
||||
<div class="launch-config-credentials">
|
||||
<div class="launch-config-credentials-label">
|
||||
<FluentLabel>Credentials</FluentLabel>
|
||||
</div>
|
||||
<div class="launch-config-credentials-field">
|
||||
<FluentSelect TOption="string" Multiple="false" Width="100%" Position="SelectPosition.Below"
|
||||
Items="@this.ViewModel.Credentials.Select(c => c.Identifier ?? string.Empty)"
|
||||
SelectedOption="@(config.Credentials?.Identifier ?? string.Empty)"
|
||||
SelectedOptionChanged="@((newValue) => this.ViewModel.CredentialsChanged(config, newValue))">
|
||||
<OptionTemplate>
|
||||
@(this.ViewModel.Credentials.FirstOrDefault(c => c.Identifier == context)?.Username ?? "Unknown")
|
||||
</OptionTemplate>
|
||||
</FluentSelect>
|
||||
</div>
|
||||
</div>
|
||||
<div class="launch-config-args">
|
||||
<div class="launch-config-args-label">
|
||||
<FluentLabel>Custom Arguments</FluentLabel>
|
||||
</div>
|
||||
<div class="launch-config-args-field">
|
||||
<FluentTextField Value="@config.Arguments"
|
||||
ValueChanged="@((newValue) => this.ViewModel.CustomArgsChanged(config, newValue))" Spellcheck="false"
|
||||
Immediate="true" ImmediateDelay="500" InputMode="InputMode.Text" AutoComplete="false"
|
||||
Placeholder="Launch arguments" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="launch-config-steam">
|
||||
<div class="launch-config-steam-label">
|
||||
<FluentLabel>Steam support</FluentLabel>
|
||||
</div>
|
||||
<div class="launch-config-steam-field">
|
||||
<FluentCheckbox Value="@config.SteamSupport"
|
||||
ValueChanged="@((newValue) => this.ViewModel.SteamSupportChanged(config, newValue))" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="launch-config-custom-mods">
|
||||
<div class="launch-config-custom-mods-label">
|
||||
<FluentLabel>Custom mod loadout</FluentLabel>
|
||||
</div>
|
||||
<div class="launch-config-custom-mods-field">
|
||||
<FluentCheckbox Value="@config.CustomModLoadoutEnabled"
|
||||
ValueChanged="@((newValue) => this.ViewModel.CustomModLoadoutChanged(config, newValue))" />
|
||||
<FluentButton Appearance="Appearance.Neutral"
|
||||
@onclick="@(() => this.ViewModel.ManageCustomMods(config))"
|
||||
Disabled="@(!config.CustomModLoadoutEnabled)"
|
||||
Title="Manage custom mods for this launch configuration">
|
||||
Manage Mods
|
||||
</FluentButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@using Daybreak.Shared.Models;
|
||||
@page "/launch-configurations"
|
||||
@inherits ViewBase<LaunchConfigurationsView, LaunchConfigurationsViewModel>
|
||||
@inherits ViewBase<LaunchConfigurationsView, LaunchConfigurationsViewModel>
|
||||
|
||||
@@ -4,18 +4,22 @@ using Daybreak.Shared.Services.Credentials;
|
||||
using Daybreak.Shared.Services.ExecutableManagement;
|
||||
using Daybreak.Shared.Services.LaunchConfigurations;
|
||||
using System.Extensions;
|
||||
using TrailBlazr.Services;
|
||||
using TrailBlazr.ViewModels;
|
||||
|
||||
namespace Daybreak.Views;
|
||||
|
||||
public sealed class LaunchConfigurationsViewModel(
|
||||
ILaunchConfigurationService launchConfigurationService,
|
||||
ICredentialManager credentialManager,
|
||||
IGuildWarsExecutableManager guildWarsExecutableManager)
|
||||
IGuildWarsExecutableManager guildWarsExecutableManager,
|
||||
IViewManager viewManager)
|
||||
: ViewModelBase<LaunchConfigurationsViewModel, LaunchConfigurationsView>
|
||||
{
|
||||
private readonly ILaunchConfigurationService launchConfigurationService = launchConfigurationService;
|
||||
private readonly ICredentialManager credentialManager = credentialManager;
|
||||
private readonly IGuildWarsExecutableManager guildWarsExecutableManager = guildWarsExecutableManager;
|
||||
private readonly IViewManager viewManager = viewManager;
|
||||
|
||||
public List<string> Executables { get; } = [];
|
||||
public List<LoginCredentials> Credentials { get; } = [];
|
||||
@@ -83,4 +87,15 @@ public sealed class LaunchConfigurationsViewModel(
|
||||
configuration.SteamSupport = isEnabled;
|
||||
this.launchConfigurationService.SaveConfiguration(configuration);
|
||||
}
|
||||
|
||||
public void CustomModLoadoutChanged(LaunchConfigurationWithCredentials configuration, bool isEnabled)
|
||||
{
|
||||
configuration.CustomModLoadoutEnabled = isEnabled;
|
||||
this.launchConfigurationService.SaveConfiguration(configuration);
|
||||
}
|
||||
|
||||
public void ManageCustomMods(LaunchConfigurationWithCredentials configuration)
|
||||
{
|
||||
this.viewManager.ShowView<ModsView>((nameof(ModsView.LaunchConfigurationIdentifier), configuration.Identifier ?? throw new InvalidOperationException("Managed configuration identifier cannot be null")));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,8 @@
|
||||
.launch-config-credentials,
|
||||
.launch-config-name,
|
||||
.launch-config-args,
|
||||
.launch-config-steam {
|
||||
.launch-config-steam,
|
||||
.launch-config-custom-mods {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
@@ -65,7 +66,8 @@
|
||||
.launch-config-credentials-label,
|
||||
.launch-config-name-label,
|
||||
.launch-config-args-label,
|
||||
.launch-config-steam-label {
|
||||
.launch-config-steam-label,
|
||||
.launch-config-custom-mods-label {
|
||||
width: 250px;
|
||||
}
|
||||
|
||||
@@ -74,8 +76,13 @@
|
||||
.launch-config-credentials-field,
|
||||
.launch-config-name-field,
|
||||
.launch-config-args-field,
|
||||
.launch-config-steam-field {
|
||||
.launch-config-steam-field,
|
||||
.launch-config-custom-mods-field {
|
||||
width: 50%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
/* Limit dropdown height to prevent container overflow */
|
||||
@@ -110,7 +117,8 @@
|
||||
.launch-config-credentials-label ::deep *,
|
||||
.launch-config-name-label ::deep *,
|
||||
.launch-config-args-label ::deep *,
|
||||
.launch-config-steam-label ::deep * {
|
||||
.launch-config-steam-label ::deep *,
|
||||
.launch-config-custom-mods-label ::deep * {
|
||||
font-size: var(--font-size-large) !important;
|
||||
}
|
||||
|
||||
@@ -119,7 +127,8 @@
|
||||
.launch-config-credentials-field ::deep fluent-text-field,
|
||||
.launch-config-name-field ::deep fluent-text-field,
|
||||
.launch-config-args-field ::deep fluent-text-field,
|
||||
.launch-config-steam-field ::deep fluent-text-field {
|
||||
.launch-config-steam-field ::deep fluent-text-field,
|
||||
.launch-config-custom-mods-field ::deep fluent-text-field {
|
||||
width: 100%;
|
||||
box-sizing: border-box !important;
|
||||
font-size: var(--font-size-large);
|
||||
@@ -130,9 +139,10 @@
|
||||
.launch-config-credentials-field ::deep fluent-select,
|
||||
.launch-config-name-field ::deep fluent-select,
|
||||
.launch-config-args-field ::deep fluent-select,
|
||||
.launch-config-steam-field ::deep fluent-select {
|
||||
.launch-config-steam-field ::deep fluent-select,
|
||||
.launch-config-custom-mods-field ::deep fluent-select {
|
||||
width: 100%;
|
||||
box-sizing: border-box !important;
|
||||
font-size: var(--font-size-large) !important;
|
||||
--type-ramp-base-font-size: var(--font-size-large);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ using System.ComponentModel;
|
||||
using System.Core.Extensions;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Daybreak.Configuration.Options;
|
||||
using Daybreak.Shared.Models;
|
||||
using Daybreak.Shared.Models.Api;
|
||||
using Daybreak.Shared.Models.LaunchConfigurations;
|
||||
@@ -13,7 +12,6 @@ using Daybreak.Shared.Services.ApplicationLauncher;
|
||||
using Daybreak.Shared.Services.LaunchConfigurations;
|
||||
using Daybreak.Shared.Services.Notifications;
|
||||
using Daybreak.Shared.Services.Onboarding;
|
||||
using Microsoft.Extensions.Options;
|
||||
using TrailBlazr.Services;
|
||||
using TrailBlazr.ViewModels;
|
||||
|
||||
@@ -25,8 +23,7 @@ public sealed class LaunchViewModel(
|
||||
IDaybreakApiService daybreakApiService,
|
||||
ILaunchConfigurationService launchConfigurationService,
|
||||
IOnboardingService onboardingService,
|
||||
IApplicationLauncher applicationLauncher,
|
||||
IOptionsMonitor<FocusViewOptions> focusViewOptions
|
||||
IApplicationLauncher applicationLauncher
|
||||
) : ViewModelBase<LaunchViewModel, LaunchView>, INotifyPropertyChanged, IDisposable
|
||||
{
|
||||
private static readonly TimeSpan LaunchTimeout = TimeSpan.FromSeconds(10);
|
||||
@@ -38,8 +35,6 @@ public sealed class LaunchViewModel(
|
||||
launchConfigurationService.ThrowIfNull();
|
||||
private readonly IOnboardingService onboardingService = onboardingService.ThrowIfNull();
|
||||
private readonly IApplicationLauncher applicationLauncher = applicationLauncher.ThrowIfNull();
|
||||
private readonly IOptionsMonitor<FocusViewOptions> focusViewOptions =
|
||||
focusViewOptions.ThrowIfNull();
|
||||
|
||||
private CancellationTokenSource? cancellationTokenSource;
|
||||
private string? autoLaunchConfigurationId;
|
||||
@@ -476,11 +471,11 @@ public sealed class LaunchViewModel(
|
||||
}
|
||||
else
|
||||
{
|
||||
// Game running with API mod - can attach or kill based on focus view setting
|
||||
// Game running with API mod available - can attach to use FocusView
|
||||
launcherViewContext.GameRunning = true;
|
||||
launcherViewContext.CanLaunch = false;
|
||||
launcherViewContext.CanAttach = this.focusViewOptions.CurrentValue.Enabled;
|
||||
launcherViewContext.CanKill = !this.focusViewOptions.CurrentValue.Enabled;
|
||||
launcherViewContext.CanAttach = true;
|
||||
launcherViewContext.CanKill = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -546,7 +541,8 @@ public sealed class LaunchViewModel(
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
if (!this.focusViewOptions.CurrentValue.Enabled)
|
||||
// Only attach if Daybreak API was enabled for this launch
|
||||
if (!context.EnabledMods.OfType<IDaybreakApiService>().Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -593,11 +589,7 @@ public sealed class LaunchViewModel(
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.focusViewOptions.CurrentValue.Enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// If we have an apiContext, the Daybreak API is running and we can attach
|
||||
using var notificationToken = this.notificationService.NotifyInformation(
|
||||
title: "Attaching to Guild Wars process...",
|
||||
description: "Attempting to attach to Guild Wars process"
|
||||
@@ -677,7 +669,9 @@ public sealed class LaunchViewModel(
|
||||
this.launchConfigurationService.SetLastLaunchConfigurationWithCredentials(
|
||||
launcherViewContext.Configuration
|
||||
);
|
||||
if (!this.focusViewOptions.CurrentValue.Enabled)
|
||||
|
||||
// Only attach to focus view if Daybreak API was enabled for this launch
|
||||
if (!launchedContext.EnabledMods.OfType<IDaybreakApiService>().Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,66 +1,79 @@
|
||||
<div class="stretch-container backdrop-panel">
|
||||
<div class="mods-container">
|
||||
@foreach (var mod in this.ViewModel.Mods)
|
||||
{
|
||||
if (!mod.IsVisible)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
<div class="view-title">
|
||||
<h3>
|
||||
@if (this.ViewModel.LaunchConfiguration is not null)
|
||||
{
|
||||
@($"Mods - {this.ViewModel.LaunchConfiguration.Name ?? this.ViewModel.LaunchConfiguration.Identifier}")
|
||||
}
|
||||
else
|
||||
{
|
||||
@("Mods")
|
||||
}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div class="mod-row">
|
||||
<div class="mod-row-left">
|
||||
<div class="mod-name-label">@mod.Name</div>
|
||||
<div class="mod-description-label">@mod.Description</div>
|
||||
</div>
|
||||
<div class="mod-row-right">
|
||||
<div class="mod-toggle @((mod.IsInstalled ? string.Empty : "disabled"))">
|
||||
<FluentLabel class="label">@(mod.IsEnabled ? "Enabled" : "Disabled")</FluentLabel>
|
||||
<FluentSwitch Value="@(mod.IsEnabled)"
|
||||
@onchange="() => this.ViewModel.ToggleMod(mod)" />
|
||||
</div>
|
||||
@if (mod.Loading)
|
||||
{
|
||||
<SpinnerWidget IsLoading="true" />
|
||||
}
|
||||
else if (!mod.IsInstalled)
|
||||
{
|
||||
<div class="mod-action-button"
|
||||
@onclick="(() => this.ViewModel.InstallMod(mod))">
|
||||
Install
|
||||
</div>
|
||||
}
|
||||
else if (mod.CanUpdate)
|
||||
{
|
||||
<div class="mod-action-button"
|
||||
@onclick="@(() => this.ViewModel.UpdateMod(mod))">
|
||||
Update
|
||||
</div>
|
||||
}
|
||||
else if (mod.IsEnabled && mod.CanManage)
|
||||
{
|
||||
<div class="mod-action-button"
|
||||
@onclick="@(() => this.ViewModel.ManageMod(mod))">
|
||||
Manage
|
||||
</div>
|
||||
}
|
||||
else if (mod.CanUninstall)
|
||||
{
|
||||
<div class="mod-action-button"
|
||||
@onclick="@(() => this.ViewModel.UninstallMod(mod))">
|
||||
Uninstall
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="mod-action-button-disabled">
|
||||
Installed
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="mods-container">
|
||||
@foreach (var mod in this.ViewModel.Mods)
|
||||
{
|
||||
if (!mod.IsVisible)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
<div class="mod-row">
|
||||
<div class="mod-row-left">
|
||||
<div class="mod-name-label">@mod.Name</div>
|
||||
<div class="mod-description-label">@mod.Description</div>
|
||||
</div>
|
||||
<div class="mod-row-right">
|
||||
<div class="mod-toggle @((mod.IsInstalled && mod.CanDisable ? string.Empty : "disabled"))">
|
||||
<FluentLabel class="label">@(mod.IsEnabled ? "Enabled" : "Disabled")</FluentLabel>
|
||||
<FluentSwitch Value="@(mod.IsEnabled)" Disabled="@(!mod.CanDisable)" @onchange="() => this.ViewModel.ToggleMod(mod)" />
|
||||
</div>
|
||||
@if (mod.Loading)
|
||||
{
|
||||
<SpinnerWidget IsLoading="true" />
|
||||
}
|
||||
else if (!mod.IsInstalled)
|
||||
{
|
||||
<div class="mod-action-button" @onclick="(() => this.ViewModel.InstallMod(mod))">
|
||||
Install
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
else if (mod.CanUpdate)
|
||||
{
|
||||
<div class="mod-action-button" @onclick="@(() => this.ViewModel.UpdateMod(mod))">
|
||||
Update
|
||||
</div>
|
||||
}
|
||||
else if (mod.IsEnabled && mod.CanManage)
|
||||
{
|
||||
<div class="mod-action-button" @onclick="@(() => this.ViewModel.ManageMod(mod))">
|
||||
Manage
|
||||
</div>
|
||||
}
|
||||
else if (mod.CanUninstall)
|
||||
{
|
||||
<div class="mod-action-button" @onclick="@(() => this.ViewModel.UninstallMod(mod))">
|
||||
Uninstall
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="mod-action-button-disabled">
|
||||
Installed
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@page "/mods"
|
||||
@inherits ViewBase<ModsView, ModsViewModel>
|
||||
@inherits ViewBase<ModsView, ModsViewModel>
|
||||
@code
|
||||
{
|
||||
[Parameter]
|
||||
public string? LaunchConfigurationIdentifier { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System.Extensions.Core;
|
||||
using Daybreak.Models;
|
||||
using Daybreak.Shared.Models.LaunchConfigurations;
|
||||
using Daybreak.Shared.Services.LaunchConfigurations;
|
||||
using Daybreak.Shared.Services.Mods;
|
||||
using Daybreak.Shared.Services.Notifications;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -7,25 +9,29 @@ using TrailBlazr.Services;
|
||||
using TrailBlazr.ViewModels;
|
||||
|
||||
namespace Daybreak.Views;
|
||||
|
||||
public sealed class ModsViewModel(
|
||||
INotificationService notificationService,
|
||||
IViewManager viewManager,
|
||||
IModsManager modsManager,
|
||||
ILaunchConfigurationService launchConfigurationService,
|
||||
ILogger<ModsViewModel> logger)
|
||||
: ViewModelBase<ModsViewModel, ModsView>
|
||||
{
|
||||
private readonly INotificationService notificationService = notificationService;
|
||||
private readonly IViewManager viewManager = viewManager;
|
||||
private readonly IModsManager modService = modsManager;
|
||||
private readonly ILaunchConfigurationService launchConfigurationService = launchConfigurationService;
|
||||
private readonly ILogger<ModsViewModel> logger = logger;
|
||||
|
||||
private CancellationTokenSource? cts;
|
||||
|
||||
public LaunchConfigurationWithCredentials? LaunchConfiguration { get; private set; }
|
||||
public IEnumerable<ModListEntry> Mods { get; private set; } = [];
|
||||
|
||||
public override ValueTask ParametersSet(ModsView view, CancellationToken cancellationToken)
|
||||
{
|
||||
this.Mods = [.. this.modService.GetMods().OrderBy(m => m.Name)
|
||||
var globalMods = this.modService.GetMods().OrderBy(m => m.Name)
|
||||
.Select(m => new ModListEntry
|
||||
{
|
||||
Name = m.Name,
|
||||
@@ -36,9 +42,26 @@ public sealed class ModsViewModel(
|
||||
CanManage = m.CanCustomManage,
|
||||
IsInstalled = m.IsInstalled,
|
||||
CanUninstall = m.CanUninstall,
|
||||
CanDisable = m.CanDisable,
|
||||
CanUpdate = false,
|
||||
Loading = true,
|
||||
})];
|
||||
}).ToList();
|
||||
|
||||
if (this.launchConfigurationService.GetLaunchConfigurations().FirstOrDefault(l => l.Identifier == view.LaunchConfigurationIdentifier) is LaunchConfigurationWithCredentials config)
|
||||
{
|
||||
this.LaunchConfiguration = config;
|
||||
foreach (var mod in globalMods)
|
||||
{
|
||||
// Mods that cannot be disabled are always enabled
|
||||
mod.IsEnabled = !mod.CanDisable || config.EnabledMods?.Any(m => m == mod.Name) is true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
this.LaunchConfiguration = null;
|
||||
}
|
||||
|
||||
this.Mods = globalMods;
|
||||
this.viewManager.ShowViewRequested += this.ViewManager_ShowViewRequested;
|
||||
this.cts?.Cancel();
|
||||
this.cts?.Dispose();
|
||||
@@ -49,13 +72,34 @@ public sealed class ModsViewModel(
|
||||
|
||||
public void ToggleMod(ModListEntry mod)
|
||||
{
|
||||
if (!mod.IsInstalled)
|
||||
if (!mod.IsInstalled || !mod.CanDisable)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
mod.IsEnabled = !mod.IsEnabled;
|
||||
mod.ModService.IsEnabled = mod.IsEnabled;
|
||||
if (this.LaunchConfiguration is not null)
|
||||
{
|
||||
if (mod.IsEnabled)
|
||||
{
|
||||
this.LaunchConfiguration.EnabledMods ??= [];
|
||||
if (!this.LaunchConfiguration.EnabledMods.Any(m => m == mod.Name))
|
||||
{
|
||||
this.LaunchConfiguration.EnabledMods.Add(mod.Name);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
this.LaunchConfiguration.EnabledMods?.Remove(mod.Name);
|
||||
}
|
||||
|
||||
this.launchConfigurationService.SaveConfiguration(this.LaunchConfiguration);
|
||||
}
|
||||
else
|
||||
{
|
||||
mod.ModService.IsEnabled = mod.IsEnabled;
|
||||
}
|
||||
|
||||
this.RefreshView();
|
||||
}
|
||||
|
||||
@@ -110,7 +154,7 @@ public sealed class ModsViewModel(
|
||||
if (!await mod.ModService.PerformUninstallation(this.cts?.Token ?? CancellationToken.None))
|
||||
{
|
||||
this.notificationService.NotifyError($"{mod.Name} uninstallation failed", $"{mod.Name} has failed to uninstall. Please check logs for more details.");
|
||||
|
||||
|
||||
}
|
||||
|
||||
mod.Loading = false;
|
||||
@@ -133,7 +177,7 @@ public sealed class ModsViewModel(
|
||||
|
||||
private async ValueTask CheckForUpdates(CancellationToken cancellationToken)
|
||||
{
|
||||
foreach(var mod in this.Mods)
|
||||
foreach (var mod in this.Mods)
|
||||
{
|
||||
mod.Loading = true;
|
||||
}
|
||||
@@ -152,7 +196,7 @@ public sealed class ModsViewModel(
|
||||
mod.CanUpdate = false;
|
||||
}
|
||||
}
|
||||
catch(Exception ex)
|
||||
catch (Exception ex)
|
||||
{
|
||||
mod.CanUpdate = false;
|
||||
var modName = mod.Name ?? "Unknown mod";
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
gap: 1rem;
|
||||
padding: 1rem;
|
||||
overflow-y: auto;
|
||||
margin-top: 40px;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.mod-row {
|
||||
|
||||
@@ -133,6 +133,13 @@ html, body {
|
||||
background: color-mix(in srgb, var(--neutral-fill-strong-hover) 80%, transparent);
|
||||
}
|
||||
|
||||
.view-title {
|
||||
text-align: center;
|
||||
margin-bottom: 2rem;
|
||||
color: var(--neutral-foreground-rest);
|
||||
font-size: var(--font-size-large);
|
||||
}
|
||||
|
||||
.option-view-title {
|
||||
text-align: center;
|
||||
margin-bottom: 2rem;
|
||||
|
||||
@@ -69,6 +69,9 @@ public sealed class WinePrefixManager(
|
||||
/// <inheritdoc />
|
||||
public bool CanUninstall => false;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool CanDisable => false;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IProgressAsyncOperation<bool> PerformInstallation(CancellationToken cancellationToken)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Diagnostics;
|
||||
using Daybreak.Shared.Services.Mods;
|
||||
|
||||
namespace Daybreak.Shared.Models.LaunchConfigurations;
|
||||
|
||||
@@ -8,6 +9,12 @@ public sealed record GuildWarsApplicationLaunchContext : IEquatable<GuildWarsApp
|
||||
public required Process GuildWarsProcess { get; init; }
|
||||
public required uint ProcessId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The list of mod services that were enabled when this process was launched.
|
||||
/// This is used to determine which mods are active for this specific instance.
|
||||
/// </summary>
|
||||
public IReadOnlyList<IModService> EnabledMods { get; init; } = [];
|
||||
|
||||
public GuildWarsApplicationLaunchContext()
|
||||
{
|
||||
}
|
||||
|
||||
@@ -21,4 +21,10 @@ public sealed class LaunchConfiguration
|
||||
|
||||
[JsonPropertyName(nameof(SteamSupport))]
|
||||
public bool? SteamSupport { get; set; } = true;
|
||||
|
||||
[JsonPropertyName(nameof(EnabledMods))]
|
||||
public List<string>? EnabledMods { get; set; }
|
||||
|
||||
[JsonPropertyName(nameof(CustomModLoadoutEnabled))]
|
||||
public bool? CustomModLoadoutEnabled { get; set; }
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ public sealed class LaunchConfigurationWithCredentials : IEquatable<LaunchConfig
|
||||
public string? Arguments { get; set; }
|
||||
public bool SteamSupport { get; set; } = true;
|
||||
public LoginCredentials? Credentials { get; set; }
|
||||
public List<string>? EnabledMods { get; set; }
|
||||
public bool CustomModLoadoutEnabled { get; set; }
|
||||
|
||||
public bool Equals(LaunchConfigurationWithCredentials? other)
|
||||
{
|
||||
@@ -27,7 +29,10 @@ public sealed class LaunchConfigurationWithCredentials : IEquatable<LaunchConfig
|
||||
(this.Name is not null && other.Name is not null && string.Equals(this.Name, other.Name, StringComparison.Ordinal))) &&
|
||||
|
||||
this.Credentials?.Equals(other.Credentials) is true &&
|
||||
this.SteamSupport == other.SteamSupport;
|
||||
this.SteamSupport == other.SteamSupport &&
|
||||
|
||||
this.EnabledMods?.SequenceEqual(other.EnabledMods ?? []) is true &&
|
||||
this.CustomModLoadoutEnabled == other.CustomModLoadoutEnabled;
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
|
||||
@@ -40,6 +40,11 @@ public interface IModService
|
||||
/// </summary>
|
||||
bool CanUninstall { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Dictates if the mod can be disabled by the user. If false, the mod is always enabled when installed.
|
||||
/// </summary>
|
||||
bool CanDisable { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Called by the mod manager to perform installation steps for the mod.
|
||||
/// </summary>
|
||||
|
||||
@@ -48,6 +48,7 @@ internal sealed class GuildwarsScreenPlacer(
|
||||
public bool IsInstalled => true;
|
||||
|
||||
public bool CanUninstall => false;
|
||||
public bool CanDisable => true;
|
||||
|
||||
public IProgressAsyncOperation<bool> PerformUninstallation(CancellationToken cancellationToken)
|
||||
{
|
||||
|
||||
@@ -27,6 +27,7 @@ public sealed class SimpleNotificationMod(
|
||||
public bool IsVisible { get; } = true;
|
||||
public bool CanCustomManage { get; } = false;
|
||||
public bool CanUninstall { get; } = false;
|
||||
public bool CanDisable { get; } = true;
|
||||
|
||||
public IEnumerable<string> GetCustomArguments() => [];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user