814 lines
30 KiB
C#
814 lines
30 KiB
C#
using EDPlayerJournal.Entries;
|
|
using System.Collections.Generic;
|
|
using System.Reflection.Metadata.Ecma335;
|
|
using System.Transactions;
|
|
|
|
namespace EDPlayerJournal.BGS;
|
|
|
|
internal class TransactionParserContext {
|
|
public string? CurrentSystem { get; set; }
|
|
public ulong? CurrentSystemAddress { get; set; }
|
|
public string? CurrentStation { get; set; }
|
|
public string? ControllingFaction { get; set; }
|
|
|
|
public bool IsOnFoot { get; set; } = false;
|
|
|
|
public string? LastRecordedAwardingFaction { get; set; }
|
|
|
|
public ulong? HighestCombatBond { get; set; }
|
|
|
|
public bool HaveSeenWarzoneNPC { get; set; } = false;
|
|
|
|
/// <summary>
|
|
/// How many on foot kills were done.
|
|
/// </summary>
|
|
public ulong OnFootKills { get; set; } = 0;
|
|
|
|
/// <summary>
|
|
/// How many ship kills were done.
|
|
/// </summary>
|
|
public ulong ShipKills { get; set; } = 0;
|
|
|
|
/// <summary>
|
|
/// A list of accepted missions index by their mission ID
|
|
/// </summary>
|
|
public Dictionary<ulong, MissionAcceptedEntry> AcceptedMissions { get; } = new();
|
|
public Dictionary<ulong, Location> AcceptedMissionLocation { get; } = new();
|
|
/// <summary>
|
|
/// A way to lookup a system by its system id
|
|
/// </summary>
|
|
public Dictionary<ulong, string> SystemsByID { get; } = new();
|
|
/// <summary>
|
|
/// A list of factions present in the given star system
|
|
/// </summary>
|
|
public Dictionary<string, List<Faction>> SystemFactions { get; } = new();
|
|
/// <summary>
|
|
/// To which faction a given named NPC belonged to.
|
|
/// </summary>
|
|
public Dictionary<string, string> NPCFaction { get; } = new();
|
|
/// <summary>
|
|
/// Buy costs for a given commodity
|
|
/// </summary>
|
|
public Dictionary<string, long> BuyCost = new();
|
|
|
|
public void DiscernCombatZone(TransactionList transactions, Entry e) {
|
|
string grade = "Low";
|
|
string cztype;
|
|
ulong? highest = HighestCombatBond;
|
|
|
|
if (highest == null || LastRecordedAwardingFaction == null) {
|
|
return;
|
|
}
|
|
|
|
if (OnFootKills > 0) {
|
|
cztype = "OnFoot";
|
|
// High on foot combat zones have enforcers that bring 80k a pop
|
|
if (highest >= 80000) {
|
|
grade = "High";
|
|
} else if (highest >= 4000) {
|
|
grade = "Medium";
|
|
}
|
|
} else if (ShipKills > 0) {
|
|
// Ship combat zones can be identified by the amount of kills
|
|
if (ShipKills > 20) {
|
|
grade = "High";
|
|
} else if (ShipKills > 10) {
|
|
grade = "Medium";
|
|
}
|
|
if (HaveSeenWarzoneNPC && grade == "Low") {
|
|
// We have seen a warzone NPC so we know its at least medium
|
|
grade = "Medium";
|
|
}
|
|
cztype = "Ship";
|
|
} else {
|
|
transactions.AddIncomplete(new CombatZone(), "Failed to discern combat zone type");
|
|
return;
|
|
}
|
|
|
|
CombatZone zone = new CombatZone() {
|
|
System = CurrentSystem,
|
|
Faction = LastRecordedAwardingFaction,
|
|
Grade = grade,
|
|
Type = cztype,
|
|
Amount = 1,
|
|
};
|
|
zone.Entries.Add(e);
|
|
transactions.Add(zone);
|
|
}
|
|
|
|
public void RecordCombatBond(FactionKillBondEntry e) {
|
|
if (HighestCombatBond == null || e.Reward > HighestCombatBond) {
|
|
HighestCombatBond = e.Reward;
|
|
}
|
|
|
|
LastRecordedAwardingFaction = e.AwardingFaction;
|
|
|
|
if (IsOnFoot) {
|
|
++OnFootKills;
|
|
} else {
|
|
++ShipKills;
|
|
}
|
|
}
|
|
|
|
public void ResetCombatZone() {
|
|
HighestCombatBond = null;
|
|
HaveSeenWarzoneNPC = false;
|
|
LastRecordedAwardingFaction = null;
|
|
OnFootKills = 0;
|
|
ShipKills = 0;
|
|
}
|
|
|
|
public void BoughtCargo(string? cargo, long? cost) {
|
|
if (cargo == null || cost == null) {
|
|
return;
|
|
}
|
|
|
|
BuyCost[cargo] = cost.Value;
|
|
}
|
|
|
|
public List<Faction>? GetFactions(string? system) {
|
|
if (system == null || !SystemFactions.ContainsKey(system)) {
|
|
return null;
|
|
}
|
|
|
|
return SystemFactions[system];
|
|
}
|
|
|
|
public void MissionAccepted(MissionAcceptedEntry accepted) {
|
|
if (CurrentSystem == null || CurrentSystemAddress == null) {
|
|
throw new Exception("Mission accepted without knowing where.");
|
|
}
|
|
|
|
if (accepted.Mission == null) {
|
|
throw new Exception("Mission is null");
|
|
}
|
|
|
|
AcceptedMissions.TryAdd(accepted.Mission.MissionID, accepted);
|
|
|
|
Location location = new() {
|
|
StarSystem = CurrentSystem,
|
|
SystemAddress = CurrentSystemAddress.Value,
|
|
Station = (CurrentStation ?? ""),
|
|
};
|
|
|
|
AcceptedMissionLocation.TryAdd(accepted.Mission.MissionID, location);
|
|
}
|
|
}
|
|
|
|
public class TransactionList : List<Transaction> {
|
|
public void AddIncomplete(Transaction underlying, string reason) {
|
|
Add(new IncompleteTransaction(underlying, reason));
|
|
}
|
|
}
|
|
|
|
internal interface TransactionParserPart{
|
|
/// <summary>
|
|
/// Parse a given entry of the entry type specified when declaring to implement this
|
|
/// interface. You must add your transaction to the passed list in case you did have
|
|
/// enough information to parse one or more. You may update the parser context
|
|
/// with new information in case the entry yielded new information.
|
|
/// Throw an exception if there was an error or a malformed entry. If you have an
|
|
/// incomplete entry, i.e. not enough information to complete one, add an
|
|
/// IncompleteTransaction to the list
|
|
/// </summary>
|
|
/// <param name="entry">The entry to parse</param>
|
|
/// <param name="context">Parsing context that may contain useful information</param>
|
|
/// <param name="transactions">List of parsed transactions</param>
|
|
public void Parse(Entry entry, TransactionParserContext context, TransactionList transactions);
|
|
}
|
|
|
|
/// <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>
|
|
internal class LocationParser : TransactionParserPart {
|
|
public void Parse(Entry e, TransactionParserContext context, TransactionList transactions) {
|
|
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;
|
|
}
|
|
|
|
if (!context.SystemFactions.ContainsKey(entry.StarSystem) &&
|
|
entry.SystemFactions != null && entry.SystemFactions.Count > 0) {
|
|
context.SystemFactions[entry.StarSystem] = entry.SystemFactions;
|
|
}
|
|
}
|
|
}
|
|
|
|
internal class FSDJumpParser : TransactionParserPart {
|
|
public void Parse(Entry e, TransactionParserContext context, TransactionList transactions) {
|
|
FSDJumpEntry? entry = e as FSDJumpEntry;
|
|
if (entry == null) {
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
if (entry.StarSystem == null) {
|
|
throw new InvalidJournalEntryException();
|
|
}
|
|
|
|
// 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();
|
|
|
|
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 (!context.SystemFactions.ContainsKey(entry.StarSystem) &&
|
|
entry.SystemFactions != null && entry.SystemFactions.Count > 0) {
|
|
context.SystemFactions[entry.StarSystem] = entry.SystemFactions;
|
|
}
|
|
}
|
|
}
|
|
|
|
internal class DockedParser : TransactionParserPart {
|
|
public void Parse(Entry e, TransactionParserContext context, TransactionList transactions) {
|
|
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)) {
|
|
context.ControllingFaction = entry.StationFaction;
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(entry.StationName)) {
|
|
context.CurrentStation = entry.StationName;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// With ship targeted we might find out to which faction a given NPC belonged. This is
|
|
/// useful later when said NPC gets killed or murdered.
|
|
/// </summary>
|
|
internal class ShipTargetedParser : TransactionParserPart {
|
|
public void Parse(Entry e, TransactionParserContext context, TransactionList transactions) {
|
|
ShipTargetedEntry? entry = e as ShipTargetedEntry;
|
|
if (entry == null) {
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
// Scan happens in stages, and sometimes this information is not known
|
|
// yet. Do now throw an error, this is expected behaviour.
|
|
if (!string.IsNullOrEmpty(entry.PilotNameLocalised) &&
|
|
!string.IsNullOrEmpty(entry.Faction)) {
|
|
context.NPCFaction.TryAdd(entry.PilotNameLocalised, entry.Faction);
|
|
}
|
|
|
|
// We have seen a spec ops, so we know its medium or higher
|
|
if (NPCs.IsWarzoneNPC(entry.PilotName)) {
|
|
context.HaveSeenWarzoneNPC = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Commit crime can result in a transaction, especially if the crime committed is
|
|
/// murder.
|
|
/// </summary>
|
|
internal class CommitCrimeParser : TransactionParserPart {
|
|
public void Parse(Entry e, TransactionParserContext context, TransactionList transactions) {
|
|
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";
|
|
}
|
|
|
|
string faction;
|
|
|
|
if (entry.IsCrime(CrimeTypes.OnFootMurder)) {
|
|
if (entry.Faction == null) {
|
|
transactions.AddIncomplete(
|
|
new FoulMurder(),
|
|
"On foot murder victim did not have a faction"
|
|
);
|
|
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(),
|
|
"Crime victim was not properly scanned."
|
|
);
|
|
return;
|
|
}
|
|
faction = context.NPCFaction[victim];
|
|
}
|
|
|
|
transactions.Add(new FoulMurder(entry) {
|
|
System = context.CurrentSystem,
|
|
Faction = faction,
|
|
});
|
|
}
|
|
}
|
|
|
|
internal class MissionAcceptedParser : TransactionParserPart {
|
|
public void Parse(Entry e, TransactionParserContext context, TransactionList transactions) {
|
|
MissionAcceptedEntry? entry = e as MissionAcceptedEntry;
|
|
if (entry == null) {
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
if (context.CurrentSystem == null || context.CurrentSystemAddress == null) {
|
|
transactions.AddIncomplete(new MissionCompleted(),
|
|
"Could not determine current location on mission acceptance."
|
|
);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
context.MissionAccepted(entry);
|
|
} catch (Exception exception) {
|
|
transactions.AddIncomplete(new MissionCompleted(),
|
|
exception.Message
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
internal class MissionCompletedParser : TransactionParserPart {
|
|
public void Parse(Entry e, TransactionParserContext context, TransactionList transactions) {
|
|
MissionCompletedEntry? entry = e as MissionCompletedEntry;
|
|
if (entry == null || entry.Mission == null) {
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
MissionAcceptedEntry? accepted = null;
|
|
Location? accepted_location = null;
|
|
string? target_faction_name = entry.Mission.TargetFaction;
|
|
string? source_faction_name = entry.Mission.Faction;
|
|
|
|
// We did not find when the mission was accepted.
|
|
if (!context.AcceptedMissions.TryGetValue(entry.Mission.MissionID, out accepted)) {
|
|
transactions.AddIncomplete(new MissionCompleted(),
|
|
String.Format("Mission acceptance for mission id {0} was not found",
|
|
entry.Mission.MissionID));
|
|
return;
|
|
}
|
|
|
|
if (!context.AcceptedMissionLocation.TryGetValue(entry.Mission.MissionID, out accepted_location)) {
|
|
transactions.AddIncomplete(new MissionCompleted(),
|
|
String.Format("Location for acceptance for mission id {0} was not found",
|
|
entry.Mission.MissionID));
|
|
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.
|
|
foreach (var other in entry.Mission.Influences) {
|
|
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;
|
|
}
|
|
other.Value.Add(context.CurrentSystemAddress.Value, "");
|
|
// 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.
|
|
other.Value.Add(accepted_location.SystemAddress, "");
|
|
|
|
// 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) {
|
|
other.Value.Add(context.CurrentSystemAddress.Value, "");
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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(),
|
|
string.Format("Unknown system {0}, unable to assign that mission a target", system_address)
|
|
);
|
|
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.
|
|
transactions.Add(new MissionCompleted(entry) {
|
|
System = accepted_location.StarSystem,
|
|
Faction = source_faction_name,
|
|
SystemAddress = accepted_location.SystemAddress,
|
|
Station = accepted_location.Station,
|
|
});
|
|
} else if (string.Compare(faction, source_faction_name, true) != 0 ||
|
|
(string.Compare(faction, source_faction_name) == 0 &&
|
|
system_address != accepted_location.SystemAddress)) {
|
|
// 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() {
|
|
Faction = faction,
|
|
Influence = influences.Value,
|
|
System = system,
|
|
SystemAddress = system_address,
|
|
RelevantMission = entry,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
internal class MissionFailedParser : TransactionParserPart {
|
|
public void Parse(Entry e, TransactionParserContext context, TransactionList transactions) {
|
|
MissionAcceptedEntry? accepted = null;
|
|
Location? accepted_location = null;
|
|
string? accepted_system = null;
|
|
|
|
MissionFailedEntry? entry = e as MissionFailedEntry;
|
|
if (entry == null) {
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
if (entry.Mission == null) {
|
|
throw new InvalidJournalEntryException("No mission specified in mission failure");
|
|
}
|
|
|
|
if (!context.AcceptedMissions.TryGetValue(entry.Mission.MissionID, out accepted)) {
|
|
transactions.AddIncomplete(new MissionFailed(),
|
|
"Mission acceptance was not found"
|
|
);
|
|
return;
|
|
}
|
|
|
|
if (!context.AcceptedMissionLocation.TryGetValue(entry.Mission.MissionID, out accepted_location)) {
|
|
transactions.AddIncomplete(new MissionFailed(),
|
|
"Unable to figure out where failed mission was accepted"
|
|
);
|
|
return;
|
|
}
|
|
|
|
if (!context.SystemsByID.TryGetValue(accepted_location.SystemAddress, out accepted_system)) {
|
|
transactions.AddIncomplete(new MissionFailed(),
|
|
"Unable to figure out in which system failed mission was accepted"
|
|
);
|
|
return;
|
|
}
|
|
|
|
transactions.Add(new MissionFailed() {
|
|
Accepted = accepted,
|
|
Faction = accepted.Mission?.Faction,
|
|
Failed = entry,
|
|
Station = accepted_location.Station,
|
|
System = accepted_location.StarSystem,
|
|
SystemAddress = accepted_location.SystemAddress,
|
|
});
|
|
}
|
|
}
|
|
|
|
internal class SellExplorationDataParser : TransactionParserPart {
|
|
public void Parse(Entry e, TransactionParserContext context, TransactionList transactions) {
|
|
SellExplorationDataEntry? entry = e as SellExplorationDataEntry;
|
|
if (entry == null) {
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
transactions.Add(new Cartographics(entry) {
|
|
System = context.CurrentSystem,
|
|
Station = context.CurrentStation,
|
|
Faction = context.ControllingFaction,
|
|
});
|
|
}
|
|
}
|
|
|
|
internal class SellOrganicDataParser : TransactionParserPart {
|
|
public void Parse(Entry e, TransactionParserContext context, TransactionList transactions) {
|
|
SellOrganicDataEntry? entry = e as SellOrganicDataEntry;
|
|
if (entry == null) {
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
transactions.Add(new OrganicData(entry) {
|
|
System = context.CurrentSystem,
|
|
Station = context.CurrentStation,
|
|
Faction = context.ControllingFaction,
|
|
});
|
|
}
|
|
}
|
|
|
|
internal class MultiSellExplorationDataParser : TransactionParserPart {
|
|
public void Parse(Entry e, TransactionParserContext context, TransactionList transactions) {
|
|
MultiSellExplorationDataEntry? entry = e as MultiSellExplorationDataEntry;
|
|
if (entry == null) {
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
transactions.Add(new Cartographics(entry) {
|
|
System = context.CurrentSystem,
|
|
Station = context.CurrentStation,
|
|
Faction = context.ControllingFaction,
|
|
});
|
|
}
|
|
}
|
|
|
|
internal class RedeemVoucherParser : TransactionParserPart {
|
|
public void Parse(Entry e, TransactionParserContext context, TransactionList transactions) {
|
|
RedeemVoucherEntry? entry = e as RedeemVoucherEntry;
|
|
if (entry == null) {
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
if (context.CurrentSystem == null) {
|
|
transactions.AddIncomplete(new Vouchers(),
|
|
"Could not find out where the vouchers were redeemed"
|
|
);
|
|
return;
|
|
}
|
|
|
|
List<Faction>? current_factions = context.GetFactions(context.CurrentSystem);
|
|
if (current_factions == null) {
|
|
transactions.AddIncomplete(new Vouchers(),
|
|
"Current system factions are unknown, so vouchers were ineffective");
|
|
}
|
|
|
|
foreach (string faction in entry.Factions) {
|
|
bool relevantBond = false;
|
|
string relevantFaction = faction;
|
|
|
|
if (string.Compare(faction, Factions.PilotsFederationVouchers) == 0) {
|
|
// Target faction is pilots' federation, so we assume thargoid bonks
|
|
// Also assign this combat bond to the Pilots Federation
|
|
relevantFaction = Factions.PilotsFederation;
|
|
relevantBond = true;
|
|
}
|
|
|
|
if (current_factions != null && !relevantBond) {
|
|
// If we have local factions, and it ain't thargoid bonds see if the bonds were
|
|
// useful in the current system
|
|
if (current_factions.Find(x => string.Compare(x.Name, faction, true) == 0) != null) {
|
|
relevantBond = true;
|
|
} else {
|
|
transactions.AddIncomplete(new Vouchers(),
|
|
string.Format("Vouchers for {0} had no effect in {1} since said " +
|
|
"faction is not present here", faction, context.CurrentSystem)
|
|
);
|
|
}
|
|
}
|
|
|
|
if (relevantBond) {
|
|
transactions.Add(new Vouchers(entry) {
|
|
System = context.CurrentSystem,
|
|
Station = context.CurrentStation,
|
|
Faction = relevantFaction,
|
|
ControllingFaction = context.ControllingFaction,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
internal class SellMicroResourcesParser : TransactionParserPart {
|
|
public void Parse(Entry e, TransactionParserContext context, TransactionList transactions) {
|
|
SellMicroResourcesEntry? entry = e as SellMicroResourcesEntry;
|
|
if (entry == null) {
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
transactions.Add(new SellMicroResources(entry) {
|
|
System = context.CurrentSystem,
|
|
Station = context.CurrentStation,
|
|
Faction = context.ControllingFaction,
|
|
});
|
|
}
|
|
}
|
|
|
|
internal class SearchAndRescueParser : TransactionParserPart {
|
|
public void Parse(Entry e, TransactionParserContext context, TransactionList transactions) {
|
|
SearchAndRescueEntry? entry = e as SearchAndRescueEntry;
|
|
if (entry == null) {
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
transactions.Add(new SearchAndRescue(entry) {
|
|
Faction = context.ControllingFaction,
|
|
Station = context.CurrentStation,
|
|
System = context.CurrentSystem,
|
|
});
|
|
}
|
|
}
|
|
|
|
internal class MarketBuyParser : TransactionParserPart {
|
|
public void Parse(Entry e, TransactionParserContext context, TransactionList transactions) {
|
|
MarketBuyEntry? entry = e as MarketBuyEntry;
|
|
if (entry == null) {
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
context.BoughtCargo(entry.Type, entry.BuyPrice);
|
|
|
|
transactions.Add(new BuyCargo(entry) {
|
|
Faction = context.ControllingFaction,
|
|
Station = context.CurrentStation,
|
|
System = context.CurrentSystem,
|
|
});
|
|
}
|
|
}
|
|
|
|
internal class MarketSellParser : TransactionParserPart {
|
|
public void Parse(Entry e, TransactionParserContext context, TransactionList transactions) {
|
|
long profit = 0;
|
|
|
|
MarketSellEntry? entry = e as MarketSellEntry;
|
|
if (entry == null) {
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
if (entry.Type == null) {
|
|
throw new InvalidJournalEntryException("market sell contains no cargo type");
|
|
}
|
|
|
|
if (context.BuyCost.ContainsKey(entry.Type)) {
|
|
long avg = context.BuyCost[entry.Type];
|
|
profit = (long)entry.TotalSale - (avg * entry.Count);
|
|
}
|
|
|
|
transactions.Add(new SellCargo(entry) {
|
|
Faction = context.ControllingFaction,
|
|
Station = context.CurrentStation,
|
|
System = context.CurrentSystem,
|
|
Profit = profit,
|
|
});
|
|
}
|
|
}
|
|
|
|
internal class FactionKillBondParser : TransactionParserPart {
|
|
public void Parse(Entry e, TransactionParserContext context, TransactionList transactions) {
|
|
FactionKillBondEntry? entry = e as FactionKillBondEntry;
|
|
if (entry == null) {
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
if (Factions.IsThargoid(entry.VictimFaction)) {
|
|
// Thargoid bonk
|
|
transactions.Add(new ThargoidKill(entry) {
|
|
System = context.CurrentSystem,
|
|
Faction = Factions.PilotsFederation,
|
|
Station = context.CurrentStation,
|
|
});
|
|
|
|
// We are done
|
|
return;
|
|
}
|
|
|
|
context.RecordCombatBond(entry);
|
|
}
|
|
}
|
|
|
|
internal class EmbarkDisembarkParser : TransactionParserPart {
|
|
public void Parse(Entry e, TransactionParserContext context, TransactionList transactions) {
|
|
if (e.Is(Events.Embark)) {
|
|
context.IsOnFoot = false;
|
|
} else if (e.Is(Events.Disembark)) {
|
|
context.IsOnFoot = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
internal class SupercruiseEntryParser : TransactionParserPart {
|
|
public void Parse(Entry entry, TransactionParserContext context, TransactionList transactions) {
|
|
context.DiscernCombatZone(transactions, entry);
|
|
context.ResetCombatZone();
|
|
}
|
|
}
|
|
|
|
internal class ShutdownParser : TransactionParserPart {
|
|
public void Parse(Entry entry, TransactionParserContext context, TransactionList transactions) {
|
|
context.DiscernCombatZone(transactions, entry);
|
|
context.ResetCombatZone();
|
|
}
|
|
}
|
|
|
|
public class TransactionParser {
|
|
private static Dictionary<string, TransactionParserPart> ParserParts { get; } = new()
|
|
{
|
|
{ Events.CommitCrime, new CommitCrimeParser() },
|
|
{ Events.Disembark, new EmbarkDisembarkParser() },
|
|
{ Events.Docked, new DockedParser() },
|
|
{ Events.Embark, new EmbarkDisembarkParser() },
|
|
{ Events.FactionKillBond, new FactionKillBondParser() },
|
|
{ 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() },
|
|
{ Events.MultiSellExplorationData, new MultiSellExplorationDataParser() },
|
|
{ Events.RedeemVoucher, new RedeemVoucherParser() },
|
|
{ Events.SearchAndRescue, new SearchAndRescueParser() },
|
|
{ Events.SellExplorationData, new SellExplorationDataParser() },
|
|
{ Events.SellMicroResources, new SellMicroResourcesParser() },
|
|
{ Events.SellOrganicData, new SellOrganicDataParser() },
|
|
{ Events.ShipTargeted, new ShipTargetedParser() },
|
|
{ Events.Shutdown, new ShutdownParser() },
|
|
{ Events.SupercruiseEntry, new SupercruiseEntryParser() },
|
|
};
|
|
|
|
public bool IsRelevant(string entry) {
|
|
return ParserParts.ContainsKey(entry);
|
|
}
|
|
|
|
public bool IsRelevant(Entry e) {
|
|
if (e.Event == null) {
|
|
return false;
|
|
}
|
|
return IsRelevant(e.Event);
|
|
}
|
|
|
|
public List<Transaction>? Parse(IEnumerable<Entry> entries) {
|
|
TransactionList transactions = new();
|
|
TransactionParserContext context = new();
|
|
|
|
foreach (Entry entry in entries) {
|
|
if (entry.Event == null) {
|
|
throw new InvalidJournalEntryException();
|
|
}
|
|
|
|
if (!ParserParts.ContainsKey(entry.Event)) {
|
|
continue;
|
|
}
|
|
|
|
TransactionParserPart transactionParserPart = ParserParts[entry.Event];
|
|
transactionParserPart.Parse(entry, context, transactions);
|
|
}
|
|
|
|
return transactions.ToList();
|
|
}
|
|
}
|