diff --git a/EDPlayerJournal/CommanderContext/CommanderContext.cs b/EDPlayerJournal/CommanderContext/CommanderContext.cs
new file mode 100644
index 0000000..4b2e230
--- /dev/null
+++ b/EDPlayerJournal/CommanderContext/CommanderContext.cs
@@ -0,0 +1,56 @@
+using EDPlayerJournal.Entries;
+
+namespace EDPlayerJournal.CommanderContext;
+
+///
+/// A class that uses various journal entries to provide a (hopefully)
+/// up to date summary of the current commander in game.
+///
+/// Data in this context may be null, in which case the current status of
+/// that information is not yet known. For example if the player died, but
+/// they have't respawned yet, their location might be null.
+///
+public class CommanderContext {
+ private static Dictionary parsers = new() {
+ { Events.Commander, new CommanderParser() },
+ { Events.Docked, new DockedParser() },
+ { Events.FSDJump, new FSDJumpParser() },
+ { Events.Location, new LocationParser() },
+ { Events.Undocked, new UndockedParser() },
+ };
+
+ ///
+ /// Name of the commander.
+ ///
+ public string? Name { get; set; } = null;
+
+ ///
+ /// Whether the commander is currently docked.
+ ///
+ public bool IsDocked { get; set; } = false;
+
+ ///
+ /// Current station
+ ///
+ public Station? Station { get; set; } = null;
+
+ ///
+ /// Current star system
+ ///
+ public StarSystem? System { get; set; } = null;
+
+ public void Update(Entry entry) {
+ if (string.IsNullOrEmpty(entry.Event)) {
+ throw new CommanderParserException("invalid or empty event given");
+ }
+
+ string ev = entry.Event;
+
+ if (!parsers.ContainsKey(ev)) {
+ return;
+ }
+
+ ICommanderParser parser = parsers[ev];
+ parser.Parse(entry, this);
+ }
+}
diff --git a/EDPlayerJournal/CommanderContext/CommanderParser.cs b/EDPlayerJournal/CommanderContext/CommanderParser.cs
new file mode 100644
index 0000000..91d857d
--- /dev/null
+++ b/EDPlayerJournal/CommanderContext/CommanderParser.cs
@@ -0,0 +1,15 @@
+using EDPlayerJournal.Entries;
+
+namespace EDPlayerJournal.CommanderContext;
+
+internal class CommanderParser : ICommanderParser {
+ public void Parse(Entry entry, CommanderContext ctx) {
+ CommanderEntry? e = entry as CommanderEntry;
+
+ if (e == null) {
+ throw new CommanderParserException("invalid entry");
+ }
+
+ ctx.Name = e.Name;
+ }
+}
diff --git a/EDPlayerJournal/CommanderContext/DockedParser.cs b/EDPlayerJournal/CommanderContext/DockedParser.cs
new file mode 100644
index 0000000..14a29b9
--- /dev/null
+++ b/EDPlayerJournal/CommanderContext/DockedParser.cs
@@ -0,0 +1,16 @@
+using EDPlayerJournal.Entries;
+
+namespace EDPlayerJournal.CommanderContext;
+
+internal class DockedParser : ICommanderParser {
+ public void Parse(Entry entry, CommanderContext ctx) {
+ DockedEntry? e = entry as DockedEntry;
+
+ if (e == null) {
+ throw new CommanderParserException("invalid entry");
+ }
+
+ ctx.Station = Station.ParseEntry(e);
+ ctx.IsDocked = true;
+ }
+}
diff --git a/EDPlayerJournal/CommanderContext/FSDJumpParser.cs b/EDPlayerJournal/CommanderContext/FSDJumpParser.cs
new file mode 100644
index 0000000..a95d164
--- /dev/null
+++ b/EDPlayerJournal/CommanderContext/FSDJumpParser.cs
@@ -0,0 +1,15 @@
+using EDPlayerJournal.Entries;
+
+namespace EDPlayerJournal.CommanderContext;
+
+internal class FSDJumpParser : ICommanderParser {
+ public void Parse(Entry entry, CommanderContext ctx) {
+ FSDJumpEntry? e = entry as FSDJumpEntry;
+
+ if (e == null) {
+ throw new CommanderParserException("invalid entry");
+ }
+
+ ctx.System = StarSystem.ParseEntry(e);
+ }
+}
diff --git a/EDPlayerJournal/CommanderContext/ICommanderParser.cs b/EDPlayerJournal/CommanderContext/ICommanderParser.cs
new file mode 100644
index 0000000..112c39d
--- /dev/null
+++ b/EDPlayerJournal/CommanderContext/ICommanderParser.cs
@@ -0,0 +1,19 @@
+using EDPlayerJournal;
+using EDPlayerJournal.Entries;
+
+namespace EDPlayerJournal.CommanderContext;
+
+public class CommanderParserException : ApplicationException {
+ public CommanderParserException() {
+ }
+
+ public CommanderParserException(string message) : base(message) {
+ }
+
+ public CommanderParserException(string message, params object?[] args) : base(string.Format(message, args)) {
+ }
+}
+
+internal interface ICommanderParser {
+ void Parse(Entry entry, CommanderContext ctx);
+}
diff --git a/EDPlayerJournal/CommanderContext/LocationParser.cs b/EDPlayerJournal/CommanderContext/LocationParser.cs
new file mode 100644
index 0000000..79955df
--- /dev/null
+++ b/EDPlayerJournal/CommanderContext/LocationParser.cs
@@ -0,0 +1,17 @@
+using EDPlayerJournal.Entries;
+
+namespace EDPlayerJournal.CommanderContext;
+
+internal class LocationParser : ICommanderParser {
+ public void Parse(Entry entry, CommanderContext ctx) {
+ LocationEntry? e = entry as LocationEntry;
+
+ if (e == null) {
+ throw new CommanderParserException("invalid entry");
+ }
+
+ ctx.Station = Station.ParseEntry(e);
+ ctx.System = StarSystem.ParseEntry(e);
+ ctx.IsDocked = e.Docked;
+ }
+}
\ No newline at end of file
diff --git a/EDPlayerJournal/CommanderContext/UndockedParser.cs b/EDPlayerJournal/CommanderContext/UndockedParser.cs
new file mode 100644
index 0000000..5e7591a
--- /dev/null
+++ b/EDPlayerJournal/CommanderContext/UndockedParser.cs
@@ -0,0 +1,16 @@
+using EDPlayerJournal.Entries;
+
+namespace EDPlayerJournal.CommanderContext;
+
+internal class UndockedParser : ICommanderParser {
+ public void Parse(Entry entry, CommanderContext ctx) {
+ UndockedEntry? e = entry as UndockedEntry;
+
+ if (e == null) {
+ throw new CommanderParserException("invalid entry");
+ }
+
+ ctx.Station = null;
+ ctx.IsDocked = false;
+ }
+}
diff --git a/EDPlayerJournal/Entries/Entry.cs b/EDPlayerJournal/Entries/Entry.cs
index 9bb0c01..a4ca767 100644
--- a/EDPlayerJournal/Entries/Entry.cs
+++ b/EDPlayerJournal/Entries/Entry.cs
@@ -59,6 +59,7 @@ public class Entry {
{ Events.SupercruiseEntry, typeof(SupercruiseEntryEntry) },
{ Events.SupercruiseExit, typeof(SupercruiseExitEntry) },
{ Events.UnderAttack, typeof(UnderAttackEntry) },
+ { Events.Undocked, typeof(UndockedEntry) },
};
private string? eventtype = null;
diff --git a/EDPlayerJournal/Entries/Events.cs b/EDPlayerJournal/Entries/Events.cs
index 6ec3909..c85ad70 100644
--- a/EDPlayerJournal/Entries/Events.cs
+++ b/EDPlayerJournal/Entries/Events.cs
@@ -50,4 +50,5 @@ public class Events {
public static readonly string SupercruiseEntry = "SupercruiseEntry";
public static readonly string SupercruiseExit = "SupercruiseExit";
public static readonly string UnderAttack = "UnderAttack";
+ public static readonly string Undocked = "Undocked";
}
diff --git a/EDPlayerJournal/Entries/UndockedEntry.cs b/EDPlayerJournal/Entries/UndockedEntry.cs
new file mode 100644
index 0000000..11bf90b
--- /dev/null
+++ b/EDPlayerJournal/Entries/UndockedEntry.cs
@@ -0,0 +1,12 @@
+namespace EDPlayerJournal.Entries;
+
+public class UndockedEntry : Entry {
+ ///
+ /// Name of the station
+ ///
+ public string? StationName { get; set; }
+
+ protected override void Initialise() {
+ StationName = JSON.Value("StationName");
+ }
+}
diff --git a/EDPlayerJournal/StarSystem.cs b/EDPlayerJournal/StarSystem.cs
new file mode 100644
index 0000000..842ac11
--- /dev/null
+++ b/EDPlayerJournal/StarSystem.cs
@@ -0,0 +1,40 @@
+using EDPlayerJournal.Entries;
+using Newtonsoft.Json.Linq;
+
+namespace EDPlayerJournal;
+
+public class StarSystem {
+ public string Name { get; set; } = string.Empty;
+
+ public double[] Position { get; set; } = new double[3] { 0.0, 0.0, 0.0 };
+
+ public ulong Address { get; set; } = 0;
+
+ public ulong Population { get; set; } = 0;
+
+ public string Allegiance { get; set; } = string.Empty;
+
+ public static StarSystem? ParseEntry(Entry e) {
+ if (!(e.Is(Events.Location) || e.Is(Events.FSDJump))) {
+ return null;
+ }
+ return ParseJSON(e.JSON);
+ }
+
+ public static StarSystem? ParseJSON(JObject obj) {
+ StarSystem s = new();
+
+ s.Name = obj.Value("StarSystem") ?? string.Empty;
+ var pos = obj.Value("StarPos");
+ if (pos != null) {
+ s.Position = pos.Select(x => (double)x).ToArray();
+ }
+ s.Address = obj.Value("SystemAddress") ?? 0;
+ s.Population = obj.Value("Population") ?? 0;
+
+ // TODO: more info
+
+ return s;
+ }
+}
+
diff --git a/EDPlayerJournal/Station.cs b/EDPlayerJournal/Station.cs
new file mode 100644
index 0000000..1abdbc5
--- /dev/null
+++ b/EDPlayerJournal/Station.cs
@@ -0,0 +1,65 @@
+using EDPlayerJournal.Entries;
+using Newtonsoft.Json.Linq;
+
+namespace EDPlayerJournal;
+
+///
+/// Represents a station ingame
+///
+public class Station {
+ public string SystemName { get; set; } = string.Empty;
+
+ public ulong SystemAddress { get; set; } = 0;
+
+ public string Name { get; set; } = string.Empty;
+
+ public string Type { get; set; } = string.Empty;
+
+ public ulong MarketID { get; set; } = 0;
+
+ public string Faction { get; set; } = string.Empty;
+
+ public string Government { get; set; } = string.Empty;
+
+ public string GovernmentLocalised { get; set; } = string.Empty;
+
+ public List Services { get; set; } = new();
+
+ public static Station? ParseEntry(Entry e) {
+ if (!(e.Is(Events.Docked) || e.Is(Events.Location))) {
+ return null;
+ }
+
+ var s = ParseJSON(e.JSON);
+ return s;
+ }
+
+ public static Station? ParseJSON(JObject obj) {
+ Station s = new();
+
+ s.Name = obj.Value("StationName") ?? string.Empty;
+ s.Type = obj.Value("StationType") ?? string.Empty;
+ s.MarketID = obj.Value("MarketID") ?? 0;
+
+ var faction = obj.Value("StationFaction");
+ if (faction != null) {
+ s.Faction = faction.Value("Name") ?? string.Empty;
+ }
+
+ s.Government = obj.Value("StationGovernment") ?? string.Empty;
+ s.GovernmentLocalised = obj.Value("StationGovernment_Localised") ?? string.Empty;
+
+ var services = obj.Value("StationServices");
+ if (services != null) {
+ s.Services = services
+ .Select(x => x.ToString())
+ .ToList()
+ ;
+ }
+
+ // TODO: more info
+
+ return s;
+ }
+}
+