Added sort ordering to actions and fixed install process not updating game from live manifest
parent
09df7a8997
commit
abecd9f2f2
|
@ -15,6 +15,7 @@ using ICSharpCode.SharpZipLib.Core;
|
|||
using YamlDotNet.Serialization;
|
||||
using YamlDotNet.Serialization.NamingConventions;
|
||||
using LANCommander.SDK.Models;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace LANCommander.PlaynitePlugin
|
||||
{
|
||||
|
@ -37,6 +38,7 @@ namespace LANCommander.PlaynitePlugin
|
|||
var gameId = Guid.Parse(Game.GameId);
|
||||
|
||||
var game = Plugin.LANCommander.GetGame(gameId);
|
||||
var manifest = Plugin.LANCommander.GetGameManifest(gameId);
|
||||
|
||||
var tempFile = Download(game);
|
||||
|
||||
|
@ -49,7 +51,7 @@ namespace LANCommander.PlaynitePlugin
|
|||
|
||||
PlayniteGame.InstallDirectory = installDirectory;
|
||||
|
||||
File.WriteAllText(Path.Combine(installDirectory, "_manifest.yml"), GetManifest(gameId));
|
||||
WriteManifest(manifest, installDirectory);
|
||||
|
||||
SaveScript(game, installDirectory, ScriptType.Install);
|
||||
SaveScript(game, installDirectory, ScriptType.Uninstall);
|
||||
|
@ -62,9 +64,9 @@ namespace LANCommander.PlaynitePlugin
|
|||
}
|
||||
catch { }
|
||||
|
||||
InvokeOnInstalled(new GameInstalledEventArgs(installInfo));
|
||||
Plugin.UpdateGame(manifest, gameId);
|
||||
|
||||
Plugin.UpdateGamesFromManifest();
|
||||
InvokeOnInstalled(new GameInstalledEventArgs(installInfo));
|
||||
}
|
||||
|
||||
private string Download(LANCommander.SDK.Models.Game game)
|
||||
|
@ -164,17 +166,15 @@ namespace LANCommander.PlaynitePlugin
|
|||
return destination;
|
||||
}
|
||||
|
||||
private string GetManifest(Guid gameId)
|
||||
private void WriteManifest(SDK.GameManifest manifest, string installDirectory)
|
||||
{
|
||||
var manifest = Plugin.LANCommander.GetGameManifest(gameId);
|
||||
|
||||
var serializer = new SerializerBuilder()
|
||||
.WithNamingConvention(PascalCaseNamingConvention.Instance)
|
||||
.Build();
|
||||
|
||||
var yaml = serializer.Serialize(manifest);
|
||||
|
||||
return yaml;
|
||||
File.WriteAllText(Path.Combine(installDirectory, "_manifest.yml"), yaml);
|
||||
}
|
||||
|
||||
private void SaveScript(LANCommander.SDK.Models.Game game, string installationDirectory, ScriptType type)
|
||||
|
|
|
@ -5,6 +5,7 @@ using Playnite.SDK.Models;
|
|||
using Playnite.SDK.Plugins;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.NetworkInformation;
|
||||
|
@ -94,7 +95,7 @@ namespace LANCommander.PlaynitePlugin
|
|||
ReleaseDate = new ReleaseDate(manifest.ReleasedOn),
|
||||
//Version = game.Archives.OrderByDescending(a => a.CreatedOn).FirstOrDefault().Version,
|
||||
Icon = new MetadataFile(iconUri.ToString()),
|
||||
GameActions = game.Actions.Select(a => new PN.SDK.Models.GameAction()
|
||||
GameActions = game.Actions.OrderBy(a => a.SortOrder).Select(a => new PN.SDK.Models.GameAction()
|
||||
{
|
||||
Name = a.Name,
|
||||
Arguments = a.Arguments,
|
||||
|
@ -139,7 +140,25 @@ namespace LANCommander.PlaynitePlugin
|
|||
metadata.Features.Add(new MetadataNameProperty($"Online Multiplayer {manifest.OnlineMultiplayer.GetPlayerCount()}".Trim()));
|
||||
|
||||
gameMetadata.Add(metadata);
|
||||
|
||||
if (existingGame != null)
|
||||
{
|
||||
existingGame.GameActions.Clear();
|
||||
|
||||
foreach (var action in game.Actions)
|
||||
{
|
||||
existingGame.GameActions.Add(new PN.SDK.Models.GameAction()
|
||||
{
|
||||
Name = action.Name,
|
||||
Arguments = action.Arguments,
|
||||
Path = action.Path,
|
||||
WorkingDir = action.WorkingDirectory,
|
||||
IsPlayAction = action.PrimaryAction
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
@ -272,113 +291,83 @@ namespace LANCommander.PlaynitePlugin
|
|||
return window;
|
||||
}
|
||||
|
||||
public void UpdateGamesFromManifest()
|
||||
public void UpdateGame(SDK.GameManifest manifest, Guid gameId)
|
||||
{
|
||||
var games = PlayniteApi.Database.Games;
|
||||
var game = PlayniteApi.Database.Games.First(g => g.GameId == gameId.ToString());
|
||||
|
||||
foreach (var game in games.Where(g => g.PluginId == Id && g.IsInstalled))
|
||||
if (game.GameActions == null)
|
||||
game.GameActions = new ObservableCollection<PN.SDK.Models.GameAction>();
|
||||
else
|
||||
game.GameActions.Clear();
|
||||
|
||||
foreach (var action in manifest.Actions.OrderBy(a => a.SortOrder))
|
||||
{
|
||||
if (!Directory.Exists(game.InstallDirectory))
|
||||
continue;
|
||||
bool isFirstAction = !manifest.Actions.Any(a => a.IsPrimaryAction) && manifest.Actions.First().Name == action.Name;
|
||||
|
||||
var manifestPath = Path.Combine(game.InstallDirectory, "_manifest.yml");
|
||||
|
||||
if (File.Exists(manifestPath))
|
||||
game.GameActions.Add(new PN.SDK.Models.GameAction()
|
||||
{
|
||||
try
|
||||
Name = action.Name,
|
||||
Arguments = action.Arguments,
|
||||
Path = PlayniteApi.ExpandGameVariables(game, action.Path?.Replace('/', Path.DirectorySeparatorChar)),
|
||||
WorkingDir = action.WorkingDirectory?.Replace('/', Path.DirectorySeparatorChar) ?? game.InstallDirectory,
|
||||
IsPlayAction = action.IsPrimaryAction || isFirstAction
|
||||
});
|
||||
}
|
||||
|
||||
#region Features
|
||||
var singlePlayerFeature = PlayniteApi.Database.Features.FirstOrDefault(f => f.Name == "Single Player");
|
||||
|
||||
if (manifest.LanMultiplayer != null)
|
||||
{
|
||||
var multiplayerInfo = manifest.LanMultiplayer;
|
||||
|
||||
string playerCount = multiplayerInfo.MinPlayers == multiplayerInfo.MaxPlayers ? $"({multiplayerInfo.MinPlayers} players)" : $"({multiplayerInfo.MinPlayers} - {multiplayerInfo.MaxPlayers} players)";
|
||||
string featureName = $"LAN Multiplayer {playerCount}";
|
||||
|
||||
if (PlayniteApi.Database.Features.Any(f => f.Name == featureName))
|
||||
{
|
||||
game.Features.Add(PlayniteApi.Database.Features.FirstOrDefault(f => f.Name == featureName));
|
||||
}
|
||||
else
|
||||
{
|
||||
PlayniteApi.Database.Features.Add(new GameFeature()
|
||||
{
|
||||
var manifestContents = File.ReadAllText(manifestPath);
|
||||
var deserializer = new DeserializerBuilder()
|
||||
.IgnoreUnmatchedProperties()
|
||||
.WithNamingConvention(PascalCaseNamingConvention.Instance)
|
||||
.Build();
|
||||
Name = featureName
|
||||
});
|
||||
|
||||
var manifest = deserializer.Deserialize<GameManifest>(manifestContents);
|
||||
|
||||
#region Actions
|
||||
if (game.GameActions == null)
|
||||
game.GameActions = new System.Collections.ObjectModel.ObservableCollection<PN.SDK.Models.GameAction>();
|
||||
|
||||
foreach (var action in manifest.Actions)
|
||||
{
|
||||
bool isFirstAction = !manifest.Actions.Any(a => a.IsPrimaryAction) && manifest.Actions.First().Name == action.Name;
|
||||
|
||||
foreach (var existingAction in game.GameActions)
|
||||
if (action.Name == existingAction.Name)
|
||||
game.GameActions.Remove(existingAction);
|
||||
|
||||
game.GameActions.AddMissing(new PN.SDK.Models.GameAction()
|
||||
{
|
||||
Name = action.Name,
|
||||
Arguments = action.Arguments,
|
||||
Path = PlayniteApi.ExpandGameVariables(game, action.Path?.Replace('/', Path.DirectorySeparatorChar)),
|
||||
WorkingDir = action.WorkingDirectory?.Replace('/', Path.DirectorySeparatorChar) ?? game.InstallDirectory,
|
||||
IsPlayAction = action.IsPrimaryAction || isFirstAction
|
||||
});
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Features
|
||||
var singlePlayerFeature = PlayniteApi.Database.Features.FirstOrDefault(f => f.Name == "Single Player");
|
||||
|
||||
if (manifest.LanMultiplayer != null)
|
||||
{
|
||||
var multiplayerInfo = manifest.LanMultiplayer;
|
||||
|
||||
string playerCount = multiplayerInfo.MinPlayers == multiplayerInfo.MaxPlayers ? $"({multiplayerInfo.MinPlayers} players)" : $"({multiplayerInfo.MinPlayers} - {multiplayerInfo.MaxPlayers} players)";
|
||||
string featureName = $"LAN Multiplayer {playerCount}";
|
||||
|
||||
if (PlayniteApi.Database.Features.Any(f => f.Name == featureName))
|
||||
{
|
||||
game.Features.Add(PlayniteApi.Database.Features.FirstOrDefault(f => f.Name == featureName));
|
||||
}
|
||||
else
|
||||
{
|
||||
PlayniteApi.Database.Features.Add(new PN.SDK.Models.GameFeature()
|
||||
{
|
||||
Name = featureName
|
||||
});
|
||||
|
||||
game.Features.Add(new PN.SDK.Models.GameFeature()
|
||||
{
|
||||
Name = $"LAN Multiplayer {playerCount}"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (manifest.LocalMultiplayer != null)
|
||||
{
|
||||
var multiplayerInfo = manifest.LocalMultiplayer;
|
||||
|
||||
string playerCount = multiplayerInfo.MinPlayers == multiplayerInfo.MaxPlayers ? $"({multiplayerInfo.MinPlayers} players)" : $"({multiplayerInfo.MinPlayers} - {multiplayerInfo.MaxPlayers} players)";
|
||||
|
||||
game.Features.Add(new PN.SDK.Models.GameFeature()
|
||||
{
|
||||
Name = $"Local Multiplayer {playerCount}"
|
||||
});
|
||||
}
|
||||
|
||||
if (manifest.OnlineMultiplayer != null)
|
||||
{
|
||||
var multiplayerInfo = manifest.OnlineMultiplayer;
|
||||
|
||||
string playerCount = multiplayerInfo.MinPlayers == multiplayerInfo.MaxPlayers ? $"({multiplayerInfo.MinPlayers} players)" : $"({multiplayerInfo.MinPlayers} - {multiplayerInfo.MaxPlayers} players)";
|
||||
|
||||
game.Features.Add(new PN.SDK.Models.GameFeature()
|
||||
{
|
||||
Name = $"Online Multiplayer {playerCount}"
|
||||
});
|
||||
}
|
||||
#endregion
|
||||
|
||||
PlayniteApi.Database.Games.Update(game);
|
||||
}
|
||||
catch (Exception ex)
|
||||
game.Features.Add(new GameFeature()
|
||||
{
|
||||
|
||||
}
|
||||
Name = $"LAN Multiplayer {playerCount}"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (manifest.LocalMultiplayer != null)
|
||||
{
|
||||
var multiplayerInfo = manifest.LocalMultiplayer;
|
||||
|
||||
string playerCount = multiplayerInfo.MinPlayers == multiplayerInfo.MaxPlayers ? $"({multiplayerInfo.MinPlayers} players)" : $"({multiplayerInfo.MinPlayers} - {multiplayerInfo.MaxPlayers} players)";
|
||||
|
||||
game.Features.Add(new GameFeature()
|
||||
{
|
||||
Name = $"Local Multiplayer {playerCount}"
|
||||
});
|
||||
}
|
||||
|
||||
if (manifest.OnlineMultiplayer != null)
|
||||
{
|
||||
var multiplayerInfo = manifest.OnlineMultiplayer;
|
||||
|
||||
string playerCount = multiplayerInfo.MinPlayers == multiplayerInfo.MaxPlayers ? $"({multiplayerInfo.MinPlayers} players)" : $"({multiplayerInfo.MinPlayers} - {multiplayerInfo.MaxPlayers} players)";
|
||||
|
||||
game.Features.Add(new GameFeature()
|
||||
{
|
||||
Name = $"Online Multiplayer {playerCount}"
|
||||
});
|
||||
}
|
||||
#endregion
|
||||
|
||||
PlayniteApi.Database.Games.Update(game);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,5 +11,6 @@ namespace LANCommander.SDK.Models
|
|||
public string Path { get; set; }
|
||||
public string WorkingDirectory { get; set; }
|
||||
public bool PrimaryAction { get; set; }
|
||||
public int SortOrder { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ namespace LANCommander.SDK
|
|||
public string Path { get; set; }
|
||||
public string WorkingDirectory { get; set; }
|
||||
public bool IsPrimaryAction { get; set; }
|
||||
public int SortOrder { get; set; }
|
||||
}
|
||||
|
||||
public class MultiplayerInfo
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
@using LANCommander.Data.Models
|
||||
@using LANCommander.Extensions
|
||||
|
||||
@{
|
||||
int i = 0;
|
||||
|
@ -21,7 +22,7 @@
|
|||
<tr><td colspan="5">Actions are used to start the game or launch other executables. It is recommended to have at least one action to launch the game.</td></tr>
|
||||
}
|
||||
|
||||
@foreach (var action in Actions)
|
||||
@foreach (var action in Actions.OrderBy(a => a.SortOrder))
|
||||
{
|
||||
var index = i;
|
||||
|
||||
|
@ -37,8 +38,23 @@
|
|||
<td>
|
||||
<input name="Game.Actions[@i].Id" type="hidden" value="@action.Id" />
|
||||
<input name="Game.Actions[@i].GameId" type="hidden" value="@GameId" />
|
||||
<input name="Game.Actions[@i].SortOrder" type="hidden" value="@i" />
|
||||
|
||||
<div class="btn-list flex-nowrap justify-content-end">
|
||||
<button class="btn btn-ghost-secondary btn-icon" @onclick="() => MoveUp(index)" type="button">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-chevron-up" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<polyline points="6 15 12 9 18 15"></polyline>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<button class="btn btn-ghost-secondary btn-icon" @onclick="() => MoveDown(index)" type="button">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-chevron-down" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<polyline points="6 9 12 15 18 9"></polyline>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<button class="btn btn-ghost-danger btn-icon" @onclick="() => RemoveAction(index)" type="button">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-x" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
|
@ -55,6 +71,7 @@
|
|||
<tr>
|
||||
<td colspan="5">
|
||||
<div class="btn-list flex-nowrap justify-content-end">
|
||||
|
||||
<button class="btn btn-ghost-primary" @onclick="AddAction" type="button">Add Action</button>
|
||||
</div>
|
||||
</td>
|
||||
|
@ -64,9 +81,18 @@
|
|||
</div>
|
||||
|
||||
@code {
|
||||
[Parameter] public ICollection<Data.Models.Action> Actions { get; set; }
|
||||
[Parameter] public List<Data.Models.Action> Actions { get; set; }
|
||||
[Parameter] public Guid GameId { get; set; }
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
Actions = Actions.OrderBy(a => a.SortOrder).ToList();
|
||||
|
||||
FixSortOrders();
|
||||
|
||||
base.OnInitialized();
|
||||
}
|
||||
|
||||
private void AddAction()
|
||||
{
|
||||
if (Actions == null)
|
||||
|
@ -74,12 +100,40 @@
|
|||
|
||||
Actions.Add(new Data.Models.Action()
|
||||
{
|
||||
PrimaryAction = Actions.Count == 0
|
||||
PrimaryAction = Actions.Count == 0,
|
||||
SortOrder = Actions.Count
|
||||
});
|
||||
}
|
||||
|
||||
private void RemoveAction(int index)
|
||||
{
|
||||
Actions.Remove(Actions.ElementAt(index));
|
||||
|
||||
FixSortOrders();
|
||||
}
|
||||
|
||||
private void MoveUp(int index)
|
||||
{
|
||||
if (index == 0)
|
||||
return;
|
||||
|
||||
Actions.Move(Actions.ElementAt(index), index - 1);
|
||||
FixSortOrders();
|
||||
}
|
||||
|
||||
private void MoveDown(int index)
|
||||
{
|
||||
if (index == Actions.Count - 1)
|
||||
return;
|
||||
|
||||
Actions.Move(Actions.ElementAt(index), index + 1);
|
||||
FixSortOrders();
|
||||
}
|
||||
|
||||
private void FixSortOrders() {
|
||||
for (int i = 0; i < Actions.Count; i++)
|
||||
{
|
||||
Actions.ElementAt(i).SortOrder = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ namespace LANCommander.Data.Models
|
|||
public string? Path { get; set; }
|
||||
public string? WorkingDirectory { get; set; }
|
||||
public bool PrimaryAction { get; set; }
|
||||
public int SortOrder { get; set; }
|
||||
|
||||
public Guid GameId { get; set; }
|
||||
[JsonIgnore]
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
namespace LANCommander.Extensions
|
||||
{
|
||||
public static class ListExtensions
|
||||
{
|
||||
public static void Move<T>(this List<T> list, int oldIndex, int newIndex)
|
||||
{
|
||||
var item = list[oldIndex];
|
||||
|
||||
list.RemoveAt(oldIndex);
|
||||
|
||||
if (newIndex > oldIndex) newIndex--;
|
||||
// the actual index could have shifted due to the removal
|
||||
|
||||
list.Insert(newIndex, item);
|
||||
}
|
||||
|
||||
public static void Move<T>(this List<T> list, T item, int newIndex)
|
||||
{
|
||||
if (item != null)
|
||||
{
|
||||
var oldIndex = list.IndexOf(item);
|
||||
|
||||
if (oldIndex > -1)
|
||||
{
|
||||
list.RemoveAt(oldIndex);
|
||||
|
||||
if (newIndex > oldIndex) newIndex--;
|
||||
// the actual index could have shifted due to the removal
|
||||
|
||||
list.Insert(newIndex, item);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,26 @@
|
|||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace LANCommander.Migrations
|
||||
{
|
||||
public partial class AddActionSortOrder : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "SortOrder",
|
||||
table: "Actions",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "SortOrder",
|
||||
table: "Actions");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -120,6 +120,9 @@ namespace LANCommander.Migrations
|
|||
b.Property<bool>("PrimaryAction")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("SortOrder")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<Guid?>("UpdatedById")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
|
|
|
@ -71,7 +71,8 @@ namespace LANCommander.Services
|
|||
Arguments = a.Arguments,
|
||||
Path = a.Path,
|
||||
WorkingDirectory = a.WorkingDirectory,
|
||||
IsPrimaryAction = a.PrimaryAction
|
||||
IsPrimaryAction = a.PrimaryAction,
|
||||
SortOrder = a.SortOrder,
|
||||
}).ToArray();
|
||||
}
|
||||
|
||||
|
|
|
@ -93,7 +93,7 @@
|
|||
<h3 class="card-title">Actions</h3>
|
||||
</div>
|
||||
|
||||
<component type="typeof(ActionEditor)" render-mode="Server" param-Actions="Model.Game.Actions" param-GameId="Model.Game.Id" />
|
||||
<component type="typeof(ActionEditor)" render-mode="Server" param-Actions="Model.Game.Actions.ToList()" param-GameId="Model.Game.Id" />
|
||||
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Multiplayer Modes</h3>
|
||||
|
|
|
@ -96,7 +96,7 @@
|
|||
<h3 class="card-title">Actions</h3>
|
||||
</div>
|
||||
|
||||
<component type="typeof(ActionEditor)" render-mode="Server" param-Actions="Model.Game.Actions" param-GameId="Model.Game.Id" />
|
||||
<component type="typeof(ActionEditor)" render-mode="Server" param-Actions="Model.Game.Actions.ToList()" param-GameId="Model.Game.Id" />
|
||||
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Multiplayer Modes</h3>
|
||||
|
|
Loading…
Reference in New Issue