diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..1ff0c42
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,63 @@
+###############################################################################
+# Set default behavior to automatically normalize line endings.
+###############################################################################
+* text=auto
+
+###############################################################################
+# Set default behavior for command prompt diff.
+#
+# This is need for earlier builds of msysgit that does not have it on by
+# default for csharp files.
+# Note: This is only used by command line
+###############################################################################
+#*.cs diff=csharp
+
+###############################################################################
+# Set the merge driver for project and solution files
+#
+# Merging from the command prompt will add diff markers to the files if there
+# are conflicts (Merging from VS is not affected by the settings below, in VS
+# the diff markers are never inserted). Diff markers may cause the following
+# file extensions to fail to load in VS. An alternative would be to treat
+# these files as binary and thus will always conflict and require user
+# intervention with every merge. To do so, just uncomment the entries below
+###############################################################################
+#*.sln merge=binary
+#*.csproj merge=binary
+#*.vbproj merge=binary
+#*.vcxproj merge=binary
+#*.vcproj merge=binary
+#*.dbproj merge=binary
+#*.fsproj merge=binary
+#*.lsproj merge=binary
+#*.wixproj merge=binary
+#*.modelproj merge=binary
+#*.sqlproj merge=binary
+#*.wwaproj merge=binary
+
+###############################################################################
+# behavior for image files
+#
+# image files are treated as binary by default.
+###############################################################################
+#*.jpg binary
+#*.png binary
+#*.gif binary
+
+###############################################################################
+# diff behavior for common document formats
+#
+# Convert binary document formats to text before diffing them. This feature
+# is only available from the command line. Turn it on by uncommenting the
+# entries below.
+###############################################################################
+#*.doc diff=astextplain
+#*.DOC diff=astextplain
+#*.docx diff=astextplain
+#*.DOCX diff=astextplain
+#*.dot diff=astextplain
+#*.DOT diff=astextplain
+#*.pdf diff=astextplain
+#*.PDF diff=astextplain
+#*.rtf diff=astextplain
+#*.RTF diff=astextplain
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..3f15629
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,264 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+
+# User-specific files
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+
+# Windows stuff
+Thumbs.db
+
+# Visual Studio 2015 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUNIT
+*.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# DNX
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+*_i.c
+*_p.c
+*_i.h
+*.ilk
+*.meta
+*.obj
+*.pch
+*.pdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_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
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# TODO: 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
+# The packages folder can be ignored because of Package Restore
+**/packages/*
+# except build/, which is used as an MSBuild target.
+!**/packages/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
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+node_modules/
+orleans.codegen.cs
+
+# 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/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+
+# SQL Server files
+*.mdf
+*.ldf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# JetBrains Rider
+.idea/
+*.sln.iml
+
+# CodeRush
+.cr/
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
\ No newline at end of file
diff --git a/Assets/hires_map.png b/Assets/hires_map.png
new file mode 100644
index 0000000..4273855
Binary files /dev/null and b/Assets/hires_map.png differ
diff --git a/Assets/screenshot.png b/Assets/screenshot.png
new file mode 100644
index 0000000..2a630e7
Binary files /dev/null and b/Assets/screenshot.png differ
diff --git a/Assets/symbols.svg b/Assets/symbols.svg
new file mode 100644
index 0000000..9480d6f
--- /dev/null
+++ b/Assets/symbols.svg
@@ -0,0 +1,442 @@
+
+
+
+
diff --git a/Common/Common.csproj b/Common/Common.csproj
new file mode 100644
index 0000000..2eab2ef
--- /dev/null
+++ b/Common/Common.csproj
@@ -0,0 +1,51 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {3C4C5A9E-9165-4074-AE85-DF62931457EB}
+ Library
+ Properties
+ Larkator.Common
+ Larkator.Common
+ v4.7
+ 512
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Common/Dino.cs b/Common/Dino.cs
new file mode 100644
index 0000000..7988d2a
--- /dev/null
+++ b/Common/Dino.cs
@@ -0,0 +1,46 @@
+using System;
+using System.Collections.Generic;
+
+namespace Larkator.Common
+{
+ public class Dino : IEquatable
+ {
+ public UInt64? Id { get; set; }
+ public string Type { get; set; }
+ public string Name { get; set; }
+ public int BaseLevel { get; set; }
+ public bool Female { get; set; }
+ public Position Location { get; set; }
+ public StatPoints WildLevels { get; set; }
+
+ public override bool Equals(object obj)
+ {
+ return Equals(obj as Dino);
+ }
+
+ public bool Equals(Dino other)
+ {
+ return other != null &&
+ EqualityComparer.Default.Equals(Id, other.Id) &&
+ Type == other.Type &&
+ Name == other.Name &&
+ BaseLevel == other.BaseLevel &&
+ Female == other.Female &&
+ EqualityComparer.Default.Equals(Location, other.Location) &&
+ EqualityComparer.Default.Equals(WildLevels, other.WildLevels);
+ }
+
+ public override int GetHashCode()
+ {
+ var hashCode = 1606339474;
+ hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Id);
+ hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Type);
+ hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Name);
+ hashCode = hashCode * -1521134295 + BaseLevel.GetHashCode();
+ hashCode = hashCode * -1521134295 + Female.GetHashCode();
+ hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Location);
+ hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(WildLevels);
+ return hashCode;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Common/Position.cs b/Common/Position.cs
new file mode 100644
index 0000000..3d9ef6c
--- /dev/null
+++ b/Common/Position.cs
@@ -0,0 +1,50 @@
+using System;
+
+namespace Larkator.Common
+{
+ public class Position : IEquatable
+ {
+ public double X { get; set; }
+ public double Y { get; set; }
+ public double Z { get; set; }
+ public double Lat { get; set; }
+ public double Lon { get; set; }
+
+ public override bool Equals(object obj)
+ {
+ return Equals(obj as Position);
+ }
+
+ public bool Equals(Position other)
+ {
+ return other != null &&
+ X == other.X &&
+ Y == other.Y &&
+ Z == other.Z &&
+ Lat == other.Lat &&
+ Lon == other.Lon;
+ }
+
+ public override int GetHashCode()
+ {
+ var hashCode = -1898883508;
+ hashCode = hashCode * -1521134295 + X.GetHashCode();
+ hashCode = hashCode * -1521134295 + Y.GetHashCode();
+ hashCode = hashCode * -1521134295 + Z.GetHashCode();
+ hashCode = hashCode * -1521134295 + Lat.GetHashCode();
+ hashCode = hashCode * -1521134295 + Lon.GetHashCode();
+ return hashCode;
+ }
+
+ public string ToString(PositionFormat format)
+ {
+ return format == PositionFormat.LatLong ? $"{Lat:0.0} , {Lon:0.0}" : $"{X:0.00} , {Y:0.00} , {Z:0.00}";
+ }
+ }
+
+ public enum PositionFormat
+ {
+ LatLong,
+ XYZ,
+ }
+}
\ No newline at end of file
diff --git a/Common/Properties/AssemblyInfo.cs b/Common/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..d3b6334
--- /dev/null
+++ b/Common/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// 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("Common")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("Common")]
+[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)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("3c4c5a9e-9165-4074-ae85-df62931457eb")]
+
+// 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/Common/SearchCriteria.cs b/Common/SearchCriteria.cs
new file mode 100644
index 0000000..8392060
--- /dev/null
+++ b/Common/SearchCriteria.cs
@@ -0,0 +1,79 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Larkator.Common
+{
+ ///
+ /// The search parameters.
+ ///
+ ///
+ /// Objects of this type use only a random immutable id when computing the hashcode or comparing for equality.
+ /// This helps avoid issues such as:
+ /// https://support.microsoft.com/en-gb/help/2909048/system-argumentexception-when-selecting-row-in-wpf-datagrid
+ ///
+ public class SearchCriteria : IEquatable
+ {
+ private Guid guid = Guid.NewGuid(); // to avoid something like https://support.microsoft.com/en-gb/help/2909048/system-argumentexception-when-selecting-row-in-wpf-datagrid
+
+ public SearchCriteria()
+ {
+ }
+
+ public SearchCriteria(SearchCriteria copy)
+ {
+ Group = copy.Group;
+ Order = copy.Order;
+ Species = copy.Species;
+ MinLevel = copy.MinLevel;
+ MaxLevel = copy.MaxLevel;
+ Female = copy.Female;
+ }
+
+ public string Group { get; set; }
+ public double Order { get; set; }
+ public string Species { get; set; }
+ public int? MinLevel { get; set; }
+ public int? MaxLevel { get; set; }
+ public bool? Female { get; set; }
+
+ ///
+ /// Compares equality based only on an internal random ID assigned on creation.
+ ///
+ ///
+ public override bool Equals(object obj)
+ {
+ return Equals(obj as SearchCriteria);
+ }
+
+ ///
+ /// Compares equality based only on an internal random ID assigned on creation.
+ ///
+ ///
+ public bool Equals(SearchCriteria other)
+ {
+ return other != null && guid.Equals(other.guid);
+ }
+
+ ///
+ /// Computes the hash code based only on an internal random ID assigned on creation.
+ ///
+ ///
+ public override int GetHashCode()
+ {
+ return guid.GetHashCode();
+ }
+
+ public bool Matches(Dino dino)
+ {
+ if (MinLevel.HasValue && dino.BaseLevel < MinLevel) return false;
+ if (MaxLevel.HasValue && dino.BaseLevel > MaxLevel) return false;
+ if (Female.HasValue && dino.Female != Female) return false;
+ if (!String.IsNullOrWhiteSpace(Species) && !String.Equals(Species, dino.Type)) return false;
+
+ return true;
+ }
+ }
+}
diff --git a/Common/SearchResult.cs b/Common/SearchResult.cs
new file mode 100644
index 0000000..468a5f9
--- /dev/null
+++ b/Common/SearchResult.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Larkator.Common
+{
+ public class SearchResult : IEquatable
+ {
+ public string Species { get; set; }
+ public int Level { get; set; }
+ public bool Female { get; set; }
+ public Position Location { get; set; }
+
+ public string Tooltip { get { return $"{Species} {(Female ? "F" : "M")}{Level} {Location.Lat}, {Location.Lon}"; } }
+
+ public override bool Equals(object obj)
+ {
+ return Equals(obj as SearchResult);
+ }
+
+ public bool Equals(SearchResult other)
+ {
+ return other != null &&
+ Species == other.Species &&
+ Level == other.Level &&
+ Female == other.Female &&
+ EqualityComparer.Default.Equals(Location, other.Location);
+ }
+
+ public override int GetHashCode()
+ {
+ var hashCode = 1223319261;
+ hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Species);
+ hashCode = hashCode * -1521134295 + Level.GetHashCode();
+ hashCode = hashCode * -1521134295 + Female.GetHashCode();
+ hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Location);
+ return hashCode;
+ }
+ }
+}
diff --git a/Common/StatPoints.cs b/Common/StatPoints.cs
new file mode 100644
index 0000000..30e3581
--- /dev/null
+++ b/Common/StatPoints.cs
@@ -0,0 +1,52 @@
+using System;
+
+namespace Larkator.Common
+{
+ public class StatPoints : IEquatable
+ {
+ public int Health { get; set; }
+ public int Stamina { get; set; }
+ public int Weight { get; set; }
+ public int Melee { get; set; }
+ public int Speed { get; set; }
+
+ public override bool Equals(object obj)
+ {
+ return Equals(obj as StatPoints);
+ }
+
+ public bool Equals(StatPoints other)
+ {
+ return other != null &&
+ Health == other.Health &&
+ Stamina == other.Stamina &&
+ Weight == other.Weight &&
+ Melee == other.Melee &&
+ Speed == other.Speed;
+ }
+
+ public override int GetHashCode()
+ {
+ var hashCode = -1014974063;
+ hashCode = hashCode * -1521134295 + Health.GetHashCode();
+ hashCode = hashCode * -1521134295 + Stamina.GetHashCode();
+ hashCode = hashCode * -1521134295 + Weight.GetHashCode();
+ hashCode = hashCode * -1521134295 + Melee.GetHashCode();
+ hashCode = hashCode * -1521134295 + Speed.GetHashCode();
+ return hashCode;
+ }
+
+ public override string ToString()
+ {
+ return $"{Health}/{Stamina}/{Weight}/{Melee}/{Speed}";
+ }
+
+ public string ToString(bool fixedWidth = false)
+ {
+ if (fixedWidth)
+ return $"{Health,2}/{Stamina,2}/{Weight,2}/{Melee,2}/{Speed,2}";
+ else
+ return ToString();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Larkator.sln b/Larkator.sln
new file mode 100644
index 0000000..627ef70
--- /dev/null
+++ b/Larkator.sln
@@ -0,0 +1,102 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.27130.2010
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Larkator", "Larkator\Larkator.csproj", "{01608968-3F09-4C38-8866-69E91BA119D5}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LarkatorGUI", "LarkatorGUI\LarkatorGUI.csproj", "{755C5448-DA6F-4FF2-9C53-65D70A180CCB}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Common", "Common\Common.csproj", "{3C4C5A9E-9165-4074-AE85-DF62931457EB}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ListViewLayout", "ListViewLayout\ListViewLayout.csproj", "{916A9879-3F2D-471F-AAFA-28F0C8A7FFA9}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{084A8862-6DEB-4188-9A7F-B587D700B59D}"
+ ProjectSection(SolutionItems) = preProject
+ README.md = README.md
+ EndProjectSection
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|ARM = Debug|ARM
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|ARM = Release|ARM
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {01608968-3F09-4C38-8866-69E91BA119D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {01608968-3F09-4C38-8866-69E91BA119D5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {01608968-3F09-4C38-8866-69E91BA119D5}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {01608968-3F09-4C38-8866-69E91BA119D5}.Debug|ARM.Build.0 = Debug|Any CPU
+ {01608968-3F09-4C38-8866-69E91BA119D5}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {01608968-3F09-4C38-8866-69E91BA119D5}.Debug|x64.Build.0 = Debug|Any CPU
+ {01608968-3F09-4C38-8866-69E91BA119D5}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {01608968-3F09-4C38-8866-69E91BA119D5}.Debug|x86.Build.0 = Debug|Any CPU
+ {01608968-3F09-4C38-8866-69E91BA119D5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {01608968-3F09-4C38-8866-69E91BA119D5}.Release|Any CPU.Build.0 = Release|Any CPU
+ {01608968-3F09-4C38-8866-69E91BA119D5}.Release|ARM.ActiveCfg = Release|Any CPU
+ {01608968-3F09-4C38-8866-69E91BA119D5}.Release|ARM.Build.0 = Release|Any CPU
+ {01608968-3F09-4C38-8866-69E91BA119D5}.Release|x64.ActiveCfg = Release|Any CPU
+ {01608968-3F09-4C38-8866-69E91BA119D5}.Release|x64.Build.0 = Release|Any CPU
+ {01608968-3F09-4C38-8866-69E91BA119D5}.Release|x86.ActiveCfg = Release|Any CPU
+ {01608968-3F09-4C38-8866-69E91BA119D5}.Release|x86.Build.0 = Release|Any CPU
+ {755C5448-DA6F-4FF2-9C53-65D70A180CCB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {755C5448-DA6F-4FF2-9C53-65D70A180CCB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {755C5448-DA6F-4FF2-9C53-65D70A180CCB}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {755C5448-DA6F-4FF2-9C53-65D70A180CCB}.Debug|ARM.Build.0 = Debug|Any CPU
+ {755C5448-DA6F-4FF2-9C53-65D70A180CCB}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {755C5448-DA6F-4FF2-9C53-65D70A180CCB}.Debug|x64.Build.0 = Debug|Any CPU
+ {755C5448-DA6F-4FF2-9C53-65D70A180CCB}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {755C5448-DA6F-4FF2-9C53-65D70A180CCB}.Debug|x86.Build.0 = Debug|Any CPU
+ {755C5448-DA6F-4FF2-9C53-65D70A180CCB}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {755C5448-DA6F-4FF2-9C53-65D70A180CCB}.Release|Any CPU.Build.0 = Release|Any CPU
+ {755C5448-DA6F-4FF2-9C53-65D70A180CCB}.Release|ARM.ActiveCfg = Release|Any CPU
+ {755C5448-DA6F-4FF2-9C53-65D70A180CCB}.Release|ARM.Build.0 = Release|Any CPU
+ {755C5448-DA6F-4FF2-9C53-65D70A180CCB}.Release|x64.ActiveCfg = Release|Any CPU
+ {755C5448-DA6F-4FF2-9C53-65D70A180CCB}.Release|x64.Build.0 = Release|Any CPU
+ {755C5448-DA6F-4FF2-9C53-65D70A180CCB}.Release|x86.ActiveCfg = Release|Any CPU
+ {755C5448-DA6F-4FF2-9C53-65D70A180CCB}.Release|x86.Build.0 = Release|Any CPU
+ {3C4C5A9E-9165-4074-AE85-DF62931457EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3C4C5A9E-9165-4074-AE85-DF62931457EB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3C4C5A9E-9165-4074-AE85-DF62931457EB}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {3C4C5A9E-9165-4074-AE85-DF62931457EB}.Debug|ARM.Build.0 = Debug|Any CPU
+ {3C4C5A9E-9165-4074-AE85-DF62931457EB}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {3C4C5A9E-9165-4074-AE85-DF62931457EB}.Debug|x64.Build.0 = Debug|Any CPU
+ {3C4C5A9E-9165-4074-AE85-DF62931457EB}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {3C4C5A9E-9165-4074-AE85-DF62931457EB}.Debug|x86.Build.0 = Debug|Any CPU
+ {3C4C5A9E-9165-4074-AE85-DF62931457EB}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3C4C5A9E-9165-4074-AE85-DF62931457EB}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3C4C5A9E-9165-4074-AE85-DF62931457EB}.Release|ARM.ActiveCfg = Release|Any CPU
+ {3C4C5A9E-9165-4074-AE85-DF62931457EB}.Release|ARM.Build.0 = Release|Any CPU
+ {3C4C5A9E-9165-4074-AE85-DF62931457EB}.Release|x64.ActiveCfg = Release|Any CPU
+ {3C4C5A9E-9165-4074-AE85-DF62931457EB}.Release|x64.Build.0 = Release|Any CPU
+ {3C4C5A9E-9165-4074-AE85-DF62931457EB}.Release|x86.ActiveCfg = Release|Any CPU
+ {3C4C5A9E-9165-4074-AE85-DF62931457EB}.Release|x86.Build.0 = Release|Any CPU
+ {916A9879-3F2D-471F-AAFA-28F0C8A7FFA9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {916A9879-3F2D-471F-AAFA-28F0C8A7FFA9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {916A9879-3F2D-471F-AAFA-28F0C8A7FFA9}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {916A9879-3F2D-471F-AAFA-28F0C8A7FFA9}.Debug|ARM.Build.0 = Debug|Any CPU
+ {916A9879-3F2D-471F-AAFA-28F0C8A7FFA9}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {916A9879-3F2D-471F-AAFA-28F0C8A7FFA9}.Debug|x64.Build.0 = Debug|Any CPU
+ {916A9879-3F2D-471F-AAFA-28F0C8A7FFA9}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {916A9879-3F2D-471F-AAFA-28F0C8A7FFA9}.Debug|x86.Build.0 = Debug|Any CPU
+ {916A9879-3F2D-471F-AAFA-28F0C8A7FFA9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {916A9879-3F2D-471F-AAFA-28F0C8A7FFA9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {916A9879-3F2D-471F-AAFA-28F0C8A7FFA9}.Release|ARM.ActiveCfg = Release|Any CPU
+ {916A9879-3F2D-471F-AAFA-28F0C8A7FFA9}.Release|ARM.Build.0 = Release|Any CPU
+ {916A9879-3F2D-471F-AAFA-28F0C8A7FFA9}.Release|x64.ActiveCfg = Release|Any CPU
+ {916A9879-3F2D-471F-AAFA-28F0C8A7FFA9}.Release|x64.Build.0 = Release|Any CPU
+ {916A9879-3F2D-471F-AAFA-28F0C8A7FFA9}.Release|x86.ActiveCfg = Release|Any CPU
+ {916A9879-3F2D-471F-AAFA-28F0C8A7FFA9}.Release|x86.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {FFF01257-EF0B-47A2-9A50-DC84DB8A4618}
+ EndGlobalSection
+EndGlobal
diff --git a/Larkator/ArkToolsConverter.cs b/Larkator/ArkToolsConverter.cs
new file mode 100644
index 0000000..42a9b7f
--- /dev/null
+++ b/Larkator/ArkToolsConverter.cs
@@ -0,0 +1,33 @@
+using Larkator.Common;
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace Larkator
+{
+ public class ArkToolsConverter
+ {
+ public bool IsConversionRequired()
+ {
+ throw new NotImplementedException();
+ }
+
+ public async Task PerformConversion()
+ {
+ await Task.CompletedTask;
+ throw new NotImplementedException();
+ }
+
+ public async Task> LoadClasses()
+ {
+ await Task.CompletedTask;
+ throw new NotImplementedException();
+ }
+
+ public async Task> LoadSpecies()
+ {
+ await Task.CompletedTask;
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/Larkator/CommandLineParameters.cs b/Larkator/CommandLineParameters.cs
new file mode 100644
index 0000000..7d3d540
--- /dev/null
+++ b/Larkator/CommandLineParameters.cs
@@ -0,0 +1,25 @@
+using Larkator.Common;
+using System.Collections.Generic;
+
+namespace Larkator
+{
+ public class CommandLineParameters
+ {
+ public bool ShowHelp { get; set; } = false;
+ public bool Quiet { get; set; } = false;
+ public string ArkToolsPath { get; set; }
+ public List Creatures { get; set; } = new List();
+ public string SaveFile { get; set; }
+ public PositionFormat PositionFormat { get; set; } = PositionFormat.LatLong;
+ public bool? Female { get; set; }
+ public int? MinLevel { get; set; }
+ public int? MaxLevel { get; set; }
+ public string TempDir { get; set; }
+ public bool DryRun { get; set; } = false;
+ public bool ShowNameMatchingResults { get; set; } = false;
+ public bool SkipConversion { get; set; } = false;
+ public bool SkipClear { get; set; } = false;
+ public double? TamingSpeedMultiplier { get; internal set; }
+ public double? TamingFoodRateMultiplier { get; internal set; }
+ }
+}
\ No newline at end of file
diff --git a/Larkator/FuzzyExtensions.cs b/Larkator/FuzzyExtensions.cs
new file mode 100644
index 0000000..b4d0bc8
--- /dev/null
+++ b/Larkator/FuzzyExtensions.cs
@@ -0,0 +1,14 @@
+using DuoVia.FuzzyStrings;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Larkator
+{
+ public static class FuzzyExtensions
+ {
+ public static string BestFuzzyMatch(this IEnumerable source, string target)
+ {
+ return source.Select(s => new { Score = s.FuzzyMatch(target), Value = s }).OrderByDescending(v => v.Score).First().Value;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Larkator/Larkator.csproj b/Larkator/Larkator.csproj
new file mode 100644
index 0000000..9a2f43b
--- /dev/null
+++ b/Larkator/Larkator.csproj
@@ -0,0 +1,18 @@
+
+
+
+ Exe
+ netcoreapp2.0
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Larkator/Program.cs b/Larkator/Program.cs
new file mode 100644
index 0000000..81e04e1
--- /dev/null
+++ b/Larkator/Program.cs
@@ -0,0 +1,376 @@
+using DuoVia.FuzzyStrings;
+using Larkator.Common;
+using Mono.Options;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace Larkator
+{
+ class Program
+ {
+ public const string PROGRAM_NAME = "Larkator";
+
+ const string ARK_TOOLS_EXE = "ark-tools.exe";
+ const double FUZZY_TOO_SIMILAR_PCT = 0.95;
+ const double FUZZY_THRESHOLD = 0.25;
+ const double FUZZY_SHOW_THRESHOLD = 0.15;
+ private const string CLASSES_JSON = "classes.json";
+ readonly int[] ColumnWidths = new int[] { 4, 6, 18, 12 };
+
+ public CommandLineParameters Options { get; private set; }
+ public JArray Species { get; private set; }
+ public string[] SpeciesNames { get; private set; }
+ public ConcurrentDictionary Results { get; private set; } = new ConcurrentDictionary();
+ public Dictionary ClassMapping { get; private set; }
+ public List SearchNames { get; private set; }
+
+ static IReadOnlyDictionary DefaultConfiguration { get; } = new Dictionary()
+ {
+ ["arkFile"] = null,
+ };
+
+ static void Main(string[] args)
+ {
+ var main = new Program();
+ Task.WaitAll(main.Execute(args));
+ }
+
+ private Program()
+ {
+ //this.Config = config;
+ }
+
+ private async Task Execute(string[] args)
+ {
+ Console.WriteLine($"{PROGRAM_NAME}: Dino Locator");
+
+ HandleCommandLine(args);
+
+ await ConvertArkIfRequiredAsync();
+ GuessSearchNames();
+ await PerformAllSearchesAsync();
+
+ DisplayResults();
+ }
+
+ private void HandleCommandLine(string[] args)
+ {
+ Options = new CommandLineParameters();
+ var optionDefinitions = new OptionSet {
+ { "h|help", "show this message and exit", h => Options.ShowHelp = h != null },
+ { "d|dryrun", "do not execute anything, just print what would be done", v => Options.DryRun = (v != null) },
+ { "q|quiet", "reduce output, only outputting results", v => Options.Quiet = (v != null) },
+ { "t|arktools=", "specify the path to the arktools directory", (string p) => Options.ArkToolsPath = p },
+ { "f|savefile=", "specify the path to the ARK save file", (string p) => Options.SaveFile = p },
+ { "xyz", "write locations using x,y,z format for teleport", v => Options.PositionFormat = (v != null) ? PositionFormat.XYZ : Options.PositionFormat },
+ { "latlon", "write locations using map lat,long format", v => Options.PositionFormat = (v != null) ? PositionFormat.LatLong : Options.PositionFormat },
+ { "tmp=", "specify the path to store temporary data output from arktools", (string p) => Options.TempDir = p },
+ { "s|gender=", "specify the gender of the dinosaur to find", (string s) => Options.Female = ParseGender(s) },
+ { "l|minlevel=", "specify the minimum level of the dinosaur to find", (int l) => Options.MinLevel = l },
+ { "m|maxlevel=", "specify the maximum level of the dinosaur to find", (int l) => Options.MaxLevel = l },
+ { "tamingspeed=", "the taming speed multiplier of your ARK", (double m) => Options.TamingSpeedMultiplier = m },
+ { "tamingfoodrate=", "the taming food rate multiplier of your ARK", (double m) => Options.TamingFoodRateMultiplier = m },
+ { "showmatches", "show debug information about name matching", v => Options.ShowNameMatchingResults = (v != null) },
+ { "<>", "list of creatures to track", (string c) => Options.Creatures.Add(c) }
+ };
+
+ try
+ {
+ optionDefinitions.Parse(args);
+
+ if (Options.ShowHelp)
+ {
+ ShowHelp(optionDefinitions);
+ Abort();
+ }
+
+ if (Options.Creatures.Count < 1) throw new OptionException("No creatures specified", "<>");
+
+ VerifyArkTools();
+ VerifySavedArk();
+ VerifyTempDir();
+ }
+ catch (OptionException e)
+ {
+ Console.Write($"{PROGRAM_NAME}: ");
+ Console.WriteLine(e.Message);
+ Console.WriteLine($"Try `{PROGRAM_NAME} --help' for more information.");
+ Abort();
+ }
+ }
+
+ private async Task ConvertArkIfRequiredAsync()
+ {
+ if (IsConvertRequired())
+ {
+ await ConvertArkAsync();
+ }
+
+ await LoadCreatureIndexAsync();
+ }
+
+ private void GuessSearchNames()
+ {
+ SearchNames = Options.Creatures.Select(ParseCreature).ToList();
+ if (SearchNames.Any(n => String.IsNullOrWhiteSpace(n)))
+ Abort(); // error already shown
+ }
+
+ private void Abort()
+ {
+ Process.GetCurrentProcess().Kill();
+ }
+
+ private void WriteLine(string msg)
+ {
+ if (!Options.Quiet) Console.WriteLine(msg);
+ }
+
+ private bool IsConvertRequired()
+ {
+ var classFile = Path.Combine(Options.TempDir, CLASSES_JSON);
+ if (!File.Exists(classFile)) return true;
+ var arkTimestamp = File.GetLastWriteTimeUtc(Options.SaveFile);
+ var convertTimestamp = File.GetLastWriteTimeUtc(classFile);
+ return (arkTimestamp >= convertTimestamp);
+ }
+
+ private Task ConvertArkAsync()
+ {
+ WriteLine("Reading ARK using ARKTools...");
+ return Task.Run(() => ExecuteArkTools($"-s -p wild \"{Options.SaveFile}\" \"{Options.TempDir}\""));
+ }
+
+ private void ClearTemp()
+ {
+ foreach (var file in Directory.GetFiles(Options.TempDir))
+ File.Delete(file);
+ }
+
+ private async Task LoadCreatureIndexAsync()
+ {
+ var classesOutput = JArray.Parse(await File.ReadAllTextAsync(Path.Combine(Options.TempDir, CLASSES_JSON)));
+ ClassMapping = classesOutput
+ .Select(mapping => new { Cls = mapping["cls"].Value(), Name = mapping["name"].Value() })
+ .Where(m => !m.Cls.Contains("BP_Ocean_C"))
+ .ToDictionary(m => m.Name, m => m.Cls);
+
+ SpeciesNames = ClassMapping.Keys.ToArray();
+ }
+
+ private async Task PerformAllSearchesAsync()
+ {
+ var searchTasks = SearchNames.Select(speciesName => PerformSearchAsync(speciesName));
+ await Task.WhenAll(searchTasks);
+ }
+
+ private async Task PerformSearchAsync(string speciesNameFromDb)
+ {
+ IEnumerable dinos = await LoadSpeciesAsync(speciesNameFromDb);
+
+ // Apply search criteria
+ if (Options.MinLevel.HasValue) dinos = dinos.Where(dino => dino.BaseLevel >= Options.MinLevel);
+ if (Options.MaxLevel.HasValue) dinos = dinos.Where(dino => dino.BaseLevel <= Options.MaxLevel);
+ if (Options.Female.HasValue) dinos = dinos.Where(dino => dino.Female == Options.Female);
+
+ // Store the results
+ Results[speciesNameFromDb] = new SearchCompletion { ValuesName = speciesNameFromDb, Dinos = dinos.ToList() };
+ }
+
+ private async Task> LoadSpeciesAsync(string speciesNameFromDb)
+ {
+ // Lookup class id
+ var speciesNameFromArkTools = ClassMapping.ContainsKey(speciesNameFromDb) ? speciesNameFromDb : ClassMapping.Keys.BestFuzzyMatch(speciesNameFromDb);
+ var speciesId = ClassMapping[speciesNameFromArkTools];
+
+ //Console.WriteLine($"Reading {classId}...");
+
+ // Read class file
+ var contents = await File.ReadAllTextAsync(Path.Combine(Options.TempDir, speciesId + ".json"));
+ var dinos = JsonConvert.DeserializeObject>(contents);
+
+ return dinos;
+ }
+
+ private void DisplayResults()
+ {
+ TableWrite("Sex", "Lvl", "Hp/St/We/Me/Sp", "Position");
+
+ foreach (var searchName in SearchNames)
+ {
+ var result = Results[searchName];
+
+ Console.WriteLine(result.ValuesName + ":");
+
+ var orderedResults = result.Dinos.OrderByDescending(dino => dino.BaseLevel);
+ var empty = true;
+ foreach (var dino in orderedResults)
+ {
+ empty = false;
+ TableWrite((dino.Female ? "F" : "M"), dino.BaseLevel.ToString(), dino.WildLevels.ToString(true), dino.Location.ToString(Options.PositionFormat));
+ }
+ if (empty) Console.WriteLine(" -");
+ }
+ }
+
+ private void TableWrite(params string[] columns)
+ {
+ Console.WriteLine(" " + String.Join("", columns.Zip(ColumnWidths, (text, width) => text.PadRight(width))));
+ }
+
+ private async Task ExecuteArkTools(string args)
+ {
+ if (Options.DryRun)
+ {
+ Console.WriteLine(Options.ArkToolsPath + " " + args);
+ }
+ else
+ {
+ var psi = new ProcessStartInfo("cmd.exe", $"/C \"cd {Options.ArkToolsPath} && {ARK_TOOLS_EXE} {args}\"")
+ {
+ CreateNoWindow = true,
+ UseShellExecute = false,
+ };
+ var proc = new Process { StartInfo = psi };
+ var completionTask = new TaskCompletionSource();
+ var errorOutput = "";
+ proc.Exited += (s, e) => completionTask.SetResult(proc.ExitCode);
+ proc.ErrorDataReceived += (s, e) => { if (!String.IsNullOrWhiteSpace(e.Data)) errorOutput += e.Data; };
+ proc.BeginErrorReadLine();
+ proc.Start();
+ proc.CancelErrorRead();
+ await completionTask.Task;
+ if (proc.ExitCode != 0) throw new InvalidOperationException("ARK Tools failed with exit code " + proc.ExitCode);
+ if (!String.IsNullOrWhiteSpace(errorOutput)) throw new InvalidOperationException("ARK Tools failed with error: " + errorOutput);
+ }
+ }
+
+ private void DisplaySearchCriteria()
+ {
+ Console.WriteLine("Search criteria:");
+ if (Options.MinLevel.HasValue) Console.WriteLine($" Min level: {Options.MinLevel}");
+ if (Options.MaxLevel.HasValue) Console.WriteLine($" Max level: {Options.MaxLevel}");
+ if (Options.Female.HasValue) Console.WriteLine($" Gender: {(Options.Female.Value ? "Female" : "Male")}");
+ }
+
+#if OBSOLETE
+ private async Task LoadDatabaseAsync()
+ {
+ database = JObject.Parse(await File.ReadAllTextAsync("values.json"));
+ Species = database.species;
+ SpeciesNames = Species.Select(species => ((string)species["name"])).ToArray();
+ WriteLine($"Database version {database.ver} contains {database.species.Count} species");
+ }
+#endif
+
+ private bool ParseGender(string input)
+ {
+ switch (input.ToLowerInvariant())
+ {
+ case "m":
+ case "male":
+ return false;
+ case "f":
+ case "fem":
+ case "female":
+ return true;
+ default:
+ throw new OptionException("Could not parse gender", "sex");
+ }
+ }
+
+#region Commandline handling
+ private static void ShowHelp(OptionSet optionDefinitions)
+ {
+ Console.WriteLine($"Usage: {PROGRAM_NAME} [OPTIONS] ( ...)");
+ Console.WriteLine("Find wild dinos within your ARK");
+ Console.WriteLine($"e.g. {PROGRAM_NAME} -t -s -l50 -sF Quetz");
+ Console.WriteLine();
+
+ // output the options
+ Console.WriteLine("Options:");
+ optionDefinitions.WriteOptionDescriptions(Console.Out);
+ }
+
+ private string ParseCreature(string shortName)
+ {
+ var matches = SpeciesNames.Select((name, i) => new { Score = name.FuzzyMatch(shortName), Index = i, Name = name })
+ .Where(result => result.Score > FUZZY_SHOW_THRESHOLD)
+ .OrderByDescending(result => result.Score).Take(5).ToArray();
+
+ if (Options.ShowNameMatchingResults)
+ {
+ Console.WriteLine($"Matches for '{shortName}':");
+ foreach (var result in matches) Console.WriteLine($" {result.Name} ({result.Score})");
+ }
+
+ var goodMatches = matches.Where(result => result.Score >= FUZZY_THRESHOLD).ToArray();
+
+ if (goodMatches.Length == 0 || goodMatches.Count(result => result.Score >= goodMatches.First().Score * FUZZY_TOO_SIMILAR_PCT) > 1)
+ {
+ Console.WriteLine($"Unable to match '{shortName}': {String.Join(", ", matches.Select(result => result.Name))}?");
+ return null;
+ }
+
+ var bestMatch = goodMatches.First().Index;
+ return SpeciesNames[bestMatch];
+ }
+
+ private void VerifyArkTools()
+ {
+ if (!Directory.Exists(Options.ArkToolsPath))
+ throw new OptionException("ARK Tools directory not found", "arktools");
+
+ if (!File.Exists(Path.Combine(Options.ArkToolsPath, ARK_TOOLS_EXE)))
+ throw new OptionException("ARK Tools executable not found within specified directory", "arktools");
+
+ Options.ArkToolsPath = Path.GetFullPath(Options.ArkToolsPath);
+ }
+
+ private void VerifySavedArk()
+ {
+ if (!File.Exists(Options.SaveFile))
+ throw new OptionException("Saved ARK file not found", "arktools");
+
+ Options.SaveFile = Path.GetFullPath(Options.SaveFile);
+ }
+
+ private void VerifyTempDir()
+ {
+ // Set to default if not set
+ if (String.IsNullOrWhiteSpace(Options.TempDir)) Options.TempDir = Path.Combine(Path.GetTempPath(), PROGRAM_NAME);
+
+ // Try to create temp dir if it doesn't already exist
+ if (!Directory.Exists(Options.TempDir))
+ {
+ try
+ {
+ if (Options.DryRun)
+ WriteLine("Creating temp directory: " + Options.TempDir);
+ else
+ Directory.CreateDirectory(Options.TempDir);
+ }
+ catch (Exception ex)
+ {
+ throw new OptionException("Unable to create temp directory: " + ex.Message, "tmp");
+ }
+ }
+
+ //Console.WriteLine("Using temp directory: " + Options.TempDir);
+ }
+
+ public class SearchCompletion
+ {
+ public string ValuesName { get; set; }
+ public List Dinos { get; set; }
+ }
+#endregion
+ }
+}
\ No newline at end of file
diff --git a/Larkator/SearchResult.cs b/Larkator/SearchResult.cs
new file mode 100644
index 0000000..ae77d3d
--- /dev/null
+++ b/Larkator/SearchResult.cs
@@ -0,0 +1,12 @@
+using Larkator.Common;
+using System.Collections.Generic;
+
+namespace Larkator
+{
+ public class SearchResult
+ {
+ public string ValuesName { get; set; }
+ //public string ClassName { get; set; }
+ public List Dinos { get; set; } = new List();
+ }
+}
\ No newline at end of file
diff --git a/Larkator/runit.bat b/Larkator/runit.bat
new file mode 100644
index 0000000..86ba577
--- /dev/null
+++ b/Larkator/runit.bat
@@ -0,0 +1 @@
+dotnet Larkator.dll -q -t C:\Programs\ARKTools -f D:\SteamLibrary\steamapps\common\ARK\ShooterGame\Saved\SavedArksLocal\TheIsland.ark -l100 Allo Rex Megath Direb
diff --git a/LarkatorGUI/App.xaml b/LarkatorGUI/App.xaml
new file mode 100644
index 0000000..4787db7
--- /dev/null
+++ b/LarkatorGUI/App.xaml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
diff --git a/LarkatorGUI/App.xaml.cs b/LarkatorGUI/App.xaml.cs
new file mode 100644
index 0000000..6eaf0e2
--- /dev/null
+++ b/LarkatorGUI/App.xaml.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Configuration;
+using System.Data;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Windows;
+
+namespace LarkatorGUI
+{
+ ///
+ /// Interaction logic for App.xaml
+ ///
+ public partial class App : Application
+ {
+ protected override void OnStartup(StartupEventArgs e)
+ {
+ base.OnStartup(e);
+ }
+ }
+}
diff --git a/LarkatorGUI/ArkReader.cs b/LarkatorGUI/ArkReader.cs
new file mode 100644
index 0000000..6eeee51
--- /dev/null
+++ b/LarkatorGUI/ArkReader.cs
@@ -0,0 +1,159 @@
+using Larkator.Common;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace LarkatorGUI
+{
+ public class ArkReader
+ {
+ private const string JSON_SUFFIX = ".json";
+
+ public List LoadedSpecies { get; private set; } = new List();
+ public Dictionary> FoundDinos = new Dictionary>();
+ public List AllSpecies { get { return ClassMapping.Keys.OrderBy(k => k).ToList(); } }
+ public int NumberOfSpecies { get { return ClassMapping.Count; } }
+ public bool Tamed { get; private set; }
+
+ Dictionary ClassMapping = new Dictionary();
+ private Process process;
+ private bool executing;
+ private string outputDir;
+
+ public ArkReader(bool wildNotTamed)
+ {
+ Tamed = !wildNotTamed;
+ }
+
+ private void EnsureDirectory(string outputDir)
+ {
+ if (!Directory.Exists(outputDir)) Directory.CreateDirectory(outputDir);
+ }
+
+ public async Task PerformConversion(bool force = false)
+ {;
+ outputDir = Path.Combine(Properties.Settings.Default.OutputDir, Tamed ? "tamed" : "wild");
+ EnsureDirectory(outputDir);
+
+ if (force || IsConversionRequired())
+ await RunArkTools();
+
+ if (ClassMapping.Count == 0)
+ await LoadClassesJson();
+ }
+
+ private bool IsConversionRequired()
+ {
+ var classFile = Path.Combine(outputDir, Properties.Resources.ClassesJson);
+ if (!File.Exists(classFile)) return true;
+ var arkTimestamp = File.GetLastWriteTimeUtc(Properties.Settings.Default.SaveFile);
+ var convertTimestamp = File.GetLastWriteTimeUtc(classFile);
+ return (arkTimestamp >= convertTimestamp);
+ }
+
+ public async Task EnsureSpeciesIsLoaded(string speciesName)
+ {
+ if (LoadedSpecies.Contains(speciesName)) return;
+ await LoadSpecies(speciesName);
+ }
+
+ private async Task RunArkTools()
+ {
+ if (executing)
+ {
+ Console.WriteLine("Skipping concurrent conversion");
+ return;
+ }
+
+ executing = true;
+ await ExecuteArkTools(Tamed ? "tamed" : "wild", Properties.Settings.Default.SaveFile, outputDir);
+ executing = false;
+
+ // Clear previously loaded data
+ ClassMapping.Clear();
+ LoadedSpecies.Clear();
+ FoundDinos.Clear();
+ }
+
+ private async Task ExecuteArkTools(string op, string saveFile, string outDir)
+ {
+ var exe = Properties.Resources.ArkToolsExe;
+ var exeDir = Path.GetDirectoryName(Properties.Settings.Default.ArkTools);
+ var commandLine = $"/S /C \"cd \"{exeDir}\" && {exe} -p {op} \"{saveFile}\" \"{outDir}\" \"";
+
+ var completionTask = new TaskCompletionSource();
+ var psi = new ProcessStartInfo("cmd.exe", commandLine)
+ {
+ CreateNoWindow = true,
+ UseShellExecute = false,
+ RedirectStandardError = true,
+ RedirectStandardOutput = true,
+ ErrorDialog = true,
+ };
+ process = new Process
+ {
+ EnableRaisingEvents = true,
+ StartInfo = psi,
+ };
+
+ string collectedErrors = "";
+ process.ErrorDataReceived += (s, e) => { if (!String.IsNullOrWhiteSpace(e.Data)) collectedErrors += e.Data; };
+ process.Exited += (s, e) => completionTask.SetResult(process.ExitCode);
+ process.Start();
+ process.BeginErrorReadLine();
+ await completionTask.Task;
+ process.CancelErrorRead();
+
+ if (!String.IsNullOrWhiteSpace(collectedErrors))
+ throw new ExternalToolsException(collectedErrors);
+
+ if (process.ExitCode != 0)
+ throw new ExternalToolsException("ARK Tools failed with no output but exit code " + process.ExitCode);
+ }
+
+ private async Task LoadClassesJson()
+ {
+ string content = await ReadFileAsync(Path.Combine(outputDir, Properties.Resources.ClassesJson));
+ var classesOutput = JArray.Parse(content);
+ ClassMapping = classesOutput
+ .Select(mapping => new { Cls = mapping["cls"].Value(), Name = mapping["name"].Value() })
+ .Where(m => !m.Cls.Contains("BP_Ocean_C"))
+ .ToDictionary(m => m.Name, m => m.Cls);
+ }
+
+ private async Task LoadSpecies(string speciesName)
+ {
+ // Lookup class id
+ var speciesId = ClassMapping[speciesName];
+
+ // Read class file
+ var path = Path.Combine(outputDir, speciesId + JSON_SUFFIX);
+ if (File.Exists(path))
+ {
+ var contents = await ReadFileAsync(path);
+ FoundDinos[speciesName] = JsonConvert.DeserializeObject>(contents);
+ }
+ else
+ {
+ FoundDinos[speciesName] = new List();
+ }
+ LoadedSpecies.Add(speciesName);
+ }
+
+ private async Task ReadFileAsync(string path)
+ {
+ string content;
+ using (var reader = File.OpenText(path))
+ {
+ content = await reader.ReadToEndAsync();
+ }
+
+ return content;
+ }
+ }
+}
diff --git a/LarkatorGUI/Converters.cs b/LarkatorGUI/Converters.cs
new file mode 100644
index 0000000..798d007
--- /dev/null
+++ b/LarkatorGUI/Converters.cs
@@ -0,0 +1,157 @@
+using Larkator.Common;
+using System;
+using System.Globalization;
+using System.Windows;
+using System.Windows.Data;
+
+namespace LarkatorGUI
+{
+ public class BoolToVisibilityConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ var v = (bool)value;
+ return v ? Visibility.Visible : Visibility.Collapsed;
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ public class PositionToStringConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ var pos = (Position)value;
+ return (pos == null) ? "" : pos.ToString(PositionFormat.LatLong);
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ public class StatsToStringConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ var stats = (StatPoints)value;
+ return (stats == null) ? "" : stats.ToString(true);
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ public class PositionToXConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ var pos = (Position)value;
+ return pos.Lon * 14.175 + 18.25;
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ public class PositionToYConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ var pos = (Position)value;
+ return pos.Lat * 13.7875 + 34.125;
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ public class GenderToImageConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ bool? gender = (bool?)value;
+ if (!gender.HasValue) return "imgs/nogender.png";
+ if (gender.Value)
+ return "imgs/female.png";
+ else
+ return "imgs/male.png";
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ public class GenderToLetterConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ bool? gender = (bool?)value;
+ if (!gender.HasValue) return "";
+ if (gender.Value)
+ return "F";
+ else
+ return "M";
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ public class SearchResultToTooltipConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ var v = (SearchResult)value;
+ return $"{v.Species} {(v.Female ? 'F' : 'M')}{v.Level} @ {v.Location.ToString(PositionFormat.LatLong)}";
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ public class DinoToTooltipConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ var v = (Dino)value;
+ return $"{(String.IsNullOrWhiteSpace(v.Name) ? "" : "\"" + v.Name + "\" ")}{v.Type} {(v.Female ? 'F' : 'M')}{v.BaseLevel} @ {v.Location.ToString(PositionFormat.LatLong)} ({v.Location.ToString(PositionFormat.XYZ)})";
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ public class OptionalIntConverter : IValueConverter
+ {
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ var v = (int?)value;
+ var def = (string)parameter;
+ return v.HasValue ? v.Value.ToString() : def;
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/LarkatorGUI/DinoViewModel.cs b/LarkatorGUI/DinoViewModel.cs
new file mode 100644
index 0000000..4cfe49b
--- /dev/null
+++ b/LarkatorGUI/DinoViewModel.cs
@@ -0,0 +1,57 @@
+using Larkator.Common;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Media;
+
+namespace LarkatorGUI
+{
+ public class DinoViewModel : DependencyObject
+ {
+ public DinoViewModel(Dino dino)
+ {
+ Dino = dino;
+ }
+
+ public Dino Dino
+ {
+ get { return (Dino)GetValue(DinoProperty); }
+ set { SetValue(DinoProperty, value); }
+ }
+
+ public bool Highlight
+ {
+ get { return (bool)GetValue(HighlightProperty); }
+ set { SetValue(HighlightProperty, value); }
+ }
+
+ public Color Color
+ {
+ get { return (Color)GetValue(ColorProperty); }
+ set { SetValue(ColorProperty, value); }
+ }
+
+ public static readonly DependencyProperty ColorProperty =
+ DependencyProperty.Register("Color", typeof(Color), typeof(DinoViewModel), new PropertyMetadata(Colors.Red));
+
+ public static readonly DependencyProperty HighlightProperty =
+ DependencyProperty.Register("Highlight", typeof(bool), typeof(DinoViewModel), new PropertyMetadata(false));
+
+ public static readonly DependencyProperty DinoProperty =
+ DependencyProperty.Register("Dino", typeof(Dino), typeof(DinoViewModel), new PropertyMetadata(null));
+
+
+ public static implicit operator Dino(DinoViewModel vm)
+ {
+ return vm.Dino;
+ }
+
+ public static implicit operator DinoViewModel(Dino dino)
+ {
+ return new DinoViewModel(dino);
+ }
+ }
+}
diff --git a/LarkatorGUI/ExternalToolsException.cs b/LarkatorGUI/ExternalToolsException.cs
new file mode 100644
index 0000000..e468c7e
--- /dev/null
+++ b/LarkatorGUI/ExternalToolsException.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Runtime.Serialization;
+
+namespace LarkatorGUI
+{
+ public class ExternalToolsException : ApplicationException
+ {
+ public ExternalToolsException()
+ {
+ }
+
+ public ExternalToolsException(string message) : base(message)
+ {
+ }
+
+ public ExternalToolsException(string message, Exception innerException) : base(message, innerException)
+ {
+ }
+
+ protected ExternalToolsException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ }
+ }
+}
diff --git a/LarkatorGUI/LarkatorGUI.csproj b/LarkatorGUI/LarkatorGUI.csproj
new file mode 100644
index 0000000..0138f46
--- /dev/null
+++ b/LarkatorGUI/LarkatorGUI.csproj
@@ -0,0 +1,198 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {755C5448-DA6F-4FF2-9C53-65D70A180CCB}
+ WinExe
+ LarkatorGUI
+ LarkatorGUI
+ v4.7
+ 512
+ {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ 4
+ true
+ false
+ C:\tmp\publish\Larkator\
+ true
+ Disk
+ false
+ Foreground
+ 7
+ Days
+ false
+ false
+ true
+ Larkator
+ true
+ 1
+ 1.0.0.%2a
+ false
+ true
+ true
+ true
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ AnyCPU
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+ compass.ico
+
+
+ 1D09CA8BF4348B848654A67BFC77EDC80A8220FB
+
+
+ LarkatorGUI_TemporaryKey.pfx
+
+
+ true
+
+
+ LocalIntranet
+
+
+ Properties\app.manifest
+
+
+ false
+
+
+
+ ..\packages\FastMember.Signed.1.1.0\lib\net40\FastMember.Signed.dll
+
+
+ ..\packages\FontAwesome.WPF.4.7.0.9\lib\net40\FontAwesome.WPF.dll
+
+
+ ..\packages\gong-wpf-dragdrop.1.2.0-alpha0057\lib\net46\GongSolutions.WPF.DragDrop.dll
+
+
+ ..\packages\Newtonsoft.Json.11.0.1-beta1\lib\net45\Newtonsoft.Json.dll
+
+
+
+
+
+
+
+
+
+
+
+
+ 4.0
+
+
+
+
+
+
+
+ MSBuild:Compile
+ Designer
+
+
+
+
+
+
+ MainWindow.xaml
+
+
+
+ Welcome.xaml
+
+
+ App.xaml
+ Code
+
+
+
+ Designer
+ MSBuild:Compile
+
+
+ Designer
+ MSBuild:Compile
+
+
+
+
+ Code
+
+
+ True
+ True
+ Resources.resx
+
+
+ True
+ Settings.settings
+ True
+
+
+ ResXFileCodeGenerator
+ Resources.Designer.cs
+
+
+
+
+
+ SettingsSingleFileGenerator
+ Settings.Designer.cs
+
+
+
+
+
+
+
+
+ {3c4c5a9e-9165-4074-ae85-df62931457eb}
+ Common
+
+
+ {916a9879-3f2d-471f-aafa-28f0c8a7ffa9}
+ ListViewLayout
+
+
+
+
+
+
+
+
+
+
+
+
+
+ False
+ Microsoft .NET Framework 4.7 %28x86 and x64%29
+ true
+
+
+ False
+ .NET Framework 3.5 SP1
+ false
+
+
+
+
\ No newline at end of file
diff --git a/LarkatorGUI/ListViewHackyCell.cs b/LarkatorGUI/ListViewHackyCell.cs
new file mode 100644
index 0000000..59e3ffa
--- /dev/null
+++ b/LarkatorGUI/ListViewHackyCell.cs
@@ -0,0 +1,75 @@
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media;
+
+namespace LarkatorGUI
+{
+ ///
+ /// Class allows for reseting hard coded ListViewItem margins and paddings
+ ///
+ public class ListViewHackyCell : Decorator
+ {
+ protected override void OnVisualParentChanged(DependencyObject oldParent)
+ {
+ base.OnVisualParentChanged(oldParent);
+
+ if (VisualTreeHelper.GetParent(this) is FrameworkElement cp)
+ {
+ cp.Margin = Padding;
+ cp.VerticalAlignment = VerticalAlignment;
+ cp.HorizontalAlignment = HorizontalAlignment;
+ }
+
+ ResetGridViewRowPresenterMargin();
+ ResetListViewItemPadding();
+ ResetRowBorderThickness();
+ }
+
+ private T FindInVisualTreeUp() where T : class
+ {
+ DependencyObject result = this;
+ do
+ {
+ result = VisualTreeHelper.GetParent(result);
+ }
+ while (result != null && !(result is T));
+ return result as T;
+ }
+
+ private void ResetGridViewRowPresenterMargin()
+ {
+ var gvrp = FindInVisualTreeUp();
+ if (gvrp != null)
+ gvrp.Margin = new Thickness(0);
+ }
+
+ private void ResetListViewItemPadding()
+ {
+ var lvi = FindInVisualTreeUp();
+ if (lvi != null)
+ lvi.Padding = new Thickness(0);
+ }
+
+ private void ResetRowBorderThickness()
+ {
+ var lvi = FindInVisualTreeUp();
+ if (lvi != null)
+ lvi.BorderThickness = new Thickness(0);
+ }
+
+ ///
+ /// Padding dependency property registration
+ ///
+ public static readonly DependencyProperty PaddingProperty =
+ DependencyProperty.Register("Padding", typeof(Thickness), typeof(ListViewHackyCell), new PropertyMetadata(default(Thickness)));
+
+ ///
+ /// Padding dependency property
+ ///
+ public Thickness Padding
+ {
+ get { return (Thickness)GetValue(PaddingProperty); }
+ set { SetValue(PaddingProperty, value); }
+ }
+ }
+}
diff --git a/LarkatorGUI/MainWindow.xaml b/LarkatorGUI/MainWindow.xaml
new file mode 100644
index 0000000..0a698fd
--- /dev/null
+++ b/LarkatorGUI/MainWindow.xaml
@@ -0,0 +1,393 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Show Tames
+
+
+ Show The Hunt
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/LarkatorGUI/MainWindow.xaml.cs b/LarkatorGUI/MainWindow.xaml.cs
new file mode 100644
index 0000000..62c3ac4
--- /dev/null
+++ b/LarkatorGUI/MainWindow.xaml.cs
@@ -0,0 +1,540 @@
+using FastMember;
+using GongSolutions.Wpf.DragDrop;
+using Larkator.Common;
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Deployment.Application;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Controls.Primitives;
+using System.Windows.Data;
+using System.Windows.Input;
+using System.Windows.Threading;
+
+namespace LarkatorGUI
+{
+ ///
+ /// Interaction logic for MainWindow.xaml
+ ///
+ public partial class MainWindow : Window, IDropTarget
+ {
+ public ObservableCollection ListSearches { get; } = new ObservableCollection();
+ public Collection ListResults { get; } = new Collection();
+ public List AllSpecies { get { return arkReaderWild.AllSpecies; } }
+
+ public string ApplicationVersion
+ {
+ get
+ {
+ try
+ {
+ return ApplicationDeployment.CurrentDeployment.CurrentVersion.ToString();
+ }
+ catch (InvalidDeploymentException)
+ {
+ return "DEVELOPMENT";
+ }
+ }
+ }
+
+ public string WindowTitle { get { return $"{Properties.Resources.ProgramName} {ApplicationVersion}"; } }
+
+ public bool IsLoading
+ {
+ get { return (bool)GetValue(IsLoadingProperty); }
+ set { SetValue(IsLoadingProperty, value); }
+ }
+
+ public string StatusText
+ {
+ get { return (string)GetValue(StatusTextProperty); }
+ set { SetValue(StatusTextProperty, value); }
+ }
+
+ public string StatusDetailText
+ {
+ get { return (string)GetValue(StatusDetailTextProperty); }
+ set { SetValue(StatusDetailTextProperty, value); }
+ }
+
+ public SearchCriteria NewSearch
+ {
+ get { return (SearchCriteria)GetValue(NewSearchProperty); }
+ set { SetValue(NewSearchProperty, value); }
+ }
+
+ public bool CreateSearchAvailable
+ {
+ get { return (bool)GetValue(CreateSearchAvailableProperty); }
+ set { SetValue(CreateSearchAvailableProperty, value); }
+ }
+
+ public bool NewSearchActive
+ {
+ get { return (bool)GetValue(NewSearchActiveProperty); }
+ set { SetValue(NewSearchActiveProperty, value); }
+ }
+
+ public bool ShowHunt
+ {
+ get { return (bool)GetValue(ShowHuntProperty); }
+ set { SetValue(ShowHuntProperty, value); }
+ }
+
+ public bool ShowTames
+ {
+ get { return (bool)GetValue(ShowTamesProperty); }
+ set { SetValue(ShowTamesProperty, value); }
+ }
+
+ public static readonly DependencyProperty ShowTamesProperty =
+ DependencyProperty.Register("ShowTames", typeof(bool), typeof(MainWindow), new PropertyMetadata(false));
+
+ public static readonly DependencyProperty ShowHuntProperty =
+ DependencyProperty.Register("ShowHunt", typeof(bool), typeof(MainWindow), new PropertyMetadata(true));
+
+ public static readonly DependencyProperty NewSearchActiveProperty =
+ DependencyProperty.Register("NewSearchActive", typeof(bool), typeof(MainWindow), new PropertyMetadata(false));
+
+ public static readonly DependencyProperty CreateSearchAvailableProperty =
+ DependencyProperty.Register("CreateSearchAvailable", typeof(bool), typeof(MainWindow), new PropertyMetadata(true));
+
+ public static readonly DependencyProperty NewSearchProperty =
+ DependencyProperty.Register("NewSearch", typeof(SearchCriteria), typeof(MainWindow), new PropertyMetadata(null));
+
+ public static readonly DependencyProperty IsLoadingProperty =
+ DependencyProperty.Register("IsLoading", typeof(bool), typeof(MainWindow), new PropertyMetadata(false));
+
+ public static readonly DependencyProperty StatusTextProperty =
+ DependencyProperty.Register("StatusText", typeof(string), typeof(MainWindow), new PropertyMetadata(""));
+
+ public static readonly DependencyProperty StatusDetailTextProperty =
+ DependencyProperty.Register("StatusDetailText", typeof(string), typeof(MainWindow), new PropertyMetadata(""));
+
+
+ ArkReader arkReaderWild;
+ ArkReader arkReaderTamed;
+ FileSystemWatcher fileWatcher;
+ DispatcherTimer reloadTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(2) };
+
+ readonly List nullableBoolValues = new List { null, false, true };
+
+ public MainWindow()
+ {
+ arkReaderWild = new ArkReader(true);
+ arkReaderTamed = new ArkReader(false);
+
+ DataContext = this;
+
+ InitializeComponent();
+
+ LoadSavedSearches();
+ EnsureOutputDirectory();
+
+ // Setup file watcher
+ fileWatcher = new FileSystemWatcher(Path.GetDirectoryName(Properties.Settings.Default.SaveFile));
+ fileWatcher.Renamed += FileWatcher_Changed;
+ fileWatcher.EnableRaisingEvents = true;
+ reloadTimer.Tick += ReloadTimer_Tick;
+
+ // Sort the results
+ resultsList.Items.SortDescriptions.Add(new SortDescription("Dino.BaseLevel", ListSortDirection.Descending));
+
+ // Sort the searches
+ searchesList.Items.SortDescriptions.Add(new SortDescription("Group", ListSortDirection.Ascending));
+ searchesList.Items.SortDescriptions.Add(new SortDescription("Order", ListSortDirection.Ascending));
+
+ // Add grouping to the searches list
+ CollectionView view = (CollectionView)CollectionViewSource.GetDefaultView(searchesList.ItemsSource);
+ view.GroupDescriptions.Add(new PropertyGroupDescription("Group"));
+ }
+
+ private void EnsureOutputDirectory()
+ {
+ if (String.IsNullOrWhiteSpace(Properties.Settings.Default.OutputDir))
+ {
+ Properties.Settings.Default.OutputDir = Path.Combine(Path.GetTempPath(), Properties.Resources.ProgramName);
+ if (!Directory.Exists(Properties.Settings.Default.OutputDir))
+ {
+ Directory.CreateDirectory(Properties.Settings.Default.OutputDir);
+ Properties.Settings.Default.Save();
+ }
+ }
+ }
+
+ private void FileWatcher_Changed(object sender, FileSystemEventArgs e)
+ {
+ if (!String.Equals(e.FullPath, Properties.Settings.Default.SaveFile)) return;
+
+ Dispatcher.Invoke(() => StatusText = "Detected change to saved ARK...");
+
+ // Cancel any existing timer to ensure we're not called multiple times
+ if (reloadTimer.IsEnabled) reloadTimer.Stop();
+
+ reloadTimer.Start();
+ }
+
+ private async void ReloadTimer_Tick(object sender, EventArgs e)
+ {
+ reloadTimer.Stop();
+ await Dispatcher.InvokeAsync(() => ReReadArk(), DispatcherPriority.Background);
+ }
+
+ private async void Window_Loaded(object sender, RoutedEventArgs e)
+ {
+ await ReReadArk();
+ }
+
+ private void Searches_SelectionChanged(object sender, SelectionChangedEventArgs e)
+ {
+ UpdateCurrentSearch();
+ }
+
+ private void RemoveSearch_Click(object sender, RoutedEventArgs e)
+ {
+ if (ShowTames) return;
+
+ var button = sender as Button;
+ if (button?.DataContext is SearchCriteria search) ListSearches.Remove(search);
+ UpdateCurrentSearch();
+
+ MarkSearchesChanged();
+ }
+
+ private void Window_SourceInitialized(object sender, EventArgs e)
+ {
+ // Handler to maintain window aspect ratio
+ WindowAspectRatio.Register((Window)sender, height => (int)Math.Round(
+ height - statusPanel.ActualHeight - 2 * SystemParameters.ResizeFrameHorizontalBorderHeight - SystemParameters.WindowCaptionHeight
+ + leftPanel.ActualWidth + rightPanel.ActualWidth + 2 * SystemParameters.ResizeFrameVerticalBorderWidth));
+ }
+
+ private void CreateSearch_Click(object sender, RoutedEventArgs e)
+ {
+ NewSearch = new SearchCriteria();
+ NewSearchActive = true;
+ CreateSearchAvailable = false;
+
+ speciesCombo.ItemsSource = arkReaderWild.AllSpecies;
+ groupsCombo.ItemsSource = ListSearches.Select(sc => sc.Group).Distinct().OrderBy(g => g).ToArray();
+ }
+
+ private async void SaveSearch_Click(object sender, RoutedEventArgs e)
+ {
+ if (String.IsNullOrWhiteSpace(NewSearch.Species)) return;
+
+ try
+ {
+ NewSearch.Order = ListSearches.Where(sc => sc.Group == NewSearch.Group).Max(sc => sc.Order) + 100;
+ }
+ catch (InvalidOperationException)
+ { }
+
+ await arkReaderWild.EnsureSpeciesIsLoaded(NewSearch.Species);
+
+ ListSearches.Add(NewSearch);
+ NewSearch = null;
+ NewSearchActive = false;
+ CreateSearchAvailable = true;
+
+ MarkSearchesChanged();
+ }
+
+#if OBSOLETE
+ private async void ProcessNow_Click(object sender, RoutedEventArgs e)
+ {
+ if (IsLoading) return;
+
+ await ReReadArk(true);
+ }
+
+ private async void WildTamed_Toggle(object sender, RoutedEventArgs e)
+ {
+ var btn = (ToggleButton)sender;
+ arkReaderWild.Tamed = (btn.IsChecked == true);
+ await ReReadArk(true);
+ }
+#endif
+
+ private void AdjustableInteger_MouseWheel(object sender, MouseWheelEventArgs e)
+ {
+ var tb = (TextBlock)sender;
+ var diff = Math.Sign(e.Delta) * Properties.Settings.Default.LevelStep;
+ var bexpr = tb.GetBindingExpression(TextBlock.TextProperty);
+ var accessor = TypeAccessor.Create(typeof(SearchCriteria));
+ var value = (int?)accessor[bexpr.ResolvedSource, bexpr.ResolvedSourcePropertyName];
+ if (value.HasValue)
+ {
+ value = value + diff;
+ if (value < 0 || value > Properties.Settings.Default.MaxLevel) value = null;
+ }
+ else
+ {
+ value = (diff > 0) ? 0 : Properties.Settings.Default.MaxLevel;
+ }
+
+ accessor[bexpr.ResolvedSource, bexpr.ResolvedSourcePropertyName] = value;
+ bexpr.UpdateTarget();
+
+ if (null != searchesList.SelectedItem)
+ UpdateCurrentSearch();
+
+ MarkSearchesChanged();
+ }
+
+ private void AdjustableGender_MouseWheel(object sender, MouseWheelEventArgs e)
+ {
+ var im = (Image)sender;
+ var diff = Math.Sign(e.Delta);
+ var nOptions = nullableBoolValues.Count;
+ var bexpr = im.GetBindingExpression(Image.SourceProperty);
+ var accessor = TypeAccessor.Create(typeof(SearchCriteria));
+ var value = (bool?)accessor[bexpr.ResolvedSource, bexpr.ResolvedSourcePropertyName];
+ var index = nullableBoolValues.IndexOf(value);
+ index = (index + diff + nOptions) % nOptions;
+ value = nullableBoolValues[index];
+ accessor[bexpr.ResolvedSource, bexpr.ResolvedSourcePropertyName] = value;
+ bexpr.UpdateTarget();
+
+ if (null != searchesList.SelectedItem)
+ UpdateCurrentSearch();
+
+ MarkSearchesChanged();
+ }
+
+ private void Result_MouseEnter(object sender, MouseEventArgs e)
+ {
+ var fe = sender as FrameworkElement;
+ if (fe == null) return;
+ var dino = fe.DataContext as DinoViewModel;
+ if (dino == null) return;
+ dino.Highlight = true;
+ }
+
+ private void Result_MouseLeave(object sender, MouseEventArgs e)
+ {
+ var fe = sender as FrameworkElement;
+ if (fe == null) return;
+ var dino = fe.DataContext as DinoViewModel;
+ if (dino == null) return;
+ dino.Highlight = false;
+ }
+
+ private void MarkSearchesChanged()
+ {
+ SaveSearches();
+ }
+
+ private void SaveSearches()
+ {
+ Properties.Settings.Default.SavedSearches = JsonConvert.SerializeObject(ListSearches);
+ Properties.Settings.Default.Save();
+ }
+
+ private void LoadSavedSearches()
+ {
+ if (!String.IsNullOrWhiteSpace(Properties.Settings.Default.SavedSearches))
+ {
+ Collection searches;
+ try
+ {
+ searches = JsonConvert.DeserializeObject>(Properties.Settings.Default.SavedSearches);
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine("Exception reading saved searches: " + e.ToString());
+ return;
+ }
+
+ ListSearches.Clear();
+ foreach (var search in searches)
+ ListSearches.Add(search);
+ }
+ }
+
+ private async Task ReReadArk(bool force = false)
+ {
+ await PerformConversion(force);
+ await LoadSearchSpecies();
+
+ UpdateSearchResults(new List());
+ }
+
+ private async Task LoadSearchSpecies()
+ {
+ var species = arkReaderWild.AllSpecies.Distinct();
+ foreach (var speciesName in species)
+ await arkReaderWild.EnsureSpeciesIsLoaded(speciesName);
+
+ species = arkReaderTamed.AllSpecies.Distinct();
+ foreach (var speciesName in species)
+ await arkReaderTamed.EnsureSpeciesIsLoaded(speciesName);
+ }
+
+ private void UpdateSearchResults(IList searches)
+ {
+ if (searches == null || searches.Count == 0)
+ {
+ ListResults.Clear();
+ }
+ else
+ {
+ // Find dinos that match the given searches
+ var found = new List();
+ var reader = ShowTames ? arkReaderTamed : arkReaderWild;
+ foreach (var search in searches)
+ {
+ if (String.IsNullOrWhiteSpace(search.Species))
+ {
+ foreach (var speciesDinos in reader.FoundDinos.Values)
+ found.AddRange(speciesDinos);
+ }
+ else
+ {
+ if (reader.FoundDinos.ContainsKey(search.Species))
+ {
+ var dinoList = reader.FoundDinos[search.Species];
+ found.AddRange(dinoList.Where(d => search.Matches(d)));
+ }
+ }
+ }
+
+ ListResults.Clear();
+ foreach (var result in found)
+ ListResults.Add(result);
+ }
+
+ var cv = (CollectionView)CollectionViewSource.GetDefaultView(ListResults);
+ cv.Refresh();
+ }
+
+ private async Task PerformConversion(bool force)
+ {
+ IsLoading = true;
+ try
+ {
+ StatusText = "Processing saved ARK : Wild";
+ await arkReaderWild.PerformConversion(force);
+ StatusText = "Processing saved ARK : Tamed";
+ await arkReaderTamed.PerformConversion(force);
+ StatusText = "ARK processing completed";
+ StatusDetailText = $"{arkReaderWild.NumberOfSpecies} species";
+ }
+ catch (Exception ex)
+ {
+ StatusText = "ARK processing failed";
+ StatusDetailText = "";
+ MessageBox.Show(ex.Message, "ARK Tools Error", MessageBoxButton.OK, MessageBoxImage.Exclamation);
+ }
+ finally
+ {
+ IsLoading = false;
+ }
+ }
+
+ private void UpdateCurrentSearch()
+ {
+ var search = (SearchCriteria)searchesList.SelectedItem;
+ var searches = new List();
+ if (search != null) searches.Add(search);
+ UpdateSearchResults(searches);
+ }
+
+ void IDropTarget.DragOver(IDropInfo dropInfo)
+ {
+ SearchCriteria sourceItem = dropInfo.Data as SearchCriteria;
+ SearchCriteria targetItem = dropInfo.TargetItem as SearchCriteria;
+
+ if (sourceItem != null && targetItem != null)
+ {
+ dropInfo.DropTargetAdorner = DropTargetAdorners.Insert;
+ dropInfo.Effects = DragDropEffects.Move;
+ }
+ }
+
+ void IDropTarget.Drop(IDropInfo dropInfo)
+ {
+ var sourceItem = (SearchCriteria)dropInfo.Data;
+ var targetItem = (SearchCriteria)dropInfo.TargetItem;
+
+ var ii = dropInfo.InsertIndex;
+ var ip = dropInfo.InsertPosition;
+
+ // Change source item's group
+ sourceItem.Group = targetItem.Group;
+
+ // Try to figure out the other item to insert between, or pick a boundary
+ var options = ListSearches
+ .OrderBy(sc => sc.Group).ThenBy(sc => sc.Order).ThenBy(sc => sc.Species).ThenBy(sc => sc.MinLevel).ThenBy(sc => sc.MaxLevel)
+ .Where(sc => (ip == RelativeInsertPosition.BeforeTargetItem) ? sc.Order < targetItem.Order : sc.Order > targetItem.Order).ToArray();
+
+ double bound;
+ if (options.Length > 0)
+ {
+ var otherItem = (ip == RelativeInsertPosition.BeforeTargetItem) ? options.Last() : options.First();
+ bound = otherItem.Order;
+ }
+ else
+ {
+ bound = targetItem.Order + ((ip == RelativeInsertPosition.BeforeTargetItem) ? -100 : 100);
+ }
+
+ // Update the order to be mid-way between the two
+ sourceItem.Order = (targetItem.Order + bound) / 2;
+
+ // Force binding update
+ CollectionViewSource.GetDefaultView(searchesList.ItemsSource).Refresh();
+
+ // Save list
+ MarkSearchesChanged();
+ }
+
+ private void ShowTames_Click(object sender, MouseButtonEventArgs e)
+ {
+ ShowTames = true;
+ ShowHunt = false;
+ NewSearchActive = false;
+ CreateSearchAvailable = false;
+
+ ShowTameSearches();
+ }
+
+ private void ShowTheHunt_Click(object sender, MouseButtonEventArgs e)
+ {
+ ShowTames = false;
+ ShowHunt = true;
+ NewSearchActive = false;
+ CreateSearchAvailable = true;
+
+ ShowWildSearches();
+ }
+
+ private void ShowTameSearches()
+ {
+ SetupTamedSearches();
+ }
+
+ private void SetupTamedSearches()
+ {
+ var wildcard = new string[] { null };
+ var speciesList = wildcard.Concat(arkReaderTamed.AllSpecies).ToList();
+ var orderList = Enumerable.Range(0, speciesList.Count);
+ var searches = speciesList.Zip(orderList, (species, order) => new SearchCriteria { Species = species, Order = order });
+
+ ListSearches.Clear();
+ foreach (var search in searches)
+ ListSearches.Add(search);
+ }
+
+ private void ShowWildSearches()
+ {
+ LoadSavedSearches();
+ }
+ }
+}
diff --git a/LarkatorGUI/Properties/AssemblyInfo.cs b/LarkatorGUI/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..34bfe11
--- /dev/null
+++ b/LarkatorGUI/Properties/AssemblyInfo.cs
@@ -0,0 +1,55 @@
+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("LarkatorGUI")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("LarkatorGUI")]
+[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/LarkatorGUI/Properties/Resources.Designer.cs b/LarkatorGUI/Properties/Resources.Designer.cs
new file mode 100644
index 0000000..6a569d4
--- /dev/null
+++ b/LarkatorGUI/Properties/Resources.Designer.cs
@@ -0,0 +1,90 @@
+//------------------------------------------------------------------------------
+//
+// 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 LarkatorGUI.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", "15.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("LarkatorGUI.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;
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to ark-tools.exe.
+ ///
+ internal static string ArkToolsExe {
+ get {
+ return ResourceManager.GetString("ArkToolsExe", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to classes.json.
+ ///
+ internal static string ClassesJson {
+ get {
+ return ResourceManager.GetString("ClassesJson", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Larkator.
+ ///
+ internal static string ProgramName {
+ get {
+ return ResourceManager.GetString("ProgramName", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/LarkatorGUI/Properties/Resources.resx b/LarkatorGUI/Properties/Resources.resx
new file mode 100644
index 0000000..18b54ee
--- /dev/null
+++ b/LarkatorGUI/Properties/Resources.resx
@@ -0,0 +1,129 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ ark-tools.exe
+
+
+ classes.json
+
+
+ Larkator
+
+
\ No newline at end of file
diff --git a/LarkatorGUI/Properties/Settings.Designer.cs b/LarkatorGUI/Properties/Settings.Designer.cs
new file mode 100644
index 0000000..e9ac6cf
--- /dev/null
+++ b/LarkatorGUI/Properties/Settings.Designer.cs
@@ -0,0 +1,120 @@
+//------------------------------------------------------------------------------
+//
+// 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 LarkatorGUI.Properties {
+
+
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.5.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;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("")]
+ public string SaveFile {
+ get {
+ return ((string)(this["SaveFile"]));
+ }
+ set {
+ this["SaveFile"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("")]
+ public string ArkTools {
+ get {
+ return ((string)(this["ArkTools"]));
+ }
+ set {
+ this["ArkTools"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("")]
+ public string OutputDir {
+ get {
+ return ((string)(this["OutputDir"]));
+ }
+ set {
+ this["OutputDir"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("120")]
+ public int MaxLevel {
+ get {
+ return ((int)(this["MaxLevel"]));
+ }
+ set {
+ this["MaxLevel"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("4")]
+ public int LevelStep {
+ get {
+ return ((int)(this["LevelStep"]));
+ }
+ set {
+ this["LevelStep"] = value;
+ }
+ }
+
+ [global::System.Configuration.UserScopedSettingAttribute()]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Configuration.DefaultSettingValueAttribute("[ { \"Group\": \"Shopping List\", \"Order\": 0.0, \"Species\": \"Allosaurus\", \"MinLevel\": " +
+ "92, \"MaxLevel\": null, \"Female\": true }, { \"Group\": \"Worries\", \"Order\": 175.0, \"S" +
+ "pecies\": \"Alpha Carnotaurus\", \"MinLevel\": 100, \"MaxLevel\": null, \"Female\": null " +
+ "}, { \"Group\": \"Worries\", \"Order\": 50.0, \"Species\": \"Alpha Leedsichthys\", \"MinLev" +
+ "el\": null, \"MaxLevel\": null, \"Female\": null }, { \"Group\": \"Worries\", \"Order\": 12" +
+ "5.0, \"Species\": \"Alpha Megalodon\", \"MinLevel\": 100, \"MaxLevel\": null, \"Female\": " +
+ "null }, { \"Group\": \"Worries\", \"Order\": 192.1875, \"Species\": \"Alpha Mosasaur\", \"M" +
+ "inLevel\": null, \"MaxLevel\": null, \"Female\": null }, { \"Group\": \"Worries\", \"Order" +
+ "\": 287.5, \"Species\": \"Alpha Raptor\", \"MinLevel\": 100, \"MaxLevel\": null, \"Female\"" +
+ ": null }, { \"Group\": \"Worries\", \"Order\": 443.75, \"Species\": \"Alpha T-Rex\", \"MinL" +
+ "evel\": 100, \"MaxLevel\": null, \"Female\": null }, { \"Group\": \"Worries\", \"Order\": 5" +
+ "21.875, \"Species\": \"Giganotosaurus\", \"MinLevel\": null, \"MaxLevel\": null, \"Female" +
+ "\": null }, { \"Group\": \"Worries\", \"Order\": 610.9375, \"Species\": \"Titanosaur\", \"Mi" +
+ "nLevel\": null, \"MaxLevel\": null, \"Female\": null }, { \"Group\": \"Shopping List\", \"" +
+ "Order\": 87.5, \"Species\": \"Baryonyx\", \"MinLevel\": 100, \"MaxLevel\": null, \"Female\"" +
+ ": null }, { \"Group\": \"Shopping List\", \"Order\": 200.0, \"Species\": \"Dire Bear\", \"M" +
+ "inLevel\": 100, \"MaxLevel\": null, \"Female\": null }, { \"Group\": \"Worries\", \"Order\"" +
+ ": 292.1875, \"Species\": \"Diseased Leech\", \"MinLevel\": null, \"MaxLevel\": null, \"Fe" +
+ "male\": null }, { \"Group\": \"Shopping List\", \"Order\": 300.0, \"Species\": \"Quetzal\"," +
+ " \"MinLevel\": null, \"MaxLevel\": null, \"Female\": null }, { \"Group\": \"Shopping List" +
+ "\", \"Order\": 419.921875, \"Species\": \"Rex\", \"MinLevel\": 100, \"MaxLevel\": null, \"Fe" +
+ "male\": null }, { \"Group\": \"Shopping List\", \"Order\": 510.9375, \"Species\": \"Yutyra" +
+ "nnus\", \"MinLevel\": 84, \"MaxLevel\": null, \"Female\": null } ]")]
+ public string SavedSearches {
+ get {
+ return ((string)(this["SavedSearches"]));
+ }
+ set {
+ this["SavedSearches"] = value;
+ }
+ }
+ }
+}
diff --git a/LarkatorGUI/Properties/Settings.settings b/LarkatorGUI/Properties/Settings.settings
new file mode 100644
index 0000000..8716e3b
--- /dev/null
+++ b/LarkatorGUI/Properties/Settings.settings
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 120
+
+
+ 4
+
+
+ [ { "Group": "Shopping List", "Order": 0.0, "Species": "Allosaurus", "MinLevel": 92, "MaxLevel": null, "Female": true }, { "Group": "Worries", "Order": 175.0, "Species": "Alpha Carnotaurus", "MinLevel": 100, "MaxLevel": null, "Female": null }, { "Group": "Worries", "Order": 50.0, "Species": "Alpha Leedsichthys", "MinLevel": null, "MaxLevel": null, "Female": null }, { "Group": "Worries", "Order": 125.0, "Species": "Alpha Megalodon", "MinLevel": 100, "MaxLevel": null, "Female": null }, { "Group": "Worries", "Order": 192.1875, "Species": "Alpha Mosasaur", "MinLevel": null, "MaxLevel": null, "Female": null }, { "Group": "Worries", "Order": 287.5, "Species": "Alpha Raptor", "MinLevel": 100, "MaxLevel": null, "Female": null }, { "Group": "Worries", "Order": 443.75, "Species": "Alpha T-Rex", "MinLevel": 100, "MaxLevel": null, "Female": null }, { "Group": "Worries", "Order": 521.875, "Species": "Giganotosaurus", "MinLevel": null, "MaxLevel": null, "Female": null }, { "Group": "Worries", "Order": 610.9375, "Species": "Titanosaur", "MinLevel": null, "MaxLevel": null, "Female": null }, { "Group": "Shopping List", "Order": 87.5, "Species": "Baryonyx", "MinLevel": 100, "MaxLevel": null, "Female": null }, { "Group": "Shopping List", "Order": 200.0, "Species": "Dire Bear", "MinLevel": 100, "MaxLevel": null, "Female": null }, { "Group": "Worries", "Order": 292.1875, "Species": "Diseased Leech", "MinLevel": null, "MaxLevel": null, "Female": null }, { "Group": "Shopping List", "Order": 300.0, "Species": "Quetzal", "MinLevel": null, "MaxLevel": null, "Female": null }, { "Group": "Shopping List", "Order": 419.921875, "Species": "Rex", "MinLevel": 100, "MaxLevel": null, "Female": null }, { "Group": "Shopping List", "Order": 510.9375, "Species": "Yutyrannus", "MinLevel": 84, "MaxLevel": null, "Female": null } ]
+
+
+
\ No newline at end of file
diff --git a/LarkatorGUI/Properties/app.manifest b/LarkatorGUI/Properties/app.manifest
new file mode 100644
index 0000000..cd90d8b
--- /dev/null
+++ b/LarkatorGUI/Properties/app.manifest
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/LarkatorGUI/Welcome.xaml b/LarkatorGUI/Welcome.xaml
new file mode 100644
index 0000000..1bbb56e
--- /dev/null
+++ b/LarkatorGUI/Welcome.xaml
@@ -0,0 +1,157 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ To get started we need to configure a few items...
+
+
+
+
+
+
+
+
+
+
+
+ 1)
+
+ ARKTools is used to read from the saved ARK.
+ Please locate ark-tools.exe:
+ Or download it from here: https://survivetheark.com/index.php?/forums/topic/80750...
+
+
+
+
+
+
+
+
+
+
+ 2)
+
+ Locate the ARK you wish to search within:
+ e.g. SteamLibrary\steamapps\common\ARK\Shooter Game\Saved\SavedArksLocal\TheIsland.ark
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/LarkatorGUI/Welcome.xaml.cs b/LarkatorGUI/Welcome.xaml.cs
new file mode 100644
index 0000000..321d183
--- /dev/null
+++ b/LarkatorGUI/Welcome.xaml.cs
@@ -0,0 +1,138 @@
+using Microsoft.Win32;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Configuration;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Text;
+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.Shapes;
+
+namespace LarkatorGUI
+{
+ ///
+ /// Interaction logic for Welcome.xaml
+ ///
+ public partial class Welcome : Window
+ {
+ public string ArkToolsPath
+ {
+ get { return (string)GetValue(ArkToolsPathProperty); }
+ set { SetValue(ArkToolsPathProperty, value); }
+ }
+
+ // Using a DependencyProperty as the backing store for ArkToolsPath. This enables animation, styling, binding, etc...
+ public static readonly DependencyProperty ArkToolsPathProperty =
+ DependencyProperty.Register("ArkToolsPath", typeof(string), typeof(Welcome), new PropertyMetadata(""));
+
+ public string SaveFilePath
+ {
+ get { return (string)GetValue(SaveFilePathProperty); }
+ set { SetValue(SaveFilePathProperty, value); }
+ }
+
+ // Using a DependencyProperty as the backing store for SaveFilePath. This enables animation, styling, binding, etc...
+ public static readonly DependencyProperty SaveFilePathProperty =
+ DependencyProperty.Register("SaveFilePath", typeof(string), typeof(Welcome), new PropertyMetadata(""));
+
+ public Welcome()
+ {
+ InitializeComponent();
+
+ DependencyPropertyDescriptor.FromProperty(ArkToolsPathProperty, GetType()).AddValueChanged(this, (_, __) => UpdateValidation());
+ DependencyPropertyDescriptor.FromProperty(SaveFilePathProperty, GetType()).AddValueChanged(this, (_, __) => UpdateValidation());
+
+ ArkToolsPath = Properties.Settings.Default.ArkTools;
+ SaveFilePath = Properties.Settings.Default.SaveFile;
+
+ // Skip the Welcome window if we're already configured
+ UpdateValidation();
+ if (LetsGoButton.IsEnabled) SwitchToMainWindow();
+ }
+
+ private void UpdateValidation()
+ {
+ var toolsGood = File.Exists(ArkToolsPath);
+ var saveGood = File.Exists(SaveFilePath) && SaveFilePath.EndsWith(".ark");
+
+ LetsGoButton.IsEnabled = toolsGood && saveGood;
+ }
+
+ private void BrowseArkTools_Click(object sender, RoutedEventArgs e)
+ {
+ var dialog = new OpenFileDialog()
+ {
+ AddExtension = true,
+ CheckFileExists = true,
+ CheckPathExists = true,
+ DefaultExt = "ark-tools.exe",
+ DereferenceLinks = true,
+ Filter = "ARK Tools Executable|ark-tools.exe",
+ Multiselect = false,
+ Title = "Locate ark-tools.exe...",
+ FileName = ArkToolsPath,
+ };
+
+ var result = dialog.ShowDialog();
+ if (result == true)
+ {
+ ArkToolsPath = dialog.FileName;
+ }
+ }
+
+ private void BrowseSaveFile_Click(object sender, RoutedEventArgs e)
+ {
+ var dialog = new OpenFileDialog()
+ {
+ AddExtension = true,
+ CheckFileExists = true,
+ CheckPathExists = true,
+ DefaultExt = ".ark",
+ DereferenceLinks = true,
+ Filter = "ARK Save File|*.ark",
+ Multiselect = false,
+ Title = "Locate saved ARK...",
+ FileName = SaveFilePath,
+ };
+
+ var result = dialog.ShowDialog();
+ if (result == true)
+ {
+ SaveFilePath = dialog.FileName;
+ }
+ }
+
+ private void LetsGoButton_Click(object sender, RoutedEventArgs e)
+ {
+ Console.WriteLine("Let's go!");
+
+ Properties.Settings.Default.ArkTools = ArkToolsPath;
+ Properties.Settings.Default.SaveFile = SaveFilePath;
+
+ Properties.Settings.Default.Save();
+
+ SwitchToMainWindow();
+ }
+
+ private void SwitchToMainWindow()
+ {
+ new MainWindow().Show();
+
+ Close();
+ }
+
+ private void LaunchHyperlink_Click(object sender, RoutedEventArgs e)
+ {
+ Process.Start((sender as Hyperlink).NavigateUri.ToString());
+ }
+ }
+}
diff --git a/LarkatorGUI/WindowAspectRatio.cs b/LarkatorGUI/WindowAspectRatio.cs
new file mode 100644
index 0000000..be4a918
--- /dev/null
+++ b/LarkatorGUI/WindowAspectRatio.cs
@@ -0,0 +1,68 @@
+using System;
+using System.Windows;
+using System.Windows.Interop;
+using System.Runtime.InteropServices;
+
+namespace LarkatorGUI
+{
+ ///
+ /// Thanks to Mike O'Brien in http://www.mikeobrien.net/blog/maintaining-aspect-ratio-when-resizing for the basis for this.
+ ///
+ public class WindowAspectRatio
+ {
+ private Func calculateWidthFromHeightFn;
+
+ private WindowAspectRatio(Window window, Func calculateWidthFromHeight)
+ {
+ this.calculateWidthFromHeightFn = calculateWidthFromHeight;
+ ((HwndSource)HwndSource.FromVisual(window)).AddHook(DragHook);
+ }
+
+ public static void Register(Window window, Func calculateWidthFromHeight)
+ {
+ new WindowAspectRatio(window, calculateWidthFromHeight);
+ }
+
+ internal enum WM
+ {
+ WINDOWPOSCHANGING = 0x0046,
+ }
+
+ [Flags()]
+ public enum SWP
+ {
+ NoMove = 0x2,
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct WINDOWPOS
+ {
+ public IntPtr hwnd;
+ public IntPtr hwndInsertAfter;
+ public int x;
+ public int y;
+ public int cx;
+ public int cy;
+ public int flags;
+ }
+
+ private IntPtr DragHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handeled)
+ {
+ if ((WM)msg == WM.WINDOWPOSCHANGING)
+ {
+ WINDOWPOS position = (WINDOWPOS)Marshal.PtrToStructure(lParam, typeof(WINDOWPOS));
+
+ if ((position.flags & (int)SWP.NoMove) != 0 ||
+ HwndSource.FromHwnd(hwnd).RootVisual == null) return IntPtr.Zero;
+
+ position.cx = calculateWidthFromHeightFn(position.cy);
+
+ Marshal.StructureToPtr(position, lParam, true);
+ handeled = true;
+ }
+
+ return IntPtr.Zero;
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/LarkatorGUI/app.config b/LarkatorGUI/app.config
new file mode 100644
index 0000000..a218fcb
--- /dev/null
+++ b/LarkatorGUI/app.config
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 120
+
+
+ 4
+
+
+ [ { "Group": "Shopping List", "Order": 0.0, "Species": "Allosaurus", "MinLevel": 92, "MaxLevel": null, "Female": true }, { "Group": "Worries", "Order": 175.0, "Species": "Alpha Carnotaurus", "MinLevel": 100, "MaxLevel": null, "Female": null }, { "Group": "Worries", "Order": 50.0, "Species": "Alpha Leedsichthys", "MinLevel": null, "MaxLevel": null, "Female": null }, { "Group": "Worries", "Order": 125.0, "Species": "Alpha Megalodon", "MinLevel": 100, "MaxLevel": null, "Female": null }, { "Group": "Worries", "Order": 192.1875, "Species": "Alpha Mosasaur", "MinLevel": null, "MaxLevel": null, "Female": null }, { "Group": "Worries", "Order": 287.5, "Species": "Alpha Raptor", "MinLevel": 100, "MaxLevel": null, "Female": null }, { "Group": "Worries", "Order": 443.75, "Species": "Alpha T-Rex", "MinLevel": 100, "MaxLevel": null, "Female": null }, { "Group": "Worries", "Order": 521.875, "Species": "Giganotosaurus", "MinLevel": null, "MaxLevel": null, "Female": null }, { "Group": "Worries", "Order": 610.9375, "Species": "Titanosaur", "MinLevel": null, "MaxLevel": null, "Female": null }, { "Group": "Shopping List", "Order": 87.5, "Species": "Baryonyx", "MinLevel": 100, "MaxLevel": null, "Female": null }, { "Group": "Shopping List", "Order": 200.0, "Species": "Dire Bear", "MinLevel": 100, "MaxLevel": null, "Female": null }, { "Group": "Worries", "Order": 292.1875, "Species": "Diseased Leech", "MinLevel": null, "MaxLevel": null, "Female": null }, { "Group": "Shopping List", "Order": 300.0, "Species": "Quetzal", "MinLevel": null, "MaxLevel": null, "Female": null }, { "Group": "Shopping List", "Order": 419.921875, "Species": "Rex", "MinLevel": 100, "MaxLevel": null, "Female": null }, { "Group": "Shopping List", "Order": 510.9375, "Species": "Yutyrannus", "MinLevel": 84, "MaxLevel": null, "Female": null } ]
+
+
+
+
\ No newline at end of file
diff --git a/LarkatorGUI/compass.ico b/LarkatorGUI/compass.ico
new file mode 100644
index 0000000..1a0caed
Binary files /dev/null and b/LarkatorGUI/compass.ico differ
diff --git a/LarkatorGUI/imgs/female.png b/LarkatorGUI/imgs/female.png
new file mode 100644
index 0000000..f327d8e
Binary files /dev/null and b/LarkatorGUI/imgs/female.png differ
diff --git a/LarkatorGUI/imgs/hires_map.png b/LarkatorGUI/imgs/hires_map.png
new file mode 100644
index 0000000..4273855
Binary files /dev/null and b/LarkatorGUI/imgs/hires_map.png differ
diff --git a/LarkatorGUI/imgs/male.png b/LarkatorGUI/imgs/male.png
new file mode 100644
index 0000000..b360830
Binary files /dev/null and b/LarkatorGUI/imgs/male.png differ
diff --git a/LarkatorGUI/imgs/nogender.png b/LarkatorGUI/imgs/nogender.png
new file mode 100644
index 0000000..d168082
Binary files /dev/null and b/LarkatorGUI/imgs/nogender.png differ
diff --git a/LarkatorGUI/packages.config b/LarkatorGUI/packages.config
new file mode 100644
index 0000000..ea9cdf4
--- /dev/null
+++ b/LarkatorGUI/packages.config
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ListViewLayout/ConverterGridViewColumn.cs b/ListViewLayout/ConverterGridViewColumn.cs
new file mode 100644
index 0000000..6c852af
--- /dev/null
+++ b/ListViewLayout/ConverterGridViewColumn.cs
@@ -0,0 +1,69 @@
+// -- FILE ------------------------------------------------------------------
+// name : ConverterGridViewColumn.cs
+// created : Jani Giannoudis - 2008.03.27
+// language : c#
+// environment: .NET 3.0
+// copyright : (c) 2008-2012 by Itenso GmbH, Switzerland
+// --------------------------------------------------------------------------
+using System;
+using System.Windows.Data;
+using System.Windows.Controls;
+using System.Globalization;
+
+namespace Itenso.Windows.Controls.ListViewLayout
+{
+
+ // ------------------------------------------------------------------------
+ public abstract class ConverterGridViewColumn : GridViewColumn, IValueConverter
+ {
+
+ // ----------------------------------------------------------------------
+ protected ConverterGridViewColumn( Type bindingType )
+ {
+ if ( bindingType == null )
+ {
+ throw new ArgumentNullException( "bindingType" );
+ }
+
+ this.bindingType = bindingType;
+
+ // binding
+ Binding binding = new Binding();
+ binding.Mode = BindingMode.OneWay;
+ binding.Converter = this;
+ DisplayMemberBinding = binding;
+ } // ConverterGridViewColumn
+
+ // ----------------------------------------------------------------------
+ public Type BindingType
+ {
+ get { return bindingType; }
+ } // BindingType
+
+ // ----------------------------------------------------------------------
+ protected abstract object ConvertValue( object value );
+
+ // ----------------------------------------------------------------------
+ object IValueConverter.Convert( object value, Type targetType, object parameter, CultureInfo culture )
+ {
+ if ( !bindingType.IsInstanceOfType( value ) )
+ {
+ throw new InvalidOperationException();
+ }
+ return ConvertValue( value );
+ } // IValueConverter.Convert
+
+ // ----------------------------------------------------------------------
+ object IValueConverter.ConvertBack( object value, Type targetType, object parameter, CultureInfo culture )
+ {
+ throw new NotImplementedException();
+ } // IValueConverter.ConvertBack
+
+ // ----------------------------------------------------------------------
+ // members
+ private readonly Type bindingType;
+
+ } // class ConverterGridViewColumn
+
+} // namespace Itenso.Windows.Controls.ListViewLayout
+// -- EOF -------------------------------------------------------------------
diff --git a/ListViewLayout/FixedColumn.cs b/ListViewLayout/FixedColumn.cs
new file mode 100644
index 0000000..56fbbf0
--- /dev/null
+++ b/ListViewLayout/FixedColumn.cs
@@ -0,0 +1,68 @@
+// -- FILE ------------------------------------------------------------------
+// name : FixedColumn.cs
+// created : Jani Giannoudis - 2008.03.27
+// language : c#
+// environment: .NET 3.0
+// copyright : (c) 2008-2012 by Itenso GmbH, Switzerland
+// --------------------------------------------------------------------------
+using System.Windows;
+using System.Windows.Controls;
+
+namespace Itenso.Windows.Controls.ListViewLayout
+{
+
+ // ------------------------------------------------------------------------
+ public sealed class FixedColumn : LayoutColumn
+ {
+
+ // ----------------------------------------------------------------------
+ public static readonly DependencyProperty WidthProperty =
+ DependencyProperty.RegisterAttached(
+ "Width",
+ typeof( double ),
+ typeof( FixedColumn ) );
+
+ // ----------------------------------------------------------------------
+ private FixedColumn()
+ {
+ } // FixedColumn
+
+ // ----------------------------------------------------------------------
+ public static double GetWidth( DependencyObject obj )
+ {
+ return (double)obj.GetValue( WidthProperty );
+ } // GetWidth
+
+ // ----------------------------------------------------------------------
+ public static void SetWidth( DependencyObject obj, double width )
+ {
+ obj.SetValue( WidthProperty, width );
+ } // SetWidth
+
+ // ----------------------------------------------------------------------
+ public static bool IsFixedColumn( GridViewColumn column )
+ {
+ if ( column == null )
+ {
+ return false;
+ }
+ return HasPropertyValue( column, WidthProperty );
+ } // IsFixedColumn
+
+ // ----------------------------------------------------------------------
+ public static double? GetFixedWidth( GridViewColumn column )
+ {
+ return GetColumnWidth( column, WidthProperty );
+ } // GetFixedWidth
+
+ // ----------------------------------------------------------------------
+ public static GridViewColumn ApplyWidth( GridViewColumn gridViewColumn, double width )
+ {
+ SetWidth( gridViewColumn, width );
+ return gridViewColumn;
+ } // ApplyWidth
+
+ } // class FixedColumn
+
+} // namespace Itenso.Windows.Controls.ListViewLayout
+// -- EOF -------------------------------------------------------------------
diff --git a/ListViewLayout/ImageGridViewColumn.cs b/ListViewLayout/ImageGridViewColumn.cs
new file mode 100644
index 0000000..4159c16
--- /dev/null
+++ b/ListViewLayout/ImageGridViewColumn.cs
@@ -0,0 +1,67 @@
+// -- FILE ------------------------------------------------------------------
+// name : ImageGridViewColumn.cs
+// created : Jani Giannoudis - 2008.03.27
+// language : c#
+// environment: .NET 3.0
+// copyright : (c) 2008-2012 by Itenso GmbH, Switzerland
+// --------------------------------------------------------------------------
+using System;
+using System.Globalization;
+using System.Windows;
+using System.Windows.Data;
+using System.Windows.Media;
+using System.Windows.Controls;
+
+namespace Itenso.Windows.Controls.ListViewLayout
+{
+
+ // ------------------------------------------------------------------------
+ public abstract class ImageGridViewColumn : GridViewColumn, IValueConverter
+ {
+
+ // ----------------------------------------------------------------------
+ protected ImageGridViewColumn() :
+ this( Stretch.None )
+ {
+ } // ImageGridViewColumn
+
+ // ----------------------------------------------------------------------
+ protected ImageGridViewColumn( Stretch imageStretch )
+ {
+ FrameworkElementFactory imageElement = new FrameworkElementFactory( typeof( Image ) );
+
+ // image source
+ Binding imageSourceBinding = new Binding();
+ imageSourceBinding.Converter = this;
+ imageSourceBinding.Mode = BindingMode.OneWay;
+ imageElement.SetBinding( Image.SourceProperty, imageSourceBinding );
+
+ // image stretching
+ Binding imageStretchBinding = new Binding();
+ imageStretchBinding.Source = imageStretch;
+ imageElement.SetBinding( Image.StretchProperty, imageStretchBinding );
+
+ DataTemplate template = new DataTemplate();
+ template.VisualTree = imageElement;
+ CellTemplate = template;
+ } // ImageGridViewColumn
+
+ // ----------------------------------------------------------------------
+ protected abstract ImageSource GetImageSource( object value );
+
+ // ----------------------------------------------------------------------
+ object IValueConverter.Convert( object value, Type targetType, object parameter, CultureInfo culture )
+ {
+ return GetImageSource( value );
+ } // Convert
+
+ // ----------------------------------------------------------------------
+ object IValueConverter.ConvertBack( object value, Type targetType, object parameter, CultureInfo culture )
+ {
+ throw new NotImplementedException();
+ } // ConvertBack
+
+ } // class ImageGridViewColumn
+
+} // namespace Itenso.Windows.Controls.ListViewLayout
+// -- EOF -------------------------------------------------------------------
diff --git a/ListViewLayout/LayoutColumn.cs b/ListViewLayout/LayoutColumn.cs
new file mode 100644
index 0000000..2fc7d88
--- /dev/null
+++ b/ListViewLayout/LayoutColumn.cs
@@ -0,0 +1,54 @@
+// -- FILE ------------------------------------------------------------------
+// name : LayoutColumn.cs
+// created : Jani Giannoudis - 2008.03.27
+// language : c#
+// environment: .NET 3.0
+// copyright : (c) 2008-2012 by Itenso GmbH, Switzerland
+// --------------------------------------------------------------------------
+using System;
+using System.Windows;
+using System.Windows.Controls;
+
+namespace Itenso.Windows.Controls.ListViewLayout
+{
+
+ // ------------------------------------------------------------------------
+ public abstract class LayoutColumn
+ {
+
+ // ----------------------------------------------------------------------
+ protected static bool HasPropertyValue( GridViewColumn column, DependencyProperty dp )
+ {
+ if ( column == null )
+ {
+ throw new ArgumentNullException( "column" );
+ }
+ object value = column.ReadLocalValue( dp );
+ if ( value != null && value.GetType() == dp.PropertyType )
+ {
+ return true;
+ }
+
+ return false;
+ } // HasPropertyValue
+
+ // ----------------------------------------------------------------------
+ protected static double? GetColumnWidth( GridViewColumn column, DependencyProperty dp )
+ {
+ if ( column == null )
+ {
+ throw new ArgumentNullException( "column" );
+ }
+ object value = column.ReadLocalValue( dp );
+ if ( value != null && value.GetType() == dp.PropertyType )
+ {
+ return (double)value;
+ }
+
+ return null;
+ } // GetColumnWidth
+
+ } // class LayoutColumn
+
+} // namespace Itenso.Windows.Controls.ListViewLayout
+// -- EOF -------------------------------------------------------------------
diff --git a/ListViewLayout/ListViewLayout.csproj b/ListViewLayout/ListViewLayout.csproj
new file mode 100644
index 0000000..29e1942
--- /dev/null
+++ b/ListViewLayout/ListViewLayout.csproj
@@ -0,0 +1,109 @@
+
+
+
+ Debug
+ AnyCPU
+ 9.0.21022
+ 2.0
+ {916A9879-3F2D-471F-AAFA-28F0C8A7FFA9}
+ Library
+ Properties
+ Itenso.Windows.Controls.ListViewLayout
+ Itenso.Windows.Controls.ListViewLayout
+ v3.0
+ 512
+ {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ 4
+
+
+
+
+ 3.5
+
+ publish\
+ true
+ Disk
+ false
+ Foreground
+ 7
+ Days
+ false
+ false
+ true
+ 0
+ 1.0.0.%2a
+ false
+ false
+ true
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+ AllRules.ruleset
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+ AllRules.ruleset
+
+
+
+
+
+ 3.0
+
+
+ 3.0
+
+
+ 3.0
+
+
+
+
+
+
+
+
+
+
+ Code
+
+
+
+
+
+
+ False
+ .NET Framework 3.5 SP1 Client Profile
+ false
+
+
+ False
+ .NET Framework 3.5 SP1
+ true
+
+
+ False
+ Windows Installer 3.1
+ true
+
+
+
+
+
\ No newline at end of file
diff --git a/ListViewLayout/ListViewLayout2008.csproj b/ListViewLayout/ListViewLayout2008.csproj
new file mode 100644
index 0000000..25b96fc
--- /dev/null
+++ b/ListViewLayout/ListViewLayout2008.csproj
@@ -0,0 +1,71 @@
+
+
+
+ Debug
+ AnyCPU
+ 9.0.21022
+ 2.0
+ {916A9879-3F2D-471F-AAFA-28F0C8A7FFA9}
+ Library
+ Properties
+ Itenso.Windows.Controls.ListViewLayout
+ Itenso.Windows.Controls.ListViewLayout
+ v3.0
+ 512
+ {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ 4
+
+
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+ 3.0
+
+
+ 3.0
+
+
+ 3.0
+
+
+
+
+
+
+
+
+
+
+ Code
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ListViewLayout/ListViewLayoutManager.cs b/ListViewLayout/ListViewLayoutManager.cs
new file mode 100644
index 0000000..fa43031
--- /dev/null
+++ b/ListViewLayout/ListViewLayoutManager.cs
@@ -0,0 +1,609 @@
+// -- FILE ------------------------------------------------------------------
+// name : ListViewLayoutManager.cs
+// created : Jani Giannoudis - 2008.03.27
+// language : c#
+// environment: .NET 3.0
+// copyright : (c) 2008-2012 by Itenso GmbH, Switzerland
+// --------------------------------------------------------------------------
+using System;
+using System.Windows;
+using System.Windows.Media;
+using System.Windows.Input;
+using System.Windows.Controls;
+using System.Windows.Controls.Primitives;
+using System.ComponentModel;
+
+namespace Itenso.Windows.Controls.ListViewLayout
+{
+
+ // ------------------------------------------------------------------------
+ public class ListViewLayoutManager
+ {
+
+ // ----------------------------------------------------------------------
+ public static readonly DependencyProperty EnabledProperty = DependencyProperty.RegisterAttached(
+ "Enabled",
+ typeof( bool ),
+ typeof( ListViewLayoutManager ),
+ new FrameworkPropertyMetadata( new PropertyChangedCallback( OnLayoutManagerEnabledChanged ) ) );
+
+ // ----------------------------------------------------------------------
+ public ListViewLayoutManager( ListView listView )
+ {
+ if ( listView == null )
+ {
+ throw new ArgumentNullException( "listView" );
+ }
+
+ this.listView = listView;
+ this.listView.Loaded += new RoutedEventHandler( ListViewLoaded );
+ this.listView.Unloaded += new RoutedEventHandler( ListViewUnloaded );
+ } // ListViewLayoutManager
+
+ // ----------------------------------------------------------------------
+ public ListView ListView
+ {
+ get { return listView; }
+ } // ListView
+
+ // ----------------------------------------------------------------------
+ public ScrollBarVisibility VerticalScrollBarVisibility
+ {
+ get { return verticalScrollBarVisibility; }
+ set { verticalScrollBarVisibility = value; }
+ } // VerticalScrollBarVisibility
+
+ // ----------------------------------------------------------------------
+ public static void SetEnabled( DependencyObject dependencyObject, bool enabled )
+ {
+ dependencyObject.SetValue( EnabledProperty, enabled );
+ } // SetEnabled
+
+ // ----------------------------------------------------------------------
+ public void Refresh()
+ {
+ InitColumns();
+ DoResizeColumns();
+ } // Refresh
+
+ // ----------------------------------------------------------------------
+ private void RegisterEvents( DependencyObject start )
+ {
+ for ( int i = 0; i < VisualTreeHelper.GetChildrenCount( start ); i++ )
+ {
+ Visual childVisual = VisualTreeHelper.GetChild( start, i ) as Visual;
+ if ( childVisual is Thumb )
+ {
+ GridViewColumn gridViewColumn = FindParentColumn( childVisual );
+ if ( gridViewColumn != null )
+ {
+ Thumb thumb = childVisual as Thumb;
+ if ( ProportionalColumn.IsProportionalColumn( gridViewColumn ) ||
+ FixedColumn.IsFixedColumn( gridViewColumn ) || IsFillColumn( gridViewColumn ) )
+ {
+ thumb.IsHitTestVisible = false;
+ }
+ else
+ {
+ thumb.PreviewMouseMove += new MouseEventHandler( ThumbPreviewMouseMove );
+ thumb.PreviewMouseLeftButtonDown += new MouseButtonEventHandler( ThumbPreviewMouseLeftButtonDown );
+ DependencyPropertyDescriptor.FromProperty(
+ GridViewColumn.WidthProperty,
+ typeof( GridViewColumn ) ).AddValueChanged( gridViewColumn, GridColumnWidthChanged );
+ }
+ }
+ }
+ else if ( childVisual is GridViewColumnHeader )
+ {
+ GridViewColumnHeader columnHeader = childVisual as GridViewColumnHeader;
+ columnHeader.SizeChanged += new SizeChangedEventHandler( GridColumnHeaderSizeChanged );
+ }
+ else if ( scrollViewer == null && childVisual is ScrollViewer )
+ {
+ scrollViewer = childVisual as ScrollViewer;
+ scrollViewer.ScrollChanged += new ScrollChangedEventHandler( ScrollViewerScrollChanged );
+ // assume we do the regulation of the horizontal scrollbar
+ scrollViewer.HorizontalScrollBarVisibility = ScrollBarVisibility.Hidden;
+ scrollViewer.VerticalScrollBarVisibility = verticalScrollBarVisibility;
+ }
+
+ RegisterEvents( childVisual ); // recursive
+ }
+ } // RegisterEvents
+
+ // ----------------------------------------------------------------------
+ private void UnregisterEvents( DependencyObject start )
+ {
+ for ( int i = 0; i < VisualTreeHelper.GetChildrenCount( start ); i++ )
+ {
+ Visual childVisual = VisualTreeHelper.GetChild( start, i ) as Visual;
+ if ( childVisual is Thumb )
+ {
+ GridViewColumn gridViewColumn = FindParentColumn( childVisual );
+ if ( gridViewColumn != null )
+ {
+ Thumb thumb = childVisual as Thumb;
+ if ( ProportionalColumn.IsProportionalColumn( gridViewColumn ) ||
+ FixedColumn.IsFixedColumn( gridViewColumn ) || IsFillColumn( gridViewColumn ) )
+ {
+ thumb.IsHitTestVisible = true;
+ }
+ else
+ {
+ thumb.PreviewMouseMove -= new MouseEventHandler( ThumbPreviewMouseMove );
+ thumb.PreviewMouseLeftButtonDown -= new MouseButtonEventHandler( ThumbPreviewMouseLeftButtonDown );
+ DependencyPropertyDescriptor.FromProperty(
+ GridViewColumn.WidthProperty,
+ typeof( GridViewColumn ) ).RemoveValueChanged( gridViewColumn, GridColumnWidthChanged );
+ }
+ }
+ }
+ else if ( childVisual is GridViewColumnHeader )
+ {
+ GridViewColumnHeader columnHeader = childVisual as GridViewColumnHeader;
+ columnHeader.SizeChanged -= new SizeChangedEventHandler( GridColumnHeaderSizeChanged );
+ }
+ else if ( scrollViewer == null && childVisual is ScrollViewer )
+ {
+ scrollViewer = childVisual as ScrollViewer;
+ scrollViewer.ScrollChanged -= new ScrollChangedEventHandler( ScrollViewerScrollChanged );
+ }
+
+ UnregisterEvents( childVisual ); // recursive
+ }
+ } // UnregisterEvents
+
+ // ----------------------------------------------------------------------
+ private GridViewColumn FindParentColumn( DependencyObject element )
+ {
+ if ( element == null )
+ {
+ return null;
+ }
+
+ while ( element != null )
+ {
+ GridViewColumnHeader gridViewColumnHeader = element as GridViewColumnHeader;
+ if ( gridViewColumnHeader != null )
+ {
+ return ( gridViewColumnHeader ).Column;
+ }
+ element = VisualTreeHelper.GetParent( element );
+ }
+
+ return null;
+ } // FindParentColumn
+
+ // ----------------------------------------------------------------------
+ private GridViewColumnHeader FindColumnHeader( DependencyObject start, GridViewColumn gridViewColumn )
+ {
+ for ( int i = 0; i < VisualTreeHelper.GetChildrenCount( start ); i++ )
+ {
+ Visual childVisual = VisualTreeHelper.GetChild( start, i ) as Visual;
+ if ( childVisual is GridViewColumnHeader )
+ {
+ GridViewColumnHeader gridViewHeader = childVisual as GridViewColumnHeader;
+ if ( gridViewHeader.Column == gridViewColumn )
+ {
+ return gridViewHeader;
+ }
+ }
+ GridViewColumnHeader childGridViewHeader = FindColumnHeader( childVisual, gridViewColumn ); // recursive
+ if ( childGridViewHeader != null )
+ {
+ return childGridViewHeader;
+ }
+ }
+ return null;
+ } // FindColumnHeader
+
+ // ----------------------------------------------------------------------
+ private void InitColumns()
+ {
+ GridView view = listView.View as GridView;
+ if ( view == null )
+ {
+ return;
+ }
+
+ foreach ( GridViewColumn gridViewColumn in view.Columns )
+ {
+ if ( !RangeColumn.IsRangeColumn( gridViewColumn ) )
+ {
+ continue;
+ }
+
+ double? minWidth = RangeColumn.GetRangeMinWidth( gridViewColumn );
+ double? maxWidth = RangeColumn.GetRangeMaxWidth( gridViewColumn );
+ if ( !minWidth.HasValue && !maxWidth.HasValue )
+ {
+ continue;
+ }
+
+ GridViewColumnHeader columnHeader = FindColumnHeader( listView, gridViewColumn );
+ if ( columnHeader == null )
+ {
+ continue;
+ }
+
+ double actualWidth = columnHeader.ActualWidth;
+ if ( minWidth.HasValue )
+ {
+ columnHeader.MinWidth = minWidth.Value;
+ if ( !double.IsInfinity( actualWidth ) && actualWidth < columnHeader.MinWidth )
+ {
+ gridViewColumn.Width = columnHeader.MinWidth;
+ }
+ }
+ if ( maxWidth.HasValue )
+ {
+ columnHeader.MaxWidth = maxWidth.Value;
+ if ( !double.IsInfinity( actualWidth ) && actualWidth > columnHeader.MaxWidth )
+ {
+ gridViewColumn.Width = columnHeader.MaxWidth;
+ }
+ }
+ }
+ } // InitColumns
+
+ // ----------------------------------------------------------------------
+ protected virtual void ResizeColumns()
+ {
+ GridView view = listView.View as GridView;
+ if ( view == null || view.Columns.Count == 0 )
+ {
+ return;
+ }
+
+ // listview width
+ double actualWidth = double.PositiveInfinity;
+ if ( scrollViewer != null )
+ {
+ actualWidth = scrollViewer.ViewportWidth;
+ }
+ if ( double.IsInfinity( actualWidth ) )
+ {
+ actualWidth = listView.ActualWidth;
+ }
+ if ( double.IsInfinity( actualWidth ) || actualWidth <= 0 )
+ {
+ return;
+ }
+
+ double resizeableRegionCount = 0;
+ double otherColumnsWidth = 0;
+ // determine column sizes
+ foreach ( GridViewColumn gridViewColumn in view.Columns )
+ {
+ if ( ProportionalColumn.IsProportionalColumn( gridViewColumn ) )
+ {
+ double? proportionalWidth = ProportionalColumn.GetProportionalWidth( gridViewColumn );
+ if ( proportionalWidth != null )
+ {
+ resizeableRegionCount += proportionalWidth.Value;
+ }
+ }
+ else
+ {
+ otherColumnsWidth += gridViewColumn.ActualWidth;
+ }
+ }
+
+ if ( resizeableRegionCount <= 0 )
+ {
+ // no proportional columns present: commit the regulation to the scroll viewer
+ if ( scrollViewer != null )
+ {
+ scrollViewer.HorizontalScrollBarVisibility = ScrollBarVisibility.Auto;
+ }
+
+ // search the first fill column
+ GridViewColumn fillColumn = null;
+ for ( int i = 0; i < view.Columns.Count; i++ )
+ {
+ GridViewColumn gridViewColumn = view.Columns[ i ];
+ if ( IsFillColumn( gridViewColumn ) )
+ {
+ fillColumn = gridViewColumn;
+ break;
+ }
+ }
+
+ if ( fillColumn != null )
+ {
+ double otherColumnsWithoutFillWidth = otherColumnsWidth - fillColumn.ActualWidth;
+ double fillWidth = actualWidth - otherColumnsWithoutFillWidth;
+ if ( fillWidth > 0 )
+ {
+ double? minWidth = RangeColumn.GetRangeMinWidth( fillColumn );
+ double? maxWidth = RangeColumn.GetRangeMaxWidth( fillColumn );
+
+ bool setWidth = !( minWidth.HasValue && fillWidth < minWidth.Value );
+ if ( maxWidth.HasValue && fillWidth > maxWidth.Value )
+ {
+ setWidth = false;
+ }
+ if ( setWidth )
+ {
+ if ( scrollViewer != null )
+ {
+ scrollViewer.HorizontalScrollBarVisibility = ScrollBarVisibility.Hidden;
+ }
+ fillColumn.Width = fillWidth;
+ }
+ }
+ }
+ return;
+ }
+
+ double resizeableColumnsWidth = actualWidth - otherColumnsWidth;
+ if ( resizeableColumnsWidth <= 0 )
+ {
+ return; // missing space
+ }
+
+ // resize columns
+ double resizeableRegionWidth = resizeableColumnsWidth / resizeableRegionCount;
+ foreach ( GridViewColumn gridViewColumn in view.Columns )
+ {
+ if ( ProportionalColumn.IsProportionalColumn( gridViewColumn ) )
+ {
+ double? proportionalWidth = ProportionalColumn.GetProportionalWidth( gridViewColumn );
+ if ( proportionalWidth != null )
+ {
+ gridViewColumn.Width = proportionalWidth.Value * resizeableRegionWidth;
+ }
+ }
+ }
+ } // ResizeColumns
+
+ // ----------------------------------------------------------------------
+ // returns the delta
+ private double SetRangeColumnToBounds( GridViewColumn gridViewColumn )
+ {
+ double startWidth = gridViewColumn.Width;
+
+ double? minWidth = RangeColumn.GetRangeMinWidth( gridViewColumn );
+ double? maxWidth = RangeColumn.GetRangeMaxWidth( gridViewColumn );
+
+ if ( ( minWidth.HasValue && maxWidth.HasValue ) && ( minWidth > maxWidth ) )
+ {
+ return 0; // invalid case
+ }
+
+ if ( minWidth.HasValue && gridViewColumn.Width < minWidth.Value )
+ {
+ gridViewColumn.Width = minWidth.Value;
+ }
+ else if ( maxWidth.HasValue && gridViewColumn.Width > maxWidth.Value )
+ {
+ gridViewColumn.Width = maxWidth.Value;
+ }
+
+ return gridViewColumn.Width - startWidth;
+ } // SetRangeColumnToBounds
+
+ // ----------------------------------------------------------------------
+ private bool IsFillColumn( GridViewColumn gridViewColumn )
+ {
+ if ( gridViewColumn == null )
+ {
+ return false;
+ }
+
+ GridView view = listView.View as GridView;
+ if ( view == null || view.Columns.Count == 0 )
+ {
+ return false;
+ }
+
+ bool? isFillColumn = RangeColumn.GetRangeIsFillColumn( gridViewColumn );
+ return isFillColumn.HasValue && isFillColumn.Value;
+ } // IsFillColumn
+
+ // ----------------------------------------------------------------------
+ private void DoResizeColumns()
+ {
+ if ( resizing )
+ {
+ return;
+ }
+
+ resizing = true;
+ try
+ {
+ ResizeColumns();
+ }
+ finally
+ {
+ resizing = false;
+ }
+ } // DoResizeColumns
+
+ // ----------------------------------------------------------------------
+ private void ListViewLoaded( object sender, RoutedEventArgs e )
+ {
+ RegisterEvents( listView );
+ InitColumns();
+ DoResizeColumns();
+ loaded = true;
+ } // ListViewLoaded
+
+ // ----------------------------------------------------------------------
+ private void ListViewUnloaded( object sender, RoutedEventArgs e )
+ {
+ if ( !loaded )
+ {
+ return;
+ }
+ UnregisterEvents( listView );
+ loaded = false;
+ } // ListViewUnloaded
+
+ // ----------------------------------------------------------------------
+ private void ThumbPreviewMouseMove( object sender, MouseEventArgs e )
+ {
+ Thumb thumb = sender as Thumb;
+ if ( thumb == null )
+ {
+ return;
+ }
+ GridViewColumn gridViewColumn = FindParentColumn( thumb );
+ if ( gridViewColumn == null )
+ {
+ return;
+ }
+
+ // suppress column resizing for proportional, fixed and range fill columns
+ if ( ProportionalColumn.IsProportionalColumn( gridViewColumn ) ||
+ FixedColumn.IsFixedColumn( gridViewColumn ) ||
+ IsFillColumn( gridViewColumn ) )
+ {
+ thumb.Cursor = null;
+ return;
+ }
+
+ // check range column bounds
+ if ( thumb.IsMouseCaptured && RangeColumn.IsRangeColumn( gridViewColumn ) )
+ {
+ double? minWidth = RangeColumn.GetRangeMinWidth( gridViewColumn );
+ double? maxWidth = RangeColumn.GetRangeMaxWidth( gridViewColumn );
+
+ if ( ( minWidth.HasValue && maxWidth.HasValue ) && ( minWidth > maxWidth ) )
+ {
+ return; // invalid case
+ }
+
+ if ( resizeCursor == null )
+ {
+ resizeCursor = thumb.Cursor; // save the resize cursor
+ }
+
+ if ( minWidth.HasValue && gridViewColumn.Width <= minWidth.Value )
+ {
+ thumb.Cursor = Cursors.No;
+ }
+ else if ( maxWidth.HasValue && gridViewColumn.Width >= maxWidth.Value )
+ {
+ thumb.Cursor = Cursors.No;
+ }
+ else
+ {
+ thumb.Cursor = resizeCursor; // between valid min/max
+ }
+ }
+ } // ThumbPreviewMouseMove
+
+ // ----------------------------------------------------------------------
+ private void ThumbPreviewMouseLeftButtonDown( object sender, MouseButtonEventArgs e )
+ {
+ Thumb thumb = sender as Thumb;
+ GridViewColumn gridViewColumn = FindParentColumn( thumb );
+
+ // suppress column resizing for proportional, fixed and range fill columns
+ if ( ProportionalColumn.IsProportionalColumn( gridViewColumn ) ||
+ FixedColumn.IsFixedColumn( gridViewColumn ) ||
+ IsFillColumn( gridViewColumn ) )
+ {
+ e.Handled = true;
+ }
+ } // ThumbPreviewMouseLeftButtonDown
+
+ // ----------------------------------------------------------------------
+ private void GridColumnWidthChanged( object sender, EventArgs e )
+ {
+ if ( !loaded )
+ {
+ return;
+ }
+
+ GridViewColumn gridViewColumn = sender as GridViewColumn;
+
+ // suppress column resizing for proportional and fixed columns
+ if ( ProportionalColumn.IsProportionalColumn( gridViewColumn ) || FixedColumn.IsFixedColumn( gridViewColumn ) )
+ {
+ return;
+ }
+
+ // ensure range column within the bounds
+ if ( RangeColumn.IsRangeColumn( gridViewColumn ) )
+ {
+ // special case: auto column width - maybe conflicts with min/max range
+ if ( gridViewColumn != null && gridViewColumn.Width.Equals( double.NaN ) )
+ {
+ autoSizedColumn = gridViewColumn;
+ return; // handled by the change header size event
+ }
+
+ // ensure column bounds
+ if ( Math.Abs( SetRangeColumnToBounds( gridViewColumn ) - 0 ) > zeroWidthRange )
+ {
+ return;
+ }
+ }
+
+ DoResizeColumns();
+ } // GridColumnWidthChanged
+
+ // ----------------------------------------------------------------------
+ // handle autosized column
+ private void GridColumnHeaderSizeChanged( object sender, SizeChangedEventArgs e )
+ {
+ if ( autoSizedColumn == null )
+ {
+ return;
+ }
+
+ GridViewColumnHeader gridViewColumnHeader = sender as GridViewColumnHeader;
+ if ( gridViewColumnHeader != null && gridViewColumnHeader.Column == autoSizedColumn )
+ {
+ if ( gridViewColumnHeader.Width.Equals( double.NaN ) )
+ {
+ // sync column with
+ gridViewColumnHeader.Column.Width = gridViewColumnHeader.ActualWidth;
+ DoResizeColumns();
+ }
+
+ autoSizedColumn = null;
+ }
+ } // GridColumnHeaderSizeChanged
+
+ // ----------------------------------------------------------------------
+ private void ScrollViewerScrollChanged( object sender, ScrollChangedEventArgs e )
+ {
+ if ( loaded && Math.Abs( e.ViewportWidthChange - 0 ) > zeroWidthRange )
+ {
+ DoResizeColumns();
+ }
+ } // ScrollViewerScrollChanged
+
+ // ----------------------------------------------------------------------
+ private static void OnLayoutManagerEnabledChanged( DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e )
+ {
+ ListView listView = dependencyObject as ListView;
+ if ( listView != null )
+ {
+ bool enabled = (bool)e.NewValue;
+ if ( enabled )
+ {
+ new ListViewLayoutManager( listView );
+ }
+ }
+ } // OnLayoutManagerEnabledChanged
+
+ // ----------------------------------------------------------------------
+ // members
+ private readonly ListView listView;
+ private ScrollViewer scrollViewer;
+ private bool loaded;
+ private bool resizing;
+ private Cursor resizeCursor;
+ private ScrollBarVisibility verticalScrollBarVisibility = ScrollBarVisibility.Auto;
+ private GridViewColumn autoSizedColumn;
+
+ private const double zeroWidthRange = 0.1;
+
+ } // class ListViewLayoutManager
+
+} // namespace Itenso.Windows.Controls.ListViewLayout
+// -- EOF -------------------------------------------------------------------
diff --git a/ListViewLayout/Properties/AssemblyInfo.cs b/ListViewLayout/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..9dcce7b
--- /dev/null
+++ b/ListViewLayout/Properties/AssemblyInfo.cs
@@ -0,0 +1,30 @@
+// -- FILE ------------------------------------------------------------------
+// name : AssemblyInfo.cs
+// created : Jani Giannoudis - 2008.04.03
+// language : c#
+// environment: .NET 3.0
+// copyright : (c) 2008-2012 by Itenso GmbH, Switzerland
+// --------------------------------------------------------------------------
+using System;
+using System.Reflection;
+using System.Runtime.InteropServices;
+using System.Windows;
+
+[assembly: AssemblyTitle( "ListViewLayout" )]
+[assembly: AssemblyDescription( "" )]
+[assembly: AssemblyConfiguration( "" )]
+[assembly: AssemblyCompany( "Itenso GmbH" )]
+[assembly: AssemblyProduct( "ListViewLayout" )]
+[assembly: AssemblyCopyright( "Copyright © Itenso GmbH 2008-2012" )]
+[assembly: AssemblyTrademark( "" )]
+[assembly: AssemblyCulture( "" )]
+[assembly: ComVisible( false )]
+[assembly: CLSCompliant( true )]
+[assembly: ThemeInfo(
+ ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
+ ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
+)]
+[assembly: AssemblyVersion( "1.3.0.0" )]
+[assembly: AssemblyFileVersion( "1.3.0.0" )]
+
+// -- EOF -------------------------------------------------------------------
diff --git a/ListViewLayout/ProportionalColumn.cs b/ListViewLayout/ProportionalColumn.cs
new file mode 100644
index 0000000..3023c97
--- /dev/null
+++ b/ListViewLayout/ProportionalColumn.cs
@@ -0,0 +1,68 @@
+// -- FILE ------------------------------------------------------------------
+// name : ProportionalColumn.cs
+// created : Jani Giannoudis - 2008.03.27
+// language : c#
+// environment: .NET 3.0
+// copyright : (c) 2008-2012 by Itenso GmbH, Switzerland
+// --------------------------------------------------------------------------
+using System.Windows;
+using System.Windows.Controls;
+
+namespace Itenso.Windows.Controls.ListViewLayout
+{
+
+ // ------------------------------------------------------------------------
+ public sealed class ProportionalColumn : LayoutColumn
+ {
+
+ // ----------------------------------------------------------------------
+ public static readonly DependencyProperty WidthProperty =
+ DependencyProperty.RegisterAttached(
+ "Width",
+ typeof( double ),
+ typeof( ProportionalColumn ) );
+
+ // ----------------------------------------------------------------------
+ private ProportionalColumn()
+ {
+ } // ProportionalColumn
+
+ // ----------------------------------------------------------------------
+ public static double GetWidth( DependencyObject obj )
+ {
+ return (double)obj.GetValue( WidthProperty );
+ } // GetWidth
+
+ // ----------------------------------------------------------------------
+ public static void SetWidth( DependencyObject obj, double width )
+ {
+ obj.SetValue( WidthProperty, width );
+ } // SetWidth
+
+ // ----------------------------------------------------------------------
+ public static bool IsProportionalColumn( GridViewColumn column )
+ {
+ if ( column == null )
+ {
+ return false;
+ }
+ return HasPropertyValue( column, WidthProperty );
+ } // IsProportionalColumn
+
+ // ----------------------------------------------------------------------
+ public static double? GetProportionalWidth( GridViewColumn column )
+ {
+ return GetColumnWidth( column, WidthProperty );
+ } // GetProportionalWidth
+
+ // ----------------------------------------------------------------------
+ public static GridViewColumn ApplyWidth( GridViewColumn gridViewColumn, double width )
+ {
+ SetWidth( gridViewColumn, width );
+ return gridViewColumn;
+ } // ApplyWidth
+
+ } // class ProportionalColumn
+
+} // namespace Itenso.Windows.Controls.ListViewLayout
+// -- EOF -------------------------------------------------------------------
diff --git a/ListViewLayout/RangeColumn.cs b/ListViewLayout/RangeColumn.cs
new file mode 100644
index 0000000..331f6d1
--- /dev/null
+++ b/ListViewLayout/RangeColumn.cs
@@ -0,0 +1,143 @@
+// -- FILE ------------------------------------------------------------------
+// name : RangeColumn.cs
+// created : Jani Giannoudis - 2008.03.27
+// language : c#
+// environment: .NET 3.0
+// copyright : (c) 2008-2012 by Itenso GmbH, Switzerland
+// --------------------------------------------------------------------------
+using System;
+using System.Windows;
+using System.Windows.Controls;
+
+namespace Itenso.Windows.Controls.ListViewLayout
+{
+
+ // ------------------------------------------------------------------------
+ public sealed class RangeColumn : LayoutColumn
+ {
+
+ // ----------------------------------------------------------------------
+ public static readonly DependencyProperty MinWidthProperty =
+ DependencyProperty.RegisterAttached(
+ "MinWidth",
+ typeof( double ),
+ typeof( RangeColumn ) );
+
+ // ----------------------------------------------------------------------
+ public static readonly DependencyProperty MaxWidthProperty =
+ DependencyProperty.RegisterAttached(
+ "MaxWidth",
+ typeof( double ),
+ typeof( RangeColumn ) );
+
+ // ----------------------------------------------------------------------
+ public static readonly DependencyProperty IsFillColumnProperty =
+ DependencyProperty.RegisterAttached(
+ "IsFillColumn",
+ typeof( bool ),
+ typeof( RangeColumn ) );
+
+ // ----------------------------------------------------------------------
+ private RangeColumn()
+ {
+ } // RangeColumn
+
+ // ----------------------------------------------------------------------
+ public static double GetMinWidth( DependencyObject obj )
+ {
+ return (double)obj.GetValue( MinWidthProperty );
+ } // GetMinWidth
+
+ // ----------------------------------------------------------------------
+ public static void SetMinWidth( DependencyObject obj, double minWidth )
+ {
+ obj.SetValue( MinWidthProperty, minWidth );
+ } // SetMinWidth
+
+ // ----------------------------------------------------------------------
+ public static double GetMaxWidth( DependencyObject obj )
+ {
+ return (double)obj.GetValue( MaxWidthProperty );
+ } // GetMaxWidth
+
+ // ----------------------------------------------------------------------
+ public static void SetMaxWidth( DependencyObject obj, double maxWidth )
+ {
+ obj.SetValue( MaxWidthProperty, maxWidth );
+ } // SetMaxWidth
+
+ // ----------------------------------------------------------------------
+ public static bool GetIsFillColumn( DependencyObject obj )
+ {
+ return (bool)obj.GetValue( IsFillColumnProperty );
+ } // GetIsFillColumn
+
+ // ----------------------------------------------------------------------
+ public static void SetIsFillColumn( DependencyObject obj, bool isFillColumn )
+ {
+ obj.SetValue( IsFillColumnProperty, isFillColumn );
+ } // SetIsFillColumn
+
+ // ----------------------------------------------------------------------
+ public static bool IsRangeColumn( GridViewColumn column )
+ {
+ if ( column == null )
+ {
+ return false;
+ }
+ return
+ HasPropertyValue( column, MinWidthProperty ) ||
+ HasPropertyValue( column, MaxWidthProperty ) ||
+ HasPropertyValue( column, IsFillColumnProperty );
+ } // IsRangeColumn
+
+ // ----------------------------------------------------------------------
+ public static double? GetRangeMinWidth( GridViewColumn column )
+ {
+ return GetColumnWidth( column, MinWidthProperty );
+ } // GetRangeMinWidth
+
+ // ----------------------------------------------------------------------
+ public static double? GetRangeMaxWidth( GridViewColumn column )
+ {
+ return GetColumnWidth( column, MaxWidthProperty );
+ } // GetRangeMaxWidth
+
+ // ----------------------------------------------------------------------
+ public static bool? GetRangeIsFillColumn( GridViewColumn column )
+ {
+ if ( column == null )
+ {
+ throw new ArgumentNullException( "column" );
+ }
+ object value = column.ReadLocalValue( IsFillColumnProperty );
+ if ( value != null && value.GetType() == IsFillColumnProperty.PropertyType )
+ {
+ return (bool)value;
+ }
+
+ return null;
+ } // GetRangeIsFillColumn
+
+ // ----------------------------------------------------------------------
+ public static GridViewColumn ApplyWidth( GridViewColumn gridViewColumn, double minWidth,
+ double width, double maxWidth )
+ {
+ return ApplyWidth( gridViewColumn, minWidth, width, maxWidth, false );
+ } // ApplyWidth
+
+ // ----------------------------------------------------------------------
+ public static GridViewColumn ApplyWidth( GridViewColumn gridViewColumn, double minWidth,
+ double width, double maxWidth, bool isFillColumn )
+ {
+ SetMinWidth( gridViewColumn, minWidth );
+ gridViewColumn.Width = width;
+ SetMaxWidth( gridViewColumn, maxWidth );
+ SetIsFillColumn( gridViewColumn, isFillColumn );
+ return gridViewColumn;
+ } // ApplyWidth
+
+ } // class RangeColumn
+
+} // namespace Itenso.Windows.Controls.ListViewLayout
+// -- EOF -------------------------------------------------------------------
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..0f20c35
--- /dev/null
+++ b/README.md
@@ -0,0 +1,52 @@
+# Larkator : ARK Dino Finder
+
+> Reads your saved ARK. Finds your dinos.
+
+### What is is?
+
+Need help finding your next high-stat tame for better dino breeding?
+Larkator uses your ARK save file to help you find both wild and tamed creatures.
+
+![Larkator Screenshot](Assets/screenshot.png)
+
+### Requirements
+
+Larkator requires **ark-tools** to be installed (or at least, extracted).
+On first run Larkator will ask you to locate it.
+You can get it from the [Steam forum post](https://survivetheark.com/index.php?/forums/topic/80750-ark-tools-v064-tools-for-reading-and-manipulating-ark-savegame-files/),
+or directly from [Qowyn/ark-tools](https://github.com/Qowyn/ark-tools/releases).
+
+### Features
+
+ - Find both wild and tamed creatures
+ - Filter based on species, gender, min and max levels
+ - Show the results on a map with full corrdinates
+ - Helps you find your lost tames
+ - Creature stats are shown to help you find that elusive next tame
+ - Automatically re-reads your save file when it changes
+
+Currently only has a map for the island, although it will load any ARK.
+
+### Tips
+
+ - Use your mouse's scroll wheel over a search filter's gender and level seletor to change them
+ - While ingame, use the command `saveworld` to force the game to save - Larkator will update immediately
+
+### Limitations
+
+Larkator is already very useful, but it is very new and is limited in some ways.
+
+ - Only has a map for the island, so far
+ - If you have too many searches the list will not scroll
+ - The results list is not scrollable either
+ - No new Aberation stats are shown
+
+### License
+
+Larkator is released under the MIT license and contributions are encouraged.
+
+### Thanks
+
+Thanks to [Roland Firmont (Qowyn)](https://github.com/Qowyn) for ark-tools, without which this tool would not be possible.
+
+The project includes [ListView Layout Manager](https://www.codeproject.com/Articles/25058/ListView-Layout-Manager) from Jani Giannoudis.