Merge remote-tracking branch 'origin/master' into compiler-update-streams

This commit is contained in:
Timothy Baldridge 2019-11-16 21:16:51 -07:00
commit d92d05dbf8
23 changed files with 569 additions and 65 deletions

View File

@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Wabbajack.Common
{
public static class EnumExt
{
public static IEnumerable<T> GetValues<T>()
where T : struct, Enum
{
return Enum.GetValues(typeof(T)).Cast<T>();
}
public static string ToDescriptionString<TEnum>(this TEnum val)
where TEnum : struct, IConvertible
{
if (!typeof(TEnum).IsEnum)
{
throw new ArgumentException("T must be an Enum");
}
DescriptionAttribute[] attributes = (DescriptionAttribute[])val.GetType().GetField(val.ToString()).GetCustomAttributes(typeof(DescriptionAttribute), false);
return attributes.Length > 0 ? attributes[0].Description : string.Empty;
}
}
}

View File

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Win32;
@ -9,6 +10,7 @@ namespace Wabbajack.Common
public int GameID;
public string Path;
public string GameName;
public Game? Game;
}
/// <summary>
@ -16,6 +18,11 @@ namespace Wabbajack.Common
/// </summary>
public class GOGHandler
{
private static readonly Lazy<GOGHandler> instance = new Lazy<GOGHandler>(
() => new GOGHandler(true),
isThreadSafe: true);
public static GOGHandler Instance => instance.Value;
private const string GOGRegKey = @"Software\GOG.com\Games";
private const string GOG64RegKey = @"Software\WOW6432Node\GOG.com\Games";
@ -47,6 +54,7 @@ namespace Wabbajack.Common
public void LoadAllGames()
{
Games = new HashSet<GOGGame>();
if (this.GOGKey == null) return;
string[] keys = GOGKey.GetSubKeyNames();
foreach (var key in keys)
{
@ -57,6 +65,9 @@ namespace Wabbajack.Common
Path = GOGKey.OpenSubKey(key)?.GetValue("PATH").ToString()
};
game.Game = GameRegistry.Games.Values
.FirstOrDefault(g => g.GOGIDs.Contains(game.GameID))?.Game;
Games.Add(game);
}
}

View File

@ -1,29 +1,43 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using Alphaleonis.Win32.Filesystem;
using Microsoft.Win32;
namespace Wabbajack.Common
{
public enum Game {
public enum Game
{
//MO2 GAMES
Morrowind,
Oblivion,
[Description("Fallout 3")]
Fallout3,
[Description("Fallout New Vegas")]
FalloutNewVegas,
Skyrim,
[Description("Skyrim Special Edition")]
SkyrimSpecialEdition,
[Description("Fallout 4")]
Fallout4,
[Description("Skyrim VR")]
SkyrimVR,
//VORTEX GAMES
[Description("Darkest Dungeon")]
DarkestDungeon,
[Description("Divinity Original Sin 2")]
DivinityOriginalSin2,
[Description("Divinity Original Sin 2 Definitive Edition")]
DivinityOriginalSin2DE, //definitive edition has its own nexus page but same Steam/GOG ids
Starbound,
[Description("Star Wars: Knights of the Old Republic")]
SWKOTOR,
[Description("Star Wars: Knights of the Old Republic 2")]
SWKOTOR2,
Witcher,
[Description("Witcher 2")]
Witcher2,
[Description("Witcher 3")]
Witcher3
}

View File

@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
@ -6,11 +8,13 @@ using Microsoft.Win32;
namespace Wabbajack.Common
{
[DebuggerDisplay("{Name}")]
public class SteamGame
{
public int AppId;
public string Name;
public string InstallDir;
public Game? Game;
}
/// <summary>
@ -18,6 +22,11 @@ namespace Wabbajack.Common
/// </summary>
public class SteamHandler
{
private static readonly Lazy<SteamHandler> instance = new Lazy<SteamHandler>(
() => new SteamHandler(true),
isThreadSafe: true);
public static SteamHandler Instance => instance.Value;
private const string SteamRegKey = @"Software\Valve\Steam";
/// <summary>
@ -95,6 +104,8 @@ namespace Wabbajack.Common
steamGame.InstallDir = Path.Combine(p, "common", GetVdfValue(l));
});
steamGame.Game = GameRegistry.Games.Values
.FirstOrDefault(g => g.SteamIDs.Contains(steamGame.AppId))?.Game;
games.Add(steamGame);
});
});

View File

@ -98,6 +98,7 @@
<Compile Include="Enums\RunMode.cs" />
<Compile Include="ExtensionManager.cs" />
<Compile Include="Extensions\DictionaryExt.cs" />
<Compile Include="Extensions\EnumExt.cs" />
<Compile Include="Extensions\HashHelper.cs" />
<Compile Include="Extensions\RxExt.cs" />
<Compile Include="Extensions\TaskExt.cs" />

View File

@ -27,29 +27,21 @@ namespace Wabbajack.Lib
public bool IgnoreMissingFiles { get; set; }
public VortexCompiler(string gameName, string gamePath)
public const string StagingMarkerName = "__vortex_staging_folder";
public const string DownloadMarkerName = "__vortex_downloads_folder";
public VortexCompiler(Game game, string gamePath, string vortexFolder, string downloadsFolder, string stagingFolder)
{
ModManager = ModManager.Vortex;
// TODO: only for testing
IgnoreMissingFiles = true;
string[] args = Environment.GetCommandLineArgs();
GamePath = gamePath;
GameName = gameName;
Game = GameRegistry.GetByNexusName(GameName).Game;
//args: wabbajacke.exe gameName gamePath vortexfolder stagingfolder downloadsfolder
VortexFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Vortex");
if (File.Exists(Path.Combine(VortexFolder, gameName, "mods", "__vortex_staging_folder")))
StagingFolder = Path.Combine(VortexFolder, gameName, "mods");
if (File.Exists(Path.Combine(VortexFolder, "downloads", "__vortex_downloads_folder")))
DownloadsFolder = Path.Combine(VortexFolder, "downloads", gameName);
if (args.Length >= 4)
StagingFolder = args[3];
if (args.Length == 5)
DownloadsFolder = args[4];
GameName = GameRegistry.Games[game].NexusName;
this.VortexFolder = vortexFolder;
this.DownloadsFolder = downloadsFolder;
this.StagingFolder = stagingFolder;
ModListOutputFolder = "output_folder";
@ -110,7 +102,7 @@ namespace Wabbajack.Lib
Directory.CreateDirectory(ModListOutputFolder);
IEnumerable<RawSourceFile> vortexStagingFiles = Directory.EnumerateFiles(StagingFolder, "*", SearchOption.AllDirectories)
.Where(p => p.FileExists() && p != "__vortex_staging_folder")
.Where(p => p.FileExists() && p != StagingMarkerName)
.Select(p => new RawSourceFile(VFS.Index.ByRootPath[p])
{Path = p.RelativeTo(StagingFolder)});
@ -423,8 +415,8 @@ namespace Wabbajack.Lib
Game == Game.DarkestDungeon ? new IncludeRegex(this, "project\\.xml$") : null,
new IgnoreStartsWith(this, " __vortex_staging_folder"),
new IgnoreEndsWith(this, "__vortex_staging_folder"),
new IgnoreStartsWith(this, StagingFolder),
new IgnoreEndsWith(this, StagingFolder),
new IgnoreGameFiles(this),
@ -437,5 +429,47 @@ namespace Wabbajack.Lib
new DropAll(this)
};
}
public static string TypicalVortexFolder()
{
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Vortex");
}
public static string RetrieveDownloadLocation(Game game, string vortexFolderPath = null)
{
vortexFolderPath = vortexFolderPath ?? TypicalVortexFolder();
return Path.Combine(vortexFolderPath, "downloads", GameRegistry.Games[game].NexusName);
}
public static string RetrieveStagingLocation(Game game, string vortexFolderPath = null)
{
vortexFolderPath = vortexFolderPath ?? TypicalVortexFolder();
var gameName = GameRegistry.Games[game].NexusName;
return Path.Combine(vortexFolderPath, gameName, "mods");
}
public static IErrorResponse IsValidBaseDownloadsFolder(string path)
{
if (!Directory.Exists(path)) return ErrorResponse.Fail($"Path does not exist: {path}");
if (Directory.EnumerateFiles(path, DownloadMarkerName, SearchOption.TopDirectoryOnly).Any()) return ErrorResponse.Success;
return ErrorResponse.Fail($"Folder must contain {DownloadMarkerName} file");
}
public static IErrorResponse IsValidDownloadsFolder(string path)
{
return IsValidBaseDownloadsFolder(Path.GetDirectoryName(path));
}
public static IErrorResponse IsValidBaseStagingFolder(string path)
{
if (!Directory.Exists(path)) return ErrorResponse.Fail($"Path does not exist: {path}");
if (Directory.EnumerateFiles(path, StagingMarkerName, SearchOption.TopDirectoryOnly).Any()) return ErrorResponse.Success;
return ErrorResponse.Fail($"Folder must contain {StagingMarkerName} file");
}
public static IErrorResponse IsValidStagingFolder(string path)
{
return IsValidBaseStagingFolder(Path.GetDirectoryName(path));
}
}
}

