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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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.