Files
OpenRA/OpenRA.Mods.Common/Widgets/Logic/ReplayBrowserLogic.cs
2022-09-08 23:45:11 +02:00

874 lines
25 KiB
C#

#region Copyright & License Information
/*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using OpenRA.FileFormats;
using OpenRA.Network;
using OpenRA.Traits;
using OpenRA.Widgets;
namespace OpenRA.Mods.Common.Widgets.Logic
{
public class ReplayBrowserLogic : ChromeLogic
{
[TranslationReference("time")]
static readonly string Duration = "duration";
[TranslationReference]
static readonly string Singleplayer = "singleplayer";
[TranslationReference]
static readonly string Multiplayer = "multiplayer";
[TranslationReference]
static readonly string Today = "today";
[TranslationReference]
static readonly string LastWeek = "last-week";
[TranslationReference]
static readonly string LastFortnight = "last-fortnight";
[TranslationReference]
static readonly string LastMonth = "last-month";
[TranslationReference]
static readonly string ReplayDurationVeryShort = "replay-duration-very-short";
[TranslationReference]
static readonly string ReplayDurationShort = "replay-duration-short";
[TranslationReference]
static readonly string ReplayDurationMedium = "replay-duration-medium";
[TranslationReference]
static readonly string ReplayDurationLong = "replay-duration-long";
[TranslationReference]
static readonly string RenameReplayTitle = "rename-replay-title";
[TranslationReference]
static readonly string RenameReplayPrompt = "rename-replay-prompt";
[TranslationReference]
static readonly string RenameReplayAccept = "rename-replay-accept";
[TranslationReference]
static readonly string DeleteReplayTitle = "delete-replay-title";
[TranslationReference("replay")]
static readonly string DeleteReplayPrompt = "delete-replay-prompt";
[TranslationReference]
static readonly string DeleteReplayAccept = "delete-replay-accept";
[TranslationReference]
static readonly string DeleteAllReplaysTitle = "delete-all-replays-title";
[TranslationReference("count")]
static readonly string DeleteAllReplaysPrompt = "delete-all-replays-prompt";
[TranslationReference]
static readonly string DeleteAllReplaysAccept = "delete-all-replays-accept";
[TranslationReference("file")]
static readonly string ReplayDeletionFailed = "replay-deletion-failed";
[TranslationReference]
static readonly string Players = "players";
[TranslationReference("team")]
static readonly string TeamNumber = "team-number";
[TranslationReference]
static readonly string NoTeam = "no-team";
[TranslationReference]
static readonly string Victory = "victory";
[TranslationReference]
static readonly string Defeat = "defeat";
static Filter filter = new Filter();
readonly Widget panel;
readonly ScrollPanelWidget replayList, playerList;
readonly ScrollItemWidget playerTemplate, playerHeader;
readonly List<ReplayMetadata> replays = new List<ReplayMetadata>();
readonly Dictionary<ReplayMetadata, ReplayState> replayState = new Dictionary<ReplayMetadata, ReplayState>();
readonly Action onStart;
readonly ModData modData;
readonly WebServices services;
MapPreview map;
ReplayMetadata selectedReplay;
volatile bool cancelLoadingReplays;
[ObjectCreator.UseCtor]
public ReplayBrowserLogic(Widget widget, ModData modData, Action onExit, Action onStart)
{
map = MapCache.UnknownMap;
panel = widget;
services = modData.Manifest.Get<WebServices>();
this.modData = modData;
this.onStart = onStart;
Game.BeforeGameStart += OnGameStart;
playerList = panel.Get<ScrollPanelWidget>("PLAYER_LIST");
playerHeader = playerList.Get<ScrollItemWidget>("HEADER");
playerTemplate = playerList.Get<ScrollItemWidget>("TEMPLATE");
playerList.RemoveChildren();
panel.Get<ButtonWidget>("CANCEL_BUTTON").OnClick = () => { cancelLoadingReplays = true; Ui.CloseWindow(); onExit(); };
replayList = panel.Get<ScrollPanelWidget>("REPLAY_LIST");
var template = panel.Get<ScrollItemWidget>("REPLAY_TEMPLATE");
var mod = modData.Manifest;
var dir = Path.Combine(Platform.SupportDir, "Replays", mod.Id, mod.Metadata.Version);
if (Directory.Exists(dir))
ThreadPool.QueueUserWorkItem(_ => LoadReplays(dir, template));
var watch = panel.Get<ButtonWidget>("WATCH_BUTTON");
watch.IsDisabled = () => selectedReplay == null || map.Status != MapStatus.Available;
watch.OnClick = () => { WatchReplay(); };
var mapPreviewRoot = panel.Get("MAP_PREVIEW_ROOT");
mapPreviewRoot.IsVisible = () => selectedReplay != null;
panel.Get("REPLAY_INFO").IsVisible = () => selectedReplay != null;
var spawnOccupants = new CachedTransform<ReplayMetadata, Dictionary<int, SpawnOccupant>>(r =>
{
// Avoid using .ToDictionary to improve robustness against replays defining duplicate spawn assignments
var occupants = new Dictionary<int, SpawnOccupant>();
foreach (var p in r.GameInfo.Players)
if (p.SpawnPoint != 0)
occupants[p.SpawnPoint] = new SpawnOccupant(p);
return occupants;
});
var noSpawns = new HashSet<int>();
var disabledSpawnPoints = new CachedTransform<ReplayMetadata, HashSet<int>>(r => r.GameInfo.DisabledSpawnPoints ?? noSpawns);
Ui.LoadWidget("MAP_PREVIEW", mapPreviewRoot, new WidgetArgs
{
{ "orderManager", null },
{ "getMap", (Func<(MapPreview, Session.MapStatus)>)(() => (map, Session.MapStatus.Playable)) },
{ "onMouseDown", (Action<MapPreviewWidget, MapPreview, MouseInput>)((preview, mapPreview, mi) => { }) },
{ "getSpawnOccupants", (Func<Dictionary<int, SpawnOccupant>>)(() => spawnOccupants.Update(selectedReplay)) },
{ "getDisabledSpawnPoints", (Func<HashSet<int>>)(() => disabledSpawnPoints.Update(selectedReplay)) },
{ "showUnoccupiedSpawnpoints", false },
});
var replayDuration = new CachedTransform<ReplayMetadata, string>(r =>
modData.Translation.GetString(Duration, Translation.Arguments("time", WidgetUtils.FormatTimeSeconds((int)selectedReplay.GameInfo.Duration.TotalSeconds))));
panel.Get<LabelWidget>("DURATION").GetText = () => replayDuration.Update(selectedReplay);
SetupFilters();
SetupManagement();
}
void LoadReplays(string dir, ScrollItemWidget template)
{
using (new Support.PerfTimer("Load replays"))
{
var loadedReplays = new ConcurrentBag<ReplayMetadata>();
Parallel.ForEach(Directory.GetFiles(dir, "*.orarep", SearchOption.AllDirectories), (fileName, pls) =>
{
if (cancelLoadingReplays)
{
pls.Stop();
return;
}
var replay = ReplayMetadata.Read(fileName);
if (replay != null)
loadedReplays.Add(replay);
});
if (cancelLoadingReplays)
return;
var sortedReplays = loadedReplays.OrderByDescending(replay => replay.GameInfo.StartTimeUtc).ToList();
Game.RunAfterTick(() =>
{
replayList.RemoveChildren();
foreach (var replay in sortedReplays)
AddReplay(replay, template);
SetupReplayDependentFilters();
ApplyFilter();
});
}
}
void SetupFilters()
{
// Game type
{
var ddb = panel.GetOrNull<DropDownButtonWidget>("FLT_GAMETYPE_DROPDOWNBUTTON");
if (ddb != null)
{
// Using list to maintain the order
var options = new List<(GameType GameType, string Text)>
{
(GameType.Any, ddb.GetText()),
(GameType.Singleplayer, modData.Translation.GetString(Singleplayer)),
(GameType.Multiplayer, modData.Translation.GetString(Multiplayer))
};
var lookup = options.ToDictionary(kvp => kvp.GameType, kvp => kvp.Text);
ddb.GetText = () => lookup[filter.Type];
ddb.OnMouseDown = _ =>
{
Func<(GameType GameType, string Text), ScrollItemWidget, ScrollItemWidget> setupItem = (option, tpl) =>
{
var item = ScrollItemWidget.Setup(
tpl,
() => filter.Type == option.GameType,
() => { filter.Type = option.GameType; ApplyFilter(); });
item.Get<LabelWidget>("LABEL").GetText = () => option.Text;
return item;
};
ddb.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", 330, options, setupItem);
};
}
}
// Date type
{
var ddb = panel.GetOrNull<DropDownButtonWidget>("FLT_DATE_DROPDOWNBUTTON");
if (ddb != null)
{
// Using list to maintain the order
var options = new List<(DateType DateType, string Text)>
{
(DateType.Any, ddb.GetText()),
(DateType.Today, modData.Translation.GetString(Today)),
(DateType.LastWeek, modData.Translation.GetString(LastWeek)),
(DateType.LastFortnight, modData.Translation.GetString(LastFortnight)),
(DateType.LastMonth, modData.Translation.GetString(LastMonth))
};
var lookup = options.ToDictionary(kvp => kvp.DateType, kvp => kvp.Text);
ddb.GetText = () => lookup[filter.Date];
ddb.OnMouseDown = _ =>
{
Func<(DateType DateType, string Text), ScrollItemWidget, ScrollItemWidget> setupItem = (option, tpl) =>
{
var item = ScrollItemWidget.Setup(
tpl,
() => filter.Date == option.DateType,
() => { filter.Date = option.DateType; ApplyFilter(); });
item.Get<LabelWidget>("LABEL").GetText = () => option.Text;
return item;
};
ddb.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", 330, options, setupItem);
};
}
}
// Duration
{
var ddb = panel.GetOrNull<DropDownButtonWidget>("FLT_DURATION_DROPDOWNBUTTON");
if (ddb != null)
{
// Using list to maintain the order
var options = new List<(DurationType DurationType, string Text)>
{
(DurationType.Any, ddb.GetText()),
(DurationType.VeryShort, modData.Translation.GetString(ReplayDurationVeryShort)),
(DurationType.Short, modData.Translation.GetString(ReplayDurationShort)),
(DurationType.Medium, modData.Translation.GetString(ReplayDurationMedium)),
(DurationType.Long, modData.Translation.GetString(ReplayDurationLong))
};
var lookup = options.ToDictionary(kvp => kvp.DurationType, kvp => kvp.Text);
ddb.GetText = () => lookup[filter.Duration];
ddb.OnMouseDown = _ =>
{
Func<(DurationType DurationType, string Text), ScrollItemWidget, ScrollItemWidget> setupItem = (option, tpl) =>
{
var item = ScrollItemWidget.Setup(
tpl,
() => filter.Duration == option.DurationType,
() => { filter.Duration = option.DurationType; ApplyFilter(); });
item.Get<LabelWidget>("LABEL").GetText = () => option.Text;
return item;
};
ddb.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", 330, options, setupItem);
};
}
}
// Outcome (depends on Player)
{
var ddb = panel.GetOrNull<DropDownButtonWidget>("FLT_OUTCOME_DROPDOWNBUTTON");
if (ddb != null)
{
ddb.IsDisabled = () => string.IsNullOrEmpty(filter.PlayerName);
// Using list to maintain the order
var options = new List<(WinState WinState, string Text)>
{
(WinState.Undefined, ddb.GetText()),
(WinState.Lost, modData.Translation.GetString(Defeat)),
(WinState.Won, modData.Translation.GetString(Victory))
};
var lookup = options.ToDictionary(kvp => kvp.WinState, kvp => kvp.Text);
ddb.GetText = () => lookup[filter.Outcome];
ddb.OnMouseDown = _ =>
{
Func<(WinState WinState, string Text), ScrollItemWidget, ScrollItemWidget> setupItem = (option, tpl) =>
{
var item = ScrollItemWidget.Setup(
tpl,
() => filter.Outcome == option.WinState,
() => { filter.Outcome = option.WinState; ApplyFilter(); });
item.Get<LabelWidget>("LABEL").GetText = () => option.Text;
return item;
};
ddb.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", 330, options, setupItem);
};
}
}
// Reset button
{
var button = panel.Get<ButtonWidget>("FLT_RESET_BUTTON");
button.IsDisabled = () => filter.IsEmpty;
button.OnClick = () => { filter = new Filter(); ApplyFilter(); };
}
}
void SetupReplayDependentFilters()
{
// Map
{
var ddb = panel.GetOrNull<DropDownButtonWidget>("FLT_MAPNAME_DROPDOWNBUTTON");
if (ddb != null)
{
var options = replays.Select(r => r.GameInfo.MapTitle).Distinct(StringComparer.OrdinalIgnoreCase).ToList();
options.Sort(StringComparer.OrdinalIgnoreCase);
options.Insert(0, null); // no filter
var anyText = ddb.GetText();
ddb.GetText = () => string.IsNullOrEmpty(filter.MapName) ? anyText : filter.MapName;
ddb.OnMouseDown = _ =>
{
Func<string, ScrollItemWidget, ScrollItemWidget> setupItem = (option, tpl) =>
{
var item = ScrollItemWidget.Setup(
tpl,
() => string.Compare(filter.MapName, option, true) == 0,
() => { filter.MapName = option; ApplyFilter(); });
item.Get<LabelWidget>("LABEL").GetText = () => option ?? anyText;
return item;
};
ddb.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", 330, options, setupItem);
};
}
}
// Players
{
var ddb = panel.GetOrNull<DropDownButtonWidget>("FLT_PLAYER_DROPDOWNBUTTON");
if (ddb != null)
{
var options = replays.SelectMany(r => r.GameInfo.Players.Select(p => p.Name)).Distinct(StringComparer.OrdinalIgnoreCase).ToList();
options.Sort(StringComparer.OrdinalIgnoreCase);
options.Insert(0, null); // no filter
var anyText = ddb.GetText();
ddb.GetText = () => string.IsNullOrEmpty(filter.PlayerName) ? anyText : filter.PlayerName;
ddb.OnMouseDown = _ =>
{
Func<string, ScrollItemWidget, ScrollItemWidget> setupItem = (option, tpl) =>
{
var item = ScrollItemWidget.Setup(
tpl,
() => string.Compare(filter.PlayerName, option, true) == 0,
() => { filter.PlayerName = option; ApplyFilter(); });
item.Get<LabelWidget>("LABEL").GetText = () => option ?? anyText;
return item;
};
ddb.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", 330, options, setupItem);
};
}
}
// Faction (depends on Player)
{
var ddb = panel.GetOrNull<DropDownButtonWidget>("FLT_FACTION_DROPDOWNBUTTON");
if (ddb != null)
{
ddb.IsDisabled = () => string.IsNullOrEmpty(filter.PlayerName);
var options = replays
.SelectMany(r => r.GameInfo.Players.Select(p => p.FactionName).Where(n => !string.IsNullOrEmpty(n)))
.Distinct(StringComparer.OrdinalIgnoreCase).ToList();
options.Sort(StringComparer.OrdinalIgnoreCase);
options.Insert(0, null); // no filter
var anyText = ddb.GetText();
ddb.GetText = () => string.IsNullOrEmpty(filter.Faction) ? anyText : filter.Faction;
ddb.OnMouseDown = _ =>
{
Func<string, ScrollItemWidget, ScrollItemWidget> setupItem = (option, tpl) =>
{
var item = ScrollItemWidget.Setup(
tpl,
() => string.Compare(filter.Faction, option, true) == 0,
() => { filter.Faction = option; ApplyFilter(); });
item.Get<LabelWidget>("LABEL").GetText = () => option ?? anyText;
return item;
};
ddb.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", 330, options, setupItem);
};
}
}
}
void SetupManagement()
{
var renameButton = panel.Get<ButtonWidget>("MNG_RENSEL_BUTTON");
renameButton.IsDisabled = () => selectedReplay == null;
renameButton.OnClick = () =>
{
var r = selectedReplay;
var initialName = Path.GetFileNameWithoutExtension(r.FilePath);
var directoryName = Path.GetDirectoryName(r.FilePath);
var invalidChars = Path.GetInvalidFileNameChars();
ConfirmationDialogs.TextInputPrompt(modData,
RenameReplayTitle,
RenameReplayPrompt,
initialName,
onAccept: newName => RenameReplay(r, newName),
onCancel: null,
acceptText: RenameReplayAccept,
cancelText: null,
inputValidator: newName =>
{
if (newName == initialName)
return false;
if (string.IsNullOrWhiteSpace(newName))
return false;
if (newName.IndexOfAny(invalidChars) >= 0)
return false;
if (File.Exists(Path.Combine(directoryName, newName)))
return false;
return true;
});
};
Action<ReplayMetadata, Action> onDeleteReplay = (r, after) =>
{
ConfirmationDialogs.ButtonPrompt(modData,
title: DeleteReplayTitle,
text: DeleteReplayPrompt,
textArguments: Translation.Arguments("replay", Path.GetFileNameWithoutExtension(r.FilePath)),
onConfirm: () =>
{
DeleteReplay(r);
after?.Invoke();
},
confirmText: DeleteReplayAccept,
onCancel: () => { });
};
var deleteButton = panel.Get<ButtonWidget>("MNG_DELSEL_BUTTON");
deleteButton.IsDisabled = () => selectedReplay == null;
deleteButton.OnClick = () =>
{
onDeleteReplay(selectedReplay, () =>
{
if (selectedReplay == null)
SelectFirstVisibleReplay();
});
};
var deleteAllButton = panel.Get<ButtonWidget>("MNG_DELALL_BUTTON");
deleteAllButton.IsDisabled = () => !replayState.Any(kvp => kvp.Value.Visible);
deleteAllButton.OnClick = () =>
{
var list = replayState.Where(kvp => kvp.Value.Visible).Select(kvp => kvp.Key).ToList();
if (list.Count == 0)
return;
if (list.Count == 1)
{
onDeleteReplay(list[0], () => { if (selectedReplay == null) SelectFirstVisibleReplay(); });
return;
}
ConfirmationDialogs.ButtonPrompt(modData,
title: DeleteAllReplaysTitle,
text: DeleteAllReplaysPrompt,
textArguments: Translation.Arguments("count", list.Count),
onConfirm: () =>
{
foreach (var replayMetadata in list)
DeleteReplay(replayMetadata);
if (selectedReplay == null)
SelectFirstVisibleReplay();
},
confirmText: DeleteAllReplaysAccept,
onCancel: () => { });
};
}
void RenameReplay(ReplayMetadata replay, string newFilenameWithoutExtension)
{
try
{
var item = replayState[replay].Item;
replay.RenameFile(newFilenameWithoutExtension);
item.Text = newFilenameWithoutExtension;
var label = item.Get<LabelWithTooltipWidget>("TITLE");
WidgetUtils.TruncateLabelToTooltip(label, item.Text);
}
catch (Exception ex)
{
Log.Write("debug", ex.ToString());
return;
}
}
void DeleteReplay(ReplayMetadata replay)
{
try
{
File.Delete(replay.FilePath);
}
catch (Exception ex)
{
TextNotificationsManager.Debug(modData.Translation.GetString(ReplayDeletionFailed, Translation.Arguments("file", replay.FilePath)));
Log.Write("debug", ex.ToString());
return;
}
if (replay == selectedReplay)
SelectReplay(null);
replayList.RemoveChild(replayState[replay].Item);
replays.Remove(replay);
replayState.Remove(replay);
}
static bool EvaluateReplayVisibility(ReplayMetadata replay)
{
// Game type
if ((filter.Type == GameType.Multiplayer && replay.GameInfo.IsSinglePlayer) || (filter.Type == GameType.Singleplayer && !replay.GameInfo.IsSinglePlayer))
return false;
// Date type
if (filter.Date != DateType.Any)
{
TimeSpan t;
switch (filter.Date)
{
case DateType.Today:
t = TimeSpan.FromDays(1d);
break;
case DateType.LastWeek:
t = TimeSpan.FromDays(7d);
break;
case DateType.LastFortnight:
t = TimeSpan.FromDays(14d);
break;
case DateType.LastMonth:
default:
t = TimeSpan.FromDays(30d);
break;
}
if (replay.GameInfo.StartTimeUtc < DateTime.UtcNow - t)
return false;
}
// Duration
if (filter.Duration != DurationType.Any)
{
var minutes = replay.GameInfo.Duration.TotalMinutes;
switch (filter.Duration)
{
case DurationType.VeryShort:
if (minutes >= 5)
return false;
break;
case DurationType.Short:
if (minutes < 5 || minutes >= 20)
return false;
break;
case DurationType.Medium:
if (minutes < 20 || minutes >= 60)
return false;
break;
case DurationType.Long:
if (minutes < 60)
return false;
break;
}
}
// Map
if (!string.IsNullOrEmpty(filter.MapName) && string.Compare(filter.MapName, replay.GameInfo.MapTitle, true) != 0)
return false;
// Player
if (!string.IsNullOrEmpty(filter.PlayerName))
{
var player = replay.GameInfo.Players.FirstOrDefault(p => string.Compare(filter.PlayerName, p.Name, true) == 0);
if (player == null)
return false;
// Outcome
if (filter.Outcome != WinState.Undefined && filter.Outcome != player.Outcome)
return false;
// Faction
if (!string.IsNullOrEmpty(filter.Faction) && string.Compare(filter.Faction, player.FactionName, true) != 0)
return false;
}
return true;
}
void ApplyFilter()
{
foreach (var replay in replays)
replayState[replay].Visible = EvaluateReplayVisibility(replay);
if (selectedReplay == null || replayState[selectedReplay].Visible == false)
SelectFirstVisibleReplay();
replayList.Layout.AdjustChildren();
replayList.ScrollToSelectedItem();
}
void SelectFirstVisibleReplay()
{
SelectReplay(replays.FirstOrDefault(r => replayState[r].Visible));
}
void SelectReplay(ReplayMetadata replay)
{
selectedReplay = replay;
map = selectedReplay != null ? selectedReplay.GameInfo.MapPreview : MapCache.UnknownMap;
if (replay == null)
return;
try
{
if (map.Status == MapStatus.Unavailable && Game.Settings.Game.AllowDownloading)
modData.MapCache.QueryRemoteMapDetails(services.MapRepository, new[] { map.Uid });
var players = replay.GameInfo.Players
.GroupBy(p => p.Team)
.OrderBy(g => g.Key);
var teams = new Dictionary<string, IEnumerable<GameInformation.Player>>();
var noTeams = players.Count() == 1;
foreach (var p in players)
{
var label = noTeams ? modData.Translation.GetString(Players) : p.Key > 0
? modData.Translation.GetString(TeamNumber, Translation.Arguments("team", p.Key))
: modData.Translation.GetString(NoTeam);
teams.Add(label, p);
}
playerList.RemoveChildren();
foreach (var kv in teams)
{
var group = kv.Key;
if (group.Length > 0)
{
var header = ScrollItemWidget.Setup(playerHeader, () => true, () => { });
header.Get<LabelWidget>("LABEL").GetText = () => group;
playerList.AddChild(header);
}
foreach (var option in kv.Value)
{
var o = option;
var color = o.Color;
var item = ScrollItemWidget.Setup(playerTemplate, () => false, () => { });
var label = item.Get<LabelWidget>("LABEL");
var font = Game.Renderer.Fonts[label.Font];
var name = WidgetUtils.TruncateText(o.Name, label.Bounds.Width, font);
label.GetText = () => name;
label.GetColor = () => color;
var flag = item.Get<ImageWidget>("FLAG");
flag.GetImageCollection = () => "flags";
var factionInfo = modData.DefaultRules.Actors[SystemActors.World].TraitInfos<FactionInfo>();
flag.GetImageName = () => (factionInfo != null && factionInfo.Any(f => f.InternalName == o.FactionId)) ? o.FactionId : "Random";
playerList.AddChild(item);
}
}
}
catch (Exception e)
{
Log.Write("debug", $"Exception while parsing replay: {replay}");
Log.Write("debug", e);
SelectReplay(null);
}
}
void WatchReplay()
{
if (selectedReplay != null && ReplayUtils.PromptConfirmReplayCompatibility(selectedReplay, modData))
{
cancelLoadingReplays = true;
DiscordService.UpdateStatus(DiscordState.WatchingReplay);
Game.JoinReplay(selectedReplay.FilePath);
}
}
void AddReplay(ReplayMetadata replay, ScrollItemWidget template)
{
replays.Add(replay);
var item = ScrollItemWidget.Setup(template,
() => selectedReplay == replay,
() => SelectReplay(replay),
() => WatchReplay());
replayState[replay] = new ReplayState
{
Item = item,
Visible = true
};
item.Text = Path.GetFileNameWithoutExtension(replay.FilePath);
var label = item.Get<LabelWithTooltipWidget>("TITLE");
WidgetUtils.TruncateLabelToTooltip(label, item.Text);
item.IsVisible = () => replayState[replay].Visible;
replayList.AddChild(item);
}
void OnGameStart()
{
Ui.CloseWindow();
onStart();
}
bool disposed;
protected override void Dispose(bool disposing)
{
if (disposing && !disposed)
{
disposed = true;
Game.BeforeGameStart -= OnGameStart;
}
base.Dispose(disposing);
}
class ReplayState
{
public bool Visible;
public ScrollItemWidget Item;
}
class Filter
{
public GameType Type;
public DateType Date;
public DurationType Duration;
public WinState Outcome;
public string PlayerName;
public string MapName;
public string Faction;
public bool IsEmpty =>
Type == default(GameType)
&& Date == default(DateType)
&& Duration == default(DurationType)
&& Outcome == default(WinState)
&& string.IsNullOrEmpty(PlayerName)
&& string.IsNullOrEmpty(MapName)
&& string.IsNullOrEmpty(Faction);
}
enum GameType
{
Any,
Singleplayer,
Multiplayer
}
enum DateType
{
Any,
Today,
LastWeek,
LastFortnight,
LastMonth
}
enum DurationType
{
Any,
VeryShort,
Short,
Medium,
Long
}
}
}