View File

@ -20,7 +20,7 @@ namespace Wabbajack.Test
Consts.TestMode = true;
utils = new TestUtils();
utils.GameName = "Skyrim Special Edition";
utils.Game = Game.SkyrimSpecialEdition;
Utils.LogMessages.Subscribe(f => TestContext.WriteLine(f));

View File

@ -19,7 +19,7 @@ namespace Wabbajack.Test
utils = new TestUtils
{
GameName = "darkestdungeon"
Game = Game.DarkestDungeon
};
Utils.LogMessages.Subscribe(f => TestContext.WriteLine(f));
@ -43,8 +43,12 @@ namespace Wabbajack.Test
protected VortexCompiler MakeCompiler()
{
var vortexCompiler = new VortexCompiler(utils.GameName, utils.GameFolder);
return vortexCompiler;
return new VortexCompiler(
game: utils.Game,
gamePath: utils.GameFolder,
vortexFolder: VortexCompiler.TypicalVortexFolder(),
downloadsFolder: VortexCompiler.RetrieveDownloadLocation(utils.Game),
stagingFolder: VortexCompiler.RetrieveStagingLocation(utils.Game));
}
protected ModList CompileAndInstall()

View File

@ -30,7 +30,7 @@ namespace Wabbajack.Test
Consts.TestMode = true;
utils = new TestUtils();
utils.GameName = "Skyrim Special Edition";
utils.Game = Game.SkyrimSpecialEdition;
Utils.LogMessages.Subscribe(f => TestContext.WriteLine(f));

View File

