private void AddLinks(ModEntityData <T> data, ModEntityData <Tech> techs) { foreach (var entity in data.Entities) { // it is possible that the pre-reqs for a something do not exist // in this we do not add them to the populated list, but leave them in the ids list var prereqs = new List <Tech>(); foreach (var prerequisiteId in entity.PrerequisiteIds) { if (techs.ContainsEntityInTree(prerequisiteId)) { var prereq = techs[prerequisiteId]; prereqs.Add(prereq); data.Links.Add(new Link() { From = prereq, To = entity }); } else { Log.Logger.Warning("Could not find prerequisite {prerequisite} for {entityType} {entityId} in {filePath}", prerequisiteId, entity.GetType().Name, entity.Id, entity.FilePath); } } entity.Prerequisites = prereqs; } }
public VisData CreateRootNotes(ModEntityData <Tech> techsAndDependencies, string imagesPath) { var result = new VisData() { ModGroup = StellarisDirectoryHelper.StellarisCoreRootDirectory }; var techAreas = Enum.GetValues(typeof(TechArea)).Cast <TechArea>(); var rootNodes = new Dictionary <TechArea, VisNode>(); var rootNodeCategories = new Dictionary <TechArea, HashSet <string> >(); foreach (var tech in techsAndDependencies.AllEntities) { rootNodeCategories.ComputeIfAbsent(tech.Area, ignored => new HashSet <string>()).AddRange(tech.Categories); } foreach (var techArea in techAreas) { var rootNode = BuildRootNode(techArea, imagesPath); rootNode.categories = rootNodeCategories.ComputeIfAbsent(techArea, ignored => new HashSet <string>()).ToArray(); rootNodes[techArea] = rootNode; } result.nodes.AddRange(rootNodes.Values); return(result); }
public ModEntityData <T> ProcessDirectoryHelper(ModEntityData <T> previous, StellarisDirectoryHelper directoryHelper, ModEntityData <Tech> techs) { var directoryPath = GetDirectory(directoryHelper); if (Directory.Exists(directoryPath)) { var result = new ModEntityData <T>(directoryHelper, previous); var techFiles = DirectoryWalker.FindFilesInDirectoryTree(directoryPath, ParseFileMask, IgnoreFiles); Log.Logger.Debug("Directory {directory} produced files {files}", directoryPath, techFiles); var parsedTechFiles = CWParserHelper.ParseParadoxFiles(techFiles.Select(x => x.FullName).ToList(), true); foreach (var(file, cwNode) in parsedTechFiles) { Log.Logger.Debug("Processing file {file}", file); // top level nodes are files, so we process the immediate children of each file, which is the individual techs. foreach (var node in cwNode.Nodes) { try { var entity = Construct(node); Initialise(entity, file, directoryHelper.ModName, directoryHelper.ModGroup, node); SetVariables(entity, node); if (ShouldInclude(entity)) { result[entity.Id] = entity; } else { Log.Logger.Debug("File {file} contained node {key} was processed, but failed the include filter so is discarded", file, entity.Id); } } catch (Exception e) { if (AbortOnFailure) { throw new Exception($"Error Processing node {node.Key} in file: {file}", e); } Log.Logger.Error(e, "Error Processing node {node} in file: {file}", node.Key, file); } } } // special case for managing techs // techs are their own tech lookup. var latestTechData = result as ModEntityData <Tech>; if (latestTechData != null) { AddLinks(result, latestTechData); } else { AddLinks(result, techs); } return(result); } Log.Logger.Debug("{mod} did not have {directory}", directoryHelper.ModName, directoryPath.Replace(directoryHelper.Root, "")); return(previous); }
public DependantsGraphCreator(ILocalisationApiHelper localisationApiHelper, ICWParserHelper cwParserHelper, StellarisDirectoryHelper stellarisDirectoryHelper, IEnumerable <StellarisDirectoryHelper> modDirectoryHelpers, ModEntityData <Tech> techsAndDependencies) { this.localisationApiHelper = localisationApiHelper; this.cwParserHelper = cwParserHelper; this.stellarisDirectoryHelper = stellarisDirectoryHelper; this.modDirectoryHelpers = modDirectoryHelpers; this.techsAndDependencies = techsAndDependencies; }
private ModEntityData <T> CreateDependant <T>(EntityCreator <T> creator, ParseTarget parseTarget) where T : Entity { ModEntityData <T> entities = null; foreach (var modDirectoryHelper in StellarisDirectoryHelper.CreateCombinedList(stellarisDirectoryHelper, modDirectoryHelpers)) { entities = creator.ProcessDirectoryHelper(entities, modDirectoryHelper, techsAndDependencies); } return(entities); }
public ModEntityData <Tech> CreateTechnologyGraph() { ModEntityData <Tech> techs = null; foreach (var modDirectoryHelper in StellarisDirectoryHelper.CreateCombinedList(stellarisDirectoryHelper, modDirectoryHelpers)) { techs = ProcessDirectoryHelper(techs, modDirectoryHelper, null); } // post process because while most things work on the principle of: // later mod override core // later mod override core // core // some have the core first, then additional features that depend on it (Zenith, I am looking at you) // so need to post process techs?.ApplyToChain((modTechs, modLinks) => { foreach (var(key, tech) in modTechs) { if (tech.Prerequisites.Count == tech.PrerequisiteIds.Count()) { continue; } Log.Logger.Debug("Tech {id} had missing pre-requisite, trying to find it in the complete listing", key); var populatedPreReqs = tech.Prerequisites.Select(preReq => preReq.Id).ToHashSet(); foreach (var missingPreReq in tech.PrerequisiteIds.Where(x => !populatedPreReqs.Contains(x))) { if (techs.ContainsEntityInTree(missingPreReq)) { Tech attemptToFindPreq = techs[missingPreReq]; tech.Prerequisites.Add(attemptToFindPreq); modLinks.Add(new Link() { From = attemptToFindPreq, To = tech }); Log.Logger.Debug("Found prereq {key} in file {file}", attemptToFindPreq.Id, attemptToFindPreq.FilePath); } else { Log.Logger.Debug("Still unable to find {prereqId} for Tech {id}", missingPreReq, key); } } } }); return(techs); }
private ModEntityData <T> ProcessDependant <T>(EntityCreator <T> creator, ParseTarget parseTarget) where T : Entity { ModEntityData <T> entities = CreateDependant(creator, parseTarget); entities?.ApplyToChain((ents, links) => { var invalidEntities = ents.Where(x => !x.Value.Prerequisites.Any()).Select(x => x.Value).ToList(); foreach (var invalidEntity in invalidEntities) { Log.Logger.Warning("Removing {entityId} from {file} dependant entities as we were unable to locate its specified pre-requisite techs", invalidEntity.Id, invalidEntity.FilePath); ents.Remove(invalidEntity.Id); var invalidLinks = links.Where(x => x.To.Id == invalidEntity.Id).ToList(); links.RemoveAll(invalidLinks); } }); if (entities != null) { foreach (var entity in entities.AllEntities) { foreach (var prerequisite in entity.Prerequisites) { if (prerequisite.ExtraDesc != null) { entity.ExtraDesc = prerequisite.ExtraDesc; } } } Log.Logger.Debug("Processed {entityCount} {parseTarget} with {linkCount} links", entities.EntityCount, parseTarget, entities.LinkCount); } else { Log.Logger.Warning("{parseTarget} had no items in any of the sources"); } return(entities); }
public void Parse(IEnumerable <ParseTarget> parseTargets) { var techTreeGraphCreator = new TechTreeGraphCreator(Localisation, CWParser, StellarisDirectoryHelper, ModDirectoryHelpers); ModEntityData <Tech> techData = techTreeGraphCreator.CreateTechnologyGraph(); Log.Logger.Debug("Processed {entityCount} techs with {linkCount} Links", techData.EntityCount, techData.LinkCount); // process technolgoies first var techImageOutputDir = OutputDirectoryHelper.GetImagesPath(ParseTarget.Technologies.ImagesDirectory()); if (CopyImages) { CopyMainImages(techData.AllEntities, ParseTarget.Technologies.ImagesDirectory(), techImageOutputDir); // because the image copying only get the most recent version of the entity, make sure that the image flag is set on all // relevant for the vanilla graph display var currentTechs = techData.AllEntitiesByKey; techData.ApplyToChain((techs, links) => { foreach (var tech in techs.Values) { tech.IconFound = currentTechs[tech.Id].IconFound; } }); var techAreas = Enum.GetValues(typeof(TechArea)).Cast <TechArea>(); var areaDir = Path.Combine(techImageOutputDir, "areas"); // tech areas Directory.CreateDirectory(areaDir); foreach (var techArea in techAreas) { var inputPath = Path.Combine(StellarisDirectoryHelper.Icons, "resources", techArea.ToString().ToLowerInvariant() + "_research.dds"); // icon to be displayed on the 3 root nodes ImageOutput.TransformAndOutputImage( inputPath, Path.Combine(techImageOutputDir, techArea + "-root.png")); // area icon ImageOutput.TransformAndOutputImage( inputPath, Path.Combine(areaDir, techArea.ToString().ToLowerInvariant() + ".png")); } // tech categories CopyCategoryImages(techImageOutputDir); } // process dependant objects ObjectsDependantOnTechs dependants = null; var parseTargetsWithoutTechs = parseTargets.WithoutTechs().ToList(); if (parseTargetsWithoutTechs.Any()) { var dependantsGraphCreator = new DependantsGraphCreator(Localisation, CWParser, StellarisDirectoryHelper, ModDirectoryHelpers, techData); dependants = dependantsGraphCreator.CreateDependantGraph(parseTargetsWithoutTechs); if (CopyImages) { foreach (var parseTarget in parseTargetsWithoutTechs) { var imageOutputDir = OutputDirectoryHelper.GetImagesPath(parseTarget.ImagesDirectory()); var entityData = dependants.Get(parseTarget); CopyMainImages(entityData, parseTarget.ImagesDirectory(), imageOutputDir); } dependants.FixImages(); } } var visDataMarshaler = new VisDataMarshaler(localisation, OutputDirectoryHelper); IDictionary <string, VisData> techVisResults = visDataMarshaler.CreateTechVisData(techData, techImageOutputDir); ModEntityData <Tech> coreGameTech = techData.FindCoreGameData(); if (coreGameTech != null) { IDictionary <string, VisData> techVisData = visDataMarshaler.CreateTechVisData(coreGameTech, techImageOutputDir); techVisResults["Stellaris-No-Mods"] = techVisData[StellarisDirectoryHelper.StellarisCoreRootDirectory]; } else { Log.Logger.Warning("Could not find core game tech files to generate vanilla tree"); } VisData rootNotes = visDataMarshaler.CreateRootNotes(techData, techImageOutputDir); techVisResults["Tech-Root-Nodes"] = rootNotes; WriteVisData(techVisResults, true); if (dependants != null) { var dependantVisResults = visDataMarshaler.CreateGroupedVisDependantData(techVisResults, dependants, parseTargetsWithoutTechs); var coreDependantsOnly = dependants.CopyOnlyCore(); // also do a no-mods lookup var stellarisNoModsTechVisResult = techVisResults["Stellaris-No-Mods"]; IDictionary <string, VisData> visLookupData = stellarisNoModsTechVisResult != null ? new Dictionary <string, VisData>() { { "Stellaris-No-Mods", stellarisNoModsTechVisResult } } : techVisResults; var coreDependantData = visDataMarshaler.CreateGroupedVisDependantData(visLookupData, coreDependantsOnly, parseTargetsWithoutTechs); if (coreDependantData.ContainsKey(StellarisDirectoryHelper.StellarisCoreRootDirectory)) { dependantVisResults["Stellaris-No-Mods"] = coreDependantData[StellarisDirectoryHelper.StellarisCoreRootDirectory]; } else { Log.Logger.Warning("Could not find core game dependant files to generate vanilla tree"); } WriteVisData(dependantVisResults, false); } }
public VisData CreateRootNotes(ModEntityData <Tech> techsAndDependencies, string imagesPath) { return(techsVisMarshaler.CreateRootNotes(techsAndDependencies, imagesPath)); }
public IDictionary <string, VisData> CreateTechVisData(ModEntityData <Tech> techsAndDependencies, string imagesPath) { return(techsVisMarshaler.CreateTechVisData(techsAndDependencies, imagesPath)); }
public IDictionary <string, VisData> CreateTechVisData(ModEntityData <Tech> allTechsAndDependencies, string imagesPath) { var modGroups = allTechsAndDependencies.AllLinks.Select(x => x.To.ModGroup).Distinct().ToList(); var coreData = allTechsAndDependencies.FindCoreGameData(); // link to supernodes var results = new Dictionary <string, VisData>(); foreach (var modGroup in modGroups) { var techsForGroup = allTechsAndDependencies.FindByModGroup(modGroup).Reverse().ToList(); var current = coreData; foreach (var forGroup in techsForGroup) { current = forGroup.Copy(current); } var techsAndDependencies = current; // perform longest path analysis to find out how many levels we want in each tech var maxPathPerTier = new Dictionary <int, int>(); List <Tech> techsWithNoPrereqs = new List <Tech>(); foreach (var tech in techsAndDependencies.AllEntities) { if (!tech.Tier.HasValue) { throw new InvalidOperationException("All Techs must have Tiers to create vis data. " + tech.Id); } int pathLength = NumberOfPrereqsInSameTier(tech); var currentMaxForTier = maxPathPerTier.ComputeIfAbsent(tech.Tier.Value, ignored => 0); if (pathLength > currentMaxForTier) { maxPathPerTier[tech.Tier.Value] = pathLength; } // need to link to a supernode to make it look good if (!tech.Prerequisites.Any()) { techsWithNoPrereqs.Add(tech); } } Log.Logger.Debug("Max path per tier {@maxPaths} Number of techs with no pre-requiste {noPreqCount}", maxPathPerTier, techsWithNoPrereqs.Count); // determine the base levels in the graph that each node will be on. var minimumLevelForTier = CalculateMinimumLevelForTier(maxPathPerTier); Log.Logger.Debug("Minimum level per tier {@minLevels}", minimumLevelForTier); //sort the input by area as it produces a nicer graph. var techList = techsAndDependencies.AllEntities.ToList(); techList.Sort((tech1, tech2) => { var primary = tech1.Area - tech2.Area; return(primary != 0 ? primary : string.Compare(tech1.Id, tech2.Id, StringComparison.Ordinal)); }); var result = new VisData() { nodes = techList.Where(x => Filter(x, modGroup)).Select(x => MarshalTech(x, minimumLevelForTier, imagesPath)).ToList(), edges = techsAndDependencies.AllLinks.Where(x => Filter(x.To, modGroup)).Select(VisHelpers.MarshalLink).ToList() }; foreach (var tech in techsWithNoPrereqs.Where(x => Filter(x, modGroup))) { result.edges.Add(BuildRootLink(tech.Area, tech.Id)); } results[modGroup] = result; } return(results); }