using System; using System.Collections.Generic; using System.Linq; using EDJournal; namespace EliteBGS.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.CompareTo(objective) == 0); bool added = false; if (found == null) { objectives.Add(objective); added = true; } return added; } public static bool IsRelevant(Entry e) { return e.Is(Events.CommitCrime) || e.Is(Events.Docked) || e.Is(Events.FactionKillBond) || e.Is(Events.FSDJump) || e.Is(Events.Location) || e.Is(Events.MarketBuy) || e.Is(Events.MarketSell) || e.Is(Events.MissionAccepted) || e.Is(Events.MissionFailed) || e.Is(Events.MultiSellExplorationData) || e.Is(Events.RedeemVoucher) || e.Is(Events.SearchAndRescue) || e.Is(Events.SellExplorationData) || e.Is(Events.SellMicroResources) || e.Is(Events.SellOrganicData) || e.Is(Events.ShipTargeted) || e.Is(Events.MissionCompleted) ; } 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 ; Scan(entries.SelectMany(x => x).ToList()); } public void Scan(List entries) { if (entries.Count <= 0) { return; } List relevant = entries .Where(x => IsRelevant(x)) .ToList() ; Dictionary acceptedMissions = new Dictionary(); Dictionary buyCost = new Dictionary(); Dictionary systems = new Dictionary(); Dictionary npcfactions = new Dictionary(); Dictionary> system_factions = new Dictionary>(); // A dictionary resolving to a station at which each mission was accepted Dictionary acceptedStations = new Dictionary(); // A dictionary resolving to a system at which each mission was accepted Dictionary acceptedSystems = new Dictionary(); string current_system = null; ulong current_system_address = 0; string current_station = null; string controlling_faction = null; objectives.ForEach(x => x.Clear()); foreach (Entry e in relevant) { List results = new List(); bool collate = false; if (e.Is(Events.Docked)) { DockedEntry docked = e as DockedEntry; /* gleem the current station from this message */ current_station = docked.StationName; current_system = docked.StarSystem; controlling_faction = docked.StationFaction; current_system_address = docked.SystemAddress; if (!systems.ContainsKey(docked.SystemAddress)) { systems.Add(docked.SystemAddress, docked.StarSystem); } } else if (e.Is(Events.FSDJump)) { /* Gleem current system and controlling faction from this message. */ FSDJumpEntry fsd = e as FSDJumpEntry; current_system_address = fsd.SystemAddress; current_system = fsd.StarSystem; controlling_faction = fsd.SystemFaction; if (!systems.ContainsKey(fsd.SystemAddress)) { systems.Add(fsd.SystemAddress, fsd.StarSystem); } if (!system_factions.ContainsKey(fsd.StarSystem) && fsd.SystemFactions.Count > 0) { system_factions[fsd.StarSystem] = fsd.SystemFactions; } } else if (e.Is(Events.Location)) { /* Get current system, faction name and station from Location message */ LocationEntry location = e as LocationEntry; current_system = location.StarSystem; current_system_address = location.SystemAddress; if (!systems.ContainsKey(location.SystemAddress)) { systems.Add(location.SystemAddress, location.StarSystem); } if (!string.IsNullOrEmpty(location.SystemFaction)) { controlling_faction = location.SystemFaction; } if (!string.IsNullOrEmpty(location.StationName)) { current_station = location.StationName; } if (!system_factions.ContainsKey(location.StarSystem) && location.SystemFactions.Count > 0) { system_factions[location.StarSystem] = location.SystemFactions; } } else if (e.Is(Events.ShipTargeted)) { ShipTargetedEntry targeted = e as ShipTargetedEntry; if (string.IsNullOrEmpty(targeted.PilotNameLocalised) || string.IsNullOrEmpty(targeted.Faction)) { continue; } npcfactions[targeted.PilotNameLocalised] = targeted.Faction; } else if (e.Is(Events.CommitCrime)) { CommitCrimeEntry crime = e as CommitCrimeEntry; string faction = crime.Faction; if (!crime.IsMurder) { /* we don't care about anything but murder for now */ continue; } /* use localised victim name if we have it, otherwise use normal name */ string victim = crime.VictimLocalised; if (string.IsNullOrEmpty(victim)) { victim = crime.Victim; } if (!npcfactions.ContainsKey(victim)) { /* The faction in the crime report is the faction that issues the bounty, * and not the faction of the victim. */ OnLog?.Invoke(string.Format( "No faction found for victim \"{0}\", using faction that issued the bounty instead.", victim, crime.Faction )); } else { faction = npcfactions[victim]; } results.Add(new FoulMurder(crime) { System = current_system, Faction = faction, }); collate = true; } else if (e.Is(Events.MissionCompleted)) { MissionCompletedEntry completed = e as MissionCompletedEntry; MissionAcceptedEntry accepted; if (!acceptedMissions.TryGetValue(completed.MissionID, out accepted)) { OnLog?.Invoke(string.Format( "Unable to find mission acceptance for mission \"{0}\". " + "Please extend range to include the mission acceptance.", completed.HumanReadableName )); continue; } string mission_giver = completed.Faction; if (completed.HumanReadableNameWasGenerated) { /* If the human readable name was generated, we send a log message. Because the * generated names all sort of suck, we should have more human readable names in * in the lookup dictionary. */ OnLog?.Invoke("Human readable name for mission \"" + completed.Name + "\" was generated, please report this."); } foreach (var other in completed.Influences) { string faction = other.Key; if (string.IsNullOrEmpty(faction)) { continue; } foreach (var influences in other.Value) { ulong system_address = influences.Key; ulong accepted_address = 0; string system, accepted_station, accepted_system; if (!systems.TryGetValue(system_address, out system)) { OnLog?.Invoke(string.Format( "Unknown system system \"{0}\" unable to assign that mission a target.", system_address )); continue; } if (!acceptedSystems.TryGetValue(completed.MissionID, out accepted_address)) { OnLog?.Invoke(string.Format( "Unable to figure out in which system mission \"{0}\" was accepted.", system_address )); continue; } if (!systems.TryGetValue(accepted_address, out accepted_system)) { OnLog?.Invoke(string.Format( "Unable to figure out in which system mission \"{0}\" was accepted.", system_address )); continue; } if (!acceptedStations.TryGetValue(completed.MissionID, out accepted_station)) { OnLog?.Invoke(string.Format( "Unable to figure out in which station mission \"{0}\" was accepted.", system_address )); continue; } if (faction.Equals(mission_giver) && system_address == accepted_address) { /* This is the influence block for the origin of the mission. */ results.Add(new MissionCompleted(completed) { System = accepted_system, Faction = mission_giver, SystemAddress = accepted_address, Station = accepted_station, }); } else if (!faction.Equals(results[0].Faction) || (faction.Equals(results[0].Faction) && system_address != accepted_address)) { /* This block is for secondary factions (first if), or if the secondary faction * is the same as the mission giver, but in another system (second if). */ results.Add(new InfluenceSupport() { Faction = faction, Influence = influences.Value, System = system, SystemAddress = system_address, RelevantMission = results[0] as MissionCompleted }); } } } } else if (e.Is(Events.MissionAccepted)) { MissionAcceptedEntry accepted = e as MissionAcceptedEntry; ulong id = accepted.MissionID; if (!acceptedMissions.ContainsKey(id)) { acceptedMissions[id] = accepted; } if (!acceptedStations.ContainsKey(id)) { acceptedStations[id] = current_station; } if (!acceptedSystems.ContainsKey(id)) { acceptedSystems[id] = current_system_address; } } else if (e.Is(Events.MissionFailed)) { var failed = e as MissionFailedEntry; MissionAcceptedEntry accepted = null; if (!acceptedMissions.TryGetValue(failed.MissionID, out accepted)) { OnLog?.Invoke("A mission failed which wasn't accepted in the given time frame. " + "Please adjust start date to when the mission was accepted to include it in the list."); continue; } results.Add(new MissionFailed(accepted) { Failed = failed, System = current_system }); if (failed.HumanReadableName == null) { OnLog?.Invoke("Human readable name for mission \"" + failed.Name + "\" was not recognised"); } /* Mission failed should be collated if they are in the same system/station */ collate = true; } else if (e.Is(Events.SellExplorationData)) { results.Add(new Cartographics(e as SellExplorationDataEntry) { System = current_system, Station = current_station, Faction = controlling_faction, }); /* colate single cartographic selling into one */ collate = true; } else if (e.Is(Events.SellOrganicData)) { /* organic data sold to Vista Genomics */ results.Add(new OrganicData(e as SellOrganicDataEntry) { System = current_system, Station = current_station, Faction = controlling_faction, }); collate = true; } else if (e.Is(Events.MultiSellExplorationData)) { /* For multi-sell-exploraton-data only the controlling faction of the station sold to matters. */ results.Add(new Cartographics(e as MultiSellExplorationDataEntry) { System = current_system, Station = current_station, Faction = controlling_faction }); collate = true; } else if (e.Is(Events.RedeemVoucher)) { RedeemVoucherEntry voucher = e as RedeemVoucherEntry; List current_factions = new List(); if (system_factions.ContainsKey(current_system)) { current_factions = system_factions[current_system]; } else { OnLog?.Invoke("There are no current system factions, so turned in vouchers were ignored."); continue; } foreach (string faction in voucher.Factions) { if (current_factions.Find(x => x.Name == faction) == null) { OnLog?.Invoke( string.Format("Vouchers for \"{0}\" were ignored in \"{1}\" since said " + "faction is not present there.", faction, current_system) ); continue; /* faction is not present, so it is ignored */ } /* Same for selling combat vouchers. Only the current controlling faction matters here. */ results.Add(new Vouchers(voucher) { System = current_system, Station = current_station, Faction = faction, ControllingFaction = controlling_faction, }); } collate = true; } else if (e.Is(Events.SellMicroResources)) { results.Add(new SellMicroResources(e as SellMicroResourcesEntry) { Faction = controlling_faction, Station = current_station, System = current_system }); } else if (e.Is(Events.MarketBuy)) { MarketBuyEntry buy = e as MarketBuyEntry; if (string.IsNullOrEmpty(buy.Type) || buy.BuyPrice == 0) { continue; } buyCost[buy.Type] = buy.BuyPrice; results.Add(new BuyCargo(buy) { Faction = controlling_faction, Station = current_station, System = current_system, }); collate = true; } else if (e.Is(Events.SearchAndRescue)) { results.Add(new SearchAndRescue(e as SearchAndRescueEntry) { Faction = controlling_faction, Station = current_station, System = current_system, }); collate = true; } else if (e.Is(Events.MarketSell)) { MarketSellEntry sell = e as MarketSellEntry; long profit = 0; if (!buyCost.ContainsKey(sell.Type)) { OnLog?.Invoke("Could not find buy order for the given commodity. Please adjust profit manually."); } else { long avg = buyCost[sell.Type]; profit = (long)sell.TotalSale - (avg * sell.Count); } results.Add(new SellCargo(e as MarketSellEntry) { Faction = controlling_faction, Station = current_station, System = current_system, Profit = profit }); } if (results == null || results.Count <= 0) { continue; } foreach (LogEntry entry in results) { /* Find all objectives that generally match. */ var matches = objectives .Where(x => x.Matches(entry) >= 2) .OrderBy(x => x.Matches(entry)) ; Objective objective = null; if (matches != null && matches.Count() > 0) { /* Then select the one that matches the most. */ objective = matches .OrderBy(x => x.Matches(entry)) .Reverse() .First() ; } else { /* create a new objective if we don't have one */ objective = new Objective() { Station = entry.Station, Faction = entry.Faction, System = entry.System, }; objectives.Add(objective); } LogEntry existing = null; existing = objective.LogEntries.Find(x => { try { return x.CompareTo(entry) == 0; } catch (NotImplementedException) { return false; } }); if (collate && existing != null) { existing.Entries.Add(e); } else if (!collate || existing == null) { objective.LogEntries.Add(entry); } } } } public void Scan(PlayerJournal journal) { Scan(journal, DateTime.Now, DateTime.Now); } } }