1 #region Copyright & License Information 2 /* 3 * Copyright 2007-2020 The OpenRA Developers (see AUTHORS) 4 * This file is part of OpenRA, which is free software. It is made 5 * available to you under the terms of the GNU General Public License 6 * as published by the Free Software Foundation, either version 3 of 7 * the License, or (at your option) any later version. For more 8 * information, see COPYING. 9 */ 10 #endregion 11 12 using System; 13 using System.Collections.Concurrent; 14 using System.Collections.Generic; 15 using System.IO; 16 using System.Linq; 17 using System.Threading; 18 using System.Threading.Tasks; 19 using OpenRA.FileFormats; 20 using OpenRA.Primitives; 21 using OpenRA.Traits; 22 using OpenRA.Widgets; 23 24 namespace OpenRA.Mods.Common.Widgets.Logic 25 { 26 public class ReplayBrowserLogic : ChromeLogic 27 { 28 static Filter filter = new Filter(); 29 30 readonly Widget panel; 31 readonly ScrollPanelWidget replayList, playerList; 32 readonly ScrollItemWidget playerTemplate, playerHeader; 33 readonly List<ReplayMetadata> replays = new List<ReplayMetadata>(); 34 readonly Dictionary<ReplayMetadata, ReplayState> replayState = new Dictionary<ReplayMetadata, ReplayState>(); 35 readonly Action onStart; 36 readonly ModData modData; 37 readonly WebServices services; 38 39 MapPreview map; 40 ReplayMetadata selectedReplay; 41 42 volatile bool cancelLoadingReplays; 43 44 [ObjectCreator.UseCtor] ReplayBrowserLogic(Widget widget, ModData modData, Action onExit, Action onStart)45 public ReplayBrowserLogic(Widget widget, ModData modData, Action onExit, Action onStart) 46 { 47 map = MapCache.UnknownMap; 48 panel = widget; 49 50 services = modData.Manifest.Get<WebServices>(); 51 this.modData = modData; 52 this.onStart = onStart; 53 Game.BeforeGameStart += OnGameStart; 54 55 playerList = panel.Get<ScrollPanelWidget>("PLAYER_LIST"); 56 playerHeader = playerList.Get<ScrollItemWidget>("HEADER"); 57 playerTemplate = playerList.Get<ScrollItemWidget>("TEMPLATE"); 58 playerList.RemoveChildren(); 59 60 panel.Get<ButtonWidget>("CANCEL_BUTTON").OnClick = () => { cancelLoadingReplays = true; Ui.CloseWindow(); onExit(); }; 61 62 replayList = panel.Get<ScrollPanelWidget>("REPLAY_LIST"); 63 var template = panel.Get<ScrollItemWidget>("REPLAY_TEMPLATE"); 64 65 var mod = modData.Manifest; 66 var dir = Platform.ResolvePath(Platform.SupportDirPrefix, "Replays", mod.Id, mod.Metadata.Version); 67 68 if (Directory.Exists(dir)) 69 ThreadPool.QueueUserWorkItem(_ => LoadReplays(dir, template)); 70 71 var watch = panel.Get<ButtonWidget>("WATCH_BUTTON"); 72 watch.IsDisabled = () => selectedReplay == null || map.Status != MapStatus.Available; 73 watch.OnClick = () => { WatchReplay(); }; 74 75 var mapPreviewRoot = panel.Get("MAP_PREVIEW_ROOT"); 76 mapPreviewRoot.IsVisible = () => selectedReplay != null; 77 panel.Get("REPLAY_INFO").IsVisible = () => selectedReplay != null; 78 79 Ui.LoadWidget("MAP_PREVIEW", mapPreviewRoot, new WidgetArgs 80 { 81 { "orderManager", null }, 82 { "getMap", (Func<MapPreview>)(() => map) }, 83 { "onMouseDown", (Action<MapPreviewWidget, MapPreview, MouseInput>)((preview, mapPreview, mi) => { }) }, 84 { 85 "getSpawnOccupants", (Func<MapPreview, Dictionary<CPos, SpawnOccupant>>)(mapPreview => 86 LobbyUtils.GetSpawnOccupants(selectedReplay.GameInfo.Players, mapPreview)) 87 }, 88 { "showUnoccupiedSpawnpoints", false }, 89 }); 90 91 var replayDuration = new CachedTransform<ReplayMetadata, string>(r => 92 "Duration: {0}".F(WidgetUtils.FormatTimeSeconds((int)selectedReplay.GameInfo.Duration.TotalSeconds))); 93 panel.Get<LabelWidget>("DURATION").GetText = () => replayDuration.Update(selectedReplay); 94 95 SetupFilters(); 96 SetupManagement(); 97 } 98 LoadReplays(string dir, ScrollItemWidget template)99 void LoadReplays(string dir, ScrollItemWidget template) 100 { 101 using (new Support.PerfTimer("Load replays")) 102 { 103 var loadedReplays = new ConcurrentBag<ReplayMetadata>(); 104 Parallel.ForEach(Directory.GetFiles(dir, "*.orarep", SearchOption.AllDirectories), (fileName, pls) => 105 { 106 if (cancelLoadingReplays) 107 { 108 pls.Stop(); 109 return; 110 } 111 112 var replay = ReplayMetadata.Read(fileName); 113 if (replay != null) 114 loadedReplays.Add(replay); 115 }); 116 117 if (cancelLoadingReplays) 118 return; 119 120 var sortedReplays = loadedReplays.OrderByDescending(replay => replay.GameInfo.StartTimeUtc).ToList(); 121 Game.RunAfterTick(() => 122 { 123 replayList.RemoveChildren(); 124 foreach (var replay in sortedReplays) 125 AddReplay(replay, template); 126 127 SetupReplayDependentFilters(); 128 ApplyFilter(); 129 }); 130 } 131 } 132 SetupFilters()133 void SetupFilters() 134 { 135 // Game type 136 { 137 var ddb = panel.GetOrNull<DropDownButtonWidget>("FLT_GAMETYPE_DROPDOWNBUTTON"); 138 if (ddb != null) 139 { 140 // Using list to maintain the order 141 var options = new List<Pair<GameType, string>> 142 { 143 Pair.New(GameType.Any, ddb.GetText()), 144 Pair.New(GameType.Singleplayer, "Singleplayer"), 145 Pair.New(GameType.Multiplayer, "Multiplayer") 146 }; 147 148 var lookup = options.ToDictionary(kvp => kvp.First, kvp => kvp.Second); 149 150 ddb.GetText = () => lookup[filter.Type]; 151 ddb.OnMouseDown = _ => 152 { 153 Func<Pair<GameType, string>, ScrollItemWidget, ScrollItemWidget> setupItem = (option, tpl) => 154 { 155 var item = ScrollItemWidget.Setup( 156 tpl, 157 () => filter.Type == option.First, 158 () => { filter.Type = option.First; ApplyFilter(); }); 159 item.Get<LabelWidget>("LABEL").GetText = () => option.Second; 160 return item; 161 }; 162 163 ddb.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", 330, options, setupItem); 164 }; 165 } 166 } 167 168 // Date type 169 { 170 var ddb = panel.GetOrNull<DropDownButtonWidget>("FLT_DATE_DROPDOWNBUTTON"); 171 if (ddb != null) 172 { 173 // Using list to maintain the order 174 var options = new List<Pair<DateType, string>> 175 { 176 Pair.New(DateType.Any, ddb.GetText()), 177 Pair.New(DateType.Today, "Today"), 178 Pair.New(DateType.LastWeek, "Last 7 days"), 179 Pair.New(DateType.LastFortnight, "Last 14 days"), 180 Pair.New(DateType.LastMonth, "Last 30 days") 181 }; 182 183 var lookup = options.ToDictionary(kvp => kvp.First, kvp => kvp.Second); 184 185 ddb.GetText = () => lookup[filter.Date]; 186 ddb.OnMouseDown = _ => 187 { 188 Func<Pair<DateType, string>, ScrollItemWidget, ScrollItemWidget> setupItem = (option, tpl) => 189 { 190 var item = ScrollItemWidget.Setup( 191 tpl, 192 () => filter.Date == option.First, 193 () => { filter.Date = option.First; ApplyFilter(); }); 194 195 item.Get<LabelWidget>("LABEL").GetText = () => option.Second; 196 return item; 197 }; 198 199 ddb.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", 330, options, setupItem); 200 }; 201 } 202 } 203 204 // Duration 205 { 206 var ddb = panel.GetOrNull<DropDownButtonWidget>("FLT_DURATION_DROPDOWNBUTTON"); 207 if (ddb != null) 208 { 209 // Using list to maintain the order 210 var options = new List<Pair<DurationType, string>> 211 { 212 Pair.New(DurationType.Any, ddb.GetText()), 213 Pair.New(DurationType.VeryShort, "Under 5 min"), 214 Pair.New(DurationType.Short, "Short (10 min)"), 215 Pair.New(DurationType.Medium, "Medium (30 min)"), 216 Pair.New(DurationType.Long, "Long (60+ min)") 217 }; 218 219 var lookup = options.ToDictionary(kvp => kvp.First, kvp => kvp.Second); 220 221 ddb.GetText = () => lookup[filter.Duration]; 222 ddb.OnMouseDown = _ => 223 { 224 Func<Pair<DurationType, string>, ScrollItemWidget, ScrollItemWidget> setupItem = (option, tpl) => 225 { 226 var item = ScrollItemWidget.Setup( 227 tpl, 228 () => filter.Duration == option.First, 229 () => { filter.Duration = option.First; ApplyFilter(); }); 230 item.Get<LabelWidget>("LABEL").GetText = () => option.Second; 231 return item; 232 }; 233 234 ddb.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", 330, options, setupItem); 235 }; 236 } 237 } 238 239 // Outcome (depends on Player) 240 { 241 var ddb = panel.GetOrNull<DropDownButtonWidget>("FLT_OUTCOME_DROPDOWNBUTTON"); 242 if (ddb != null) 243 { 244 ddb.IsDisabled = () => string.IsNullOrEmpty(filter.PlayerName); 245 246 // Using list to maintain the order 247 var options = new List<Pair<WinState, string>> 248 { 249 Pair.New(WinState.Undefined, ddb.GetText()), 250 Pair.New(WinState.Lost, "Defeat"), 251 Pair.New(WinState.Won, "Victory") 252 }; 253 254 var lookup = options.ToDictionary(kvp => kvp.First, kvp => kvp.Second); 255 256 ddb.GetText = () => lookup[filter.Outcome]; 257 ddb.OnMouseDown = _ => 258 { 259 Func<Pair<WinState, string>, ScrollItemWidget, ScrollItemWidget> setupItem = (option, tpl) => 260 { 261 var item = ScrollItemWidget.Setup( 262 tpl, 263 () => filter.Outcome == option.First, 264 () => { filter.Outcome = option.First; ApplyFilter(); }); 265 item.Get<LabelWidget>("LABEL").GetText = () => option.Second; 266 return item; 267 }; 268 269 ddb.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", 330, options, setupItem); 270 }; 271 } 272 } 273 274 // Reset button 275 { 276 var button = panel.Get<ButtonWidget>("FLT_RESET_BUTTON"); 277 button.IsDisabled = () => filter.IsEmpty; 278 button.OnClick = () => { filter = new Filter(); ApplyFilter(); }; 279 } 280 } 281 SetupReplayDependentFilters()282 void SetupReplayDependentFilters() 283 { 284 // Map 285 { 286 var ddb = panel.GetOrNull<DropDownButtonWidget>("FLT_MAPNAME_DROPDOWNBUTTON"); 287 if (ddb != null) 288 { 289 var options = replays.Select(r => r.GameInfo.MapTitle).Distinct(StringComparer.OrdinalIgnoreCase).ToList(); 290 options.Sort(StringComparer.OrdinalIgnoreCase); 291 options.Insert(0, null); // no filter 292 293 var anyText = ddb.GetText(); 294 ddb.GetText = () => string.IsNullOrEmpty(filter.MapName) ? anyText : filter.MapName; 295 ddb.OnMouseDown = _ => 296 { 297 Func<string, ScrollItemWidget, ScrollItemWidget> setupItem = (option, tpl) => 298 { 299 var item = ScrollItemWidget.Setup( 300 tpl, 301 () => string.Compare(filter.MapName, option, true) == 0, 302 () => { filter.MapName = option; ApplyFilter(); }); 303 item.Get<LabelWidget>("LABEL").GetText = () => option ?? anyText; 304 return item; 305 }; 306 307 ddb.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", 330, options, setupItem); 308 }; 309 } 310 } 311 312 // Players 313 { 314 var ddb = panel.GetOrNull<DropDownButtonWidget>("FLT_PLAYER_DROPDOWNBUTTON"); 315 if (ddb != null) 316 { 317 var options = replays.SelectMany(r => r.GameInfo.Players.Select(p => p.Name)).Distinct(StringComparer.OrdinalIgnoreCase).ToList(); 318 options.Sort(StringComparer.OrdinalIgnoreCase); 319 options.Insert(0, null); // no filter 320 321 var anyText = ddb.GetText(); 322 ddb.GetText = () => string.IsNullOrEmpty(filter.PlayerName) ? anyText : filter.PlayerName; 323 ddb.OnMouseDown = _ => 324 { 325 Func<string, ScrollItemWidget, ScrollItemWidget> setupItem = (option, tpl) => 326 { 327 var item = ScrollItemWidget.Setup( 328 tpl, 329 () => string.Compare(filter.PlayerName, option, true) == 0, 330 () => { filter.PlayerName = option; ApplyFilter(); }); 331 item.Get<LabelWidget>("LABEL").GetText = () => option ?? anyText; 332 return item; 333 }; 334 335 ddb.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", 330, options, setupItem); 336 }; 337 } 338 } 339 340 // Faction (depends on Player) 341 { 342 var ddb = panel.GetOrNull<DropDownButtonWidget>("FLT_FACTION_DROPDOWNBUTTON"); 343 if (ddb != null) 344 { 345 ddb.IsDisabled = () => string.IsNullOrEmpty(filter.PlayerName); 346 347 var options = replays 348 .SelectMany(r => r.GameInfo.Players.Select(p => p.FactionName).Where(n => !string.IsNullOrEmpty(n))) 349 .Distinct(StringComparer.OrdinalIgnoreCase).ToList(); 350 options.Sort(StringComparer.OrdinalIgnoreCase); 351 options.Insert(0, null); // no filter 352 353 var anyText = ddb.GetText(); 354 ddb.GetText = () => string.IsNullOrEmpty(filter.Faction) ? anyText : filter.Faction; 355 ddb.OnMouseDown = _ => 356 { 357 Func<string, ScrollItemWidget, ScrollItemWidget> setupItem = (option, tpl) => 358 { 359 var item = ScrollItemWidget.Setup( 360 tpl, 361 () => string.Compare(filter.Faction, option, true) == 0, 362 () => { filter.Faction = option; ApplyFilter(); }); 363 item.Get<LabelWidget>("LABEL").GetText = () => option ?? anyText; 364 return item; 365 }; 366 367 ddb.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", 330, options, setupItem); 368 }; 369 } 370 } 371 } 372 SetupManagement()373 void SetupManagement() 374 { 375 var renameButton = panel.Get<ButtonWidget>("MNG_RENSEL_BUTTON"); 376 renameButton.IsDisabled = () => selectedReplay == null; 377 renameButton.OnClick = () => 378 { 379 var r = selectedReplay; 380 var initialName = Path.GetFileNameWithoutExtension(r.FilePath); 381 var directoryName = Path.GetDirectoryName(r.FilePath); 382 var invalidChars = Path.GetInvalidFileNameChars(); 383 384 ConfirmationDialogs.TextInputPrompt( 385 "Rename Replay", 386 "Enter a new file name:", 387 initialName, 388 onAccept: newName => RenameReplay(r, newName), 389 onCancel: null, 390 acceptText: "Rename", 391 cancelText: null, 392 inputValidator: newName => 393 { 394 if (newName == initialName) 395 return false; 396 397 if (string.IsNullOrWhiteSpace(newName)) 398 return false; 399 400 if (newName.IndexOfAny(invalidChars) >= 0) 401 return false; 402 403 if (File.Exists(Path.Combine(directoryName, newName))) 404 return false; 405 406 return true; 407 }); 408 }; 409 410 Action<ReplayMetadata, Action> onDeleteReplay = (r, after) => 411 { 412 ConfirmationDialogs.ButtonPrompt( 413 title: "Delete selected replay?", 414 text: "Delete replay '{0}'?".F(Path.GetFileNameWithoutExtension(r.FilePath)), 415 onConfirm: () => 416 { 417 DeleteReplay(r); 418 if (after != null) 419 after.Invoke(); 420 }, 421 confirmText: "Delete", 422 onCancel: () => { }); 423 }; 424 425 var deleteButton = panel.Get<ButtonWidget>("MNG_DELSEL_BUTTON"); 426 deleteButton.IsDisabled = () => selectedReplay == null; 427 deleteButton.OnClick = () => 428 { 429 onDeleteReplay(selectedReplay, () => 430 { 431 if (selectedReplay == null) 432 SelectFirstVisibleReplay(); 433 }); 434 }; 435 436 var deleteAllButton = panel.Get<ButtonWidget>("MNG_DELALL_BUTTON"); 437 deleteAllButton.IsDisabled = () => replayState.Count(kvp => kvp.Value.Visible) == 0; 438 deleteAllButton.OnClick = () => 439 { 440 var list = replayState.Where(kvp => kvp.Value.Visible).Select(kvp => kvp.Key).ToList(); 441 if (list.Count == 0) 442 return; 443 444 if (list.Count == 1) 445 { 446 onDeleteReplay(list[0], () => { if (selectedReplay == null) SelectFirstVisibleReplay(); }); 447 return; 448 } 449 450 ConfirmationDialogs.ButtonPrompt( 451 title: "Delete all selected replays?", 452 text: "Delete {0} replays?".F(list.Count), 453 onConfirm: () => 454 { 455 list.ForEach(DeleteReplay); 456 if (selectedReplay == null) 457 SelectFirstVisibleReplay(); 458 }, 459 confirmText: "Delete All", 460 onCancel: () => { }); 461 }; 462 } 463 RenameReplay(ReplayMetadata replay, string newFilenameWithoutExtension)464 void RenameReplay(ReplayMetadata replay, string newFilenameWithoutExtension) 465 { 466 try 467 { 468 var item = replayState[replay].Item; 469 replay.RenameFile(newFilenameWithoutExtension); 470 item.Text = newFilenameWithoutExtension; 471 472 var label = item.Get<LabelWithTooltipWidget>("TITLE"); 473 WidgetUtils.TruncateLabelToTooltip(label, item.Text); 474 } 475 catch (Exception ex) 476 { 477 Log.Write("debug", ex.ToString()); 478 return; 479 } 480 } 481 DeleteReplay(ReplayMetadata replay)482 void DeleteReplay(ReplayMetadata replay) 483 { 484 try 485 { 486 File.Delete(replay.FilePath); 487 } 488 catch (Exception ex) 489 { 490 Game.Debug("Failed to delete replay file '{0}'. See the logs for details.", replay.FilePath); 491 Log.Write("debug", ex.ToString()); 492 return; 493 } 494 495 if (replay == selectedReplay) 496 SelectReplay(null); 497 498 replayList.RemoveChild(replayState[replay].Item); 499 replays.Remove(replay); 500 replayState.Remove(replay); 501 } 502 EvaluateReplayVisibility(ReplayMetadata replay)503 bool EvaluateReplayVisibility(ReplayMetadata replay) 504 { 505 // Game type 506 if ((filter.Type == GameType.Multiplayer && replay.GameInfo.IsSinglePlayer) || (filter.Type == GameType.Singleplayer && !replay.GameInfo.IsSinglePlayer)) 507 return false; 508 509 // Date type 510 if (filter.Date != DateType.Any) 511 { 512 TimeSpan t; 513 switch (filter.Date) 514 { 515 case DateType.Today: 516 t = TimeSpan.FromDays(1d); 517 break; 518 519 case DateType.LastWeek: 520 t = TimeSpan.FromDays(7d); 521 break; 522 523 case DateType.LastFortnight: 524 t = TimeSpan.FromDays(14d); 525 break; 526 527 case DateType.LastMonth: 528 default: 529 t = TimeSpan.FromDays(30d); 530 break; 531 } 532 533 if (replay.GameInfo.StartTimeUtc < DateTime.UtcNow - t) 534 return false; 535 } 536 537 // Duration 538 if (filter.Duration != DurationType.Any) 539 { 540 var minutes = replay.GameInfo.Duration.TotalMinutes; 541 switch (filter.Duration) 542 { 543 case DurationType.VeryShort: 544 if (minutes >= 5) 545 return false; 546 break; 547 548 case DurationType.Short: 549 if (minutes < 5 || minutes >= 20) 550 return false; 551 break; 552 553 case DurationType.Medium: 554 if (minutes < 20 || minutes >= 60) 555 return false; 556 break; 557 558 case DurationType.Long: 559 if (minutes < 60) 560 return false; 561 break; 562 } 563 } 564 565 // Map 566 if (!string.IsNullOrEmpty(filter.MapName) && string.Compare(filter.MapName, replay.GameInfo.MapTitle, true) != 0) 567 return false; 568 569 // Player 570 if (!string.IsNullOrEmpty(filter.PlayerName)) 571 { 572 var player = replay.GameInfo.Players.FirstOrDefault(p => string.Compare(filter.PlayerName, p.Name, true) == 0); 573 if (player == null) 574 return false; 575 576 // Outcome 577 if (filter.Outcome != WinState.Undefined && filter.Outcome != player.Outcome) 578 return false; 579 580 // Faction 581 if (!string.IsNullOrEmpty(filter.Faction) && string.Compare(filter.Faction, player.FactionName, true) != 0) 582 return false; 583 } 584 585 return true; 586 } 587 ApplyFilter()588 void ApplyFilter() 589 { 590 foreach (var replay in replays) 591 replayState[replay].Visible = EvaluateReplayVisibility(replay); 592 593 if (selectedReplay == null || replayState[selectedReplay].Visible == false) 594 SelectFirstVisibleReplay(); 595 596 replayList.Layout.AdjustChildren(); 597 replayList.ScrollToSelectedItem(); 598 } 599 SelectFirstVisibleReplay()600 void SelectFirstVisibleReplay() 601 { 602 SelectReplay(replays.FirstOrDefault(r => replayState[r].Visible)); 603 } 604 SelectReplay(ReplayMetadata replay)605 void SelectReplay(ReplayMetadata replay) 606 { 607 selectedReplay = replay; 608 map = selectedReplay != null ? selectedReplay.GameInfo.MapPreview : MapCache.UnknownMap; 609 610 if (replay == null) 611 return; 612 613 try 614 { 615 if (map.Status != MapStatus.Available) 616 { 617 if (map.Status == MapStatus.DownloadAvailable) 618 LoadMapPreviewRules(map); 619 else if (Game.Settings.Game.AllowDownloading) 620 modData.MapCache.QueryRemoteMapDetails(services.MapRepository, new[] { map.Uid }, LoadMapPreviewRules); 621 } 622 623 var players = replay.GameInfo.Players 624 .GroupBy(p => p.Team) 625 .OrderBy(g => g.Key); 626 627 var teams = new Dictionary<string, IEnumerable<GameInformation.Player>>(); 628 var noTeams = players.Count() == 1; 629 foreach (var p in players) 630 { 631 var label = noTeams ? "Players" : p.Key == 0 ? "No Team" : "Team {0}".F(p.Key); 632 teams.Add(label, p); 633 } 634 635 playerList.RemoveChildren(); 636 637 foreach (var kv in teams) 638 { 639 var group = kv.Key; 640 if (group.Length > 0) 641 { 642 var header = ScrollItemWidget.Setup(playerHeader, () => true, () => { }); 643 header.Get<LabelWidget>("LABEL").GetText = () => group; 644 playerList.AddChild(header); 645 } 646 647 foreach (var option in kv.Value) 648 { 649 var o = option; 650 651 var color = o.Color; 652 653 var item = ScrollItemWidget.Setup(playerTemplate, () => false, () => { }); 654 655 var label = item.Get<LabelWidget>("LABEL"); 656 var font = Game.Renderer.Fonts[label.Font]; 657 var name = WidgetUtils.TruncateText(o.Name, label.Bounds.Width, font); 658 label.GetText = () => name; 659 label.GetColor = () => color; 660 661 var flag = item.Get<ImageWidget>("FLAG"); 662 flag.GetImageCollection = () => "flags"; 663 var factionInfo = modData.DefaultRules.Actors["world"].TraitInfos<FactionInfo>(); 664 flag.GetImageName = () => (factionInfo != null && factionInfo.Any(f => f.InternalName == o.FactionId)) ? o.FactionId : "Random"; 665 666 playerList.AddChild(item); 667 } 668 } 669 } 670 catch (Exception e) 671 { 672 Log.Write("debug", "Exception while parsing replay: {0}", e); 673 SelectReplay(null); 674 } 675 } 676 LoadMapPreviewRules(MapPreview map)677 void LoadMapPreviewRules(MapPreview map) 678 { 679 new Task(() => 680 { 681 // Force map rules to be loaded on this background thread 682 map.PreloadRules(); 683 }).Start(); 684 } 685 WatchReplay()686 void WatchReplay() 687 { 688 if (selectedReplay != null && ReplayUtils.PromptConfirmReplayCompatibility(selectedReplay)) 689 { 690 cancelLoadingReplays = true; 691 Game.JoinReplay(selectedReplay.FilePath); 692 } 693 } 694 AddReplay(ReplayMetadata replay, ScrollItemWidget template)695 void AddReplay(ReplayMetadata replay, ScrollItemWidget template) 696 { 697 replays.Add(replay); 698 699 var item = ScrollItemWidget.Setup(template, 700 () => selectedReplay == replay, 701 () => SelectReplay(replay), 702 () => WatchReplay()); 703 704 replayState[replay] = new ReplayState 705 { 706 Item = item, 707 Visible = true 708 }; 709 710 item.Text = Path.GetFileNameWithoutExtension(replay.FilePath); 711 var label = item.Get<LabelWithTooltipWidget>("TITLE"); 712 WidgetUtils.TruncateLabelToTooltip(label, item.Text); 713 714 item.IsVisible = () => replayState[replay].Visible; 715 replayList.AddChild(item); 716 } 717 OnGameStart()718 void OnGameStart() 719 { 720 Ui.CloseWindow(); 721 onStart(); 722 } 723 724 bool disposed; Dispose(bool disposing)725 protected override void Dispose(bool disposing) 726 { 727 if (disposing && !disposed) 728 { 729 disposed = true; 730 Game.BeforeGameStart -= OnGameStart; 731 } 732 733 base.Dispose(disposing); 734 } 735 736 class ReplayState 737 { 738 public bool Visible; 739 public ScrollItemWidget Item; 740 } 741 742 class Filter 743 { 744 public GameType Type; 745 public DateType Date; 746 public DurationType Duration; 747 public WinState Outcome; 748 public string PlayerName; 749 public string MapName; 750 public string Faction; 751 752 public bool IsEmpty 753 { 754 get 755 { 756 return Type == default(GameType) 757 && Date == default(DateType) 758 && Duration == default(DurationType) 759 && Outcome == default(WinState) 760 && string.IsNullOrEmpty(PlayerName) 761 && string.IsNullOrEmpty(MapName) 762 && string.IsNullOrEmpty(Faction); 763 } 764 } 765 } 766 767 enum GameType 768 { 769 Any, 770 Singleplayer, 771 Multiplayer 772 } 773 774 enum DateType 775 { 776 Any, 777 Today, 778 LastWeek, 779 LastFortnight, 780 LastMonth 781 } 782 783 enum DurationType 784 { 785 Any, 786 VeryShort, 787 Short, 788 Medium, 789 Long 790 } 791 } 792 } 793