2023-05-11 20:43:48 +02:00
|
|
|
|
using EDPlayerJournal.BGS.Parsers;
|
|
|
|
|
using EDPlayerJournal.Entries;
|
2022-11-01 18:01:28 +01:00
|
|
|
|
|
2023-03-02 20:09:19 +01:00
|
|
|
|
namespace EDPlayerJournal.BGS;
|
|
|
|
|
|
|
|
|
|
public class TransactionParserOptions {
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Whether to ignore exo biology. It does not contribute to BGS, so this
|
|
|
|
|
/// is true per default.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public bool IgnoreExoBiology { get; set; } = true;
|
2023-03-03 15:59:41 +01:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Whether to ignore influence support. Usually one only cares about the
|
|
|
|
|
/// primary faction for the influence.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public bool IgnoreInfluenceSupport { get; set; } = false;
|
2023-04-19 09:06:54 +02:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Whether to ignore market buy. Buying from a market gives a small amount
|
|
|
|
|
/// of INF if it is sold to a high demand market, but generally one buys from
|
|
|
|
|
/// a market to aid the faction the stuff is being sold to. So allow it to be
|
|
|
|
|
/// disabled.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public bool IgnoreMarketBuy { get; set; } = false;
|
2023-05-15 18:15:38 +02:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Whether we should ignore things done for the fleet carrier faction.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public bool IgnoreFleetCarrierFaction { get; set; } = true;
|
2024-04-28 12:35:14 +02:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Filter out double redeem vouchers that happen when you redeem a specific
|
|
|
|
|
/// voucher, and then redeem the rest of your vouchers (say from a KWS) in
|
|
|
|
|
/// bulk. The bulk redeem will also list the first voucher redeem again in
|
|
|
|
|
/// its bulk list.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public bool FilterDoubleRedeemVouchers { get; set; } = true;
|
2023-03-02 20:09:19 +01:00
|
|
|
|
}
|
2022-11-01 18:01:28 +01:00
|
|
|
|
|
|
|
|
|
public class TransactionList : List<Transaction> {
|
2022-12-18 10:13:35 +01:00
|
|
|
|
public void AddIncomplete(Transaction underlying, string reason, Entry entry) {
|
|
|
|
|
Add(new IncompleteTransaction(underlying, reason, entry));
|
2022-11-01 18:01:28 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// The location parser only updates the context with useful information, and does not
|
|
|
|
|
/// by itself generate any transactions. Location is the best information gatherer here
|
|
|
|
|
/// as we are getting controlling faction, system factions, address and station name.
|
|
|
|
|
/// </summary>
|
2023-05-11 20:43:48 +02:00
|
|
|
|
internal class LocationParser : ITransactionParserPart {
|
2023-03-02 20:09:19 +01:00
|
|
|
|
public void Parse(Entry e, TransactionParserContext context, TransactionParserOptions options, TransactionList transactions) {
|
2022-11-01 18:01:28 +01:00
|
|
|
|
LocationEntry? entry = e as LocationEntry;
|
|
|
|
|
if (entry == null) {
|
|
|
|
|
throw new NotImplementedException();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (entry.StarSystem == null) {
|
|
|
|
|
throw new InvalidJournalEntryException();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
context.CurrentSystem = entry.StarSystem;
|
|
|
|
|
context.CurrentSystemAddress = entry.SystemAddress;
|
|
|
|
|
|
|
|
|
|
context.SystemsByID.TryAdd(entry.SystemAddress, entry.StarSystem);
|
|
|
|
|
|
|
|
|
|
if (!string.IsNullOrEmpty(entry.SystemFaction)) {
|
|
|
|
|
context.ControllingFaction = entry.SystemFaction;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!string.IsNullOrEmpty(entry.StationName)) {
|
|
|
|
|
context.CurrentStation = entry.StationName;
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-19 09:01:32 +02:00
|
|
|
|
if (!string.IsNullOrEmpty(entry.StationFaction)) {
|
|
|
|
|
context.StationOwner = entry.StationFaction;
|
|
|
|
|
} else {
|
|
|
|
|
context.StationOwner = null;
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-14 20:20:43 +02:00
|
|
|
|
if (entry.SystemFactions != null && entry.SystemFactions.Count > 0) {
|
2022-11-01 18:01:28 +01:00
|
|
|
|
context.SystemFactions[entry.StarSystem] = entry.SystemFactions;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-11 20:43:48 +02:00
|
|
|
|
internal class FSDJumpParser : ITransactionParserPart {
|
2023-03-02 20:09:19 +01:00
|
|
|
|
public void Parse(Entry e, TransactionParserContext context, TransactionParserOptions options, TransactionList transactions) {
|
2022-11-01 18:01:28 +01:00
|
|
|
|
FSDJumpEntry? entry = e as FSDJumpEntry;
|
|
|
|
|
if (entry == null) {
|
|
|
|
|
throw new NotImplementedException();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (entry.StarSystem == null) {
|
|
|
|
|
throw new InvalidJournalEntryException();
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-26 16:22:35 +01:00
|
|
|
|
// If you FSD jump straight out of the combat zone into a different system
|
|
|
|
|
// then the combat zone will be placed in the wrong system otherwise.
|
|
|
|
|
// This call needs to be *before* changing the current system.
|
|
|
|
|
context.DiscernCombatZone(transactions, e);
|
|
|
|
|
context.ResetCombatZone();
|
|
|
|
|
|
2023-05-11 20:43:48 +02:00
|
|
|
|
context.LeftInstance();
|
|
|
|
|
|
2022-11-01 18:01:28 +01:00
|
|
|
|
context.CurrentSystem = entry.StarSystem;
|
|
|
|
|
context.CurrentSystemAddress = entry.SystemAddress;
|
|
|
|
|
|
|
|
|
|
context.SystemsByID.TryAdd(entry.SystemAddress, entry.StarSystem);
|
|
|
|
|
|
|
|
|
|
if (!string.IsNullOrEmpty(entry.SystemFaction)) {
|
|
|
|
|
context.ControllingFaction = entry.SystemFaction;
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-14 20:20:43 +02:00
|
|
|
|
if (entry.SystemFactions != null && entry.SystemFactions.Count > 0) {
|
2022-11-01 18:01:28 +01:00
|
|
|
|
context.SystemFactions[entry.StarSystem] = entry.SystemFactions;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-11 20:43:48 +02:00
|
|
|
|
internal class DockedParser : ITransactionParserPart {
|
2023-03-02 20:09:19 +01:00
|
|
|
|
public void Parse(Entry e, TransactionParserContext context, TransactionParserOptions options, TransactionList transactions) {
|
2022-11-01 18:01:28 +01:00
|
|
|
|
DockedEntry? entry = e as DockedEntry;
|
|
|
|
|
if (entry == null) {
|
|
|
|
|
throw new NotImplementedException();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (entry.StarSystem == null || entry.SystemAddress == null) {
|
|
|
|
|
throw new InvalidJournalEntryException();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
context.CurrentSystem = entry.StarSystem;
|
|
|
|
|
context.CurrentSystemAddress = entry.SystemAddress;
|
|
|
|
|
|
|
|
|
|
context.SystemsByID.TryAdd(entry.SystemAddress.Value, entry.StarSystem);
|
|
|
|
|
|
|
|
|
|
if (!string.IsNullOrEmpty(entry.StationFaction)) {
|
2023-04-19 09:01:32 +02:00
|
|
|
|
context.StationOwner = entry.StationFaction;
|
2022-11-01 18:01:28 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!string.IsNullOrEmpty(entry.StationName)) {
|
|
|
|
|
context.CurrentStation = entry.StationName;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Commit crime can result in a transaction, especially if the crime committed is
|
|
|
|
|
/// murder.
|
|
|
|
|
/// </summary>
|
2023-05-11 20:43:48 +02:00
|
|
|
|
internal class CommitCrimeParser : ITransactionParserPart {
|
2023-03-02 20:09:19 +01:00
|
|
|
|
public void Parse(Entry e, TransactionParserContext context, TransactionParserOptions options, TransactionList transactions) {
|
2022-11-01 18:01:28 +01:00
|
|
|
|
CommitCrimeEntry? entry = e as CommitCrimeEntry;
|
|
|
|
|
if (entry == null) {
|
|
|
|
|
throw new NotImplementedException();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Right now we only care for murder
|
|
|
|
|
if (!entry.IsMurder) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
string? victim = entry.Victim;
|
|
|
|
|
|
|
|
|
|
if (victim == null) {
|
|
|
|
|
victim = entry.VictimLocalised;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If they were not properly scanned prior to the murder we do not have
|
|
|
|
|
// This information. But in the end the name of the NPC does not matter.
|
|
|
|
|
if (victim == null) {
|
|
|
|
|
victim = "Unknown";
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-26 00:30:42 +01:00
|
|
|
|
string faction;
|
|
|
|
|
|
|
|
|
|
if (entry.IsCrime(CrimeTypes.OnFootMurder)) {
|
|
|
|
|
if (entry.Faction == null) {
|
|
|
|
|
transactions.AddIncomplete(
|
|
|
|
|
new FoulMurder(),
|
2022-12-18 10:13:35 +01:00
|
|
|
|
"On foot murder victim did not have a faction", e
|
2022-11-26 00:30:42 +01:00
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// On foot murders are different, there the faction is
|
|
|
|
|
// the faction the NPC belonged too
|
|
|
|
|
faction = entry.Faction;
|
|
|
|
|
} else {
|
|
|
|
|
if (!context.NPCFaction.ContainsKey(victim)) {
|
|
|
|
|
transactions.AddIncomplete(
|
|
|
|
|
new FoulMurder(),
|
2022-12-18 10:13:35 +01:00
|
|
|
|
"Crime victim was not properly scanned.", e
|
2022-11-26 00:30:42 +01:00
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
faction = context.NPCFaction[victim];
|
2022-11-01 18:01:28 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
transactions.Add(new FoulMurder(entry) {
|
|
|
|
|
System = context.CurrentSystem,
|
2022-11-29 16:30:46 +01:00
|
|
|
|
IsLegacy = context.IsLegacy,
|
2022-11-01 18:01:28 +01:00
|
|
|
|
Faction = faction,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-11 20:43:48 +02:00
|
|
|
|
internal class MissionsParser : ITransactionParserPart {
|
2023-03-02 20:09:19 +01:00
|
|
|
|
public void Parse(Entry entry, TransactionParserContext context, TransactionParserOptions options, TransactionList transactions) {
|
2022-12-20 18:33:09 +01:00
|
|
|
|
MissionsEntry? missions = entry as MissionsEntry;
|
|
|
|
|
|
|
|
|
|
if (missions == null) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (context.CurrentSystem == null || context.CurrentSystemAddress == null) {
|
|
|
|
|
transactions.AddIncomplete(new MissionCompleted(),
|
|
|
|
|
"Could not determine current location on Missions event.",
|
|
|
|
|
entry
|
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach (Mission mission in missions.Active) {
|
|
|
|
|
try {
|
|
|
|
|
context.MissionAccepted(mission);
|
|
|
|
|
} catch (Exception exception) {
|
|
|
|
|
transactions.AddIncomplete(new MissionCompleted(),
|
|
|
|
|
exception.Message,
|
|
|
|
|
entry
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-11 20:43:48 +02:00
|
|
|
|
internal class MissionAcceptedParser : ITransactionParserPart {
|
2023-03-02 20:09:19 +01:00
|
|
|
|
public void Parse(Entry e, TransactionParserContext context, TransactionParserOptions options, TransactionList transactions) {
|
2022-11-01 18:01:28 +01:00
|
|
|
|
MissionAcceptedEntry? entry = e as MissionAcceptedEntry;
|
|
|
|
|
if (entry == null) {
|
|
|
|
|
throw new NotImplementedException();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (context.CurrentSystem == null || context.CurrentSystemAddress == null) {
|
|
|
|
|
transactions.AddIncomplete(new MissionCompleted(),
|
2022-12-18 10:13:35 +01:00
|
|
|
|
"Could not determine current location on mission acceptance.",
|
|
|
|
|
e
|
2022-11-01 18:01:28 +01:00
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
context.MissionAccepted(entry);
|
|
|
|
|
} catch (Exception exception) {
|
|
|
|
|
transactions.AddIncomplete(new MissionCompleted(),
|
2022-12-18 10:13:35 +01:00
|
|
|
|
exception.Message,
|
|
|
|
|
e
|
2022-11-01 18:01:28 +01:00
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-11 20:43:48 +02:00
|
|
|
|
internal class MissionCompletedParser : ITransactionParserPart {
|
2023-03-02 20:09:19 +01:00
|
|
|
|
public void Parse(Entry e, TransactionParserContext context, TransactionParserOptions options, TransactionList transactions) {
|
2022-11-01 18:01:28 +01:00
|
|
|
|
MissionCompletedEntry? entry = e as MissionCompletedEntry;
|
2022-11-24 15:53:40 +01:00
|
|
|
|
if (entry == null || entry.Mission == null) {
|
2022-11-01 18:01:28 +01:00
|
|
|
|
throw new NotImplementedException();
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-19 09:01:32 +02:00
|
|
|
|
Mission? mission;
|
|
|
|
|
Location? accepted_location;
|
2022-11-24 15:53:40 +01:00
|
|
|
|
string? target_faction_name = entry.Mission.TargetFaction;
|
|
|
|
|
string? source_faction_name = entry.Mission.Faction;
|
2022-11-01 18:01:28 +01:00
|
|
|
|
|
|
|
|
|
// We did not find when the mission was accepted.
|
2022-12-20 18:33:09 +01:00
|
|
|
|
if (!context.AcceptedMissions.TryGetValue(entry.Mission.MissionID, out mission)) {
|
2022-11-01 18:01:28 +01:00
|
|
|
|
transactions.AddIncomplete(new MissionCompleted(),
|
|
|
|
|
String.Format("Mission acceptance for mission id {0} was not found",
|
2022-12-18 10:13:35 +01:00
|
|
|
|
entry.Mission.MissionID), e);
|
2022-11-01 18:01:28 +01:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-24 15:53:40 +01:00
|
|
|
|
if (!context.AcceptedMissionLocation.TryGetValue(entry.Mission.MissionID, out accepted_location)) {
|
2022-11-01 18:01:28 +01:00
|
|
|
|
transactions.AddIncomplete(new MissionCompleted(),
|
|
|
|
|
String.Format("Location for acceptance for mission id {0} was not found",
|
2022-12-18 10:13:35 +01:00
|
|
|
|
entry.Mission.MissionID), e);
|
2022-11-01 18:01:28 +01:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// This block does some preliminary "repairs" on the influences block of a completed
|
|
|
|
|
// mission. Sometimes these entries are broken, or are missing information for later
|
|
|
|
|
// parsing.
|
2022-11-24 15:53:40 +01:00
|
|
|
|
foreach (var other in entry.Mission.Influences) {
|
2022-11-01 18:01:28 +01:00
|
|
|
|
string faction = other.Key;
|
|
|
|
|
if (string.IsNullOrEmpty(faction)) {
|
|
|
|
|
// Target faction might be empty string, in special cases. For example if you
|
|
|
|
|
// scan a surface installation, and the target faction of the surface installation
|
|
|
|
|
// gets negative REP, but the surface installation is not owned by anyone.
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Fun ahead: sometimes the influence list is empty for a faction entry. Here
|
|
|
|
|
// we try to repair it.
|
|
|
|
|
if (other.Value.Count == 0) {
|
|
|
|
|
if (string.Compare(target_faction_name, faction, true) == 0) {
|
|
|
|
|
if (context.CurrentSystemAddress == null) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2023-09-08 11:20:49 +02:00
|
|
|
|
other.Value.Add(context.CurrentSystemAddress.Value, new MissionInfluence());
|
2022-11-01 18:01:28 +01:00
|
|
|
|
// Mission gave no influence to the target faction, so we assume
|
|
|
|
|
// the target faction was in the same system.
|
|
|
|
|
} else if (string.Compare(source_faction_name, faction, true) == 0) {
|
|
|
|
|
// This happens if the source faction is not getting any influence
|
|
|
|
|
// This could be if the source faction is in a conflict, and thus does
|
|
|
|
|
// not gain any influence at all.
|
2023-09-08 11:20:49 +02:00
|
|
|
|
other.Value.Add(accepted_location.SystemAddress, new MissionInfluence());
|
2022-11-01 18:01:28 +01:00
|
|
|
|
|
|
|
|
|
// Just check if the target/source faction are the same, in which case
|
|
|
|
|
// we also have to make an additional entry
|
|
|
|
|
if (string.Compare(source_faction_name, target_faction_name, true) == 0 &&
|
|
|
|
|
context.CurrentSystemAddress != null) {
|
2023-09-08 11:20:49 +02:00
|
|
|
|
other.Value.Add(context.CurrentSystemAddress.Value, new MissionInfluence());
|
2022-11-01 18:01:28 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Now actually parse completed mission
|
|
|
|
|
foreach (var influences in other.Value) {
|
|
|
|
|
ulong system_address = influences.Key;
|
|
|
|
|
string? system;
|
|
|
|
|
|
|
|
|
|
if (!context.SystemsByID.TryGetValue(system_address, out system)) {
|
|
|
|
|
transactions.AddIncomplete(new MissionCompleted(),
|
2022-12-18 10:13:35 +01:00
|
|
|
|
string.Format("Unknown system {0}, unable to assign that mission a target", system_address),
|
|
|
|
|
e
|
2022-11-01 18:01:28 +01:00
|
|
|
|
);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (string.Compare(faction, source_faction_name, true) == 0 &&
|
|
|
|
|
system_address == accepted_location.SystemAddress) {
|
|
|
|
|
// Source and target faction are the same, and this is the block
|
|
|
|
|
// for the source system. So we make a full mission completed entry.
|
2022-12-03 14:16:45 +01:00
|
|
|
|
transactions.Add(new MissionCompleted() {
|
|
|
|
|
CompletedEntry = entry,
|
2022-12-20 18:33:09 +01:00
|
|
|
|
Mission = mission,
|
2022-11-01 18:01:28 +01:00
|
|
|
|
System = accepted_location.StarSystem,
|
|
|
|
|
Faction = source_faction_name,
|
|
|
|
|
SystemAddress = accepted_location.SystemAddress,
|
|
|
|
|
Station = accepted_location.Station,
|
2022-11-29 16:30:46 +01:00
|
|
|
|
IsLegacy = context.IsLegacy,
|
2022-11-01 18:01:28 +01:00
|
|
|
|
});
|
|
|
|
|
} else if (string.Compare(faction, source_faction_name, true) != 0 ||
|
|
|
|
|
(string.Compare(faction, source_faction_name) == 0 &&
|
|
|
|
|
system_address != accepted_location.SystemAddress)) {
|
2023-03-03 15:59:41 +01:00
|
|
|
|
// Whether we ignore influence support
|
|
|
|
|
if (options.IgnoreInfluenceSupport) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2022-11-01 18:01:28 +01:00
|
|
|
|
// Source or target faction are different, and/or the system
|
|
|
|
|
// differs. Sometimes missions go to different systems but to
|
|
|
|
|
// the same faction.
|
|
|
|
|
transactions.Add(new InfluenceSupport() {
|
2022-12-20 18:33:09 +01:00
|
|
|
|
Mission = mission,
|
2022-11-01 18:01:28 +01:00
|
|
|
|
Faction = faction,
|
|
|
|
|
Influence = influences.Value,
|
|
|
|
|
System = system,
|
|
|
|
|
SystemAddress = system_address,
|
|
|
|
|
RelevantMission = entry,
|
2022-11-29 16:30:46 +01:00
|
|
|
|
IsLegacy = context.IsLegacy,
|
2022-11-01 18:01:28 +01:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-11 20:43:48 +02:00
|
|
|
|
internal class MissionFailedParser : ITransactionParserPart {
|
2023-03-02 20:09:19 +01:00
|
|
|
|
public void Parse(Entry e, TransactionParserContext context, TransactionParserOptions options, TransactionList transactions) {
|
2023-04-19 09:01:32 +02:00
|
|
|
|
Mission? mission;
|
|
|
|
|
Location? accepted_location;
|
|
|
|
|
string? accepted_system;
|
2022-11-01 18:01:28 +01:00
|
|
|
|
|
|
|
|
|
MissionFailedEntry? entry = e as MissionFailedEntry;
|
|
|
|
|
if (entry == null) {
|
|
|
|
|
throw new NotImplementedException();
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-24 14:25:31 +01:00
|
|
|
|
if (entry.Mission == null) {
|
|
|
|
|
throw new InvalidJournalEntryException("No mission specified in mission failure");
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-20 18:33:09 +01:00
|
|
|
|
if (!context.AcceptedMissions.TryGetValue(entry.Mission.MissionID, out mission)) {
|
2022-11-01 18:01:28 +01:00
|
|
|
|
transactions.AddIncomplete(new MissionFailed(),
|
2022-12-18 10:13:35 +01:00
|
|
|
|
"Mission acceptance was not found", e
|
2022-11-01 18:01:28 +01:00
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-20 18:33:09 +01:00
|
|
|
|
if (!context.AcceptedMissionLocation.TryGetValue(mission.MissionID, out accepted_location)) {
|
2022-11-01 18:01:28 +01:00
|
|
|
|
transactions.AddIncomplete(new MissionFailed(),
|
2022-12-18 10:13:35 +01:00
|
|
|
|
"Unable to figure out where failed mission was accepted", e
|
2022-11-01 18:01:28 +01:00
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!context.SystemsByID.TryGetValue(accepted_location.SystemAddress, out accepted_system)) {
|
|
|
|
|
transactions.AddIncomplete(new MissionFailed(),
|
2022-12-18 10:13:35 +01:00
|
|
|
|
"Unable to figure out in which system failed mission was accepted", e
|
2022-11-01 18:01:28 +01:00
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-20 18:48:00 +01:00
|
|
|
|
if (string.IsNullOrEmpty(mission.Faction)) {
|
|
|
|
|
transactions.AddIncomplete(new MissionFailed(),
|
|
|
|
|
"Could not determine for what faction you failed a mission. This happens if the " +
|
|
|
|
|
"mission acceptance is not within the given time frame.",
|
|
|
|
|
entry
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-20 18:33:09 +01:00
|
|
|
|
transactions.Add(new MissionFailed(entry) {
|
|
|
|
|
Faction = mission?.Faction,
|
|
|
|
|
Mission = mission,
|
2022-11-01 18:01:28 +01:00
|
|
|
|
Station = accepted_location.Station,
|
|
|
|
|
System = accepted_location.StarSystem,
|
|
|
|
|
SystemAddress = accepted_location.SystemAddress,
|
2022-11-29 16:30:46 +01:00
|
|
|
|
IsLegacy = context.IsLegacy,
|
2022-11-01 18:01:28 +01:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-11 20:43:48 +02:00
|
|
|
|
internal class SellMicroResourcesParser : ITransactionParserPart {
|
2023-03-02 20:09:19 +01:00
|
|
|
|
public void Parse(Entry e, TransactionParserContext context, TransactionParserOptions options, TransactionList transactions) {
|
2022-11-01 18:01:28 +01:00
|
|
|
|
SellMicroResourcesEntry? entry = e as SellMicroResourcesEntry;
|
|
|
|
|
if (entry == null) {
|
|
|
|
|
throw new NotImplementedException();
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-19 09:01:32 +02:00
|
|
|
|
if (context.StationOwner == null) {
|
|
|
|
|
transactions.AddIncomplete(
|
|
|
|
|
new SellMicroResources(),
|
|
|
|
|
"Could not discern the station owner for micro resources sell.",
|
|
|
|
|
e);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-01 18:01:28 +01:00
|
|
|
|
transactions.Add(new SellMicroResources(entry) {
|
|
|
|
|
System = context.CurrentSystem,
|
|
|
|
|
Station = context.CurrentStation,
|
2023-04-19 09:01:32 +02:00
|
|
|
|
Faction = context.StationOwner,
|
2022-11-29 16:30:46 +01:00
|
|
|
|
IsLegacy = context.IsLegacy,
|
2022-11-01 18:01:28 +01:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-11 20:43:48 +02:00
|
|
|
|
internal class SearchAndRescueParser : ITransactionParserPart {
|
2023-03-02 20:09:19 +01:00
|
|
|
|
public void Parse(Entry e, TransactionParserContext context, TransactionParserOptions options, TransactionList transactions) {
|
2022-11-01 18:01:28 +01:00
|
|
|
|
SearchAndRescueEntry? entry = e as SearchAndRescueEntry;
|
|
|
|
|
if (entry == null) {
|
|
|
|
|
throw new NotImplementedException();
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-19 09:01:32 +02:00
|
|
|
|
if (context.StationOwner == null) {
|
|
|
|
|
transactions.AddIncomplete(
|
|
|
|
|
new OrganicData(),
|
|
|
|
|
"Could not discern the station owner for S&R operations.",
|
|
|
|
|
e);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-01 18:01:28 +01:00
|
|
|
|
transactions.Add(new SearchAndRescue(entry) {
|
|
|
|
|
System = context.CurrentSystem,
|
2023-04-19 09:01:32 +02:00
|
|
|
|
Station = context.CurrentStation,
|
|
|
|
|
Faction = context.StationOwner,
|
2022-11-29 16:30:46 +01:00
|
|
|
|
IsLegacy = context.IsLegacy,
|
2022-11-01 18:01:28 +01:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-11 20:43:48 +02:00
|
|
|
|
internal class FactionKillBondParser : ITransactionParserPart {
|
2023-03-02 20:09:19 +01:00
|
|
|
|
public void Parse(Entry e, TransactionParserContext context, TransactionParserOptions options, TransactionList transactions) {
|
2022-11-25 13:45:02 +01:00
|
|
|
|
FactionKillBondEntry? entry = e as FactionKillBondEntry;
|
|
|
|
|
if (entry == null) {
|
|
|
|
|
throw new NotImplementedException();
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-25 15:48:00 +01:00
|
|
|
|
if (Factions.IsThargoid(entry.VictimFaction)) {
|
2022-11-25 13:45:02 +01:00
|
|
|
|
// Thargoid bonk
|
2022-11-25 15:48:00 +01:00
|
|
|
|
transactions.Add(new ThargoidKill(entry) {
|
|
|
|
|
System = context.CurrentSystem,
|
|
|
|
|
Faction = Factions.PilotsFederation,
|
|
|
|
|
Station = context.CurrentStation,
|
2022-11-29 16:30:46 +01:00
|
|
|
|
IsLegacy = context.IsLegacy,
|
2022-11-25 15:48:00 +01:00
|
|
|
|
});
|
2022-11-25 17:13:17 +01:00
|
|
|
|
|
2022-12-03 14:42:32 +01:00
|
|
|
|
ThargoidVessel vessel = Thargoid.GetVesselByPayout(entry.Reward);
|
|
|
|
|
if (vessel != ThargoidVessel.Unknown) {
|
|
|
|
|
if (vessel == ThargoidVessel.Scout) {
|
|
|
|
|
++context.ThargoidScoutKills;
|
|
|
|
|
} else {
|
|
|
|
|
++context.ThargoidInterceptorKills;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-25 17:13:17 +01:00
|
|
|
|
// We are done
|
|
|
|
|
return;
|
2022-11-25 13:45:02 +01:00
|
|
|
|
}
|
2022-11-25 17:13:17 +01:00
|
|
|
|
|
|
|
|
|
context.RecordCombatBond(entry);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-11 20:43:48 +02:00
|
|
|
|
internal class EmbarkDisembarkParser : ITransactionParserPart {
|
2023-03-02 20:09:19 +01:00
|
|
|
|
public void Parse(Entry e, TransactionParserContext context, TransactionParserOptions options, TransactionList transactions) {
|
2022-11-25 17:13:17 +01:00
|
|
|
|
if (e.Is(Events.Embark)) {
|
|
|
|
|
context.IsOnFoot = false;
|
|
|
|
|
} else if (e.Is(Events.Disembark)) {
|
|
|
|
|
context.IsOnFoot = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-11 20:43:48 +02:00
|
|
|
|
internal class SupercruiseEntryParser : ITransactionParserPart {
|
2023-03-02 20:09:19 +01:00
|
|
|
|
public void Parse(Entry entry, TransactionParserContext context, TransactionParserOptions options, TransactionList transactions) {
|
2022-12-09 17:36:26 +01:00
|
|
|
|
// After a super cruise entry we are no longer on foot.
|
|
|
|
|
context.IsOnFoot = false;
|
2022-11-25 23:06:08 +01:00
|
|
|
|
context.DiscernCombatZone(transactions, entry);
|
2022-11-25 17:13:17 +01:00
|
|
|
|
context.ResetCombatZone();
|
2023-05-11 20:43:48 +02:00
|
|
|
|
// Supercruise entry means you left the current local instance
|
|
|
|
|
context.LeftInstance();
|
2022-11-25 17:13:17 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-11 20:43:48 +02:00
|
|
|
|
internal class ShutdownParser : ITransactionParserPart {
|
2023-03-02 20:09:19 +01:00
|
|
|
|
public void Parse(Entry entry, TransactionParserContext context, TransactionParserOptions options, TransactionList transactions) {
|
2022-11-25 23:06:08 +01:00
|
|
|
|
context.DiscernCombatZone(transactions, entry);
|
2022-11-25 17:13:17 +01:00
|
|
|
|
context.ResetCombatZone();
|
2023-05-11 20:43:48 +02:00
|
|
|
|
// Shutdown (logout) means you left the instance
|
|
|
|
|
context.LeftInstance();
|
2022-11-25 13:45:02 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-11 20:43:48 +02:00
|
|
|
|
internal class CapShipBondParser : ITransactionParserPart {
|
2023-03-02 20:09:19 +01:00
|
|
|
|
public void Parse(Entry entry, TransactionParserContext context, TransactionParserOptions options, TransactionList transactions) {
|
2022-11-28 19:47:41 +01:00
|
|
|
|
if (entry.GetType() != typeof(CapShipBondEntry)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
context.HaveSeenCapShip = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-11 20:43:48 +02:00
|
|
|
|
internal class FileHeaderParser : ITransactionParserPart {
|
2023-03-02 20:09:19 +01:00
|
|
|
|
public void Parse(Entry entry, TransactionParserContext context, TransactionParserOptions options, TransactionList transactions) {
|
2022-11-29 16:30:46 +01:00
|
|
|
|
FileHeaderEntry? fileheader = entry as FileHeaderEntry;
|
|
|
|
|
|
|
|
|
|
if (fileheader == null) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
context.IsLegacy = fileheader.IsLegacy;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-11 20:43:48 +02:00
|
|
|
|
internal class ReceiveTextParser : ITransactionParserPart {
|
2023-03-02 20:09:19 +01:00
|
|
|
|
public void Parse(Entry entry, TransactionParserContext context, TransactionParserOptions options, TransactionList transactions) {
|
2022-12-07 16:42:44 +01:00
|
|
|
|
ReceiveTextEntry? receivetext = entry as ReceiveTextEntry;
|
|
|
|
|
|
|
|
|
|
if (receivetext == null) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (string.Compare(receivetext.Channel, Channels.NPC) != 0) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (string.Compare(receivetext.NPCCategory, NPCs.AXMilitary) == 0) {
|
|
|
|
|
context.HaveSeenAXWarzoneNPC = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-11 20:43:48 +02:00
|
|
|
|
internal class DiedParser : ITransactionParserPart {
|
2023-03-02 20:09:19 +01:00
|
|
|
|
public void Parse(Entry entry, TransactionParserContext context, TransactionParserOptions options, TransactionList transactions) {
|
2023-05-25 17:33:59 +02:00
|
|
|
|
// Death only matters in ship. On foot you can just redeploy with the dropship.
|
|
|
|
|
if (context.IsOnFoot) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2022-12-07 16:42:44 +01:00
|
|
|
|
// You can't complete a combat zone if you die in it. Others might keep it open
|
|
|
|
|
// for you, but still you will not have completed it unless you jump back in.
|
|
|
|
|
context.ResetCombatZone();
|
2023-05-11 20:43:48 +02:00
|
|
|
|
// Dying also moves you back to another instance
|
|
|
|
|
context.LeftInstance();
|
2022-12-07 16:42:44 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-11 20:43:48 +02:00
|
|
|
|
internal class DropshipDeployParser : ITransactionParserPart {
|
2023-03-02 20:09:19 +01:00
|
|
|
|
public void Parse(Entry entry, TransactionParserContext context, TransactionParserOptions options, TransactionList transactions) {
|
2022-12-09 17:36:26 +01:00
|
|
|
|
// On drop ship deploy we are now on foot
|
|
|
|
|
context.IsOnFoot = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-01 18:01:28 +01:00
|
|
|
|
public class TransactionParser {
|
2023-05-11 20:43:48 +02:00
|
|
|
|
private static Dictionary<string, ITransactionParserPart> ParserParts { get; } = new()
|
2022-11-01 18:01:28 +01:00
|
|
|
|
{
|
2023-08-26 10:53:25 +02:00
|
|
|
|
{ Events.ApproachSettlement, new ApproachSettlementParser() },
|
2022-11-28 19:47:41 +01:00
|
|
|
|
{ Events.CapShipBond, new CapShipBondParser() },
|
2024-01-29 18:23:19 +01:00
|
|
|
|
{ Events.CarrierJump, new CarrierJumpParser() },
|
2022-12-11 12:29:32 +01:00
|
|
|
|
{ Events.Commander, new CommanderParser() },
|
2022-11-01 18:01:28 +01:00
|
|
|
|
{ Events.CommitCrime, new CommitCrimeParser() },
|
2022-12-07 16:42:44 +01:00
|
|
|
|
{ Events.Died, new DiedParser() },
|
2022-11-25 17:13:17 +01:00
|
|
|
|
{ Events.Disembark, new EmbarkDisembarkParser() },
|
2022-11-01 18:01:28 +01:00
|
|
|
|
{ Events.Docked, new DockedParser() },
|
2022-12-09 17:36:26 +01:00
|
|
|
|
{ Events.DropshipDeploy, new DropshipDeployParser() },
|
2022-11-25 17:13:17 +01:00
|
|
|
|
{ Events.Embark, new EmbarkDisembarkParser() },
|
2022-11-25 13:45:02 +01:00
|
|
|
|
{ Events.FactionKillBond, new FactionKillBondParser() },
|
2022-11-29 16:30:46 +01:00
|
|
|
|
{ Events.FileHeader, new FileHeaderParser() },
|
2022-11-01 18:01:28 +01:00
|
|
|
|
{ Events.FSDJump, new FSDJumpParser() },
|
|
|
|
|
{ Events.Location, new LocationParser() },
|
|
|
|
|
{ Events.MarketBuy, new MarketBuyParser() },
|
|
|
|
|
{ Events.MarketSell, new MarketSellParser() },
|
|
|
|
|
{ Events.MissionAccepted, new MissionAcceptedParser() },
|
|
|
|
|
{ Events.MissionCompleted, new MissionCompletedParser() },
|
|
|
|
|
{ Events.MissionFailed, new MissionFailedParser() },
|
2022-12-20 18:33:09 +01:00
|
|
|
|
{ Events.Missions, new MissionsParser() },
|
2022-11-01 18:01:28 +01:00
|
|
|
|
{ Events.MultiSellExplorationData, new MultiSellExplorationDataParser() },
|
2024-01-29 19:24:53 +01:00
|
|
|
|
{ Events.Music, new MusicParser() },
|
2022-12-07 16:42:44 +01:00
|
|
|
|
{ Events.ReceiveText, new ReceiveTextParser() },
|
2022-11-01 18:01:28 +01:00
|
|
|
|
{ Events.RedeemVoucher, new RedeemVoucherParser() },
|
|
|
|
|
{ Events.SearchAndRescue, new SearchAndRescueParser() },
|
|
|
|
|
{ Events.SellExplorationData, new SellExplorationDataParser() },
|
|
|
|
|
{ Events.SellMicroResources, new SellMicroResourcesParser() },
|
|
|
|
|
{ Events.SellOrganicData, new SellOrganicDataParser() },
|
|
|
|
|
{ Events.ShipTargeted, new ShipTargetedParser() },
|
2022-11-25 17:13:17 +01:00
|
|
|
|
{ Events.Shutdown, new ShutdownParser() },
|
2023-05-11 20:43:48 +02:00
|
|
|
|
{ Events.SupercruiseDestinationDrop, new SupercruiseDestinationDropParser() },
|
2022-11-25 17:13:17 +01:00
|
|
|
|
{ Events.SupercruiseEntry, new SupercruiseEntryParser() },
|
2022-11-01 18:01:28 +01:00
|
|
|
|
};
|
|
|
|
|
|
2022-11-25 18:06:19 +01:00
|
|
|
|
public bool IsRelevant(string entry) {
|
|
|
|
|
return ParserParts.ContainsKey(entry);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool IsRelevant(Entry e) {
|
|
|
|
|
if (e.Event == null) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return IsRelevant(e.Event);
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-02 20:09:19 +01:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Parses a list of entries with default options.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="entries"></param>
|
|
|
|
|
/// <returns></returns>
|
2022-11-01 18:01:28 +01:00
|
|
|
|
public List<Transaction>? Parse(IEnumerable<Entry> entries) {
|
2023-03-02 20:09:19 +01:00
|
|
|
|
TransactionParserOptions defaultOptions = new();
|
|
|
|
|
return Parse(entries, defaultOptions);
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-18 21:33:20 +02:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// List of commanders seen during parsing.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public List<string> Commanders { get; set; } = new();
|
|
|
|
|
|
2023-03-02 20:09:19 +01:00
|
|
|
|
public List<Transaction>? Parse(IEnumerable<Entry> entries, TransactionParserOptions options) {
|
2022-11-01 18:01:28 +01:00
|
|
|
|
TransactionList transactions = new();
|
|
|
|
|
TransactionParserContext context = new();
|
|
|
|
|
|
|
|
|
|
foreach (Entry entry in entries) {
|
|
|
|
|
if (entry.Event == null) {
|
|
|
|
|
throw new InvalidJournalEntryException();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!ParserParts.ContainsKey(entry.Event)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-11 20:43:48 +02:00
|
|
|
|
ITransactionParserPart transactionParserPart = ParserParts[entry.Event];
|
2023-03-02 20:09:19 +01:00
|
|
|
|
transactionParserPart.Parse(entry, context, options, transactions);
|
2022-11-01 18:01:28 +01:00
|
|
|
|
}
|
|
|
|
|
|
2024-09-18 21:33:20 +02:00
|
|
|
|
// Copy out list of commanders seen
|
|
|
|
|
Commanders = context.Commanders;
|
|
|
|
|
|
2022-11-01 18:01:28 +01:00
|
|
|
|
return transactions.ToList();
|
|
|
|
|
}
|
|
|
|
|
}
|