@ -19,7 +19,6 @@ namespace Wabbajack.Test
{
public class TestUtils : IDisposable
{
public TestUtils()
{
RNG = new Random();
@ -31,7 +30,7 @@ namespace Wabbajack.Test
public string ID { get; }
public Random RNG { get; }
public string GameName { get; set; }
public Game Game { get; set; }
public string TestFolder => Path.Combine(WorkingDirectory, ID);
public string GameFolder => Path.Combine(WorkingDirectory, ID, "game_folder");
@ -42,13 +41,16 @@ namespace Wabbajack.Test
public string InstallFolder => Path.Combine(TestFolder, "installed");
public HashSet<string> Profiles = new HashSet<string>();
public List<string> Mods = new List<string>();
public void Configure()
{
File.WriteAllLines(Path.Combine(MO2Folder, "ModOrganizer.ini"), new []
{
"[General]",
$"gameName={GameName}",
$"gameName={GameRegistry.Games[this.Game].MO2Name}",
$"gamePath={GameFolder.Replace("\\", "\\\\")}",
$"download_directory={DownloadsFolder}"
});
@ -70,7 +72,6 @@ namespace Wabbajack.Test
Profiles.Add(profile_name);
return profile_name;
}
public HashSet<string> Profiles = new HashSet<string>();
public string AddMod(string name = null)
{
@ -82,8 +83,6 @@ namespace Wabbajack.Test
return mod_name;
}
public List<string> Mods = new List<string>();
/// <summary>
/// Adds a file to the given mod with a given path in the mod. Fills it with random data unless
/// random_fill == 0;
@ -129,11 +128,9 @@ namespace Wabbajack.Test
});
}
/// <summary>
/// Returns a random string name (with spaces)
/// </summary>
private char[] _nameChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'+=-_ ".ToCharArray();
public string RandomName()
{
return Guid.NewGuid().ToString();
@ -210,16 +207,12 @@ namespace Wabbajack.Test
var fi_src = new FileInfo(src_file);
var fi_dest = new FileInfo(dest_file);
if (!skip_extensions.Contains(Path.GetExtension(src_file)))
{
Assert.AreEqual(fi_src.Length, fi_dest.Length, $"Differing sizes {rel_file}");
Assert.AreEqual(src_file.FileHash(), dest_file.FileHash(), $"Differing content hash {rel_file}");
}
}
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -58,7 +58,6 @@ namespace Wabbajack
public class ModlistInstallationSettings
{
public string InstallationLocation { get; set; }
public string StagingLocation { get; set; }
public string DownloadLocation { get; set; }
}
@ -88,8 +87,15 @@ namespace Wabbajack
public class VortexCompilationSettings
{
public string StagingLocation { get; set; }
public Game LastCompiledGame { get; set; }
public Dictionary<Game, CompilationModlistSettings> ModlistSettings { get; } = new Dictionary<Game, CompilationModlistSettings>();
public Dictionary<Game, VortexGameSettings> ModlistSettings { get; } = new Dictionary<Game, VortexGameSettings>();
}
public class VortexGameSettings
{
public string GameLocation { get; set; }
public string DownloadLocation { get; set; }
public string StagingLocation { get; set; }
public CompilationModlistSettings ModlistSettings { get; } = new CompilationModlistSettings();
}
}

View File

