using Dominion.NET_WPF.Controls; using Dominion.NET_WPF.Models; using Dominion.NET_WPF.ViewModel; using DominionBase; using DominionBase.Cards; using DominionBase.Enums; using DominionBase.Players; using DominionBase.Players.PlayerMessages; using DominionBase.Utilities; using ICSharpCode.SharpZipLib.Zip; using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Threading; using CtrlOrientation = System.Windows.Controls.Orientation; using WinApplication = System.Windows.Application; namespace Dominion.NET_WPF { internal delegate void player_ChooseDelegate(DominionBase.Players.Player player, Choice choice); /// /// Interaction logic for wMain.xaml /// public partial class WMain : Window { private static Settings _settings; public static Settings Settings { get { return _settings; } private set { _settings = value; } } public static IGame Game; private DominionBase.Players.Player _player; private VersionInfo _latestVersionInfo; private readonly Dictionary _matEventHandlers = new Dictionary(); private Thread _gameThread; public AutoResetEvent WaitEvent = new AutoResetEvent(false); private Label _tradeRouteLabel; private int _currentPlayDepth; private bool _startingNewGame; private Statistics _statistics; private wCardPreview _wcp; private MainViewModel Context => (MainViewModel)DataContext; private int CurrentPlayDepth { get { return _currentPlayDepth; } set { _currentPlayDepth = Math.Max(0, value); } } private readonly Stack _playStack = new Stack(); public WMain() { //Thread.CurrentThread.CurrentUICulture = new CultureInfo("de"); //Thread.CurrentThread.CurrentCulture = new CultureInfo("de"); InitializeComponent(); if (!Directory.Exists(DominionBase.Utilities.Application.ApplicationPath)) Directory.CreateDirectory(DominionBase.Utilities.Application.ApplicationPath); tiGamePoints.Visibility = Visibility.Collapsed; InitPointsChart(); bTurnDone.IsEnabled = false; bPlayTreasures.Text = "Play _Treasures"; bPlayTreasures.IsEnabled = false; bDonePlayingTreasures.IsEnabled = false; bSpendVillager.IsEnabled = false; bSpendCoffers.IsEnabled = false; bPayOffDebtTokens.IsEnabled = false; bBuyPhase.IsEnabled = false; bNightPhase.IsEnabled = false; bUndo.IsEnabled = false; SetGameBackground(); _statistics = Statistics.Load(); } internal Control FindGameObject(IGameObject gameObject) { //stackPanelSupplyPiles.Children.OfType().SelectMany(sp => sp.Children.OfType()).Select(sc => sc.FindGameObject(gameObject)).FirstOrDefault(c => c != null); return stackPanelSupplyPiles.Children.OfType().SelectMany(sp => sp.Children.OfType()).Select(sc => sc.FindGameObject(gameObject)).FirstOrDefault(c => c != null) ?? (from TabItem ti in tcAreas.Items select ti.Content as UcPlayerDisplay into display select display?.FindGameObject(gameObject)).FirstOrDefault(fe => fe != null); } private async void Window_Initialized(object sender, EventArgs e) { Settings = Settings.Load(); Context.Settings = Settings; glMain.LogFile = Path.Combine(DominionBase.Utilities.Application.ApplicationPath, "game.log"); if (_settings.WindowSize.Width > 0) Width = _settings.WindowSize.Width; if (_settings.WindowSize.Height > 0) Height = _settings.WindowSize.Height; WindowState = _settings.WindowState; #if DEBUG await Task.Run(() => { }); #else miCheckForUpdates.IsEnabled = false; await Task.Run(() => { try { CheckForUpdates(false); } catch { } }); miCheckForUpdates.IsEnabled = true; #endif } private void SetGameBackground() { // Don't load brushes while updating //if (WinApplication.Current.Properties["Update"] != null) // return; var bg = Settings.BackgroundBrush; if (Settings.BackgroundBrush is ImageBrush image) { image.Stretch = Stretch.Fill; image.TileMode = TileMode.Tile; image.ViewportUnits = BrushMappingMode.Absolute; image.Viewport = new Rect(0, 0, image.ImageSource.Height / 2, image.ImageSource.Width / 2); } gGame.Fill = bg; foreach (var wp in dpMatsandPiles.Children.OfType()) { foreach (var ccc in wp.Children.OfType()) { ccc.Foreground = Settings.ForegroundBrush; } } } private void EnqueueGameMessageAndWait(GameMessage message) { var lockWasTaken = false; var temp = Game.MessageRequestQueue; try { Monitor.Enter(temp, ref lockWasTaken); { Game.MessageRequestQueue.Enqueue(message); } } finally { if (lockWasTaken) Monitor.Exit(temp); } Game.WaitEvent.Set(); // So... it turns out that this will completely hang the application if allowed to actually wait for the MessageResponseQueue to empty out. // It's not great that this isn't working the way it's *supposed to* work, but that's an investigation for a different day. For now, it works // as it works and it's unclear what the ramifications of that are. //while (game.MessageResponseQueue.Count == 0) //Thread.Sleep(100); } private void ReleaseEvents() { if (Game == null) return; Game.GameEndedEvent -= Game_GameEndedEvent; Game.GameMessage -= Game_GameMessage; if (Game.Table != null) { if (Game.Table.TokenPiles != null) Game.Table.TokenPiles.TokenCollectionsChanged -= TokenPiles_TokenCollectionsChanged; if (Game.Table.Trash != null) Game.Table.Trash.PileChanged -= Trash_PileChanged; foreach (var pileKey in Game.Table.SpecialPiles.Keys) { if (_matEventHandlers.ContainsKey(pileKey)) Game.Table.SpecialPiles[pileKey].PileChanged -= _matEventHandlers[pileKey]; if (pileKey == DominionBase.Cards.Nocturne.TypeClass.Boons) ((IBoonSupply)Game.Table.SpecialPiles[pileKey]).Shuffled += SpecialPile_Shuffle; if (pileKey == DominionBase.Cards.Nocturne.TypeClass.Hexes) ((IHexSupply)Game.Table.SpecialPiles[pileKey]).Shuffled += SpecialPile_Shuffle; } } if (Game.Players != null) { foreach (var player in Game.Players) { player.Choose = null; player.Revealed.PileChanged -= Revealed_PileChanged; player.SetAside.PileChanged -= SetAside_PileChanged; player.BenefitReceivingInitiated -= Player_BenefitReceiving; player.CardPlaying -= Player_CardPlaying; player.CardPlayFinished -= Player_CardPlayed; player.CardUndoPlaying -= Player_CardUndoPlaying; player.CardUndoPlayed -= Player_CardUndoPlayed; player.CardBuying -= Player_CardBuying; player.CardBought -= Player_CardBought; player.CardBuyFinished -= Player_CardBuyFinished; player.CardGaining -= Player_CardGaining; player.CardGainedInto -= Player_CardGainedInto; player.CardGainFinished -= Player_CardGainFinished; player.TokenPlaying -= Player_TokenPlaying; player.TokenPlayed -= Player_TokenPlayed; player.Trashing -= Player_Trashing; player.TrashedFinished -= Player_Trashed; player.Calling -= Player_Calling; player.CalledFinished -= Player_Called; player.PhaseChangedFinished -= Player_PhaseChangedEvent; player.PlayerModeChanged -= Player_PlayerModeChangedEvent; player.CardsDrawn -= Player_CardsDrawn; player.TurnStarting -= Player_TurnStarting; player.TurnEnded -= Player_TurnEnded; player.ShufflingStart -= Player_ShufflingStart; player.CardsAddedToDeck -= Player_CardsAddedToDeck; player.CardsAddedToHand -= Player_CardsAddedToHand; player.CardsDiscarded -= Player_CardsDiscarded; player.PlayerMats.CardMatsChanged -= PlayerMats_DecksChanged; player.TokenPiles.TokenCollectionsChanged -= PlayerTokenPiles_TokenCollectionsChanged; player.BenefitsChanged -= Player_BenefitsChanged; player.CardFlippedOver -= Player_CardFlippedOver; player.TakeablesChanged -= Player_TakeablesChanged; if (player == _player) { player.CardReceived -= Player_CardReceived; } } } } private void ReleaseGame() { _player = null; uccChooser.IsReady = false; uccChooser.ClearAutoYields(AutoYieldDuration.Game); cardTrash.Pile = new DisplayableCollection(); foreach (TabItem ti in tcAreas.Items) { if (!(ti.Content is UcPlayerDisplay display)) continue; display.TearDown(); (((ti.Header as DockPanel)?.ToolTip as ToolTip)?.Content as UcPlayerOverview)?.TearDown(); } foreach (var control in stackPanelSupplyPiles.Children.OfType().SelectMany(sp => sp.Children.OfType())) control.Supply = null; if (Game == null) return; ReleaseEvents(); Game.Clear(); Game = null; GC.Collect(); GC.Collect(1); GC.Collect(2); GC.Collect(3); GC.WaitForPendingFinalizers(); GC.Collect(); } private void StartGame(GameSettings settings) { _startingNewGame = false; ReleaseGame(); Settings = Settings.Load(); SetGameBackground(); // Clean out the Image Repository before starting a new game -- // so we don't allocate too much memory for cards we're not even using Caching.ImageRepository.Reset(); tiGamePoints.Visibility = Visibility.Collapsed; dpGameInfo.Visibility = Visibility.Visible; glMain.TearDown(); glMain.Clear(); LayoutSupplyPiles(); while (tcAreas.Items.Count > 3) tcAreas.Items.RemoveAt(tcAreas.Items.Count - 1); dpMatsandPiles.Children.Clear(); dpGameStuff.Children.Clear(); _tradeRouteLabel = null; // Try to force garbage collection to save some memory GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced); try { Game = new Game( _settings.NumberOfHumanPlayers, _settings.PlayerSettings.Take(_settings.NumberOfPlayers).Select(ps => ps.Name), _settings.PlayerSettings.Take(_settings.NumberOfPlayers).Select(ps => ps.AIClassType), settings); Game.SelectCards(); if (Game.Settings.Preset != null || Settings.AutomaticallyAcceptKingdomCards) AcceptGame(); else { var selector = new WCardSelection { Owner = this }; if (selector.ShowDialog() == true) { Settings.AutomaticallyAcceptKingdomCards = selector.cbAutoAccept.IsChecked == true; Settings.Save(); AcceptGame(); } else Game = null; } } catch (DominionBase.Cards.ConstraintException ce) { wMessageBox.Show(ce.Message, "Constraint exception!", MessageBoxButton.OK, MessageBoxImage.Exclamation); } catch (GameCreationException gce) { wMessageBox.Show(gce.Message, "Game creation exception!", MessageBoxButton.OK, MessageBoxImage.Exclamation); } } private void AcceptGame() { Game.AcceptCards(); Game.GameEndedEvent += Game_GameEndedEvent; Settings.PlayerSettings.ForEach(ps => glMain.AddPlayerColor(ps.Name, ps.UIColor)); glMain.NewSection($"Game started with {Game.Players.Count} players"); Game.Table.TokenPiles.TokenCollectionsChanged += TokenPiles_TokenCollectionsChanged; Game.Table.Trash.PileChanged += Trash_PileChanged; Trash_PileChanged(Game.Table.Trash, new DominionBase.Piles.PileChangedEventArgs(Operation.Refresh)); Game.GameMessage += Game_GameMessage; foreach (var player in Game.Players.FindAll(p => p.PlayerType == PlayerType.Human)) player.Choose = Player_Choose; _player = Game.Players.Any(player => player.PlayerType == PlayerType.Human) ? Game.Players.OfType().First() : null; var message = "Using the following cards"; if (Game.Settings.Preset != null) message = $"{message} from the preset \"{Game.Settings.Preset.Name}\""; glMain.Log($"{message}:"); var kingdomSupplies = Game.Table.TableEntities.Values.Where(s => s.Randomizer.Location == Location.Kingdom).OrderBy(s => s.Name); glMain.Log(" ", kingdomSupplies); var sidewaysSupplies = Game.Table.TableEntities.Values.Where(s => s.Randomizer.Location == Location.LandscapeCard).OrderBy(s => s.Name); if (sidewaysSupplies.Any()) glMain.Log(" ", sidewaysSupplies); var prosperityPiles = Game.Table.TableEntities.Count(kvp => kvp.Value.Location == Location.Kingdom && kvp.Value.Source == Source.Prosperity); var darkAgesPiles = Game.Table.TableEntities.Count(kvp => kvp.Value.Location == Location.Kingdom && kvp.Value.Source == Source.DarkAges); var kingdomPiles = Game.Table.TableEntities.Count(kvp => kvp.Value.Location == Location.Kingdom && kvp.Value.Tokens.Count(t => t is DominionBase.Cards.Cornucopia.BaneMarker) == 0); glMain.Log( $"Prosperity Kingdom card ratio is {prosperityPiles}/{kingdomPiles} = {(float)prosperityPiles / kingdomPiles:P0}"); glMain.Log( $" Colony / Platinum {(Game.Settings.ColonyPlatinumSelected == ColonyPlatinumSelected.Yes ? "" : "not ")}selected"); glMain.Log( $"Dark Ages Kingdom card ratio is {darkAgesPiles}/{kingdomPiles} = {(float)darkAgesPiles / kingdomPiles:P0}"); glMain.Log( $" Shelters {(Game.Settings.ShelterSelected == ShelterSelected.Yes ? "" : "not ")}selected"); glMain.Log("Turn order is: ", string.Join(", ", Game.Players.Select(p => p == _player ? $"{p.Name} (You)" : p.Name))); Type[] specialTypes = { DominionBase.Cards.Promotional.TypeClass.BlackMarketSupply, DominionBase.Cards.Cornucopia.TypeClass.PrizeSupply, DominionBase.Cards.Cornucopia2ndEdition.TypeClass.PrizeSupply, DominionBase.Cards.DarkAges.TypeClass.Madman, DominionBase.Cards.DarkAges.TypeClass.Mercenary, DominionBase.Cards.DarkAges.TypeClass.Spoils, DominionBase.Cards.DarkAges2ndEdition.TypeClass.Madman, DominionBase.Cards.DarkAges2ndEdition.TypeClass.Mercenary, DominionBase.Cards.Adventures.TypeClass.TreasureHunter, DominionBase.Cards.Adventures.TypeClass.Warrior, DominionBase.Cards.Adventures.TypeClass.Hero, DominionBase.Cards.Adventures.TypeClass.Champion, DominionBase.Cards.Adventures.TypeClass.Soldier, DominionBase.Cards.Adventures.TypeClass.Fugitive, DominionBase.Cards.Adventures.TypeClass.Disciple, DominionBase.Cards.Adventures.TypeClass.Teacher, DominionBase.Cards.Adventures2ndEdition.TypeClass.TreasureHunter, DominionBase.Cards.Adventures2ndEdition.TypeClass.Warrior, DominionBase.Cards.Adventures2ndEdition.TypeClass.Hero, DominionBase.Cards.Adventures2ndEdition.TypeClass.Champion, DominionBase.Cards.Nocturne.TypeClass.Boons, DominionBase.Cards.Nocturne.TypeClass.DruidBoons, DominionBase.Cards.Nocturne.TypeClass.Hexes, DominionBase.Cards.Nocturne.TypeClass.Bat, DominionBase.Cards.Nocturne.TypeClass.Ghost, DominionBase.Cards.Nocturne.TypeClass.Imp, DominionBase.Cards.Nocturne.TypeClass.WillOWisp, DominionBase.Cards.Nocturne.TypeClass.Wish, DominionBase.Cards.Menagerie.TypeClass.Horse }; foreach (var specialType in specialTypes) { if (!Game.Table.SpecialPiles.ContainsKey(specialType)) continue; _matEventHandlers[specialType] = GamePile_PileChanged; Game.Table.SpecialPiles[specialType].PileChanged += _matEventHandlers[specialType]; GamePile_PileChanged(Game.Table.SpecialPiles[specialType], new DominionBase.Piles.PileChangedEventArgs(Operation.Refresh)); } if (Game.Table.SpecialPiles.ContainsKey(DominionBase.Cards.Nocturne.TypeClass.Boons)) ((IBoonSupply)Game.Table.SpecialPiles[DominionBase.Cards.Nocturne.TypeClass.Boons]).Shuffled += SpecialPile_Shuffle; if (Game.Table.SpecialPiles.ContainsKey(DominionBase.Cards.Nocturne.TypeClass.Hexes)) ((IHexSupply)Game.Table.SpecialPiles[DominionBase.Cards.Nocturne.TypeClass.Hexes]).Shuffled += SpecialPile_Shuffle; if (Game.Table.TableEntities.ContainsKey(DominionBase.Cards.Prosperity.TypeClass.TradeRoute) || Game.Table.TableEntities.ContainsKey(DominionBase.Cards.Prosperity2ndEdition.TypeClass.TradeRoute)) { if (dpGameStuff.Children.Count > 0) { var bDiv = new Border { BorderThickness = new Thickness(2), BorderBrush = Brushes.Black }; Panel.SetZIndex(bDiv, 1); DockPanel.SetDock(bDiv, Dock.Left); dpGameStuff.Children.Add(bDiv); } var lTradeRoute = new Label { Content = "Trade Route Tokens:", FontSize = 16d, FontWeight = FontWeights.Bold, HorizontalContentAlignment = HorizontalAlignment.Right, Background = Caching.BrushRepository.GetBackgroundBrush(Categories.Treasure) }; DockPanel.SetDock(lTradeRoute, Dock.Left); dpGameStuff.Children.Add(lTradeRoute); _tradeRouteLabel = new Label { Content = "0", FontWeight = FontWeights.Bold, VerticalAlignment = VerticalAlignment.Stretch, VerticalContentAlignment = VerticalAlignment.Center, Background = Caching.BrushRepository.GetBackgroundBrush(Categories.Treasure), Padding = new Thickness(0, 0, 5, 0), BorderThickness = new Thickness(0, 0, 1, 0) }; DockPanel.SetDock(_tradeRouteLabel, Dock.Left); dpGameStuff.Children.Add(_tradeRouteLabel); } bStuffDivider.Visibility = dpGameStuff.Children.Count > 0 ? Visibility.Visible : Visibility.Collapsed; foreach (var player in Game.Players) { var tiPlayer = new TabItem(); var dpHeader = new DockPanel(); var iHeader = new Image { Stretch = Stretch.None, Margin = new Thickness(0, 0, 5, 0) }; DockPanel.SetDock(iHeader, Dock.Left); switch (player.PlayerType) { case PlayerType.Human: iHeader.Source = (BitmapImage)Resources["imHuman"]; break; case PlayerType.Computer: iHeader.Source = (BitmapImage)Resources["imComputer"]; break; } dpHeader.Children.Add(iHeader); var tbHeader = new TextBlock { Text = player.Name }; dpHeader.Children.Add(tbHeader); var iFooter = new Image { Visibility = Visibility.Hidden, Stretch = Stretch.None, Margin = new Thickness(5, 0, 0, 0) }; DockPanel.SetDock(iFooter, Dock.Right); iFooter.Source = (BitmapImage)Resources["imEffects"]; iFooter.ToolTip = $"{player} has an Attack card in play"; dpHeader.Children.Add(iFooter); tiPlayer.Header = dpHeader; tcAreas.Items.Add(tiPlayer); var ucpdPlayer = new UcPlayerDisplay(); tiPlayer.Content = ucpdPlayer; ucpdPlayer.IsUIPlayer = player == _player; ucpdPlayer.Player = player; var playerSettings = _settings.PlayerSettings.FirstOrDefault(ps => ps.Name == player.Name); if (playerSettings != null) { var hlsValue = HlsColor.RgbToHls(playerSettings.UIColor); var cPlayer = HlsColor.HlsToRgb(hlsValue.H, hlsValue.L * 1.125, hlsValue.S * 0.95, hlsValue.A); var gsc = new GradientStopCollection { new GradientStop(cPlayer, 0), new GradientStop(playerSettings.UIColor, 0.25), new GradientStop(playerSettings.UIColor, 0.75), new GradientStop(cPlayer, 1) }; gsc.Freeze(); tiPlayer.Background = new LinearGradientBrush(gsc, 0); //tiPlayer.Background = new SolidColorBrush(playerSettings.UIColor); ucpdPlayer.ColorFocus = playerSettings.UIColor; ucpdPlayer.DisplayVictoryPoints = _settings.DisplayVictoryPoints; //if (WinApplication.Current.Properties["Update"] == null) ucpdPlayer.BackgroundFocus = playerSettings.BackgroundBrush as ImageBrush; ucpdPlayer.ForegroundFocus = playerSettings.ForegroundBrush; } var tt = new ToolTip(); var ucpo = new UcPlayerOverview { Player = player }; tt.Content = ucpo; ToolTipService.SetToolTip(dpHeader, tt); if (Settings.ToolTipShowDuration == ToolTipShowDuration.Off) ToolTipService.SetIsEnabled(dpHeader, false); else { ToolTipService.SetIsEnabled(dpHeader, true); ToolTipService.SetShowDuration(dpHeader, (int)Settings.ToolTipShowDuration); } dpHeader.MouseDown += TiPlayer_MouseDown; dpHeader.MouseUp += TiPlayer_MouseUp; player.Revealed.PileChanged += Revealed_PileChanged; player.SetAside.PileChanged += SetAside_PileChanged; player.BenefitReceivingInitiated += Player_BenefitReceiving; //player.DiscardPile.PileChanged += DiscardPile_PileChanged; player.CardPlaying += Player_CardPlaying; player.CardPlayFinished += Player_CardPlayed; player.CardUndoPlaying += Player_CardUndoPlaying; player.CardUndoPlayed += Player_CardUndoPlayed; player.CardBuying += Player_CardBuying; player.CardBought += Player_CardBought; player.CardBuyFinished += Player_CardBuyFinished; player.CardGaining += Player_CardGaining; player.CardGainedInto += Player_CardGainedInto; player.CardGainFinished += Player_CardGainFinished; player.TokenPlaying += Player_TokenPlaying; player.TokenPlayed += Player_TokenPlayed; player.Trashing += Player_Trashing; player.TrashedFinished += Player_Trashed; player.Calling += Player_Calling; player.CalledFinished += Player_Called; player.PhaseChangedFinished += Player_PhaseChangedEvent; player.PlayerModeChanged += Player_PlayerModeChangedEvent; player.CardsDrawn += Player_CardsDrawn; player.TurnStarting += Player_TurnStarting; player.TurnEnded += Player_TurnEnded; player.ShufflingStart += Player_ShufflingStart; player.CardsAddedToDeck += Player_CardsAddedToDeck; player.CardsAddedToHand += Player_CardsAddedToHand; player.CardsDiscarded += Player_CardsDiscarded; player.PlayerMats.CardMatsChanged += PlayerMats_DecksChanged; player.TokenPiles.TokenCollectionsChanged += PlayerTokenPiles_TokenCollectionsChanged; player.BenefitsChanged += Player_BenefitsChanged; player.CardFlippedOver += Player_CardFlippedOver; player.TakeablesChanged += Player_TakeablesChanged; if (player == _player) { tcAreas.SelectedItem = tiPlayer; player.CardReceived += Player_CardReceived; } } Game.FinalizeSetup(); LayoutSupplyPiles(); miNewGame.IsEnabled = false; miLoadGame.IsEnabled = false; miEndGame.IsEnabled = true; miSaveGame.IsEnabled = false; _gameThread = new Thread(Game.StartAsync); _gameThread.Start(); UpdateDisplay(); } private void LayoutSupplyPiles() { stackPanelSupplyPiles.Children.Clear(); ucGameLog gameLog = null; if (tiGameLog.Visibility == Visibility.Visible) { gameLog = tiGameLog.Content as ucGameLog; tiGameLog.Content = null; tiGameLog.Visibility = Visibility.Collapsed; } else if (dpGameInfo.Children.OfType().FirstOrDefault() != null) { gameLog = dpGameInfo.Children.OfType().FirstOrDefault(); dpGameInfo.Children.Remove(gameLog); } switch (_settings.GameLogLocation) { case GameLogLocation.InCommonArea: dpGameInfo.Children.Add(gameLog); break; case GameLogLocation.InGameTabArea: tiGameLog.Visibility = Visibility.Visible; tiGameLog.Content = gameLog; break; } if (Game?.Table == null) return; var pilesPerColumn = 0; switch (_settings.LayoutStyle) { case LayoutStyle.Supply2Columns: pilesPerColumn = Math.Max( Game.Table.TableEntities.Count(skv => skv.Value.Location == Location.Kingdom), Game.Table.TableEntities.Count(skv => skv.Value.Location == Location.General || skv.Value.Location == Location.LandscapeCard)); break; case LayoutStyle.Supply4Columns: pilesPerColumn = Math.Max( (Game.Table.TableEntities.Count(skv => skv.Value.Location == Location.Kingdom) + 1) / 2, (Game.Table.TableEntities.Count(skv => skv.Value.Location == Location.General || skv.Value.Location == Location.LandscapeCard) + 1) / 2); break; } var spFirstAction = new StackPanel { FlowDirection = FlowDirection.LeftToRight, HorizontalAlignment = HorizontalAlignment.Stretch, Margin = new Thickness(0, 0, 4, 0) }; stackPanelSupplyPiles.Children.Add(spFirstAction); var borderSupply = new Border { BorderThickness = new Thickness(1), BorderBrush = Brushes.DarkSlateBlue }; stackPanelSupplyPiles.Children.Add(borderSupply); var spFirstGeneral = new StackPanel { FlowDirection = FlowDirection.LeftToRight, HorizontalAlignment = HorizontalAlignment.Stretch, Margin = new Thickness(0, 0, 4, 0) }; stackPanelSupplyPiles.Children.Add(spFirstGeneral); var spCurrentAction = spFirstAction; var spCurrentGeneral = spFirstGeneral; foreach (var supplyType in Game.Table.TableEntityKeysOrdered) { StackPanel sp; switch (Game.Table.TableEntities[supplyType].Location) { case Location.General: case Location.LandscapeCard: sp = spCurrentGeneral; if (_settings.LayoutStyle == LayoutStyle.Supply4Columns && sp.Children.Count > 0 && sp.Children.OfType().Last().Supply.Category.HasFlag(Categories.Curse)) { sp = new StackPanel { FlowDirection = FlowDirection.LeftToRight, HorizontalAlignment = HorizontalAlignment.Stretch, Margin = new Thickness(0, 0, 4, 0) }; borderSupply = new Border { BorderThickness = new Thickness(1), BorderBrush = Brushes.DarkSlateBlue }; stackPanelSupplyPiles.Children.Add(borderSupply); stackPanelSupplyPiles.Children.Add(sp); spCurrentGeneral = sp; } break; case Location.Kingdom: sp = spCurrentAction; if (sp.Children.OfType().Count() >= pilesPerColumn) { sp = new StackPanel { FlowDirection = FlowDirection.LeftToRight, HorizontalAlignment = HorizontalAlignment.Stretch, Margin = new Thickness(0, 0, 4, 0) }; borderSupply = new Border { BorderThickness = new Thickness(1), BorderBrush = Brushes.DarkSlateBlue }; stackPanelSupplyPiles.Children.Insert(stackPanelSupplyPiles.Children.IndexOf(spFirstGeneral), sp); stackPanelSupplyPiles.Children.Insert(stackPanelSupplyPiles.Children.IndexOf(spFirstGeneral), borderSupply); spCurrentAction = sp; } break; default: continue; } var newSC = new SupplyControl(); sp.Children.Add(newSC); var previousSCIndex = sp.Children.Count - 1; while (previousSCIndex >= 0 && !(sp.Children[previousSCIndex--] is SupplyControl)) ; newSC.HorizontalAlignment = HorizontalAlignment.Stretch; newSC.Width = sp.Width; newSC.Supply = Game.Table.TableEntities[supplyType]; if (previousSCIndex >= 0) { if ((Game.Table.TableEntities[supplyType].Location == Location.General && ((SupplyControl)sp.Children[previousSCIndex]).Supply.Category != newSC.Supply.Category) || (Game.Table.TableEntities[supplyType].Location == Location.Kingdom && ((DominionBase.Piles.Supply)((SupplyControl)sp.Children[previousSCIndex]).Supply).Randomizer.BaseCost != ((DominionBase.Piles.Supply)newSC.Supply).Randomizer.BaseCost) || (Game.Table.TableEntities[supplyType].Location == Location.LandscapeCard && ((SupplyControl)sp.Children[previousSCIndex]).Supply.Location != newSC.Supply.Location) ) { borderSupply = new Border { Margin = new Thickness(15, 0, 15, 0), BorderThickness = new Thickness(1), BorderBrush = Brushes.LightSkyBlue }; sp.Children.Insert(sp.Children.Count - 1, borderSupply); } } } CheckBuyable(Game.ActivePlayer); stackPanelSupplyPiles.InvalidateVisual(); rdGrid0.Height = new GridLength(stackPanelSupplyPiles.ActualHeight + 5); rdGrid0.Height = GridLength.Auto; } private void TiPlayer_MouseDown(object sender, MouseButtonEventArgs e) { if (Settings != null && Settings.ShowToolTipOnRightClick && e.ChangedButton == MouseButton.Right && e.ButtonState == MouseButtonState.Pressed) { (sender as UIElement)?.CaptureMouse(); var element = sender as FrameworkElement; var tt = element?.ToolTip as ToolTip; if (tt?.Content is UcPlayerOverview ucpo) ucpo.Turn = Game.TurnsTaken.LastOrDefault(t => t.Player == ((element.Parent as ContentControl)?.Content as UcPlayerDisplay)?.Player && t != Game.CurrentTurn); if (tt != null) tt.IsOpen = true; } } private static void TiPlayer_MouseUp(object sender, MouseButtonEventArgs e) { if (Settings != null && Settings.ShowToolTipOnRightClick && e.ChangedButton == MouseButton.Right && e.ButtonState == MouseButtonState.Released) { (sender as UIElement)?.ReleaseMouseCapture(); if ((sender as FrameworkElement)?.ToolTip is ToolTip tt) tt.IsOpen = false; } } private void Game_GameMessage(object sender, GameMessageEventArgs e) { if (Dispatcher.CheckAccess()) { if (e.SourceCard != null) { var type = e.SourceCard.Type; if ((type == DominionBase.Cards.Base.TypeClass.Chancellor || type == DominionBase.Cards.Cornucopia.TypeClass.TrustySteed || type == DominionBase.Cards.Cornucopia2ndEdition.TypeClass.TrustySteed || type == DominionBase.Cards.DarkAges.TypeClass.Scavenger || type == DominionBase.Cards.DarkAges2ndEdition.TypeClass.Scavenger || type == DominionBase.Cards.Adventures.TypeClass.Messenger || (type == DominionBase.Cards.Nocturne.TypeClass.BadOmens && e.MessageType == "Discard")) && !e.Cards.Any()) { if (e.Player == _player) { glMain.Log(e.Player, "You put your deck into your discard pile"); } else { glMain.Log(e.Player, e.Player, " puts deck into discard pile"); } } else if (type == DominionBase.Cards.Intrigue.TypeClass.Bridge || type == DominionBase.Cards.Intrigue2ndEdition.TypeClass.Bridge || type == DominionBase.Cards.Prosperity.TypeClass.Quarry || type == DominionBase.Cards.Cornucopia.TypeClass.Princess || type == DominionBase.Cards.Hinterlands.TypeClass.Highway || type == DominionBase.Cards.Adventures.TypeClass.BridgeTroll || type == DominionBase.Cards.Adventures2ndEdition.TypeClass.BridgeTroll || type == DominionBase.Cards.Renaissance.TypeClass.Inventor || type == DominionBase.Cards.Renaissance.TypeClass.Canal ) { var types = string.Empty; if (type == DominionBase.Cards.Prosperity.TypeClass.Quarry) types = "Action "; glMain.Log(e.Player, e.SourceCard, " reduces the cost of all ", types, "cards by ", new DominionBase.Currencies.Coin(e.Count)); } else if (type == DominionBase.Cards.Intrigue.TypeClass.Masquerade || type == DominionBase.Cards.Intrigue2ndEdition.TypeClass.Masquerade) { string postText = $" to the left ({e.AffectedPlayer})"; if (e.Player == _player) glMain.Log(e.Player, "You pass ", e.Cards[0], postText); else glMain.Log(e.Player, e.Player, " passes a card", postText); } else if ((type == DominionBase.Cards.Intrigue.TypeClass.WishingWell || type == DominionBase.Cards.Intrigue2ndEdition.TypeClass.WishingWell || type == DominionBase.Cards.DarkAges.TypeClass.Mystic || type == DominionBase.Cards.DarkAges.TypeClass.Rebuild || type == DominionBase.Cards.DarkAges2ndEdition.TypeClass.Mystic || type == DominionBase.Cards.DarkAges2ndEdition.TypeClass.Rebuild || type == DominionBase.Cards.Guilds.TypeClass.Doctor || type == DominionBase.Cards.Guilds.TypeClass.Journeyman || type == DominionBase.Cards.Guilds2ndEdition.TypeClass.Doctor || type == DominionBase.Cards.Guilds2ndEdition.TypeClass.Journeyman ) && e.Cards.Any() ) { glMain.Log( e.Player, e.Player == _player ? (object)"You" : (object)e.Player, $" name{(e.Player == _player ? "" : "s")} ", e.Cards[0]); } else if (type == DominionBase.Cards.Seaside.TypeClass.Ambassador || type == DominionBase.Cards.Seaside2ndEdition.TypeClass.Ambassador ) { glMain.Log( e.Player, e.Player == _player ? (object)"You" : (object)e.Player, $" return{(e.Player == _player ? "" : "s")} {StringUtility.Plural("card", e.Cards.Count)} to the ", e.Cards.FirstOrDefault(), " supply pile"); } else if (type == DominionBase.Cards.Seaside.TypeClass.Embargo || type == DominionBase.Cards.Seaside2ndEdition.TypeClass.Embargo //|| type == DominionBase.Cards.Seaside2019Errata.TypeClass.Embargo ) { glMain.Log( e.Player, e.Player == _player ? (object)"You" : (object)e.Player, $" put{(e.Player == _player ? "" : "s")} {e.SourceCard.Name} token on ", e.Cards.FirstOrDefault()); } else if (type == DominionBase.Cards.Empires.TypeClass.Tax) { glMain.Log( e.Player, e.Player == _player ? (object)"You" : (object)e.Player, $" put{(e.Player == _player ? "" : "s")} 2 Debt tokens on ", e.Cards.FirstOrDefault()); } else if (type == DominionBase.Cards.Seaside.TypeClass.Haven || type == DominionBase.Cards.Seaside2ndEdition.TypeClass.Haven || type == DominionBase.Cards.Adventures.TypeClass.Gear || type == DominionBase.Cards.Adventures2ndEdition.TypeClass.Gear || type == DominionBase.Cards.Empires.TypeClass.Archive || type == DominionBase.Cards.Nocturne.TypeClass.Crypt || type == DominionBase.Cards.Renaissance.TypeClass.Research ) { if (e.Player == _player) glMain.Log(e.Player, "You set aside ", e.Cards.Any() ? e.Cards : (object)"nothing"); else glMain.Log(e.Player, e.Player, " sets aside ", StringUtility.Plural("card", e.Cards.Count, true)); } else if (type == DominionBase.Cards.Promotional.TypeClass.Prince || type == DominionBase.Cards.Nocturne.TypeClass.Ghost || type == DominionBase.Cards.Renaissance.TypeClass.CargoShip ) { if (e.MessageType == "SetAside") { if (e.Player == _player) glMain.Log(e.Player, "You set aside ", e.Cards.FirstOrDefault(), " on ", e.SourceCard); else glMain.Log(e.Player, e.Player, " sets aside ", e.Cards, " on ", e.SourceCard); } else if (e.MessageType == "Retrieve") { if (e.Player == _player) glMain.Log(e.Player, "You retrieve ", e.Cards.FirstOrDefault(), " from ", e.SourceCard); else glMain.Log(e.Player, e.Player, " retrieves ", e.Cards, " from ", e.SourceCard); } } else if (type == DominionBase.Cards.Adventures.TypeClass.Save || type == DominionBase.Cards.Adventures2ndEdition.TypeClass.Save ) { if (e.MessageType == "SetAside") { if (e.Player == _player) glMain.Log(e.Player, "You set aside ", e.Cards.FirstOrDefault(), " on ", e.SourceCard); else glMain.Log(e.Player, e.Player, " sets aside ", StringUtility.Plural("card", e.Cards.Count), " on ", e.SourceCard); } else if (e.MessageType == "Retrieve") { if (e.Player == _player) glMain.Log(e.Player, "You retrieve ", e.Cards.FirstOrDefault(), " from ", e.SourceCard); else glMain.Log(e.Player, e.Player, " retrieves ", StringUtility.Plural("card", e.Cards.Count), " from ", e.SourceCard); } } //else if (type == DominionBase.Cards.Adventures.TypeClass.Inheritance) //{ // if (e.Count == 1) // { // if (e.Player == _Player) // glMain.Log(e.Player, "You set aside ", e.Cards.FirstOrDefault()); // else // glMain.Log(e.Player, e.Player, " sets aside ", e.Cards.FirstOrDefault()); // } // else // { // if (e.Player == _Player) // glMain.Log(e.Player, "You retrieve ", e.Cards.FirstOrDefault()); // else // glMain.Log(e.Player, e.Player, " retrieves ", e.Cards.FirstOrDefault()); // } //} else if (type == DominionBase.Cards.Empires.TypeClass.Encampment || type == DominionBase.Cards.Nocturne.TypeClass.FaithfulHound) { var targetCard = e.Cards.FirstOrDefault() ?? e.SourceCard; switch (e.MessageType) { case "SetAside": if (e.Player == _player) glMain.Log(e.Player, "You set aside ", targetCard); else glMain.Log(e.Player, e.Player, " sets aside ", targetCard); break; case "Return": glMain.Log( e.Player, e.Player == _player ? (object)"You" : (object)e.Player, $" return{(e.Player == _player ? "" : "s")} {StringUtility.Plural("card", e.Cards.Count)} to the ", targetCard, " supply pile"); break; case "PutIntoHand": if (e.Player == _player) glMain.Log(e.Player, "You put ", targetCard, " into your hand"); else glMain.Log(e.Player, e.Player, " puts ", targetCard, " into hand"); break; } } else if (type == DominionBase.Cards.Seaside.TypeClass.Lighthouse || type == DominionBase.Cards.Seaside2ndEdition.TypeClass.Lighthouse || type == DominionBase.Cards.Adventures.TypeClass.Champion || type == DominionBase.Cards.Adventures2ndEdition.TypeClass.Champion || type == DominionBase.Cards.Nocturne.TypeClass.Guardian ) { glMain.Log( e.Player, e.Player == _player ? (object)"Your" : (object)e.Player, e.Player == _player ? " " : "'s ", ((Card)e.SourceCard).PhysicalCard, " provides immunity to the attack."); } else if (type == DominionBase.Cards.Prosperity.TypeClass.Contraband || type == DominionBase.Cards.DarkAges.TypeClass.BandOfMisfits || type == DominionBase.Cards.DarkAges2ndEdition.TypeClass.BandOfMisfits //|| type == DominionBase.Cards.DarkAges2019Errata.TypeClass.BandOfMisfits || type == DominionBase.Cards.Empires.TypeClass.Overlord //|| type == DominionBase.Cards.Empires2019Errata.TypeClass.Overlord ) { glMain.Log( e.Player, e.Player == _player ? (object)"You" : (object)e.Player, $" name{(e.Player == _player ? "" : "s")} ", e.Cards.Any() ? (object)e.Cards[0] : (object)"nothing"); } else if (type == DominionBase.Cards.Hinterlands.TypeClass.Trader || type == DominionBase.Cards.Hinterlands2ndEdition.TypeClass.Trader ) { glMain.Log( e.Player, e.Player == _player ? (object)"You" : (object)e.Player, $" gain{(e.Player == _player ? "" : "s")} ", e.Cards[1], " instead of ", e.Cards[0]); } else if (type == DominionBase.Cards.Guilds.TypeClass.Butcher || type == DominionBase.Cards.Guilds2ndEdition.TypeClass.Butcher ) { glMain.Log( e.Player, e.Player == _player ? (object)"You" : (object)e.Player, $" spend{(e.Player == _player ? "" : "s")} {StringUtility.Plural("Coffer", e.Count)}"); } else if (type == DominionBase.Cards.Adventures.TypeClass.Page || type == DominionBase.Cards.Adventures.TypeClass.TreasureHunter || type == DominionBase.Cards.Adventures.TypeClass.Warrior || type == DominionBase.Cards.Adventures.TypeClass.Hero || type == DominionBase.Cards.Adventures.TypeClass.Peasant || type == DominionBase.Cards.Adventures.TypeClass.Soldier || type == DominionBase.Cards.Adventures.TypeClass.Fugitive || type == DominionBase.Cards.Adventures.TypeClass.Disciple || type == DominionBase.Cards.Adventures2ndEdition.TypeClass.Page || type == DominionBase.Cards.Adventures2ndEdition.TypeClass.TreasureHunter || type == DominionBase.Cards.Adventures2ndEdition.TypeClass.Warrior || type == DominionBase.Cards.Adventures2ndEdition.TypeClass.Hero || type == DominionBase.Cards.Nocturne.TypeClass.Bat || type == DominionBase.Cards.Nocturne.TypeClass.Changeling || type == DominionBase.Cards.Nocturne.TypeClass.Vampire) { glMain.Log( e.Player, e.Player == _player ? (object)"You" : (object)e.Player, " exchange", e.Player == _player ? " " : "s ", e.Cards[0], " for ", e.Cards[1]); } else if (type == DominionBase.Cards.Adventures.TypeClass.Teacher || type == DominionBase.Cards.Adventures.TypeClass.Ferry || type == DominionBase.Cards.Adventures.TypeClass.LostArts || type == DominionBase.Cards.Adventures.TypeClass.Pathfinding || type == DominionBase.Cards.Adventures.TypeClass.Plan || type == DominionBase.Cards.Adventures.TypeClass.Seaway || type == DominionBase.Cards.Adventures.TypeClass.Training || type == DominionBase.Cards.Adventures2ndEdition.TypeClass.Ferry || type == DominionBase.Cards.Adventures2ndEdition.TypeClass.LostArts || type == DominionBase.Cards.Adventures2ndEdition.TypeClass.Pathfinding || type == DominionBase.Cards.Adventures2ndEdition.TypeClass.Plan || type == DominionBase.Cards.Adventures2ndEdition.TypeClass.Seaway || type == DominionBase.Cards.Adventures2ndEdition.TypeClass.Training ) { var token = e.Tokens.FirstOrDefault(); if (e.Player == _player) glMain.Log(e.Player, "You move your ", token, " to ", e.Cards[0]); else glMain.Log(e.Player, e.Player, " moves their ", token, " to ", e.Cards[0]); } else if (type == DominionBase.Cards.Empires.TypeClass.MountainPass) { if (e.Currency == (Currency)null) { if (e.Player == _player) glMain.Log(e.Player, "You pass on ", e.SourceCard); else glMain.Log(e.Player, e.Player, " passes on ", e.SourceCard); } else { if (e.Player == _player) glMain.Log(e.Player, "You bid ", e.Currency, " on ", e.SourceCard); else glMain.Log(e.Player, e.Player, " bids ", e.Currency, " on ", e.SourceCard); } } else if (type == DominionBase.Cards.Seaside.TypeClass.Outpost || type == DominionBase.Cards.Seaside2ndEdition.TypeClass.Outpost || type == DominionBase.Cards.Adventures.TypeClass.Expedition || type == DominionBase.Cards.Renaissance.TypeClass.Flag ) { if (e.MessageType != "Taken" && e.MessageType != "Returned") glMain.Log( e.Player, e.Player == _player ? (object)"You" : (object)e.Player, $" will draw {StringUtility.Plural("card", e.Count)} for next turn from ", e.SourceCard ); } else if (type == DominionBase.Cards.Intrigue2ndEdition.TypeClass.SecretPassage) { if (e.Player == _player) glMain.Log(e.Player, "You place ", e.Cards[0], " in your deck"); else glMain.Log(e.Player, e.Player, " places a card in their deck"); } else if ( //type == DominionBase.Cards.Nocturne.TypeClass.BadOmens //|| type == DominionBase.Cards.Nocturne.TypeClass.Delusion //|| type == DominionBase.Cards.Nocturne.TypeClass.Envy //|| type == DominionBase.Cards.Nocturne.TypeClass.Famine //|| type == DominionBase.Cards.Nocturne.TypeClass.Fear //|| type == DominionBase.Cards.Nocturne.TypeClass.Greed //|| type == DominionBase.Cards.Nocturne.TypeClass.Haunting //|| type == DominionBase.Cards.Nocturne.TypeClass.Locusts //|| type == DominionBase.Cards.Nocturne.TypeClass.Misery //|| type == DominionBase.Cards.Nocturne.TypeClass.Plague //|| type == DominionBase.Cards.Nocturne.TypeClass.Poverty //|| type == DominionBase.Cards.Nocturne.TypeClass.TheEarthsGift || type == DominionBase.Cards.Nocturne.TypeClass.TheFieldsGift || type == DominionBase.Cards.Nocturne.TypeClass.TheFlamesGift || type == DominionBase.Cards.Nocturne.TypeClass.TheForestsGift || type == DominionBase.Cards.Nocturne.TypeClass.TheMoonsGift || type == DominionBase.Cards.Nocturne.TypeClass.TheMountainsGift || type == DominionBase.Cards.Nocturne.TypeClass.TheRiversGift || type == DominionBase.Cards.Nocturne.TypeClass.TheSeasGift || type == DominionBase.Cards.Nocturne.TypeClass.TheSkysGift || type == DominionBase.Cards.Nocturne.TypeClass.TheSunsGift || type == DominionBase.Cards.Nocturne.TypeClass.TheSwampsGift || type == DominionBase.Cards.Nocturne.TypeClass.TheWindsGift //|| type == DominionBase.Cards.Nocturne.TypeClass.War ) { var verb = "receive"; if (e.MessageType == "Return") verb = "return"; if (e.Player == _player) glMain.Log(e.Player, $"You {verb} ", e.SourceCard); else glMain.Log(e.Player, e.Player, $" {verb}s ", e.SourceCard); } //else if (type == DominionBase.Cards.Nocturne.TypeClass.Deluded // || type == DominionBase.Cards.Nocturne.TypeClass.Envious // || type == DominionBase.Cards.Nocturne.TypeClass.LostInTheWoods) //{ // var verb = "take"; // if ((string)e.Data == "Returned") // verb = "return"; // if (e.Player == _player) // glMain.Log(e.Player, $"You {verb} ", e.SourceCard); // else // glMain.Log(e.Player, e.Player, $" {verb}s ", e.SourceCard); //} else { switch (e.MessageType) { case "AddToken": if (e.Player == _player) glMain.Log(e.Player, $"You add a token to {e.SourceCard}"); else glMain.Log(e.Player, e.Player, $" adds a token to {e.SourceCard}"); break; case "RemoveAllTokens": if (e.Player == _player) glMain.Log(e.Player, $"You remove all tokens from {e.SourceCard}"); else glMain.Log(e.Player, e.Player, $" removes all tokens from {e.SourceCard}"); break; case "Retrieve": if (e.Player == _player) glMain.Log(e.Player, "You retrieve ", e.Cards.FirstOrDefault(), " from ", e.SourceCard); else glMain.Log(e.Player, e.Player, " retrieves ", e.Cards, " from ", e.SourceCard); break; case "Receive": if (e.Player == _player) glMain.Log(e.Player, "You receive ", e.SourceCard); else glMain.Log(e.Player, e.Player, " receives ", e.SourceCard); break; case "Return": if (e.Player == _player) glMain.Log(e.Player, "You return ", e.Cards.FirstOrDefault(), " to its pile"); else glMain.Log(e.Player, e.Player, " returns ", e.Cards.FirstOrDefault(), " to its pile"); break; case "SetAside": if (e.Player == _player) glMain.Log(e.Player, "You set aside ", e.Cards.FirstOrDefault(), " on ", e.SourceCard); else glMain.Log(e.Player, e.Player, " sets aside ", e.Cards, " on ", e.SourceCard); break; case "ActionGainChanged": glMain.Log(e.Player, "Actions gained changed to ", e.Count.ToString(), " from ", e.SourceCard); break; case "Overpay": glMain.Log( e.Player, e.Player == _player ? (object)"You" : (object)e.Player, $" overpay{(e.Player == _player ? "" : "s")} by ", e.Currency); break; case "LookThroughDiscard": if (e.Player == _player) glMain.Log(e.Player, "You look through your discard pile, containing ", e.Cards.Any() ? e.Cards : (object)"nothing"); else glMain.Log(e.Player, e.Player, " looks through their discard pile"); break; case "Reveal": if (e.Player == _player) glMain.Log(e.Player, "You reveal, containing ", e.Cards.Any() ? e.Cards : (object)"nothing"); else glMain.Log(e.Player, e.Player, " reveals, containing ", e.Cards.Any() ? e.Cards : (object)"nothing"); break; case "Exile": if (e.Player == _player) glMain.Log(e.Player, "You exile ", e.Cards.Any() ? e.Cards : (object)"nothing"); else glMain.Log(e.Player, e.Player, " exiles ", e.Cards.Any() ? e.Cards : (object)"nothing"); break; case "PlayAs": glMain.Log(e.Player, " as ", e.Cards.FirstOrDefault()); break; case "ConvertCardsToCoins": glMain.Log(e.Player, "Converted ", $"+{e.Count}{(e.Count == 1 ? ResourcesHelper.Get("Card") : ResourcesHelper.Get("Cards"))}", " to ", $"{e.Count}"); break; case "ConvertCoinsToCards": glMain.Log(e.Player, "Converted ", $"{e.Count}", " to ", $"+{e.Count}{(e.Count == 1 ? ResourcesHelper.Get("Card") : ResourcesHelper.Get("Cards"))}"); break; } } } else if (e.SourceToken != null) { if (e.SourceToken is DominionBase.Cards.Adventures.JourneyToken jToken) { if (e.Player == _player) glMain.Log(e.Player, "You flip over your ", jToken, " to ", jToken.FaceShowing); else glMain.Log(e.Player, e.Player, " flips over ", e.Player, "'s ", jToken, " to ", jToken.FaceShowing); } } } else { Dispatcher.BeginInvoke(new EventHandler(Game_GameMessage), DispatcherPriority.Normal, sender, e); } } internal string DepthPrefix() { var sb = new StringBuilder(); for (var c = 0; c < CurrentPlayDepth; c++) sb.Append("... "); return sb.ToString(); } private void PlayerTokenPiles_TokenCollectionsChanged(object sender, TokenCollectionsChangedEventArgs e) { if (Dispatcher.CheckAccess()) { switch (e.OperationPerformed) { case TokenCollectionsChangedEventArgs.Operation.Added: if (e.AddedTokens[0] is DominionBase.Cards.Seaside.PirateShipToken) { glMain.Log( e.Player, e.Player == _player ? (object)"You" : (object)e.Player, $" gain{(e.Player == _player ? "" : "s")} a Pirate Ship token" ); } else if (e.AddedTokens[0] is DominionBase.Cards.Guilds.Coffer) { glMain.Log( e.Player, e.Player == _player ? (object)"You" : (object)e.Player, $" gain{(e.Player == _player ? "" : "s")} a Coffers" ); } else if (e.AddedTokens[0] is DominionBase.Cards.Adventures.MinusOneCardToken || e.AddedTokens[0] is DominionBase.Cards.Adventures.MinusOneCoinToken) { if (e.Player == _player) { glMain.Log( e.Player, "You take your ", e.AddedTokens[0].Name ); } else { glMain.Log( e.Player, e.Player, " takes ", e.Player, "'s ", e.AddedTokens[0].Name ); } } else if (e.AddedTokens[0] is DominionBase.Cards.Empires.DebtToken debtToken) { glMain.Log( e.Player, e.Player == _player ? (object)"You" : (object)e.Player, $" gain{(e.Player == _player ? "" : "s")} {StringUtility.Plural(debtToken.LongDisplayString, e.AddedTokens.Count)}" ); } else if (e.AddedTokens[0] is DominionBase.Cards.Renaissance.Villager) { glMain.Log( e.Player, e.Player == _player ? (object)"You" : (object)e.Player, $" gain{(e.Player == _player ? "" : "s")} a Villager" ); } break; case TokenCollectionsChangedEventArgs.Operation.Removed: if (e.RemovedTokens[0] is DominionBase.Cards.Adventures.MinusOneCardToken || e.RemovedTokens[0] is DominionBase.Cards.Adventures.MinusOneCoinToken) { if (e.Player == _player) { glMain.Log( e.Player, "You discard your ", e.RemovedTokens[0].Name ); } else { glMain.Log( e.Player, e.Player, " discards ", e.RemovedTokens[0].Name ); } } break; } } else { Dispatcher.BeginInvoke(new EventHandler(PlayerTokenPiles_TokenCollectionsChanged), DispatcherPriority.Normal, sender, e); } } private void PlayerMats_DecksChanged(object sender, DominionBase.Piles.CardMatsChangedEventArgs e) { if (Dispatcher.CheckAccess()) { if (e.CardMat is DominionBase.Cards.Seaside.IslandMat) { if (e.OperationPerformed == DominionBase.Piles.CardMatsChangedEventArgs.Operation.Added) { glMain.Log( e.Player, e.Player == _player ? (object)"You" : (object)e.Player, $" set{(e.Player == _player ? "" : "s")} aside ", e.AddedCards.Select(c => c.PhysicalCard), " on Island Mat"); } } else if (e.CardMat is DominionBase.Cards.Seaside.NativeVillageMat) { if (e.OperationPerformed == DominionBase.Piles.CardMatsChangedEventArgs.Operation.Added) { if (e.Player == _player) glMain.Log(e.Player, "You put ", e.AddedCards.Select(c => c.PhysicalCard), " on Native Village Mat"); else glMain.Log( e.Player, e.Player, $" puts {StringUtility.Plural("card", e.AddedCards.Count)} on Native Village Mat"); } else if (e.OperationPerformed == DominionBase.Piles.CardMatsChangedEventArgs.Operation.Removed && e.Player.Phase != PhaseEnum.Endgame) { if (e.Player == _player) { var nvTakenCards = e.RemovedCards.Select(c => c.PhysicalCard); glMain.Log(e.Player, "You take ", !nvTakenCards.Any() ? (object)"nothing" : (object)nvTakenCards, " from Native Village Mat"); } else glMain.Log( e.Player, e.Player, $" takes {StringUtility.Plural("card", e.RemovedCards.Count)} from Native Village Mat"); } } else if ( e.CardMat is DominionBase.Cards.Promotional.PrinceSetAside || e.CardMat is DominionBase.Cards.Promotional.SummonSetAside ) { if (e.OperationPerformed == DominionBase.Piles.CardMatsChangedEventArgs.Operation.Added && e.AddedCards.Count > 0) { glMain.Log( e.Player, e.Player == _player ? (object)"You" : (object)e.Player, $" set{(e.Player == _player ? "" : "s")} aside ", e.AddedCards.Select(c => c.PhysicalCard)); } if (e.OperationPerformed == DominionBase.Piles.CardMatsChangedEventArgs.Operation.Removed && e.RemovedCards.Count > 0) { glMain.Log( e.Player, e.Player == _player ? (object)"You" : (object)e.Player, $" retrieve{(e.Player == _player ? "" : "s")} ", e.RemovedCards.Select(c => c.PhysicalCard)); } } else if (e.CardMat is DominionBase.Cards.Adventures.TavernMat) { if (e.OperationPerformed == DominionBase.Piles.CardMatsChangedEventArgs.Operation.Added) { glMain.Log( e.Player, e.Player == _player ? (object)"You" : (object)e.Player, $" put{(e.Player == _player ? "" : "s")} ", e.AddedCards.Select(c => c.PhysicalCard), " on Tavern Mat"); } } } else { Dispatcher.BeginInvoke(new EventHandler(PlayerMats_DecksChanged), DispatcherPriority.Normal, sender, e); } } private void Player_CardsAddedToDeck(object sender, DominionBase.Players.CardsAddedToDeckEventArgs e) { if (Dispatcher.CheckAccess()) { var locationMod = string.Empty; if (e.DeckPosition == DominionBase.Piles.DeckPosition.Bottom) locationMod = "the "; if (e.Cards.Count == 0) return; if (sender == _player) glMain.Log( sender as DominionBase.Players.Player, "You put ", e.Cards.Select(c => c.PhysicalCard), $" on {locationMod}{e.DeckPosition.ToString().ToLower()} of your deck" ); else glMain.Log( sender as DominionBase.Players.Player, sender, string.Format(" puts {0} on {2}{1} of their deck", StringUtility.Plural("card", e.Cards.Count), e.DeckPosition.ToString().ToLower(), locationMod )); } else { Dispatcher.BeginInvoke(new EventHandler(Player_CardsAddedToDeck), DispatcherPriority.Normal, sender, e); } } private void Player_CardsAddedToHand(object sender, DominionBase.Players.CardsAddedToHandEventArgs e) { if (Dispatcher.CheckAccess()) { if (e.Cards.Count == 0) return; if (sender == _player) glMain.Log( sender as DominionBase.Players.Player, "You put ", e.Cards.Select(c => c.PhysicalCard), " into your hand"); else glMain.Log(sender as DominionBase.Players.Player, sender, $" puts {StringUtility.Plural("card", e.Cards.Count)} into their hand"); } else { Dispatcher.BeginInvoke(new EventHandler(Player_CardsAddedToHand), DispatcherPriority.Normal, sender, e); } } private void Player_CardsDiscarded(object sender, DominionBase.Players.CardsDiscardEventArgs e) { if (Dispatcher.CheckAccess()) { if (e.Cards.Count == 0 || e.HandledBy.Contains(this)) return; e.HandledBy.Add(this); var location = string.Empty; switch (e.FromLocation) { case DeckLocation.InPlay: case DeckLocation.SetAside: case DeckLocation.InPlayAndSetAside: return; case DeckLocation.Hand: case DeckLocation.Deck: location = $" from {(sender == _player ? "your" : "their")} {e.FromLocation.ToString().ToLower()}"; break; case DeckLocation.PlayerMat: location = $" from {((IPlayer)sender).PlayerMats[e.FromMatType].Name}"; break; } var name = sender == _player ? (object)"You" : (object)sender; string verb = $" discard{(sender == _player ? "" : "s")} "; if (e.Cards.Count == 1) glMain.Log(sender as DominionBase.Players.Player, name, verb, e.Cards.Select(c => c.PhysicalCard), location); else glMain.Log(sender as DominionBase.Players.Player, name, verb, $"{StringUtility.Plural("card", e.Cards.Count)}{location}"); } else { Dispatcher.BeginInvoke(new EventHandler(Player_CardsDiscarded), DispatcherPriority.Normal, sender, e); } } private void Player_ShufflingStart(object sender, DominionBase.Players.ShuffleEventArgs e) { if (Dispatcher.CheckAccess()) { glMain.Log( e.Player, "(", e.Player == _player ? (object)"You" : (object)e.Player, $" shuffle{(e.Player == _player ? "" : "s")}...)" ); } else { Dispatcher.BeginInvoke(new EventHandler(Player_ShufflingStart), DispatcherPriority.Normal, sender, e); } } private void SpecialPile_Shuffle(object sender, DominionBase.Players.ShuffleEventArgs e) { if (Dispatcher.CheckAccess()) { glMain.Log( e.Player, "(", e.Player == _player ? (object)"You" : (object)e.Player, $" shuffle{(e.Player == _player ? "" : "s")} {sender}...)" ); } else { Dispatcher.BeginInvoke(new EventHandler(SpecialPile_Shuffle), DispatcherPriority.Normal, sender, e); } } private void Player_BenefitReceiving(object sender, DominionBase.Players.BenefitReceiveVisualEventArgs e) { if (Dispatcher.CheckAccess()) { if (!e.Benefit.Any && !string.IsNullOrWhiteSpace(e.Benefit.FlavorText)) return; var sb = new StringBuilder(); sb.AppendFormat(" get{0}", _player != null && e.Player.PlayerUniqueId == _player.UniqueId ? "" : "s"); if (e.Benefit.Cards > 0) sb.AppendFormat(" +{0} card{1}", e.Benefit.Cards, e.Benefit.Cards == 1 ? "" : "s"); else if (e.Benefit.Cards < 0 && e.Benefit.Actions <= 0 && e.Benefit.Buys <= 0 && e.Benefit.Currency <= new Currency() && e.Benefit.VictoryPoints <= 0) return; if (e.Benefit.Actions > 0) sb.AppendFormat(" +{0}", StringUtility.Plural("action", e.Benefit.Actions)); else if (e.Benefit.Actions < 0) sb.AppendFormat(" {0}", StringUtility.Plural("action", e.Benefit.Actions)); if (e.Benefit.Buys > 0) sb.AppendFormat(" +{0}", StringUtility.Plural("buy", e.Benefit.Buys)); else if (e.Benefit.Buys < 0) sb.AppendFormat(" {0}", StringUtility.Plural("buy", e.Benefit.Buys)); if (e.Benefit.Currency > new Currency()) sb.AppendFormat(" +{0}", Utilities.RenderText(e.Benefit.Currency.ToString())); else if (e.Benefit.Currency < new Currency()) sb.AppendFormat(" {0}", Utilities.RenderText(e.Benefit.Currency.ToString())); if (e.Benefit.Coffers > 0) sb.AppendFormat(" +{0}", StringUtility.Plural("coffer", e.Benefit.Coffers)); else if (e.Benefit.Coffers < 0) sb.AppendFormat(" {0}", StringUtility.Plural("coffer", e.Benefit.Coffers)); if (e.Benefit.Villagers > 0) sb.AppendFormat(" +{0}", StringUtility.Plural("villager", e.Benefit.Villagers)); else if (e.Benefit.Villagers < 0) sb.AppendFormat(" {0}", StringUtility.Plural("villager", e.Benefit.Villagers)); if (e.Benefit.VictoryPoints > 0) sb.Append(Utilities.RenderText($" +{e.Benefit.VictoryPoints}")); else if (e.Benefit.VictoryPoints < 0) sb.Append(Utilities.RenderText($" -{-e.Benefit.VictoryPoints}")); sb.Append(e.Benefit.FlavorText); if (e.Phase == PhaseEnum.Starting || !_playStack.Any() || (sender is ICardBase sCardBase && _playStack.Peek().Type != sCardBase.Type) || sender is Token) { sb.Append(" from "); var from = sender; if (sender is ICardBase cardBase && cardBase.Type.IsSubclassOf(typeof(Card))) from = ((Card)sender).PhysicalCard; glMain.Log( e.Player, _player != null && e.Player.PlayerUniqueId == _player.UniqueId ? (object)"You" : (object)e.Player, sb.ToString(), from); } else glMain.Log( e.Player, _player != null && e.Player.PlayerUniqueId == _player.UniqueId ? (object)"You" : (object)e.Player, sb.ToString()); } else { Dispatcher.BeginInvoke(new EventHandler(Player_BenefitReceiving), DispatcherPriority.Normal, sender, e); } } private void Player_TurnStarting(object sender, DominionBase.Players.TurnStartingEventArgs e) { if (Dispatcher.CheckAccess()) { if (e.Player == _player) { dpStuff.IsEnabled = true; miSaveGame.IsEnabled = true; } else { dpStuff.IsEnabled = false; miSaveGame.IsEnabled = false; } // Just in case CurrentPlayDepth = 0; _playStack.Clear(); if (Game.Players[0] == e.Player && e.GrantedBy == null) glMain.NewTurn(Game.TurnsTaken.TurnNumber(e.Player)); glMain.NewTurn(e.Player, e.GrantedBy); } else { Dispatcher.BeginInvoke(new EventHandler(Player_TurnStarting), DispatcherPriority.Normal, sender, e); } } private void Player_TurnEnded(object sender, DominionBase.Players.TurnEndedEventArgs e) { if (Dispatcher.CheckAccess()) { uccChooser.ClearAutoYields(AutoYieldDuration.Turn); } else { Dispatcher.BeginInvoke(new EventHandler(Player_TurnEnded), DispatcherPriority.Normal, sender, e); } } private void Player_CardsDrawn(object sender, DominionBase.Players.CardsDrawnEventArgs e) { if (Dispatcher.CheckAccess()) { if (e.Cards.Any()) { var from = string.Empty; if (e.FromDeckPosition == DominionBase.Piles.DeckPosition.Bottom) from = $" from the bottom of {(sender == _player ? "your" : "their")} deck"; if (sender == _player) glMain.Log( sender as DominionBase.Players.Player, "You draw ", e.Cards.Select(c => c.PhysicalCard), from); else glMain.Log( sender as DominionBase.Players.Player, sender, $" draws {StringUtility.Plural("card", e.Cards.Count)}", from); } } else { Dispatcher.BeginInvoke(new EventHandler(Player_CardsDrawn), DispatcherPriority.Normal, sender, e); } } private void Game_GameEndedEvent(object sender, GameEndedEventArgs e) { if (Dispatcher.CheckAccess()) { //_Statistics.Add(game, _Player); //_Statistics.Save(); miNewGame.IsEnabled = true; miLoadGame.IsEnabled = true; miEndGame.IsEnabled = false; miSaveGame.IsEnabled = false; miReplay.IsEnabled = true; if (_startingNewGame) { glMain.TearDown(); glMain.Clear(); } glMain.NewSection("Game ended"); foreach (var player in Game.Players) { var playerType = string.Empty; switch (player.PlayerType) { case PlayerType.Human: playerType = "Human"; break; case PlayerType.Computer: playerType = (string)player.GetType().GetProperty("AIType", BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetProperty | BindingFlags.FlattenHierarchy).GetValue(player, null); break; } glMain.Log(string.Empty, player, $" ({playerType}): ", Colors.Crimson, player.VictoryPoints, Colors.Transparent, $" {StringUtility.Plural("point", player.VictoryPoints, false)} in ", Colors.DodgerBlue, Game.UnmodifiedTurnsTaken.Count(t => t.Player == player && t.IsTurnFinished), Colors.Transparent, " turns"); } if (Game.Winners.Any()) glMain.Log(StringUtility.Plural("Winner", Game.Winners.Count, false), ": ", Game.Winners, " with ", Game.Winners[0].VictoryPoints, StringUtility.Plural(" point", Game.Winners[0].VictoryPoints, false)); miReplay.IsEnabled = Game.State == GameState.Ended || Game.State == GameState.Aborted; tbActions.Text = string.Empty; tbBuys.Text = string.Empty; tbCurrency.Text = string.Empty; bPlayTreasures.Text = "Play _Treasures"; bPlayTreasures.IsEnabled = false; bDonePlayingTreasures.IsEnabled = false; bSpendVillager.IsEnabled = false; bSpendCoffers.IsEnabled = false; bPayOffDebtTokens.IsEnabled = false; bBuyPhase.IsEnabled = false; bNightPhase.IsEnabled = false; bUndo.IsEnabled = false; bTurnDone.IsEnabled = false; foreach (var control in stackPanelSupplyPiles.Children.OfType().SelectMany(sp => sp.Children.OfType())) control.Clickability = SupplyVisibility.Plain; ReleaseEvents(); if (_startingNewGame) Game_NewGame_Click(null, null); ChartGame(Game); } else { Dispatcher.BeginInvoke(new EventHandler(Game_GameEndedEvent), DispatcherPriority.Normal, sender, e); } } private void InitPointsChart() { Context.GameEndModel.IsLegendVisible = true; Context.GameEndModel.LegendPosition = OxyPlot.LegendPosition.TopLeft; Context.GameEndModel.Title = "Player points per turn of the game"; Context.GameEndModel.Axes.Add(new OxyPlot.Axes.LinearAxis { Position = OxyPlot.Axes.AxisPosition.Bottom, Title = "Turn" }); Context.GameEndModel.Axes.Add(new OxyPlot.Axes.LinearAxis { Position = OxyPlot.Axes.AxisPosition.Left, Title = "Points" }); } private void ChartGame(IGame game) { Context.GameEndModel.Series.Clear(); var minPoints = 0; var maxPoints = 0; var playersSeries = new Dictionary>(); foreach (var turn in game.TurnsTaken) { if (!playersSeries.ContainsKey(turn.Player)) playersSeries[turn.Player] = new List(); double index = playersSeries[turn.Player].Count(dp => dp.X == Math.Floor(dp.X)) + 1; // Extra turns are 1/2 index off if (turn.GrantedBy != null) index -= 0.5; if (turn.Points == null) continue; minPoints = minPoints == 0 ? turn.Points.Value : Math.Min(minPoints, turn.Points.Value); maxPoints = maxPoints == 0 ? turn.Points.Value : Math.Max(maxPoints, turn.Points.Value); playersSeries[turn.Player].Add(new OxyPlot.DataPoint(index, turn.Points.Value)); } var keys = playersSeries.Keys.ToList(); if (!keys.Any() || !playersSeries[keys[0]].Any()) return; foreach (var player in playersSeries) { // If there weren't an equal number of turns, then fill out the turns to account for it if (player.Value.Count == 0 || Math.Floor(player.Value[player.Value.Count - 1].X) < Math.Floor(playersSeries[keys[0]][playersSeries[keys[0]].Count - 1].X)) player.Value.Add(new OxyPlot.DataPoint(playersSeries[keys[0]][playersSeries[keys[0]].Count - 1].X, player.Key.VictoryPoints)); var playerSettings = Context.Settings.PlayerSettings.FirstOrDefault(ps => ps.Name == player.Key.Name); var hlsValue = HlsColor.RgbToHls(playerSettings.UIColor); var seriesColor = Math.Abs(hlsValue.A) >= 0.001 ? HlsColor.HlsToRgb(hlsValue.H, hlsValue.L * 0.65, hlsValue.S * 1.05, hlsValue.A) : HlsColor.HlsToRgb(0d, hlsValue.L * 0.65, 0d, 1d); var markerColor = Math.Abs(hlsValue.A) >= 0.001 ? HlsColor.HlsToRgb(hlsValue.H, hlsValue.L * 0.90, hlsValue.S * 1.05, hlsValue.A) : HlsColor.HlsToRgb(0d, hlsValue.L * 0.90, 0d, 1d); var series = new OxyPlot.Series.LineSeries { CanTrackerInterpolatePoints = false, Color = OxyPlot.OxyColor.FromRgb(seriesColor.R, seriesColor.G, seriesColor.B), MarkerFill = OxyPlot.OxyColor.FromRgb(markerColor.R, markerColor.G, markerColor.B), MarkerType = (OxyPlot.MarkerType)(Context.GameEndModel.Series.Count + 1), Title = player.Key.Name, }; series.Points.AddRange(player.Value); Context.GameEndModel.Series.Add(series); } tiGamePoints.Visibility = Visibility.Visible; } private void Player_BenefitsChanged(object sender, DominionBase.Players.BenefitsChangedEventArgs e) { if (Dispatcher.CheckAccess()) { if (e.Player == Game.ActivePlayer) { tbActions.Inlines.Clear(); tbActions.Inlines.Add(((TextBlock)Utilities.RenderText( $"{(e.Actions > 0 ? "" : "")}{e.Actions}{(e.Actions > 0 ? "" : "")}", NET_WPF.RenderSize.Tiny, true)[0]).Inlines.ElementAt(0)); tbBuys.Inlines.Clear(); tbBuys.Inlines.Add(((TextBlock)Utilities.RenderText( $"{(e.Buys > 0 ? "" : "")}{e.Buys}{(e.Buys > 0 ? "" : "")}", NET_WPF.RenderSize.Tiny, true)[0]).Inlines.ElementAt(0)); tbCurrency.Inlines.Clear(); var tbTemp = (TextBlock)Utilities.RenderText(e.Player.Currency.ToString(), NET_WPF.RenderSize.Tiny, false)[0]; while (tbTemp.Inlines.Any()) { var container = tbTemp.Inlines.ElementAt(0) as InlineUIContainer; if (container?.Child is Canvas canvas) canvas.Margin = new Thickness(2, 0, 2, 0); tbCurrency.Inlines.Add(tbTemp.Inlines.ElementAt(0)); } UpdateDisplay(); } } else { Dispatcher.BeginInvoke(new EventHandler(Player_BenefitsChanged), DispatcherPriority.Normal, sender, e); } } private void Player_CardFlippedOver(object sender, DominionBase.Players.CardFlippedOverEventArgs e) { if (Dispatcher.CheckAccess()) { var player = (IPlayer)sender; glMain.Log( player, player == _player ? (object)"You" : (object)player, $" flip{(player == _player ? "" : "s")} over ", e.Card, " ", e.NewFacing ); } else { Dispatcher.BeginInvoke(new EventHandler(Player_CardFlippedOver), DispatcherPriority.Normal, sender, e); } } private void Player_TakeablesChanged(object sender, DominionBase.Players.TakeableEventArgs e) { if (Dispatcher.CheckAccess()) { var player = (IPlayer)sender; var verb = "unknown"; switch (e.TakeableAction) { case TakeableAction.Taken: verb = "take"; break; case TakeableAction.Returned: verb = "return"; break; } glMain.Log( player, player == _player ? (object)"You" : (object)player, $" {verb}{(player == _player ? "" : "s")} ", e.Takeables ); } else { Dispatcher.BeginInvoke(new EventHandler(Player_TakeablesChanged), DispatcherPriority.Normal, sender, e); } } private void Player_PhaseChangedEvent(object sender, DominionBase.Players.PhaseChangedEventArgs e) { if (Dispatcher.CheckAccess()) { phaseDisplay.Phase = e.NewPhase; if (e.CurrentPlayer == Game.ActivePlayer) { //System.Diagnostics.Trace.WriteLine(String.Format("{2} Phase changed: {0} to {1}", e.OldPhase, e.NewPhase, DateTime.Now.ToString("o"))); if (e.NewPhase == PhaseEnum.Starting || e.NewPhase == PhaseEnum.Buy || e.CurrentPlayer.PlayerMode == PlayerMode.Waiting) CheckBuyable(e.CurrentPlayer); else ClearBuyable(); } if (e.CurrentPlayer != _player) return; if (e.NewPhase == PhaseEnum.Setup || e.NewPhase == PhaseEnum.Starting || e.NewPhase == PhaseEnum.Endgame) miSettings.IsEnabled = miCurrentGame.IsEnabled = true; else if (e.CurrentPlayer.PlayerMode == PlayerMode.Waiting) miSettings.IsEnabled = miCurrentGame.IsEnabled = false; UpdateDisplay(); if (_settings.AutoPlayTreasures && e.NewPhase == PhaseEnum.BuyTreasure) { // Ugly hack, but it mostly works -- just a slight delay between the end of the PhaseChangedEvent and the AutoPlay Task.Run(() => { Thread.Sleep(TimeSpan.FromMilliseconds(50)); AutoPlayTreasures(); }); //var autoplayInvoker = new BackgroundWorker(); //autoplayInvoker.DoWork += delegate //{ // Thread.Sleep(TimeSpan.FromMilliseconds(50)); // AutoPlayTreasures(); //}; //autoplayInvoker.RunWorkerAsync(); } } else { Dispatcher.BeginInvoke(new EventHandler(Player_PhaseChangedEvent), DispatcherPriority.Normal, sender, e); } } private void Player_PlayerModeChangedEvent(object sender, DominionBase.Players.PlayerModeChangedEventArgs e) { if (Dispatcher.CheckAccess()) { if (e.CurrentPlayer == Game.ActivePlayer) { //System.Diagnostics.Trace.WriteLine(String.Format("{2} Phase changed: {0} to {1}", e.OldPhase, e.NewPhase, DateTime.Now.ToString("o"))); if (e.CurrentPlayer.Phase == PhaseEnum.Starting || e.CurrentPlayer.Phase == PhaseEnum.Buy || e.NewPlayerMode == PlayerMode.Waiting) CheckBuyable(e.CurrentPlayer); else ClearBuyable(); } if (e.CurrentPlayer != _player) return; if (e.CurrentPlayer.Phase == PhaseEnum.Setup || e.CurrentPlayer.Phase == PhaseEnum.Starting || e.CurrentPlayer.Phase == PhaseEnum.Endgame) miSettings.IsEnabled = miCurrentGame.IsEnabled = true; else if (e.CurrentPlayer.PlayerMode == PlayerMode.Waiting) miSettings.IsEnabled = miCurrentGame.IsEnabled = false; UpdateDisplay(); if (_settings.AutoPlayTreasures && e.NewPlayerMode == PlayerMode.Normal && e.CurrentPlayer.Phase == PhaseEnum.BuyTreasure) { // Ugly hack, but it mostly works -- just a slight delay between the end of the PhaseChangedEvent and the AutoPlay Task.Run(() => { Thread.Sleep(TimeSpan.FromMilliseconds(50)); AutoPlayTreasures(); }); //var autoplayInvoker = new BackgroundWorker(); //autoplayInvoker.DoWork += delegate //{ // Thread.Sleep(TimeSpan.FromMilliseconds(50)); // AutoPlayTreasures(); //}; //autoplayInvoker.RunWorkerAsync(); } } else { Dispatcher.BeginInvoke(new EventHandler(Player_PlayerModeChangedEvent), DispatcherPriority.Normal, sender, e); } } private void AutoPlayTreasures() { WaitCallback wcb = UpdateDisplayTarget; GamePlayMessage gpm; // Always play Contraband first var contrabandTreasures = _player.Hand[DominionBase.Cards.Prosperity.TypeClass.Contraband]; foreach (var card in contrabandTreasures) { if (_player.Phase != PhaseEnum.ActionTreasure && _player.Phase != PhaseEnum.BuyTreasure) break; while (Game.MessageResponseQueue.TryDequeue(out GameMessage result)) ; gpm = new GamePlayMessage(wcb, _player, card) { Message = $"{_player} playing {card}" }; EnqueueGameMessageAndWait(gpm); } // Play "normal" Treasure cards next var tNormal = _player.Hand[c => c.Category.HasFlag(Categories.Treasure) && c.Type != DominionBase.Cards.Prosperity.TypeClass.Bank && c.Type != DominionBase.Cards.Prosperity.TypeClass.Contraband && c.Type != DominionBase.Cards.Prosperity.TypeClass.Loan && c.Type != DominionBase.Cards.Prosperity.TypeClass.Venture && c.Type != DominionBase.Cards.Prosperity2ndEdition.TypeClass.Loan && c.Type != DominionBase.Cards.Cornucopia.TypeClass.HornOfPlenty && c.Type != DominionBase.Cards.Cornucopia2ndEdition.TypeClass.HornOfPlenty ]; if (tNormal.Any()) _player.PlayCards(tNormal); // Only play Loan & Venture after cards like Philosopher's Stone that work better with more cards // There are some very specific situations where playing Horn Of Plenty before Philospher's Stone // or Venture is the right way to play things, but that's so incredibly rare. var tLoanVenture = _player.Hand[DominionBase.Cards.Prosperity.TypeClass.Venture]; if (_settings.AutoPlayTreasuresIncludingLoan) { if (_settings.AutoPlayTreasuresLoanFirst) tLoanVenture.InsertRange(0, _player.Hand[c => c is DominionBase.Cards.Prosperity.Loan || c is DominionBase.Cards.Prosperity2ndEdition.Loan]); else tLoanVenture.AddRange(_player.Hand[c => c is DominionBase.Cards.Prosperity.Loan || c is DominionBase.Cards.Prosperity2ndEdition.Loan]); } foreach (var card in tLoanVenture) { if (_player.Phase != PhaseEnum.ActionTreasure && _player.Phase != PhaseEnum.BuyTreasure) break; gpm = new GamePlayMessage(wcb, _player, card) { Message = $"{_player} playing {card}" }; EnqueueGameMessageAndWait(gpm); return; } // Always play Bank & Horn of Plenty last var tBankHornofPlenty = _player.Hand[DominionBase.Cards.Prosperity.TypeClass.Bank]; if (_settings.AutoPlayTreasuresIncludingHornOfPlenty) { // If Horn Of Plenty is to be played first, play ALL Horn Of Plenty cards first if (_settings.AutoPlayTreasuresHornOfPlentyFirst) tBankHornofPlenty.InsertRange(0, _player.Hand[c => c is DominionBase.Cards.Cornucopia.HornOfPlenty || c is DominionBase.Cards.Cornucopia2ndEdition.HornOfPlenty]); // Otherwise, play a SINGLE Bank card, then ALL Horn of Plenty cards, then all remaining Bank cards else tBankHornofPlenty.InsertRange(tBankHornofPlenty.Count == 0 ? 0 : 1, _player.Hand[c => c is DominionBase.Cards.Cornucopia.HornOfPlenty || c is DominionBase.Cards.Cornucopia2ndEdition.HornOfPlenty]); } foreach (var card in tBankHornofPlenty) { if (_player.Phase != PhaseEnum.ActionTreasure && _player.Phase != PhaseEnum.BuyTreasure) break; gpm = new GamePlayMessage(wcb, _player, card) { Message = $"{_player} playing {card}" }; EnqueueGameMessageAndWait(gpm); } } private void ClearBuyable() { foreach (var control in stackPanelSupplyPiles.Children.OfType().SelectMany(sp => sp.Children.OfType())) { control.SupplyClick -= SupplyControl_SupplyClick; control.Clickability = control.Clickability; } } private void CheckBuyable(IPlayer player) { var buyablePhase = player != null && player.Phase == PhaseEnum.Buy; foreach (var control in stackPanelSupplyPiles.Children.OfType().SelectMany(sp => sp.Children.OfType())) { control.SupplyClick -= SupplyControl_SupplyClick; if (_player != player) control.Clickability = SupplyVisibility.Plain; else if (buyablePhase && player.Buys > 0 && _player == player && control.Supply is IBuyable sBuyable && sBuyable.CanBuy(player)) { control.Clickability = SupplyVisibility.Gainable; // Only attach the SupplyClick event if we're actually buying a card. // Otherwise, we'll double-trigger on certain events (like Border Village's Gain ability) if (player.PlayerMode == PlayerMode.Normal) control.SupplyClick += SupplyControl_SupplyClick; } else control.Clickability = SupplyVisibility.NotClickable; } } private void Player_Trashing(object sender, DominionBase.Players.TrashEventArgs e) { if (Dispatcher.CheckAccess()) { e.TrashedCards.Sort(); glMain.Log( sender as DominionBase.Players.Player, sender == _player ? (object)"You" : (object)sender, $" trash{(sender == _player ? "" : "es")} ", e.TrashedCards.Select(c => c.PhysicalCard)); glMain.Push(); CurrentPlayDepth++; } else { Dispatcher.BeginInvoke(new EventHandler(Player_Trashing), DispatcherPriority.Normal, sender, e); } } private void Player_Trashed(object sender, DominionBase.Players.TrashEventArgs e) { if (Dispatcher.CheckAccess()) { glMain.Pop(); CurrentPlayDepth--; } else { Dispatcher.BeginInvoke(new EventHandler(Player_Trashed), DispatcherPriority.Normal, sender, e); } } private void Player_Calling(object sender, DominionBase.Players.CallEventArgs e) { if (Dispatcher.CheckAccess()) { glMain.Log( sender as DominionBase.Players.Player, sender == _player ? (object)"You" : (object)sender, $" call{(sender == _player ? "" : "s")} ", e.CalledCard.PhysicalCard); glMain.Push(); CurrentPlayDepth++; } else { Dispatcher.BeginInvoke(new EventHandler(Player_Calling), DispatcherPriority.Normal, sender, e); } } private void Player_Called(object sender, DominionBase.Players.CallEventArgs e) { if (Dispatcher.CheckAccess()) { glMain.Pop(); CurrentPlayDepth--; } else { Dispatcher.BeginInvoke(new EventHandler(Player_Called), DispatcherPriority.Normal, sender, e); } } private void Player_CardReceived(object sender, DominionBase.Players.CardReceivedEventArgs e) { if (Dispatcher.CheckAccess()) { var player = sender as DominionBase.Players.Player; var extra = new StringBuilder(); switch (e.Location) { case DeckLocation.Deck: var locationMod = string.Empty; if (player != null) { var dp = player.ResolveDeckPosition(e.Location, e.Position); if (dp == DominionBase.Piles.DeckPosition.Bottom) locationMod = "the "; extra.AppendFormat(", putting it on {1}{0} of your {2}", dp.ToString().ToLower(), locationMod, e.Location.ToString().ToLower()); } break; case DeckLocation.Hand: case DeckLocation.InPlay: case DeckLocation.SetAside: extra.AppendFormat(", putting it into your {0}", e.Location.ToString().ToLower()); break; } glMain.Log( player, player == _player ? (object)"You" : (object)player, $" receive{(player == _player ? "" : "s")} ", e.Card.PhysicalCard, " from ", e.FromPlayer, extra); UpdateDisplay(); } else { Dispatcher.BeginInvoke(new EventHandler(Player_CardReceived), DispatcherPriority.Normal, sender, e); } } private void Player_CardGaining(object sender, DominionBase.Players.CardGainEventArgs e) { if (Dispatcher.CheckAccess()) { if (!e.Bought) { List items = new List { sender == _player ? (object)"You" : (object)sender, $" gain{(sender == _player ? "" : "s")} ", e.Card.PhysicalCard }; if (CurrentPlayDepth == 0) items.AddRange(new List { " from ", e.Source }); glMain.Log( sender as DominionBase.Players.Player, items.ToArray() ); } glMain.Push(); CurrentPlayDepth++; } else { Dispatcher.BeginInvoke(new EventHandler(Player_CardGaining), DispatcherPriority.Normal, sender, e); } } private void Player_CardGainedInto(object sender, DominionBase.Players.CardGainEventArgs e) { if (Dispatcher.CheckAccess()) { // the CardBought event will have already handled this, so we can just skip printing any message switch (e.Location) { case DeckLocation.Discard: case DeckLocation.PlayerMat: return; } // PlayerMats handle themselves var extra = new StringBuilder(); var pronoun = "their"; if (sender as DominionBase.Players.Player == _player) pronoun = "your"; switch (e.Location) { case DeckLocation.Deck: var locationMod = string.Empty; if (sender is DominionBase.Players.Player player && player.ResolveDeckPosition(e.Location, e.Position) == DominionBase.Piles.DeckPosition.Bottom) locationMod = "the bottom of "; extra.AppendFormat(" on {0}{2} {1}", locationMod, e.Location.ToString().ToLower(), pronoun); break; case DeckLocation.Hand: case DeckLocation.InPlay: case DeckLocation.SetAside: extra.AppendFormat(" into {1} {0}", e.Location.ToString().ToLower(), pronoun); break; } glMain.Log( sender as DominionBase.Players.Player, sender == _player ? (object)"You" : (object)sender, $" put{(sender == _player ? "" : "s")} ", e.Card.PhysicalCard, extra.ToString()); UpdateDisplay(); } else { Dispatcher.BeginInvoke(new EventHandler(Player_CardGainedInto), DispatcherPriority.Normal, sender, e); } } private void Player_CardGainFinished(object sender, DominionBase.Players.CardGainEventArgs e) { if (Dispatcher.CheckAccess()) { glMain.Pop(); CurrentPlayDepth--; } else { Dispatcher.BeginInvoke(new EventHandler(Player_CardGainFinished), DispatcherPriority.Normal, sender, e); } } private void Player_CardBuying(object sender, DominionBase.Players.CardBuyEventArgs e) { if (Dispatcher.CheckAccess()) { object card = e.Card; if (card is Card) card = ((Card)e.Card).PhysicalCard; glMain.Log( sender as DominionBase.Players.Player, sender == _player ? (object)"You" : (object)sender, $" buy{(sender == _player ? "" : "s")} ", card); glMain.Push(); CurrentPlayDepth++; } else { Dispatcher.BeginInvoke(new EventHandler(Player_CardBuying), DispatcherPriority.Normal, sender, e); } } private void Player_CardBought(object sender, DominionBase.Players.CardBuyEventArgs e) { if (Dispatcher.CheckAccess()) { } else { Dispatcher.BeginInvoke(new EventHandler(Player_CardBought), DispatcherPriority.Normal, sender, e); } } private void Player_CardBuyFinished(object sender, DominionBase.Players.CardBuyEventArgs e) { if (Dispatcher.CheckAccess()) { glMain.Pop(); CurrentPlayDepth--; if (sender == _player) CheckBuyable((DominionBase.Players.Player)sender); } else { Dispatcher.BeginInvoke(new EventHandler(Player_CardBuyFinished), DispatcherPriority.Normal, sender, e); } } private void Player_CardPlaying(object sender, DominionBase.Players.CardPlayingEventArgs e) { if (Dispatcher.CheckAccess()) { var cardItems = new List { e.Player == _player ? (object) "You" : (object) e.Player, $" play{(e.Player == _player ? "" : "s")} " }; if (e.Cards.Count == 0) cardItems.Add("nothing"); else { // Don't lose groupings var cardBunch = new List(); foreach (var card in e.Cards) { if (card.PhysicalCard == card.LogicalCard) cardBunch.Add(card); else { if (cardBunch.Count > 0) { cardItems.Add(cardBunch); cardBunch.Clear(); } cardItems.Add(card.PhysicalCard); cardItems.Add(" as "); cardItems.Add(card.LogicalCard); } } if (cardBunch.Count > 0) cardItems.Add(cardBunch); } cardItems.Add($" {e.Modifier}"); glMain.Log( e.Player, cardItems.ToArray() ); //e.Player == _Player ? (Object)"You" : (Object)e.Player, //String.Format(" play{0} ", e.Player == _Player ? "" : "s"), //cardItems.ToArray(), //String.Format(" {0}", e.Modifier)); glMain.Push(); CurrentPlayDepth++; if (e.Cards.Count > 0) _playStack.Push(e.Cards.First()); } else { Dispatcher.BeginInvoke(new EventHandler(Player_CardPlaying), DispatcherPriority.Normal, sender, e); } } private void Player_CardPlayed(object sender, DominionBase.Players.CardPlayedEventArgs e) { if (Dispatcher.CheckAccess()) { miSaveGame.IsEnabled = false; glMain.Pop(); CurrentPlayDepth--; if (_playStack.Any()) _playStack.Pop(); // Clear action-duration auto-yields if (CurrentPlayDepth == 0) uccChooser.ClearAutoYields(AutoYieldDuration.Action); } else { Dispatcher.BeginInvoke(new EventHandler(Player_CardPlayed), DispatcherPriority.Normal, sender, e); } } private void Player_CardUndoPlaying(object sender, DominionBase.Players.CardUndoPlayingEventArgs e) { if (Dispatcher.CheckAccess()) { glMain.Log( e.Player, e.Player == _player ? (object)"You" : (object)e.Player, $" undo{(e.Player == _player ? "" : "es")} playing ", e.Cards.Count == 0 ? (object)"nothing" : (object)e.Cards.Select(c => c.PhysicalCard), $" {e.Modifier}"); glMain.Push(); CurrentPlayDepth++; } else { Dispatcher.BeginInvoke(new EventHandler(Player_CardUndoPlaying), DispatcherPriority.Normal, sender, e); } } private void Player_CardUndoPlayed(object sender, DominionBase.Players.CardUndoPlayedEventArgs e) { if (Dispatcher.CheckAccess()) { glMain.Pop(); CurrentPlayDepth--; } else { Dispatcher.BeginInvoke(new EventHandler(Player_CardUndoPlayed), DispatcherPriority.Normal, sender, e); } } private void Player_TokenPlaying(object sender, DominionBase.Players.TokenPlayingEventArgs e) { if (Dispatcher.CheckAccess()) { var verb = e.Tokens.First() is DominionBase.Cards.Empires.DebtToken ? $" pay{(e.Player == _player ? "" : "s")} off " : $" play{(e.Player == _player ? "" : "s")} "; glMain.Log( e.Player, e.Player == _player ? (object)"You" : (object)e.Player, verb, e.Tokens == null ? (object)"nothing" : (object)e.Tokens); glMain.Push(); CurrentPlayDepth++; } else { Dispatcher.BeginInvoke(new EventHandler(Player_TokenPlaying), DispatcherPriority.Normal, sender, e); } } private void Player_TokenPlayed(object sender, DominionBase.Players.TokenPlayedEventArgs e) { if (Dispatcher.CheckAccess()) { miSaveGame.IsEnabled = false; glMain.Pop(); CurrentPlayDepth--; UpdateDisplay(); CheckBuyable(Game.ActivePlayer); } else { Dispatcher.BeginInvoke(new EventHandler(Player_TokenPlayed), DispatcherPriority.Normal, sender, e); } } private void Revealed_PileChanged(object sender, DominionBase.Piles.PileChangedEventArgs e) { if (e.OperationPerformed == Operation.Added) { if (Dispatcher.CheckAccess()) { glMain.Log( e.Player, _player != null && e.Player.PlayerUniqueId == _player.UniqueId ? (object)"You" : (object)e.Player, $" reveal{(_player != null && e.Player.PlayerUniqueId == _player.UniqueId ? "" : "s")}: ", e.AddedCards.OfType().Select(c => c.PhysicalCard)); } else { Dispatcher.BeginInvoke(new EventHandler>(Revealed_PileChanged), DispatcherPriority.Normal, sender, e); } } } private void SetAside_PileChanged(object sender, DominionBase.Piles.PileChangedEventArgs e) { if (Dispatcher.CheckAccess()) { if (_player == null || e.Player.PlayerUniqueId != _player.UniqueId) { foreach (var item in tcAreas.Items.OfType()) { if (!(item.Content is UcPlayerDisplay uPlayerDisplay)) continue; if (uPlayerDisplay.Player.UniqueId != e.Player.PlayerUniqueId) continue; if (!(item.Header is DockPanel dockPanel)) continue; dockPanel.Children[dockPanel.Children.Count - 1].Visibility = uPlayerDisplay.Player.SetAside[Categories.Attack].Any() ? Visibility.Visible : Visibility.Hidden; } } } else { Dispatcher.BeginInvoke(new EventHandler>(SetAside_PileChanged), DispatcherPriority.Normal, sender, e); } } private void Trash_PileChanged(object sender, DominionBase.Piles.PileChangedEventArgs e) { if (cardTrash.Dispatcher.CheckAccess()) { cardTrash.ExactCount = true; cardTrash.IsCardsVisible = true; cardTrash.Phase = PhaseEnum.Action; cardTrash.PlayerMode = PlayerMode.Waiting; cardTrash.CardSize = CardSize.Text; cardTrash.Pile = Game.Table.Trash; } else { cardTrash.Dispatcher.BeginInvoke(new EventHandler>(Trash_PileChanged), DispatcherPriority.Normal, sender, e); } } private void TokenPiles_TokenCollectionsChanged(object sender, TokenCollectionsChangedEventArgs e) { if (_tradeRouteLabel == null) return; if (_tradeRouteLabel.Dispatcher.CheckAccess()) { _tradeRouteLabel.Content = e.Count.ToString(); } else { _tradeRouteLabel.Dispatcher.BeginInvoke(new EventHandler(TokenPiles_TokenCollectionsChanged), DispatcherPriority.Normal, sender, e); } } private void Player_Choose(IPlayer player, Choice choice) { if (Dispatcher.CheckAccess()) { UpdateDisplay(); uccChooser.Player = player; uccChooser.Choice = choice; uccChooser.Visibility = Visibility.Visible; var supplyControls = new List(); foreach (var sp in stackPanelSupplyPiles.Children.OfType()) supplyControls.AddRange(sp.Children.OfType()); uccChooser.SupplyControls = supplyControls; uccChooser.Target = "PlayerChoiceMessage"; uccChooser.IsReady = true; } else { Dispatcher.BeginInvoke(new player_ChooseDelegate(Player_Choose), DispatcherPriority.Normal, player, choice); } } private void UccChooser_ChooserOKClick(object sender, RoutedEventArgs e) { uccChooser.Visibility = Visibility.Collapsed; var chooser = sender as ucChooser; WaitCallback wcb; GamePlayTokensMessage gptm; switch (chooser?.Target) { case "PlayerChoiceMessage": var pcm = new PlayerChoiceMessage(WaitEvent, chooser.Player, chooser.ChoiceResult) { Message = $"{chooser.Player} chooses" }; chooser.Player.MessageRequestQueue.Enqueue(pcm); chooser.Player.WaitEvent.Set(); while (WaitEvent.WaitOne(250)) ; chooser.Player.MessageResponseQueue.TryDequeue(out _); break; //case "GameSpendVillagersMessage": // wcb = UpdateDisplayTarget; // gptm = new GamePlayTokensMessage(Game.ActivePlayer, DominionBase.Cards.Renaissance.TypeClass.Villager, // int.Parse(chooser.ChoiceResult.Options[0])) // { Message = $"{Game.ActivePlayer} playing Tokens" }; // EnqueueGameMessageAndWait(gptm); // break; case "GameSpendCoffersMessage": wcb = UpdateDisplayTarget; gptm = new GamePlayTokensMessage(Game.ActivePlayer, DominionBase.Cards.Guilds.TypeClass.Coffer, int.Parse(chooser.ChoiceResult.Options[0])) { Message = $"{Game.ActivePlayer} playing Tokens" }; EnqueueGameMessageAndWait(gptm); break; //case "GamePlayDebtTokensMessage": // wcb = UpdateDisplayTarget; // gptm = new GamePlayTokensMessage(Game.ActivePlayer, DominionBase.Cards.Empires.TypeClass.DebtToken, // int.Parse(chooser.ChoiceResult.Options[0])) // { Message = $"{Game.ActivePlayer} playing Tokens" }; // EnqueueGameMessageAndWait(gptm); // break; } UpdateDisplay(); CheckBuyable(chooser?.Player); } private void UpdateDisplayTarget(object target) { if (Dispatcher.CheckAccess()) { UpdateDisplay(); } else { WaitCallback wcb = UpdateDisplayTarget; Dispatcher.BeginInvoke(wcb, DispatcherPriority.Normal, target); } } private void UpdateDisplay() { miReplay.IsEnabled = Game.State == GameState.Ended || Game.State == GameState.Aborted; UpdateDisplayPlayer(Game.ActivePlayer); } private void UpdateDisplayPlayer(IPlayer player) { bPlayTreasures.IsEnabled = false; bDonePlayingTreasures.IsEnabled = false; bSpendVillager.IsEnabled = false; bSpendCoffers.IsEnabled = false; bPayOffDebtTokens.IsEnabled = false; bBuyPhase.IsEnabled = false; bNightPhase.IsEnabled = false; bTurnDone.IsEnabled = false; bUndo.IsEnabled = false; bPlayTreasures.Text = "Play _Treasures"; if (player == null) { return; } if (player == _player) { ItemCollection treasures; Currency totalCurrency; var currency = string.Empty; switch (player.Phase) { case PhaseEnum.Action: if (player.PlayerMode == PlayerMode.Normal) { bSpendVillager.IsEnabled = player.TokenPiles[DominionBase.Cards.Renaissance.TypeClass.Villager].Any(); treasures = player.Hand[c => c.Category.HasFlag(Categories.Treasure) && (c.Location == Location.General || c.Type.GetMethod("FollowInstructions", new[] { typeof(DominionBase.Players.Player) }).DeclaringType == typeof(Card))]; bPlayTreasures.IsEnabled = treasures.Any(); totalCurrency = treasures.Select(t => t.Benefit.Currency).Aggregate(new Currency(), (current, c) => current + c); currency = totalCurrency.ToStringInline(); bBuyPhase.IsEnabled = true; bTurnDone.IsEnabled = true; } break; case PhaseEnum.ActionTreasure: case PhaseEnum.BuyTreasure: if (player.PlayerMode == PlayerMode.Normal) { treasures = player.Hand[c => c.Category.HasFlag(Categories.Treasure) && (c.Location == Location.General || c.Type.GetMethod("FollowInstructions", new[] { typeof(DominionBase.Players.Player) }).DeclaringType == typeof(Card))]; bPlayTreasures.IsEnabled = treasures.Any(); bDonePlayingTreasures.IsEnabled = true; totalCurrency = treasures.Select(t => t.Benefit.Currency).Aggregate(new Currency(), (current, c) => current + c); currency = totalCurrency.ToStringInline(); if (player.Phase == PhaseEnum.ActionTreasure) { bSpendVillager.IsEnabled = player.TokenPiles[DominionBase.Cards.Renaissance.TypeClass.Villager].Any(); } if (player.Phase == PhaseEnum.BuyTreasure) { bSpendCoffers.IsEnabled = player.TokenPiles[DominionBase.Cards.Guilds.TypeClass.Coffer].Any() && player.CurrentTurn.CardsBought.Count == 0; } bTurnDone.IsEnabled = true; } break; case PhaseEnum.Buy: if (player.PlayerMode == PlayerMode.Normal) { bSpendCoffers.IsEnabled = player.TokenPiles[DominionBase.Cards.Guilds.TypeClass.Coffer].Any() && player.CurrentTurn.CardsBought.Count == 0; bPayOffDebtTokens.IsEnabled = player.TokenPiles[DominionBase.Cards.Empires.TypeClass.DebtToken].Any() && player.Currency.Coin > 0; if (Game.Table.TableEntities.Values.Any(s => s.Category.HasFlag(Categories.Night)) || Game.Table.SpecialPiles.Values.Any(s => s.Category.HasFlag(Categories.Night))) bNightPhase.IsEnabled = true; bTurnDone.IsEnabled = true; } break; case PhaseEnum.Night: if (player.PlayerMode == PlayerMode.Normal) { bTurnDone.IsEnabled = true; } break; case PhaseEnum.Cleanup: case PhaseEnum.Endgame: break; } if (!Game.Table.TableEntities.Values.All(te => te.CanUndo)) bUndo.IsEnabled = false; else if (player.CurrentTurn.CardsPlayed.Count == 0 || player.CurrentTurn.CardsBought.Any()) bUndo.IsEnabled = false; else if (!player.CurrentTurn.CardsPlayed.Last().CanUndo) bUndo.IsEnabled = false; else bUndo.IsEnabled = true; //bUndo.IsEnabled = player.CurrentTurn.CardsPlayed.Any() && player.CurrentTurn.CardsPlayed[player.CurrentTurn.CardsPlayed.Count - 1].CanUndo && player.CurrentTurn.CardsBought.Count == 0; if (bPlayTreasures.IsEnabled) bPlayTreasures.Text = $"Play _Treasures\r\nfor: {currency}"; } } private void BPlayTreasures_Click(object sender, RoutedEventArgs e) { if (_settings.PromptUnplayedActions && Game.ActivePlayer.Phase == PhaseEnum.Action && Game.ActivePlayer.Hand[Categories.Action].Any() && (Game.ActivePlayer.Actions > 0 || Game.ActivePlayer.TokenPiles[DominionBase.Cards.Renaissance.TypeClass.Villager].Count > 0)) { if (wMessageBox.Show("You have unplayed actions left. Are you sure you want to do this?", "Please confirm", MessageBoxButton.YesNo) != MessageBoxResult.Yes) return; } if (sender is Control control) control.IsEnabled = false; WaitCallback wcb = UpdateDisplayTarget; var gptm = new GamePlayTreasuresMessage(wcb, Game.ActivePlayer) { Message = $"{Game.ActivePlayer} playing treasures" }; EnqueueGameMessageAndWait(gptm); } private void BDonePlayingTreasures_Click(object sender, RoutedEventArgs e) { if (Game.ActivePlayer.Phase != PhaseEnum.BuyTreasure) return; if (sender is Control control) control.IsEnabled = false; WaitCallback wcb = UpdateDisplayTarget; var ggtbpm = new GameGoToBuyPhaseMessage(wcb, Game.ActivePlayer) { Message = $"{Game.ActivePlayer} going to Buy phase" }; EnqueueGameMessageAndWait(ggtbpm); } private void BSpendVillager_Click(object sender, RoutedEventArgs e) { UpdateDisplay(); var gptm = new GamePlayTokensMessage(Game.ActivePlayer, DominionBase.Cards.Renaissance.TypeClass.Villager, 1) { Message = $"{Game.ActivePlayer} playing Tokens" }; EnqueueGameMessageAndWait(gptm); } private void BSpendCoffers_Click(object sender, RoutedEventArgs e) { if (_settings.PromptUnplayedActions && Game.ActivePlayer.Phase == PhaseEnum.Action && Game.ActivePlayer.Hand[Categories.Action].Any() && (Game.ActivePlayer.Actions > 0 || Game.ActivePlayer.TokenPiles[DominionBase.Cards.Renaissance.TypeClass.Villager].Count > 0)) { if (wMessageBox.Show("You have unplayed actions left. Are you sure you want to do this?", "Please confirm", MessageBoxButton.YesNo) != MessageBoxResult.Yes) return; } UpdateDisplay(); uccChooser.Player = Game.ActivePlayer; var options = new List(); for (var i = 0; i <= Game.ActivePlayer.TokenPiles[DominionBase.Cards.Guilds.TypeClass.Coffer].Count; i++) options.Add(i.ToString()); var choice = new Choice("How many Coffers do you want to spend?", null, new ICardBaseCollection(), options, Game.ActivePlayer); uccChooser.Choice = choice; uccChooser.Visibility = Visibility.Visible; uccChooser.Target = "GameSpendCoffersMessage"; uccChooser.IsReady = true; } private void BPayOffDebtTokens_Click(object sender, RoutedEventArgs e) { if (_settings.PromptUnplayedActions && Game.ActivePlayer.Phase == PhaseEnum.Action && Game.ActivePlayer.Hand[Categories.Action].Any() && (Game.ActivePlayer.Actions > 0 || Game.ActivePlayer.TokenPiles[DominionBase.Cards.Renaissance.TypeClass.Villager].Count > 0)) { if (wMessageBox.Show("You have unplayed actions left. Are you sure you want to do this?", "Please confirm", MessageBoxButton.YesNo) != MessageBoxResult.Yes) return; } UpdateDisplay(); var maxTokens = Math.Min(Game.ActivePlayer.TokenPiles[DominionBase.Cards.Empires.TypeClass.DebtToken].Count, Game.ActivePlayer.Currency.Coin.Value); var gptm = new GamePlayTokensMessage(Game.ActivePlayer, DominionBase.Cards.Empires.TypeClass.DebtToken, maxTokens) { Message = $"{Game.ActivePlayer} playing Tokens" }; EnqueueGameMessageAndWait(gptm); } private void BUndo_Click(object sender, RoutedEventArgs e) { if (sender is Control control) control.IsEnabled = false; WaitCallback wcb = UpdateDisplayTarget; GameUndoPlayMessage gupm; if (Game.ActivePlayer.Phase == PhaseEnum.Buy && Game.ActivePlayer.CurrentTurn.CardsBought.Count == 0 && Game.ActivePlayer.Hand.Count(c => c.Category.HasFlag(Categories.Treasure)) > 0) gupm = new GameUndoPlayMessage(wcb, Game.ActivePlayer, PhaseEnum.BuyTreasure); else gupm = new GameUndoPlayMessage(wcb, Game.ActivePlayer, Game.ActivePlayer.CurrentTurn.CardsPlayed[Game.ActivePlayer.CurrentTurn.CardsPlayed.Count - 1]); gupm.Message = $"{Game.ActivePlayer} undoing"; EnqueueGameMessageAndWait(gupm); } private void SupplyControl_SupplyClick(object sender, RoutedEventArgs e) { IDisplayable supply = (e.Source as SupplyControl)?.Supply; if (!(supply is IBuyable buyable)) return; WaitCallback wcb = Bought; var gbm = new GameBuyMessage(wcb, Game.ActivePlayer, buyable) { Message = $"{Game.ActivePlayer} buying {supply}" }; EnqueueGameMessageAndWait(gbm); } private void Bought(object target) { if (Dispatcher.CheckAccess()) { if (Game.ActivePlayer == null || (Game.ActivePlayer.Phase != PhaseEnum.Buy && Game.ActivePlayer.Phase != PhaseEnum.BuyTreasure)) return; if (Game.ActivePlayer.TokenPiles[DominionBase.Cards.Empires.TypeClass.DebtToken].Any()) return; if (Game.ActivePlayer.Buys == 0) { if (!Game.ActivePlayer.Hand[Categories.Night].Any()) BTurnDone_Click(bTurnDone, null); else BNightPhase_Click(bNightPhase, null); } // If the only cards we can buy are Copper, Curse, & Ruins and there are no Goons or Merchant Guild cards // in play and the proper setting is enabled, it will automatically skip the remaining buys else if (_settings.NeverBuyCopperOrCurseExceptWhenGoonsIsInPlay && Game.ActivePlayer.InPlay[c => c is DominionBase.Cards.Prosperity.Goons || c is DominionBase.Cards.Prosperity2ndEdition.Goons || c is DominionBase.Cards.Guilds.MerchantGuild || c is DominionBase.Cards.Guilds2ndEdition.MerchantGuild ].Count == 0 && Game.Table.TableEntities.Values.OfType().Count(tableItem => tableItem.TableableType != DominionBase.Cards.Universal.TypeClass.Copper && tableItem.TableableType != DominionBase.Cards.Universal.TypeClass.Curse && tableItem.TableableType != DominionBase.Cards.DarkAges.TypeClass.RuinsSupply && tableItem.CanBuy(Game.ActivePlayer)) == 0) { if (!Game.ActivePlayer.Hand[Categories.Night].Any()) BTurnDone_Click(bTurnDone, null); else BNightPhase_Click(bNightPhase, null); } UpdateDisplay(); } else { WaitCallback wcb = Bought; Dispatcher.BeginInvoke(wcb, DispatcherPriority.Normal, target); } } private void CardCollectionControl_CardCollectionControlClick(object sender, RoutedEventArgs e) { if (Game?.ActivePlayer == null || !(e.OriginalSource is CardStackControl csc)) return; if ((Game.ActivePlayer.Phase == PhaseEnum.Action && (csc.ClickedCard.Category.HasFlag(Categories.Action) || csc.ClickedCard.Category.HasFlag(Categories.Treasure))) || ((Game.ActivePlayer.Phase == PhaseEnum.ActionTreasure || Game.ActivePlayer.Phase == PhaseEnum.BuyTreasure) && csc.ClickedCard.Category.HasFlag(Categories.Treasure)) || ((Game.ActivePlayer.Phase == PhaseEnum.Night && csc.ClickedCard.Category.HasFlag(Categories.Night)))) { if (_settings.PromptUnplayedActions && Game.ActivePlayer.Phase == PhaseEnum.Action && Game.ActivePlayer.Hand[Categories.Action].Any() && (Game.ActivePlayer.Actions > 0 || Game.ActivePlayer.TokenPiles[DominionBase.Cards.Renaissance.TypeClass.Villager].Count > 0) && (!csc.ClickedCard.Category.HasFlag(Categories.Action))) { if (wMessageBox.Show("You have unplayed action cards left. Are you sure you want to do this?", "Please confirm", MessageBoxButton.YesNo) != MessageBoxResult.Yes) return; } // Card is clicked to be played // holding down Ctrl turns off prompting for Ways for the action being clicked if (Keyboard.Modifiers.HasFlag(ModifierKeys.Control)) { foreach (var way in stackPanelSupplyPiles.Children.OfType().SelectMany(sp => sp.Children.OfType()).Where(sc => sc.Supply?.Category.HasFlag(Categories.Way) == true)) uccChooser.AutoYields.Add(new AutoYield { Event = typeof(CardFollowingInstructionsEventArgs), Duration = AutoYieldDuration.Action, Action = AutoYieldAction.Skip, Source = way.Supply.Type, SourceValue = ResourcesHelper.Get("ResolveViaCard").Replace("{card}", way.Supply.Name), Target = csc.ClickedCard.Type }); } WaitCallback wcb = UpdateDisplayTarget; var gpm = new GamePlayMessage(wcb, Game.ActivePlayer, csc.ClickedCard) { Message = $"{Game.ActivePlayer} playing {csc.ClickedCard}" }; EnqueueGameMessageAndWait(gpm); } } private void BTurnDone_Click(object sender, RoutedEventArgs e) { if (sender is Control control) control.IsEnabled = false; if (_settings.PromptUnspentBuysTreasure && Game.ActivePlayer.Buys > 0 && Game.ActivePlayer.Phase != PhaseEnum.Night && (!_settings.PromptUnspentBuysTreasureOnlyNotCopperCurseRuins || Game.Table.TableEntities.Values.OfType().Count(tableItem => tableItem.TableableType != DominionBase.Cards.Universal.TypeClass.Copper && tableItem.TableableType != DominionBase.Cards.Universal.TypeClass.Curse && tableItem.TableableType != DominionBase.Cards.DarkAges.TypeClass.RuinsSupply && tableItem.CanBuy(Game.ActivePlayer)) > 0)) { if (wMessageBox.Show("You have unused coins and buys left. Are you sure you want to do this?", "Please confirm", MessageBoxButton.YesNo) != MessageBoxResult.Yes) { if (sender is Control o) o.IsEnabled = true; return; } } if (Game.ActivePlayer.Hand[Categories.Night].Any()) { if (wMessageBox.Show("You have unplayed night cards left. Are you sure you want to do this?", "Please confirm", MessageBoxButton.YesNo) != MessageBoxResult.Yes) return; } WaitCallback wcb = UpdateDisplayTarget; var getm = new GameEndTurnMessage(wcb, Game.ActivePlayer) { Message = $"{Game.ActivePlayer} ending turn" }; EnqueueGameMessageAndWait(getm); } private void Game_Exit_Click(object sender, RoutedEventArgs e) { Close(); } private void Game_Settings_Click(object sender, RoutedEventArgs e) { var settingsDialogBox = new wSettings(ref _settings) { Owner = this }; if (settingsDialogBox.ShowDialog() == true) { Settings.Save(); Context.Settings = null; Context.Settings = Settings; SetGameBackground(); LayoutSupplyPiles(); } } private void CurrentGame_Save_Click(object sender, RoutedEventArgs e) { var sfd = new Microsoft.Win32.SaveFileDialog { DefaultExt = ".save", Filter = "Saved game files (.save)|*.save", FileName = "dominion_net.save" }; var dialogResult = sfd.ShowDialog(this); if (dialogResult == true) { Game.Save(sfd.FileName); } } private void Game_Load_Click(object sender, RoutedEventArgs e) { var ofd = new Microsoft.Win32.OpenFileDialog { DefaultExt = ".save", Filter = "Saved game files (.save)|*.save" }; var dialogResult = ofd.ShowDialog(this); if (dialogResult != true) return; Game = new Game(); Game.Load(ofd.FileName); // Clean out the Image Repository before starting a new game -- // so we don't allocate too much memory for cards we're not even using Caching.ImageRepository.Reset(); tiGamePoints.Visibility = Visibility.Collapsed; dpGameInfo.Visibility = Visibility.Visible; glMain.TearDown(); glMain.Clear(); LayoutSupplyPiles(); while (tcAreas.Items.Count > 3) tcAreas.Items.RemoveAt(tcAreas.Items.Count - 1); dpMatsandPiles.Children.Clear(); dpGameStuff.Children.Clear(); _tradeRouteLabel = null; // Try to force garbage collection to save some memory GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced); // ------------------------------------------------------------------------------- Game.GameEndedEvent += Game_GameEndedEvent; Settings.PlayerSettings.ForEach(ps => glMain.AddPlayerColor(ps.Name, ps.UIColor)); if (Game.ActivePlayer != null) { glMain.NewSection($"Game loaded from {Game.StartTime}."); glMain.Log("It's ", Game.ActivePlayer, "'s turn"); } Game.Table.TokenPiles.TokenCollectionsChanged += TokenPiles_TokenCollectionsChanged; Game.Table.Trash.PileChanged += Trash_PileChanged; Trash_PileChanged(Game.Table.Trash, new DominionBase.Piles.PileChangedEventArgs(Operation.Refresh)); Game.GameMessage += Game_GameMessage; foreach (var player in Game.Players.FindAll(p => p.PlayerType == PlayerType.Human)) player.Choose = Player_Choose; _player = Game.Players.Any(player => player.PlayerType == PlayerType.Human) ? Game.Players.OfType().First() : null; // ------------------------------------------------------------------------------- Type[] specialTypes = { DominionBase.Cards.Promotional.TypeClass.BlackMarketSupply, DominionBase.Cards.Cornucopia.TypeClass.PrizeSupply, DominionBase.Cards.Cornucopia2ndEdition.TypeClass.PrizeSupply, DominionBase.Cards.DarkAges.TypeClass.Madman, DominionBase.Cards.DarkAges.TypeClass.Mercenary, DominionBase.Cards.DarkAges.TypeClass.Spoils, DominionBase.Cards.DarkAges2ndEdition.TypeClass.Madman, DominionBase.Cards.DarkAges2ndEdition.TypeClass.Mercenary, DominionBase.Cards.Adventures.TypeClass.TreasureHunter, DominionBase.Cards.Adventures.TypeClass.Warrior, DominionBase.Cards.Adventures.TypeClass.Hero, DominionBase.Cards.Adventures.TypeClass.Champion, DominionBase.Cards.Adventures.TypeClass.Soldier, DominionBase.Cards.Adventures.TypeClass.Fugitive, DominionBase.Cards.Adventures.TypeClass.Disciple, DominionBase.Cards.Adventures.TypeClass.Teacher, DominionBase.Cards.Adventures2ndEdition.TypeClass.TreasureHunter, DominionBase.Cards.Adventures2ndEdition.TypeClass.Warrior, DominionBase.Cards.Adventures2ndEdition.TypeClass.Hero, DominionBase.Cards.Adventures2ndEdition.TypeClass.Champion, DominionBase.Cards.Nocturne.TypeClass.Boons, DominionBase.Cards.Nocturne.TypeClass.DruidBoons, DominionBase.Cards.Nocturne.TypeClass.Hexes, DominionBase.Cards.Nocturne.TypeClass.Bat, DominionBase.Cards.Nocturne.TypeClass.Ghost, DominionBase.Cards.Nocturne.TypeClass.Imp, DominionBase.Cards.Nocturne.TypeClass.WillOWisp, DominionBase.Cards.Nocturne.TypeClass.Wish, DominionBase.Cards.Menagerie.TypeClass.Horse }; foreach (var specialType in specialTypes) { if (!Game.Table.SpecialPiles.ContainsKey(specialType)) continue; _matEventHandlers[specialType] = GamePile_PileChanged; Game.Table.SpecialPiles[specialType].PileChanged += _matEventHandlers[specialType]; GamePile_PileChanged(Game.Table.SpecialPiles[specialType], new DominionBase.Piles.PileChangedEventArgs(Operation.Refresh)); } if (Game.Table.TableEntities.ContainsKey(DominionBase.Cards.Prosperity.TypeClass.TradeRoute) || Game.Table.TableEntities.ContainsKey(DominionBase.Cards.Prosperity2ndEdition.TypeClass.TradeRoute)) { if (dpGameStuff.Children.Count > 0) { var bDiv = new Border { BorderThickness = new Thickness(2), BorderBrush = Brushes.Black }; Panel.SetZIndex(bDiv, 1); DockPanel.SetDock(bDiv, Dock.Left); dpGameStuff.Children.Add(bDiv); } var lTradeRoute = new Label { Content = "Trade Route Tokens:", FontSize = 16d, FontWeight = FontWeights.Bold, HorizontalContentAlignment = HorizontalAlignment.Right, Background = Caching.BrushRepository.GetBackgroundBrush(Categories.Treasure) }; DockPanel.SetDock(lTradeRoute, Dock.Left); dpGameStuff.Children.Add(lTradeRoute); _tradeRouteLabel = new Label { Content = Game.Table.TokenPiles[DominionBase.Cards.Prosperity.TypeClass.TradeRouteToken].Count, FontWeight = FontWeights.Bold, VerticalAlignment = VerticalAlignment.Stretch, VerticalContentAlignment = VerticalAlignment.Center, Background = Caching.BrushRepository.GetBackgroundBrush(Categories.Treasure), Padding = new Thickness(0, 0, 5, 0), BorderThickness = new Thickness(0, 0, 1, 0) }; DockPanel.SetDock(_tradeRouteLabel, Dock.Left); dpGameStuff.Children.Add(_tradeRouteLabel); } bStuffDivider.Visibility = dpGameStuff.Children.Count > 0 ? Visibility.Visible : Visibility.Collapsed; foreach (var player in Game.Players) { var tiPlayer = new TabItem(); var dpHeader = new DockPanel(); var iHeader = new Image { Stretch = Stretch.None, Margin = new Thickness(0, 0, 5, 0) }; DockPanel.SetDock(iHeader, Dock.Left); switch (player.PlayerType) { case PlayerType.Human: iHeader.Source = (BitmapImage)Resources["imHuman"]; break; case PlayerType.Computer: iHeader.Source = (BitmapImage)Resources["imComputer"]; break; } dpHeader.Children.Add(iHeader); var tbHeader = new TextBlock { Text = player.Name }; dpHeader.Children.Add(tbHeader); var iFooter = new Image { Visibility = Visibility.Hidden, Stretch = Stretch.None, Margin = new Thickness(5, 0, 0, 0) }; DockPanel.SetDock(iFooter, Dock.Right); iFooter.Source = (BitmapImage)Resources["imEffects"]; dpHeader.Children.Add(iFooter); tiPlayer.Header = dpHeader; tcAreas.Items.Add(tiPlayer); var ucpdPlayer = new UcPlayerDisplay(); tiPlayer.Content = ucpdPlayer; ucpdPlayer.IsUIPlayer = player == _player; ucpdPlayer.Player = player; var playerSettings = _settings.PlayerSettings.FirstOrDefault(ps => ps.Name == player.Name); if (playerSettings != null) { var hlsValue = HlsColor.RgbToHls(playerSettings.UIColor); var cPlayer = HlsColor.HlsToRgb(hlsValue.H, hlsValue.L * 1.125, hlsValue.S * 0.95, hlsValue.A); var gsc = new GradientStopCollection { new GradientStop(cPlayer, 0), new GradientStop(playerSettings.UIColor, 0.25), new GradientStop(playerSettings.UIColor, 0.75), new GradientStop(cPlayer, 1) }; gsc.Freeze(); tiPlayer.Background = new LinearGradientBrush(gsc, 0); //tiPlayer.Background = new SolidColorBrush(playerSettings.UIColor); ucpdPlayer.ColorFocus = playerSettings.UIColor; ucpdPlayer.DisplayVictoryPoints = _settings.DisplayVictoryPoints; } var tt = new ToolTip(); var ucpo = new UcPlayerOverview { Player = player }; tt.Content = ucpo; ToolTipService.SetToolTip(dpHeader, tt); if (Settings.ToolTipShowDuration == ToolTipShowDuration.Off) ToolTipService.SetIsEnabled(dpHeader, false); else { ToolTipService.SetIsEnabled(dpHeader, true); ToolTipService.SetShowDuration(dpHeader, (int)Settings.ToolTipShowDuration); } dpHeader.MouseDown += TiPlayer_MouseDown; dpHeader.MouseUp += TiPlayer_MouseUp; player.Revealed.PileChanged += Revealed_PileChanged; player.SetAside.PileChanged += SetAside_PileChanged; player.BenefitReceivingInitiated += Player_BenefitReceiving; //player.DiscardPile.PileChanged += DiscardPile_PileChanged; player.CardPlaying += Player_CardPlaying; player.CardPlayFinished += Player_CardPlayed; player.CardUndoPlaying += Player_CardUndoPlaying; player.CardUndoPlayed += Player_CardUndoPlayed; player.CardBuying += Player_CardBuying; player.CardBought += Player_CardBought; player.CardBuyFinished += Player_CardBuyFinished; player.CardGaining += Player_CardGaining; player.CardGainedInto += Player_CardGainedInto; player.CardGainFinished += Player_CardGainFinished; player.TokenPlaying += Player_TokenPlaying; player.TokenPlayed += Player_TokenPlayed; player.Trashing += Player_Trashing; player.TrashedFinished += Player_Trashed; player.Calling += Player_Calling; player.CalledFinished += Player_Called; player.PhaseChangedFinished += Player_PhaseChangedEvent; player.PlayerModeChanged += Player_PlayerModeChangedEvent; player.CardsDrawn += Player_CardsDrawn; player.TurnStarting += Player_TurnStarting; player.TurnEnded += Player_TurnEnded; player.ShufflingStart += Player_ShufflingStart; player.CardsAddedToDeck += Player_CardsAddedToDeck; player.CardsAddedToHand += Player_CardsAddedToHand; player.CardsDiscarded += Player_CardsDiscarded; player.PlayerMats.CardMatsChanged += PlayerMats_DecksChanged; player.TokenPiles.TokenCollectionsChanged += PlayerTokenPiles_TokenCollectionsChanged; player.BenefitsChanged += Player_BenefitsChanged; if (player == _player) { tcAreas.SelectedItem = tiPlayer; player.CardReceived += Player_CardReceived; } } miNewGame.IsEnabled = false; miLoadGame.IsEnabled = false; miEndGame.IsEnabled = true; miSaveGame.IsEnabled = false; _gameThread = new Thread(Game.StartAsync); _gameThread.Start(); if (Game.ActivePlayer != null) { if (Game.Players[0] != Game.ActivePlayer) glMain.NewTurn(Game.TurnsTaken.TurnNumber(Game.ActivePlayer)); Player_BenefitsChanged(Game.ActivePlayer, new DominionBase.Players.BenefitsChangedEventArgs(Game.ActivePlayer) //{ // Actions = game.ActivePlayer.Actions, // Buys = game.ActivePlayer.Buys, // Currency = game.ActivePlayer.Currency, // Player = game.ActivePlayer //} ); } if (_player.Phase == PhaseEnum.Starting || _player.Phase == PhaseEnum.Buy || _player.PlayerMode == PlayerMode.Waiting) CheckBuyable(_player); else ClearBuyable(); UpdateDisplay(); } private void Game_NewGame_Click(object sender, RoutedEventArgs e) { if (Game != null && Game.State == GameState.Running) { _startingNewGame = true; if (!IsEndGameOk()) return; } else { var settings = new GameSettings { IdenticalStartingHands = _settings.IdenticalStartingHands, RandomAI_Unique = _settings.RandomAIUnique, ColonyPlatinumUsage = _settings.ColonyPlatinumUsage, ShelterUsage = _settings.ShelterUsage, LandscapeCardUsage = _settings.LandscapeCardUsage, EditionUsage = _settings.EditionUsage, AISpeed = _settings.AISpeed }; settings.Constraints.AddRange(_settings.Constraints); settings.LandscapeCardConstraints.AddRange(_settings.LandscapeCardConstraints); if (_settings.UsePreset) { settings.Constraints.Clear(); settings.LandscapeCardConstraints.Clear(); try { settings.Preset = _settings.Presets.SingleOrDefault(p => p.Name == _settings.PresetName); } catch { } if (settings.Preset == null) { wMessageBox.Show( $"Cannot find preset named \"{_settings.PresetName}\"!{Environment.NewLine}Please check your presets.txt file.", "Cannot find preset", MessageBoxButton.OK, MessageBoxImage.Exclamation); return; } } settings.CardSettings.AddRange(_settings.CardSettings); settings.RandomAI_AllowedAIs.Clear(); settings.RandomAI_AllowedAIs.AddRange(_settings.RandomAIAllowedAIs); StartGame(settings); Context.Settings = Settings; SetGameBackground(); } } private void GamePile_PileChanged(object sender, DominionBase.Piles.PileChangedEventArgs e) { if (Dispatcher.CheckAccess()) { if (sender is DominionBase.Piles.Supply supply) { string wpName = $"wp{supply.TableableType.Name}"; string cccName = $"card{supply.TableableType.Name}"; var cccPileName = supply.Randomizer.Name; if (supply.TableableType == DominionBase.Cards.Promotional.TypeClass.BlackMarketSupply) { cccPileName = "Black Market cards"; } else if (supply.TableableType == DominionBase.Cards.Cornucopia.TypeClass.PrizeSupply || supply.TableableType == DominionBase.Cards.Cornucopia2ndEdition.TypeClass.PrizeSupply ) { cccPileName = "Prizes"; } else if (supply.TableableType == DominionBase.Cards.Adventures.TypeClass.TreasureHunter || supply.TableableType == DominionBase.Cards.Adventures.TypeClass.Warrior || supply.TableableType == DominionBase.Cards.Adventures.TypeClass.Hero || supply.TableableType == DominionBase.Cards.Adventures.TypeClass.Champion || supply.TableableType == DominionBase.Cards.Adventures2ndEdition.TypeClass.TreasureHunter || supply.TableableType == DominionBase.Cards.Adventures2ndEdition.TypeClass.Warrior || supply.TableableType == DominionBase.Cards.Adventures2ndEdition.TypeClass.Hero || supply.TableableType == DominionBase.Cards.Adventures2ndEdition.TypeClass.Champion ) { wpName = "wpPageSet"; } else if (supply.TableableType == DominionBase.Cards.Adventures.TypeClass.Soldier || supply.TableableType == DominionBase.Cards.Adventures.TypeClass.Fugitive || supply.TableableType == DominionBase.Cards.Adventures.TypeClass.Disciple || supply.TableableType == DominionBase.Cards.Adventures.TypeClass.Teacher) { wpName = "wpPeasantSet"; } var wpSpecialPile = dpMatsandPiles.Children.OfType().SingleOrDefault(wp => wp.Name == wpName); if (wpSpecialPile == null) { if (dpMatsandPiles.Children.Count > 0) { var bDiv = new Border { BorderThickness = new Thickness(2), BorderBrush = Brushes.Black }; DockPanel.SetDock(bDiv, Dock.Top); dpMatsandPiles.Children.Add(bDiv); } wpSpecialPile = new WrapPanel { Name = wpName, Orientation = CtrlOrientation.Horizontal }; DockPanel.SetDock(wpSpecialPile, Dock.Top); dpMatsandPiles.Children.Add(wpSpecialPile); } var cccSpecialPile = wpSpecialPile.Children.OfType().SingleOrDefault(ccc => ccc.Name == cccName); if (cccSpecialPile == null) { cccSpecialPile = new CardCollectionControl { Foreground = Settings.ForegroundBrush, Name = cccName, Padding = new Thickness(0), PileName = cccPileName, CardSize = CardSize.Text, ExactCount = true, IsCardsVisible = true, IsDisplaySorted = true, Phase = PhaseEnum.Action, PlayerMode = PlayerMode.Waiting }; wpSpecialPile.Children.Add(cccSpecialPile); } cccSpecialPile.Pile = supply; return; } if (sender is DominionBase.Piles.BoonSupply boons) { string wpName = $"wp{boons.TableableType.Name}"; string spcName = $"card{boons.TableableType.Name}"; var spcPileName = boons.TableableType.Name; var wpSpecialPile = dpMatsandPiles.Children.OfType().SingleOrDefault(wp => wp.Name == wpName); if (wpSpecialPile == null) { if (dpMatsandPiles.Children.Count > 0) { var bDiv = new Border { BorderThickness = new Thickness(2), BorderBrush = Brushes.Black }; DockPanel.SetDock(bDiv, Dock.Top); dpMatsandPiles.Children.Add(bDiv); } wpSpecialPile = new WrapPanel { Name = wpName, Orientation = CtrlOrientation.Horizontal }; DockPanel.SetDock(wpSpecialPile, Dock.Top); dpMatsandPiles.Children.Add(wpSpecialPile); } var spcSpecialPile = wpSpecialPile.Children.OfType().SingleOrDefault(spc => spc.Name == spcName); if (spcSpecialPile == null) { spcSpecialPile = new ShuffleablePileControl { Foreground = Settings.ForegroundBrush, Name = spcName, Padding = new Thickness(0), PileName = spcPileName, CardSize = CardSize.Text, Phase = PhaseEnum.Action, PlayerMode = PlayerMode.Waiting }; wpSpecialPile.Children.Add(spcSpecialPile); } spcSpecialPile.Pile = boons; return; } if (sender is DominionBase.Piles.HexSupply hexes) { string wpName = $"wp{hexes.TableableType.Name}"; string spcName = $"card{hexes.TableableType.Name}"; var spcPileName = hexes.TableableType.Name; var wpSpecialPile = dpMatsandPiles.Children.OfType().SingleOrDefault(wp => wp.Name == wpName); if (wpSpecialPile == null) { if (dpMatsandPiles.Children.Count > 0) { var bDiv = new Border { BorderThickness = new Thickness(2), BorderBrush = Brushes.Black }; DockPanel.SetDock(bDiv, Dock.Top); dpMatsandPiles.Children.Add(bDiv); } wpSpecialPile = new WrapPanel { Name = wpName, Orientation = CtrlOrientation.Horizontal }; DockPanel.SetDock(wpSpecialPile, Dock.Top); dpMatsandPiles.Children.Add(wpSpecialPile); } var spcSpecialPile = wpSpecialPile.Children.OfType().SingleOrDefault(spc => spc.Name == spcName); if (spcSpecialPile == null) { spcSpecialPile = new ShuffleablePileControl { Foreground = Settings.ForegroundBrush, Name = spcName, Padding = new Thickness(0), PileName = spcPileName, CardSize = CardSize.Text, Phase = PhaseEnum.Action, PlayerMode = PlayerMode.Waiting }; wpSpecialPile.Children.Add(spcSpecialPile); } spcSpecialPile.Pile = hexes; return; } if (sender is DominionBase.Cards.Nocturne.DruidBoons druidBoons) { string wpName = $"wp{druidBoons.TableableType.Name}"; string cccName = $"card{druidBoons.TableableType.Name}"; var cccPileName = druidBoons.TableableType.Name; var wpSpecialPile = dpMatsandPiles.Children.OfType().SingleOrDefault(wp => wp.Name == wpName); if (wpSpecialPile == null) { if (dpMatsandPiles.Children.Count > 0) { var bDiv = new Border { BorderThickness = new Thickness(2), BorderBrush = Brushes.Black }; DockPanel.SetDock(bDiv, Dock.Top); dpMatsandPiles.Children.Add(bDiv); } wpSpecialPile = new WrapPanel { Name = wpName, Orientation = CtrlOrientation.Horizontal }; DockPanel.SetDock(wpSpecialPile, Dock.Top); dpMatsandPiles.Children.Add(wpSpecialPile); } var cccSpecialPile = wpSpecialPile.Children.OfType().SingleOrDefault(ccc => ccc.Name == cccName); if (cccSpecialPile == null) { cccSpecialPile = new CardCollectionControl { Foreground = Settings.ForegroundBrush, Name = cccName, Padding = new Thickness(0), PileName = cccPileName, CardSize = CardSize.Text, ExactCount = true, IsCardsVisible = true, IsDisplaySorted = true, Phase = PhaseEnum.Action, PlayerMode = PlayerMode.Waiting }; wpSpecialPile.Children.Add(cccSpecialPile); } cccSpecialPile.Pile = druidBoons; return; } } else { Dispatcher.BeginInvoke( new EventHandler>(GamePile_PileChanged), DispatcherPriority.Normal, sender, e); } } private void Window_Closing(object sender, CancelEventArgs e) { if (Game != null) { if (!IsEndGameOk()) { e.Cancel = true; return; } if (uccChooser.Visibility == Visibility.Visible) uccChooser.Player.WaitEvent.Set(); } _gameThread?.Join(); _settings.WindowSize = new Size(Width, Height); _settings.WindowState = WindowState; _settings.Save(); } private void BBuyPhase_Click(object sender, RoutedEventArgs e) { if (_settings.PromptUnplayedActions && Game.ActivePlayer.Phase == PhaseEnum.Action && Game.ActivePlayer.Hand[Categories.Action].Any() && (Game.ActivePlayer.Actions > 0 || Game.ActivePlayer.TokenPiles[DominionBase.Cards.Renaissance.TypeClass.Villager].Count > 0)) { if (wMessageBox.Show("You have unplayed actions left. Are you sure you want to do this?", "Please confirm", MessageBoxButton.YesNo) != MessageBoxResult.Yes) return; } if (sender is Control control) control.IsEnabled = false; WaitCallback wcb = UpdateDisplayTarget; var ggtbpm = new GameGoToBuyTreasurePhaseMessage(wcb, Game.ActivePlayer) { Message = $"{Game.ActivePlayer} going to Buy Treasure phase" }; EnqueueGameMessageAndWait(ggtbpm); } private void BNightPhase_Click(object sender, RoutedEventArgs e) { if (_settings.PromptUnplayedActions && Game.ActivePlayer.Phase == PhaseEnum.Action && Game.ActivePlayer.Hand[Categories.Action].Any() && (Game.ActivePlayer.Actions > 0 || Game.ActivePlayer.TokenPiles[DominionBase.Cards.Renaissance.TypeClass.Villager].Count > 0)) { if (wMessageBox.Show("You have unplayed actions left. Are you sure you want to do this?", "Please confirm", MessageBoxButton.YesNo) != MessageBoxResult.Yes) return; } if (_settings.PromptUnspentBuysTreasure && Game.ActivePlayer.Buys > 0 && (!_settings.PromptUnspentBuysTreasureOnlyNotCopperCurseRuins || Game.Table.TableEntities.Values.OfType().Count(tableItem => tableItem.TableableType != DominionBase.Cards.Universal.TypeClass.Copper && tableItem.TableableType != DominionBase.Cards.Universal.TypeClass.Curse && tableItem.TableableType != DominionBase.Cards.DarkAges.TypeClass.RuinsSupply && tableItem.CanBuy(Game.ActivePlayer)) > 0)) { if (wMessageBox.Show("You have unused coins and buys left. Are you sure you want to do this?", "Please confirm", MessageBoxButton.YesNo) != MessageBoxResult.Yes) { if (sender is Control o) o.IsEnabled = true; return; } } if (sender is Control control) control.IsEnabled = false; WaitCallback wcb = UpdateDisplayTarget; var ggtbpm = new GameGoToNightPhaseMessage(wcb, Game.ActivePlayer) { Message = $"{Game.ActivePlayer} going to Night phase" }; EnqueueGameMessageAndWait(ggtbpm); } private void TcAreas_SelectionChanged(object sender, SelectionChangedEventArgs e) { foreach (var removedTab in e.RemovedItems.OfType()) { if (!(removedTab.Content is UcPlayerDisplay uPlayerDisp)) continue; uPlayerDisp.IsActive = false; } foreach (var addedTab in e.AddedItems.OfType()) { if (!(addedTab.Content is UcPlayerDisplay uPlayerDisp)) continue; uPlayerDisp.IsActive = true; addedTab.InvalidateVisual(); } } private static void OpenUrl(string target) { try { Process.Start(target); } catch (Win32Exception noBrowser) { if (noBrowser.ErrorCode == -2147467259) wMessageBox.Show(noBrowser.Message); } catch (Exception other) { wMessageBox.Show(other.Message); } } private void Help_OfficialSite_Click(object sender, RoutedEventArgs e) { OpenUrl("http://www.riograndegames.com/games.html?id=278"); } private void Help_DeveloperSite_Click(object sender, RoutedEventArgs e) { OpenUrl("http://dominion.technowall.net/"); } private void Help_CheckForUpdates_Click(object sender, RoutedEventArgs e) { try { CheckForUpdates(true); } catch { } } private void Help_CardViewer_Click(object sender, RoutedEventArgs e) { var wcv = new wCardViewer(); wcv.Show(); } private void Help_CardPreview_Click(object sender, RoutedEventArgs e) { if (_wcp == null) { _wcp = new wCardPreview(); _wcp.Closed += Wcp_Closed; _wcp.Show(); } else { _wcp.Focus(); } } private void Wcp_Closed(object sender, EventArgs e) { _wcp = null; } private void SvGame_ScrollChanged(object sender, ScrollChangedEventArgs e) { var sv = sender as ScrollViewer; if (sv.ExtentHeight == 0 || Math.Abs(sv.ExtentWidth) < 0.001) return; bGameHorizontal.Width = sv.ViewportWidth * sv.ViewportWidth / sv.ExtentWidth; bGameVertical.Height = sv.ViewportHeight * sv.ViewportHeight / sv.ExtentHeight; bGameHorizontal.Margin = new Thickness(sv.ViewportWidth * sv.HorizontalOffset / sv.ExtentWidth, 0, 0, 0); bGameVertical.Margin = new Thickness(0, sv.ViewportHeight * sv.VerticalOffset / sv.ExtentHeight, 0, 0); bOpacityLayerLeft.Visibility = bOpacityLayerRight.Visibility = Visibility.Visible; if (bGameHorizontal.Width >= sv.ViewportWidth) bOpacityLayerLeft.Visibility = bOpacityLayerRight.Visibility = Visibility.Collapsed; else if (bGameHorizontal.Margin.Left <= 0) bOpacityLayerLeft.Visibility = Visibility.Collapsed; else if (bGameHorizontal.Margin.Left + bGameHorizontal.Width >= sv.ViewportWidth) bOpacityLayerRight.Visibility = Visibility.Collapsed; bOpacityLayerTop.Visibility = bOpacityLayerBottom.Visibility = Visibility.Visible; if (bGameVertical.Height >= sv.ViewportHeight) bOpacityLayerTop.Visibility = bOpacityLayerBottom.Visibility = Visibility.Collapsed; else if (bGameVertical.Margin.Top <= 0) bOpacityLayerTop.Visibility = Visibility.Collapsed; else if (bGameVertical.Margin.Top + bGameVertical.Height >= sv.ViewportHeight) bOpacityLayerBottom.Visibility = Visibility.Collapsed; } private void Grid_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { var g = (Grid)sender; g.CaptureMouse(); g.Cursor = Cursors.ScrollNS; svGame.ScrollToVerticalOffset(e.GetPosition(g).Y / g.ActualHeight * svGame.ExtentHeight); } private void Grid_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) { var g = (Grid)sender; g.ReleaseMouseCapture(); g.Cursor = Cursors.Arrow; } private void Grid_MouseMove(object sender, MouseEventArgs e) { var g = (Grid)sender; if (g.IsMouseCaptured) svGame.ScrollToVerticalOffset(e.GetPosition(g).Y / g.ActualHeight * svGame.ExtentHeight); } private void Grid_MouseLeftButtonDown_1(object sender, MouseButtonEventArgs e) { var g = (Grid)sender; g.CaptureMouse(); g.Cursor = Cursors.ScrollWE; svGame.ScrollToHorizontalOffset(e.GetPosition(g).X / g.ActualWidth * svGame.ExtentWidth); } private void Grid_MouseMove_1(object sender, MouseEventArgs e) { var g = (Grid)sender; if (g.IsMouseCaptured) svGame.ScrollToHorizontalOffset(e.GetPosition(g).X / g.ActualWidth * svGame.ExtentWidth); } private void CheckForUpdates(bool forceCheck) { var assembly = Assembly.GetExecutingAssembly(); // If this isn't null, we're trying to update to a version if (!forceCheck && WinApplication.Current.Properties["Update"] != null) { using (var wClient = new System.Net.WebClient()) { wClient.DownloadFileCompleted += WClient_DownloadFileCompleted; wClient.DownloadProgressChanged += WClient_DownloadProgressChanged; wClient.DownloadFileAsync( new Uri(WinApplication.Current.Properties["Update"].ToString()), Path.Combine(Path.GetDirectoryName(assembly.Location), "update.zip" )); } } else { if (WinApplication.Current.Properties["Updated"] != null && (bool)WinApplication.Current.Properties["Updated"]) { WinApplication.Current.Dispatcher.Invoke(() => wMessageBox.Show($"Successfully updated to latest version {assembly.GetName().Version}!", "Update complete!", MessageBoxButton.OK, MessageBoxImage.Information) ); _settings.UpdateAvailable = false; _settings.Save(); } var tempPath = Path.Combine(Path.GetDirectoryName(assembly.Location), "update"); // Blow away any existing files if (Directory.Exists(tempPath)) Directory.Delete(tempPath, true); WinApplication.Current.Dispatcher.Invoke(() => { iUpdate.Visibility = miDownload.Visibility = Visibility.Collapsed; }); // Only check for an update if we haven't checked in the last 24 hours // Or if we're forcing a check if (forceCheck || DateTime.Now - TimeSpan.FromHours(24) > _settings.LastUpdateCheck) { _latestVersionInfo = VersionChecker.GetLatestVersion(); _settings.LastUpdateCheck = DateTime.Now; var currentVersion = assembly.GetName().Version; if (_latestVersionInfo.IsVersionValid && _latestVersionInfo.IsNewerThan(currentVersion)) _settings.UpdateAvailable = true; else _settings.UpdateAvailable = false; if (forceCheck) WinApplication.Current.Dispatcher.Invoke(() => wMessageBox.Show( $"Your version: {currentVersion}{Environment.NewLine}Latest version: {_latestVersionInfo.Version}", "Version info", MessageBoxButton.OK, MessageBoxImage.Information )); _settings.Save(); } if (_settings.UpdateAvailable) { WinApplication.Current.Dispatcher.Invoke(() => { iUpdate.Visibility = miDownload.Visibility = Visibility.Visible; iUpdate.ToolTip = miDownload.ToolTip = "Update available"; }); } } } private void WClient_DownloadProgressChanged(object sender, System.Net.DownloadProgressChangedEventArgs e) { if (Dispatcher.CheckAccess()) { cpStatus.Content = $"Downloading {WinApplication.Current.Properties["Update"]}..."; pbStatus.Visibility = Visibility.Visible; pbStatus.Minimum = 0; pbStatus.Value = 0; pbStatus.Maximum = e.TotalBytesToReceive; pbStatus.Value = e.BytesReceived; pbStatus.ToolTip = $"{e.BytesReceived / 1024.0:0.00}KB / {e.TotalBytesToReceive / 1024.0:0.00}KB ({e.ProgressPercentage}%)"; } else { Dispatcher.BeginInvoke(new EventHandler(WClient_DownloadProgressChanged), DispatcherPriority.Normal, sender, e); } } private void WClient_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e) { if (Dispatcher.CheckAccess()) { cpStatus.Content = string.Empty; // "Hi!"; pbStatus.Visibility = Visibility.Collapsed; var a = Assembly.GetExecutingAssembly(); var di = new DirectoryInfo(Path.GetDirectoryName(a.Location)); var zipFile = Path.Combine(Path.GetDirectoryName(a.Location), "update.zip"); try { using (var fileStreamIn = File.OpenRead(zipFile)) using (var zipInStream = new ZipInputStream(fileStreamIn)) { ZipEntry zEntry; while ((zEntry = zipInStream.GetNextEntry()) != null) { var buffer = new byte[4096]; var fullZipToPath = Path.Combine(di.Parent?.FullName, zEntry.Name); var directoryName = Path.GetDirectoryName(zEntry.Name); if (!string.IsNullOrWhiteSpace(directoryName) && !Directory.Exists(directoryName)) Directory.CreateDirectory(directoryName); if (string.IsNullOrWhiteSpace(Path.GetFileName(fullZipToPath))) continue; using (var streamWriter = File.Create(fullZipToPath)) { ICSharpCode.SharpZipLib.Core.StreamUtils.Copy(zipInStream, streamWriter, buffer); } } } } catch (Exception ex) { wMessageBox.Show(ex.Message); wMessageBox.Show(ex.StackTrace); throw; } var tempFile = Path.Combine(di.Parent?.FullName, Path.GetFileName(a.Location)); Process.Start(tempFile, "-U"); Close(); } else { Dispatcher.BeginInvoke(new EventHandler(WClient_DownloadFileCompleted), DispatcherPriority.Normal, sender, e); } } private void Help_DownloadLatest_Click(object sender, RoutedEventArgs e) { var a = Assembly.GetExecutingAssembly(); var tempPath = Path.Combine(Path.GetDirectoryName(a.Location), "update"); // Blow away any existing files if (Directory.Exists(tempPath)) Directory.Delete(tempPath, true); Directory.CreateDirectory(tempPath); // Copy all DLLs to the temp Update directory -- these are the only ones that can't be overwritten foreach (var file in Directory.GetFiles(Path.GetDirectoryName(a.Location), "*", SearchOption.TopDirectoryOnly)) { if (file.ToLower().EndsWith(".dll") || file == a.Location) File.Copy(file, Path.Combine(tempPath, Path.GetFileName(file))); } var tempFile = Path.Combine(tempPath, Path.GetFileName(a.Location)); if (_latestVersionInfo == null) _latestVersionInfo = VersionChecker.GetLatestVersion(); Process.Start(tempFile, $"-u \"{_latestVersionInfo.FileUrl}\""); Close(); } private void Game_EndGame_Click(object sender, RoutedEventArgs e) { IsEndGameOk(); } private bool IsEndGameOk() { if (Game != null && Game.State == GameState.Running) { if (Game.State == GameState.Running && Game.Players.Contains(_player)) { var mbr = wMessageBox.Show("Do you want to abort the current game? This will count as a loss in your statistics.", "Please confirm", MessageBoxButton.YesNo, MessageBoxImage.None, MessageBoxResult.No); if (mbr != MessageBoxResult.Yes) return false; } if (uccChooser.Visibility == Visibility.Visible) { Game.Abort(); uccChooser.Player.WaitEvent.Set(); uccChooser.Player = null; uccChooser.Choice = null; uccChooser.IsReady = false; uccChooser.Visibility = Visibility.Collapsed; uccChooser.ClearAutoYields(AutoYieldDuration.Game); } var gem = new GameEndMessage(_player) { Message = $"{_player} ending game" }; EnqueueGameMessageAndWait(gem); } bTurnDone.IsEnabled = false; bPlayTreasures.Text = "Play _Treasures"; bPlayTreasures.IsEnabled = false; bDonePlayingTreasures.IsEnabled = false; bSpendVillager.IsEnabled = false; bSpendCoffers.IsEnabled = false; bPayOffDebtTokens.IsEnabled = false; bBuyPhase.IsEnabled = false; bNightPhase.IsEnabled = false; bUndo.IsEnabled = false; return true; } private void Game_Replay_Click(object sender, RoutedEventArgs e) { if (Game == null || Game.State == GameState.Running || Game.State == GameState.NotStarted) { miReplay.IsEnabled = false; return; } var settings = new GameSettings { IdenticalStartingHands = Game.Settings.IdenticalStartingHands, ColonyPlatinumUsage = ColonyPlatinumUsage.Never, ShelterUsage = ShelterUsage.Never, Preset = new Preset("Replay") }; foreach (var tableItem in Game.Table.TableEntities.Values) { if (tableItem.Location == Location.Kingdom && tableItem.Type.IsSubclassOf(typeof(Card))) settings.Preset.Cards.Add(Card.CreateInstance(tableItem.Type)); else if (tableItem.Location == Location.LandscapeCard && tableItem.Type.IsSubclassOf(typeof(Event))) settings.Preset.Cards.Add(Event.CreateInstance(tableItem.Type)); else if (tableItem.Location == Location.LandscapeCard && tableItem.Type.IsSubclassOf(typeof(Landmark))) settings.Preset.Cards.Add(Landmark.CreateInstance(tableItem.Type)); else if (tableItem.Location == Location.LandscapeCard && tableItem.Type.IsSubclassOf(typeof(Project))) settings.Preset.Cards.Add(Project.CreateInstance(tableItem.Type)); else if (tableItem.Location == Location.LandscapeCard && tableItem.Type.IsSubclassOf(typeof(Way))) settings.Preset.Cards.Add(Way.CreateInstance(tableItem.Type)); } switch (Game.Settings.ColonyPlatinumSelected) { case ColonyPlatinumSelected.Yes: settings.ColonyPlatinumUsage = ColonyPlatinumUsage.Always; break; case ColonyPlatinumSelected.No: settings.ColonyPlatinumUsage = ColonyPlatinumUsage.Never; break; } switch (Game.Settings.ShelterSelected) { case ShelterSelected.Yes: settings.ShelterUsage = ShelterUsage.Always; break; case ShelterSelected.No: settings.ShelterUsage = ShelterUsage.Never; break; } var copyOfPresetCards = new List(settings.Preset.Cards); foreach (var presetCard in copyOfPresetCards) presetCard.CheckSetup(settings.Preset, Game.Table); settings.RandomAI_Unique = Game.Settings.RandomAI_Unique; settings.RandomAI_AllowedAIs.Clear(); settings.RandomAI_AllowedAIs.AddRange(Game.Settings.RandomAI_AllowedAIs); settings.AISpeed = Game.Settings.AISpeed; StartGame(settings); } private void GridSplitter_MouseDoubleClick(object sender, MouseButtonEventArgs e) { var glc = new GridLengthConverter(); gMainDisplay.RowDefinitions[0].Height = GridLength.Auto; var convertFrom = glc.ConvertFrom(gMainDisplay.RowDefinitions[0].MinHeight + 4); if (convertFrom != null) gMainDisplay.RowDefinitions[0].Height = (GridLength)convertFrom; Debug.WriteLine($"Margin: {((GridSplitter)sender).Margin}"); Debug.WriteLine($"ActualHeight: {stackPanelSupplyPiles.ActualHeight}"); } private void CurrentGame_ViewGameLog_Click(object sender, RoutedEventArgs e) { if (File.Exists(glMain.LogFile)) Process.Start(glMain.LogFile); } private void MenuItem_SubmenuOpened(object sender, RoutedEventArgs e) { miViewGameLog.IsEnabled = File.Exists(glMain.LogFile); } private void MiLayoutChanged(object sender, RoutedEventArgs e) { Settings.Save(); SetGameBackground(); LayoutSupplyPiles(); } private void Window_KeyDown(object sender, KeyEventArgs e) { if (e.Key == Key.LeftCtrl || e.Key == Key.RightCtrl || Keyboard.Modifiers.HasFlag(ModifierKeys.Control)) { Context.KeysPressed |= ModifierKeys.Control; // This is nasty and I hate it. It needs to be converted to MVVM for sweet, sweet slickness foreach (var supply in stackPanelSupplyPiles.Children.OfType().SelectMany(sp => sp.Children.OfType())) supply.ModifierKeysPressed = Context.KeysPressed; } } private void Window_KeyUp(object sender, KeyEventArgs e) { if (e.Key == Key.LeftCtrl || e.Key == Key.RightCtrl || !Keyboard.Modifiers.HasFlag(ModifierKeys.Control)) { Context.KeysPressed &= ~ModifierKeys.Control; // This is nasty and I hate it. It needs to be converted to MVVM for sweet, sweet slickness foreach (var supply in stackPanelSupplyPiles.Children.OfType().SelectMany(sp => sp.Children.OfType())) supply.ModifierKeysPressed = Context.KeysPressed; } } } }