From 6ac5a373cc53fde94476650314daa2309611f429 Mon Sep 17 00:00:00 2001 From: TrippSC2 Date: Sun, 29 Mar 2020 15:29:40 -0400 Subject: [PATCH] Implemented Undo/Redo. --- OpenTracker.Setup/OpenTracker.Setup.vdproj | 6 +- OpenTracker/Actions/AddItem.cs | 25 ++ OpenTracker/Actions/ChangeBoss.cs | 29 ++ OpenTracker/Actions/ChangeBossShuffle.cs | 29 ++ .../Actions/ChangeDungeonItemShuffle.cs | 30 ++ OpenTracker/Actions/ChangeEnemyShuffle.cs | 29 ++ OpenTracker/Actions/ChangeEntranceShuffle.cs | 29 ++ OpenTracker/Actions/ChangeItemPlacement.cs | 35 +++ OpenTracker/Actions/ChangePrize.cs | 29 ++ OpenTracker/Actions/ChangeWorldState.cs | 30 ++ OpenTracker/Actions/ClearLocation.cs | 116 +++++++ OpenTracker/Actions/CollectSection.cs | 77 +++++ OpenTracker/Actions/CycleItem.cs | 31 ++ OpenTracker/Actions/MarkSection.cs | 30 ++ OpenTracker/Actions/PinLocation.cs | 39 +++ OpenTracker/Actions/RemoveItem.cs | 25 ++ OpenTracker/Actions/TogglePrize.cs | 25 ++ OpenTracker/Actions/UncollectSection.cs | 48 +++ OpenTracker/Actions/UnpinLocation.cs | 32 ++ OpenTracker/Interfaces/IItemControlVM.cs | 2 +- OpenTracker/Interfaces/IUndoable.cs | 8 + OpenTracker/OpenTracker.csproj | 6 +- OpenTracker/Utils/ObservableStack.cs | 67 ++++ OpenTracker/ViewModels/BossControlVM.cs | 21 +- .../ViewModels/DungeonItemControlVM.cs | 13 +- .../ViewModels/DungeonPrizeControlVM.cs | 25 +- OpenTracker/ViewModels/ItemControlVM.cs | 27 +- OpenTracker/ViewModels/MainWindowVM.cs | 289 +++++++++++------- OpenTracker/ViewModels/MapControlVM.cs | 9 +- .../ViewModels/MapLocationControlVM.cs | 39 ++- .../ViewModels/PinnedLocationControlVM.cs | 12 +- OpenTracker/ViewModels/SectionControlVM.cs | 72 +++-- OpenTracker/ViewModels/UndoRedoManager.cs | 55 ++++ OpenTracker/Views/MainWindow.xaml | 103 +++++-- 34 files changed, 1208 insertions(+), 234 deletions(-) create mode 100644 OpenTracker/Actions/AddItem.cs create mode 100644 OpenTracker/Actions/ChangeBoss.cs create mode 100644 OpenTracker/Actions/ChangeBossShuffle.cs create mode 100644 OpenTracker/Actions/ChangeDungeonItemShuffle.cs create mode 100644 OpenTracker/Actions/ChangeEnemyShuffle.cs create mode 100644 OpenTracker/Actions/ChangeEntranceShuffle.cs create mode 100644 OpenTracker/Actions/ChangeItemPlacement.cs create mode 100644 OpenTracker/Actions/ChangePrize.cs create mode 100644 OpenTracker/Actions/ChangeWorldState.cs create mode 100644 OpenTracker/Actions/ClearLocation.cs create mode 100644 OpenTracker/Actions/CollectSection.cs create mode 100644 OpenTracker/Actions/CycleItem.cs create mode 100644 OpenTracker/Actions/MarkSection.cs create mode 100644 OpenTracker/Actions/PinLocation.cs create mode 100644 OpenTracker/Actions/RemoveItem.cs create mode 100644 OpenTracker/Actions/TogglePrize.cs create mode 100644 OpenTracker/Actions/UncollectSection.cs create mode 100644 OpenTracker/Actions/UnpinLocation.cs create mode 100644 OpenTracker/Interfaces/IUndoable.cs create mode 100644 OpenTracker/Utils/ObservableStack.cs create mode 100644 OpenTracker/ViewModels/UndoRedoManager.cs diff --git a/OpenTracker.Setup/OpenTracker.Setup.vdproj b/OpenTracker.Setup/OpenTracker.Setup.vdproj index 2311c98a..c1588e43 100644 --- a/OpenTracker.Setup/OpenTracker.Setup.vdproj +++ b/OpenTracker.Setup/OpenTracker.Setup.vdproj @@ -189,15 +189,15 @@ { "Name" = "8:Microsoft Visual Studio" "ProductName" = "8:OpenTracker" - "ProductCode" = "8:{20E7EBC1-65BD-460F-BDF5-E79044503EB1}" - "PackageCode" = "8:{C80117F4-F719-453B-B059-B0DA3FD3F4BF}" + "ProductCode" = "8:{69BC09D7-A658-4CB4-A62C-53432514C370}" + "PackageCode" = "8:{F3B764AD-9DCE-48AA-A136-6058A7DB0D66}" "UpgradeCode" = "8:{C98C2121-B4C5-473A-9B86-5407FEFFE8F1}" "AspNetVersion" = "8:2.0.50727.0" "RestartWWWService" = "11:FALSE" "RemovePreviousVersions" = "11:TRUE" "DetectNewerInstalledVersion" = "11:TRUE" "InstallAllUsers" = "11:TRUE" - "ProductVersion" = "8:0.4.0" + "ProductVersion" = "8:0.5.0" "Manufacturer" = "8:OpenTracker" "ARPHELPTELEPHONE" = "8:" "ARPHELPLINK" = "8:" diff --git a/OpenTracker/Actions/AddItem.cs b/OpenTracker/Actions/AddItem.cs new file mode 100644 index 00000000..f64d852e --- /dev/null +++ b/OpenTracker/Actions/AddItem.cs @@ -0,0 +1,25 @@ +using OpenTracker.Interfaces; +using OpenTracker.Models; + +namespace OpenTracker.Actions +{ + public class AddItem : IUndoable + { + private readonly Item _item; + + public AddItem(Item item) + { + _item = item; + } + + public void Execute() + { + _item.Change(1); + } + + public void Undo() + { + _item.Change(-1); + } + } +} diff --git a/OpenTracker/Actions/ChangeBoss.cs b/OpenTracker/Actions/ChangeBoss.cs new file mode 100644 index 00000000..b96dc342 --- /dev/null +++ b/OpenTracker/Actions/ChangeBoss.cs @@ -0,0 +1,29 @@ +using OpenTracker.Interfaces; +using OpenTracker.Models; + +namespace OpenTracker.Actions +{ + public class ChangeBoss : IUndoable + { + private readonly BossSection _bossSection; + private readonly Boss _boss; + private Boss _previousBoss; + + public ChangeBoss(BossSection bossSection, Boss boss) + { + _bossSection = bossSection; + _boss = boss; + } + + public void Execute() + { + _previousBoss = _bossSection.Boss; + _bossSection.Boss = _boss; + } + + public void Undo() + { + _bossSection.Boss = _previousBoss; + } + } +} diff --git a/OpenTracker/Actions/ChangeBossShuffle.cs b/OpenTracker/Actions/ChangeBossShuffle.cs new file mode 100644 index 00000000..f6761171 --- /dev/null +++ b/OpenTracker/Actions/ChangeBossShuffle.cs @@ -0,0 +1,29 @@ +using OpenTracker.Interfaces; +using OpenTracker.Models; + +namespace OpenTracker.Actions +{ + public class ChangeBossShuffle : IUndoable + { + private readonly Mode _mode; + private readonly bool _bossShuffle; + private bool _previousBossShuffle; + + public ChangeBossShuffle(Mode mode, bool bossShuffle) + { + _mode = mode; + _bossShuffle = bossShuffle; + } + + public void Execute() + { + _previousBossShuffle = _mode.BossShuffle.Value; + _mode.BossShuffle = _bossShuffle; + } + + public void Undo() + { + _mode.BossShuffle = _previousBossShuffle; + } + } +} diff --git a/OpenTracker/Actions/ChangeDungeonItemShuffle.cs b/OpenTracker/Actions/ChangeDungeonItemShuffle.cs new file mode 100644 index 00000000..06a2baa0 --- /dev/null +++ b/OpenTracker/Actions/ChangeDungeonItemShuffle.cs @@ -0,0 +1,30 @@ +using OpenTracker.Interfaces; +using OpenTracker.Models; +using OpenTracker.Models.Enums; + +namespace OpenTracker.Actions +{ + public class ChangeDungeonItemShuffle : IUndoable + { + private readonly Mode _mode; + private readonly DungeonItemShuffle _dungeonItemShuffle; + private DungeonItemShuffle _previousDungeonItemShuffle; + + public ChangeDungeonItemShuffle(Mode mode, DungeonItemShuffle dungeonItemShuffle) + { + _mode = mode; + _dungeonItemShuffle = dungeonItemShuffle; + } + + public void Execute() + { + _previousDungeonItemShuffle = _mode.DungeonItemShuffle.Value; + _mode.DungeonItemShuffle = _dungeonItemShuffle; + } + + public void Undo() + { + _mode.DungeonItemShuffle = _previousDungeonItemShuffle; + } + } +} diff --git a/OpenTracker/Actions/ChangeEnemyShuffle.cs b/OpenTracker/Actions/ChangeEnemyShuffle.cs new file mode 100644 index 00000000..34c9ac1e --- /dev/null +++ b/OpenTracker/Actions/ChangeEnemyShuffle.cs @@ -0,0 +1,29 @@ +using OpenTracker.Interfaces; +using OpenTracker.Models; + +namespace OpenTracker.Actions +{ + public class ChangeEnemyShuffle : IUndoable + { + private readonly Mode _mode; + private readonly bool _enemyShuffle; + private bool _previousEnemyShuffle; + + public ChangeEnemyShuffle(Mode mode, bool enemyShuffle) + { + _mode = mode; + _enemyShuffle = enemyShuffle; + } + + public void Execute() + { + _previousEnemyShuffle = _mode.EnemyShuffle.Value; + _mode.EnemyShuffle = _enemyShuffle; + } + + public void Undo() + { + _mode.EnemyShuffle = _previousEnemyShuffle; + } + } +} diff --git a/OpenTracker/Actions/ChangeEntranceShuffle.cs b/OpenTracker/Actions/ChangeEntranceShuffle.cs new file mode 100644 index 00000000..51408fc5 --- /dev/null +++ b/OpenTracker/Actions/ChangeEntranceShuffle.cs @@ -0,0 +1,29 @@ +using OpenTracker.Interfaces; +using OpenTracker.Models; + +namespace OpenTracker.Actions +{ + public class ChangeEntranceShuffle : IUndoable + { + private readonly Mode _mode; + private readonly bool _entranceShuffle; + private bool _previousEntranceShuffle; + + public ChangeEntranceShuffle(Mode mode, bool entranceShuffle) + { + _mode = mode; + _entranceShuffle = entranceShuffle; + } + + public void Execute() + { + _previousEntranceShuffle = _mode.EntranceShuffle.Value; + _mode.EntranceShuffle = _entranceShuffle; + } + + public void Undo() + { + _mode.EntranceShuffle = _previousEntranceShuffle; + } + } +} diff --git a/OpenTracker/Actions/ChangeItemPlacement.cs b/OpenTracker/Actions/ChangeItemPlacement.cs new file mode 100644 index 00000000..000731b0 --- /dev/null +++ b/OpenTracker/Actions/ChangeItemPlacement.cs @@ -0,0 +1,35 @@ +using OpenTracker.Interfaces; +using OpenTracker.Models; +using OpenTracker.Models.Enums; + +namespace OpenTracker.Actions +{ + public class ChangeItemPlacement : IUndoable + { + private readonly Mode _mode; + private readonly ItemPlacement _itemPlacement; + private ItemPlacement _previousItemPlacement; + + public ChangeItemPlacement(Mode mode, ItemPlacement itemPlacement) + { + _mode = mode; + _itemPlacement = itemPlacement; + } + + public void Execute() + { + _previousItemPlacement = _mode.ItemPlacement.Value; + _mode.ItemPlacement = _itemPlacement; + } + + public void Undo() + { + _mode.ItemPlacement = _previousItemPlacement; + } + + public bool Validate(ItemPlacement itemPlacement) + { + return _itemPlacement == itemPlacement; + } + } +} diff --git a/OpenTracker/Actions/ChangePrize.cs b/OpenTracker/Actions/ChangePrize.cs new file mode 100644 index 00000000..4b5ce100 --- /dev/null +++ b/OpenTracker/Actions/ChangePrize.cs @@ -0,0 +1,29 @@ +using OpenTracker.Interfaces; +using OpenTracker.Models; + +namespace OpenTracker.Actions +{ + public class ChangePrize : IUndoable + { + private readonly BossSection _prizeSection; + private readonly Item _item; + private Item _previousItem; + + public ChangePrize(BossSection prizeSection, Item item) + { + _prizeSection = prizeSection; + _item = item; + } + + public void Execute() + { + _previousItem = _prizeSection.Prize; + _prizeSection.Prize = _item; + } + + public void Undo() + { + _prizeSection.Prize = _previousItem; + } + } +} diff --git a/OpenTracker/Actions/ChangeWorldState.cs b/OpenTracker/Actions/ChangeWorldState.cs new file mode 100644 index 00000000..7b6339f5 --- /dev/null +++ b/OpenTracker/Actions/ChangeWorldState.cs @@ -0,0 +1,30 @@ +using OpenTracker.Interfaces; +using OpenTracker.Models; +using OpenTracker.Models.Enums; + +namespace OpenTracker.Actions +{ + public class ChangeWorldState : IUndoable + { + private readonly Mode _mode; + private readonly WorldState _worldState; + private WorldState _previousWorldState; + + public ChangeWorldState(Mode mode, WorldState worldState) + { + _mode = mode; + _worldState = worldState; + } + + public void Execute() + { + _previousWorldState = _mode.WorldState.Value; + _mode.WorldState = _worldState; + } + + public void Undo() + { + _mode.WorldState = _previousWorldState; + } + } +} diff --git a/OpenTracker/Actions/ClearLocation.cs b/OpenTracker/Actions/ClearLocation.cs new file mode 100644 index 00000000..81a01d9c --- /dev/null +++ b/OpenTracker/Actions/ClearLocation.cs @@ -0,0 +1,116 @@ +using OpenTracker.Interfaces; +using OpenTracker.Models; +using OpenTracker.Models.Enums; +using OpenTracker.Models.Interfaces; +using System; +using System.Collections.Generic; + +namespace OpenTracker.Actions +{ + public class ClearLocation : IUndoable + { + private readonly Game _game; + private readonly Location _location; + private List _previousLocationCounts; + private List _previousMarkings; + private List _markedItems; + + public ClearLocation(Game game, Location location) + { + _game = game; + _location = location; + _previousLocationCounts = new List(); + _previousMarkings = new List(); + _markedItems = new List(); + } + + public void Execute() + { + _previousLocationCounts.Clear(); + _previousMarkings.Clear(); + _markedItems.Clear(); + + foreach (ISection section in _location.Sections) + { + if (section.IsAvailable() && + (section.Accessibility >= AccessibilityLevel.Inspect || + section is EntranceSection)) + { + switch (section) + { + case BossSection bossSection: + _previousLocationCounts.Add(1); + _previousMarkings.Add(null); + _markedItems.Add(null); + bossSection.Available = false; + break; + case EntranceSection entranceSection: + _previousLocationCounts.Add(1); + _previousMarkings.Add(null); + _markedItems.Add(null); + entranceSection.Available = false; + break; + case ItemSection itemSection: + + _previousLocationCounts.Add(itemSection.Available); + _previousMarkings.Add(itemSection.Marking); + + if (itemSection.Marking != null) + { + Item item = _game.Items[Enum.Parse(itemSection.Marking.Value.ToString())]; + itemSection.Marking = null; + + if (item.Current < item.Maximum) + { + _markedItems.Add(item); + item.Change(1); + } + else + _markedItems.Add(null); + } + else + _markedItems.Add(null); + + itemSection.Available = 0; + + break; + } + } + else + { + _previousLocationCounts.Add(null); + _previousMarkings.Add(null); + _markedItems.Add(null); + } + } + } + + public void Undo() + { + for (int i = 0; i < _previousLocationCounts.Count; i++) + { + if (_previousLocationCounts[i] != null) + { + switch (_location.Sections[i]) + { + case BossSection bossSection: + bossSection.Available = true; + break; + case EntranceSection entranceSection: + entranceSection.Available = true; + break; + case ItemSection itemSection: + itemSection.Available = _previousLocationCounts[i].Value; + break; + } + } + + if (_previousMarkings[i] != null) + _location.Sections[i].Marking = _previousMarkings[i]; + + if (_markedItems[i] != null) + _markedItems[i].Change(-1); + } + } + } +} diff --git a/OpenTracker/Actions/CollectSection.cs b/OpenTracker/Actions/CollectSection.cs new file mode 100644 index 00000000..45e88cad --- /dev/null +++ b/OpenTracker/Actions/CollectSection.cs @@ -0,0 +1,77 @@ +using OpenTracker.Interfaces; +using OpenTracker.Models; +using OpenTracker.Models.Enums; +using OpenTracker.Models.Interfaces; +using System; + +namespace OpenTracker.Actions +{ + public class CollectSection : IUndoable + { + private readonly Game _game; + private readonly ISection _section; + private MarkingType? _previousMarking; + private Item _markedItem; + + public CollectSection(Game game, ISection section) + { + _game = game; + _section = section; + } + + public void Execute() + { + switch (_section) + { + case BossSection bossSection: + bossSection.Available = false; + break; + case EntranceSection entranceSection: + entranceSection.Available = false; + break; + case ItemSection itemSection: + + itemSection.Available--; + + if (itemSection.Available == 0 && itemSection.Marking != null) + { + _previousMarking = itemSection.Marking; + itemSection.Marking = null; + Item item = _game.Items[Enum.Parse(_previousMarking.Value.ToString())]; + + if (item.Current < item.Maximum) + { + _markedItem = item; + _markedItem.Change(1); + } + } + + break; + } + } + + public void Undo() + { + switch (_section) + { + case BossSection bossSection: + bossSection.Available = true; + break; + case EntranceSection entranceSection: + entranceSection.Available = true; + break; + case ItemSection itemSection: + + itemSection.Available++; + + if (_previousMarking != null) + itemSection.Marking = _previousMarking; + + if (_markedItem != null) + _markedItem.Change(-1); + + break; + } + } + } +} diff --git a/OpenTracker/Actions/CycleItem.cs b/OpenTracker/Actions/CycleItem.cs new file mode 100644 index 00000000..caaacedc --- /dev/null +++ b/OpenTracker/Actions/CycleItem.cs @@ -0,0 +1,31 @@ +using OpenTracker.Interfaces; +using OpenTracker.Models; + +namespace OpenTracker.Actions +{ + public class CycleItem : IUndoable + { + private readonly Item _item; + + public CycleItem(Item item) + { + _item = item; + } + + public void Execute() + { + if (_item.Current == _item.Maximum) + _item.SetCurrent(); + else + _item.Change(1); + } + + public void Undo() + { + if (_item.Current == 0) + _item.SetCurrent(_item.Maximum); + else + _item.Change(-1); + } + } +} diff --git a/OpenTracker/Actions/MarkSection.cs b/OpenTracker/Actions/MarkSection.cs new file mode 100644 index 00000000..da02b948 --- /dev/null +++ b/OpenTracker/Actions/MarkSection.cs @@ -0,0 +1,30 @@ +using OpenTracker.Interfaces; +using OpenTracker.Models.Enums; +using OpenTracker.Models.Interfaces; + +namespace OpenTracker.Actions +{ + public class MarkSection : IUndoable + { + private readonly ISection _section; + private readonly MarkingType? _marking; + private MarkingType? _previousMarking; + + public MarkSection(ISection section, MarkingType? marking) + { + _section = section; + _marking = marking; + } + + public void Execute() + { + _previousMarking = _section.Marking; + _section.Marking = _marking; + } + + public void Undo() + { + _section.Marking = _previousMarking; + } + } +} diff --git a/OpenTracker/Actions/PinLocation.cs b/OpenTracker/Actions/PinLocation.cs new file mode 100644 index 00000000..e92572fd --- /dev/null +++ b/OpenTracker/Actions/PinLocation.cs @@ -0,0 +1,39 @@ +using OpenTracker.Interfaces; +using OpenTracker.ViewModels; +using System.Collections.ObjectModel; + +namespace OpenTracker.Actions +{ + public class PinLocation : IUndoable + { + private readonly ObservableCollection _pinnedLocations; + private readonly PinnedLocationControlVM _pinnedLocation; + private int? _existingIndex; + + public PinLocation(ObservableCollection pinnedLocations, + PinnedLocationControlVM pinnedLocation) + { + _pinnedLocations = pinnedLocations; + _pinnedLocation = pinnedLocation; + } + + public void Execute() + { + if (_pinnedLocations.Contains(_pinnedLocation)) + { + _existingIndex = _pinnedLocations.IndexOf(_pinnedLocation); + _pinnedLocations.Remove(_pinnedLocation); + } + + _pinnedLocations.Insert(0, _pinnedLocation); + } + + public void Undo() + { + _pinnedLocations.Remove(_pinnedLocation); + + if (_existingIndex.HasValue) + _pinnedLocations.Insert(_existingIndex.Value, _pinnedLocation); + } + } +} diff --git a/OpenTracker/Actions/RemoveItem.cs b/OpenTracker/Actions/RemoveItem.cs new file mode 100644 index 00000000..27b8cc39 --- /dev/null +++ b/OpenTracker/Actions/RemoveItem.cs @@ -0,0 +1,25 @@ +using OpenTracker.Interfaces; +using OpenTracker.Models; + +namespace OpenTracker.Actions +{ + public class RemoveItem : IUndoable + { + private readonly Item _item; + + public RemoveItem(Item item) + { + _item = item; + } + + public void Execute() + { + _item.Change(-1); + } + + public void Undo() + { + _item.Change(1); + } + } +} diff --git a/OpenTracker/Actions/TogglePrize.cs b/OpenTracker/Actions/TogglePrize.cs new file mode 100644 index 00000000..ab81b47e --- /dev/null +++ b/OpenTracker/Actions/TogglePrize.cs @@ -0,0 +1,25 @@ +using OpenTracker.Interfaces; +using OpenTracker.Models; + +namespace OpenTracker.Actions +{ + public class TogglePrize : IUndoable + { + private readonly BossSection _prizeSection; + + public TogglePrize(BossSection prizeSection) + { + _prizeSection = prizeSection; + } + + public void Execute() + { + _prizeSection.Available = !_prizeSection.Available; + } + + public void Undo() + { + _prizeSection.Available = !_prizeSection.Available; + } + } +} diff --git a/OpenTracker/Actions/UncollectSection.cs b/OpenTracker/Actions/UncollectSection.cs new file mode 100644 index 00000000..b648cf6f --- /dev/null +++ b/OpenTracker/Actions/UncollectSection.cs @@ -0,0 +1,48 @@ +using OpenTracker.Interfaces; +using OpenTracker.Models; +using OpenTracker.Models.Interfaces; + +namespace OpenTracker.Actions +{ + public class UncollectSection : IUndoable + { + private readonly ISection _section; + + public UncollectSection(ISection section) + { + _section = section; + } + + public void Execute() + { + switch (_section) + { + case BossSection bossSection: + bossSection.Available = true; + break; + case EntranceSection entranceSection: + entranceSection.Available = true; + break; + case ItemSection itemSection: + itemSection.Available++; + break; + } + } + + public void Undo() + { + switch (_section) + { + case BossSection bossSection: + bossSection.Available = false; + break; + case EntranceSection entranceSection: + entranceSection.Available = false; + break; + case ItemSection itemSection: + itemSection.Available--; + break; + } + } + } +} diff --git a/OpenTracker/Actions/UnpinLocation.cs b/OpenTracker/Actions/UnpinLocation.cs new file mode 100644 index 00000000..ec0134c1 --- /dev/null +++ b/OpenTracker/Actions/UnpinLocation.cs @@ -0,0 +1,32 @@ +using OpenTracker.Interfaces; +using OpenTracker.ViewModels; +using System.Collections.ObjectModel; + +namespace OpenTracker.Actions +{ + public class UnpinLocation : IUndoable + { + private readonly ObservableCollection _pinnedLocations; + private readonly PinnedLocationControlVM _pinnedLocation; + private int? _existingIndex; + + public UnpinLocation(ObservableCollection pinnedLocations, + PinnedLocationControlVM pinnedLocation) + { + _pinnedLocations = pinnedLocations; + _pinnedLocation = pinnedLocation; + } + + public void Execute() + { + _existingIndex = _pinnedLocations.IndexOf(_pinnedLocation); + _pinnedLocations.Remove(_pinnedLocation); + } + + public void Undo() + { + if (_existingIndex.HasValue) + _pinnedLocations.Insert(_existingIndex.Value, _pinnedLocation); + } + } +} diff --git a/OpenTracker/Interfaces/IItemControlVM.cs b/OpenTracker/Interfaces/IItemControlVM.cs index 2fd0e7cc..4ca47922 100644 --- a/OpenTracker/Interfaces/IItemControlVM.cs +++ b/OpenTracker/Interfaces/IItemControlVM.cs @@ -2,6 +2,6 @@ { interface IItemControlVM { - void ChangeItem(bool alternate = false); + void ChangeItem(bool rightClick = false); } } diff --git a/OpenTracker/Interfaces/IUndoable.cs b/OpenTracker/Interfaces/IUndoable.cs new file mode 100644 index 00000000..2be9dc27 --- /dev/null +++ b/OpenTracker/Interfaces/IUndoable.cs @@ -0,0 +1,8 @@ +namespace OpenTracker.Interfaces +{ + public interface IUndoable + { + void Execute(); + void Undo(); + } +} diff --git a/OpenTracker/OpenTracker.csproj b/OpenTracker/OpenTracker.csproj index e8b0a8a1..29714421 100644 --- a/OpenTracker/OpenTracker.csproj +++ b/OpenTracker/OpenTracker.csproj @@ -4,10 +4,10 @@ netcoreapp3.1 triforce.ico - 0.4.0 + 0.5.0 Tripp - 0.4.0.0 - 0.4.0.0 + 0.5.0.0 + 0.5.0.0 diff --git a/OpenTracker/Utils/ObservableStack.cs b/OpenTracker/Utils/ObservableStack.cs new file mode 100644 index 00000000..8577dd12 --- /dev/null +++ b/OpenTracker/Utils/ObservableStack.cs @@ -0,0 +1,67 @@ +using System.Collections.Generic; +using System.Collections.Specialized; +using System.ComponentModel; + +namespace OpenTracker.Utils +{ + public class ObservableStack : Stack, INotifyCollectionChanged, INotifyPropertyChanged + { + public virtual event NotifyCollectionChangedEventHandler CollectionChanged; + protected virtual event PropertyChangedEventHandler PropertyChanged; + + public ObservableStack() + { + } + + public ObservableStack(IEnumerable collection) + { + foreach (var item in collection) + base.Push(item); + } + + public new virtual void Clear() + { + base.Clear(); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } + + public new virtual T Pop() + { + var item = base.Pop(); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item)); + return item; + } + + public new virtual void Push(T item) + { + base.Push(item); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)); + } + + protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e) + { + RaiseCollectionChanged(e); + } + + protected virtual void OnPropertyChanged(PropertyChangedEventArgs e) + { + RaisePropertyChanged(e); + } + + private void RaiseCollectionChanged(NotifyCollectionChangedEventArgs e) + { + CollectionChanged?.Invoke(this, e); + } + + private void RaisePropertyChanged(PropertyChangedEventArgs e) + { + PropertyChanged?.Invoke(this, e); + } + + event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged + { + add { PropertyChanged += value; } + remove { PropertyChanged -= value; } + } + } +} diff --git a/OpenTracker/ViewModels/BossControlVM.cs b/OpenTracker/ViewModels/BossControlVM.cs index c4d21248..8d53899c 100644 --- a/OpenTracker/ViewModels/BossControlVM.cs +++ b/OpenTracker/ViewModels/BossControlVM.cs @@ -1,4 +1,5 @@ -using OpenTracker.Interfaces; +using OpenTracker.Actions; +using OpenTracker.Interfaces; using OpenTracker.Models; using OpenTracker.Models.Enums; using ReactiveUI; @@ -8,6 +9,7 @@ namespace OpenTracker.ViewModels { public class BossControlVM : ViewModelBase, IItemControlVM { + private readonly UndoRedoManager _undoRedoManager; private readonly Game _game; private readonly BossSection _bossSection; @@ -18,8 +20,10 @@ public string ImageSource set => this.RaiseAndSetIfChanged(ref _imageSource, value); } - public BossControlVM(Game game, BossSection bossSection) + public BossControlVM(UndoRedoManager undoRedoManager, Game game, + BossSection bossSection) { + _undoRedoManager = undoRedoManager; _game = game; _bossSection = bossSection; @@ -48,16 +52,19 @@ private void Update() ImageSource = imageBaseString + ".png"; } - public void ChangeItem(bool alternate = false) + public void ChangeItem(bool rightClick = false) { if (_bossSection.Boss == null) - _bossSection.Boss = _game.Bosses[BossType.Armos]; + { + _undoRedoManager.Execute(new ChangeBoss(_bossSection, + _game.Bosses[BossType.Armos])); + } else if (_bossSection.Boss.Type == BossType.Trinexx) - _bossSection.Boss = null; + _undoRedoManager.Execute(new ChangeBoss(_bossSection, null)); else { - BossType newBoss = _bossSection.Boss.Type + 1; - _bossSection.Boss = _game.Bosses[newBoss]; + Boss newBoss = _game.Bosses[_bossSection.Boss.Type + 1]; + _undoRedoManager.Execute(new ChangeBoss(_bossSection, newBoss)); } } } diff --git a/OpenTracker/ViewModels/DungeonItemControlVM.cs b/OpenTracker/ViewModels/DungeonItemControlVM.cs index eb3d51d4..c4b194eb 100644 --- a/OpenTracker/ViewModels/DungeonItemControlVM.cs +++ b/OpenTracker/ViewModels/DungeonItemControlVM.cs @@ -1,4 +1,5 @@ using Avalonia.Media; +using OpenTracker.Actions; using OpenTracker.Interfaces; using OpenTracker.Models; using OpenTracker.Models.Enums; @@ -10,6 +11,7 @@ namespace OpenTracker.ViewModels { public class DungeonItemControlVM : ViewModelBase, IItemControlVM { + private readonly UndoRedoManager _undoRedoManager; private readonly AppSettingsVM _appSettings; private readonly string _imageSourceBase; private readonly Item _item; @@ -43,8 +45,10 @@ public bool TextVisible private set => this.RaiseAndSetIfChanged(ref _textVisible, value); } - public DungeonItemControlVM(AppSettingsVM appSettings, Mode mode, Item item) + public DungeonItemControlVM(UndoRedoManager undoRedoManager, AppSettingsVM appSettings, + Mode mode, Item item) { + _undoRedoManager = undoRedoManager; _appSettings = appSettings; _item = item; @@ -130,10 +134,13 @@ public void ChangeItem(bool rightClick = false) if (rightClick) { if (_item.Current > 0) - _item.Change(-1); + _undoRedoManager.Execute(new RemoveItem(_item)); } else - _item.Change(1); + { + if (_item.Current < _item.Maximum) + _undoRedoManager.Execute(new AddItem(_item)); + } } } } diff --git a/OpenTracker/ViewModels/DungeonPrizeControlVM.cs b/OpenTracker/ViewModels/DungeonPrizeControlVM.cs index 0ef6a7f4..6c185748 100644 --- a/OpenTracker/ViewModels/DungeonPrizeControlVM.cs +++ b/OpenTracker/ViewModels/DungeonPrizeControlVM.cs @@ -1,4 +1,5 @@ -using OpenTracker.Interfaces; +using OpenTracker.Actions; +using OpenTracker.Interfaces; using OpenTracker.Models; using OpenTracker.Models.Enums; using ReactiveUI; @@ -8,6 +9,7 @@ namespace OpenTracker.ViewModels { public class DungeonPrizeControlVM : ViewModelBase, IItemControlVM { + private readonly UndoRedoManager _undoRedoManager; private readonly Game _game; private readonly BossSection _prizeSection; @@ -18,8 +20,10 @@ public string ImageSource set => this.RaiseAndSetIfChanged(ref _imageSource, value); } - public DungeonPrizeControlVM(Game game, BossSection prizeSection) + public DungeonPrizeControlVM(UndoRedoManager undoRedoManager, + Game game, BossSection prizeSection) { + _undoRedoManager = undoRedoManager; _game = game; _prizeSection = prizeSection; @@ -45,22 +49,25 @@ private void Update() ImageSource = imageBaseString + (_prizeSection.Available ? "0" : "1") + ".png"; } - public void ChangeItem(bool alternate = false) + public void ChangeItem(bool rightClick = false) { - if (alternate) + if (rightClick) { if (_prizeSection.Prize == null) - _prizeSection.Prize = _game.Items[ItemType.GreenPendant]; + { + _undoRedoManager.Execute(new ChangePrize(_prizeSection, + _game.Items[ItemType.GreenPendant])); + } else if (_prizeSection.Prize.Type == ItemType.RedCrystal) - _prizeSection.Prize = null; + _undoRedoManager.Execute(new ChangePrize(_prizeSection, null)); else { - ItemType newPrize = _prizeSection.Prize.Type + 1; - _prizeSection.Prize = _game.Items[newPrize]; + Item newPrize = _game.Items[_prizeSection.Prize.Type + 1]; + _undoRedoManager.Execute(new ChangePrize(_prizeSection, newPrize)); } } else - _prizeSection.Available = !_prizeSection.Available; + _undoRedoManager.Execute(new TogglePrize(_prizeSection)); } } } diff --git a/OpenTracker/ViewModels/ItemControlVM.cs b/OpenTracker/ViewModels/ItemControlVM.cs index 9acc0873..d5561b02 100644 --- a/OpenTracker/ViewModels/ItemControlVM.cs +++ b/OpenTracker/ViewModels/ItemControlVM.cs @@ -1,15 +1,16 @@ using Avalonia.Media; +using OpenTracker.Actions; using OpenTracker.Interfaces; using OpenTracker.Models; using OpenTracker.Models.Enums; using ReactiveUI; -using System; using System.ComponentModel; namespace OpenTracker.ViewModels { public class ItemControlVM : ViewModelBase, IItemControlVM { + private readonly UndoRedoManager _undoRedoManager; private readonly string _imageSourceBase; private readonly Item[] _items; @@ -34,8 +35,9 @@ public IBrush TextColor private set => this.RaiseAndSetIfChanged(ref _textColor, value); } - public ItemControlVM(Item[] items) + public ItemControlVM(UndoRedoManager undoRedoManager, Item[] items) { + _undoRedoManager = undoRedoManager; _items = items; if (_items != null) @@ -90,29 +92,16 @@ public void ChangeItem(bool rightClick = false) if (_items.Length == 2) { if (rightClick) - { - if (_items[1].Current == _items[1].Maximum) - _items[1].SetCurrent(); - else - _items[1].Change(1); - } + _undoRedoManager.Execute(new CycleItem(_items[1])); else - { - if (_items[0].Current == _items[0].Maximum) - _items[0].SetCurrent(); - else - _items[0].Change(1); - } + _undoRedoManager.Execute(new CycleItem(_items[0])); } else { if (rightClick) - { - if (_items[0].Current > 0) - _items[0].Change(-1); - } + _undoRedoManager.Execute(new RemoveItem(_items[0])); else - _items[0].Change(1); + _undoRedoManager.Execute(new AddItem(_items[0])); } } } diff --git a/OpenTracker/ViewModels/MainWindowVM.cs b/OpenTracker/ViewModels/MainWindowVM.cs index d10ef2c4..b61fb81f 100644 --- a/OpenTracker/ViewModels/MainWindowVM.cs +++ b/OpenTracker/ViewModels/MainWindowVM.cs @@ -1,4 +1,5 @@ using Newtonsoft.Json; +using OpenTracker.Actions; using OpenTracker.Interfaces; using OpenTracker.JsonConverters; using OpenTracker.Models; @@ -6,6 +7,7 @@ using ReactiveUI; using System; using System.Collections.ObjectModel; +using System.Collections.Specialized; using System.ComponentModel; using System.IO; using System.Reactive; @@ -15,6 +17,7 @@ namespace OpenTracker.ViewModels public class MainWindowVM : ViewModelBase, IMainWindowVM { private readonly IDialogService _dialogService; + private readonly UndoRedoManager _undoRedoManager; private readonly Game _game; public ObservableCollection Maps { get; } @@ -27,7 +30,29 @@ public class MainWindowVM : ViewModelBase, IMainWindowVM public ObservableCollection Prizes { get; } public ObservableCollection Bosses { get; } + public ReactiveCommand UndoCommand { get; } + public ReactiveCommand RedoCommand { get; } public ReactiveCommand ToggleDisplayAllLocationsCommand { get; } + public ReactiveCommand ItemPlacementCommand { get; } + public ReactiveCommand DungeonItemShuffleCommand { get; } + public ReactiveCommand WorldStateCommand { get; } + public ReactiveCommand EntranceShuffleCommand { get; } + public ReactiveCommand BossShuffleCommand { get; } + public ReactiveCommand EnemyShuffleCommand { get; } + + private bool _canUndo; + public bool CanUndo + { + get => _canUndo; + private set => this.RaiseAndSetIfChanged(ref _canUndo, value); + } + + private bool _canRedo; + public bool CanRedo + { + get => _canRedo; + private set => this.RaiseAndSetIfChanged(ref _canRedo, value); + } private AppSettingsVM _appSettings; public AppSettingsVM AppSettings @@ -141,7 +166,20 @@ public MainWindowVM(IDialogService dialogService) : this() public MainWindowVM() { + _undoRedoManager = new UndoRedoManager(); + + _undoRedoManager.UndoableActions.CollectionChanged += OnUndoChanged; + _undoRedoManager.RedoableActions.CollectionChanged += OnRedoChanged; + + UndoCommand = ReactiveCommand.Create(Undo, this.WhenAnyValue(x => x.CanUndo)); + RedoCommand = ReactiveCommand.Create(Redo, this.WhenAnyValue(x => x.CanRedo)); ToggleDisplayAllLocationsCommand = ReactiveCommand.Create(ToggleDisplayAllLocations); + ItemPlacementCommand = ReactiveCommand.Create(SetItemPlacement); + DungeonItemShuffleCommand = ReactiveCommand.Create(SetDungeonItemShuffle); + WorldStateCommand = ReactiveCommand.Create(SetWorldState); + EntranceShuffleCommand = ReactiveCommand.Create(ToggleEntranceShuffle); + BossShuffleCommand = ReactiveCommand.Create(ToggleBossShuffle); + EnemyShuffleCommand = ReactiveCommand.Create(ToggleEnemyShuffle); string appData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); @@ -172,69 +210,69 @@ public MainWindowVM() PinnedLocations = new ObservableCollection(); HCItems = new ObservableCollection() { - new DungeonItemControlVM(_appSettings, _game.Mode, _game.Items[ItemType.HCSmallKey]) + new DungeonItemControlVM(_undoRedoManager, _appSettings, _game.Mode, _game.Items[ItemType.HCSmallKey]) }; ATItems = new ObservableCollection() { - new DungeonItemControlVM(_appSettings, _game.Mode, _game.Items[ItemType.ATSmallKey]) + new DungeonItemControlVM(_undoRedoManager, _appSettings, _game.Mode, _game.Items[ItemType.ATSmallKey]) }; SmallKeyItems = new ObservableCollection() { - new DungeonItemControlVM(_appSettings, _game.Mode, null), - new DungeonItemControlVM(_appSettings, _game.Mode, _game.Items[ItemType.DPSmallKey]), - new DungeonItemControlVM(_appSettings, _game.Mode, _game.Items[ItemType.ToHSmallKey]), - new DungeonItemControlVM(_appSettings, _game.Mode, _game.Items[ItemType.PoDSmallKey]), - new DungeonItemControlVM(_appSettings, _game.Mode, _game.Items[ItemType.SPSmallKey]), - new DungeonItemControlVM(_appSettings, _game.Mode, _game.Items[ItemType.SWSmallKey]), - new DungeonItemControlVM(_appSettings, _game.Mode, _game.Items[ItemType.TTSmallKey]), - new DungeonItemControlVM(_appSettings, _game.Mode, _game.Items[ItemType.IPSmallKey]), - new DungeonItemControlVM(_appSettings, _game.Mode, _game.Items[ItemType.MMSmallKey]), - new DungeonItemControlVM(_appSettings, _game.Mode, _game.Items[ItemType.TRSmallKey]), - new DungeonItemControlVM(_appSettings, _game.Mode, _game.Items[ItemType.GTSmallKey]) + new DungeonItemControlVM(_undoRedoManager, _appSettings, _game.Mode, null), + new DungeonItemControlVM(_undoRedoManager, _appSettings, _game.Mode, _game.Items[ItemType.DPSmallKey]), + new DungeonItemControlVM(_undoRedoManager, _appSettings, _game.Mode, _game.Items[ItemType.ToHSmallKey]), + new DungeonItemControlVM(_undoRedoManager, _appSettings, _game.Mode, _game.Items[ItemType.PoDSmallKey]), + new DungeonItemControlVM(_undoRedoManager, _appSettings, _game.Mode, _game.Items[ItemType.SPSmallKey]), + new DungeonItemControlVM(_undoRedoManager, _appSettings, _game.Mode, _game.Items[ItemType.SWSmallKey]), + new DungeonItemControlVM(_undoRedoManager, _appSettings, _game.Mode, _game.Items[ItemType.TTSmallKey]), + new DungeonItemControlVM(_undoRedoManager, _appSettings, _game.Mode, _game.Items[ItemType.IPSmallKey]), + new DungeonItemControlVM(_undoRedoManager, _appSettings, _game.Mode, _game.Items[ItemType.MMSmallKey]), + new DungeonItemControlVM(_undoRedoManager, _appSettings, _game.Mode, _game.Items[ItemType.TRSmallKey]), + new DungeonItemControlVM(_undoRedoManager, _appSettings, _game.Mode, _game.Items[ItemType.GTSmallKey]) }; BigKeyItems = new ObservableCollection() { - new DungeonItemControlVM(_appSettings, _game.Mode, _game.Items[ItemType.EPBigKey]), - new DungeonItemControlVM(_appSettings, _game.Mode, _game.Items[ItemType.DPBigKey]), - new DungeonItemControlVM(_appSettings, _game.Mode, _game.Items[ItemType.ToHBigKey]), - new DungeonItemControlVM(_appSettings, _game.Mode, _game.Items[ItemType.PoDBigKey]), - new DungeonItemControlVM(_appSettings, _game.Mode, _game.Items[ItemType.SPBigKey]), - new DungeonItemControlVM(_appSettings, _game.Mode, _game.Items[ItemType.SWBigKey]), - new DungeonItemControlVM(_appSettings, _game.Mode, _game.Items[ItemType.TTBigKey]), - new DungeonItemControlVM(_appSettings, _game.Mode, _game.Items[ItemType.IPBigKey]), - new DungeonItemControlVM(_appSettings, _game.Mode, _game.Items[ItemType.MMBigKey]), - new DungeonItemControlVM(_appSettings, _game.Mode, _game.Items[ItemType.TRBigKey]), - new DungeonItemControlVM(_appSettings, _game.Mode, _game.Items[ItemType.GTBigKey]) + new DungeonItemControlVM(_undoRedoManager, _appSettings, _game.Mode, _game.Items[ItemType.EPBigKey]), + new DungeonItemControlVM(_undoRedoManager, _appSettings, _game.Mode, _game.Items[ItemType.DPBigKey]), + new DungeonItemControlVM(_undoRedoManager, _appSettings, _game.Mode, _game.Items[ItemType.ToHBigKey]), + new DungeonItemControlVM(_undoRedoManager, _appSettings, _game.Mode, _game.Items[ItemType.PoDBigKey]), + new DungeonItemControlVM(_undoRedoManager, _appSettings, _game.Mode, _game.Items[ItemType.SPBigKey]), + new DungeonItemControlVM(_undoRedoManager, _appSettings, _game.Mode, _game.Items[ItemType.SWBigKey]), + new DungeonItemControlVM(_undoRedoManager, _appSettings, _game.Mode, _game.Items[ItemType.TTBigKey]), + new DungeonItemControlVM(_undoRedoManager, _appSettings, _game.Mode, _game.Items[ItemType.IPBigKey]), + new DungeonItemControlVM(_undoRedoManager, _appSettings, _game.Mode, _game.Items[ItemType.MMBigKey]), + new DungeonItemControlVM(_undoRedoManager, _appSettings, _game.Mode, _game.Items[ItemType.TRBigKey]), + new DungeonItemControlVM(_undoRedoManager, _appSettings, _game.Mode, _game.Items[ItemType.GTBigKey]) }; Prizes = new ObservableCollection() { - new DungeonPrizeControlVM(_game, _game.Locations[LocationID.EasternPalace].BossSection), - new DungeonPrizeControlVM(_game, _game.Locations[LocationID.DesertPalace].BossSection), - new DungeonPrizeControlVM(_game, _game.Locations[LocationID.TowerOfHera].BossSection), - new DungeonPrizeControlVM(_game, _game.Locations[LocationID.PalaceOfDarkness].BossSection), - new DungeonPrizeControlVM(_game, _game.Locations[LocationID.SwampPalace].BossSection), - new DungeonPrizeControlVM(_game, _game.Locations[LocationID.SkullWoods].BossSection), - new DungeonPrizeControlVM(_game, _game.Locations[LocationID.ThievesTown].BossSection), - new DungeonPrizeControlVM(_game, _game.Locations[LocationID.IcePalace].BossSection), - new DungeonPrizeControlVM(_game, _game.Locations[LocationID.MiseryMire].BossSection), - new DungeonPrizeControlVM(_game, _game.Locations[LocationID.TurtleRock].BossSection) + new DungeonPrizeControlVM(_undoRedoManager, _game, _game.Locations[LocationID.EasternPalace].BossSection), + new DungeonPrizeControlVM(_undoRedoManager, _game, _game.Locations[LocationID.DesertPalace].BossSection), + new DungeonPrizeControlVM(_undoRedoManager, _game, _game.Locations[LocationID.TowerOfHera].BossSection), + new DungeonPrizeControlVM(_undoRedoManager, _game, _game.Locations[LocationID.PalaceOfDarkness].BossSection), + new DungeonPrizeControlVM(_undoRedoManager, _game, _game.Locations[LocationID.SwampPalace].BossSection), + new DungeonPrizeControlVM(_undoRedoManager, _game, _game.Locations[LocationID.SkullWoods].BossSection), + new DungeonPrizeControlVM(_undoRedoManager, _game, _game.Locations[LocationID.ThievesTown].BossSection), + new DungeonPrizeControlVM(_undoRedoManager, _game, _game.Locations[LocationID.IcePalace].BossSection), + new DungeonPrizeControlVM(_undoRedoManager, _game, _game.Locations[LocationID.MiseryMire].BossSection), + new DungeonPrizeControlVM(_undoRedoManager, _game, _game.Locations[LocationID.TurtleRock].BossSection) }; Bosses = new ObservableCollection() { - new BossControlVM(_game, _game.Locations[LocationID.EasternPalace].BossSection), - new BossControlVM(_game, _game.Locations[LocationID.DesertPalace].BossSection), - new BossControlVM(_game, _game.Locations[LocationID.TowerOfHera].BossSection), - new BossControlVM(_game, _game.Locations[LocationID.PalaceOfDarkness].BossSection), - new BossControlVM(_game, _game.Locations[LocationID.SwampPalace].BossSection), - new BossControlVM(_game, _game.Locations[LocationID.SkullWoods].BossSection), - new BossControlVM(_game, _game.Locations[LocationID.ThievesTown].BossSection), - new BossControlVM(_game, _game.Locations[LocationID.IcePalace].BossSection), - new BossControlVM(_game, _game.Locations[LocationID.MiseryMire].BossSection), - new BossControlVM(_game, _game.Locations[LocationID.TurtleRock].BossSection) + new BossControlVM(_undoRedoManager, _game, _game.Locations[LocationID.EasternPalace].BossSection), + new BossControlVM(_undoRedoManager, _game, _game.Locations[LocationID.DesertPalace].BossSection), + new BossControlVM(_undoRedoManager, _game, _game.Locations[LocationID.TowerOfHera].BossSection), + new BossControlVM(_undoRedoManager, _game, _game.Locations[LocationID.PalaceOfDarkness].BossSection), + new BossControlVM(_undoRedoManager, _game, _game.Locations[LocationID.SwampPalace].BossSection), + new BossControlVM(_undoRedoManager, _game, _game.Locations[LocationID.SkullWoods].BossSection), + new BossControlVM(_undoRedoManager, _game, _game.Locations[LocationID.ThievesTown].BossSection), + new BossControlVM(_undoRedoManager, _game, _game.Locations[LocationID.IcePalace].BossSection), + new BossControlVM(_undoRedoManager, _game, _game.Locations[LocationID.MiseryMire].BossSection), + new BossControlVM(_undoRedoManager, _game, _game.Locations[LocationID.TurtleRock].BossSection) }; for (int i = 0; i < Enum.GetValues(typeof(MapID)).Length; i++) - Maps.Add(new MapControlVM(_appSettings, _game, this, (MapID)i)); + Maps.Add(new MapControlVM(_undoRedoManager, _appSettings, _game, this, (MapID)i)); Items = new ObservableCollection(); @@ -250,12 +288,13 @@ public MainWindowVM() case ItemType.Ether: case ItemType.Quake: case ItemType.Flute: - Items.Add(new ItemControlVM(new Item[2] { - _game.Items[(ItemType)i], _game.Items[(ItemType)(i + 1)] })); + Items.Add(new ItemControlVM(_undoRedoManager, new Item[2] { + _game.Items[(ItemType)i], _game.Items[(ItemType)(i + 1)] + })); break; case ItemType.MoonPearl: - Items.Add(new ItemControlVM(new Item[1] { _game.Items[(ItemType)i] })); - Items.Add(new ItemControlVM(null)); + Items.Add(new ItemControlVM(_undoRedoManager, new Item[1] { _game.Items[(ItemType)i] })); + Items.Add(new ItemControlVM(_undoRedoManager, null)); break; case ItemType.Hookshot: case ItemType.Mushroom: @@ -282,34 +321,14 @@ public MainWindowVM() case ItemType.Sword: case ItemType.Shield: case ItemType.Mail: - Items.Add(new ItemControlVM(new Item[1] { _game.Items[(ItemType)i] })); + Items.Add(new ItemControlVM(_undoRedoManager, new Item[1] { _game.Items[(ItemType)i] })); break; default: break; } } - PropertyChanged += OnPropertyChanged; - } - - private void ToggleDisplayAllLocations() - { - _appSettings.DisplayAllLocations = !_appSettings.DisplayAllLocations; - } - - private async void Reset() - { - bool? result = await _dialogService.ShowDialog( - new MessageBoxDialogVM("Warning", - "Resetting the tracker will set all items and locations back to their starting values. This cannot be undone.\nDo you wish to proceed?")); - - if (result.HasValue && result.Value) - _game.Reset(); - } - - private async void ColorSelect() - { - bool? result = await _dialogService.ShowDialog(_appSettings); + _game.Mode.PropertyChanged += OnModeChanged; } private void RefreshItemPlacement() @@ -340,30 +359,40 @@ private void RefreshDungeonItemShuffle() DungeonItemShuffleMapsCompasses = false; DungeonItemShuffleMapsCompassesSmallKeys = false; DungeonItemShuffleKeysanity = false; + SmallKeyShuffle = false; + BigKeyShuffle = false; break; case DungeonItemShuffle.Standard: DungeonItemShuffleStandard = true; DungeonItemShuffleMapsCompasses = false; DungeonItemShuffleMapsCompassesSmallKeys = false; DungeonItemShuffleKeysanity = false; + SmallKeyShuffle = false; + BigKeyShuffle = false; break; case DungeonItemShuffle.MapsCompasses: DungeonItemShuffleStandard = false; DungeonItemShuffleMapsCompasses = true; DungeonItemShuffleMapsCompassesSmallKeys = false; DungeonItemShuffleKeysanity = false; + SmallKeyShuffle = false; + BigKeyShuffle = false; break; case DungeonItemShuffle.MapsCompassesSmallKeys: DungeonItemShuffleStandard = false; DungeonItemShuffleMapsCompasses = false; DungeonItemShuffleMapsCompassesSmallKeys = true; DungeonItemShuffleKeysanity = false; + SmallKeyShuffle = true; + BigKeyShuffle = false; break; case DungeonItemShuffle.Keysanity: DungeonItemShuffleStandard = false; DungeonItemShuffleMapsCompasses = false; DungeonItemShuffleMapsCompassesSmallKeys = false; DungeonItemShuffleKeysanity = true; + SmallKeyShuffle = true; + BigKeyShuffle = true; break; } } @@ -411,80 +440,108 @@ private void RefreshEnemyShuffle() EnemyShuffle = false; } - private void SetItemPlacement(ItemPlacement itemPlacement) + private void SetItemPlacement(string itemPlacementString) { - _game.Mode.ItemPlacement = itemPlacement; + if (Enum.TryParse(itemPlacementString, out ItemPlacement itemPlacement)) + _undoRedoManager.Execute(new ChangeItemPlacement(_game.Mode, itemPlacement)); } - private void SetDungeonItemShuffle(DungeonItemShuffle dungeonItemShuffle) + private void SetDungeonItemShuffle(string dungeonItemShuffleString) { - _game.Mode.DungeonItemShuffle = dungeonItemShuffle; - - if (dungeonItemShuffle >= DungeonItemShuffle.MapsCompassesSmallKeys) - SmallKeyShuffle = true; - else - SmallKeyShuffle = false; - - if (dungeonItemShuffle == DungeonItemShuffle.Keysanity) - BigKeyShuffle = true; - else - BigKeyShuffle = false; + if (Enum.TryParse(dungeonItemShuffleString, out DungeonItemShuffle dungeonItemShuffle)) + _undoRedoManager.Execute(new ChangeDungeonItemShuffle(_game.Mode, dungeonItemShuffle)); } - private void SetWorldState(WorldState worldState) + private void SetWorldState(string worldStateString) { - _game.Mode.WorldState = worldState; + if (Enum.TryParse(worldStateString, out WorldState worldState)) + _undoRedoManager.Execute(new ChangeWorldState(_game.Mode, worldState)); } - private void SetEntranceShuffle(bool entranceShuffle) + private void ToggleEntranceShuffle() { - _game.Mode.EntranceShuffle = entranceShuffle; + _undoRedoManager.Execute(new ChangeEntranceShuffle(_game.Mode, !_game.Mode.EntranceShuffle.Value)); } - private void SetBossShuffle(bool bossShuffle) + private void ToggleBossShuffle() { - _game.Mode.BossShuffle = bossShuffle; + _undoRedoManager.Execute(new ChangeBossShuffle(_game.Mode, !_game.Mode.BossShuffle.Value)); } - private void SetEnemyShuffle(bool enemyShuffle) + private void ToggleEnemyShuffle() { - _game.Mode.EnemyShuffle = enemyShuffle; + _undoRedoManager.Execute(new ChangeEnemyShuffle(_game.Mode, !_game.Mode.EnemyShuffle.Value)); } - private void OnPropertyChanged(object sender, PropertyChangedEventArgs e) + private void OnModeChanged(object sender, PropertyChangedEventArgs e) { - if (e.PropertyName == nameof(ItemPlacementBasic) && ItemPlacementBasic) - SetItemPlacement(ItemPlacement.Basic); + if (e.PropertyName == nameof(_game.Mode.ItemPlacement)) + RefreshItemPlacement(); - if (e.PropertyName == nameof(ItemPlacementAdvanced) && ItemPlacementAdvanced) - SetItemPlacement(ItemPlacement.Advanced); + if (e.PropertyName == nameof(_game.Mode.DungeonItemShuffle)) + RefreshDungeonItemShuffle(); - if (e.PropertyName == nameof(DungeonItemShuffleStandard) && DungeonItemShuffleStandard) - SetDungeonItemShuffle(DungeonItemShuffle.Standard); + if (e.PropertyName == nameof(_game.Mode.WorldState)) + RefreshWorldState(); - if (e.PropertyName == nameof(DungeonItemShuffleMapsCompasses) && DungeonItemShuffleMapsCompasses) - SetDungeonItemShuffle(DungeonItemShuffle.MapsCompasses); + if (e.PropertyName == nameof(_game.Mode.EntranceShuffle)) + RefreshEntranceShuffle(); - if (e.PropertyName == nameof(DungeonItemShuffleMapsCompassesSmallKeys) && DungeonItemShuffleMapsCompassesSmallKeys) - SetDungeonItemShuffle(DungeonItemShuffle.MapsCompassesSmallKeys); + if (e.PropertyName == nameof(_game.Mode.BossShuffle)) + RefreshBossShuffle(); - if (e.PropertyName == nameof(DungeonItemShuffleKeysanity) && DungeonItemShuffleKeysanity) - SetDungeonItemShuffle(DungeonItemShuffle.Keysanity); + if (e.PropertyName == nameof(_game.Mode.EnemyShuffle)) + RefreshEnemyShuffle(); + } - if (e.PropertyName == nameof(WorldStateStandardOpen) && WorldStateStandardOpen) - SetWorldState(WorldState.StandardOpen); + private void OnUndoChanged(object sender, NotifyCollectionChangedEventArgs e) + { + UpdateCanUndo(); + } + + private void OnRedoChanged(object sender, NotifyCollectionChangedEventArgs e) + { + UpdateCanRedo(); + } + + private void UpdateCanUndo() + { + CanUndo = _undoRedoManager.CanUndo(); + } + + private void Undo() + { + _undoRedoManager.Undo(); + } + + private void UpdateCanRedo() + { + CanRedo = _undoRedoManager.CanRedo(); + } + + private void Redo() + { + _undoRedoManager.Redo(); + } - if (e.PropertyName == nameof(WorldStateInverted) && WorldStateInverted) - SetWorldState(WorldState.Inverted); + private void ToggleDisplayAllLocations() + { + _appSettings.DisplayAllLocations = !_appSettings.DisplayAllLocations; + } - if (e.PropertyName == nameof(EntranceShuffle)) - SetEntranceShuffle(EntranceShuffle); + public async void Reset() + { + bool? result = await _dialogService.ShowDialog( + new MessageBoxDialogVM("Warning", + "Resetting the tracker will set all items and locations back to their starting values. This cannot be undone.\nDo you wish to proceed?")); - if (e.PropertyName == nameof(BossShuffle)) - SetBossShuffle(BossShuffle); + if (result.HasValue && result.Value) + _game.Reset(); + } - if (e.PropertyName == nameof(EnemyShuffle)) - SetEnemyShuffle(EnemyShuffle); + public async void ColorSelect() + { + bool? result = await _dialogService.ShowDialog(_appSettings); } public void SaveAppSettings() diff --git a/OpenTracker/ViewModels/MapControlVM.cs b/OpenTracker/ViewModels/MapControlVM.cs index 706a77d6..26f21fd5 100644 --- a/OpenTracker/ViewModels/MapControlVM.cs +++ b/OpenTracker/ViewModels/MapControlVM.cs @@ -20,8 +20,8 @@ public string ImageSource private set => this.RaiseAndSetIfChanged(ref _imageSource, value); } - public MapControlVM(AppSettingsVM appSettings, Game game, - MainWindowVM mainWindow, MapID iD) + public MapControlVM(UndoRedoManager undoRedoManager, AppSettingsVM appSettings, + Game game, MainWindowVM mainWindow, MapID iD) { _game = game; _iD = iD; @@ -35,7 +35,10 @@ public MapControlVM(AppSettingsVM appSettings, Game game, foreach (MapLocation mapLocation in location.MapLocations) { if (mapLocation.Map == iD) - MapLocations.Add(new MapLocationControlVM(appSettings, game, mainWindow, mapLocation)); + { + MapLocations.Add(new MapLocationControlVM(undoRedoManager, appSettings, + game, mainWindow, mapLocation)); + } } } diff --git a/OpenTracker/ViewModels/MapLocationControlVM.cs b/OpenTracker/ViewModels/MapLocationControlVM.cs index 4c838d93..103a6bda 100644 --- a/OpenTracker/ViewModels/MapLocationControlVM.cs +++ b/OpenTracker/ViewModels/MapLocationControlVM.cs @@ -1,5 +1,6 @@ using Avalonia; using Avalonia.Media; +using OpenTracker.Actions; using OpenTracker.Interfaces; using OpenTracker.Models; using OpenTracker.Models.Enums; @@ -13,6 +14,7 @@ namespace OpenTracker.ViewModels { public class MapLocationControlVM : ViewModelBase, IMapLocationControlVM { + private readonly UndoRedoManager _undoRedoManager; private readonly AppSettingsVM _appSettings; private readonly Game _game; private readonly MainWindowVM _mainWindow; @@ -88,9 +90,10 @@ public string ImageSource private set => this.RaiseAndSetIfChanged(ref _imageSource, value); } - public MapLocationControlVM(AppSettingsVM appSettings, Game game, - MainWindowVM mainWindow, MapLocation mapLocation) + public MapLocationControlVM(UndoRedoManager undoRedoManager, AppSettingsVM appSettings, + Game game, MainWindowVM mainWindow, MapLocation mapLocation) { + _undoRedoManager = undoRedoManager; _appSettings = appSettings; _game = game; _mainWindow = mainWindow; @@ -112,9 +115,10 @@ public MapLocationControlVM(AppSettingsVM appSettings, Game game, private void CheckLocationAvailability() { + ObservableCollection pinnedLocations = _mainWindow.PinnedLocations; PinnedLocationControlVM thisPinnedLocation = null; - foreach (PinnedLocationControlVM pinnedLocation in _mainWindow.PinnedLocations) + foreach (PinnedLocationControlVM pinnedLocation in pinnedLocations) { if (pinnedLocation.Location == _mapLocation.Location) thisPinnedLocation = pinnedLocation; @@ -134,7 +138,7 @@ private void CheckLocationAvailability() } if (!sectionAvailable) - _mainWindow.PinnedLocations.Remove(thisPinnedLocation); + thisPinnedLocation.Close(); } } @@ -287,30 +291,41 @@ private void SetVisibility() public void ClearAvailableSections() { + bool canBeCleared = false; + foreach (ISection section in _mapLocation.Location.Sections) { - if (section.IsAvailable() && (section.Accessibility >= AccessibilityLevel.Inspect || + if (section.IsAvailable() && + (section.Accessibility >= AccessibilityLevel.SequenceBreak || + (section.Accessibility == AccessibilityLevel.Inspect && + section.Marking == null) || section is EntranceSection)) - section.Clear(); + canBeCleared = true; } - CheckLocationAvailability(); + if (canBeCleared) + _undoRedoManager.Execute(new ClearLocation(_game, _mapLocation.Location)); } public void PinLocation() { + ObservableCollection pinnedLocations = _mainWindow.PinnedLocations; PinnedLocationControlVM existingPinnedLocation = null; - foreach (PinnedLocationControlVM pinnedLocation in _mainWindow.PinnedLocations) + foreach (PinnedLocationControlVM pinnedLocation in pinnedLocations) { if (pinnedLocation.Location == _mapLocation.Location) existingPinnedLocation = pinnedLocation; } - if (existingPinnedLocation != null) - _mainWindow.PinnedLocations.Remove(existingPinnedLocation); - - _mainWindow.PinnedLocations.Insert(0, new PinnedLocationControlVM(_appSettings, _game, _mainWindow, _mapLocation.Location)); + if (existingPinnedLocation == null) + { + _undoRedoManager.Execute(new PinLocation(pinnedLocations, + new PinnedLocationControlVM(_undoRedoManager, _appSettings, _game, _mainWindow, + _mapLocation.Location))); + } + else if (pinnedLocations[0] != existingPinnedLocation) + _undoRedoManager.Execute(new PinLocation(pinnedLocations, existingPinnedLocation)); } private void OnAppSettingsChanged(object sender, PropertyChangedEventArgs e) diff --git a/OpenTracker/ViewModels/PinnedLocationControlVM.cs b/OpenTracker/ViewModels/PinnedLocationControlVM.cs index 03b6e31e..14a9d524 100644 --- a/OpenTracker/ViewModels/PinnedLocationControlVM.cs +++ b/OpenTracker/ViewModels/PinnedLocationControlVM.cs @@ -1,4 +1,5 @@ -using OpenTracker.Interfaces; +using OpenTracker.Actions; +using OpenTracker.Interfaces; using OpenTracker.Models; using OpenTracker.Models.Interfaces; using System.Collections.ObjectModel; @@ -7,14 +8,17 @@ namespace OpenTracker.ViewModels { public class PinnedLocationControlVM : ViewModelBase, IPinnedLocationControlVM { + private readonly UndoRedoManager _undoRedoManager; private readonly MainWindowVM _mainWindow; public Location Location { get; } public string Name { get; } public ObservableCollection Sections { get; } - public PinnedLocationControlVM(AppSettingsVM appSettings, Game game, MainWindowVM mainWindow, Location location) + public PinnedLocationControlVM(UndoRedoManager undoRedoManager, AppSettingsVM appSettings, + Game game, MainWindowVM mainWindow, Location location) { + _undoRedoManager = undoRedoManager; _mainWindow = mainWindow; Location = location; @@ -23,12 +27,12 @@ public PinnedLocationControlVM(AppSettingsVM appSettings, Game game, MainWindowV Sections = new ObservableCollection(); foreach (ISection section in location.Sections) - Sections.Add(new SectionControlVM(appSettings, game, section)); + Sections.Add(new SectionControlVM(undoRedoManager, appSettings, game, section)); } public void Close() { - _mainWindow.PinnedLocations.Remove(this); + _undoRedoManager.Execute(new UnpinLocation(_mainWindow.PinnedLocations, this)); } } } \ No newline at end of file diff --git a/OpenTracker/ViewModels/SectionControlVM.cs b/OpenTracker/ViewModels/SectionControlVM.cs index 2cb7f525..88c90c9d 100644 --- a/OpenTracker/ViewModels/SectionControlVM.cs +++ b/OpenTracker/ViewModels/SectionControlVM.cs @@ -1,4 +1,5 @@ using Avalonia.Media; +using OpenTracker.Actions; using OpenTracker.Interfaces; using OpenTracker.Models; using OpenTracker.Models.Enums; @@ -13,6 +14,7 @@ namespace OpenTracker.ViewModels { public class SectionControlVM : ViewModelBase, ISectionControlVM { + private readonly UndoRedoManager _undoRedoManager; private readonly AppSettingsVM _appSettings; private readonly Game _game; private readonly ISection _section; @@ -82,13 +84,16 @@ public string BossImageSource public ReactiveCommand ClearVisibleItemCommand { get; } - public SectionControlVM(AppSettingsVM appSettings, Game game, ISection section) + public SectionControlVM(UndoRedoManager undoRedoManager, AppSettingsVM appSettings, + Game game, ISection section) { - ClearVisibleItemCommand = ReactiveCommand.Create(ClearMarking); + _undoRedoManager = undoRedoManager; _appSettings = appSettings; _game = game; _section = section; + ClearVisibleItemCommand = ReactiveCommand.Create(ClearMarking); + ItemSelect = new ObservableCollection(); if (_section.HasMarking) @@ -176,7 +181,7 @@ public SectionControlVM(AppSettingsVM appSettings, Game game, ISection section) private void ClearMarking() { - _section.Marking = null; + _undoRedoManager.Execute(new MarkSection(_section, null)); MarkingPopupOpen = false; } @@ -294,7 +299,6 @@ private void Update() } } - switch (_section) { case ItemSection itemSection: @@ -388,7 +392,7 @@ public void ChangeMarking(MarkingType? marking) } } - _section.Marking = marking; + _undoRedoManager.Execute(new MarkSection(_section, marking)); if (Enum.TryParse(_section.Marking.ToString(), out ItemType newItemType)) { @@ -415,40 +419,46 @@ public void OpenMarkingSelect() public void ChangeAvailable(bool rightClick = false) { - if (_section.Accessibility >= AccessibilityLevel.SequenceBreak || rightClick || - _section is EntranceSection) + if (rightClick) { switch (_section) { - case ItemSection itemSection: - - itemSection.Available = Math.Max(0, Math.Min(itemSection.Total, itemSection.Available + (rightClick ? 1 : -1))); - - if (itemSection.Marking != null && itemSection.Available == 0) - { - if (Enum.TryParse(itemSection.Marking.ToString(), out ItemType itemType)) - { - Item item = _game.Items[itemType]; - item.Change(1); - itemSection.Marking = null; - } - } - + case BossSection bossSectionUncollect: + if (!bossSectionUncollect.Available) + _undoRedoManager.Execute(new UncollectSection(_section)); break; - - case BossSection bossSection: - - bossSection.Available = rightClick; - + case EntranceSection entranceSectionUncollect: + if (!entranceSectionUncollect.Available) + _undoRedoManager.Execute(new UncollectSection(_section)); break; - - case EntranceSection entranceSection: - - entranceSection.Available = rightClick; - + case ItemSection itemSectionUncollect: + if (itemSectionUncollect.Available < itemSectionUncollect.Total) + _undoRedoManager.Execute(new UncollectSection(_section)); break; } } + else + { + if (_section is EntranceSection || + _section.Accessibility >= AccessibilityLevel.SequenceBreak) + { + switch (_section) + { + case BossSection bossSectionCollect: + if (bossSectionCollect.Available) + _undoRedoManager.Execute(new CollectSection(_game, _section)); + break; + case EntranceSection entranceSectionCollect: + if (entranceSectionCollect.Available) + _undoRedoManager.Execute(new CollectSection(_game, _section)); + break; + case ItemSection itemSectionCollect: + if (itemSectionCollect.Available > 0) + _undoRedoManager.Execute(new CollectSection(_game, _section)); + break; + } + } + } } } } diff --git a/OpenTracker/ViewModels/UndoRedoManager.cs b/OpenTracker/ViewModels/UndoRedoManager.cs new file mode 100644 index 00000000..50b42c32 --- /dev/null +++ b/OpenTracker/ViewModels/UndoRedoManager.cs @@ -0,0 +1,55 @@ +using OpenTracker.Interfaces; +using OpenTracker.Utils; + +namespace OpenTracker.ViewModels +{ + public class UndoRedoManager + { + public ObservableStack UndoableActions { get; } + public ObservableStack RedoableActions { get; } + + public UndoRedoManager() + { + UndoableActions = new ObservableStack(); + RedoableActions = new ObservableStack(); + } + + public bool CanUndo() + { + return UndoableActions.Count > 0; + } + + public bool CanRedo() + { + return RedoableActions.Count > 0; + } + + public void Execute(IUndoable action, bool clearRedo = true) + { + UndoableActions.Push(action); + action.Execute(); + + if (clearRedo) + RedoableActions.Clear(); + } + + public void Undo() + { + if (UndoableActions.Count > 0) + { + IUndoable action = UndoableActions.Pop(); + RedoableActions.Push(action); + action.Undo(); + } + } + + public void Redo() + { + if (RedoableActions.Count > 0) + { + IUndoable action = RedoableActions.Pop(); + Execute(action, false); + } + } + } +} diff --git a/OpenTracker/Views/MainWindow.xaml b/OpenTracker/Views/MainWindow.xaml index c01cbf09..887905e4 100644 --- a/OpenTracker/Views/MainWindow.xaml +++ b/OpenTracker/Views/MainWindow.xaml @@ -19,6 +19,8 @@ + + @@ -62,13 +64,15 @@ Basic + Background="Transparent" IsChecked="{Binding Path=ItemPlacementBasic, Mode=OneWay}" + GroupName="ItemPlacement" Margin="4,4,4,8" Command="{Binding + Path=ItemPlacementCommand}" CommandParameter="Basic">Basic Advanced + Background="Transparent" IsChecked="{Binding Path=ItemPlacementAdvanced, Mode=OneWay}" + GroupName="ItemPlacement" Margin="4,4,4,8" Command="{Binding + Path=ItemPlacementCommand}" CommandParameter="Advanced">Advanced Standard + Mode=OneWay}" GroupName="DungeonItemShuffle" Margin="4,4,4,8" Command="{Binding + Path=DungeonItemShuffleCommand}" CommandParameter="Standard">Standard Maps/Compasses + Mode=OneWay}" GroupName="DungeonItemShuffle" + Margin="4,4,4,8" Command="{Binding Path=DungeonItemShuffleCommand}" + CommandParameter="MapsCompasses">Maps/Compasses Maps/Compasses/Small Keys + Path=DungeonItemShuffleMapsCompassesSmallKeys, Mode=OneWay}" + GroupName="DungeonItemShuffle" Margin="4,4,4,8" Command="{Binding + Path=DungeonItemShuffleCommand}" + CommandParameter="MapsCompassesSmallKeys">Maps/Compasses/Small Keys Keysanity + Mode=OneWay}" GroupName="DungeonItemShuffle" Margin="4,4,4,8" Command="{Binding + Path=DungeonItemShuffleCommand}" CommandParameter="Keysanity">Keysanity Standard/Open + Mode=OneWay}" GroupName="WorldState" Margin="4,4,4,8" Command="{Binding + Path=WorldStateCommand}" CommandParameter="StandardOpen">Standard/Open Inverted + Background="Transparent" IsChecked="{Binding Path=WorldStateInverted, Mode=OneWay}" + GroupName="WorldState" Margin="4,4,4,8" Command="{Binding Path=WorldStateCommand}" + CommandParameter="Inverted">Inverted Entrance Shuffle + Background="Transparent" IsChecked="{Binding Path=EntranceShuffle, Mode=OneWay}" + Margin="4" Command="{Binding Path=EntranceShuffleCommand}">Entrance Shuffle Boss Shuffle + Background="Transparent" IsChecked="{Binding Path=BossShuffle, Mode=OneWay}" + Margin="4" Command="{Binding Path=BossShuffleCommand}">Boss Shuffle Enemy Shuffle + Background="Transparent" IsChecked="{Binding Path=EnemyShuffle, Mode=OneWay}" + Margin="4" Command="{Binding Path=EnemyShuffleCommand}">Enemy Shuffle @@ -373,6 +384,35 @@ Height="16" Width="16" ToolTip.Tip="App Settings" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -383,11 +423,27 @@ - - - + + + + + + + + + + + + + + + + + + + - + @@ -404,6 +460,7 @@ +