diff --git a/.gitignore b/.gitignore
index 1c9a181..8a30d25 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,10 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
# User-specific files
+*.rsuser
*.suo
*.user
*.userosscache
@@ -10,48 +13,72 @@
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
+# Mono auto generated files
+mono_crash.*
+
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
-[Xx]64/
-[Xx]86/
-[Bb]uild/
+x64/
+x86/
+[Ww][Ii][Nn]32/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
+[Ll]og/
+[Ll]ogs/
-# Visual Studio 2015 cache/options directory
+# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
-# NUNIT
+# NUnit
*.VisualState.xml
TestResult.xml
+nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
-# DNX
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
project.lock.json
+project.fragment.lock.json
artifacts/
+# ASP.NET Scaffolding
+ScaffoldingReadMe.txt
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
*_i.c
*_p.c
-*_i.h
+*_h.h
*.ilk
*.meta
*.obj
+*.iobj
*.pch
*.pdb
+*.ipdb
*.pgc
*.pgd
*.rsp
@@ -61,7 +88,9 @@ artifacts/
*.tlh
*.tmp
*.tmp_proj
+*_wpftmp.csproj
*.log
+*.tlog
*.vspscc
*.vssscc
.builds
@@ -81,6 +110,7 @@ ipch/
*.sdf
*.cachefile
*.VC.db
+*.VC.VC.opendb
# Visual Studio profiler
*.psess
@@ -88,6 +118,9 @@ ipch/
*.vspx
*.sap
+# Visual Studio Trace Files
+*.e2e
+
# TFS 2012 Local Workspace
$tf/
@@ -99,15 +132,25 @@ _ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
-# JustCode is a .NET coding add-in
-.JustCode
-
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Coverlet is a free, cross platform Code Coverage Tool
+coverage*.json
+coverage*.xml
+coverage*.info
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
# NCrunch
_NCrunch_*
.*crunch*.local.xml
@@ -139,22 +182,27 @@ publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
-
-# TODO: Un-comment the next line if you do not want to checkin
-# your web deploy settings because they may include unencrypted
-# passwords
-#*.pubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
*.publishproj
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
# NuGet Packages
*.nupkg
+# NuGet Symbol Packages
+*.snupkg
# The packages folder can be ignored because of Package Restore
-**/packages/*
+**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
-!**/packages/build/
+!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
-#!**/packages/repositories.config
-# NuGet v3's project.json files produces more ignoreable files
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
@@ -166,28 +214,40 @@ csx/
ecf/
rcf/
-# Windows Store app package directory
+# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
-!*.[Cc]ache/
+!?*.[Cc]ache/
# Others
ClientBin/
-[Ss]tyle[Cc]op.*
~$*
*~
*.dbmdl
*.dbproj.schemaview
+*.jfm
*.pfx
*.publishsettings
-node_modules/
orleans.codegen.cs
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
# RIA/Silverlight projects
Generated_Code/
@@ -198,15 +258,22 @@ _UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
+*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
@@ -216,6 +283,7 @@ FakesAssemblies/
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
+node_modules/
# Visual Studio 6 build log
*.plg
@@ -223,6 +291,20 @@ FakesAssemblies/
# Visual Studio 6 workspace options file
*.opt
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio 6 auto-generated project file (contains which files were open etc.)
+*.vbp
+
+# Visual Studio 6 workspace and project file (working project files containing files to include in project)
+*.dsw
+*.dsp
+
+# Visual Studio 6 technical files
+*.ncb
+*.aps
+
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
@@ -231,12 +313,86 @@ FakesAssemblies/
**/*.Server/ModelManifest.xml
_Pvt_Extensions
-# LightSwitch generated files
-GeneratedArtifacts/
-ModelManifest.xml
-
# Paket dependency manager
.paket/paket.exe
+paket-files/
# FAKE - F# Make
.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# Visual Studio History (VSHistory) files
+.vshistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
+
+# Fody - auto-generated XML schema
+FodyWeavers.xsd
+
+# VS Code files for those working on multiple tools
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+*.code-workspace
+
+# Local History for Visual Studio Code
+.history/
+
+# Windows Installer files from build outputs
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# JetBrains Rider
+*.sln.iml
diff --git a/GwentDiscovery/App.config b/GwentDiscovery/App.config
deleted file mode 100644
index 4bfa005..0000000
--- a/GwentDiscovery/App.config
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/GwentDiscovery/App.xaml b/GwentDiscovery/App.xaml
deleted file mode 100644
index 6b6d505..0000000
--- a/GwentDiscovery/App.xaml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
diff --git a/GwentDiscovery/App.xaml.cs b/GwentDiscovery/App.xaml.cs
deleted file mode 100644
index 1027a10..0000000
--- a/GwentDiscovery/App.xaml.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Configuration;
-using System.Data;
-using System.Linq;
-using System.Threading.Tasks;
-using System.Windows;
-
-namespace GwentDiscovery
-{
- ///
- /// Interaction logic for App.xaml
- ///
- public partial class App : Application
- {
- }
-}
diff --git a/GwentDiscovery/GwentDiscovery.csproj b/GwentDiscovery/GwentDiscovery.csproj
deleted file mode 100644
index 8d7a1fd..0000000
--- a/GwentDiscovery/GwentDiscovery.csproj
+++ /dev/null
@@ -1,133 +0,0 @@
-
-
-
-
- Debug
- AnyCPU
- {F8E5957C-4B09-4ABF-AA3B-2C7CEE53D26B}
- WinExe
- Properties
- GwentDiscovery
- GwentDiscovery
- v4.8
- 512
- {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
- 4
- true
-
-
-
- x64
- true
- full
- false
- bin\Debug\
- DEBUG;TRACE
- prompt
- 4
-
-
- AnyCPU
- pdbonly
- true
- bin\Release\
- TRACE
- prompt
- 4
-
-
- true
- bin\x86\Debug\
- DEBUG;TRACE
- full
- x86
- prompt
- MinimumRecommendedRules.ruleset
- true
-
-
- bin\x86\Release\
- TRACE
- true
- pdbonly
- x86
- prompt
- MinimumRecommendedRules.ruleset
- true
-
-
-
-
-
-
-
-
-
-
-
- 4.0
-
-
-
-
-
-
-
- MSBuild:Compile
- Designer
-
-
- MSBuild:Compile
- Designer
-
-
- App.xaml
- Code
-
-
- MainWindow.xaml
- Code
-
-
-
-
- Code
-
-
- True
- True
- Resources.resx
-
-
- True
- Settings.settings
- True
-
-
- ResXFileCodeGenerator
- Resources.Designer.cs
-
-
- SettingsSingleFileGenerator
- Settings.Designer.cs
-
-
-
-
-
-
-
-
- {7a608786-5c00-40f7-8f84-846d25871ec8}
- Witcher3MapViewer
-
-
-
-
-
\ No newline at end of file
diff --git a/GwentDiscovery/MainWindow.xaml b/GwentDiscovery/MainWindow.xaml
deleted file mode 100644
index 775d570..0000000
--- a/GwentDiscovery/MainWindow.xaml
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
diff --git a/GwentDiscovery/MainWindow.xaml.cs b/GwentDiscovery/MainWindow.xaml.cs
deleted file mode 100644
index a33a811..0000000
--- a/GwentDiscovery/MainWindow.xaml.cs
+++ /dev/null
@@ -1,257 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Collections.ObjectModel;
-using System.IO;
-using System.Linq;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-using System.Windows;
-using System.Windows.Controls;
-using System.Windows.Data;
-using System.Windows.Documents;
-using System.Windows.Input;
-using System.Windows.Media;
-using System.Windows.Media.Imaging;
-using System.Windows.Navigation;
-using Witcher3MapViewer;
-
-namespace GwentDiscovery
-{
- ///
- /// Interaction logic for MainWindow.xaml
- ///
- public partial class MainWindow : Window
- {
- string SaveFolder = @"C:\Users\Reuben\Documents\The Witcher 3\gamesaves";
- private string appdir;
- Witcher3SaveFile ActiveSave;
- string ActiveSaveFileCopy;
- FileSystemWatcher _fileSystemWatcher;
- bool SaveChanged = false;
- ManualResetEvent _refreshRequested = new ManualResetEvent(false);
- List BaseGame;
- List BloodAndWine;
- ObservableCollection _cards;
- public ObservableCollection Cards { get { return _cards; } }
- System.Diagnostics.Stopwatch updatedtimer = new System.Diagnostics.Stopwatch();
-
- string _statusText = "This is the status";
- public string StatusText { get
- {
- return _statusText;
- }
- set
- {
- _statusText = value;
- }
- }
- int numowned = 0;
-
- public MainWindow()
- {
- DataContext = this;
- _cards = new ObservableCollection();
- appdir = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
-
- GetGwentCardList();
- GetMostRecentSaveFile();
- SetGwentCardStatuses();
- _cards.Sort();
- InitializeComponent();
-
-
-
-
- SetUpFilewatcher();
- }
-
- private void SetGwentCardStatuses()
- {
- Dictionary OwnedCards = new Dictionary();
- foreach (Witcher3GwentCard thecard in ActiveSave.GwentManager.RegularCards)
- OwnedCards[thecard.cardIndex] = thecard;
- foreach (Witcher3GwentCard thecard in ActiveSave.GwentManager.LeaderCards)
- OwnedCards[thecard.cardIndex] = thecard;
-
- numowned = 0;
- foreach (Witcher3GwentCard thecard in BaseGame)
- {
- bool isowned = false;
- if (OwnedCards.ContainsKey(thecard.cardIndex))
- {
- Witcher3GwentCard owned = OwnedCards[thecard.cardIndex];
- thecard.numCopies = owned.numCopies;
- isowned = true;
- numowned++;
- }
- GwentCardViewModel model = new GwentCardViewModel(thecard);
- model.IsChecked = isowned;
- _cards.Add(model);
- }
- StatusText = "Currently have [" + numowned.ToString() + "/" + BaseGame.Count.ToString() + "]";
- //foreach (Witcher3GwentCard thecard in ActiveSave.GwentManager.LeaderCards)
- // _cards.Add(new GwentCardViewModel(thecard));
- }
-
- private void GetGwentCardList()
- {
- var reader = new Witcher3MapViewer.Readers.GwentXMLReader(Path.Combine(@"C:\Users\Reuben\Dropbox\Programs\Witcher3MapViewer\Witcher3MapViewer\Witcher3MapViewer\bin\x64\Debug", "Gwent.xml"));
-
- BaseGame = new List();
- foreach (Witcher3MapViewer.Readers.GwentCardAsRead cardasread in reader.Sets[0].Cards)
- {
- Witcher3GwentCard card = new Witcher3GwentCard();
- card.cardIndex = cardasread.ID;
- card.Name = cardasread.Name;
- card.Location = cardasread.Location;
- card.AssociatedQuest = cardasread.AssociatedQuest;
- BaseGame.Add(card);
- }
- }
-
- private void GetMostRecentSaveFile()
- {
- //Get file listing, find most recent file
- DirectoryInfo directory = new DirectoryInfo(SaveFolder);
- FileInfo myFile = (from f in directory.GetFiles("*.sav")
- orderby f.LastWriteTime descending
- select f).First();
- //copy to local directory
- if (CopyWhenAvailable(myFile.FullName))
- {
- myFile = new FileInfo(Path.Combine(appdir, "SavedGames", myFile.Name));
- ActiveSaveFileCopy = myFile.FullName;
- ActiveSave = new Witcher3SaveFile(myFile.FullName, Witcher3ReadLevel.Quick);
- }
- }
-
- private bool CopyWhenAvailable(string fullPath)
- {
- int numTries = 0;
- while (true)
- {
- numTries++;
- try
- {
- File.Copy(fullPath, Path.Combine(appdir, "SavedGames", Path.GetFileName(fullPath)), true);
- break;
- }
- catch (System.Reflection.AmbiguousMatchException) //this is just so we don't assign the exception to a variable
- {
- //Failed to get access
- if (numTries > 10)
- {
- //give up
- return false;
- }
- // Wait for the lock to be released
- Thread.Sleep(500);
- }
- }
- //Success
- return true;
- }
-
- private void SetUpFilewatcher()
- {
- _fileSystemWatcher = new FileSystemWatcher(SaveFolder);
- _fileSystemWatcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName;
- _fileSystemWatcher.Filter = "*.sav";
- _fileSystemWatcher.Changed += _fileSystemWatcher_Changed;
- _fileSystemWatcher.Created += _fileSystemWatcher_Changed;
- _fileSystemWatcher.Renamed += _fileSystemWatcher_Changed;
- _fileSystemWatcher.EnableRaisingEvents = true;
- }
-
- private void _fileSystemWatcher_Changed(object sender, FileSystemEventArgs e)
- {
- Thread.Sleep(2000);
- SaveChanged = true;
- _refreshRequested.Set();
- ThreadPool.QueueUserWorkItem(RefreshIfNecessary);
- }
-
- private void RefreshIfNecessary(object state)
- {
- lock (this)
- {
- if (!_refreshRequested.WaitOne(0))
- {
- // Refresh not necessary
- return;
- }
- if (SaveChanged)
- {
- ThreadPool.QueueUserWorkItem(delegate
- {
- Dispatcher.BeginInvoke(new Action(ExecuteSaveChange));
- });
- }
- SaveChanged = false;
- _refreshRequested.Reset();
- }
- }
-
- private void ExecuteSaveChange()
- {
- ClearLocalSave();
- GetMostRecentSaveFile();
- updatedtimer.Stop();
- //foreach (Witcher3GwentCard thecard in ActiveSave.GwentManager.RegularCards)
- //{
- // if (!AlreadyHaveThatCard(thecard))
- // {
- // GwentCardViewModel gcvm = new GwentCardViewModel(thecard);
- // gcvm.IsObtained = true;
- // _cards.Add(gcvm);
- // }
- //}
- //foreach (Witcher3GwentCard thecard in ActiveSave.GwentManager.LeaderCards)
- //{
- // if (!AlreadyHaveThatCard(thecard))
- // {
- // GwentCardViewModel gcvm = new GwentCardViewModel(thecard);
- // gcvm.IsObtained = true;
- // _cards.Add(gcvm);
- // }
- //}
- _cards.Sort();
- updatedtimer.Reset();
- updatedtimer.Start();
- }
-
- private bool AlreadyHaveThatCard(Witcher3GwentCard thecard)
- {
- //var foo = _cards.Where(item => item.IDNumber == thecard.cardIndex).FirstOrDefault();
- //if (foo != null)
- //{
- // if (foo.NumHeld != thecard.numCopies)
- // {
- // foo.NumHeld = thecard.numCopies;
- // foo.IsObtained = true;
- // }
- // else if(updatedtimer.ElapsedMilliseconds > 10000) foo.IsObtained = false;
- // return true;
- //}
- //return false;
- return false;
- }
-
- private void ClearLocalSave()
- {
- if (File.Exists(ActiveSaveFileCopy))
- File.Delete(ActiveSaveFileCopy);
- }
- }
-
- static class Extensions
- {
- public static void Sort(this ObservableCollection collection) where T : IComparable
- {
- List sorted = collection.OrderBy(x => x).ToList();
- for (int i = 0; i < sorted.Count(); i++)
- collection.Move(collection.IndexOf(sorted[i]), i);
- }
- }
-}
diff --git a/GwentDiscovery/Properties/AssemblyInfo.cs b/GwentDiscovery/Properties/AssemblyInfo.cs
deleted file mode 100644
index 486cda5..0000000
--- a/GwentDiscovery/Properties/AssemblyInfo.cs
+++ /dev/null
@@ -1,55 +0,0 @@
-using System.Reflection;
-using System.Resources;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-using System.Windows;
-
-// General Information about an assembly is controlled through the following
-// set of attributes. Change these attribute values to modify the information
-// associated with an assembly.
-[assembly: AssemblyTitle("GwentDiscovery")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("GwentDiscovery")]
-[assembly: AssemblyCopyright("Copyright © 2017")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-
-// Setting ComVisible to false makes the types in this assembly not visible
-// to COM components. If you need to access a type in this assembly from
-// COM, set the ComVisible attribute to true on that type.
-[assembly: ComVisible(false)]
-
-//In order to begin building localizable applications, set
-//CultureYouAreCodingWith in your .csproj file
-//inside a . For example, if you are using US english
-//in your source files, set the to en-US. Then uncomment
-//the NeutralResourceLanguage attribute below. Update the "en-US" in
-//the line below to match the UICulture setting in the project file.
-
-//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
-
-
-[assembly: ThemeInfo(
- ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
- //(used if a resource is not found in the page,
- // or application resource dictionaries)
- ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
- //(used if a resource is not found in the page,
- // app, or any theme specific resource dictionaries)
-)]
-
-
-// Version information for an assembly consists of the following four values:
-//
-// Major Version
-// Minor Version
-// Build Number
-// Revision
-//
-// You can specify all the values or you can default the Build and Revision Numbers
-// by using the '*' as shown below:
-// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("1.0.0.0")]
-[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/GwentDiscovery/Properties/Resources.Designer.cs b/GwentDiscovery/Properties/Resources.Designer.cs
deleted file mode 100644
index 5e8df1c..0000000
--- a/GwentDiscovery/Properties/Resources.Designer.cs
+++ /dev/null
@@ -1,63 +0,0 @@
-//------------------------------------------------------------------------------
-//
-// This code was generated by a tool.
-// Runtime Version:4.0.30319.42000
-//
-// Changes to this file may cause incorrect behavior and will be lost if
-// the code is regenerated.
-//
-//------------------------------------------------------------------------------
-
-namespace GwentDiscovery.Properties {
- using System;
-
-
- ///
- /// A strongly-typed resource class, for looking up localized strings, etc.
- ///
- // This class was auto-generated by the StronglyTypedResourceBuilder
- // class via a tool like ResGen or Visual Studio.
- // To add or remove a member, edit your .ResX file then rerun ResGen
- // with the /str option, or rebuild your VS project.
- [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
- [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
- internal class Resources {
-
- private static global::System.Resources.ResourceManager resourceMan;
-
- private static global::System.Globalization.CultureInfo resourceCulture;
-
- [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
- internal Resources() {
- }
-
- ///
- /// Returns the cached ResourceManager instance used by this class.
- ///
- [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
- internal static global::System.Resources.ResourceManager ResourceManager {
- get {
- if (object.ReferenceEquals(resourceMan, null)) {
- global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("GwentDiscovery.Properties.Resources", typeof(Resources).Assembly);
- resourceMan = temp;
- }
- return resourceMan;
- }
- }
-
- ///
- /// Overrides the current thread's CurrentUICulture property for all
- /// resource lookups using this strongly typed resource class.
- ///
- [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
- internal static global::System.Globalization.CultureInfo Culture {
- get {
- return resourceCulture;
- }
- set {
- resourceCulture = value;
- }
- }
- }
-}
diff --git a/GwentDiscovery/Properties/Resources.resx b/GwentDiscovery/Properties/Resources.resx
deleted file mode 100644
index af7dbeb..0000000
--- a/GwentDiscovery/Properties/Resources.resx
+++ /dev/null
@@ -1,117 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- text/microsoft-resx
-
-
- 2.0
-
-
- System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
-
- System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
-
\ No newline at end of file
diff --git a/GwentDiscovery/Properties/Settings.Designer.cs b/GwentDiscovery/Properties/Settings.Designer.cs
deleted file mode 100644
index 69f8565..0000000
--- a/GwentDiscovery/Properties/Settings.Designer.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-//------------------------------------------------------------------------------
-//
-// This code was generated by a tool.
-// Runtime Version:4.0.30319.42000
-//
-// Changes to this file may cause incorrect behavior and will be lost if
-// the code is regenerated.
-//
-//------------------------------------------------------------------------------
-
-namespace GwentDiscovery.Properties {
-
-
- [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
- [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.4.0.0")]
- internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
-
- private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
-
- public static Settings Default {
- get {
- return defaultInstance;
- }
- }
- }
-}
diff --git a/GwentDiscovery/Properties/Settings.settings b/GwentDiscovery/Properties/Settings.settings
deleted file mode 100644
index 033d7a5..0000000
--- a/GwentDiscovery/Properties/Settings.settings
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/SaveExtractor/App.config b/SaveExtractor/App.config
deleted file mode 100644
index 4bfa005..0000000
--- a/SaveExtractor/App.config
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/SaveExtractor/App.xaml b/SaveExtractor/App.xaml
deleted file mode 100644
index 84d7ec9..0000000
--- a/SaveExtractor/App.xaml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
diff --git a/SaveExtractor/App.xaml.cs b/SaveExtractor/App.xaml.cs
deleted file mode 100644
index edde8c3..0000000
--- a/SaveExtractor/App.xaml.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Configuration;
-using System.Data;
-using System.Linq;
-using System.Threading.Tasks;
-using System.Windows;
-
-namespace SaveExtractor
-{
- ///
- /// Interaction logic for App.xaml
- ///
- public partial class App : Application
- {
- }
-}
diff --git a/SaveExtractor/MainWindow.xaml b/SaveExtractor/MainWindow.xaml
deleted file mode 100644
index 8645cfd..0000000
--- a/SaveExtractor/MainWindow.xaml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
-
diff --git a/SaveExtractor/MainWindow.xaml.cs b/SaveExtractor/MainWindow.xaml.cs
deleted file mode 100644
index 47056ef..0000000
--- a/SaveExtractor/MainWindow.xaml.cs
+++ /dev/null
@@ -1,45 +0,0 @@
-using System.IO;
-using Witcher3MapViewer;
-using System.Windows;
-using Microsoft.Win32;
-
-namespace SaveExtractor
-{
- ///
- /// Interaction logic for MainWindow.xaml
- ///
- public partial class MainWindow : Window
- {
- public MainWindow()
- {
- InitializeComponent();
- }
-
- public void dumpfile(string filename)
- {
-
- using (FileStream compressedStream = File.OpenRead(filename))
- using (Stream decompressedStream = ChunkedLz4File.Decompress(compressedStream))
- using (FileStream outputstream = File.OpenWrite(filename.Substring(0, filename.Length-4) + "decomp.sav"))
- {
- decompressedStream.CopyTo(outputstream);
- }
- }
-
- private void Button_Click(object sender, RoutedEventArgs e)
- {
- OpenFileDialog dlg = new OpenFileDialog
- {
- DefaultExt = ".sav",
- Filter = "Save Files (*.sav)|*.sav"
- };
- bool? result = dlg.ShowDialog();
- string path;
- if (result == true)
- {
- path = dlg.FileName;
- dumpfile(path);
- }
- }
- }
-}
diff --git a/SaveExtractor/Properties/AssemblyInfo.cs b/SaveExtractor/Properties/AssemblyInfo.cs
deleted file mode 100644
index 7619269..0000000
--- a/SaveExtractor/Properties/AssemblyInfo.cs
+++ /dev/null
@@ -1,55 +0,0 @@
-using System.Reflection;
-using System.Resources;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-using System.Windows;
-
-// General Information about an assembly is controlled through the following
-// set of attributes. Change these attribute values to modify the information
-// associated with an assembly.
-[assembly: AssemblyTitle("SaveExtractor")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("SaveExtractor")]
-[assembly: AssemblyCopyright("Copyright © 2019")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-
-// Setting ComVisible to false makes the types in this assembly not visible
-// to COM components. If you need to access a type in this assembly from
-// COM, set the ComVisible attribute to true on that type.
-[assembly: ComVisible(false)]
-
-//In order to begin building localizable applications, set
-//CultureYouAreCodingWith in your .csproj file
-//inside a . For example, if you are using US english
-//in your source files, set the to en-US. Then uncomment
-//the NeutralResourceLanguage attribute below. Update the "en-US" in
-//the line below to match the UICulture setting in the project file.
-
-//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
-
-
-[assembly: ThemeInfo(
- ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
- //(used if a resource is not found in the page,
- // or application resource dictionaries)
- ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
- //(used if a resource is not found in the page,
- // app, or any theme specific resource dictionaries)
-)]
-
-
-// Version information for an assembly consists of the following four values:
-//
-// Major Version
-// Minor Version
-// Build Number
-// Revision
-//
-// You can specify all the values or you can default the Build and Revision Numbers
-// by using the '*' as shown below:
-// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("1.0.0.0")]
-[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/SaveExtractor/Properties/Resources.Designer.cs b/SaveExtractor/Properties/Resources.Designer.cs
deleted file mode 100644
index 21e743e..0000000
--- a/SaveExtractor/Properties/Resources.Designer.cs
+++ /dev/null
@@ -1,63 +0,0 @@
-//------------------------------------------------------------------------------
-//
-// This code was generated by a tool.
-// Runtime Version:4.0.30319.42000
-//
-// Changes to this file may cause incorrect behavior and will be lost if
-// the code is regenerated.
-//
-//------------------------------------------------------------------------------
-
-namespace SaveExtractor.Properties {
- using System;
-
-
- ///
- /// A strongly-typed resource class, for looking up localized strings, etc.
- ///
- // This class was auto-generated by the StronglyTypedResourceBuilder
- // class via a tool like ResGen or Visual Studio.
- // To add or remove a member, edit your .ResX file then rerun ResGen
- // with the /str option, or rebuild your VS project.
- [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
- [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
- [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
- internal class Resources {
-
- private static global::System.Resources.ResourceManager resourceMan;
-
- private static global::System.Globalization.CultureInfo resourceCulture;
-
- [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
- internal Resources() {
- }
-
- ///
- /// Returns the cached ResourceManager instance used by this class.
- ///
- [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
- internal static global::System.Resources.ResourceManager ResourceManager {
- get {
- if (object.ReferenceEquals(resourceMan, null)) {
- global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("SaveExtractor.Properties.Resources", typeof(Resources).Assembly);
- resourceMan = temp;
- }
- return resourceMan;
- }
- }
-
- ///
- /// Overrides the current thread's CurrentUICulture property for all
- /// resource lookups using this strongly typed resource class.
- ///
- [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
- internal static global::System.Globalization.CultureInfo Culture {
- get {
- return resourceCulture;
- }
- set {
- resourceCulture = value;
- }
- }
- }
-}
diff --git a/SaveExtractor/Properties/Resources.resx b/SaveExtractor/Properties/Resources.resx
deleted file mode 100644
index af7dbeb..0000000
--- a/SaveExtractor/Properties/Resources.resx
+++ /dev/null
@@ -1,117 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- text/microsoft-resx
-
-
- 2.0
-
-
- System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
-
- System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
-
\ No newline at end of file
diff --git a/SaveExtractor/Properties/Settings.Designer.cs b/SaveExtractor/Properties/Settings.Designer.cs
deleted file mode 100644
index 28570d0..0000000
--- a/SaveExtractor/Properties/Settings.Designer.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-//------------------------------------------------------------------------------
-//
-// This code was generated by a tool.
-// Runtime Version:4.0.30319.42000
-//
-// Changes to this file may cause incorrect behavior and will be lost if
-// the code is regenerated.
-//
-//------------------------------------------------------------------------------
-
-namespace SaveExtractor.Properties {
-
-
- [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
- [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.4.0.0")]
- internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
-
- private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
-
- public static Settings Default {
- get {
- return defaultInstance;
- }
- }
- }
-}
diff --git a/SaveExtractor/Properties/Settings.settings b/SaveExtractor/Properties/Settings.settings
deleted file mode 100644
index 033d7a5..0000000
--- a/SaveExtractor/Properties/Settings.settings
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/SaveExtractor/SaveExtractor.csproj b/SaveExtractor/SaveExtractor.csproj
deleted file mode 100644
index 847d60b..0000000
--- a/SaveExtractor/SaveExtractor.csproj
+++ /dev/null
@@ -1,124 +0,0 @@
-
-
-
-
- Debug
- AnyCPU
- {E91F3CB6-0CE9-4E4C-8C37-58C9A03042CF}
- WinExe
- SaveExtractor
- SaveExtractor
- v4.8
- 512
- {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
- 4
- true
-
-
-
- AnyCPU
- true
- full
- false
- bin\Debug\
- DEBUG;TRACE
- prompt
- 4
-
-
- AnyCPU
- pdbonly
- true
- bin\Release\
- TRACE
- prompt
- 4
-
-
- true
- bin\x64\Debug\
- DEBUG;TRACE
- full
- x64
- prompt
- MinimumRecommendedRules.ruleset
- true
-
-
- bin\x64\Release\
- TRACE
- true
- pdbonly
- x64
- prompt
- MinimumRecommendedRules.ruleset
- true
-
-
-
-
-
-
-
-
-
-
-
- 4.0
-
-
-
-
-
-
-
- MSBuild:Compile
- Designer
-
-
- MSBuild:Compile
- Designer
-
-
- App.xaml
- Code
-
-
- MainWindow.xaml
- Code
-
-
-
-
- Code
-
-
- True
- True
- Resources.resx
-
-
- True
- Settings.settings
- True
-
-
- ResXFileCodeGenerator
- Resources.Designer.cs
-
-
- SettingsSingleFileGenerator
- Settings.Designer.cs
-
-
-
-
-
-
-
- {7a608786-5c00-40f7-8f84-846d25871ec8}
- Witcher3MapViewer
-
-
-
-
\ No newline at end of file
diff --git a/SaveTools/Program.cs b/SaveTools/Program.cs
new file mode 100644
index 0000000..e840879
--- /dev/null
+++ b/SaveTools/Program.cs
@@ -0,0 +1,84 @@
+using CommandLine;
+using SaveFile;
+
+Parser.Default.ParseArguments(args)
+ .WithParsed(o =>
+ {
+ string filename = o.filename;
+ Console.WriteLine($"Opening {filename}");
+ if (!File.Exists(filename))
+ {
+ Console.WriteLine($"Cannot find filename {filename}");
+ return;
+ }
+
+
+ Witcher3SaveFile saveFile = new Witcher3SaveFile(filename, Witcher3ReadLevel.Quick);
+ Console.WriteLine("Success");
+ Console.WriteLine($"Player level is {saveFile.CharacterLevel}");
+ foreach (Witcher3JournalEntryStatus f in saveFile.CJournalManager.Statuses)
+ {
+ Console.WriteLine($"{f.PrimaryGUID}: {f.Status}");
+ }
+ })
+ .WithParsed(o =>
+ {
+ Console.WriteLine("Looking for save folder");
+ string myDocuments = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
+ string ersatzpath = Path.Combine(myDocuments, "The Witcher 3", "gamesaves");
+ if (Directory.Exists(ersatzpath))
+ Console.WriteLine($"Found at {ersatzpath}");
+
+ string l_filepath = Path.Combine(ersatzpath, o.filename1);
+ string r_filepath = Path.Combine(ersatzpath, o.filename2);
+ if (!File.Exists(l_filepath))
+ {
+ Console.WriteLine($"Cannot find filename {l_filepath}");
+ return;
+ }
+ if (!File.Exists(r_filepath))
+ {
+ Console.WriteLine($"Cannot find filename {r_filepath}");
+ return;
+ }
+ Console.WriteLine($"Comparing {l_filepath} to {r_filepath}");
+ Witcher3SaveFile l_saveFile = new Witcher3SaveFile(l_filepath, Witcher3ReadLevel.Quick);
+ Witcher3SaveFile r_saveFile = new Witcher3SaveFile(r_filepath, Witcher3ReadLevel.Quick);
+ var l_statuses = l_saveFile.CJournalManager.StatusDict;
+ var r_statuses = r_saveFile.CJournalManager.StatusDict;
+ foreach (var stat in l_statuses)
+ {
+ Witcher3JournalEntryStatus foo = stat.Value;
+ if (foo.Status != r_statuses[stat.Key].Status)
+ {
+ Console.WriteLine($"Status of {stat.Key} is different! In left is {foo.Status} but in right is {r_statuses[stat.Key].Status}");
+ }
+ }
+ });
+
+[Verb("parse", HelpText = "Parse a save file")]
+class ParseOptions
+{
+ [Value(0)]
+ public string verb { get; set; }
+
+ [Value(0)]
+ public string filename { get; set; }
+}
+
+[Verb("compare", HelpText = "Compare two save files")]
+class CompareOptions
+{
+ //[Value(0)]
+ //public string verb { get; set; }
+
+ [Value(1)]
+ public string filename1 { get; set; }
+
+ [Value(2)]
+ public string filename2 { get; set; }
+}
+//if (args.Length > 0)
+//{
+
+//}
diff --git a/SaveTools/Properties/launchSettings.json b/SaveTools/Properties/launchSettings.json
new file mode 100644
index 0000000..2e9e29d
--- /dev/null
+++ b/SaveTools/Properties/launchSettings.json
@@ -0,0 +1,8 @@
+{
+ "profiles": {
+ "SaveTools": {
+ "commandName": "Project",
+ "commandLineArgs": "compare ManualSave_1266d8_7e716c00_5048320.sav ManualSave_11b6cd_7e716c00_505e1b1.sav"
+ }
+ }
+}
\ No newline at end of file
diff --git a/SaveTools/SaveTools.args.json b/SaveTools/SaveTools.args.json
new file mode 100644
index 0000000..80895fe
--- /dev/null
+++ b/SaveTools/SaveTools.args.json
@@ -0,0 +1,26 @@
+{
+ "FileVersion": 2,
+ "Id": "91c48cf9-bd79-456e-90bf-418a753f219b",
+ "Items": [
+ {
+ "Id": "1b0ea989-bf34-480e-9404-eef2f00c835d",
+ "Command": "parse"
+ },
+ {
+ "Id": "eb16f64f-b0b5-4b8c-8bec-d56a55aa381f",
+ "Command": "C:\\Users\\reube\\Desktop\\badsave1.sav"
+ },
+ {
+ "Id": "6d722017-3451-4671-afe5-345b2a55048e",
+ "Command": "compare"
+ },
+ {
+ "Id": "b3999b34-c9be-48b0-b7c7-17b193fe0ea0",
+ "Command": "ManualSave_1266d8_7e716c00_5048320.sav"
+ },
+ {
+ "Id": "a49b90a4-6fe1-46b4-b8a5-89f5b06ea9e5",
+ "Command": "ManualSave_11b6cd_7e716c00_505e1b1.sav"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/SaveTools/SaveTools.csproj b/SaveTools/SaveTools.csproj
new file mode 100644
index 0000000..c463100
--- /dev/null
+++ b/SaveTools/SaveTools.csproj
@@ -0,0 +1,18 @@
+
+
+
+ Exe
+ net6.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Witcher3MapViewer.Core/BaseViewModel.cs b/Witcher3MapViewer.Core/BaseViewModel.cs
new file mode 100644
index 0000000..a260bab
--- /dev/null
+++ b/Witcher3MapViewer.Core/BaseViewModel.cs
@@ -0,0 +1,15 @@
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+
+namespace Witcher3MapViewer.Core
+{
+ public class BaseViewModel : INotifyPropertyChanged
+ {
+ public event PropertyChangedEventHandler? PropertyChanged;
+
+ protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+ }
+}
\ No newline at end of file
diff --git a/Witcher3MapViewer.Core/DAO/ApplicationSettingRootDAO.cs b/Witcher3MapViewer.Core/DAO/ApplicationSettingRootDAO.cs
new file mode 100644
index 0000000..0a9a838
--- /dev/null
+++ b/Witcher3MapViewer.Core/DAO/ApplicationSettingRootDAO.cs
@@ -0,0 +1,77 @@
+using System.Xml.Serialization;
+
+namespace Witcher3MapViewer.Core.DAO
+{
+ [XmlRoot("settings")]
+ public class ApplicationSettingRootDAO
+ {
+ [XmlElement("worldsettings")]
+ public WorldSettingsDAO? worldsettings { get; set; }
+
+ [XmlElement("iconsettings")]
+ public IconSettingsDAO? iconsettings { get; set; }
+ }
+
+ public class WorldSettingsDAO
+ {
+ [XmlElement("worldsetting")]
+ public List? Worlds { get; set; }
+ }
+
+ public class IconSettingsDAO
+ {
+ [XmlElement("icon")]
+ public List? Icons { get; set; }
+
+ [XmlElement("largeiconpath")]
+ public string? LargeIconPath { get; set; }
+
+ [XmlElement("smalliconpath")]
+ public string? SmallIconPath { get; set; }
+ }
+
+ public class IconSettingDAO
+ {
+ [XmlElement("image")]
+ public string ImageName { get; set; } = default!;
+
+ [XmlElement("internalname")]
+ public string InternalName { get; set; } = default!;
+
+ [XmlElement("alias")]
+ public List? Aliases { get; set; }
+
+ [XmlElement("groupname")]
+ public string Groupname { get; set; } = default!;
+ }
+
+ public class WorldSettingDAO
+ {
+ [XmlAttribute("name")]
+ public string Name { get; set; } = default!;
+
+ [XmlAttribute("shortname")]
+ public string ShortName { get; set; } = default!;
+
+ [XmlElement("alias")]
+ public string? AliasShortName { get; set; }
+
+ [XmlElement("conversion")]
+ public ConversionSettingDAO conversionsetting { get; set; } = default!;
+
+ [XmlElement("tilesource")]
+ public string filename { get; set; } = default!;
+ }
+
+ public class ConversionSettingDAO
+ {
+ [XmlAttribute("slope")]
+ public double Slope { get; set; }
+
+ [XmlAttribute("xi")]
+ public double xintercept { get; set; }
+
+ [XmlAttribute("yi")]
+ public double yintercept { get; set; }
+ }
+}
diff --git a/Witcher3MapViewer.Core/DAO/GwentCardDAO.cs b/Witcher3MapViewer.Core/DAO/GwentCardDAO.cs
new file mode 100644
index 0000000..3bdf3e3
--- /dev/null
+++ b/Witcher3MapViewer.Core/DAO/GwentCardDAO.cs
@@ -0,0 +1,33 @@
+using System.Xml.Serialization;
+using Witcher3MapViewer.Core.Readers;
+
+namespace Witcher3MapViewer.Core.DAO
+{
+ [XmlRoot("gwentcards")]
+ public class GwentCardSets
+ {
+ [XmlElement("set")]
+ public List Sets { get; set; }
+ }
+
+ public class GwentSet
+ {
+ [XmlAttribute("name")]
+ public string Name { get; set; }
+
+ [XmlElement("card")]
+ public List Cards { get; set; }
+ }
+
+ internal class GwentCardDAO
+ {
+ [XmlAttribute("id")]
+ public int ID { get; set; }
+
+ [XmlElement("name")]
+ public string Name { get; set; }
+
+ [XmlElement("loc")]
+ public string Location { get; set; }
+ }
+}
diff --git a/Witcher3MapViewer.Core/DAO/MapPinCollectionDAO.cs b/Witcher3MapViewer.Core/DAO/MapPinCollectionDAO.cs
new file mode 100644
index 0000000..6441c20
--- /dev/null
+++ b/Witcher3MapViewer.Core/DAO/MapPinCollectionDAO.cs
@@ -0,0 +1,49 @@
+using System.Xml.Serialization;
+
+namespace Witcher3MapViewer.Core.DAO
+{
+ [XmlRoot("mappins")]
+ public class MapPinCollectionDAO
+ {
+ [XmlElement("world")]
+ public List Worlds { get; set; }
+ }
+
+
+ public class MapPinWorldDAO
+ {
+ [XmlAttribute("code")]
+ public string Code { get; set; }
+
+ [XmlAttribute("name")]
+ public string Name { get; set; }
+
+ [XmlElement("mappin")]
+ public List Pins { get; set; }
+ }
+
+ public class MapPinDAO
+ {
+ [XmlAttribute("type")]
+ public string Type { get; set; }
+
+ [XmlElement("position")]
+ public MapPinPositionDAO Position { get; set; }
+
+ [XmlElement("internalname")]
+ public string InternalName { get; set; }
+
+ [XmlElement("name")]
+ public string Name { get; set; }
+
+ }
+
+ public class MapPinPositionDAO
+ {
+ [XmlAttribute("x")]
+ public int x { get; set; }
+
+ [XmlAttribute("y")]
+ public int y { get; set; }
+ }
+}
diff --git a/Witcher3MapViewer.Core/DAO/QuestListDAO.cs b/Witcher3MapViewer.Core/DAO/QuestListDAO.cs
new file mode 100644
index 0000000..7f50151
--- /dev/null
+++ b/Witcher3MapViewer.Core/DAO/QuestListDAO.cs
@@ -0,0 +1,156 @@
+#nullable disable
+using System.Xml.Serialization;
+
+namespace Witcher3MapViewer.Core.DAO
+{
+
+
+ [XmlRoot("quests")]
+ public class QuestListDAO
+ {
+ [XmlElement("quest")]
+ public List Quests { get; set; }
+
+ [XmlElement("outcome")]
+ public List Outcomes { get; set; }
+ }
+
+ public class QuestDAO
+ {
+ [XmlElement("name")]
+ public string Name { get; set; }
+
+ [XmlAttribute("type")]
+ public string QuestType { get; set; }
+
+ [XmlAttribute("level")]
+ public int LevelRequirement { get; set; }
+
+ [XmlAttribute("world")]
+ public string World { get; set; }
+
+ [XmlAttribute("id")]
+ public int UniqueID { get; set; }
+
+ [XmlElement("discovered")]
+ public List DiscoveredConditions { get; set; }
+
+ [XmlElement("available")]
+ public List AvailableConditions { get; set; }
+
+ [XmlElement("oneofabsolutelyrequired")]
+ public List AbsolutelyRequiredConditions { get; set; }
+
+ [XmlElement("strict")]
+ public List StrictConditions { get; set; }
+
+ [XmlArray("subquests"), XmlArrayItem("quest")]
+ public QuestDAO[] SubquestsAsRead { get; set; }
+
+ [XmlElement("reward")]
+ public QuestRewardDAO Reward { get; set; }
+
+ [XmlElement("GUID")]
+ public QuestGUIDStateDAO GUID { get; set; }
+
+ [XmlArray("objectives"), XmlArrayItem("objective")]
+ public QuestObjectiveDAO[] ObjectivesAsRead { get; set; }
+
+ [XmlElement("discoverprompt")]
+ public QuestDiscoverPromptDAO DiscoverPrompt { get; set; }
+
+ [XmlArray("setdoneautomatic"), XmlArrayItem("GUID")]
+ public List AutomaticConditions { get; set; }
+
+ [XmlArray("hideif"), XmlArrayItem("GUID")]
+ public List HideConditions { get; set; }
+ }
+
+ public class QuestConditionDAO
+ {
+ [XmlElement("GUID")]
+ public List GUIDStates { get; set; }
+ }
+
+ public class QuestGUIDStateDAO
+ {
+ [XmlIgnore]
+ public QuestStatusState ActiveState;
+
+ [XmlAttribute("state")]
+ public string ActiveStateProxy
+ {
+ get { return ActiveState.ToString(); }
+ set
+ {
+ if (string.IsNullOrEmpty(value))
+ {
+ ActiveState = default(QuestStatusState);
+ }
+ else
+ {
+ switch (value)
+ {
+ case "JS_Success":
+ ActiveState = QuestStatusState.Success;
+ break;
+ case "JS_Failed":
+ ActiveState = QuestStatusState.Failed;
+ break;
+ case "JS_Inactive":
+ ActiveState = QuestStatusState.Inactive;
+ break;
+ case "JS_Active":
+ ActiveState = QuestStatusState.Active;
+ break;
+ default:
+ ActiveState = QuestStatusState.NotFound;
+ break;
+ }
+ }
+ }
+ }
+
+ [XmlText]
+ public string Value { get; set; }
+ }
+
+ public class QuestRewardDAO
+ {
+ [XmlAttribute("XP")]
+ public int RewardXP { get; set; }
+ }
+
+ public class QuestDiscoverPositionDAO
+ {
+ [XmlAttribute("X")]
+ public int X { get; set; }
+
+ [XmlAttribute("Y")]
+ public int Y { get; set; }
+
+ [XmlAttribute("world")]
+ public string World { get; set; }
+ }
+
+ public class QuestDiscoverPromptDAO
+ {
+ [XmlElement("XY")]
+ public QuestDiscoverPositionDAO DiscoverPosition { get; set; }
+
+ [XmlElement("info")]
+ public string Info;
+ }
+
+ public class QuestObjectiveDAO
+ {
+ [XmlAttribute("id")]
+ public int UniqueID { get; set; }
+
+ [XmlElement("name")]
+ public string Name { get; set; }
+
+ [XmlElement("GUID")]
+ public QuestGUIDStateDAO GUID { get; set; }
+ }
+}
diff --git a/Witcher3MapViewer.Core/GwentTrackerViewModel.cs b/Witcher3MapViewer.Core/GwentTrackerViewModel.cs
new file mode 100644
index 0000000..1afbc7a
--- /dev/null
+++ b/Witcher3MapViewer.Core/GwentTrackerViewModel.cs
@@ -0,0 +1,82 @@
+using System.Collections.ObjectModel;
+
+namespace Witcher3MapViewer.Core
+{
+ public class GwentTrackerViewModel : BaseViewModel
+ {
+ private ObservableCollection _cards;
+
+ public ObservableCollection Cards
+ {
+ get { return _cards; }
+ set { _cards = value; }
+ }
+
+ public string CollectEmAllStatus
+ {
+ get
+ {
+ int numOwned = _cards.Select(x => x.IsChecked ? 1 : 0).Sum();
+ return $"Collect 'em all: {numOwned}/{_cards.Count}";
+ }
+ }
+
+
+ public GwentTrackerViewModel(List BaseGameCards,
+ IGwentStatusProvider gwentStatusProvider)
+ {
+ _cards = new ObservableCollection();
+ foreach (var card in BaseGameCards)
+ {
+ GwentCardViewModel item = new GwentCardViewModel(card, gwentStatusProvider);
+ item.StatusChanged += GwentStatusProvider_StatusUpdated;
+ _cards.Add(item);
+ }
+ gwentStatusProvider.StatusUpdated += GwentStatusProvider_StatusUpdated;
+ }
+
+
+ private void GwentStatusProvider_StatusUpdated()
+ {
+ OnPropertyChanged(nameof(CollectEmAllStatus));
+ }
+ }
+
+ public class GwentCardViewModel : BaseViewModel
+ {
+ private readonly GwentCard thecard;
+ private readonly IGwentStatusProvider gwentStatusProvider;
+ public event Action StatusChanged;
+
+ public GwentCardViewModel(GwentCard thecard, IGwentStatusProvider gwentStatusProvider)
+ {
+ this.thecard = thecard;
+ this.gwentStatusProvider = gwentStatusProvider;
+ gwentStatusProvider.StatusUpdated += GwentStatusProvider_StatusUpdated;
+ _isChecked = gwentStatusProvider.GetCount(thecard.cardIndex) > 0;
+ }
+
+ private void GwentStatusProvider_StatusUpdated()
+ {
+ OnPropertyChanged(nameof(IsChecked));
+ }
+
+ private bool _isChecked;
+
+ public bool IsChecked
+ {
+ get { return _isChecked; }
+ set
+ {
+ _isChecked = value;
+ gwentStatusProvider.SetCount(thecard.cardIndex, value == true ? 1 : 0);
+ OnPropertyChanged(nameof(IsChecked));
+ StatusChanged?.Invoke();
+ }
+ }
+
+
+ public string Name => thecard.Name;
+ public string Location => thecard.Location;
+ }
+}
diff --git a/Witcher3MapViewer.Core/IGwentCardProvider.cs b/Witcher3MapViewer.Core/IGwentCardProvider.cs
new file mode 100644
index 0000000..50d44f6
--- /dev/null
+++ b/Witcher3MapViewer.Core/IGwentCardProvider.cs
@@ -0,0 +1,21 @@
+namespace Witcher3MapViewer.Core
+{
+ public interface IGwentCardProvider
+ {
+ List GetGwentCards();
+ }
+
+ public class GwentCard
+ {
+ public int cardIndex;
+ public string Name;
+ public string Location;
+
+ public GwentCard(int cardIndex, string name, string location)
+ {
+ this.cardIndex = cardIndex;
+ Name = name;
+ Location = location;
+ }
+ }
+}
diff --git a/Witcher3MapViewer.Core/IGwentStatusProvider.cs b/Witcher3MapViewer.Core/IGwentStatusProvider.cs
new file mode 100644
index 0000000..7a9abd5
--- /dev/null
+++ b/Witcher3MapViewer.Core/IGwentStatusProvider.cs
@@ -0,0 +1,9 @@
+namespace Witcher3MapViewer.Core
+{
+ public interface IGwentStatusProvider
+ {
+ event Action? StatusUpdated;
+ int GetCount(int id);
+ void SetCount(int id, int count);
+ }
+}
\ No newline at end of file
diff --git a/Witcher3MapViewer.Core/Interfaces/GameStatusProvider.cs b/Witcher3MapViewer.Core/Interfaces/GameStatusProvider.cs
new file mode 100644
index 0000000..33a8d32
--- /dev/null
+++ b/Witcher3MapViewer.Core/Interfaces/GameStatusProvider.cs
@@ -0,0 +1,8 @@
+namespace Witcher3MapViewer.Core
+{
+ public interface GameStatusProvider
+ {
+ QuestStatusState GetQuestStatusState(string guid);
+ void SetQuestStatusState(string guid, QuestStatusState state);
+ }
+}
diff --git a/Witcher3MapViewer.Core/Interfaces/IGwentTrackerWindow.cs b/Witcher3MapViewer.Core/Interfaces/IGwentTrackerWindow.cs
new file mode 100644
index 0000000..1b90251
--- /dev/null
+++ b/Witcher3MapViewer.Core/Interfaces/IGwentTrackerWindow.cs
@@ -0,0 +1,8 @@
+namespace Witcher3MapViewer.Core.Interfaces
+{
+ public interface IGwentTrackerWindow
+ {
+ void LaunchWindow(List BaseGameCards,
+ IGwentStatusProvider gwentStatusProvider);
+ }
+}
diff --git a/Witcher3MapViewer.Core/Interfaces/ILevelProvider.cs b/Witcher3MapViewer.Core/Interfaces/ILevelProvider.cs
new file mode 100644
index 0000000..12f75a4
--- /dev/null
+++ b/Witcher3MapViewer.Core/Interfaces/ILevelProvider.cs
@@ -0,0 +1,9 @@
+namespace Witcher3MapViewer.Core
+{
+ public interface ILevelProvider
+ {
+ event Action LevelChanged;
+ int GetLevel();
+ void SetLevel(int level);
+ }
+}
diff --git a/Witcher3MapViewer.Core/Interfaces/IMainWindow.cs b/Witcher3MapViewer.Core/Interfaces/IMainWindow.cs
new file mode 100644
index 0000000..b334763
--- /dev/null
+++ b/Witcher3MapViewer.Core/Interfaces/IMainWindow.cs
@@ -0,0 +1,7 @@
+namespace Witcher3MapViewer.Core.Interfaces
+{
+ public interface IMainWindow
+ {
+ void Close();
+ }
+}
diff --git a/Witcher3MapViewer.Core/Interfaces/IMap.cs b/Witcher3MapViewer.Core/Interfaces/IMap.cs
new file mode 100644
index 0000000..8365a2b
--- /dev/null
+++ b/Witcher3MapViewer.Core/Interfaces/IMap.cs
@@ -0,0 +1,26 @@
+namespace Witcher3MapViewer.Core
+{
+ public class MarkerSpec
+ {
+ public string ImagePath;
+ public List WorldLocations;
+ public string type;
+ public string FullName;
+
+ public MarkerSpec(string imagePath, List worldLocations, string type, string fullName)
+ {
+ ImagePath = imagePath;
+ WorldLocations = worldLocations;
+ this.type = type;
+ FullName = fullName;
+ }
+ }
+
+ public interface IMap
+ {
+ void LoadMap(string path);
+ void LoadMarkers(MarkerSpec markerSpec);
+ void SetLayerVisibility(int layerNumber, bool visible);
+ void CenterMap(double x, double y);
+ }
+}
diff --git a/Witcher3MapViewer.Core/Interfaces/IMapSettingsProvider.cs b/Witcher3MapViewer.Core/Interfaces/IMapSettingsProvider.cs
new file mode 100644
index 0000000..f34a43d
--- /dev/null
+++ b/Witcher3MapViewer.Core/Interfaces/IMapSettingsProvider.cs
@@ -0,0 +1,9 @@
+namespace Witcher3MapViewer.Core
+{
+ public interface IMapSettingsProvider
+ {
+ WorldSetting GetWorldSetting(string worldShortName);
+ IconSettings GetIconSettings();
+ List GetAll();
+ }
+}
diff --git a/Witcher3MapViewer.Core/Interfaces/IMarkerProvider.cs b/Witcher3MapViewer.Core/Interfaces/IMarkerProvider.cs
new file mode 100644
index 0000000..df62107
--- /dev/null
+++ b/Witcher3MapViewer.Core/Interfaces/IMarkerProvider.cs
@@ -0,0 +1,7 @@
+namespace Witcher3MapViewer.Core
+{
+ public interface IMarkerProvider
+ {
+ List GetMarkerSpecs(string worldShortName);
+ }
+}
diff --git a/Witcher3MapViewer.Core/Interfaces/IOptionsDialogWindow.cs b/Witcher3MapViewer.Core/Interfaces/IOptionsDialogWindow.cs
new file mode 100644
index 0000000..48b6faf
--- /dev/null
+++ b/Witcher3MapViewer.Core/Interfaces/IOptionsDialogWindow.cs
@@ -0,0 +1,11 @@
+namespace Witcher3MapViewer.Core.Interfaces
+{
+
+ public interface IOptionsDialogWindow
+ {
+ bool ShowDialog();
+ bool ResetWasRequested();
+ bool ModeChanged();
+ Options GetNewOptions();
+ }
+}
diff --git a/Witcher3MapViewer.Core/Interfaces/IQuestAvailabilityProvider.cs b/Witcher3MapViewer.Core/Interfaces/IQuestAvailabilityProvider.cs
new file mode 100644
index 0000000..0a09466
--- /dev/null
+++ b/Witcher3MapViewer.Core/Interfaces/IQuestAvailabilityProvider.cs
@@ -0,0 +1,12 @@
+namespace Witcher3MapViewer.Core
+{
+ public interface IQuestAvailabilityProvider
+ {
+ bool IsQuestAvailable(Quest q);
+ void SetState(string guid, QuestStatusState? state);
+ QuestStatusState GetState(string guid);
+ void ResetManualStates();
+
+ event Action? AvailabilityChanged;
+ }
+}
diff --git a/Witcher3MapViewer.Core/Interfaces/IQuestListProvider.cs b/Witcher3MapViewer.Core/Interfaces/IQuestListProvider.cs
new file mode 100644
index 0000000..5fcb97f
--- /dev/null
+++ b/Witcher3MapViewer.Core/Interfaces/IQuestListProvider.cs
@@ -0,0 +1,8 @@
+namespace Witcher3MapViewer.Core
+{
+ public interface IQuestListProvider
+ {
+ List GetAllQuests();
+ Advent FindAdvent(string guid);
+ }
+}
diff --git a/Witcher3MapViewer.Core/MainWindowViewModel.cs b/Witcher3MapViewer.Core/MainWindowViewModel.cs
new file mode 100644
index 0000000..db3982f
--- /dev/null
+++ b/Witcher3MapViewer.Core/MainWindowViewModel.cs
@@ -0,0 +1,209 @@
+using Prism.Commands;
+using System.Windows.Input;
+using Witcher3MapViewer.Core.Interfaces;
+
+namespace Witcher3MapViewer.Core
+{
+ public class MainWindowViewModel : BaseViewModel
+ {
+ private readonly IMap _map;
+ private readonly IMarkerProvider _markerProvider;
+ private readonly IMapSettingsProvider _mapSettingsProvider;
+ private readonly IQuestListProvider _questListProvider;
+ private readonly IQuestAvailabilityProvider _availabilityProvider;
+ private readonly IGwentCardProvider gwentCardProvider;
+ private readonly ILevelProvider levelProvider;
+ private readonly IGwentStatusProvider gwentStatusProvider;
+ private readonly IGwentTrackerWindow gwentTrackerWindow;
+ private readonly IOptionsDialogWindow optionsDialogWindow;
+ private readonly OptionsStore optionsStore;
+ private readonly IMainWindow mainWindow;
+ private readonly Dictionary TileMapPathMap;
+ Dictionary shortToLongNameMap;
+
+ public MainWindowViewModel(IMap map,
+ IMarkerProvider markerProvider,
+ IMapSettingsProvider mapSettingsProvider,
+ IQuestListProvider questListProvider,
+ IQuestAvailabilityProvider availabilityProvider,
+ IGwentCardProvider gwentCardProvider,
+ ILevelProvider levelProvider,
+ IGwentStatusProvider gwentStatusProvider,
+ IGwentTrackerWindow gwentTrackerWindow,
+ IOptionsDialogWindow optionsDialogWindow,
+ OptionsStore optionsStore,
+ IMainWindow mainWindow
+ )
+ {
+ _map = map;
+ _markerProvider = markerProvider;
+ _mapSettingsProvider = mapSettingsProvider;
+ _questListProvider = questListProvider;
+ _availabilityProvider = availabilityProvider;
+ this.gwentCardProvider = gwentCardProvider;
+ this.levelProvider = levelProvider;
+ this.gwentStatusProvider = gwentStatusProvider;
+ this.gwentTrackerWindow = gwentTrackerWindow;
+ this.optionsDialogWindow = optionsDialogWindow;
+ this.optionsStore = optionsStore;
+ this.mainWindow = mainWindow;
+ List worldSettings = _mapSettingsProvider.GetAll();
+ shortToLongNameMap = worldSettings.ToDictionary(x => x.ShortName, x => x.Name);
+ shortToLongNameMap["VE"] = shortToLongNameMap["NO"];
+ ListOfMaps = worldSettings.Select(x => x.Name).ToList();
+ TileMapPathMap = worldSettings.ToDictionary(x => x.Name, x => x);
+ MarkerToggleViewModel = new MarkerToggleViewModel(_map);
+
+ QuestListViewModel = new QuestListViewModel(questListProvider.GetAllQuests(), availabilityProvider, levelProvider, optionsStore);
+ QuestListViewModel.ItemSelectedChanged += QuestListViewModel_ItemSelectedChanged;
+ QuestListViewModel.SelectBest();
+ gwentStatusProvider.StatusUpdated += GwentStatusProvider_StatusUpdated;
+ }
+
+ public bool ShouldRestart { get; set; } = false;
+ public ICommand IncreaseLevelCommand => new DelegateCommand(IncreaseLevel);
+ private void IncreaseLevel()
+ {
+ levelProvider.SetLevel(levelProvider.GetLevel() + 1);
+ OnPropertyChanged(nameof(PlayerLevel));
+ }
+
+ public bool ShowLevelControls => optionsStore.IsManual;
+
+ public ICommand DecreaseLevelCommand => new DelegateCommand(DecreaseLevel);
+ private void DecreaseLevel()
+ {
+ int level = levelProvider.GetLevel();
+ if (level == 1) { return; }
+ levelProvider.SetLevel(level - 1);
+ OnPropertyChanged(nameof(PlayerLevel));
+ }
+
+ public string PlayerLevel => levelProvider.GetLevel().ToString();
+
+ private void GwentStatusProvider_StatusUpdated()
+ {
+ OnPropertyChanged(nameof(GwentMessage));
+ }
+
+ private void QuestListViewModel_ItemSelectedChanged(QuestViewModel obj)
+ {
+ var longName = shortToLongNameMap[obj._quest.World];
+ if (SelectedMap != longName)
+ {
+ SelectedMap = longName;
+ }
+ var quest = obj._quest;
+ if (quest.DiscoverPrompt == null)
+ {
+ InfoMessage = "Advance main quest";
+ return;
+ }
+ var p = quest.DiscoverPrompt;
+ if (p.Location != null)
+ {
+ var worldpoint = RealToGameSpaceConversion.ToWorldSpace(new Point(p.Location.X, p.Location.Y), TileMapPathMap[SelectedMap]);
+ _map.CenterMap(worldpoint.X, worldpoint.Y);
+ }
+ if (quest.DiscoverPrompt != null)
+ InfoMessage = quest.DiscoverPrompt.Info;
+ else InfoMessage = string.Empty;
+ }
+
+ private string _infoMessage;
+
+ public string InfoMessage
+ {
+ get { return _infoMessage; }
+ set { _infoMessage = value; OnPropertyChanged(nameof(InfoMessage)); }
+ }
+
+ public string GwentMessage
+ {
+ get
+ {
+ int totalCount = 0;
+ int totalOwned = 0;
+ foreach (var g in gwentCardProvider.GetGwentCards())
+ {
+ totalCount++;
+ if (gwentStatusProvider.GetCount(g.cardIndex) > 0) totalOwned++;
+ }
+ return $"Gwent cards {totalOwned}/{totalCount}";
+ }
+ }
+
+ public ICommand LoadInitialMapCommand { get => new DelegateCommand(LoadInitialMap); }
+ public ICommand OpenGwentWindowCommand => new DelegateCommand(LaunchGwentWindow);
+
+ public ICommand OpenOptionsWindowCommand => new DelegateCommand(LaunchOptionsWindow);
+
+ private void LaunchOptionsWindow()
+ {
+ var result = optionsDialogWindow.ShowDialog();
+ if (result == true)
+ {
+ var newOptions = optionsDialogWindow.GetNewOptions();
+ newOptions.Save("options.json");
+ if (optionsDialogWindow.ResetWasRequested())
+ {
+ _availabilityProvider.ResetManualStates();
+ }
+ ShouldRestart = true;
+ mainWindow.Close();
+ }
+
+ }
+
+ private void LaunchGwentWindow()
+ {
+ gwentTrackerWindow.LaunchWindow(gwentCardProvider.GetGwentCards(), gwentStatusProvider);
+ }
+
+ public List ListOfMaps { get; set; }
+
+ public MarkerToggleViewModel MarkerToggleViewModel { get; set; }
+ public QuestListViewModel QuestListViewModel { get; set; }
+
+ private string _selectedMap = "";
+
+ public string SelectedMap
+ {
+ get { return _selectedMap; }
+ set
+ {
+ _selectedMap = value;
+ OnPropertyChanged(nameof(SelectedMap));
+ UpdateMap();
+ }
+ }
+
+ private void UpdateMap()
+ {
+ MarkerToggleViewModel.Clear();
+ WorldSetting worldSetting = TileMapPathMap[SelectedMap];
+ _map.LoadMap(worldSetting.TileSource);
+ MarkerSpec? roadsign = null;
+ List layers = _markerProvider.GetMarkerSpecs(worldSetting.ShortName);
+ int layerNumber = 1;
+ foreach (var layer in layers)
+ {
+ if (layer.type == "RoadSign")
+ roadsign = layer;
+ else
+ {
+ _map.LoadMarkers(layer);
+ MarkerToggleViewModel.AddItemToRoot(layer.FullName, layer.ImagePath, layerNumber);
+ layerNumber++;
+ }
+ }
+ if (roadsign != null) _map.LoadMarkers(roadsign);
+ }
+
+ private void LoadInitialMap()
+ {
+ if (string.IsNullOrEmpty(SelectedMap))
+ SelectedMap = ListOfMaps.First();
+ }
+ }
+}
diff --git a/Witcher3MapViewer.Core/ManualGwentProvider.cs b/Witcher3MapViewer.Core/ManualGwentProvider.cs
new file mode 100644
index 0000000..bcd1982
--- /dev/null
+++ b/Witcher3MapViewer.Core/ManualGwentProvider.cs
@@ -0,0 +1,49 @@
+using System.Text.Json;
+
+namespace Witcher3MapViewer.Core
+{
+ public class ManualGwentProvider : IGwentStatusProvider
+ {
+ public event Action? StatusUpdated;
+ private Dictionary Statuses;
+ private readonly string filename;
+
+ public ManualGwentProvider(string filename)
+ {
+ if (File.Exists(filename))
+ {
+ var file = JsonSerializer.Deserialize(File.ReadAllText(filename));
+ if (file == null) { throw new Exception("Gwent status file broken"); }
+ Statuses = file.Statuses;
+ }
+ else
+ {
+ var file = JsonSerializer.Deserialize(File.ReadAllText("auto_gwent_statuses.json"));
+ if (file == null) { throw new Exception("Could not find auto_gwent_statuses.json"); }
+ Statuses = file.Statuses;
+ File.WriteAllText(filename, JsonSerializer.Serialize(new GwentStatusJsonFile { Statuses = Statuses }));
+ }
+
+ this.filename = filename;
+ }
+
+ public int GetCount(int id)
+ {
+ if (Statuses.ContainsKey(id))
+ return Statuses[id];
+ return 0;
+ }
+
+ public void SetCount(int id, int count)
+ {
+ Statuses[id] = count;
+ string txt = JsonSerializer.Serialize(new GwentStatusJsonFile { Statuses = Statuses });
+ File.WriteAllText(filename, txt);
+ }
+ }
+
+ class GwentStatusJsonFile
+ {
+ public Dictionary Statuses { get; set; }
+ }
+}
diff --git a/Witcher3MapViewer.Core/ManualLevelProvider.cs b/Witcher3MapViewer.Core/ManualLevelProvider.cs
new file mode 100644
index 0000000..6590559
--- /dev/null
+++ b/Witcher3MapViewer.Core/ManualLevelProvider.cs
@@ -0,0 +1,43 @@
+using System.Text.Json;
+
+namespace Witcher3MapViewer.Core
+{
+ public class ManualLevelProvider : ILevelProvider
+ {
+ private readonly string filename;
+ private int Level;
+ public event Action LevelChanged;
+ public ManualLevelProvider(string filename)
+ {
+ this.filename = filename;
+ if (File.Exists(filename))
+ {
+ var file = JsonSerializer.Deserialize(File.ReadAllText(filename));
+ if (file == null) throw new Exception("Bad level file format");
+ Level = file.Level;
+ }
+ else
+ {
+ Level = 1;
+ File.WriteAllText(filename, JsonSerializer.Serialize(new CharacterLevelFile { Level = 1 }));
+ }
+ }
+
+ public int GetLevel()
+ {
+ return Level;
+ }
+
+ public void SetLevel(int level)
+ {
+ Level = level;
+ File.WriteAllText(filename, JsonSerializer.Serialize(new CharacterLevelFile { Level = level }));
+ LevelChanged?.Invoke();
+ }
+ }
+
+ class CharacterLevelFile
+ {
+ public int Level { get; set; }
+ }
+}
diff --git a/Witcher3MapViewer.Core/ManualQuestAvailabilityProvider.cs b/Witcher3MapViewer.Core/ManualQuestAvailabilityProvider.cs
new file mode 100644
index 0000000..36c636d
--- /dev/null
+++ b/Witcher3MapViewer.Core/ManualQuestAvailabilityProvider.cs
@@ -0,0 +1,80 @@
+using System.Text.Json;
+
+namespace Witcher3MapViewer.Core
+{
+ public abstract class ManualQuestAvailabilityProvider : IQuestAvailabilityProvider
+ {
+ protected Dictionary _statuses = new Dictionary();
+
+ public event Action? AvailabilityChanged;
+
+ public QuestStatusState GetState(string guid)
+ {
+ if (!_statuses.ContainsKey(guid)) return QuestStatusState.NotFound;
+ return _statuses[guid];
+ }
+
+ public bool IsQuestAvailable(Quest q)
+ {
+ if (!q.HasAnyConditions) return true;
+ foreach (var item in q.AvailableIfAny.Success)
+ {
+ if (_statuses.ContainsKey(item) && _statuses[item] >= QuestStatusState.Success)
+ return true;
+ }
+ return false;
+ }
+
+ public abstract void ResetManualStates();
+
+ public virtual void SetState(string guid, QuestStatusState? state)
+ {
+ if (state == null)
+ _statuses.Remove(guid);
+ else _statuses[guid] = (QuestStatusState)state;
+ }
+
+ protected void OnAvailabilityChanged()
+ {
+ AvailabilityChanged?.Invoke();
+ }
+ }
+
+ public class JsonManualQuestAvailabilityProvider : ManualQuestAvailabilityProvider
+ {
+ private readonly string filepath;
+
+ public JsonManualQuestAvailabilityProvider(string filepath)
+ {
+ this.filepath = filepath;
+ if (File.Exists(filepath))
+ {
+ var jsonString = File.ReadAllText(filepath);
+ var read = JsonSerializer.Deserialize>(jsonString);
+ if (read != null)
+ {
+ _statuses = read;
+ }
+ }
+ }
+
+ public override void ResetManualStates()
+ {
+ _statuses.Clear();
+ SaveFile();
+ OnAvailabilityChanged();
+ }
+
+ public override void SetState(string guid, QuestStatusState? state)
+ {
+ base.SetState(guid, state);
+ SaveFile();
+ }
+
+ private void SaveFile()
+ {
+ var write = JsonSerializer.Serialize(_statuses);
+ File.WriteAllText(filepath, write);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Witcher3MapViewer.Core/MarkerToggleItemViewModel.cs b/Witcher3MapViewer.Core/MarkerToggleItemViewModel.cs
new file mode 100644
index 0000000..b275b08
--- /dev/null
+++ b/Witcher3MapViewer.Core/MarkerToggleItemViewModel.cs
@@ -0,0 +1,75 @@
+using System.Collections.ObjectModel;
+using Witcher3MapViewer.Core;
+
+namespace Witcher3MapViewer
+{
+ public class MarkerToggleItemViewModel : BaseViewModel
+ {
+ public string Text { get; set; }
+ public ObservableCollection Children { get; set; }
+
+ private bool? _isChecked = true;
+ private readonly MarkerToggleItemViewModel? _parent;
+ private readonly IMap? map;
+ private readonly int layerNum;
+
+ public bool? IsChecked
+ {
+ get { return _isChecked; }
+ set { SetIsChecked(value, updateChildren: true, updateParent: true); }
+ }
+
+ public string SmallIconPath { get; set; }
+ public MarkerToggleItemViewModel(string text, string smallIconPath, MarkerToggleItemViewModel? parent, IMap? _map, int layerNum)
+ {
+ Text = text;
+ Children = new ObservableCollection();
+ SmallIconPath = smallIconPath;
+ _parent = parent;
+ map = _map;
+ this.layerNum = layerNum;
+ }
+
+ void SetIsChecked(bool? value, bool updateChildren, bool updateParent)
+ {
+ if (value == _isChecked)
+ return;
+
+ _isChecked = value;
+
+ //cascade this to all children
+ if (updateChildren && _isChecked.HasValue)
+ Children.ToList().ForEach(c => c.SetIsChecked(_isChecked, true, false));
+
+ //ask the parent to check its state and all its parents
+ if (updateParent && _parent != null)
+ _parent.VerifyCheckState();
+
+ OnPropertyChanged("IsChecked");
+
+ if (layerNum > 0)
+ map?.SetLayerVisibility(layerNum, (bool)(IsChecked == null ? false : IsChecked));
+ }
+
+ void VerifyCheckState()
+ {
+ bool? state = null;
+ for (int i = 0; i < Children.Count; ++i)
+ {
+ bool? current = Children[i].IsChecked;
+ if (i == 0)
+ {
+ state = current;
+ }
+ else if (state != current)
+ {
+ //if any item doesn't equal the first one, set the status to partial
+ state = null;
+ break;
+ }
+ }
+ // update our state and all its parents
+ SetIsChecked(state, updateChildren: false, updateParent: true);
+ }
+ }
+}
diff --git a/Witcher3MapViewer.Core/MarkerToggleViewModel.cs b/Witcher3MapViewer.Core/MarkerToggleViewModel.cs
new file mode 100644
index 0000000..26a36b5
--- /dev/null
+++ b/Witcher3MapViewer.Core/MarkerToggleViewModel.cs
@@ -0,0 +1,40 @@
+using System.Collections.ObjectModel;
+
+namespace Witcher3MapViewer.Core
+{
+ public class MarkerToggleViewModel : BaseViewModel
+ {
+ string appdir = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
+ MarkerToggleItemViewModel root;
+ private readonly IMap _map;
+
+ public ObservableCollection Markers { get; set; }
+ public MarkerToggleViewModel(IMap map)
+ {
+ root = new MarkerToggleItemViewModel("All Items", Path.Combine(appdir, "SmallMarkerImages", "alchemy.png"), null, _map, 0);
+ Markers = new ObservableCollection
+ {
+ root
+ };
+ root.Children = new ObservableCollection();
+ _map = map;
+ }
+
+ public void AddItemToRoot(string name, string pathToImage, int layerNum)
+ {
+ root.Children.Add(new MarkerToggleItemViewModel
+ (
+ name,
+ Path.Combine(appdir, pathToImage),
+ root,
+ _map,
+ layerNum
+ ));
+ }
+
+ public void Clear()
+ {
+ root.Children.Clear();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Witcher3MapViewer.Core/OptionsStore.cs b/Witcher3MapViewer.Core/OptionsStore.cs
new file mode 100644
index 0000000..51f5b6b
--- /dev/null
+++ b/Witcher3MapViewer.Core/OptionsStore.cs
@@ -0,0 +1,123 @@
+using System.Text.Json;
+
+namespace Witcher3MapViewer.Core
+{
+ public class OptionsStore
+ {
+ private Options options;
+ private readonly IQuestAvailabilityProvider _availabilityProvider;
+
+ public Options Options { get { return options; } set { options = value; } }
+
+ public OptionsStore(Options options, IQuestAvailabilityProvider availabilityProvider)
+ {
+ this.options = options;
+ _availabilityProvider = availabilityProvider;
+ }
+
+ public bool QuestDisplayPolicy(Quest q)
+ {
+ if (options.ShowOnlyAvailable && !_availabilityProvider.IsQuestAvailable(q))
+ {
+ return false;
+ }
+ if (!options.ShowComplete && _availabilityProvider.GetState(q.GUID) == QuestStatusState.Success)
+ {
+ return false;
+ }
+ if (q.HideIfAny != null && q.HideIfAny.AllConditions.Count > 0)
+ {
+ foreach (string c in q.HideIfAny.AllConditions)
+ {
+ if (_availabilityProvider.GetState(c) >= QuestStatusState.Active) return false;
+ }
+ }
+ if (q.QuestType == QuestType.Race && !options.ShowRaces) return false;
+ if (q.QuestType == QuestType.Treasure && !options.ShowTreasure) return false;
+ if (q.QuestType == QuestType.Event && !options.ShowEvents) return false;
+ return true;
+ }
+
+ public bool IsManual => options.TrackingMode == TrackingMode.Manual;
+ }
+
+ public enum TrackingMode
+ {
+ Manual, Automatic
+ }
+
+ public class Options
+ {
+ public bool ShowOnlyAvailable { get; set; }
+ public bool ShowComplete { get; set; }
+ public bool ShowRaces { get; set; }
+ public bool ShowEvents { get; set; }
+ public bool ShowTreasure { get; set; }
+ public TrackingMode TrackingMode { get; set; }
+ public string SaveFilePath { get; set; }
+
+ public Options(bool showOnlyAvailable, bool showComplete, TrackingMode trackingMode, string saveFilePath, bool showRaces, bool showEvents, bool showTreasure)
+ {
+ ShowOnlyAvailable = showOnlyAvailable;
+ ShowComplete = showComplete;
+ TrackingMode = trackingMode;
+ SaveFilePath = saveFilePath;
+ ShowRaces = showRaces;
+ ShowEvents = showEvents;
+ ShowTreasure = showTreasure;
+ }
+
+ public void Save(string path)
+ {
+ var file = new OptionsFile
+ {
+ ShowOnlyAvailable = ShowOnlyAvailable,
+ ShowComplete = ShowComplete,
+ TrackingMode = TrackingMode == TrackingMode.Automatic ? "Automatic" : "Manual",
+ SaveFilePath = SaveFilePath,
+ ShowRaces = ShowRaces,
+ ShowEvents = ShowEvents,
+ ShowTreasure = ShowTreasure
+ };
+ File.WriteAllText(path, JsonSerializer.Serialize(file, new JsonSerializerOptions { WriteIndented = true }));
+ }
+
+ public Options Copy()
+ {
+ return new Options(ShowOnlyAvailable, ShowComplete, TrackingMode, SaveFilePath, ShowRaces, ShowEvents, ShowTreasure);
+ }
+
+ public static Options FromFile(string path)
+ {
+ if (!File.Exists(path))
+ {
+ throw new FileNotFoundException();
+ }
+ var jsonString = File.ReadAllText(path);
+ var file = JsonSerializer.Deserialize(jsonString);
+ if (file == null) throw new Exception("Could not load options from file");
+ TrackingMode trackingMode;
+ if (file.TrackingMode == "Manual") trackingMode = TrackingMode.Manual;
+ else if (file.TrackingMode == "Automatic") trackingMode = TrackingMode.Automatic;
+ else { throw new Exception("Unknown tracking mode in file"); }
+ return new Options(file.ShowOnlyAvailable, file.ShowComplete, trackingMode, file.SaveFilePath, file.ShowRaces, file.ShowEvents, file.ShowTreasure);
+ }
+
+ public static Options Default()
+ {
+ return new Options(true, false, TrackingMode.Manual, "", true, true, true);
+ }
+ }
+
+ public class OptionsFile
+ {
+ public bool ShowOnlyAvailable { get; set; } = true;
+ public bool ShowComplete { get; set; } = false;
+ public bool ShowRaces { get; set; } = true;
+ public bool ShowEvents { get; set; } = true;
+ public bool ShowTreasure { get; set; } = true;
+
+ public string TrackingMode { get; set; } = "Manual";
+ public string SaveFilePath { get; set; } = "";
+ }
+}
diff --git a/Witcher3MapViewer.Core/OptionsWindowViewModel.cs b/Witcher3MapViewer.Core/OptionsWindowViewModel.cs
new file mode 100644
index 0000000..15a4765
--- /dev/null
+++ b/Witcher3MapViewer.Core/OptionsWindowViewModel.cs
@@ -0,0 +1,108 @@
+using Prism.Commands;
+using System.Windows.Input;
+
+namespace Witcher3MapViewer.Core
+{
+ public class OptionsWindowViewModel : BaseViewModel
+ {
+ readonly Options options;
+
+ public OptionsWindowViewModel(Options originalOptions)
+ {
+ options = originalOptions.Copy();
+ if (options.TrackingMode == TrackingMode.Automatic) CheckPathBox();
+ }
+
+ public Options NewOptions => options;
+ public bool ShowOnlyAvailable { get => options.ShowOnlyAvailable; set => options.ShowOnlyAvailable = value; }
+ public bool ShowComplete { get => options.ShowComplete; set => options.ShowComplete = value; }
+ public bool ShowTreasure { get => options.ShowTreasure; set => options.ShowTreasure = value; }
+ public bool ShowEvents { get => options.ShowEvents; set => options.ShowEvents = value; }
+ public bool ShowRaces { get => options.ShowRaces; set => options.ShowRaces = value; }
+
+ public bool IsManualModeChecked
+ {
+ get => options.TrackingMode == TrackingMode.Manual;
+ set
+ {
+ options.TrackingMode = TrackingMode.Manual;
+ OnPropertyChanged(nameof(IsFileModeChecked));
+ }
+ }
+ public bool IsFileModeChecked
+ {
+ get => options.TrackingMode == TrackingMode.Automatic;
+ set
+ {
+ options.TrackingMode = TrackingMode.Automatic;
+ CheckPathBox();
+ OnPropertyChanged(nameof(IsManualModeChecked));
+ OnPropertyChanged(nameof(IsFileModeChecked));
+ }
+ }
+
+ private void CheckPathBox()
+ {
+ if (string.IsNullOrEmpty(SaveFilePath))
+ {
+ ErrorMessage = "Please enter a path";
+ PathOk = false;
+ return;
+ }
+ if (!Directory.Exists(SaveFilePath))
+ {
+ ErrorMessage = "Not a valid directory";
+ PathOk = false;
+ return;
+ }
+ ErrorMessage = "Path looks good";
+ PathOk = true;
+ }
+ private bool pathOk;
+
+ public bool PathOk
+ {
+ get { return pathOk; }
+ set { pathOk = value; OnPropertyChanged(nameof(PathOk)); }
+ }
+
+
+ public ICommand RequestResetCommand => new DelegateCommand(() => RequestedResetOfQuests = true);
+ public bool RequestedResetOfQuests { get; set; } = false;
+
+
+ public string SaveFilePath
+ {
+ get => options.SaveFilePath;
+ set
+ {
+ options.SaveFilePath = value;
+ CheckPathBox();
+ }
+ }
+ public ICommand FindSaveFilePathCommand => new DelegateCommand(FindPath);
+ private string errorMessage = "";
+
+ public string ErrorMessage
+ {
+ get { return errorMessage; }
+ set { errorMessage = value; OnPropertyChanged(nameof(ErrorMessage)); }
+ }
+
+
+ private void FindPath()
+ {
+ string myDocuments = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
+ string ersatzpath = Path.Combine(myDocuments, "The Witcher 3", "gamesaves");
+ if (Directory.Exists(ersatzpath))
+ {
+ SaveFilePath = ersatzpath;
+ OnPropertyChanged(nameof(SaveFilePath));
+ }
+ else
+ {
+ ErrorMessage = "Sorry, couldn't find the save path";
+ }
+ }
+ }
+}
diff --git a/Witcher3MapViewer.Core/Quest.cs b/Witcher3MapViewer.Core/Quest.cs
new file mode 100644
index 0000000..0a26c89
--- /dev/null
+++ b/Witcher3MapViewer.Core/Quest.cs
@@ -0,0 +1,86 @@
+namespace Witcher3MapViewer.Core
+{
+ public enum QuestStatusState
+ {
+ NotFound = 0, Inactive = 1, Active = 2, Failed = 3, Success = 4
+ }
+
+ public enum QuestType
+ {
+ Unknown,
+ Main,
+ DLCMain,
+ SideQuest,
+ Contract,
+ Treasure,
+ Event,
+ Race,
+ EndGame
+ }
+
+ public abstract class Advent
+ {
+ public int UniqueID { get; set; }
+ public string GUID { get; set; } = default!;
+ public string Name { get; set; } = default!;
+ }
+
+ public class Quest : Advent
+ {
+ public QuestType QuestType { get; set; }
+ public int LevelRequirement { get; set; }
+ public string World { get; set; } = default!;
+ public QuestReward? Reward { get; set; }
+ public QuestConditions AvailableIfAny { get; set; } = new QuestConditions();
+ public QuestConditions OneOfAbsolutelyRequired { get; set; } = new QuestConditions();
+ public QuestConditions HideIfAny { get; set; } = new QuestConditions();
+ public QuestConditions RequiredStrictConditions { get; set; } = new QuestConditions();
+ public QuestConditions AutomaticallyDoneIfConditions { get; set; } = new QuestConditions();
+ public QuestDiscoverPrompt? DiscoverPrompt { get; set; }
+ public List? Objectives { get; set; }
+ public List Subquests { get; set; } = new List();
+
+ public bool HasAnyConditions => AvailableIfAny.HasAny || HideIfAny.HasAny || RequiredStrictConditions.HasAny;
+
+ }
+
+ public class Outcome : Advent
+ {
+
+ }
+
+ //public class QuestObjective
+ //{
+ // public string Name { get; set; } = default!;
+ // public string GUID { get; set; } = default!;
+
+ //}
+
+ public class QuestDiscoverPrompt
+ {
+ public string Info { get; set; } = default!;
+ public QuestDiscoverLocation? Location { get; set; } = default!;
+ }
+
+ public class QuestDiscoverLocation
+ {
+ public string WorldCode { get; set; } = default!;
+ public int X { get; set; }
+ public int Y { get; set; }
+ }
+
+ public class QuestConditions
+ {
+ public List Success { get; set; } = new List();
+ public List Any { get; set; } = new List();
+ public List Active { get; internal set; } = new List();
+
+ public bool HasAny => Any.Count > 0 || Success.Count > 0 || Active.Count > 0;
+ public List AllConditions => Success.Concat(Any).Concat(Active).ToList();
+ }
+
+ public class QuestReward
+ {
+ public int XP { get; set; }
+ }
+}
diff --git a/Witcher3MapViewer.Core/QuestListViewModel.cs b/Witcher3MapViewer.Core/QuestListViewModel.cs
new file mode 100644
index 0000000..5d018d9
--- /dev/null
+++ b/Witcher3MapViewer.Core/QuestListViewModel.cs
@@ -0,0 +1,197 @@
+using System.Collections.ObjectModel;
+
+namespace Witcher3MapViewer.Core
+{
+ public class QuestListViewModel : BaseViewModel
+ {
+ public event Action? ItemSelectedChanged;
+
+ ObservableCollection _currentQuests;
+ private readonly ILevelProvider _levelProvider;
+ private readonly OptionsStore policyStore;
+
+ public ObservableCollection CurrentQuests { get { return _currentQuests; } }
+
+ public QuestListViewModel(List currentQuests, IQuestAvailabilityProvider questAvailabilityProvider, ILevelProvider levelProvider, OptionsStore policyStore)
+ {
+ _currentQuests = new ObservableCollection(
+ currentQuests.Select(q => new QuestViewModel(q, questAvailabilityProvider, levelProvider, null, policyStore.QuestDisplayPolicy)).OrderBy(x => x._quest.UniqueID)
+ );
+
+ foreach (var qvm in _currentQuests)
+ {
+ qvm.ItemWasChanged += RefreshAndSelectNew;
+ qvm.SelectedWasChanged += ChildSelectedChanged;
+ foreach (var child in qvm.Children)
+ child.ItemWasChanged += RefreshAndSelectNew;
+ }
+ questAvailabilityProvider.AvailabilityChanged += RefreshAndSelectNew;
+ _levelProvider = levelProvider;
+ _levelProvider.LevelChanged += RefreshAndSelectNew;
+ this.policyStore = policyStore;
+ }
+
+ private void ChildSelectedChanged(QuestViewModel obj)
+ {
+ ItemSelectedChanged?.Invoke(obj);
+ }
+
+ private void RefreshAndSelectNew()
+ {
+ RefreshVisible();
+ SelectBest();
+ }
+
+ private void RefreshVisible()
+ {
+ foreach (var qvm in _currentQuests)
+ qvm.RefreshVisibility();
+
+ }
+
+ public void SelectBest()
+ {
+ int level = _levelProvider.GetLevel();
+ QuestViewModel? best = null;
+ foreach (QuestViewModel q in CurrentQuests)
+ {
+ if (q.IsChecked != true)
+ {
+ if (q.Visible == true)
+ {
+ if (q.QuestType != QuestType.Main && q.QuestType != QuestType.EndGame && q.QuestType != QuestType.DLCMain && q.SuggestedLevel <= level + 2)
+ {
+ q.IsSelected = true;
+ return;
+ }
+ else if ((best == null || best.QuestType == QuestType.Main) && q.QuestType == QuestType.DLCMain)
+ {
+ best = q;
+ }
+ else if (best == null && q.QuestType == QuestType.Main)
+ best = q;
+ }
+ }
+ }
+ if (best != null) best.IsSelected = true;
+ }
+ }
+
+ public class QuestViewModel : BaseViewModel//, IComparable
+ {
+ public List Children { get; private set; }
+ public QuestViewModel? _parent;
+ public Quest _quest;
+ private readonly IQuestAvailabilityProvider _questAvailabilityProvider;
+ public event Action? ItemWasChanged;
+ public event Action? SelectedWasChanged;
+
+ private Func _showPolicy;
+
+ public Func ShowPolicy
+ {
+ get { return _showPolicy; }
+ set { _showPolicy = value; }
+ }
+
+ public string Name => _quest.Name;
+ public int SuggestedLevel => _quest.LevelRequirement;
+ public int PlayerLevel => 0;
+ public QuestType QuestType => _quest.QuestType;
+
+ bool? _isSelected = false;
+
+ public bool Visible
+ {
+ get
+ {
+ if (_parent != null)
+ return _showPolicy(_parent._quest);
+ return _showPolicy(_quest);
+ }
+ }
+
+
+ public QuestViewModel(Quest quest, IQuestAvailabilityProvider questAvailabilityProvider, ILevelProvider levelProvider, QuestViewModel? parent, Func? showpolicy = null)
+ {
+ _parent = parent;
+ _quest = quest;
+ _questAvailabilityProvider = questAvailabilityProvider;
+
+ if (showpolicy == null)
+ _showPolicy = q => _questAvailabilityProvider.IsQuestAvailable(q);
+ else _showPolicy = showpolicy;
+
+ Children = new List();
+ if (_quest.Objectives != null)
+ {
+ foreach (Quest o in _quest.Objectives)
+ {
+ Children.Add(new QuestViewModel(o, questAvailabilityProvider, levelProvider, this, showpolicy));
+ }
+ }
+ foreach (Quest sq in _quest.Subquests)
+ {
+ Children.Add(new QuestViewModel(sq, questAvailabilityProvider, levelProvider, this, showpolicy));
+ }
+ }
+
+
+ public bool? IsChecked
+ {
+ get
+ {
+ QuestStatusState questStatusState = _questAvailabilityProvider.GetState(_quest.GUID);
+
+ return questStatusState == QuestStatusState.Success;
+ }
+ set
+ {
+ if (value == true)
+ _questAvailabilityProvider.SetState(_quest.GUID, QuestStatusState.Success);
+ else
+ _questAvailabilityProvider.SetState(_quest.GUID, null);
+ SetIsChecked(value, true, false);
+ ItemWasChanged?.Invoke();
+ }
+ }
+
+ public bool? IsSelected
+ {
+ get { return _isSelected; }
+ set
+ {
+ _isSelected = value;
+ OnPropertyChanged(nameof(IsSelected));
+ if (value == true)
+ SelectedWasChanged?.Invoke(this);
+ }
+ }
+
+ public void RefreshVisibility()
+ {
+ OnPropertyChanged(nameof(Visible));
+ OnPropertyChanged(nameof(IsChecked));
+ foreach (var c in Children)
+ {
+ c.RefreshVisibility();
+ }
+ }
+
+
+
+
+ void SetIsChecked(bool? value, bool updateChildren, bool updateParent)
+ {
+ if (updateChildren && value.HasValue)
+ Children.ForEach(c => c.IsChecked = value);
+
+ //if (updateParent && _parent != null)
+ // _parent.VerifyCheckState();
+
+ OnPropertyChanged(nameof(IsChecked));
+ }
+
+
+ }
+}
diff --git a/Witcher3MapViewer/GwentXMLReader.cs b/Witcher3MapViewer.Core/Readers/GwentXMLReader.cs
similarity index 83%
rename from Witcher3MapViewer/GwentXMLReader.cs
rename to Witcher3MapViewer.Core/Readers/GwentXMLReader.cs
index dac5da3..f0da176 100644
--- a/Witcher3MapViewer/GwentXMLReader.cs
+++ b/Witcher3MapViewer.Core/Readers/GwentXMLReader.cs
@@ -1,12 +1,6 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using System.Xml.Serialization;
-
-namespace Witcher3MapViewer.Readers
+using System.Xml.Serialization;
+
+namespace Witcher3MapViewer.Core.Readers
{
[XmlRoot("gwentcards")]
public class GwentCardSets
@@ -52,7 +46,7 @@ public GwentXMLReader(string filename)
GwentCardSets thesets = (GwentCardSets)serializer.Deserialize(reader);
Sets = thesets.Sets;
}
- }
+ }
}
}
diff --git a/Witcher3MapViewer/MapPin.cs b/Witcher3MapViewer.Core/Readers/MapPin.cs
similarity index 84%
rename from Witcher3MapViewer/MapPin.cs
rename to Witcher3MapViewer.Core/Readers/MapPin.cs
index b462465..b2ad224 100644
--- a/Witcher3MapViewer/MapPin.cs
+++ b/Witcher3MapViewer.Core/Readers/MapPin.cs
@@ -1,11 +1,9 @@
-using System.Collections.Generic;
-using System.Xml.Serialization;
-using System.IO;
+using System.Xml.Serialization;
-namespace Witcher3MapViewer
+namespace Witcher3MapViewer.Core.Readers
{
class MapPinReader
- {
+ {
List Worlds;
Dictionary> WorldData;
@@ -18,11 +16,11 @@ public MapPinReader(string filename, Dictionary TypeLookup)
MapPinCollectionAsRead readitems = (MapPinCollectionAsRead)serializer.Deserialize(reader);
Worlds = readitems.Worlds;
}
- WorldData = new Dictionary>();
- foreach(MapPinWorld w in Worlds)
+ WorldData = new Dictionary>();
+ foreach (MapPinWorld w in Worlds)
{
- Dictionary Collated = new Dictionary();
- foreach(MapPin pin in w.Pins)
+ Dictionary Collated = new Dictionary();
+ foreach (MapPin pin in w.Pins)
{
if (!Collated.ContainsKey(TypeLookup[pin.Type]))
Collated[TypeLookup[pin.Type]] = new MapPinCollection();
@@ -35,9 +33,9 @@ public MapPinReader(string filename, Dictionary TypeLookup)
public Dictionary GetPins(string code)
{
- foreach(MapPinWorld w in WorldData.Keys)
+ foreach (MapPinWorld w in WorldData.Keys)
{
- if(w.Code == code)
+ if (w.Code == code)
{
return WorldData[w];
}
@@ -68,7 +66,7 @@ public class MapPinWorld
public class MapPin
{
[XmlIgnore]
- public Mapsui.Geometries.Point Location { get { return new Mapsui.Geometries.Point(PositionAsRead.x, PositionAsRead.y); }}
+ public Point Location { get { return new Point(PositionAsRead.x, PositionAsRead.y); } }
[XmlAttribute("type")]
public string Type { get; set; }
@@ -124,7 +122,7 @@ public class MapPinType
public string InternalName;
public string IconFile;
public List Aliases;
- public bool Shown = true;
+ public bool Shown = true;
public MapPinType(string name, string internalname)
{
diff --git a/Witcher3MapViewer.Core/RealToGameSpaceConversion.cs b/Witcher3MapViewer.Core/RealToGameSpaceConversion.cs
new file mode 100644
index 0000000..599fb0e
--- /dev/null
+++ b/Witcher3MapViewer.Core/RealToGameSpaceConversion.cs
@@ -0,0 +1,29 @@
+namespace Witcher3MapViewer.Core
+{
+ public class Point
+ {
+ public readonly double X;
+ public readonly double Y;
+
+ public Point(double x, double y)
+ {
+ this.X = x;
+ this.Y = y;
+ }
+ }
+
+ public static class RealToGameSpaceConversion
+ {
+ public static Point ToGameSpace(Point realspace, WorldSetting worldSetting)
+ {
+ return new Point(Math.Round(worldSetting.Slope * realspace.X + worldSetting.XIntercept),
+ Math.Round(worldSetting.Slope * realspace.Y + worldSetting.YIntercept));
+ }
+
+ public static Point ToWorldSpace(Point gamespace, WorldSetting worldSetting)
+ {
+ return new Point((gamespace.X - worldSetting.XIntercept) / worldSetting.Slope,
+ (gamespace.Y - worldSetting.YIntercept) / worldSetting.Slope);
+ }
+ }
+}
diff --git a/Witcher3MapViewer.Core/SaveFileAvailabilityProvider.cs b/Witcher3MapViewer.Core/SaveFileAvailabilityProvider.cs
new file mode 100644
index 0000000..ec78415
--- /dev/null
+++ b/Witcher3MapViewer.Core/SaveFileAvailabilityProvider.cs
@@ -0,0 +1,137 @@
+using SaveFile;
+
+namespace Witcher3MapViewer.Core
+{
+ public class SaveFileAvailabilityProvider : IQuestAvailabilityProvider
+ {
+ FileSystemWatcher _fileSystemWatcher;
+ private readonly string _saveFileDirectory;
+ private readonly JsonManualQuestAvailabilityProvider _localProgress;
+ private readonly string _tempfolder;
+ private Witcher3SaveFile _saveFile;
+
+ public event Action? AvailabilityChanged;
+
+ public int PlayerLevel => _saveFile.CharacterLevel;
+
+ public SaveFileAvailabilityProvider(string saveFileDirectory, string localProgressFilename, string tempfolder)
+ {
+ _saveFileDirectory = saveFileDirectory;
+ _localProgress = new JsonManualQuestAvailabilityProvider(localProgressFilename);
+ _tempfolder = tempfolder;
+ _saveFile = LoadNewestFile();
+ _fileSystemWatcher = new FileSystemWatcher(_saveFileDirectory);
+ SetUpFilewatcher(_fileSystemWatcher);
+ }
+
+ private Witcher3SaveFile LoadNewestFile()
+ {
+ DirectoryInfo directory = new DirectoryInfo(_saveFileDirectory);
+ FileInfo myFile = (from f in directory.GetFiles("*.sav")
+ orderby f.LastWriteTime descending
+ select f).First();
+ bool success = CopyWhenAvailable(myFile.FullName);
+ if (!success) { throw new Exception("File is locked even after 5 seconds"); }
+ return new Witcher3SaveFile(Path.Combine(_tempfolder, myFile.Name), Witcher3ReadLevel.Quick);
+ }
+
+ public QuestStatusState GetState(string guid)
+ {
+ var localState = _localProgress.GetState(guid);
+ if (localState != QuestStatusState.NotFound) return localState;
+ if (!_saveFile.CJournalManager.StatusDict.ContainsKey(guid))
+ {
+ return QuestStatusState.NotFound;
+ }
+ Witcher3JournalEntryStatus status = _saveFile.CJournalManager.StatusDict[guid];
+ return (QuestStatusState)status.Status;
+ }
+
+ public bool IsQuestAvailable(Quest q)
+ {
+ if (!q.HasAnyConditions) return true;
+ if (q.OneOfAbsolutelyRequired.HasAny && !IsAnyAbsolutelyRequiredMet(q))
+ return false;
+ foreach (var item in q.AvailableIfAny.Success)
+ {
+ if (GetState(item) == QuestStatusState.Success)
+ return true;
+ }
+ foreach (var item in q.AvailableIfAny.Active)
+ {
+ if (GetState(item) >= QuestStatusState.Active)
+ return true;
+ }
+ return false;
+ }
+
+ private bool IsAnyAbsolutelyRequiredMet(Quest q)
+ {
+ foreach (var item in q.OneOfAbsolutelyRequired.Success)
+ {
+ if (GetState(item) == QuestStatusState.Success)
+ return true;
+ }
+ return false;
+ }
+
+ public void SetState(string guid, QuestStatusState? state)
+ {
+ _localProgress.SetState(guid, state);
+ }
+
+ private void SetUpFilewatcher(FileSystemWatcher watcher)
+ {
+ watcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName;
+ watcher.Filter = "*.sav";
+ watcher.Changed += _fileSystemWatcher_Changed;
+ watcher.Created += _fileSystemWatcher_Changed;
+ watcher.Renamed += _fileSystemWatcher_Changed;
+ watcher.EnableRaisingEvents = true;
+ }
+
+ private void _fileSystemWatcher_Changed(object sender, FileSystemEventArgs e)
+ {
+ Thread.Sleep(2000);
+ _saveFile = LoadNewestFile();
+ AvailabilityChanged?.Invoke();
+ }
+
+ private bool CopyWhenAvailable(string fullPath)
+ {
+ int numTries = 0;
+ while (true)
+ {
+ numTries++;
+ try
+ {
+ File.Copy(fullPath, Path.Combine(_tempfolder, Path.GetFileName(fullPath)), true);
+ return true;
+ }
+ catch (System.Reflection.AmbiguousMatchException) //this is just so we don't assign the exception to a variable
+ {
+ //Failed to get access
+ if (numTries > 10)
+ {
+ //give up
+ return false;
+ }
+ // Wait for the lock to be released
+ Thread.Sleep(500);
+ }
+ catch (Exception) { return false; }
+ }
+ }
+
+ public Dictionary GetGwent()
+ {
+ return _saveFile.GwentManager.Counts;
+ }
+
+ public void ResetManualStates()
+ {
+ _localProgress.ResetManualStates();
+ AvailabilityChanged?.Invoke();
+ }
+ }
+}
diff --git a/Witcher3MapViewer.Core/SaveFileGwentStatusProvider.cs b/Witcher3MapViewer.Core/SaveFileGwentStatusProvider.cs
new file mode 100644
index 0000000..18e2a08
--- /dev/null
+++ b/Witcher3MapViewer.Core/SaveFileGwentStatusProvider.cs
@@ -0,0 +1,35 @@
+namespace Witcher3MapViewer.Core
+{
+ public class SaveFileGwentStatusProvider : IGwentStatusProvider
+ {
+ private readonly SaveFileAvailabilityProvider saveFile;
+
+ public SaveFileGwentStatusProvider(SaveFileAvailabilityProvider saveFile)
+ {
+ this.saveFile = saveFile;
+ this.saveFile.AvailabilityChanged += SaveFile_AvailabilityChanged;
+ }
+
+ private void SaveFile_AvailabilityChanged()
+ {
+ StatusUpdated?.Invoke();
+ }
+
+ public event Action? StatusUpdated;
+
+ public int GetCount(int id)
+ {
+ Dictionary counts = saveFile.GetGwent();
+ if (counts.ContainsKey(id))
+ {
+ return counts[id];
+ }
+ return 0;
+ }
+
+ public void SetCount(int id, int count)
+ {
+
+ }
+ }
+}
diff --git a/Witcher3MapViewer.Core/SaveFileLevelProvider.cs b/Witcher3MapViewer.Core/SaveFileLevelProvider.cs
new file mode 100644
index 0000000..1c94baf
--- /dev/null
+++ b/Witcher3MapViewer.Core/SaveFileLevelProvider.cs
@@ -0,0 +1,29 @@
+namespace Witcher3MapViewer.Core
+{
+ public class SaveFileLevelProvider : ILevelProvider
+ {
+ SaveFileAvailabilityProvider _save;
+
+ public SaveFileLevelProvider(SaveFileAvailabilityProvider save)
+ {
+ _save = save;
+ _save.AvailabilityChanged += _save_AvailabilityChanged;
+ }
+
+ private void _save_AvailabilityChanged()
+ {
+ LevelChanged?.Invoke();
+ }
+
+ public event Action LevelChanged;
+
+ public int GetLevel()
+ {
+ return _save.PlayerLevel;
+ }
+
+ public void SetLevel(int level)
+ {
+ }
+ }
+}
diff --git a/Witcher3MapViewer.Core/Witcher3MapViewer.Core.csproj b/Witcher3MapViewer.Core/Witcher3MapViewer.Core.csproj
new file mode 100644
index 0000000..b67c5f4
--- /dev/null
+++ b/Witcher3MapViewer.Core/Witcher3MapViewer.Core.csproj
@@ -0,0 +1,17 @@
+
+
+
+ net6.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Witcher3MapViewer.Core/WorldSetting.cs b/Witcher3MapViewer.Core/WorldSetting.cs
new file mode 100644
index 0000000..acec311
--- /dev/null
+++ b/Witcher3MapViewer.Core/WorldSetting.cs
@@ -0,0 +1,29 @@
+namespace Witcher3MapViewer.Core
+{
+ public class WorldSetting
+ {
+ public string Name { get; set; } = default!;
+ public string ShortName { get; set; } = default!;
+ public double Slope { get; set; }
+ public double XIntercept { get; set; }
+ public double YIntercept { get; set; }
+ public string TileSource { get; set; } = default!;
+ }
+
+ public class IconSettings
+ {
+ public string LargeIconPath { get; set; } = default!;
+ public string SmallIconPath { get; set; } = default!;
+ public List IconInfos { get; set; } = default!;
+
+ }
+
+ public class IconInfo
+ {
+ public string Image { get; set; } = default!;
+ public string InternalName { get; set; } = default!;
+ public string GroupName { get; set; } = default!;
+ public List Aliases { get; set; } = default!;
+
+ }
+}
\ No newline at end of file
diff --git a/Witcher3MapViewer.Core/XMLGwentCardProvider.cs b/Witcher3MapViewer.Core/XMLGwentCardProvider.cs
new file mode 100644
index 0000000..a788e1f
--- /dev/null
+++ b/Witcher3MapViewer.Core/XMLGwentCardProvider.cs
@@ -0,0 +1,33 @@
+using System.Xml.Serialization;
+using Witcher3MapViewer.Core.DAO;
+
+namespace Witcher3MapViewer.Core
+{
+ public class XMLGwentCardProvider : IGwentCardProvider
+ {
+ List _gwentCards;
+
+ public XMLGwentCardProvider(Stream s)
+ {
+ XmlSerializer serializer = new XmlSerializer(typeof(GwentCardSets));
+ GwentCardSets? readitems = (GwentCardSets?)serializer.Deserialize(s);
+ if (readitems == null) throw new Exception();
+ _gwentCards = new List();
+ foreach (var item in readitems.Sets[0].Cards)
+ {
+ _gwentCards.Add(new GwentCard(item.ID, item.Name, item.Location));
+ }
+ }
+
+ public List GetGwentCards()
+ {
+ return _gwentCards;
+ }
+
+ public static XMLGwentCardProvider FromFile(string filepath)
+ {
+ using FileStream sr = new FileStream(filepath, FileMode.Open);
+ return new XMLGwentCardProvider(sr);
+ }
+ }
+}
diff --git a/Witcher3MapViewer.Core/XMLMapSettingsProvider.cs b/Witcher3MapViewer.Core/XMLMapSettingsProvider.cs
new file mode 100644
index 0000000..49dee8a
--- /dev/null
+++ b/Witcher3MapViewer.Core/XMLMapSettingsProvider.cs
@@ -0,0 +1,80 @@
+using System.Xml.Serialization;
+using Witcher3MapViewer.Core.DAO;
+
+namespace Witcher3MapViewer.Core
+{
+ public class XMLMapSettingsProvider : IMapSettingsProvider
+ {
+ Dictionary? WorldSettings;
+ private IconSettings? _iconSettings;
+ private List _allSettings;
+
+ public XMLMapSettingsProvider(Stream s)
+ {
+ XmlSerializer serializer = new XmlSerializer(typeof(ApplicationSettingRootDAO));
+ ApplicationSettingRootDAO? readitems = (ApplicationSettingRootDAO?)serializer.Deserialize(s);
+ _allSettings = new List();
+ if (readitems == null) throw new Exception();
+ if (readitems.worldsettings != null && readitems.worldsettings.Worlds != null)
+ {
+ WorldSettings = new Dictionary();
+ foreach (WorldSettingDAO? item in readitems.worldsettings.Worlds)
+ {
+ WorldSetting world = new WorldSetting
+ {
+ Name = item.Name,
+ ShortName = item.ShortName,
+ Slope = item.conversionsetting.Slope,
+ XIntercept = item.conversionsetting.xintercept,
+ YIntercept = item.conversionsetting.yintercept,
+ TileSource = item.filename
+ };
+ WorldSettings[item.ShortName] = world;
+ _allSettings.Add(world);
+ }
+ }
+ if (readitems.iconsettings != null)
+ {
+ _iconSettings = new IconSettings
+ {
+ SmallIconPath = readitems.iconsettings.SmallIconPath ?? "",
+ LargeIconPath = readitems.iconsettings.LargeIconPath ?? "",
+ IconInfos = readitems.iconsettings?.Icons?.Select(x => new IconInfo
+ {
+ GroupName = x.Groupname,
+ Image = x.ImageName,
+ InternalName = x.InternalName,
+ Aliases = x.Aliases ?? new List()
+ }).ToList() ?? new List()
+ };
+ }
+
+
+ }
+
+ public IconSettings GetIconSettings()
+ {
+ if (_iconSettings == null) throw new Exception();
+ return _iconSettings;
+ }
+
+ public WorldSetting GetWorldSetting(string worldShortName)
+ {
+ if (WorldSettings == null) throw new Exception();
+ return WorldSettings[worldShortName];
+ }
+
+ public static XMLMapSettingsProvider FromFile(string filepath)
+ {
+ using FileStream sr = new FileStream(filepath, FileMode.Open);
+ return new XMLMapSettingsProvider(sr);
+ }
+
+ public List GetAll()
+ {
+ return _allSettings;
+ }
+ }
+
+
+}
diff --git a/Witcher3MapViewer.Core/XMLMarkerProvider.cs b/Witcher3MapViewer.Core/XMLMarkerProvider.cs
new file mode 100644
index 0000000..516c84e
--- /dev/null
+++ b/Witcher3MapViewer.Core/XMLMarkerProvider.cs
@@ -0,0 +1,92 @@
+using System.Xml.Serialization;
+using Witcher3MapViewer.Core.DAO;
+
+namespace Witcher3MapViewer.Core
+{
+ public class XMLMarkerProvider : IMarkerProvider
+ {
+ private Dictionary> Items;
+ IconSettings iconSettings;
+ Dictionary filenameLookup;
+ Dictionary nameLookup;
+ private Dictionary canonicalNameLookup;
+ private readonly IMapSettingsProvider mapSettingsProvider;
+
+ public XMLMarkerProvider(Stream s, IMapSettingsProvider mapSettingsProvider)
+ {
+ this.mapSettingsProvider = mapSettingsProvider;
+ Items = new Dictionary>();
+ XmlSerializer serializer = new XmlSerializer(typeof(MapPinCollectionDAO));
+ MapPinCollectionDAO? readitems = (MapPinCollectionDAO?)serializer.Deserialize(s);
+ if (readitems == null) throw new Exception();
+ iconSettings = mapSettingsProvider.GetIconSettings();
+ filenameLookup = new Dictionary();
+ nameLookup = new Dictionary();
+ canonicalNameLookup = new Dictionary();
+ foreach (IconInfo? iconInfo in iconSettings.IconInfos)
+ {
+ filenameLookup[iconInfo.InternalName] = iconInfo.Image;
+ nameLookup[iconInfo.InternalName] = iconInfo.GroupName;
+ canonicalNameLookup[iconInfo.InternalName] = iconInfo.InternalName;
+ if (iconInfo.Aliases != null)
+ {
+ foreach (string alias in iconInfo.Aliases)
+ {
+ filenameLookup[alias] = iconInfo.Image;
+ nameLookup[alias] = iconInfo.GroupName;
+ canonicalNameLookup[alias] = iconInfo.InternalName;
+ }
+
+ }
+ }
+ foreach (MapPinWorldDAO worldDAO in readitems.Worlds)
+ {
+ Items[worldDAO.Code] = GroupMarkerTypes(worldDAO, worldDAO.Code);
+ }
+
+ }
+
+ private List GroupMarkerTypes(MapPinWorldDAO worldDAO, string worldShortName)
+ {
+ //type maps to InternalName or Alias
+ ILookup? groups = worldDAO.Pins.ToLookup(x => canonicalNameLookup[x.Type]);
+ return groups.Select(x => new MarkerSpec(
+ FindPathToIcon(x.Key),
+ x.Select(p => ComputeMapPosition(p.Position, worldShortName)).ToList(),
+ x.Key,
+ LookUpGroupName(x.Key)
+ )
+ ).ToList();
+ }
+
+ private string LookUpGroupName(string key)
+ {
+ return nameLookup[key];
+ }
+
+ private string FindPathToIcon(string key)
+ {
+ string iconFilename = filenameLookup[key];
+ return Path.Combine(iconSettings.LargeIconPath, iconFilename);
+ }
+
+ private Point ComputeMapPosition(MapPinPositionDAO worldPosition, string worldShortName)
+ {
+ WorldSetting worldSetting = mapSettingsProvider.GetWorldSetting(worldShortName);
+ return RealToGameSpaceConversion.ToWorldSpace(new Point(worldPosition.x, worldPosition.y), worldSetting);
+ }
+
+ public List GetMarkerSpecs(string worldName)
+ {
+ return Items[worldName];
+ }
+
+ public static XMLMarkerProvider FromFile(string filepath, IMapSettingsProvider mapSettingsProvider)
+ {
+ using FileStream sr = new FileStream(filepath, FileMode.Open);
+ return new XMLMarkerProvider(sr, mapSettingsProvider);
+ }
+ }
+
+
+}
diff --git a/Witcher3MapViewer.Core/XMLQuestListProvider.cs b/Witcher3MapViewer.Core/XMLQuestListProvider.cs
new file mode 100644
index 0000000..d080319
--- /dev/null
+++ b/Witcher3MapViewer.Core/XMLQuestListProvider.cs
@@ -0,0 +1,235 @@
+using System.Xml.Serialization;
+using Witcher3MapViewer.Core.DAO;
+
+namespace Witcher3MapViewer.Core
+{
+ public class XMLQuestListProvider : IQuestListProvider
+ {
+ private readonly List quests;
+ private Dictionary Index;
+
+ public List GetAllQuests()
+ {
+ return quests;
+ }
+
+ public XMLQuestListProvider(Stream s)
+ {
+ XmlSerializer serializer = new XmlSerializer(typeof(QuestListDAO));
+ QuestListDAO? readitems = (QuestListDAO?)serializer.Deserialize(s);
+ if (readitems == null) throw new Exception();
+ Index = new Dictionary();
+
+ quests = new List();
+
+ if (readitems.Quests != null)
+ {
+ foreach (QuestDAO? item in readitems.Quests)
+ {
+ Quest quest = ExtractQuest(item);
+
+ quests.Add(quest);
+ Index[quest.GUID] = quest;
+ }
+ }
+ if (readitems.Outcomes != null)
+ {
+ foreach (QuestDAO? item in readitems.Outcomes)
+ {
+ Outcome outcome = new Outcome
+ {
+ GUID = item.GUID.Value,
+ UniqueID = item.UniqueID,
+ Name = item.Name
+ };
+ Index[outcome.GUID] = outcome;
+
+ }
+ }
+ }
+
+
+
+ private Quest ExtractQuest(QuestDAO item)
+ {
+ Quest quest = new Quest
+ {
+ UniqueID = item.UniqueID,
+ QuestType = GetQuestType(item.QuestType),
+ LevelRequirement = item.LevelRequirement,
+ World = item.World,
+ Name = item.Name,
+ GUID = item.GUID?.Value ?? "",
+
+ };
+ if (item.Reward != null)
+ {
+ quest.Reward = new QuestReward { XP = item.Reward.RewardXP };
+ }
+ if (item.AvailableConditions != null)
+ {
+ foreach (var condition in item.AvailableConditions)
+ {
+ foreach (var reference in condition.GUIDStates)
+ {
+ if (reference.ActiveState == QuestStatusState.Success)
+ {
+ quest.AvailableIfAny.Success.Add(reference.Value);
+ }
+ else if (reference.ActiveState == QuestStatusState.NotFound)
+ {
+ quest.AvailableIfAny.Any.Add(reference.Value);
+ }
+ else if (reference.ActiveState == QuestStatusState.Active)
+ {
+ quest.AvailableIfAny.Active.Add(reference.Value);
+ }
+ else
+ {
+ throw new NotImplementedException();
+ }
+ }
+ }
+ }
+ if (item.AbsolutelyRequiredConditions != null)
+ {
+ foreach (var condition in item.AbsolutelyRequiredConditions)
+ {
+ foreach (var reference in condition.GUIDStates)
+ {
+ if (reference.ActiveState == QuestStatusState.Success)
+ {
+ quest.OneOfAbsolutelyRequired.Success.Add(reference.Value);
+ }
+ else if (reference.ActiveState == QuestStatusState.NotFound)
+ {
+ quest.OneOfAbsolutelyRequired.Any.Add(reference.Value);
+ }
+ else if (reference.ActiveState == QuestStatusState.Active)
+ {
+ quest.OneOfAbsolutelyRequired.Active.Add(reference.Value);
+ }
+ else
+ {
+ throw new NotImplementedException();
+ }
+ }
+ }
+ }
+ if (item.DiscoverPrompt != null)
+ {
+ QuestDiscoverPromptDAO prompt = item.DiscoverPrompt;
+ quest.DiscoverPrompt = new QuestDiscoverPrompt
+ {
+ Info = prompt.Info,
+
+ };
+ if (prompt.DiscoverPosition != null)
+ quest.DiscoverPrompt.Location = new QuestDiscoverLocation
+ {
+ WorldCode = prompt.DiscoverPosition.World,
+ X = prompt.DiscoverPosition.X,
+ Y = prompt.DiscoverPosition.Y
+ };
+ }
+ if (item.ObjectivesAsRead != null)
+ {
+ quest.Objectives = new List();
+ foreach (var objective in item.ObjectivesAsRead)
+ {
+ quest.Objectives.Add(new Quest
+ {
+ Name = objective.Name,
+ GUID = objective.GUID.Value,
+ });
+ }
+ }
+ if (item.SubquestsAsRead != null)
+ {
+ foreach (var subquestDAO in item.SubquestsAsRead)
+ {
+ var subquest = ExtractQuest(subquestDAO);
+ quest.Subquests.Add(subquest);
+ }
+ }
+ if (item.HideConditions != null)
+ {
+ quest.HideIfAny = new QuestConditions();
+ foreach (var c in item.HideConditions)
+ {
+ if (c.ActiveState == QuestStatusState.Active)
+ quest.HideIfAny.Active.Add(c.Value);
+ else if (c.ActiveState == QuestStatusState.Success)
+ quest.HideIfAny.Success.Add(c.Value);
+ else
+ {
+ throw new NotImplementedException();
+ }
+ }
+ }
+ if (item.StrictConditions != null)
+ {
+ foreach (var c in item.StrictConditions)
+ {
+ foreach (var g in c.GUIDStates)
+ {
+ if (g.ActiveState == QuestStatusState.Success)
+ quest.RequiredStrictConditions.Success.Add(g.Value);
+ else
+ {
+ throw new NotImplementedException();
+ }
+ }
+ }
+ }
+ if (item.AutomaticConditions != null)
+ {
+ foreach (var ac in item.AutomaticConditions)
+ {
+ if (ac.ActiveState == QuestStatusState.Success)
+ {
+ quest.AutomaticallyDoneIfConditions.Success.Add(ac.Value);
+ }
+ else throw new NotImplementedException();
+ }
+ }
+ return quest;
+ }
+
+ private QuestType GetQuestType(string typeName)
+ {
+ switch (typeName)
+ {
+ case "main":
+ return QuestType.Main;
+ case "dlcmain":
+ return QuestType.DLCMain;
+ case "side":
+ return QuestType.SideQuest;
+ case "contract":
+ return QuestType.Contract;
+ case "treasure":
+ return QuestType.Treasure;
+ case "event":
+ return QuestType.Event;
+ case "race":
+ return QuestType.Race;
+ case "endgame":
+ return QuestType.EndGame;
+ default:
+ throw new NotImplementedException();
+ }
+ }
+
+ public static XMLQuestListProvider FromFile(string filepath)
+ {
+ using FileStream sr = new FileStream(filepath, FileMode.Open);
+ return new XMLQuestListProvider(sr);
+ }
+
+ public Advent FindAdvent(string guid)
+ {
+ return Index[guid];
+ }
+ }
+}
diff --git a/Witcher3MapViewer.Test/MainWindowTest.cs b/Witcher3MapViewer.Test/MainWindowTest.cs
new file mode 100644
index 0000000..9ddf9b2
--- /dev/null
+++ b/Witcher3MapViewer.Test/MainWindowTest.cs
@@ -0,0 +1,157 @@
+using Moq;
+using Witcher3MapViewer.Core;
+using Witcher3MapViewer.Core.Interfaces;
+
+namespace Witcher3MapViewer.Test
+{
+
+
+ public class MainWindowTests
+ {
+ MarkerSpec RoadSign = new MarkerSpec(
+ @"C:\repos\Witcher3MapViewer\Witcher3MapViewer\MarkerImages\RoadSign.png",
+ new List { new Point(0, 0) },
+ "RoadSign",
+ "Road sign"
+ );
+ MarkerSpec PlaceOfPower = new MarkerSpec(
+ @"C:\repos\Witcher3MapViewer\Witcher3MapViewer\MarkerImages\PlaceOfPower.png",
+ new List { new Point(0, 0) },
+ "PlaceOfPower",
+ "Place of Power"
+ );
+ MarkerSpec Harbor = new MarkerSpec(
+ @"C:\repos\Witcher3MapViewer\Witcher3MapViewer\MarkerImages\Harbor.png",
+ new List { new Point(0, 0) },
+ "Harbor",
+ "Harbor"
+ );
+ MarkerSpec Herbalist = new MarkerSpec(
+ @"C:\repos\Witcher3MapViewer\Witcher3MapViewer\MarkerImages\Herbalist.png",
+ new List { new Point(0, 0) },
+ "Herbalist",
+ "Herbalist"
+ );
+
+ Mock mockMap;
+ Mock mockSettingsProvider;
+ Mock mockMarkerProvider;
+ Mock mockQuestAvailabilityProvider;
+ Mock mockQuestListProvider;
+ MainWindowViewModel vm;
+
+ [SetUp]
+ public void SetUp()
+ {
+ mockMap = new Mock();
+ mockMarkerProvider = new Mock();
+ mockSettingsProvider = new Mock();
+ mockSettingsProvider.Setup(x => x.GetAll()).Returns(() =>
+ new List()
+ {
+ new WorldSetting {Name = "Location 1", ShortName = "loc1", TileSource = "l1.mbtiles"},
+ new WorldSetting {Name = "Location 2", ShortName = "loc2", TileSource = "l2.mbtiles"},
+ });
+ mockMarkerProvider.Setup(x => x.GetMarkerSpecs("loc1")).Returns(() =>
+ new List() { RoadSign, PlaceOfPower });
+ mockMarkerProvider.Setup(x => x.GetMarkerSpecs("loc2")).Returns(() =>
+ new List() { RoadSign });
+ mockQuestAvailabilityProvider = new Mock();
+ mockQuestListProvider = new Mock();
+ mockQuestListProvider.Setup(x => x.GetAllQuests()).Returns(() => new List());
+ vm = new MainWindowViewModel(mockMap.Object, mockMarkerProvider.Object,
+ mockSettingsProvider.Object, mockQuestListProvider.Object, mockQuestAvailabilityProvider.Object, new Mock().Object, new Mock().Object, new Mock().Object, new Mock().Object, new Mock().Object, new OptionsStore(new Options(true, false, TrackingMode.Manual, "", true, true, true), mockQuestAvailabilityProvider.Object), new Mock().Object);
+ }
+
+ [Test]
+ public void WhenMainWindowLoads_LoadsAMap()
+ {
+ vm.LoadInitialMapCommand.Execute(null);
+ mockMap.Verify(m => m.LoadMap(It.IsAny()));
+ }
+
+ [Test]
+ public void UsesSettingsToPopulateListOfMaps()
+ {
+ mockSettingsProvider.Verify(x => x.GetAll());
+ }
+
+ [Test]
+ public void MainWindow_UsesMapSettingsToPopulateListOfMaps()
+ {
+
+ Assert.That(vm.ListOfMaps.Count, Is.EqualTo(2));
+ }
+
+ [Test]
+ public void WhenMainWindowLoads_SelectsAListItem()
+ {
+ vm.LoadInitialMapCommand.Execute(null);
+ Assert.That(vm.SelectedMap, Is.Not.Null);
+ }
+
+ [Test]
+ public void WhenMapSelectionChanges_LoadsCorrespondingMap()
+ {
+ vm.LoadInitialMapCommand.Execute(null);
+ vm.SelectedMap = vm.ListOfMaps[1];
+ mockMap.Verify(m => m.LoadMap("l2.mbtiles"));
+ }
+
+ [Test]
+ public void WhenMapIsLoaded_LoadsMarkers()
+ {
+ vm.LoadInitialMapCommand.Execute(null);
+ mockMap.Verify(m => m.LoadMarkers(It.IsAny()));
+ }
+
+ [Test]
+ public void LoadsMapsFromWorldSettings()
+ {
+ vm.LoadInitialMapCommand.Execute(null);
+ mockMap.Verify(m => m.LoadMap("l1.mbtiles"));
+ }
+
+ [Test]
+ public void GetsMarkersFromSettings()
+ {
+ vm.LoadInitialMapCommand.Execute(null);
+ mockMarkerProvider.Verify(x => x.GetMarkerSpecs("loc1"));
+ }
+
+ [Test]
+ public void LoadsRoadSignLastIfPresent()
+ {
+
+
+ int callOrder = 0;
+ mockMap.Setup(x => x.LoadMarkers(PlaceOfPower)).Callback(() => Assert.That(callOrder++, Is.EqualTo(0)));
+ mockMap.Setup(x => x.LoadMarkers(RoadSign)).Callback(() => Assert.That(callOrder++, Is.EqualTo(1)));
+ vm.LoadInitialMapCommand.Execute(null);
+ }
+
+ [Test]
+ public void AddsMarkerLayersToToggleMenu()
+ {
+ mockMarkerProvider.Setup(x => x.GetMarkerSpecs("loc1")).Returns(() =>
+ new List() { RoadSign, PlaceOfPower, Harbor, Herbalist });
+ vm.LoadInitialMapCommand.Execute(null);
+ Assert.That(vm.MarkerToggleViewModel.Markers[0].Children.Count, Is.EqualTo(3));
+ }
+
+ [Test]
+ public void ClearsToggleMenuOnNewLoad()
+ {
+ vm.LoadInitialMapCommand.Execute(null);
+ vm.SelectedMap = vm.ListOfMaps[1];
+ Assert.That(vm.MarkerToggleViewModel.Markers[0].Children.Count, Is.EqualTo(0));
+ }
+
+ [Test]
+ public void ToggleMenuShowsFullName()
+ {
+ vm.LoadInitialMapCommand.Execute(null);
+ Assert.That(vm.MarkerToggleViewModel.Markers[0].Children[0].Text, Is.EqualTo(PlaceOfPower.FullName));
+ }
+ }
+}
\ No newline at end of file
diff --git a/Witcher3MapViewer.Test/QuestAvailabilityProviderTest.cs b/Witcher3MapViewer.Test/QuestAvailabilityProviderTest.cs
new file mode 100644
index 0000000..5afd979
--- /dev/null
+++ b/Witcher3MapViewer.Test/QuestAvailabilityProviderTest.cs
@@ -0,0 +1,70 @@
+using Witcher3MapViewer.Core;
+
+namespace Witcher3MapViewer.Test
+{
+ class FakeManualQuestAvailabilityProvider : ManualQuestAvailabilityProvider
+ {
+ public override void ResetManualStates()
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ public class QuestAvailabilityProviderTest
+ {
+ ManualQuestAvailabilityProvider provider;
+
+ Quest questWithNoDependency = new Quest
+ {
+ Name = "First Quest",
+ LevelRequirement = 0,
+ QuestType = QuestType.Main,
+ World = "WO",
+ UniqueID = 2,
+ GUID = "0AE82268-4FFF5D8D-4A219CB4-11E480F8"
+ };
+
+ Quest questWithDependency = new Quest
+ {
+ Name = "Second Quest",
+ LevelRequirement = 0,
+ QuestType = QuestType.Main,
+ World = "WO",
+ UniqueID = 3,
+ GUID = "C651CB60-4D784070-4F8AD0B6-36D4289A",
+ };
+
+ [SetUp]
+ public void SetUp()
+ {
+ provider = new FakeManualQuestAvailabilityProvider();
+ questWithDependency.AvailableIfAny.Success.Add("0AE82268-4FFF5D8D-4A219CB4-11E480F8");
+ }
+
+ [Test]
+ public void ReturnsTrueOnAnyQuestWithNoDependencies()
+ {
+ Assert.That(provider.IsQuestAvailable(questWithNoDependency), Is.True);
+ }
+
+ [Test]
+ public void QuestWithUnmetDependenciesReturnsFalse()
+ {
+ Assert.That(provider.IsQuestAvailable(questWithDependency), Is.False);
+ }
+
+ [Test]
+ public void QuestWithMetDependenciesReturnsTrue()
+ {
+ provider.SetState(questWithNoDependency.GUID, QuestStatusState.Success);
+ Assert.That(provider.IsQuestAvailable(questWithDependency), Is.True);
+ }
+
+ [Test]
+ public void WhenStateIsActiveButSuccessNeededReturnsFalse()
+ {
+ provider.SetState(questWithNoDependency.GUID, QuestStatusState.Active);
+ Assert.That(provider.IsQuestAvailable(questWithDependency), Is.False);
+ }
+ }
+}
diff --git a/Witcher3MapViewer.Test/QuestListViewModelTest.cs b/Witcher3MapViewer.Test/QuestListViewModelTest.cs
new file mode 100644
index 0000000..86b419f
--- /dev/null
+++ b/Witcher3MapViewer.Test/QuestListViewModelTest.cs
@@ -0,0 +1,54 @@
+using Moq;
+using Witcher3MapViewer.Core;
+
+namespace Witcher3MapViewer.Test
+{
+ internal class QuestListViewModelTest
+ {
+ Quest makeQuest(QuestType t, int levelRequired)
+ {
+ return new Quest
+ {
+ Name = "First Quest",
+ LevelRequirement = levelRequired,
+ QuestType = t,
+ World = "WO",
+ UniqueID = 2,
+ GUID = Guid.NewGuid().ToString(),
+ };
+ }
+ Options o = new Options(true, false, TrackingMode.Manual, "", true, true, true);
+
+ [Test]
+ public void Test_When_No_Requirement_SelectBest_Sets_SideQuest_as_Best()
+ {
+ List quests = new List { makeQuest(QuestType.Main, 0), makeQuest(QuestType.SideQuest, 0) };
+ IQuestAvailabilityProvider avail = new Mock().Object;
+ QuestListViewModel vm = new QuestListViewModel(quests, avail, new Mock().Object, new OptionsStore(o, avail));
+ vm.SelectBest();
+ Assert.That(vm.CurrentQuests[1].IsSelected, Is.True);
+ }
+
+ [Test]
+ public void Test_When_No_Requirement_SelectBest_Sets_Lowest_Main_as_Best()
+ {
+ List quests = new List { makeQuest(QuestType.Main, 0), makeQuest(QuestType.Main, 1) };
+ IQuestAvailabilityProvider avail = new Mock().Object;
+ QuestListViewModel vm = new QuestListViewModel(quests, avail, new Mock().Object, new OptionsStore(o, avail));
+ vm.SelectBest();
+ Assert.That(vm.CurrentQuests[0].IsSelected, Is.True);
+ }
+
+ [Test]
+ public void Test_When_Below_Sidequest_Requirement_Sets_MainQuest_as_Best()
+ {
+ List quests = new List { makeQuest(QuestType.Main, 1), makeQuest(QuestType.SideQuest, 2) };
+ Mock mock = new Mock();
+ mock.Setup(x => x.GetLevel()).Returns(1);
+ IQuestAvailabilityProvider avail = new Mock().Object;
+ QuestListViewModel vm = new QuestListViewModel(quests, avail, mock.Object, new OptionsStore(o, avail));
+ vm.SelectBest();
+ Assert.That(vm.CurrentQuests[0].IsSelected, Is.True);
+ }
+ }
+}
diff --git a/Witcher3MapViewer.Test/Usings.cs b/Witcher3MapViewer.Test/Usings.cs
new file mode 100644
index 0000000..cefced4
--- /dev/null
+++ b/Witcher3MapViewer.Test/Usings.cs
@@ -0,0 +1 @@
+global using NUnit.Framework;
\ No newline at end of file
diff --git a/Witcher3MapViewer.Test/Witcher3MapViewer.Test.csproj b/Witcher3MapViewer.Test/Witcher3MapViewer.Test.csproj
new file mode 100644
index 0000000..5531c2e
--- /dev/null
+++ b/Witcher3MapViewer.Test/Witcher3MapViewer.Test.csproj
@@ -0,0 +1,24 @@
+
+
+
+ net6.0
+ enable
+ enable
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Witcher3MapViewer.Test/XMLMapSettingsProviderTests.cs b/Witcher3MapViewer.Test/XMLMapSettingsProviderTests.cs
new file mode 100644
index 0000000..03a0631
--- /dev/null
+++ b/Witcher3MapViewer.Test/XMLMapSettingsProviderTests.cs
@@ -0,0 +1,106 @@
+using System.Text;
+using Witcher3MapViewer.Core;
+
+namespace Witcher3MapViewer.Test
+{
+ public class XMLMapSettingsProviderTests
+ {
+ [Test]
+ public void WhenInstantiatedReadsFile()
+ {
+ MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(""));
+ var p = new XMLMapSettingsProvider(ms);
+ Assert.That(ms.Position, Is.EqualTo(ms.Length));
+ }
+
+ [Test]
+ public void WhenHasWorldSettingGetsIt()
+ {
+ string mock = @"
+
+
+
+ Maps\WhiteOrchard.mbtiles
+
+
+ ";
+ MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(mock));
+ var p = new XMLMapSettingsProvider(ms);
+ Assert.DoesNotThrow(() => p.GetWorldSetting("WO"));
+ }
+ [Test]
+ public void WhenHasWorldSettingParsesConversion()
+ {
+ string mock = @"
+
+
+
+ Maps\WhiteOrchard.mbtiles
+
+
+ ";
+ MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(mock));
+ var p = new XMLMapSettingsProvider(ms);
+ WorldSetting ws = p.GetWorldSetting("WO");
+ Assert.That(ws.YIntercept, Is.EqualTo(19.2).Within(0.000001));
+ Assert.That(ws.XIntercept, Is.EqualTo(60).Within(0.0000001));
+ Assert.That(ws.Slope, Is.EqualTo(0.000038).Within(0.0000001));
+ Assert.That(ws.TileSource, Is.EqualTo("Maps\\WhiteOrchard.mbtiles"));
+ }
+
+ [Test]
+ public void WhenHasIconSettingsLoadsSomething()
+ {
+ string mock = @"
+
+ MarkerImages
+ SmallMarkerImages
+
+ ";
+ MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(mock));
+ var p = new XMLMapSettingsProvider(ms);
+ Assert.That(p.GetIconSettings(), Is.Not.Null);
+ }
+
+ [Test]
+ public void WhenHasIconSettingsHasPathsLoadsThem()
+ {
+ string mock = @"
+
+ MarkerImages
+ SmallMarkerImages
+
+ ";
+ MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(mock));
+ var p = new XMLMapSettingsProvider(ms);
+ var result = p.GetIconSettings();
+ Assert.That(result.SmallIconPath, Is.EqualTo("SmallMarkerImages"));
+ Assert.That(result.LargeIconPath, Is.EqualTo("MarkerImages"));
+ }
+
+ [Test]
+ public void WhenHasIconInfosLoadsThem()
+ {
+ string mock = @"
+
+ MarkerImages
+ SmallMarkerImages
+
+ NoticeBoard.png
+ NoticeBoard
+ Notice boards
+
+
+ MonsterNest.png
+ MonsterNest
+ Monster nests
+
+
+ ";
+ MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(mock));
+ var p = new XMLMapSettingsProvider(ms);
+ var result = p.GetIconSettings();
+ Assert.That(result.IconInfos.Count, Is.EqualTo(2));
+ }
+ }
+}
diff --git a/Witcher3MapViewer.Test/XMLMarkerProviderTests.cs b/Witcher3MapViewer.Test/XMLMarkerProviderTests.cs
new file mode 100644
index 0000000..73bb3ed
--- /dev/null
+++ b/Witcher3MapViewer.Test/XMLMarkerProviderTests.cs
@@ -0,0 +1,150 @@
+using Moq;
+using System.Text;
+using Witcher3MapViewer.Core;
+
+namespace Witcher3MapViewer.Test
+{
+ public class XMLMarkerProviderTests
+ {
+ private const string mockFull = @"
+
+
+
+
+ Woesong bridge
+ woesong_bridge
+
+
+
+ pop_quen2_prlg
+ pop_quen2_prlg
+
+
+ ";
+ Mock mockSettingsProvider;
+
+ [SetUp]
+ public void SetUp()
+ {
+ mockSettingsProvider = new Mock();
+ mockSettingsProvider.Setup(x => x.GetIconSettings()).Returns(() => new IconSettings()
+ {
+ LargeIconPath = "MarkerImages",
+ IconInfos = new List() {
+ new IconInfo() { InternalName = "RoadSign", Image = "RoadSign.png" },
+ new IconInfo() { InternalName = "PlaceOfPower", Image = "PlaceOfPower.png" },
+ }
+ });
+ mockSettingsProvider.Setup(x => x.GetWorldSetting("WO")).Returns(() => new WorldSetting()
+ {
+ ShortName = "WO",
+ Slope = 2,
+ XIntercept = 10,
+ YIntercept = -5
+ });
+ }
+
+ [Test]
+ public void LoadsFileWhenInstantiated()
+ {
+ MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(""));
+ var p = new XMLMarkerProvider(ms, mockSettingsProvider.Object);
+ Assert.That(ms.Position, Is.EqualTo(ms.Length));
+ }
+
+ [Test]
+ public void WhenWorldIsPresentThenReturnAMarkerSpec()
+ {
+ string mock = @"
+
+
+
+ ";
+ MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(mock));
+ var p = new XMLMarkerProvider(ms, mockSettingsProvider.Object);
+ Assert.DoesNotThrow(() => p.GetMarkerSpecs("WO"));
+ }
+
+ [Test]
+ public void WhenWorldHasAPinThenItIsInTheSpec()
+ {
+ string mock = @"
+
+
+
+
+ Woesong bridge
+ woesong_bridge
+
+
+ ";
+ MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(mock));
+ var p = new XMLMarkerProvider(ms, mockSettingsProvider.Object);
+ Assert.That(p.GetMarkerSpecs("WO").Count(), Is.EqualTo(1));
+ }
+
+ [Test]
+ public void WhenWorldHasTwoPinsThenTheyIsInTheSpec()
+ {
+ string mock = mockFull;
+ MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(mock));
+ var p = new XMLMarkerProvider(ms, mockSettingsProvider.Object);
+ Assert.That(p.GetMarkerSpecs("WO").Count(), Is.EqualTo(2));
+ }
+
+ [Test]
+ public void CallsGetIconSettings()
+ {
+ MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(mockFull));
+ var p = new XMLMarkerProvider(ms, mockSettingsProvider.Object);
+ mockSettingsProvider.Verify(x => x.GetIconSettings());
+ }
+
+ [Test]
+ public void UsesInternalNameFromMapSettingsToLookupImage()
+ {
+ MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(mockFull));
+ var p = new XMLMarkerProvider(ms, mockSettingsProvider.Object);
+ var result = p.GetMarkerSpecs("WO");
+ Assert.That(result[0].ImagePath, Is.EqualTo("MarkerImages\\RoadSign.png"));
+ }
+
+ [Test]
+ public void WhenMakingSpecConvertsWorldPositionUsingFormula()
+ {
+ MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(mockFull));
+ var p = new XMLMarkerProvider(ms, mockSettingsProvider.Object);
+ var result = p.GetMarkerSpecs("WO");
+ double expectedX = (101.0 - 10.0) / 2.0;
+ double expectedY = (-9.0 + 5.0) / 2.0;
+ Assert.That(result[0].WorldLocations[0].X, Is.EqualTo(expectedX).Within(0.00001));
+ Assert.That(result[0].WorldLocations[0].Y, Is.EqualTo(expectedY).Within(0.00001));
+ }
+
+ [Test]
+ public void WhenPinUsesAliasLooksUpCorrectImage()
+ {
+ string mock = @"
+
+
+
+
+ Woesong bridge
+ woesong_bridge
+
+
+ ";
+ mockSettingsProvider.Setup(x => x.GetIconSettings()).Returns(() => new IconSettings()
+ {
+ LargeIconPath = "MarkerImages",
+ IconInfos = new List() {
+ new IconInfo() { InternalName = "RoadSign", Image = "RoadSign.png", Aliases = new List { "RoadSignAlias" } },
+ }
+ });
+ MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(mock));
+ var p = new XMLMarkerProvider(ms, mockSettingsProvider.Object);
+ var result = p.GetMarkerSpecs("WO");
+ Assert.That(result[0].ImagePath, Is.EqualTo("MarkerImages\\RoadSign.png"));
+ }
+ }
+}
diff --git a/Witcher3MapViewer.Test/XMLQuestListProviderTests.cs b/Witcher3MapViewer.Test/XMLQuestListProviderTests.cs
new file mode 100644
index 0000000..79b364a
--- /dev/null
+++ b/Witcher3MapViewer.Test/XMLQuestListProviderTests.cs
@@ -0,0 +1,208 @@
+using System.Text;
+using Witcher3MapViewer.Core;
+
+namespace Witcher3MapViewer.Test
+{
+ public class XMLQuestListProviderTests
+ {
+ string mockwithOne = @"
+ Kaer Morhen
+ C1AA4441-48FF64E8-972B3BB9-250CC191
+
+ ";
+
+ string mockWithDependency = @"
+
+ Quest 1
+ C1AA4441-48FF64E8-972B3BB9-250CC191
+
+
+
+
+ Quest 2
+
+ C1AA4441-48FF64E8-972B3BB9-250CC191
+
+
+ ! sign over a man talking to a dog
+
+
+ 3B6DBB8A-4E5095A7-8DA31EB3-4F2C4D1F
+
+
+ ";
+
+ string mockWithObjectives = @"
+
+Lilac and Gooseberries
+768A197C-4630DC8A-735A4BB3-C9535B0B
+
+C1AA4441-48FF64E8-972B3BB9-250CC191
+
+
+
+
+Ask travelers about Yennefer
+42CE0F47-454E0055-8AC991AD-A09CAFB3
+
+
+
+
+The Beast of White Orchard
+F6086B74-4E68EDFF-D79CE191-13BA33A7
+
+42CE0F47-454E0055-8AC991AD-A09CAFB3
+
+
+
+Ask the herbalist about buckthorn
+50E659DB-44831AA8-31BA8EA6-8EB62F91
+
+
+
+
+
+The Incident at White Orchard
+03E485C6-4719EFBD-29B7288E-2145E02E
+
+42CE0F47-454E0055-8AC991AD-A09CAFB3
+
+
+
+
+
+";
+
+ string mockWithHide = @"
+Take What You Want
+7258B2FB-4A41EF24-647735A2-6528FC9F
+
+BD40C6D4-4532FB2D-187DEDBF-2C42C6F6
+
+
+3D38A3BB-43644405-D5D87083-7731411C
+
+
+Killed Gaetan in Where the Cat and Wolf Play
+3D38A3BB-43644405-D5D87083-7731411C
+";
+
+ string mockWithStrict = @"
+Following the Thread
+23EBF49C-4CD2BFB5-A2D8FB94-B27040A6
+
+550442BD-43AF7D49-9E0A09B5-54BCCDCC
+
+
+75CE5700-4B1F1BBE-069237AE-6D20CA0F
+
+
+130A322C-479E4E1C-6C0ED1B4-0180136D
+
+
+
+Notice board (Monster in the Bits)
+
+";
+
+ string mockWithAuto = @"
+
+The Beast of Honorton
+BD40C6D4-4532FB2D-187DEDBF-2C42C6F6
+
+3A6B433B-47EA9285-23D96FB9-9A37E56E
+
+
+
+Notice board
+
+
+35CCCB0A-4C518790-D11C34B4-6CDBEA77
+
+
+";
+
+ [Test]
+ public void WhenOnlyOneQuestReadsOne()
+ {
+ MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(mockwithOne));
+ var reader = new XMLQuestListProvider(ms);
+ Assert.That(reader.GetAllQuests().Count, Is.EqualTo(1));
+ }
+
+ [Test]
+ public void WhenHasOneQuestReadsFields()
+ {
+ MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(mockwithOne));
+ var reader = new XMLQuestListProvider(ms);
+ var result = reader.GetAllQuests()[0];
+ Assert.That(result.QuestType, Is.EqualTo(QuestType.Main));
+ Assert.That(result.UniqueID, Is.EqualTo(2));
+ Assert.That(result.LevelRequirement, Is.EqualTo(24));
+ Assert.That(result.World, Is.EqualTo("WO"));
+ Assert.That(result.Name, Is.EqualTo("Kaer Morhen"));
+ Assert.That(result.GUID, Is.EqualTo("C1AA4441-48FF64E8-972B3BB9-250CC191"));
+ Assert.That(result.Reward.XP, Is.EqualTo(250));
+ }
+
+ [Test]
+ public void CanReadSideQuestFields()
+ {
+ MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(mockWithDependency));
+ var reader = new XMLQuestListProvider(ms);
+ var result = reader.GetAllQuests()[1];
+ Assert.That(result.QuestType, Is.EqualTo(QuestType.SideQuest));
+ Assert.That(result.AvailableIfAny.Success[0], Is.EqualTo("C1AA4441-48FF64E8-972B3BB9-250CC191"));
+ Assert.That(result.DiscoverPrompt.Info, Is.EqualTo("! sign over a man talking to a dog"));
+ Assert.That(result.DiscoverPrompt.Location.WorldCode, Is.EqualTo("WO"));
+ Assert.That(result.DiscoverPrompt.Location.X, Is.EqualTo(476));
+ Assert.That(result.DiscoverPrompt.Location.Y, Is.EqualTo(-106));
+ }
+
+ [Test]
+ public void ReadsObjectives()
+ {
+ MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(mockWithObjectives));
+ var reader = new XMLQuestListProvider(ms);
+ var result = reader.GetAllQuests()[0];
+ Assert.That(result.Objectives.Count, Is.EqualTo(1));
+ Assert.That(result.Subquests.Count, Is.EqualTo(2));
+ }
+
+ [Test]
+ public void WhenHasHideConditionsReadsThem()
+ {
+ MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(mockWithHide));
+ var reader = new XMLQuestListProvider(ms);
+ var result = reader.GetAllQuests()[0];
+ Assert.That(result.HideIfAny.Active.Count, Is.EqualTo(1));
+ }
+
+ [Test]
+ public void ReadsStrictConditions()
+ {
+ MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(mockWithStrict));
+ var reader = new XMLQuestListProvider(ms);
+ var result = reader.GetAllQuests()[0];
+ Assert.That(result.RequiredStrictConditions.Success.Count, Is.EqualTo(2));
+ }
+
+ [Test]
+ public void ReadsAutoDone()
+ {
+ MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(mockWithAuto));
+ var reader = new XMLQuestListProvider(ms);
+ var result = reader.GetAllQuests()[0];
+ Assert.That(result.AutomaticallyDoneIfConditions.Success.Count, Is.EqualTo(1));
+ }
+
+ [Test]
+ public void WhenHasOutcomeLinkAddsIt()
+ {
+ MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(mockWithHide));
+ var reader = new XMLQuestListProvider(ms);
+ var result = reader.GetAllQuests()[0];
+ Assert.That(reader.FindAdvent(result.HideIfAny.Active[0]).GUID, Is.EqualTo("3D38A3BB-43644405-D5D87083-7731411C"));
+ }
+ }
+}
diff --git a/Witcher3MapViewer.sln b/Witcher3MapViewer.sln
index 3959b21..9205fca 100644
--- a/Witcher3MapViewer.sln
+++ b/Witcher3MapViewer.sln
@@ -1,64 +1,49 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 15
-VisualStudioVersion = 15.0.27703.2000
+# Visual Studio Version 17
+VisualStudioVersion = 17.2.32630.192
MinimumVisualStudioVersion = 10.0.40219.1
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Witcher3MapViewer", "Witcher3MapViewer\Witcher3MapViewer.csproj", "{7A608786-5C00-40F7-8F84-846D25871EC8}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Witcher3MapViewer.WPF", "Witcher3MapViewer\Witcher3MapViewer.WPF.csproj", "{59C1FA03-95AA-4E49-AE38-0B243EEA5F2C}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GwentDiscovery", "GwentDiscovery\GwentDiscovery.csproj", "{F8E5957C-4B09-4ABF-AA3B-2C7CEE53D26B}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Witcher3MapViewer.Test", "Witcher3MapViewer.Test\Witcher3MapViewer.Test.csproj", "{8D36A4EB-2332-4D63-833D-D116B160475B}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SaveExtractor", "SaveExtractor\SaveExtractor.csproj", "{E91F3CB6-0CE9-4E4C-8C37-58C9A03042CF}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Witcher3MapViewer.Core", "Witcher3MapViewer.Core\Witcher3MapViewer.Core.csproj", "{AD62B725-0126-425F-85BF-56E5587FC3E1}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Witcher3SaveFile", "Witcher3SaveFile\Witcher3SaveFile.csproj", "{14E4EF8F-FB07-433C-9EF2-1E0F381E4AD8}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SaveTools", "SaveTools\SaveTools.csproj", "{91C48CF9-BD79-456E-90BF-418A753F219B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
- Debug|x64 = Debug|x64
- Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
- Release|x64 = Release|x64
- Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {7A608786-5C00-40F7-8F84-846D25871EC8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {7A608786-5C00-40F7-8F84-846D25871EC8}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {7A608786-5C00-40F7-8F84-846D25871EC8}.Debug|x64.ActiveCfg = Debug|x64
- {7A608786-5C00-40F7-8F84-846D25871EC8}.Debug|x64.Build.0 = Debug|x64
- {7A608786-5C00-40F7-8F84-846D25871EC8}.Debug|x86.ActiveCfg = Debug|x86
- {7A608786-5C00-40F7-8F84-846D25871EC8}.Debug|x86.Build.0 = Debug|x86
- {7A608786-5C00-40F7-8F84-846D25871EC8}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {7A608786-5C00-40F7-8F84-846D25871EC8}.Release|Any CPU.Build.0 = Release|Any CPU
- {7A608786-5C00-40F7-8F84-846D25871EC8}.Release|x64.ActiveCfg = Release|x64
- {7A608786-5C00-40F7-8F84-846D25871EC8}.Release|x64.Build.0 = Release|x64
- {7A608786-5C00-40F7-8F84-846D25871EC8}.Release|x86.ActiveCfg = Release|x86
- {7A608786-5C00-40F7-8F84-846D25871EC8}.Release|x86.Build.0 = Release|x86
- {F8E5957C-4B09-4ABF-AA3B-2C7CEE53D26B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {F8E5957C-4B09-4ABF-AA3B-2C7CEE53D26B}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {F8E5957C-4B09-4ABF-AA3B-2C7CEE53D26B}.Debug|x64.ActiveCfg = Debug|Any CPU
- {F8E5957C-4B09-4ABF-AA3B-2C7CEE53D26B}.Debug|x64.Build.0 = Debug|Any CPU
- {F8E5957C-4B09-4ABF-AA3B-2C7CEE53D26B}.Debug|x86.ActiveCfg = Debug|x86
- {F8E5957C-4B09-4ABF-AA3B-2C7CEE53D26B}.Debug|x86.Build.0 = Debug|x86
- {F8E5957C-4B09-4ABF-AA3B-2C7CEE53D26B}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {F8E5957C-4B09-4ABF-AA3B-2C7CEE53D26B}.Release|Any CPU.Build.0 = Release|Any CPU
- {F8E5957C-4B09-4ABF-AA3B-2C7CEE53D26B}.Release|x64.ActiveCfg = Release|Any CPU
- {F8E5957C-4B09-4ABF-AA3B-2C7CEE53D26B}.Release|x64.Build.0 = Release|Any CPU
- {F8E5957C-4B09-4ABF-AA3B-2C7CEE53D26B}.Release|x86.ActiveCfg = Release|x86
- {E91F3CB6-0CE9-4E4C-8C37-58C9A03042CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {E91F3CB6-0CE9-4E4C-8C37-58C9A03042CF}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {E91F3CB6-0CE9-4E4C-8C37-58C9A03042CF}.Debug|x64.ActiveCfg = Debug|x64
- {E91F3CB6-0CE9-4E4C-8C37-58C9A03042CF}.Debug|x64.Build.0 = Debug|x64
- {E91F3CB6-0CE9-4E4C-8C37-58C9A03042CF}.Debug|x86.ActiveCfg = Debug|Any CPU
- {E91F3CB6-0CE9-4E4C-8C37-58C9A03042CF}.Debug|x86.Build.0 = Debug|Any CPU
- {E91F3CB6-0CE9-4E4C-8C37-58C9A03042CF}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {E91F3CB6-0CE9-4E4C-8C37-58C9A03042CF}.Release|Any CPU.Build.0 = Release|Any CPU
- {E91F3CB6-0CE9-4E4C-8C37-58C9A03042CF}.Release|x64.ActiveCfg = Release|Any CPU
- {E91F3CB6-0CE9-4E4C-8C37-58C9A03042CF}.Release|x64.Build.0 = Release|Any CPU
- {E91F3CB6-0CE9-4E4C-8C37-58C9A03042CF}.Release|x86.ActiveCfg = Release|Any CPU
- {E91F3CB6-0CE9-4E4C-8C37-58C9A03042CF}.Release|x86.Build.0 = Release|Any CPU
+ {59C1FA03-95AA-4E49-AE38-0B243EEA5F2C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {59C1FA03-95AA-4E49-AE38-0B243EEA5F2C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {59C1FA03-95AA-4E49-AE38-0B243EEA5F2C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {59C1FA03-95AA-4E49-AE38-0B243EEA5F2C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8D36A4EB-2332-4D63-833D-D116B160475B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8D36A4EB-2332-4D63-833D-D116B160475B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8D36A4EB-2332-4D63-833D-D116B160475B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8D36A4EB-2332-4D63-833D-D116B160475B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {AD62B725-0126-425F-85BF-56E5587FC3E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {AD62B725-0126-425F-85BF-56E5587FC3E1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {AD62B725-0126-425F-85BF-56E5587FC3E1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {AD62B725-0126-425F-85BF-56E5587FC3E1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {14E4EF8F-FB07-433C-9EF2-1E0F381E4AD8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {14E4EF8F-FB07-433C-9EF2-1E0F381E4AD8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {14E4EF8F-FB07-433C-9EF2-1E0F381E4AD8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {14E4EF8F-FB07-433C-9EF2-1E0F381E4AD8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {91C48CF9-BD79-456E-90BF-418A753F219B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {91C48CF9-BD79-456E-90BF-418A753F219B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {91C48CF9-BD79-456E-90BF-418A753F219B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {91C48CF9-BD79-456E-90BF-418A753F219B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {21AA2ED8-A2A7-427E-897F-5C2C5AF2F70D}
+ SolutionGuid = {1B00DFC9-AC34-4608-8825-DDEEA81E3A46}
EndGlobalSection
EndGlobal
diff --git a/Witcher3MapViewer/App.config b/Witcher3MapViewer/App.config
deleted file mode 100644
index d4b4327..0000000
--- a/Witcher3MapViewer/App.config
+++ /dev/null
@@ -1,36 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- True
-
-
- True
-
-
- False
-
-
- False
-
-
- True
-
-
- True
-
-
-
-
diff --git a/Witcher3MapViewer/App.xaml b/Witcher3MapViewer/App.xaml
index 29d4679..61876ae 100644
--- a/Witcher3MapViewer/App.xaml
+++ b/Witcher3MapViewer/App.xaml
@@ -1,8 +1,8 @@
-
+ xmlns:local="clr-namespace:Witcher3MapViewer.WPF"
+ >
diff --git a/Witcher3MapViewer/App.xaml.cs b/Witcher3MapViewer/App.xaml.cs
index 8d9a87e..6aef95d 100644
--- a/Witcher3MapViewer/App.xaml.cs
+++ b/Witcher3MapViewer/App.xaml.cs
@@ -1,17 +1,102 @@
-using System;
-using System.Collections.Generic;
-using System.Configuration;
-using System.Data;
-using System.Linq;
-using System.Threading.Tasks;
+using System.IO;
using System.Windows;
+using Witcher3MapViewer.Core;
-namespace Witcher3MapViewer
+namespace Witcher3MapViewer.WPF
{
- ///
- /// Interaction logic for App.xaml
- ///
public partial class App : Application
{
+ private const string OptionsPath = "options.json";
+ private const string SettingsXMLPath = "Settings.xml";
+ private const string MapPinsXMLPath = "MapPins.xml";
+ private const string QuestsXMLPath = "Quests.xml";
+ private const string CirclePNGPath = "Circle.png";
+ private const string OptionsJSONPath = "options.json";
+ private const string QuestStatusJsonPath = "quest_statuses.json";
+ private const string ManualLevelJsonPath = "level.json";
+ private const string ManualGwentJsonPath = "gwent_statuses.json";
+ private const string GwentXMLPath = "Gwent.xml";
+
+ public App()
+ {
+ ShutdownMode = ShutdownMode.OnExplicitShutdown;
+ bool canExit = false;
+ while (!canExit)
+ {
+ XMLMapSettingsProvider mapSettingsProvider = XMLMapSettingsProvider.FromFile(SettingsXMLPath);
+ XMLMarkerProvider markerProvider = XMLMarkerProvider.FromFile(MapPinsXMLPath, mapSettingsProvider);
+ MainWindow mainWindow = new MainWindow();
+ string largeIconPath = mapSettingsProvider.GetIconSettings().LargeIconPath;
+ MapsUIMap mapsUIMap = new MapsUIMap(mainWindow.MapControl, Path.Combine(largeIconPath, CirclePNGPath));
+
+
+ Options options;
+ if (!File.Exists(OptionsPath))
+ {
+ options = Options.Default();
+ OptionsWPFDialogWindow optionsWPFDialogWindow = new OptionsWPFDialogWindow(options);
+ var accepted = optionsWPFDialogWindow.ShowDialog();
+ if (!accepted)
+ {
+ mainWindow.Close();
+ return;
+ };
+ options = optionsWPFDialogWindow.GetNewOptions();
+ options.Save(OptionsJSONPath);
+ }
+ else
+ {
+ options = Options.FromFile(OptionsPath);
+ }
+
+
+ XMLQuestListProvider questListProvider = XMLQuestListProvider.FromFile(QuestsXMLPath);
+ XMLGwentCardProvider gwentCardProvider = XMLGwentCardProvider.FromFile(GwentXMLPath);
+ GwentTrackerWPFWindow gwentTrackerWPFWindow = new GwentTrackerWPFWindow();
+
+
+
+
+ IQuestAvailabilityProvider availabilityProvider;
+ ILevelProvider levelProvider;
+ IGwentStatusProvider gwentStatusProvider;
+ if (options.TrackingMode == TrackingMode.Automatic)
+ {
+ SaveFileAvailabilityProvider saveFileAvailabilityProvider = new SaveFileAvailabilityProvider(
+ options.SaveFilePath, QuestStatusJsonPath, Path.GetTempPath()
+ );
+ availabilityProvider = saveFileAvailabilityProvider;
+ levelProvider = new SaveFileLevelProvider(saveFileAvailabilityProvider);
+ gwentStatusProvider = new SaveFileGwentStatusProvider(saveFileAvailabilityProvider);
+ }
+ else
+ {
+ availabilityProvider = new JsonManualQuestAvailabilityProvider(QuestStatusJsonPath);
+ levelProvider = new ManualLevelProvider(ManualLevelJsonPath);
+ gwentStatusProvider = new ManualGwentProvider(ManualGwentJsonPath);
+ }
+ OptionsStore optionsStore = new OptionsStore(options, availabilityProvider);
+
+
+ MainWindowViewModel mainWindowViewModel = new MainWindowViewModel(
+ mapsUIMap,
+ markerProvider,
+ mapSettingsProvider,
+ questListProvider,
+ availabilityProvider,
+ gwentCardProvider,
+ levelProvider,
+ gwentStatusProvider,
+ gwentTrackerWPFWindow,
+ new OptionsWPFDialogWindow(options),
+ optionsStore,
+ new MainWPFWindow(mainWindow));
+ mainWindow.DataContext = mainWindowViewModel;
+
+ mainWindow.ShowDialog();
+ canExit = !mainWindowViewModel.ShouldRestart;
+ }
+ Application.Current.Shutdown();
+ }
}
}
diff --git a/Witcher3MapViewer/ApplicationSettingsReader.cs b/Witcher3MapViewer/ApplicationSettingsReader.cs
deleted file mode 100644
index 595374a..0000000
--- a/Witcher3MapViewer/ApplicationSettingsReader.cs
+++ /dev/null
@@ -1,142 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using System.IO;
-using System.Xml.Serialization;
-
-namespace Witcher3MapViewer.Readers
-{
- public class ApplicationSettingsReader
- {
- public Dictionary ConversionLibrary;
- public Dictionary PathLibrary;
- public Dictionary TypeLookup;
- public List PinTypes;
- public Dictionary ShortNameLookup;
- public List LongNames;
- public List ShortNames;
- public string largeiconpath, smalliconpath;
-
- public ApplicationSettingsReader(string appdir, string filename)
- {
- XmlSerializer serializer = new XmlSerializer(typeof(ApplicationSettingRoot));
- ApplicationSettingRoot readitems;
- using (Stream reader = new FileStream(Path.Combine(appdir, filename), FileMode.Open))
- {
- readitems = (ApplicationSettingRoot)serializer.Deserialize(reader);
- }
- ConversionLibrary = new Dictionary();
- PathLibrary = new Dictionary();
- //IconFiles = new Dictionary();
- PinTypes = new List();
- ShortNameLookup = new Dictionary();
- ShortNames = new List();
- LongNames = new List();
- foreach (WorldSetting ws in readitems.worldsettings.Worlds)
- {
- string loc = ws.ShortName;
- ConversionLibrary[loc] = new RealToGameSpaceConversion(ws.conversionsetting.Slope,
- ws.conversionsetting.xintercept, ws.conversionsetting.yintercept);
- if (!File.Exists(Path.Combine(appdir, ws.filename)))
- throw new FileNotFoundException("Invalid filename");
- PathLibrary[loc] = Path.Combine(appdir, ws.filename);
- ShortNameLookup[ws.ShortName] = loc;
- if (ws.AliasShortName != null)
- ShortNameLookup[ws.AliasShortName] = ws.ShortName;
- LongNames.Add(ws.Name);
- ShortNames.Add(ws.ShortName);
- }
- TypeLookup = new Dictionary();
- foreach(IconSetting icset in readitems.iconsettings.Icons)
- {
- MapPinType m = new MapPinType(icset.Groupname, icset.InternalName);
- m.Aliases = icset.Aliases;
- m.IconFile = icset.ImageName;
- TypeLookup[icset.InternalName] = m;
- foreach (string alias in icset.Aliases)
- TypeLookup[alias] = m;
- PinTypes.Add(m);
- }
- largeiconpath = readitems.iconsettings.LargeIconPath;
- smalliconpath = readitems.iconsettings.SmallIconPath;
- }
-
- }
-
- [XmlRoot("settings")]
- public class ApplicationSettingRoot
- {
- [XmlElement("worldsettings")]
- public WorldSettings worldsettings { get; set; }
-
- [XmlElement("iconsettings")]
- public IconSettings iconsettings { get; set; }
- }
-
- public class WorldSettings
- {
- [XmlElement("worldsetting")]
- public List Worlds { get; set; }
- }
-
- public class IconSettings
- {
- [XmlElement("icon")]
- public List Icons { get; set; }
-
- [XmlElement("largeiconpath")]
- public string LargeIconPath { get; set; }
-
- [XmlElement("smalliconpath")]
- public string SmallIconPath { get; set; }
- }
-
- public class IconSetting
- {
- [XmlElement("image")]
- public string ImageName { get; set; }
-
- [XmlElement("internalname")]
- public string InternalName { get; set; }
-
- [XmlElement("alias")]
- public List Aliases { get; set; }
-
- [XmlElement("groupname")]
- public string Groupname { get; set; }
- }
-
- public class WorldSetting
- {
- [XmlAttribute("name")]
- public string Name { get; set; }
-
- [XmlAttribute("shortname")]
- public string ShortName { get; set; }
-
- [XmlElement("alias")]
- public string AliasShortName { get; set; }
-
- [XmlElement("conversion")]
- public ConversionSetting conversionsetting { get; set; }
-
- [XmlElement("tilesource")]
- public string filename { get; set; }
- }
-
- public class ConversionSetting
- {
- [XmlAttribute("slope")]
- public double Slope { get; set; }
-
- [XmlAttribute("xi")]
- public double xintercept { get; set; }
-
- [XmlAttribute("yi")]
- public double yintercept { get; set; }
- }
-
-
-}
diff --git a/Witcher3MapViewer/AssemblyInfo.cs b/Witcher3MapViewer/AssemblyInfo.cs
new file mode 100644
index 0000000..8b5504e
--- /dev/null
+++ b/Witcher3MapViewer/AssemblyInfo.cs
@@ -0,0 +1,10 @@
+using System.Windows;
+
+[assembly: ThemeInfo(
+ ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
+ //(used if a resource is not found in the page,
+ // or application resource dictionaries)
+ ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
+ //(used if a resource is not found in the page,
+ // app, or any theme specific resource dictionaries)
+)]
diff --git a/Witcher3MapViewer/Extensions.cs b/Witcher3MapViewer/Extensions.cs
deleted file mode 100644
index b6f1024..0000000
--- a/Witcher3MapViewer/Extensions.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Collections.ObjectModel;
-using System.Linq;
-
-namespace Witcher3MapViewer
-{
- static class Extensions
- {
- public static void Sort(this ObservableCollection collection) where T : IComparable
- {
- List sorted = collection.OrderBy(x => x).ToList();
- for (int i = 0; i < sorted.Count(); i++)
- collection.Move(collection.IndexOf(sorted[i]), i);
- }
-
- public static bool CaseInsensitiveContains(this string text, string value, StringComparison stringComparison = StringComparison.CurrentCultureIgnoreCase)
- {
- return text.IndexOf(value, stringComparison) >= 0;
- }
- }
-}
diff --git a/Witcher3MapViewer/GameStatusSaver.cs b/Witcher3MapViewer/GameStatusSaver.cs
deleted file mode 100644
index 467971c..0000000
--- a/Witcher3MapViewer/GameStatusSaver.cs
+++ /dev/null
@@ -1,125 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.IO;
-
-namespace Witcher3MapViewer.Readers
-{
- static class GameStatusSaver
- {
-
- public static void Save(string filename, List models, int level)
- {
- using (StreamWriter sw = new StreamWriter(filename))
- {
- WriteLevelInfo(sw, level);
- WriteQuestInfo(sw, models);
-
- }
- }
-
- private static void WriteLevelInfo(StreamWriter sw, int level)
- {
- sw.WriteLine("[Level]");
- sw.WriteLine(level);
- }
-
- private static void WriteQuestInfo(StreamWriter sw, List models)
- {
- sw.WriteLine("[Quest]");
- foreach (QuestViewModel qvm in models)
- {
- bool deferred = qvm.IsDeferred ?? false;
- if (deferred || qvm.correspondingQuest.Forced)
- {
- WriteModelStatus(sw, qvm);
- sw.Write("\n");
- }
- }
- }
-
- private static void WriteModelStatus(StreamWriter sw, QuestViewModel qvm)
- {
- sw.Write(qvm.correspondingQuest.UniqueID);
- sw.Write("\t");
- if(qvm.correspondingQuest.GUID != null && qvm.correspondingQuest.GUID.Value != "")
- {
- sw.Write(qvm.correspondingQuest.GUID.Value);
- }
- if (qvm.IsDeferred == true)
- sw.Write("\tD");
- else sw.Write("\tU");
- if(qvm.IsChecked == true)
- sw.Write("\tD");
- else sw.Write("\tU");
- }
- }
-
- public class GameStatusReader
- {
- public List QuestInfo;
- public int PlayerLevel = 1;
-
- public GameStatusReader(string filename)
- {
- using (StreamReader sr = new StreamReader(filename))
- {
- if (!ReadUntil(sr, "[Level]"))
- return;
- int.TryParse(sr.ReadLine(), out PlayerLevel);
- if (!ReadUntil(sr, "[Quest]"))
- return;
- QuestInfo = ReadQuestInfo(sr);
- }
-
- }
-
- private static List ReadQuestInfo(StreamReader sr)
- {
- List output = new List();
- while (!sr.EndOfStream)
- {
- string line = sr.ReadLine();
- if (line != "")
- {
- try
- {
- output.Add(ReadModelStatus(line));
- }
- catch (System.Reflection.AmbiguousMatchException)
- {
- return output;
- }
- }
- }
- return output;
- }
-
- private static bool ReadUntil(StreamReader sw, string query)
- {
- while (!sw.EndOfStream)
- {
- if (sw.ReadLine().Trim() == query)
- return true;
- }
- return false;
- }
-
- private static Quest ReadModelStatus(string line)
- {
- string[] fields = line.Split('\t');
- Quest q = new Quest();
- q.UniqueID = int.Parse(fields[0]);
- q.GUID = new QuestGUIDState();
- q.GUID.Value = fields[1];
- if (fields[2] == "D")
- q.Deferred = true;
- else q.Deferred = false;
- if (fields[3] == "D")
- q.Done = true;
- else q.Done = false;
- return q;
- }
- }
-}
diff --git a/Witcher3MapViewer/GwentCardViewModel.cs b/Witcher3MapViewer/GwentCardViewModel.cs
deleted file mode 100644
index c9f638e..0000000
--- a/Witcher3MapViewer/GwentCardViewModel.cs
+++ /dev/null
@@ -1,66 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace Witcher3MapViewer
-{
- public class GwentCardViewModel : INotifyPropertyChanged, IComparable
- {
- public event PropertyChangedEventHandler PropertyChanged;
-
- bool _isChecked = false;
- public bool IsChecked
- {
- get { return _isChecked; }
- set { _isChecked = value; }
- }
-
- int _id;
- public int ID { get { return _id; } }
-
- int _numHeld;
- public int NumHeld { get { return _numHeld; } set { _numHeld = value; OnPropertyChanged("NumHeld"); } }
-
- string _name;
- public string Name
- {
- set { _name = value; }
- get
- {
- if (_name == null || _name == "") return "Unknown";
- else return _name;
- }
- }
-
- string _location;
- public string Location { get { return _location; } }
-
- public GwentCardViewModel(Witcher3GwentCard thecard)
- {
- _id = thecard.cardIndex;
- _numHeld = thecard.numCopies;
- _name = thecard.Name;
- _location = thecard.Location;
- }
-
- void OnPropertyChanged(string prop)
- {
- if (PropertyChanged != null)
- PropertyChanged(this, new PropertyChangedEventArgs(prop));
- }
-
- public int CompareTo(object o)
- {
- GwentCardViewModel a = this;
- GwentCardViewModel b = (GwentCardViewModel)o;
- //bool aChecked = a.IsChecked ?? false;
- //int ret = aChecked.CompareTo(b.IsChecked);
- //if (ret != 0) return -ret;
- return a.Name.CompareTo(b.Name);
-
- }
- }
-}
diff --git a/Witcher3MapViewer/GwentTracker.xaml b/Witcher3MapViewer/GwentTracker.xaml
index db7c495..490934a 100644
--- a/Witcher3MapViewer/GwentTracker.xaml
+++ b/Witcher3MapViewer/GwentTracker.xaml
@@ -1,11 +1,11 @@
-
+ Title="GwentTracker" Height="450" Width="800">
@@ -14,11 +14,18 @@
- DLC summary
+
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
- Level
- 1
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
diff --git a/Witcher3MapViewer/MainWindow.xaml.cs b/Witcher3MapViewer/MainWindow.xaml.cs
index 81105b4..a1d4de1 100644
--- a/Witcher3MapViewer/MainWindow.xaml.cs
+++ b/Witcher3MapViewer/MainWindow.xaml.cs
@@ -1,1117 +1,15 @@
using System.Windows;
-using SQLite;
-using BruTile;
-using System.Collections.Generic;
-using System.Collections.Specialized;
-using Mapsui.Styles;
-using System.IO;
-using System.Threading;
-using System;
-using System.ComponentModel;
-using System.Collections.ObjectModel;
-using System.Linq;
-using System.Windows.Input;
-namespace Witcher3MapViewer
+namespace Witcher3MapViewer.WPF
{
///
/// Interaction logic for MainWindow.xaml
///
- ///
-
- /*
- * Known Issues:
- * Gwent window showing random players does not update the treeview
- * Visibility of info window binding does not appear to work
- *
- * TODO:
- * Gwent window highlight should show on map (this is kind of a lot of work)
- * Gwent players should be circles around regular mappins?
- * Is there a way to tell whether a random gwent player has been played? Probably not easy
- * Add ability for multiple points of discovery
- * Debug manual mode and switching between modes
- * DLC gwent? Not really necessary
- * Save gwent info in manual mode
- * De-uglify some of the icons
- * Shift SetIfUndone to the viewmodel
- */
-
- public partial class MainWindow : Window, INotifyPropertyChanged
+ public partial class MainWindow : Window
{
- private string appdir;
- private Dictionary ConversionLibrary;
- private Dictionary MarkersPerWorld;
- private Dictionary PathLibrary;
- private string currentLocation, circleShownOnLocation;
- MapPinReader mappindata;
- Dictionary ShortNameLookup; //needed because of aliases
- List ShortNames;
- string largeiconpath;
- string smalliconpath;
- StringDictionary MarkersState;
- Dictionary IconCache = new Dictionary();
- ManualResetEvent _refreshRequested = new ManualResetEvent(false);
- bool MapChanged = false, QuestChanged = false, SaveChanged = false, autosave = false;
- RealToGameSpaceConversion CurrentConvertor;
- Witcher3ProgressStatus progressStatus;
- Witcher3SaveFile ActiveSave;
- string ActiveSaveFileCopy;
- Mapsui.Layers.MemoryLayer CircleLayer;
- Mapsui.Layers.TileLayerAbort tileLayer;
- FileSystemWatcher _fileSystemWatcher;
- QuestViewModel _currentSelection;
- List BaseGameGwentCards;
- GwentTracker activeGwentTrackerWindow;
- bool RandomPlayersOnMap = false;
- bool skipnextrecenter = false;
-
- //Applications settings
- private bool showAccessibleOnly, showEvents, showRaces, showTreasure, manualMode;
- private string SaveFolder;
-
-
- public event PropertyChangedEventHandler PropertyChanged;
-
- ObservableCollection _currentQuests;
- public ObservableCollection CurrentQuests { get { return _currentQuests; } }
-
- ObservableCollection _markers;
- public ObservableCollection Markers { get { return _markers; } set { _markers = value; } }
-
- private int _playerlevel = 1;
- public int PlayerLevel
- {
- get { return _playerlevel; }
- set
- {
- _playerlevel = value;
- Level_textbox.Text = _playerlevel.ToString();
- RaisePropertyChanged("PlayerLevel");
- }
- }
-
- private int _worldSelectIndex;
- public int WorldSelectIndex
- {
- get { return _worldSelectIndex; }
- set
- {
- _worldSelectIndex = value;
- LoadMap(ShortNames[_worldSelectIndex]);
- //we don't want to show the current circles on other maps
- if (currentLocation == circleShownOnLocation && CircleLayer != null)
- CircleLayer.Enabled = true;
- else CircleLayer.Enabled = false;
-
- RaisePropertyChanged("WorldSelectIndex");
- }
- }
-
- private List _worldSelectList;
- public List WorldSelectList
- {
- get { return _worldSelectList; }
- set
- {
- _worldSelectList = value;
- RaisePropertyChanged("WorldSelectList");
- }
- }
-
-
-
- private string _infoMessage;
- public string InfoMessage
- {
- get
- {
- return _infoMessage;
- }
- set
- {
- _infoMessage = value;
- if (_infoMessage == "")
- InfoShown = false;
- else InfoShown = true;
-
- RaisePropertyChanged("InfoMessage");
- }
- }
-
- private bool _infoShown;
- public bool InfoShown
- {
- get { return _infoShown; }
- set { _infoShown = value; RaisePropertyChanged("InfoShown"); }
- }
-
- string _gwentStatusText = "This is the status";
- public string GwentStatusText
- {
- get
- {
- return _gwentStatusText;
- }
- set
- {
- _gwentStatusText = value;
- RaisePropertyChanged("GwentStatusText");
- }
- }
-
- string _searchQuery = "";
- public string SearchQuery
- {
- get { return _searchQuery; }
- set { _searchQuery = value; UpdateSearch(); }
- }
-
- bool _searchShown = false;
- public bool SearchShown { get { return _searchShown; } set { _searchShown = value; RaisePropertyChanged("SearchShown"); } }
-
public MainWindow()
{
- //suppresses pesky debug errors about invalid ancestor to combobox
-#if DEBUG
- System.Diagnostics.PresentationTraceSources.DataBindingSource.Switch.Level = System.Diagnostics.SourceLevels.Critical;
-#endif
-
- int localplayerlevel = 1;
- DataSetup();
- if (manualMode && File.Exists(Path.Combine(appdir, "status.dat")))
- {
- Readers.GameStatusReader statusreader = new Readers.GameStatusReader(Path.Combine(appdir, "status.dat"));
- if (statusreader.QuestInfo != null && statusreader.QuestInfo.Count != 0)
- {
- MessageBoxResult result =
- MessageBox.Show("Load quest info from previous session?", "Load data?", MessageBoxButton.YesNo);
- if (result == MessageBoxResult.Yes)
- {
- ProcessLastStatus(statusreader.QuestInfo);
- localplayerlevel = statusreader.PlayerLevel;
- }
- }
-
- }
- DataContext = this;
InitializeComponent();
- PlayerLevel = localplayerlevel;
-
- if (Properties.Settings.Default.FirstRun)
- {
- Properties.Settings.Default.FirstRun = false;
- var foo = new SettingsDialog();
- foo.ShowDialog();
- }
- SettingsFromConfig();
-
- _currentQuests = new ObservableCollection();
- if (!manualMode)
- {
- if (Directory.Exists(SaveFolder))
- {
- GetMostRecentSaveFile(); //automatically calls updatequests
- SetUpFilewatcher();
- }
- else
- {
- MessageBox.Show("The tracker has been put into manual mode. If you want to track quests" +
- " automatically, choose a valid folder in the settings.");
- manualMode = true;
- Properties.Settings.Default.ManualMode = true;
- }
- }
- else
- {
- GwentStatusText = "Click to see Gwent cards";
- UpdateQuests();
- }
- _currentQuests.Sort();
- RaisePropertyChanged("CurrentQuests");
-
- InfoMessage = "";
-
- System.Windows.Threading.DispatcherTimer autosaveTimer = new System.Windows.Threading.DispatcherTimer(
- TimeSpan.FromSeconds(120), System.Windows.Threading.DispatcherPriority.Background,
- new EventHandler(DoAutoSave), Application.Current.Dispatcher);
- autosaveTimer.Start();
-
- SeekNextUndone();
- MyMapControl.Map.Viewport.Resolution = 20000;
-
- SearchBox.NextItemButton.Click += NextItemButton_Click;
- SearchBox.PrevItemButton.Click += PrevItemButton_Click;
- SearchBox.CloseSearchButton.Click += CloseSearchButton_Click;
- CommandBindings.Add(new CommandBinding(ApplicationCommands.Find, FindExecuted, FindCanExecute));
- }
-
- private void FindCanExecute(object sender, CanExecuteRoutedEventArgs e)
- {
- e.CanExecute = !SearchShown;
- }
-
- private void FindExecuted(object sender, ExecutedRoutedEventArgs e)
- {
- SearchShown = true;
- SearchBox.QueryText.Focus();
- Keyboard.Focus(SearchBox.QueryText);
- }
-
- private void UpdateSearch()
- {
- SearchQuests(SearchQuery, 0, SearchDirection.Forward);
- }
-
- private void PrevItemButton_Click(object sender, RoutedEventArgs e)
- {
- int start = 0;
- if (_currentSelection != null)
- start = CurrentQuests.IndexOf(_currentSelection);
-
- SearchQuests(SearchQuery, start, SearchDirection.Backward);
-
- }
-
- private void NextItemButton_Click(object sender, RoutedEventArgs e)
- {
- int start = 0;
- if (_currentSelection != null)
- start = CurrentQuests.IndexOf(_currentSelection);
-
- SearchQuests(SearchQuery, start, SearchDirection.Forward);
- }
-
- private enum SearchDirection { Forward, Backward }
-
- private void SearchQuests(string query, int start, SearchDirection direction)
- {
- if (CurrentQuests == null || CurrentQuests.Count == 0)
- return;
- if (query == "") return;
- int increment = 1;
- if (direction == SearchDirection.Backward)
- increment = -1;
- int current = start + increment;
- int count = CurrentQuests.Count;
- while (current != start)
- {
- QuestViewModel qvm = CurrentQuests[current];
- if (qvm.Name.CaseInsensitiveContains(SearchQuery))
- {
- qvm.IsSelected = true;
- _currentSelection = qvm;
- return;
- }
- current += increment;
- if (current == count)
- current = 0;
- if (current < 0)
- current = count - 1;
- }
- }
-
- private void CloseSearchButton_Click(object sender, RoutedEventArgs e)
- {
- SearchShown = false;
- }
-
- private void ProcessLastStatus(List lastStatus)
- {
- //Go through the quests and set the flags according to the status.dat file.
- bool foundproblem = false;
- foreach (Quest q in lastStatus)
- {
- Quest questtoupdate = progressStatus.Quests.Where(item => item.UniqueID == q.UniqueID).First();
-
- if (questtoupdate.GUID.Value != q.GUID.Value)
- {
- foundproblem = true;
- continue;
- }
- if (q.Done)
- {
- questtoupdate.Done = true;
- questtoupdate.Forced = true;
- questtoupdate.Status = QuestStatusState.Success;
- }
- if (q.Deferred)
- questtoupdate.Deferred = true;
-
- }
- if (foundproblem)
- {
- MessageBox.Show("The data from a previous run is inconsistent with the current xml file. Previous manual statuses have been" +
- " restored where possible");
- }
- }
-
- private void SetUpFilewatcher()
- {
- _fileSystemWatcher = new FileSystemWatcher(SaveFolder);
- _fileSystemWatcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName;
- _fileSystemWatcher.Filter = "*.sav";
- _fileSystemWatcher.Changed += _fileSystemWatcher_Changed;
- _fileSystemWatcher.Created += _fileSystemWatcher_Changed;
- _fileSystemWatcher.Renamed += _fileSystemWatcher_Changed;
- _fileSystemWatcher.EnableRaisingEvents = true;
- }
-
-
-
- private void CanFindNext(object sender, CanExecuteRoutedEventArgs e)
- {
- e.CanExecute = true;
- }
-
- private void _fileSystemWatcher_Changed(object sender, FileSystemEventArgs e)
- {
- Thread.Sleep(2000);
- SaveChanged = true;
- _refreshRequested.Set();
- ThreadPool.QueueUserWorkItem(RefreshIfNecessary);
- }
-
- private void DataSetup()
- {
- appdir = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
- if (!Directory.Exists(Path.Combine(appdir, "SavedGames")))
- Directory.CreateDirectory(Path.Combine(appdir, "SavedGames"));
-
- MarkersPerWorld = new Dictionary();
- Readers.ApplicationSettingsReader Settings = new Readers.ApplicationSettingsReader(appdir, "Settings.xml");
- ConversionLibrary = Settings.ConversionLibrary;
- PathLibrary = Settings.PathLibrary;
- ShortNameLookup = Settings.ShortNameLookup;
- ShortNames = Settings.ShortNames;
- largeiconpath = Settings.largeiconpath;
- smalliconpath = Settings.smalliconpath;
-
- List source = new List();
- foreach (string longname in Settings.LongNames)
- {
- source.Add(new System.Windows.Controls.ComboBoxItem { Content = longname });
- }
- WorldSelectList = source;
- mappindata = new MapPinReader(Path.Combine(appdir, "MapPins.xml"), Settings.TypeLookup);
- GetGwentCardList();
- LoadQuestData();
- }
-
- private void GetGwentCardList()
- {
- var reader = new Readers.GwentXMLReader(Path.Combine(appdir, "Gwent.xml"));
-
- BaseGameGwentCards = new List();
- foreach (Readers.GwentCardAsRead cardasread in reader.Sets[0].Cards)
- {
- Witcher3GwentCard card = new Witcher3GwentCard();
- card.cardIndex = cardasread.ID;
- card.Name = cardasread.Name;
- card.Location = cardasread.Location;
- card.AssociatedQuest = cardasread.AssociatedQuest;
- BaseGameGwentCards.Add(card);
- }
- }
-
- private void comboBox_Loaded(object sender, RoutedEventArgs e)
- {
- //Make combobox open upwards
- System.Windows.Controls.ControlTemplate ct = comboBox.Template;
- System.Windows.Controls.Primitives.Popup pop = ct.FindName("PART_Popup", comboBox) as System.Windows.Controls.Primitives.Popup;
- pop.Placement = System.Windows.Controls.Primitives.PlacementMode.Top;
- }
-
- private void LoadMap(string location)
- {
- ObservableCollection tempmarkers = new ObservableCollection();
-
- MarkerViewModel root;
- bool worldWasAlreadyLoaded = MarkersPerWorld.ContainsKey(location);
- if (worldWasAlreadyLoaded)
- {
- root = MarkersPerWorld[location];
- }
- else
- {
- root = MarkerViewModel.CreateRoot(Path.Combine(appdir, smalliconpath));
- MarkersPerWorld.Add(location, root);
- }
-
- currentLocation = location;
- CurrentConvertor = ConversionLibrary[location];
- if (tileLayer != null)
- {
- //fixes memory leak in Mapsui
- tileLayer.AbortFetch();
- MyMapControl.Map.Layers.Clear();
- }
-
- MbTilesTileSource _source = new MbTilesTileSource(new SQLiteConnectionString(PathLibrary[location], false));
- tileLayer = new Mapsui.Layers.TileLayerAbort(_source);
- MyMapControl.Map.Layers.Add(tileLayer);
- Dictionary worldpindata = mappindata.GetPins(ShortNameLookup[location]);
- foreach (MapPinType pintype in worldpindata.Keys)
- {
- //I want to do this last
- if (pintype.InternalName == "RoadSign")
- continue;
-
- if (worldWasAlreadyLoaded)
- {
- MyMapControl.Map.Layers.Add(root.FindChild(pintype.InternalName).AssociatedLayer);
- continue;
- }
-
- Mapsui.Layers.MemoryLayer _pointlayer = new Mapsui.Layers.MemoryLayer { Style = null };
- List _points = new List();
- string path = Path.Combine(appdir, largeiconpath, pintype.IconFile);
-
- //Load the image if needed
- if (!IconCache.ContainsKey(path))
- {
- using (FileStream fs = new FileStream(path, FileMode.Open))
- {
- MemoryStream ms = new MemoryStream();
- fs.CopyTo(ms);
- int id = BitmapRegistry.Instance.Register(ms);
- IconCache[path] = id;
- }
- }
-
- if (pintype.InternalName.Contains("Quest"))
- {
- foreach (MapPin c in worldpindata[pintype].Locations)
- {
- //Try to match Pin coords and Quest coords
- Quest q = progressStatus.getQuestByCoordinates(Convert.ToInt32(c.Location.X), Convert.ToInt32(c.Location.Y));
- //If no matching quest found then still display the Pin
- bool questNotDoneOrNotFound = q == null || !q.Done;
- if (questNotDoneOrNotFound)
- _points.Add(CurrentConvertor.ToWorldSpace(c.Location));
- }
- }
- else
- foreach (MapPin c in worldpindata[pintype].Locations)
- _points.Add(CurrentConvertor.ToWorldSpace(c.Location));
-
- _pointlayer.DataSource = new Mapsui.Providers.MemoryProvider(_points);
-
- SymbolStyle _style = new SymbolStyle();
-
-
- _style.BitmapId = IconCache[path];
- _pointlayer.Style = _style;
-
- _pointlayer.Name = pintype.Name;
- MyMapControl.Map.Layers.Add(_pointlayer);
- root.AddChild(new MarkerViewModel(pintype, _pointlayer));
- }
- root.VerifyCheckState();
- AddCircleLayer();
- //Add label layer last so that it's on top
- MyMapControl.Map.Layers.Add(GetLabelLayer(worldpindata));
- MyMapControl.Map.BackColor = new Color(184, 222, 230);
- tempmarkers.Add(root);
- _markers = tempmarkers;
- RaisePropertyChanged("Markers");
- if (RandomPlayersOnMap)
- ToggleGwentPlayers(true);
- else
- {
- MarkerViewModel gw = Markers[0].FindChild("GwentPlayer");
- if (gw != null) gw.IsChecked = false;
- }
-
- MyMapControl.Refresh();
- }
-
- private void ToggleGwentPlayers(bool ShowLayer)
- {
- Markers[0].IsChecked = !ShowLayer; //turn off all
- MarkerViewModel gw = Markers[0].FindChild("GwentPlayer");
- if (gw != null) gw.IsChecked = ShowLayer; //enable only gwent players
- RandomPlayersOnMap = ShowLayer;
- }
-
- private Mapsui.Layers.MemoryLayer GetLabelLayer(Dictionary worldpindata)
- {
- Mapsui.Layers.MemoryLayer _pointlayer = new Mapsui.Layers.MemoryLayer { Style = null };
- List _points = new List();
- MapPinType pintype = worldpindata.Keys.ToList().Where(item => item.InternalName == "RoadSign").FirstOrDefault();
- string path = Path.Combine(appdir, largeiconpath, pintype.IconFile);
- var memoryProvider = new Mapsui.Providers.MemoryProvider();
- //Load the image if needed
- if (!IconCache.ContainsKey(path))
- {
- using (FileStream fs = new FileStream(path, FileMode.Open))
- {
- MemoryStream ms = new MemoryStream();
- fs.CopyTo(ms);
- int id = BitmapRegistry.Instance.Register(ms);
- IconCache[path] = id;
- }
- }
-
- foreach (MapPin c in worldpindata[pintype].Locations)
- {
- var feature = new Mapsui.Providers.Feature
- {
- Geometry = CurrentConvertor.ToWorldSpace(c.Location),
- };
- feature.Styles.Add(new LabelStyle
- {
- Text = BreakLines(c.Name, 17),
- ForeColor = Color.White,
- BackColor = new Brush(Color.Gray),
- Halo = new Pen(Color.Black, 2),
- VerticalAlignment = LabelStyle.VerticalAlignmentEnum.Bottom,
- Offset = new Offset(0, 14),
- CollisionDetection = false
- });
- feature.Styles.Add(new SymbolStyle { BitmapId = IconCache[path] });
- memoryProvider.Features.Add(feature);
- }
- _pointlayer.DataSource = memoryProvider;
- return _pointlayer;
- }
-
- private string BreakLines(string tobreak, int desiredmaxchars)
- {
- if (!tobreak.Contains(" ")) return tobreak;
- string[] words = tobreak.Split(' ');
- string broken = words[0];
- int count = broken.Length;
- for (int i = 1; i < words.Length; i++)
- {
- if (count + words[i].Length > desiredmaxchars)
- {
- broken += "\n";
- count = 0;
- }
- else
- {
- broken += " ";
- count++;
- }
- broken += words[i];
- count += words[i].Length;
- }
- return broken;
- }
-
- private void AddCircleLayer()
- {
- if (CircleLayer == null)
- {
- Mapsui.Layers.MemoryLayer _pointlayer = new Mapsui.Layers.MemoryLayer();
- List _points = new List();
- _pointlayer.DataSource = new Mapsui.Providers.MemoryProvider(_points);
- _pointlayer.Name = "Circle";
- SymbolStyle _style = new SymbolStyle();
- string path = Path.Combine(appdir, largeiconpath, Path.Combine(appdir, @"MarkerImages\Circle.png"));
- if (!IconCache.ContainsKey(path))
- {
- using (FileStream fs = new FileStream(path, FileMode.Open))
- {
- MemoryStream ms = new MemoryStream();
- fs.CopyTo(ms);
- int id = BitmapRegistry.Instance.Register(ms);
- IconCache[path] = id;
- }
- }
- _style.BitmapId = IconCache[path];
- _pointlayer.Style = _style;
- CircleLayer = _pointlayer;
- }
- MyMapControl.Map.Layers.Add(CircleLayer);
- }
-
- private void AddCircleAt(Mapsui.Geometries.Point Center)
- {
- circleShownOnLocation = currentLocation;
- CircleLayer.DataSource = new Mapsui.Providers.MemoryProvider(CurrentConvertor.ToWorldSpace(Center));
- CircleLayer.Enabled = true;
- }
-
- private void ToggleLayers()
- {
- MyMapControl.Refresh();
- }
-
- //Check a marker checkbox
- private void CheckBox_Checked(object sender, RoutedEventArgs e)
- {
-
- MapChanged = true;
- _refreshRequested.Set();
- ThreadPool.QueueUserWorkItem(RefreshIfNecessary);
- }
-
- private void RefreshIfNecessary(object state)
- {
- lock (this)
- {
- if (!_refreshRequested.WaitOne(0))
- {
- // Refresh not necessary
- return;
- }
- if (MapChanged)
- {
- ToggleLayers();
- }
- if (QuestChanged)
- {
- //needed because the _quests collection created in UI thread
- ThreadPool.QueueUserWorkItem(delegate
- {
- Dispatcher.BeginInvoke(new Action(UpdateQuests));
- });
- }
- if (SaveChanged)
- {
- ThreadPool.QueueUserWorkItem(delegate
- {
- Dispatcher.BeginInvoke(new Action(ExecuteSaveChange));
- });
- }
- if (manualMode && autosave)
- Readers.GameStatusSaver.Save("status.dat", _currentQuests.ToList(), PlayerLevel);
-
- // Clear all flags
- MapChanged = false;
- QuestChanged = false;
- SaveChanged = false;
- autosave = false;
- _refreshRequested.Reset();
- }
- }
-
- private void ExecuteSaveChange()
- {
- ClearLocalSave();
- GetMostRecentSaveFile();
- SeekNextUndone();
- }
-
- private void ClearLocalSave()
- {
- if (File.Exists(ActiveSaveFileCopy))
- File.Delete(ActiveSaveFileCopy);
- }
-
- private void UpdateQuests()
- {
- Witcher3ProgressStatus.AvailCond Conditions = FormConditions();
- List addedItems = progressStatus.GetAvailable(Conditions);
- foreach (Quest q in addedItems)
- _currentQuests.Add(new QuestViewModel(q));
-
- List removedItems = progressStatus.CullNewlyUnavailable(Conditions);
- if (removedItems.Count != 0)
- {
- foreach (Quest q in removedItems)
- {
- QuestViewModel goner = _currentQuests.Where(item => item.correspondingQuest == q).First();
- _currentQuests.Remove(goner);
- }
- }
-
- //Update checked status from underlying quests
- foreach (QuestViewModel q in _currentQuests)
- q.Recheck();
-
- _currentQuests.Sort(); //even if none were added, we need to sort done objects to the top, etc.
- if (_currentSelection != null && _currentSelection.IsSelected == false)
- {
- //sorting resets IsSelected field, so reset it.
- skipnextrecenter = true;
- _currentSelection.IsSelected = true;
- }
-
- if (manualMode)
- SeekNextUndone();
- }
-
- private Witcher3ProgressStatus.AvailCond FormConditions()
- {
- Witcher3ProgressStatus.AvailCond Conditions = 0;
- if (showAccessibleOnly)
- Conditions = Conditions | Witcher3ProgressStatus.AvailCond.Accessible;
- if (showTreasure)
- Conditions = Conditions | Witcher3ProgressStatus.AvailCond.Treasure;
- if (showRaces)
- Conditions = Conditions | Witcher3ProgressStatus.AvailCond.Races;
- if (showEvents)
- Conditions = Conditions | Witcher3ProgressStatus.AvailCond.Events;
- return Conditions;
- }
-
- private void MyMapControl_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
- {
- Point screenPosition = e.GetPosition(MyMapControl);
- Mapsui.Geometries.Point earthPosition = MyMapControl.Map.Viewport.ScreenToWorld(screenPosition.X, screenPosition.Y);
- Mapsui.Geometries.Point worldPosition = CurrentConvertor.ToGameSpace(earthPosition);
- HoverPositionXY.Text = $"{worldPosition.X:F0}, {worldPosition.Y:F0}";
- }
-
- private void PART_IncreaseButton_Click(object sender, RoutedEventArgs e)
- {
- PlayerLevel++;
- }
-
- private void PART_DecreaseButton_Click(object sender, RoutedEventArgs e)
- {
- if (PlayerLevel > 1)
- PlayerLevel--;
- }
-
- private void Level_textbox_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
- {
- int i;
- if (int.TryParse(Level_textbox.Text, out i))
- {
- PlayerLevel = i;
- }
- else Level_textbox.Text = PlayerLevel.ToString();
- }
-
- private void CheckBox_Checked_1(object sender, RoutedEventArgs e)
- {
- QuestChanged = true;
- _refreshRequested.Set();
- ThreadPool.QueueUserWorkItem(RefreshIfNecessary);
- }
-
- private void questTree_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs