diff --git a/App.config b/App.config
new file mode 100644
index 0000000..56efbc7
--- /dev/null
+++ b/App.config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/App.xaml b/App.xaml
new file mode 100644
index 0000000..041c236
--- /dev/null
+++ b/App.xaml
@@ -0,0 +1,9 @@
+
+
+
+
+
diff --git a/App.xaml.cs b/App.xaml.cs
new file mode 100644
index 0000000..e7ace89
--- /dev/null
+++ b/App.xaml.cs
@@ -0,0 +1,18 @@
+using System;
+using System.Collections.Generic;
+using System.Configuration;
+using System.Data;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Windows;
+using NonaBGS.Journal;
+
+namespace NonaBGS
+{
+ ///
+ /// Interaction logic for App.xaml
+ ///
+ public partial class NonaBGSApplication : Application
+ {
+ }
+}
diff --git a/BGS/Cartographics.cs b/BGS/Cartographics.cs
new file mode 100644
index 0000000..c0e2cdf
--- /dev/null
+++ b/BGS/Cartographics.cs
@@ -0,0 +1,38 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Globalization;
+using System.Threading.Tasks;
+using NonaBGS.Journal;
+
+namespace NonaBGS.BGS {
+ public class Cartographics : LogEntry {
+ public Cartographics(MultiSellExplorationDataEntry e, string current_system, string current_station) {
+ this.Entries.Add(e);
+ this.System = current_system;
+ this.Station = current_station;
+ }
+
+ public int TotalSum {
+ get {
+ return (from entry in Entries
+ where entry.Is(Events.MultiSellExplorationData)
+ select (entry as MultiSellExplorationDataEntry).TotalEarnings)
+ .Sum()
+ ;
+ }
+ }
+
+ public override string ToString() {
+ StringBuilder builder = new StringBuilder();
+ builder.AppendFormat("Sold {0} CR worth of Cartographics Data", TotalSum);
+ return builder.ToString();
+ }
+
+ ///
+ /// Cartographics only help the controlling faction.
+ ///
+ public override bool OnlyControllingFaction => true;
+ }
+}
diff --git a/BGS/CombatZone.cs b/BGS/CombatZone.cs
new file mode 100644
index 0000000..4c009fa
--- /dev/null
+++ b/BGS/CombatZone.cs
@@ -0,0 +1,11 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace NonaBGS.BGS {
+ public class CombatZone {
+ private int level;
+ }
+}
diff --git a/BGS/DiscordLogGenerator.cs b/BGS/DiscordLogGenerator.cs
new file mode 100644
index 0000000..40974e2
--- /dev/null
+++ b/BGS/DiscordLogGenerator.cs
@@ -0,0 +1,11 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace NonaBGS.BGS {
+ public interface IDiscordLogGenerator {
+ string GenerateDiscordLog(Report report);
+ }
+}
diff --git a/BGS/LogEntry.cs b/BGS/LogEntry.cs
new file mode 100644
index 0000000..8aa3c76
--- /dev/null
+++ b/BGS/LogEntry.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using NonaBGS.Journal;
+
+namespace NonaBGS.BGS {
+ public class LogEntry {
+ private List entries = new List();
+
+ public List Entries => entries;
+ public string Station { get; set; }
+ public string System { get; set; }
+ public string Faction { get; set; }
+
+ ///
+ /// Whether this entry only benefits the controlling faction or not, default: no
+ ///
+ public virtual bool OnlyControllingFaction {
+ get { return false; }
+ }
+ }
+}
diff --git a/BGS/MissionCompleted.cs b/BGS/MissionCompleted.cs
new file mode 100644
index 0000000..3e29a24
--- /dev/null
+++ b/BGS/MissionCompleted.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using NonaBGS.Journal;
+using Newtonsoft.Json.Linq;
+
+namespace NonaBGS.BGS {
+ public class MissionCompleted : LogEntry {
+ public MissionCompleted(MissionCompletedEntry e, string system, string station) {
+ this.Entries.Add(e);
+ this.Faction = e.JSON.GetValue("Faction").ToString();
+ this.System = system;
+ this.Station = station;
+ }
+
+ public string MissionName {
+ get { return (Entries[0] as MissionCompletedEntry).HumanReadableName; }
+ }
+
+ public string Influence {
+ get { return (Entries[0] as MissionCompletedEntry).GetInfluenceForFaction(Faction); }
+ }
+
+ public override string ToString() {
+ if (Entries.Count <= 0) {
+ return "";
+ }
+ StringBuilder builder = new StringBuilder();
+ var entry = Entries[0] as MissionCompletedEntry;
+ var influence = entry.GetInfluenceForFaction(Faction);
+
+ builder.AppendFormat("{0}", entry.HumanReadableName);
+ if (influence != "") {
+ builder.AppendFormat(", Influence: {0}", influence);
+ }
+
+ return builder.ToString();
+ }
+ }
+}
diff --git a/BGS/NonaDiscordLog.cs b/BGS/NonaDiscordLog.cs
new file mode 100644
index 0000000..1fbf8d4
--- /dev/null
+++ b/BGS/NonaDiscordLog.cs
@@ -0,0 +1,158 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Globalization;
+using System.Threading.Tasks;
+using NonaBGS.Journal;
+
+namespace NonaBGS.BGS {
+ public class NonaDiscordLog : IDiscordLogGenerator {
+ private string FormatDate() {
+ StringBuilder date = new StringBuilder();
+ DateTime today = DateTime.Now;
+ string suffix;
+
+ if (today.Day == 1 || today.Day == 21 || today.Day == 31) {
+ suffix = "st";
+ } else if (today.Day == 2 || today.Day == 22) {
+ suffix = "nd";
+ } else {
+ suffix = "th";
+ }
+
+ date.AppendFormat("{0} {1}{2}, {3}",
+ today.ToString("MMMM"), today.Day, suffix,
+ today.Year + EliteDangerous.YearOffset
+ );
+
+ return date.ToString();
+ }
+
+ private string BuildCartoGraphics(Objective objective) {
+ var total = from entries in objective.LogEntries
+ where entries.GetType() == typeof(Cartographics)
+ select entries
+ ;
+ var pages = total.Count();
+ var sum = total.Sum(x => (x as Cartographics).TotalSum);
+
+ if (pages <= 0 || sum <= 0) {
+ return "";
+ }
+
+ return string.Format("Sold {0} page(s) worth of universal cartographics\n" +
+ "(Total value: {1} CR)\n", pages, sum);
+ }
+
+ private string BuildMicroResourcesSold(Objective objective) {
+ var total = from entries in objective.LogEntries
+ where entries.GetType() == typeof(SellMicroResources)
+ select entries
+ ;
+ var sum = total.Sum(x => (x as SellMicroResources).TotalSum);
+
+ if (sum <= 0) {
+ return "";
+ }
+
+ return string.Format("Sold {0} CR worth of Micro Resources\n", sum);
+ }
+
+ private string BuildVouchers(Objective objective) {
+ StringBuilder builder = new StringBuilder();
+ var missions = from entries in objective.LogEntries
+ where entries.GetType() == typeof(Vouchers)
+ select entries
+ ;
+
+ if (missions == null || missions.Count() <= 0) {
+ return "";
+ }
+
+ foreach (var mission in missions) {
+ var m = mission as Vouchers;
+ builder.AppendFormat("Handed in {0} vouchers for {1}\n", m.Type, m.Faction);
+ builder.AppendFormat("(Total value: {0} CR)\n", m.TotalSum);
+ builder.AppendFormat("\n");
+ }
+
+ return builder.ToString();
+ }
+
+ private string BuildMissionList(Objective objective) {
+ Dictionary> collated = new Dictionary>();
+ StringBuilder output = new StringBuilder();
+
+ var missions = from entries in objective.LogEntries
+ where entries.GetType() == typeof(MissionCompleted)
+ select entries
+ ;
+
+ if (missions == null) {
+ return "";
+ }
+
+ foreach (MissionCompleted m in missions) {
+ if (!collated.ContainsKey(m.MissionName)) {
+ collated[m.MissionName] = new Dictionary();
+ }
+ if (!collated[m.MissionName].ContainsKey(m.Influence)) {
+ collated[m.MissionName][m.Influence] = 0;
+ }
+
+ ++collated[m.MissionName][m.Influence];
+ }
+
+ foreach (var mission in collated) {
+ if (objective.Faction != null) {
+ output.AppendFormat("{0} for {1}\n", mission.Key, objective.Faction);
+ } else {
+ output.AppendFormat("{0}\n", mission.Key);
+ }
+ output.Append("(");
+ foreach (var influence in mission.Value.OrderBy(x => x.Key.Length)) {
+ output.AppendFormat("Inf{0} x{1}, ", influence.Key, influence.Value);
+ }
+ output.Remove(output.Length - 2, 2); // remove last ", "
+ output.Append(")\n\n");
+ }
+
+ return output.ToString();
+ }
+
+ public string GenerateDiscordLog(Report report) {
+ StringBuilder log = new StringBuilder();
+
+ log.AppendFormat(":clock2: `Date:` {0}\n", FormatDate());
+ foreach (var objective in report.Objectives) {
+ if (objective.LogEntries.Count <= 0) {
+ continue;
+ }
+
+ log.AppendFormat(":globe_with_meridians: `Location:` {0}\n", objective.ToShortString());
+ log.Append(":clipboard: `Conducted:`\n");
+ log.Append("```\n");
+
+ StringBuilder entries = new StringBuilder();
+
+ var missions = BuildMissionList(objective);
+ entries.Append(missions);
+
+ var vouchers = BuildVouchers(objective);
+ entries.Append(vouchers);
+
+ var carto = BuildCartoGraphics(objective);
+ entries.Append(carto);
+
+ var micro = BuildMicroResourcesSold(objective);
+ entries.Append(micro);
+
+ log.Append(entries.ToString().Trim());
+ log.Append("\n```\n");
+ }
+
+ return log.ToString();
+ }
+ }
+}
diff --git a/BGS/Objective.cs b/BGS/Objective.cs
new file mode 100644
index 0000000..aee1b17
--- /dev/null
+++ b/BGS/Objective.cs
@@ -0,0 +1,103 @@
+using System.Collections.Generic;
+using System.Text;
+using Newtonsoft.Json;
+
+namespace NonaBGS.BGS {
+ public class Objective {
+ private string system;
+ private string station;
+ private string faction;
+
+ private List entries = new List();
+
+ [JsonIgnore]
+ public List LogEntries {
+ get => entries;
+ set => entries = value;
+ }
+
+ public int Matches(LogEntry e) {
+ int match_count = 0;
+
+ if (e.OnlyControllingFaction) {
+ if (Faction == null || (e.Faction != Faction)) {
+ return 0;
+ }
+ }
+
+ if (e.System != null && system != null &&
+ e.System == system) {
+ ++match_count;
+ }
+
+ if (e.Station != null && station != null &&
+ e.Station == station) {
+ ++match_count;
+ }
+
+ if (e.Faction != null && faction != null &&
+ e.Faction == faction) {
+ ++match_count;
+ }
+
+ return match_count;
+ }
+
+ public int CompareTo(Objective other) {
+ return (other.System == System &&
+ other.Station == Station &&
+ other.Faction == Faction) ? 0 : -1;
+ }
+
+ public bool IsValid => System != null && Faction != null;
+
+ public string System {
+ get { return system; }
+ set { system = value; }
+ }
+
+ public string Station {
+ get { return station; }
+ set { station = value; }
+ }
+
+ public string Faction {
+ get { return faction; }
+ set { faction = value; }
+ }
+
+ public override string ToString() {
+ StringBuilder str = new StringBuilder();
+ if (system != null && system.Length > 0) {
+ str.AppendFormat("System: {0}", system);
+ }
+ if (station != null && station.Length > 0) {
+ if (str.Length > 0) {
+ str.Append(", ");
+ }
+ str.AppendFormat("Station: {0}", station);
+ }
+ if (faction != null && faction.Length > 0) {
+ if (str.Length > 0) {
+ str.Append(", ");
+ }
+ str.AppendFormat("Faction: {0}", faction);
+ }
+ return str.ToString();
+ }
+
+ public string ToShortString() {
+ StringBuilder str = new StringBuilder();
+ if (system != null && system.Length > 0) {
+ str.AppendFormat("{0}", system);
+ }
+ if (station != null && station.Length > 0) {
+ if (str.Length > 0) {
+ str.Append(", ");
+ }
+ str.AppendFormat("{0}", station);
+ }
+ return str.ToString();
+ }
+ }
+}
diff --git a/BGS/Report.cs b/BGS/Report.cs
new file mode 100644
index 0000000..f6c42fb
--- /dev/null
+++ b/BGS/Report.cs
@@ -0,0 +1,121 @@
+using System;
+using System.ComponentModel;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using NonaBGS.Journal;
+
+namespace NonaBGS.BGS {
+ public class Report {
+ private List objectives = new List();
+
+ public delegate void OnLogDelegate(string log);
+
+ public event OnLogDelegate OnLog;
+
+ public List Objectives {
+ get { return objectives; }
+ set { objectives = value; }
+ }
+
+ public bool AddObjective(Objective objective) {
+ var found = objectives.Find(x => x == objective);
+ bool added = false;
+
+ if (found == null) {
+ objectives.Add(objective);
+ added = true;
+ }
+
+ return added;
+ }
+
+ private bool IsRelevant(Entry e) {
+ return e.Is(Events.MissionCompleted) ||
+ e.Is(Events.Docked) ||
+ e.Is(Events.FSDJump) ||
+ e.Is(Events.MultiSellExplorationData) ||
+ e.Is(Events.SellMicroResources) ||
+ e.Is(Events.RedeemVoucher)
+ ;
+ }
+
+ public void Scan(PlayerJournal journal, DateTime start, DateTime end) {
+ var entries = from file in journal.Files
+ where file.NormalisedTimestamp >= start && file.NormalisedTimestamp <= end
+ select file.Entries
+ ;
+ var relevant = from log in entries.SelectMany(array => array)
+ where IsRelevant(log)
+ select log
+ ;
+
+ string current_system = null;
+ string current_station = null;
+ string controlling_faction = null;
+
+ this.objectives.ForEach(x => x.LogEntries.Clear());
+
+ foreach (var e in relevant) {
+ LogEntry entry = null;
+
+ if (e.Is(Events.Docked)) {
+ /* gleem the current station from this message
+ */
+ current_station = (e as DockedEntry).StationName;
+ } else if (e.Is(Events.FSDJump)) {
+ current_system = (e as FSDJumpEntry).StarSystem;
+ controlling_faction = (e as FSDJumpEntry).SystemFaction;
+ } else if (e.Is(Events.MissionCompleted)) {
+ var completed = e as MissionCompletedEntry;
+ entry = new MissionCompleted(completed, current_system, current_station);
+ if (completed.HumanReadableNameWasGenerated) {
+ OnLog?.Invoke("Human readable name for mission \"" +
+ completed.Name +
+ "\" was generated, please report this.");
+ }
+ } else if (e.Is(Events.MultiSellExplorationData)) {
+ entry = new Cartographics(e as MultiSellExplorationDataEntry, current_system, current_station);
+ entry.Faction = controlling_faction;
+ } else if (e.Is(Events.RedeemVoucher)) {
+ entry = new Vouchers();
+ entry.Entries.Add(e);
+ entry.System = current_system;
+ entry.Station = current_station;
+ entry.Faction = controlling_faction;
+ } else if (e.Is(Events.SellMicroResources)) {
+ entry = new SellMicroResources(current_system, current_station);
+ entry.Entries.Add(e);
+ }
+
+ if (entry == null) {
+ continue;
+ }
+
+ var matches = objectives
+ .Where(x => x.Matches(entry) > 0)
+ .OrderBy(x => x.Matches(entry))
+ ;
+ if (matches == null || matches.Count() <= 0) {
+ continue;
+ }
+
+ var objective = matches
+ .OrderBy(x => x.Matches(entry))
+ .Reverse()
+ .First()
+ ;
+
+ if (objective != null) {
+ objective.LogEntries.Add(entry);
+ }
+ }
+ }
+
+ public void Scan(PlayerJournal journal) {
+ Scan(journal, DateTime.Now, DateTime.Now);
+ }
+ }
+}
diff --git a/BGS/SellMicroResources.cs b/BGS/SellMicroResources.cs
new file mode 100644
index 0000000..5daa3e3
--- /dev/null
+++ b/BGS/SellMicroResources.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using NonaBGS.Journal;
+
+namespace NonaBGS.BGS {
+ public class SellMicroResources : LogEntry {
+ public SellMicroResources(string system, string station) {
+ System = system;
+ Station = Station;
+ }
+
+ public int TotalSum {
+ get {
+ return Entries
+ .Where(x => x.GetType() == typeof(SellMicroResourcesEntry))
+ .Sum(x => (x as SellMicroResourcesEntry).Price)
+ ;
+ }
+ }
+
+ public override string ToString() {
+ return string.Format("Sell Micro Resources: {0} CR", TotalSum);
+ }
+ }
+}
diff --git a/BGS/Vouchers.cs b/BGS/Vouchers.cs
new file mode 100644
index 0000000..b5832d8
--- /dev/null
+++ b/BGS/Vouchers.cs
@@ -0,0 +1,44 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using NonaBGS.Journal;
+
+namespace NonaBGS.BGS {
+ public class Vouchers : LogEntry {
+
+ public int TotalSum {
+ get {
+ return Entries
+ .Where(x => x.GetType() == typeof(RedeemVoucherEntry))
+ .Sum(x => (x as RedeemVoucherEntry).Amount)
+ ;
+ }
+ }
+
+ public string Type {
+ get {
+ string v = Entries
+ .Where(x => x.GetType() == typeof(RedeemVoucherEntry))
+ .GroupBy(x => (x as RedeemVoucherEntry).Type)
+ .Select(x => x.Key)
+ .First();
+ return v;
+ }
+ }
+
+ public override string ToString() {
+ StringBuilder builder = new StringBuilder();
+
+ builder.AppendFormat("{0} Vouchers: {1} CR", TotalSum, Type);
+
+ return builder.ToString();
+ }
+
+ ///
+ /// Vouchers only help the controlling faction
+ ///
+ public override bool OnlyControllingFaction => true;
+ }
+}
diff --git a/Journal/DockedEntry.cs b/Journal/DockedEntry.cs
new file mode 100644
index 0000000..40bbdd8
--- /dev/null
+++ b/Journal/DockedEntry.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace NonaBGS.Journal {
+ public class DockedEntry : Entry {
+ public string StationName {
+ get { return JSON.GetValue("StationName").ToString(); }
+ }
+ }
+}
diff --git a/Journal/EliteDangerous.cs b/Journal/EliteDangerous.cs
new file mode 100644
index 0000000..3e99fcd
--- /dev/null
+++ b/Journal/EliteDangerous.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace NonaBGS.Journal {
+ public class EliteDangerous {
+ ///
+ /// The ingame universe is 1286 years in the future. This is needed to convert dates
+ /// and times to ingame dates and times
+ ///
+ public static readonly int YearOffset = 1286;
+ }
+}
diff --git a/Journal/Entry.cs b/Journal/Entry.cs
new file mode 100644
index 0000000..86a27ea
--- /dev/null
+++ b/Journal/Entry.cs
@@ -0,0 +1,88 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace NonaBGS.Journal {
+ ///
+ /// Base class for a single entry within the player journal. If no specific sub class is available
+ /// this class gives basic information, such as EventType or date when it happened. It also allows
+ /// base classes access to the underlying JSON object. Base classes should be named after the event
+ /// type that they map + Entry. So "FSDJump" event is handled by FSDJumpEntry.
+ ///
+ public class Entry {
+ private static readonly Dictionary classes = new Dictionary {
+ { Events.Docked, typeof(DockedEntry) },
+ { Events.FSDJump, typeof(FSDJumpEntry) },
+ { Events.MissionCompleted, typeof(MissionCompletedEntry) },
+ { Events.MultiSellExplorationData, typeof(MultiSellExplorationDataEntry) },
+ { Events.MarketSell, typeof(MarketSellEntry) },
+ { Events.SellMicroResources, typeof(SellMicroResourcesEntry) },
+ { Events.RedeemVoucher, typeof(RedeemVoucherEntry) },
+ };
+
+ private string eventtype = null;
+ private string datetime = null;
+ private DateTime timestamp;
+
+ private string jsonstr = null;
+ protected JObject json = null;
+
+ public Entry() {
+ }
+
+ public static Entry Parse(string journalline) {
+ var json = JObject.Parse(journalline);
+ return Parse(json);
+ }
+
+ public static Entry Parse(JObject json) {
+ string event_name = json.GetValue("event").ToString();
+
+ classes.TryGetValue(event_name, out Type classhandler);
+ if (classhandler == null) {
+ classhandler = typeof(Entry);
+ }
+
+ var obj = (Entry)Activator.CreateInstance(classhandler);
+ obj.InternalInitialise(json);
+ obj.Initialise();
+ return obj;
+ }
+
+ private void InternalInitialise(JObject jobject) {
+ this.json = jobject;
+ this.jsonstr = json.ToString(formatting: Formatting.None);
+
+ this.eventtype = json.GetValue("event").ToString();
+ this.datetime = json.GetValue("timestamp").ToString();
+ this.timestamp = DateTime.Parse(this.datetime);
+ }
+
+ protected virtual void Initialise() {
+ }
+
+ public bool Is(string eventtype) {
+ if (eventtype == null || this.eventtype == null) {
+ return false;
+ }
+
+ return String.Equals(this.eventtype, eventtype, StringComparison.OrdinalIgnoreCase);
+ }
+
+ public string Event {
+ get { return eventtype; }
+ }
+
+ public DateTime Timestamp {
+ get { return timestamp; }
+ }
+
+ public JObject JSON {
+ get { return this.json; }
+ }
+ }
+}
diff --git a/Journal/Events.cs b/Journal/Events.cs
new file mode 100644
index 0000000..3c93a33
--- /dev/null
+++ b/Journal/Events.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace NonaBGS.Journal {
+ public class Events {
+ public static readonly string MissionCompleted = "MissionCompleted";
+ public static readonly string Docked = "Docked";
+ public static readonly string FSDJump = "FSDJump";
+ public static readonly string MultiSellExplorationData = "MultiSellExplorationData";
+ public static readonly string MarketSell = "MarketSell";
+ public static readonly string SellMicroResources = "SellMicroResources";
+ public static readonly string RedeemVoucher = "RedeemVoucher";
+ }
+}
diff --git a/Journal/FSDJumpEntry.cs b/Journal/FSDJumpEntry.cs
new file mode 100644
index 0000000..42a8322
--- /dev/null
+++ b/Journal/FSDJumpEntry.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Newtonsoft.Json.Linq;
+
+namespace NonaBGS.Journal {
+ public class FSDJumpEntry : Entry {
+ private string starsystem = null;
+ private string systemfaction = null;
+ protected override void Initialise() {
+ starsystem = JSON.Value("StarSystem");
+ var faction = JSON.Value("SystemFaction");
+ if (faction != null) {
+ systemfaction = faction.Value("Name");
+ }
+ }
+ public string StarSystem => starsystem;
+
+ public string SystemFaction => systemfaction;
+ }
+}
diff --git a/Journal/JournalException.cs b/Journal/JournalException.cs
new file mode 100644
index 0000000..179298b
--- /dev/null
+++ b/Journal/JournalException.cs
@@ -0,0 +1,11 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace NonaBGS.Journal {
+ public class JournalException : Exception {
+ public JournalException(string message) : base(message) {
+ }
+ }
+}
diff --git a/Journal/JournalFile.cs b/Journal/JournalFile.cs
new file mode 100644
index 0000000..2b35776
--- /dev/null
+++ b/Journal/JournalFile.cs
@@ -0,0 +1,112 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.IO;
+using System.Globalization;
+
+namespace NonaBGS.Journal
+{
+ public class JournalFile : IComparable
+ {
+ private string fullpath = null;
+ private string part = null;
+ private int intpart = 0;
+ private DateTime datetime;
+ private DateTime normalised;
+ private List entries = new List();
+
+ private static Regex fileregex = new Regex("Journal\\.(\\d+)\\.(\\d+)\\.log");
+ private static string iso8601 = "yyyyMMddTHHmmss";
+ private static string iso8601_notime = "yyyyMMdd";
+
+ private void SetFilename(string path) {
+ string filename = Path.GetFileName(path);
+ if (!File.Exists(path) || filename == null) {
+ throw new JournalException(string.Format("Invalid journal file: {0}", filename));
+ }
+
+ var matches = fileregex.Matches(filename);
+ if (matches.Count < 1) {
+ throw new JournalException(string.Format("Invalid journal file: {0}", filename));
+ }
+
+ this.fullpath = path;
+ this.part = matches[0].Groups[2].ToString();
+ this.intpart = int.Parse(part);
+ // The ISO is not quite correct on journal filenames, so build
+ // a proper ISO 8601 date time stamp out of it.
+ var date = new StringBuilder();
+ date.Append("20"); // Add the missing century in front.
+ date.Append(matches[0].Groups[1].ToString());
+ date.Insert(8, "T"); // Add the "T" separating date and time
+ this.datetime = DateTime.ParseExact(date.ToString(), iso8601, CultureInfo.InvariantCulture);
+ this.normalised = DateTime.Parse(this.datetime.ToShortDateString());
+ }
+
+ public int CompareTo(JournalFile other) {
+ if (datetime == null || other.datetime == null) {
+ return 0;
+ }
+ var comp = DateTime.Compare(datetime, other.datetime);
+ if (comp == 0) {
+ /* If we have the exact same datetime we compare by parts.
+ */
+ return intpart - other.intpart;
+ } else {
+ return comp;
+ }
+ }
+
+ public JournalFile(string path) {
+ SetFilename(path);
+ }
+
+ public DateTime Timestamp {
+ get { return datetime; }
+ }
+
+ public DateTime NormalisedTimestamp {
+ get { return normalised; }
+ }
+
+ public int Part {
+ get { return intpart; }
+ }
+
+ public IEnumerable Entries {
+ get {
+ if (entries == null || entries.Count == 0) {
+ try {
+ LoadEntries();
+ } catch (IOException) {
+ entries = new List();
+ }
+ }
+ return entries;
+ }
+ }
+
+ public void LoadEntries() {
+ List lines = new List();
+
+ /* This needs to be done this way, otherwise, if the game is still running the journal files cannot
+ * be accessed. And it is very much convenient to generate logs while the game is still running.
+ */
+ using (var fs = new FileStream(this.fullpath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) {
+ using (var sr = new StreamReader(fs, Encoding.UTF8)) {
+ string line = null;
+ while ((line = sr.ReadLine()) != null) {
+ lines.Add(line);
+ }
+ }
+ }
+
+ entries.Clear();
+ foreach(var line in lines) {
+ Entry entry = Entry.Parse(line);
+ entries.Add(entry);
+ }
+ }
+ }
+}
diff --git a/Journal/MarketSellEntry.cs b/Journal/MarketSellEntry.cs
new file mode 100644
index 0000000..0551aae
--- /dev/null
+++ b/Journal/MarketSellEntry.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace NonaBGS.Journal {
+ public class MarketSellEntry : Entry {
+ private int totalsale = 0;
+
+ protected override void Initialise() {
+ totalsale = JSON.Value("TotalSale");
+ }
+
+ public int TotalSale => totalsale;
+ }
+}
diff --git a/Journal/MissionCompletedEntry.cs b/Journal/MissionCompletedEntry.cs
new file mode 100644
index 0000000..4ad147d
--- /dev/null
+++ b/Journal/MissionCompletedEntry.cs
@@ -0,0 +1,146 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace NonaBGS.Journal {
+ public class MissionCompletedEntry : Entry {
+ private Dictionary influences = new Dictionary();
+ private string readable_name = null;
+ private bool readable_name_generated = false;
+ private string name = null;
+ private string commodity = null;
+ private int count = 0;
+ private int donated = 0;
+
+ private readonly Dictionary humanreadable = new Dictionary {
+ { "Mission_AltruismCredits_name", "Donate Credits" },
+ { "Mission_Collect_name", "Collect" },
+ { "Mission_Courier_Elections_name", "Courier (Elections)" },
+ { "Mission_Courier_name", "Courier" },
+ { "Mission_Courier_RankEmp_name", "Courier (Empire)" },
+ { "Mission_Delivery_Boom_name", "Delivery (Boom)" },
+ { "Mission_Delivery_Retreat_name", "Delivery (Retreat)" },
+ { "Mission_Delivery_name", "Delivery" },
+ { "Mission_Delivery_Agriculture_name", "Delivery (Agriculture)" },
+ { "Mission_HackMegaship_name", "Hack Megaship" },
+ { "Mission_MassacreWing_name", "Massacre (Wing)" },
+ { "Mission_OnFoot_Onslaught_MB_name", "On Foot Onslaught" },
+ { "Mission_OnFoot_RebootRestore_MB_name", "On Foot Reboot/Restore" },
+ { "Mission_OnFoot_Reboot_MB_name", "On Foot Reboot" },
+ { "Mission_Rescue_Planet_name", "Planet Rescue" },
+ { "Mission_Salvage_name", "Salvage" },
+ { "MISSION_Salvage_Illegal_name", "Salvage (Illegal)" },
+ { "MISSION_Salvage_Retreat_name", "Salvage (Retreat)" },
+ { "MISSION_Salvage_Expansion_name", "Salvage (Expansion)" },
+ { "Mission_Salvage_RankEmp_name", "Salvage (Imperial Navy)" },
+ { "MISSION_Scan_name", "Scan" },
+ };
+
+ protected override void Initialise() {
+ MakeHumanReadableName();
+ name = JSON.Value("Name");
+ if (JSON.ContainsKey("Commodity_Localised")) {
+ commodity = JSON.Value("Commodity_Localised");
+ }
+ if (JSON.ContainsKey("Count")) {
+ count = JSON.Value("Count");
+ }
+ if (JSON.ContainsKey("Donated")) {
+ donated = JSON.Value("Donated");
+ }
+ }
+
+ public string Name => name;
+ public string Commodity => commodity;
+ public int Count => count;
+
+ private void MakeHumanReadableName() {
+ if (readable_name != null && readable_name.Length > 0) {
+ return;
+ }
+
+ if (Name == null) {
+ return;
+ }
+
+ StringBuilder builder;
+
+ if (humanreadable.ContainsKey(Name)) {
+ builder = new StringBuilder(humanreadable[Name]);
+ readable_name_generated = false;
+ } else {
+ builder = new StringBuilder(Name);
+ builder.Replace("Mission_", "");
+ builder.Replace("_name", "");
+ builder.Replace("_MB", "");
+ builder.Replace('_', ' ');
+ builder.Replace("Illegal", " (Illegal)");
+ builder.Replace("OnFoot", "On Foot");
+ builder.Replace(" BS", "");
+ builder.Replace("HackMegaship", "Hack Megaship");
+ builder.Replace("Boom", "");
+ builder.Replace("RebootRestore", "Reboot/Restore");
+ builder.Replace("CivilLiberty", "");
+ readable_name_generated = true;
+ }
+
+ if (count > 0 && commodity != null) {
+ builder.AppendFormat(" ({0} {1})", count, commodity);
+ }
+
+ if (donated > 0) {
+ builder.AppendFormat(" ({0} CR)", donated);
+ }
+
+ readable_name = builder.ToString().Trim();
+ }
+
+ public string HumanReadableName {
+ get {
+ MakeHumanReadableName();
+ return readable_name;
+ }
+ }
+
+ public bool HumanReadableNameWasGenerated {
+ get {
+ MakeHumanReadableName();
+ return readable_name_generated;
+ }
+ }
+
+ public string GetInfluenceForFaction(string faction) {
+ if (influences.ContainsKey(faction)) {
+ return influences[faction];
+ }
+
+ var effects = JSON.Value("FactionEffects");
+ foreach (var effect in effects.Children()) {
+ if (effect.GetValue("Faction").ToString() != faction) {
+ continue;
+ }
+
+ var influence = effect.Value("Influence");
+ if (influence == null || influence.Count == 0) {
+ // No influence reward, happens on courier missions sometimes.
+ // There is always one point of rep, even if the mission won't state it
+ influences.Add(faction, "+");
+ }
+
+ foreach (var infl in influence.Children()) {
+ infl.TryGetValue("Influence", out JToken result);
+ if (result != null && result.Type == JTokenType.String) {
+ influences.Add(faction, result.ToString());
+ return result.ToString();
+ }
+ }
+ }
+
+ return "";
+ }
+ }
+}
diff --git a/Journal/MultiSellExplorationDataEntry.cs b/Journal/MultiSellExplorationDataEntry.cs
new file mode 100644
index 0000000..139a546
--- /dev/null
+++ b/Journal/MultiSellExplorationDataEntry.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace NonaBGS.Journal {
+ public class MultiSellExplorationDataEntry : Entry {
+ private int totalearnings = 0;
+
+ protected override void Initialise() {
+ totalearnings = JSON.Value("TotalEarnings");
+ }
+
+ public int TotalEarnings => totalearnings;
+ }
+}
diff --git a/Journal/PlayerJournal.cs b/Journal/PlayerJournal.cs
new file mode 100644
index 0000000..24f09ff
--- /dev/null
+++ b/Journal/PlayerJournal.cs
@@ -0,0 +1,56 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace NonaBGS.Journal {
+ public class PlayerJournal {
+ public static string DefaultPath = "%UserProfile%\\Saved Games\\Frontier Developments\\Elite Dangerous";
+
+ private List journalfiles = new List();
+ private string basepath = null;
+
+ public PlayerJournal() {
+ Initialise(DefaultPath);
+ }
+
+ public PlayerJournal(string path) {
+ Initialise(path);
+ }
+
+ private void Initialise(string path) {
+ basepath = Environment.ExpandEnvironmentVariables(path);
+ }
+
+ public List Files {
+ get { return journalfiles; }
+ }
+
+ public void Open() {
+ if (!Directory.Exists(basepath)) {
+ throw new JournalException("Invalid base path, path could not be found");
+ }
+ this.ScanFiles();
+ }
+
+ public void ScanFiles() {
+ var files = Directory.EnumerateFiles(basepath);
+
+ journalfiles.Clear();
+
+ foreach (var file in files) {
+ string full = Path.Combine(basepath, file);
+ try {
+ JournalFile journalfile = new JournalFile(full);
+ journalfiles.Add(journalfile);
+ } catch (JournalException) {
+ /* invalid journal file, or one of the other json files in there */
+ continue;
+ }
+ }
+
+ journalfiles.Sort();
+ }
+ }
+}
diff --git a/Journal/RedeemVoucherEntry.cs b/Journal/RedeemVoucherEntry.cs
new file mode 100644
index 0000000..f172359
--- /dev/null
+++ b/Journal/RedeemVoucherEntry.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace NonaBGS.Journal {
+ public class RedeemVoucherEntry : Entry {
+ private int amount = 0;
+ private string type = null;
+ protected override void Initialise() {
+ amount = JSON.Value("Amount");
+ type = JSON.Value("Type");
+ }
+
+ public int Amount => amount;
+ public string Type => type;
+ }
+}
diff --git a/Journal/SellMicroResourcesEntry.cs b/Journal/SellMicroResourcesEntry.cs
new file mode 100644
index 0000000..dc23bda
--- /dev/null
+++ b/Journal/SellMicroResourcesEntry.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace NonaBGS.Journal {
+ public class SellMicroResourcesEntry : Entry {
+ private int price;
+
+ protected override void Initialise() {
+ price = JSON.Value("Price");
+ }
+
+ public int Price => price;
+ }
+}
diff --git a/MainWindow.xaml b/MainWindow.xaml
new file mode 100644
index 0000000..32cea3c
--- /dev/null
+++ b/MainWindow.xaml
@@ -0,0 +1,122 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/MainWindow.xaml.cs b/MainWindow.xaml.cs
new file mode 100644
index 0000000..04f8eaa
--- /dev/null
+++ b/MainWindow.xaml.cs
@@ -0,0 +1,192 @@
+using System;
+using System.Collections.ObjectModel;
+using System.Collections.Generic;
+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.Navigation;
+using System.Windows.Shapes;
+using Ookii.Dialogs.Wpf;
+
+using NonaBGS.Journal;
+using NonaBGS.BGS;
+using NonaBGS.Util;
+
+namespace NonaBGS {
+ ///
+ /// Interaction logic for MainWindow.xaml
+ ///
+ public partial class MainWindow : Window {
+ private PlayerJournal journal = null;
+ private Report report = new Report();
+ private Config config = new Config();
+ private EDDB eddb = null;
+
+ public Config Config => config;
+
+ public Report Report => report;
+
+ public MainWindow() {
+ InitializeComponent();
+
+ try {
+ config.LoadGlobal();
+ } catch (Exception) {
+ /* ignored */
+ }
+
+ report.OnLog += Report_OnLog;
+
+ eddb = new EDDB(config.ConfigPath);
+ journal = new PlayerJournal(config.Global.JournalLocation);
+
+ // Set both to now
+ startdate.SelectedDate = DateTime.Now;
+ enddate.SelectedDate = DateTime.Now;
+ journallocation.Text = Config.Global.JournalLocation;
+ useeddb.IsChecked = Config.Global.UseEDDB;
+
+ try {
+ config.LoadObjectives(Report);
+ RefreshObjectives();
+
+ SyncDatabases();
+ } catch (Exception) {
+ /* ignored */
+ }
+ }
+
+ private void Report_OnLog(string message) {
+ StringBuilder builder = new StringBuilder();
+
+ builder.Append(DateTime.Now.ToString());
+ builder.Append(": ");
+ builder.Append(message);
+ builder.Append("\n");
+
+ log.AppendText(builder.ToString());
+ }
+
+ private void SyncDatabases() {
+ if (!config.Global.UseEDDB) {
+ return;
+ }
+
+ eddb.Download();
+ }
+
+ private void RefreshObjectives() {
+ entries.Items.Clear();
+
+ if (report.Objectives == null) {
+ return;
+ }
+
+ foreach (var obj in report.Objectives) {
+ var item_objective = new TreeViewItem {
+ Header = obj.ToString(),
+ Tag = obj
+ };
+
+ foreach (var log in obj.LogEntries) {
+ var log_objective = new TreeViewItem {
+ Header = log.ToString(),
+ Tag = log
+ };
+ item_objective.Items.Add(log_objective);
+ }
+
+ item_objective.IsExpanded = true;
+ entries.Items.Add(item_objective);
+ }
+ }
+
+ private void ParseJournal_Click(object sender, RoutedEventArgs e) {
+ journal.Open(); // Load all files
+
+ var start = startdate.SelectedDate ?? DateTime.Now;
+ var end = startdate.SelectedDate ?? DateTime.Now;
+
+ report.Scan(journal, start, end);
+
+ RefreshObjectives();
+ }
+
+ private void AddFilter_Click(object sender, RoutedEventArgs e) {
+ Objective objective = new Objective {
+ System = system.Text,
+ Faction = faction.Text,
+ Station = station.Text
+ };
+
+ if (!objective.IsValid) {
+ return;
+ }
+
+ if (report.AddObjective(objective)) {
+ RefreshObjectives();
+ config.SaveObjectives(Report);
+ }
+ }
+
+ private void GenerateDiscord_Click(object sender, RoutedEventArgs e) {
+ NonaDiscordLog discord = new NonaDiscordLog();
+ string report = discord.GenerateDiscordLog(Report);
+
+ DiscordLog.Text = report;
+ }
+
+ private void RemoveCurrentObjective() {
+ if (entries.SelectedItem == null) {
+ return;
+ }
+
+ TreeViewItem item = entries.SelectedItem as TreeViewItem;
+ var obj = item.Tag;
+ bool removed = false;
+
+ if (obj.GetType() == typeof(Objective)) {
+ removed = report.Objectives.Remove(obj as Objective);
+ } else if (obj.GetType() == typeof(LogEntry) ||
+ obj.GetType().IsSubclassOf(typeof(LogEntry))) {
+ var parent = (item.Parent as TreeViewItem).Tag as Objective;
+ removed = parent.LogEntries.Remove(obj as LogEntry);
+ }
+
+ if (removed) {
+ RefreshObjectives();
+ config.SaveObjectives(Report);
+ }
+ }
+
+ private void entries_KeyUp(object sender, KeyEventArgs e) {
+ if (e.Key == Key.Delete) {
+ RemoveCurrentObjective();
+ }
+ }
+
+ private void browsejournallocation_Click(object sender, RoutedEventArgs e) {
+ var dialog = new VistaFolderBrowserDialog();
+
+ if ((bool)!dialog.ShowDialog()) {
+ return;
+ }
+
+ Config.Global.JournalLocation = dialog.SelectedPath;
+ journallocation.Text = Config.Global.JournalLocation;
+ journal = new PlayerJournal(config.Global.JournalLocation);
+ }
+
+ private void useeddb_Click(object sender, RoutedEventArgs e) {
+ Config.Global.UseEDDB = (bool)useeddb.IsChecked;
+ SyncDatabases();
+ }
+ }
+}
diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..90c1cfb
--- /dev/null
+++ b/Properties/AssemblyInfo.cs
@@ -0,0 +1,55 @@
+using System.Reflection;
+using System.Resources;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Windows;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("nonabgs")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("nonabgs")]
+[assembly: AssemblyCopyright("Copyright © 2021")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+//In order to begin building localizable applications, set
+//CultureYouAreCodingWith in your .csproj file
+//inside a . For example, if you are using US english
+//in your source files, set the to en-US. Then uncomment
+//the NeutralResourceLanguage attribute below. Update the "en-US" in
+//the line below to match the UICulture setting in the project file.
+
+//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
+
+
+[assembly: ThemeInfo(
+ ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
+ //(used if a resource is not found in the page,
+ // or application resource dictionaries)
+ ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
+ //(used if a resource is not found in the page,
+ // app, or any theme specific resource dictionaries)
+)]
+
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/Properties/Resources.Designer.cs b/Properties/Resources.Designer.cs
new file mode 100644
index 0000000..9cadb94
--- /dev/null
+++ b/Properties/Resources.Designer.cs
@@ -0,0 +1,63 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace NonaBGS.Properties {
+ using System;
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resources {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resources() {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("NonaBGS.Properties.Resources", typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+ }
+}
diff --git a/Properties/Resources.resx b/Properties/Resources.resx
new file mode 100644
index 0000000..af7dbeb
--- /dev/null
+++ b/Properties/Resources.resx
@@ -0,0 +1,117 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/Properties/Settings.Designer.cs b/Properties/Settings.Designer.cs
new file mode 100644
index 0000000..0a4249a
--- /dev/null
+++ b/Properties/Settings.Designer.cs
@@ -0,0 +1,26 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace NonaBGS.Properties {
+
+
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.8.1.0")]
+ internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
+
+ private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+
+ public static Settings Default {
+ get {
+ return defaultInstance;
+ }
+ }
+ }
+}
diff --git a/Properties/Settings.settings b/Properties/Settings.settings
new file mode 100644
index 0000000..033d7a5
--- /dev/null
+++ b/Properties/Settings.settings
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Util/AppConfig.cs b/Util/AppConfig.cs
new file mode 100644
index 0000000..ab72813
--- /dev/null
+++ b/Util/AppConfig.cs
@@ -0,0 +1,43 @@
+using System;
+using System.ComponentModel;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace NonaBGS.Util {
+ public class AppConfig : INotifyPropertyChanged {
+ private static readonly string default_journal_location = "%UserProfile%\\Saved Games\\Frontier Developments\\Elite Dangerous";
+ private string journal_location = default_journal_location;
+ private bool useeddb = false;
+
+ public string DefaultJournalLocation => default_journal_location;
+
+ public bool UseEDDB {
+ get => useeddb;
+ set {
+ useeddb = value;
+ FirePropertyChanged("UseEDDB");
+ }
+ }
+
+ public string JournalLocation {
+ get {
+ if (journal_location == null) {
+ return DefaultJournalLocation;
+ }
+ return journal_location;
+ }
+ set {
+ journal_location = value;
+ FirePropertyChanged("JournalLocation");
+ }
+ }
+
+ private void FirePropertyChanged(string property) {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
+ }
+
+ public event PropertyChangedEventHandler PropertyChanged;
+ }
+}
diff --git a/Util/Config.cs b/Util/Config.cs
new file mode 100644
index 0000000..8fdeaa8
--- /dev/null
+++ b/Util/Config.cs
@@ -0,0 +1,85 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.IO;
+using Newtonsoft.Json;
+using NonaBGS.BGS;
+
+namespace NonaBGS.Util {
+ public class Config {
+ private string config_folder = null;
+ private string objectives_file = null;
+ private string config_file = null;
+
+ private AppConfig global_config = new AppConfig();
+
+ public Config() {
+ DetermineConfigFolder();
+ global_config.PropertyChanged += Global_config_PropertyChanged;
+ }
+
+ private void Global_config_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) {
+ try {
+ SaveGlobal();
+ } catch (Exception) {
+ /* ignored */
+ }
+ }
+
+ public string ConfigPath => config_folder;
+
+ public AppConfig Global => global_config;
+
+ private void DetermineConfigFolder() {
+ string folder = Environment.ExpandEnvironmentVariables("%appdata%\\NonaBGS");
+
+ if (!Directory.Exists(folder)) {
+ Directory.CreateDirectory(folder);
+ }
+
+ config_folder = folder;
+
+ objectives_file = Path.Combine(config_folder, "objectives.json");
+ config_file = Path.Combine(config_folder, "config.json");
+ }
+
+ public void SaveGlobal() {
+ var serializer = JsonSerializer.CreateDefault();
+ using (var file = new StreamWriter(File.OpenWrite(config_file), Encoding.UTF8)) {
+ var stream = new JsonTextWriter(file);
+ serializer.Serialize(stream, global_config);
+ }
+ }
+
+ public void LoadGlobal() {
+ var serializer = JsonSerializer.CreateDefault();
+ using (var file = new StreamReader(File.OpenRead(config_file), Encoding.UTF8)) {
+ var stream = new JsonTextReader(file);
+ var app = serializer.Deserialize(stream);
+
+ if (app != null) {
+ this.global_config = app;
+ global_config.PropertyChanged += Global_config_PropertyChanged;
+ }
+ }
+ }
+
+ public void SaveObjectives(Report report) {
+ var serializer = JsonSerializer.CreateDefault();
+ using (var file = new StreamWriter(File.OpenWrite(objectives_file), Encoding.UTF8)) {
+ var stream = new JsonTextWriter(file);
+ serializer.Serialize(stream, report.Objectives);
+ }
+ }
+
+ public void LoadObjectives(Report report) {
+ var serializer = JsonSerializer.CreateDefault();
+ using (var file = new StreamReader(File.OpenRead(objectives_file), Encoding.UTF8)) {
+ var stream = new JsonTextReader(file);
+ var objectives = serializer.Deserialize>(stream);
+
+ report.Objectives = objectives;
+ }
+ }
+ }
+}
diff --git a/Util/EDDB.cs b/Util/EDDB.cs
new file mode 100644
index 0000000..59a1e81
--- /dev/null
+++ b/Util/EDDB.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.IO;
+using System.Threading.Tasks;
+using System.Net;
+
+namespace NonaBGS.Util {
+ public class EDDB {
+ private static readonly string EDDB_SYSTEMS_ARCHIVE = "https://eddb.io/archive/v6/systems_populated.json";
+ private string cache_folder = null;
+ private WebClient client = new WebClient();
+
+ private string systems_file = null;
+
+ public string SystemsFile => systems_file;
+
+ public string Cache {
+ get => cache_folder;
+ set => cache_folder = value;
+ }
+
+ public EDDB(string cache_folder) {
+ this.cache_folder = cache_folder;
+ Initialise();
+ }
+
+ public void Initialise() {
+ client.DownloadDataCompleted += Client_DownloadDataCompleted;
+ }
+
+ private void DownloadFile(string url) {
+ Uri uri = new Uri(url);
+ string name = Path.GetFileName(uri.AbsolutePath);
+ systems_file = Path.Combine(this.cache_folder, name);
+
+ client.DownloadFileAsync(new Uri(EDDB_SYSTEMS_ARCHIVE), systems_file);
+ }
+
+ public void Download() {
+ DownloadFile(EDDB_SYSTEMS_ARCHIVE);
+ }
+
+ private void Client_DownloadDataCompleted(object sender, DownloadDataCompletedEventArgs e) {
+ }
+ }
+}
diff --git a/nonabgs.csproj b/nonabgs.csproj
new file mode 100644
index 0000000..39292a9
--- /dev/null
+++ b/nonabgs.csproj
@@ -0,0 +1,140 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {73BFB315-C808-40E7-8D69-B651F875880C}
+ WinExe
+ NonaBGS
+ nonabgs
+ v4.7.2
+ 512
+ {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ 4
+ true
+ true
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ AnyCPU
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+ NonaBGSApplication
+
+
+
+ packages\Newtonsoft.Json.13.0.1\lib\net45\Newtonsoft.Json.dll
+
+
+ packages\Ookii.Dialogs.Wpf.3.1.0\lib\net45\Ookii.Dialogs.Wpf.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 4.0
+
+
+
+
+
+
+
+ MSBuild:Compile
+ Designer
+
+
+
+
+
+
+
+
+
+
+
+
+
+ MSBuild:Compile
+ Designer
+
+
+ App.xaml
+ Code
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ MainWindow.xaml
+ Code
+
+
+
+
+ Code
+
+
+ True
+ True
+ Resources.resx
+
+
+ True
+ Settings.settings
+ True
+
+
+ ResXFileCodeGenerator
+ Resources.Designer.cs
+
+
+
+ SettingsSingleFileGenerator
+ Settings.Designer.cs
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/nonabgs.sln b/nonabgs.sln
new file mode 100644
index 0000000..53b2007
--- /dev/null
+++ b/nonabgs.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.31205.134
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "nonabgs", "nonabgs.csproj", "{73BFB315-C808-40E7-8D69-B651F875880C}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {73BFB315-C808-40E7-8D69-B651F875880C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {73BFB315-C808-40E7-8D69-B651F875880C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {73BFB315-C808-40E7-8D69-B651F875880C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {73BFB315-C808-40E7-8D69-B651F875880C}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {40F4B1BD-FC53-485B-9AEE-3357B375CED2}
+ EndGlobalSection
+EndGlobal
diff --git a/packages.config b/packages.config
new file mode 100644
index 0000000..e390de6
--- /dev/null
+++ b/packages.config
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file