@ -54,11 +54,18 @@ namespace Wabbajack
case ModManager.MO2:
return new MO2CompilerVM(this);
case ModManager.Vortex:
return new VortexCompilerVM();
return new VortexCompilerVM(this);
default:
return null;
}
})
// Unload old VM
.Pairwise()
.Do(pair =>
{
pair.Previous?.Unload();
})
.Select(p => p.Current)
.ToProperty(this, nameof(this.Compiler));
// Let sub VM determine what settings we're displaying and when
@ -71,7 +78,7 @@ namespace Wabbajack
.DistinctUntilChanged()
.Select(path =>
{
if (string.IsNullOrWhiteSpace(path)) return UIUtils.BitmapImageFromResource("Wabbajack.Resources.Banner_Dark.png");
if (string.IsNullOrWhiteSpace(path)) return UIUtils.BitmapImageFromResource("Wabbajack.Resources.Wabba_Mouth.png");
if (UIUtils.TryGetBitmapImageFromFile(path, out var image))
{
return image;

View File

@ -13,5 +13,6 @@ namespace Wabbajack
IReactiveCommand BeginCommand { get; }
bool Compiling { get; }
ModlistSettingsEditorVM ModlistSettings { get; }
void Unload();
}
}

View File

@ -16,6 +16,8 @@ namespace Wabbajack
{
public class MO2CompilerVM : ViewModel, ISubCompilerVM
{
private readonly MO2CompilationSettings settings;
private readonly ObservableAsPropertyHelper<string> _Mo2Folder;
public string Mo2Folder => _Mo2Folder.Value;
@ -139,19 +141,14 @@ namespace Wabbajack
.ToProperty(this, nameof(this.Compiling));
// Load settings
var settings = parent.MWVM.Settings.Compiler.MO2Compilation;
this.settings = parent.MWVM.Settings.Compiler.MO2Compilation;
this.ModlistLocation.TargetPath = settings.LastCompiledProfileLocation;
if (!string.IsNullOrWhiteSpace(settings.DownloadLocation))
{
this.DownloadLocation.TargetPath = settings.DownloadLocation;
}
parent.MWVM.Settings.SaveSignal
.Subscribe(_ =>
{
settings.DownloadLocation = this.DownloadLocation.TargetPath;
settings.LastCompiledProfileLocation = this.ModlistLocation.TargetPath;
this.ModlistSettings?.Save();
})
.Subscribe(_ => Unload())
.DisposeWith(this.CompositeDisposable);
// Load custom modlist settings per MO2 profile
@ -165,7 +162,10 @@ namespace Wabbajack
{
if (u.State.Failed) return null;
var modlistSettings = settings.ModlistSettings.TryCreate(u.Path);
return new ModlistSettingsEditorVM(modlistSettings, this.MOProfile);
return new ModlistSettingsEditorVM(modlistSettings)
{
ModListName = this.MOProfile
};
})
// Interject and save old while loading new
.Pairwise()
@ -199,8 +199,13 @@ namespace Wabbajack
}
})
.DisposeWith(this.CompositeDisposable);
}
public void Unload()
{
settings.DownloadLocation = this.DownloadLocation.TargetPath;
settings.LastCompiledProfileLocation = this.ModlistLocation.TargetPath;
this.ModlistSettings?.Save();
}
}
}

View File

@ -14,7 +14,6 @@ namespace Wabbajack
public class ModlistSettingsEditorVM : ViewModel
{
private CompilationModlistSettings settings;
private string mo2Profile;
[Reactive]
public string ModListName { get; set; }
@ -32,10 +31,9 @@ namespace Wabbajack
[Reactive]
public string Website { get; set; }
public ModlistSettingsEditorVM(CompilationModlistSettings settings, string mo2Profile)
public ModlistSettingsEditorVM(CompilationModlistSettings settings)
{
this.settings = settings;
this.mo2Profile = mo2Profile;
this.ImagePath = new FilePickerVM()
{
DoExistsCheck = false,
@ -48,19 +46,14 @@ namespace Wabbajack
this.ReadMeText = new FilePickerVM()
{
PathType = FilePickerVM.PathTypeOptions.File,
DoExistsCheck = true,
DoExistsCheck = false,
};
}
public void Init()
{
this.AuthorText = settings.Author;
if (string.IsNullOrWhiteSpace(settings.ModListName))
{
// Set ModlistName initially off just the MO2Profile
this.ModListName = mo2Profile;
}
else
if (!string.IsNullOrWhiteSpace(settings.ModListName))
{
this.ModListName = settings.ModListName;
}

View File

@ -1,19 +1,213 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using DynamicData.Binding;
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
using Wabbajack.Common;
using Wabbajack.Lib;
namespace Wabbajack
{
public class VortexCompilerVM : ViewModel, ISubCompilerVM
{
public IReactiveCommand BeginCommand => throw new NotImplementedException();
private readonly VortexCompilationSettings settings;
public bool Compiling => throw new NotImplementedException();
public IReactiveCommand BeginCommand { get; }
public ModlistSettingsEditorVM ModlistSettings => throw new NotImplementedException();
private readonly ObservableAsPropertyHelper<bool> _Compiling;
public bool Compiling => _Compiling.Value;
private readonly ObservableAsPropertyHelper<ModlistSettingsEditorVM> _ModlistSettings;
public ModlistSettingsEditorVM ModlistSettings => _ModlistSettings.Value;
private static ObservableCollectionExtended<GameVM> gameOptions = new ObservableCollectionExtended<GameVM>(
EnumExt.GetValues<Game>()
.Select(g => new GameVM(g))
.OrderBy(g => g.DisplayName));
public ObservableCollectionExtended<GameVM> GameOptions => gameOptions;
[Reactive]
public GameVM SelectedGame { get; set; } = gameOptions.First(x => x.Game == Game.SkyrimSpecialEdition);
[Reactive]
public FilePickerVM GameLocation { get; set; }
[Reactive]
public FilePickerVM DownloadsLocation { get; set; }
[Reactive]
public FilePickerVM StagingLocation { get; set; }
public ICommand FindGameInSteamCommand { get; }
public ICommand FindGameInGogCommand { get; }
public VortexCompilerVM(CompilerVM parent)
{
this.GameLocation = new FilePickerVM()
{
DoExistsCheck = true,
PathType = FilePickerVM.PathTypeOptions.Folder,
PromptTitle = "Select Game Folder Location"
};
this.DownloadsLocation = new FilePickerVM()
{
DoExistsCheck = true,
PathType = FilePickerVM.PathTypeOptions.Folder,
PromptTitle = "Select Downloads Folder"
};
this.StagingLocation = new FilePickerVM()
{
DoExistsCheck = true,
PathType = FilePickerVM.PathTypeOptions.Folder,
PromptTitle = "Select Staging Folder"
};
// Wire start command
this.BeginCommand = ReactiveCommand.CreateFromTask(
canExecute: Observable.CombineLatest(
this.WhenAny(x => x.GameLocation.InError),
this.WhenAny(x => x.DownloadsLocation.InError),
this.WhenAny(x => x.StagingLocation.InError),
resultSelector: (g, d, s) => !g && !d && !s)
.ObserveOnGuiThread(),
execute: async () =>
{
VortexCompiler compiler;
try
{
compiler = new VortexCompiler(
game: this.SelectedGame.Game,
gamePath: this.GameLocation.TargetPath,
vortexFolder: VortexCompiler.TypicalVortexFolder(),
downloadsFolder: this.DownloadsLocation.TargetPath,
stagingFolder: this.StagingLocation.TargetPath);
}
catch (Exception ex)
{
while (ex.InnerException != null) ex = ex.InnerException;
Utils.Log($"Compiler error: {ex.ExceptionToString()}");
return;
}
await Task.Run(() =>
{
try
{
compiler.Compile();
}
catch (Exception ex)
{
while (ex.InnerException != null) ex = ex.InnerException;
Utils.Log($"Compiler error: {ex.ExceptionToString()}");
}
});
});
this._Compiling = this.BeginCommand.IsExecuting
.ToProperty(this, nameof(this.Compiling));
// Load settings
this.settings = parent.MWVM.Settings.Compiler.VortexCompilation;
this.SelectedGame = gameOptions.First(x => x.Game == settings.LastCompiledGame);
parent.MWVM.Settings.SaveSignal
.Subscribe(_ => Unload())
.DisposeWith(this.CompositeDisposable);
// Load custom game settings when game type changes
this.WhenAny(x => x.SelectedGame)
.Select(game => settings.ModlistSettings.TryCreate(game.Game))
.Pairwise()
.Subscribe(pair =>
{
// Save old
if (pair.Previous != null)
{
pair.Previous.GameLocation = this.GameLocation.TargetPath;
}
// Load new
this.GameLocation.TargetPath = pair.Current?.GameLocation ?? null;
if (string.IsNullOrWhiteSpace(this.GameLocation.TargetPath))
{
this.SetGameToSteamLocation();
}
if (string.IsNullOrWhiteSpace(this.GameLocation.TargetPath))
{
this.SetGameToGogLocation();
}
this.DownloadsLocation.TargetPath = pair.Current?.DownloadLocation ?? null;
if (string.IsNullOrWhiteSpace(this.DownloadsLocation.TargetPath))
{
this.DownloadsLocation.TargetPath = VortexCompiler.RetrieveDownloadLocation(this.SelectedGame.Game);
}
this.StagingLocation.TargetPath = pair.Current?.StagingLocation ?? null;
if (string.IsNullOrWhiteSpace(this.StagingLocation.TargetPath))
{
this.StagingLocation.TargetPath = VortexCompiler.RetrieveStagingLocation(this.SelectedGame.Game);
}
})
.DisposeWith(this.CompositeDisposable);
// Load custom modlist settings when game type changes
this._ModlistSettings = this.WhenAny(x => x.SelectedGame)
.Select(game =>
{
var gameSettings = settings.ModlistSettings.TryCreate(game.Game);
return new ModlistSettingsEditorVM(gameSettings.ModlistSettings);
})
// Interject and save old while loading new
.Pairwise()
.Do(pair =>
{
pair.Previous?.Save();
pair.Current?.Init();
})
.Select(x => x.Current)
// Save to property
.ObserveOnGuiThread()
.ToProperty(this, nameof(this.ModlistSettings));
// Find game commands
this.FindGameInSteamCommand = ReactiveCommand.Create(SetGameToSteamLocation);
this.FindGameInGogCommand = ReactiveCommand.Create(SetGameToGogLocation);
// Add additional criteria to download/staging folders
this.DownloadsLocation.AdditionalError = this.WhenAny(x => x.DownloadsLocation.TargetPath)
.Select(path =>
{
if (path == null) return ErrorResponse.Success;
return VortexCompiler.IsValidDownloadsFolder(path);
});
this.StagingLocation.AdditionalError = this.WhenAny(x => x.StagingLocation.TargetPath)
.Select(path =>
{
if (path == null) return ErrorResponse.Success;
return VortexCompiler.IsValidBaseStagingFolder(path);
});
}
public void Unload()
{
settings.LastCompiledGame = this.SelectedGame.Game;
this.ModlistSettings?.Save();
}
private void SetGameToSteamLocation()
{
var steamGame = SteamHandler.Instance.Games.FirstOrDefault(g => g.Game.HasValue && g.Game == this.SelectedGame.Game);
this.GameLocation.TargetPath = steamGame?.InstallDir;
}
private void SetGameToGogLocation()
{
var gogGame = GOGHandler.Instance.Games.FirstOrDefault(g => g.Game.HasValue && g.Game == this.SelectedGame.Game);
this.GameLocation.TargetPath = gogGame?.Path;
}
}
}
}

View File

@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Wabbajack.Common;
namespace Wabbajack
{
public class GameVM
{
public Game Game { get; }
public string DisplayName { get; }
public GameVM(Game game)
{
this.Game = game;
this.DisplayName = game.ToDescriptionString();
if (string.IsNullOrWhiteSpace(this.DisplayName))
{
this.DisplayName = game.ToString();
}
}
}
}

View File

@ -198,6 +198,9 @@
<DataTemplate DataType="{x:Type local:MO2CompilerVM}">
<local:MO2CompilerConfigView />
</DataTemplate>
<DataTemplate DataType="{x:Type local:VortexCompilerVM}">
<local:VortexCompilerConfigView />
</DataTemplate>
</ContentPresenter.Resources>
</ContentPresenter>
<local:BeginButton

View File

@ -0,0 +1,132 @@
<UserControl
x:Class="Wabbajack.VortexCompilerConfigView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Wabbajack"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DataContext="{d:DesignInstance local:VortexCompilerVM}"
d:DesignHeight="450"
d:DesignWidth="800"
mc:Ignorable="d">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="20" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="30" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="20" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="28" />
<RowDefinition Height="40" />
<RowDefinition Height="40" />
<RowDefinition Height="28" />
</Grid.RowDefinitions>
<TextBlock
Grid.Row="1"
Grid.Column="0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
FontSize="14"
Text="Game"
TextAlignment="Center"
ToolTip="The game you wish to target" />
<ComboBox
Grid.Row="1"
Grid.Column="2"
Height="30"
VerticalAlignment="Center"
VerticalContentAlignment="Center"
FontSize="14"
ItemsSource="{Binding GameOptions}"
SelectedValue="{Binding SelectedGame}"
ToolTip="The game you wish to target">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding DisplayName}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock
Grid.Row="2"
Grid.Column="0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
FontSize="14"
Text="Game Folder"
TextAlignment="Center"
ToolTip="The install folder for the game" />
<local:FilePicker
Grid.Row="2"
Grid.Column="2"
Height="30"
VerticalAlignment="Center"
DataContext="{Binding GameLocation}"
FontSize="14"
ToolTip="The install folder for the game" />
<Grid
Grid.Row="3"
Grid.Column="2"
Height="28"
HorizontalAlignment="Left"
VerticalAlignment="Top">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Button
Grid.Column="0"
Margin="0,0,5,0"
Background="Transparent"
Command="{Binding FindGameInSteamCommand}"
Style="{StaticResource CircleButtonStyle}"
ToolTip="Attempt to locate the game in Steam">
<Image Margin="1" Source="../../Resources/Icons/steam.png" />
</Button>
<Button
Grid.Column="1"
Background="Transparent"
Command="{Binding FindGameInGogCommand}"
Style="{StaticResource CircleButtonStyle}"
ToolTip="Attempt to locate game in GoG">
<Image Margin="1" Source="../../Resources/Icons/gog.png" />
</Button>
</Grid>
<TextBlock
Grid.Row="1"
Grid.Column="4"
HorizontalAlignment="Right"
VerticalAlignment="Center"
FontSize="14"
Text="Download Location"
TextAlignment="Center"
ToolTip="The folder to downloads your mods" />
<local:FilePicker
Grid.Row="1"
Grid.Column="6"
Height="30"
VerticalAlignment="Center"
DataContext="{Binding DownloadsLocation}"
FontSize="14"
ToolTip="The folder to downloads your mods" />
<TextBlock
Grid.Row="2"
Grid.Column="4"
HorizontalAlignment="Right"
VerticalAlignment="Center"
FontSize="14"
Text="Staging Location"
TextAlignment="Center" />
<local:FilePicker
Grid.Row="2"
Grid.Column="6"
Height="30"
VerticalAlignment="Center"
DataContext="{Binding StagingLocation}"
FontSize="14" />
</Grid>
</UserControl>

View File

@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace Wabbajack
{
/// <summary>
/// Interaction logic for VortexCompilerConfigView.xaml
/// </summary>
public partial class VortexCompilerConfigView : UserControl
{
public VortexCompilerConfigView()
{
InitializeComponent();
}
}
}

View File

@ -162,6 +162,7 @@
<SubType>Designer</SubType>
</ApplicationDefinition>
<Compile Include="Converters\EqualsToBoolConverter.cs" />
<Compile Include="View Models\GameVM.cs" />
<Compile Include="Views\Compilers\MO2CompilerConfigView.xaml.cs">
<DependentUpon>MO2CompilerConfigView.xaml</DependentUpon>
</Compile>
@ -226,6 +227,9 @@
<DependentUpon>TextViewer.xaml</DependentUpon>
</Compile>
<Compile Include="Views\UserControlRx.cs" />
<Compile Include="Views\Compilers\VortexCompilerConfigView.xaml.cs">
<DependentUpon>VortexCompilerConfigView.xaml</DependentUpon>
</Compile>
<Page Include="Views\Compilers\MO2CompilerConfigView.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
@ -300,6 +304,10 @@
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="Views\Compilers\VortexCompilerConfigView.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Compile Include="Properties\AssemblyInfo.cs">
@ -465,5 +473,9 @@
<Resource Include="Resources\MO2Button.png" />
<Resource Include="Resources\VortexButton.png" />
</ItemGroup>
<ItemGroup>
<Resource Include="Resources\Icons\gog.png" />
<Resource Include="Resources\Icons\steam.png" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>