/* * Joins N QF subfragments into one QF fragment that can be properly binded into Skyrim VM * @throws ConversionException */ public TES5Target JoinQFFragments(IBuildTarget target, string resultingFragmentName, List <QuestStageScript> subfragmentsTrees) { int tes4FormID = GetTES4FormID(resultingFragmentName); StageMap stageMap = StageMapBuilder.Build(target, resultingFragmentName, esmAnalyzer, tes4FormID); /* * We need script fragment for objective handling for each stage, so when parsing the script fragments, * we"ll be marking them there, and intersecting this with stage. * This will give us an array of stages which don"t have script fragment, but will need it anyways * for objective handling. */ TES5ScriptHeader resultingScriptHeader = TES5ScriptHeaderFactory.GetFromCacheOrConstructByBasicType(resultingFragmentName, TES5BasicType.T_QUEST, TES5TypeFactory.TES4_Prefix, true); TES5GlobalScope resultingGlobalScope = new TES5GlobalScope(resultingScriptHeader); string[] referenceAliases = ReferenceAliasBuilder.Build(target, resultingFragmentName, esmAnalyzer, tes4FormID).ToArray(); foreach (string propertyName in referenceAliases) { resultingGlobalScope.AddProperty(TES5PropertyFactory.ConstructWithoutFormID(propertyName, TES5BasicType.T_REFERENCEALIAS, propertyName)); } List <QuestStageBlock> questStageBlocks = new List <QuestStageBlock>(); HashSet <int> implementedStages = new HashSet <int>(); HashSet <string> propertiesNamesDeclared = new HashSet <string>(); foreach (var subfragment in subfragmentsTrees) { TES5Target subfragmentsTree = subfragment.Script; TES5Script subfragmentScript = subfragmentsTree.Script; TES5GlobalScope subfragmentGlobalScope = subfragmentScript.GlobalScope; foreach (TES5Property subfragmentProperty in subfragmentGlobalScope.Properties) { /* * Move over the properties to the new global scope */ if (propertiesNamesDeclared.Add(subfragmentProperty.Name)) { resultingGlobalScope.AddProperty(subfragmentProperty); } else { if (subfragmentProperty.IsPlayerRef) { continue; } //WTM: Change: I don't think renaming these properties actually helps anything. /* * string propertyName = GeneratePropertyName(subfragmentScript.ScriptHeader, subfragmentProperty); * subfragmentProperty.Rename(propertyName); * if (!propertiesNamesDeclared.Add(subfragmentProperty.Name)) * { * throw new ConversionException(nameof(propertiesNamesDeclared) + " already contained property " + subfragmentProperty.Name + "."); * } */ //WTM: Change: I'm trying to unify properties and include extended type declarations. TES5Property existingProperty = resultingGlobalScope.GetPropertyByName(subfragmentProperty.Name); if (TES5InheritanceGraphAnalyzer.IsTypeOrExtendsType(existingProperty.TES5Type, subfragmentProperty.TES5Type)) { continue; } if (TES5InheritanceGraphAnalyzer.IsExtending(subfragmentProperty.TES5Type, existingProperty.TES5Type)) { existingProperty.TES5Type = subfragmentProperty.TES5Type; continue; } if (TES5InheritanceGraphAnalyzer.IsExtending(existingProperty.TES5Type, subfragmentProperty.TES5Type.NativeType)) { subfragmentProperty.TES5Type.NativeType = existingProperty.TES5Type.NativeType; existingProperty.TES5Type = subfragmentProperty.TES5Type; continue; } throw new ConversionException("Types were not compatible for property " + subfragmentProperty.Name + ": " + subfragmentProperty.TES5Type.Value + " should extend " + existingProperty.TES5Type.Value + " (" + existingProperty.TES5Type.NativeType.Value + ")."); } } List <ITES5CodeBlock> subfragmentBlocks = subfragmentScript.BlockList.Blocks; if (subfragmentBlocks.Count != 1) { throw new ConversionException("Wrong QF fragment, actual function count: " + subfragmentBlocks.Count + ".."); } ITES5CodeBlock subfragmentBlock = subfragmentBlocks[0]; if (subfragmentBlock.FunctionScope.BlockName != TES5FragmentFactory.GetFragmentName(0)) { throw new ConversionException("Wrong QF fragment funcname, actual function name: " + subfragmentBlock.FunctionScope.BlockName + "."); } string newFragmentFunctionName = TES5FragmentFactory.GetFragmentName(subfragment.Stage, subfragment.LogIndex); subfragmentBlock.FunctionScope.Rename(newFragmentFunctionName); List <int>?stageMapOfStage = stageMap.TryGetStageTargetsMap(subfragment.Stage); if (stageMapOfStage != null) { var objectiveCodeChunks = objectiveHandlingFactory.GenerateObjectiveHandling(subfragmentBlock, resultingGlobalScope, stageMapOfStage); foreach (var newCodeChunk in objectiveCodeChunks) { subfragmentBlock.AddChunk(newCodeChunk); } } questStageBlocks.Add(new QuestStageBlock(subfragment.Stage, subfragment.LogIndex, subfragmentBlock)); implementedStages.Add(subfragment.Stage); } /* * Diff to find stages which we still need to mark */ int[] nonDoneStages = stageMap.StageIDs.Where(stageID => !implementedStages.Contains(stageID)).ToArray(); foreach (int nonDoneStage in nonDoneStages) { TES5FunctionCodeBlock fragment = objectiveHandlingFactory.CreateEnclosedFragment(resultingGlobalScope, nonDoneStage, stageMap.GetStageTargetsMap(nonDoneStage)); questStageBlocks.Add(new QuestStageBlock(nonDoneStage, 0, fragment)); } this.mappedTargetsLogService.WriteScriptName(resultingFragmentName); foreach (var kvp in stageMap.MappedTargetsIndex) { var originalTargetIndex = kvp.Key; var mappedTargetIndexes = kvp.Value; this.mappedTargetsLogService.WriteLine(originalTargetIndex, mappedTargetIndexes); } TES5BlockList resultingBlockList = new TES5BlockList(questStageBlocks.OrderBy(b => b.StageID).ThenBy(b => b.LogIndex).Select(b => b.CodeBlock)); TES5Script resultingTree = new TES5Script(resultingGlobalScope, resultingBlockList, true); string outputPath = target.GetTranspileToPath(resultingFragmentName); return(new TES5Target(resultingTree, outputPath)); }
public override void Execute() { if (!PreExecutionChecks(false, false, false, false)) { return; } Build build = new Build(Build.DEFAULT_BUILD_PATH); Dictionary <int, List <int> > originalStageMap; using (BuildLogServices buildLogServices = new BuildLogServices(build)) { BuildTarget buildTarget = BuildTargetFactory.Get(BuildTarget.BUILD_TARGET_QF, build, buildLogServices); originalStageMap = QFFragmentFactory.BuildStageMapDictionary(buildTarget, "QF_FGC01Rats_01035713"); } StageMap stageMap = new StageMap(originalStageMap.ToDictionary(m => m.Key, m => m.Value.ToList()));//Copy dictionary StringBuilder output = new StringBuilder(); foreach (var stageId in stageMap.StageIDs) { output.AppendLine(stageId.ToString() + " - " + string.Join(" ", originalStageMap[stageId])); output.Append(stageId.ToString() + " -"); List <int> map = stageMap.GetStageTargetsMap(stageId); foreach (var val in map) { output.Append(" " + val); } output.AppendLine(); } output.Append("Mapping index print"); foreach (var kvp in stageMap.MappedTargetsIndex) { var originalTargetIndex = kvp.Key; var mappedTargetIndexes = kvp.Value; output.AppendLine(); output.Append(originalTargetIndex + " - " + string.Join(" ", mappedTargetIndexes)); } string outputString = output.ToString(); Console.WriteLine(outputString); const string fgc01RatsResultFromPHP = @"10 - 0 0 0 1 10 - 0 0 0 1 0 0 0 0 0 0 20 - 1 0 0 0 20 - 1 0 0 0 0 0 0 0 0 0 30 - 0 0 0 1 30 - 0 0 0 0 1 0 0 0 0 0 40 - 0 0 1 0 40 - 0 0 1 0 0 0 0 0 0 0 50 - 0 0 1 0 50 - 0 0 1 0 0 0 0 0 0 0 55 - 0 0 0 1 55 - 0 0 0 0 0 1 0 0 0 0 60 - 1 0 0 0 60 - 0 0 0 0 0 0 1 0 0 0 65 - 0 0 0 1 65 - 0 0 0 0 0 0 0 1 0 0 70 - 0 1 0 0 70 - 0 1 0 0 0 0 0 0 0 0 80 - 0 1 0 0 80 - 0 1 0 0 0 0 0 0 0 0 90 - 0 0 0 1 90 - 0 0 0 0 0 0 0 0 1 0 100 - 0 0 0 0 100 - 0 0 0 0 0 0 0 0 0 0 105 - 0 1 0 0 105 - 0 0 0 0 0 0 0 0 0 1 110 - 0 0 0 0 110 - 0 0 0 0 0 0 0 0 0 0 200 - 0 0 0 0 200 - 0 0 0 0 0 0 0 0 0 0 Mapping index print 3 - 4 5 7 8 0 - 6 1 - 9"; bool match = fgc01RatsResultFromPHP.Replace("\r\n", "\n") == outputString.Replace("\r\n", "\n"); Console.WriteLine("Output " + (match ? "matched" : "did not match") + " the output of the PHP version."); }
/* * Joins N QF subfragments into one QF fragment that can be properly binded into Skyrim VM * @throws ConversionException */ public TES5Target JoinQFFragments(BuildTarget target, string resultingFragmentName, List <QuestStageScript> subfragmentsTrees) { StageMap stageMap = BuildStageMap(target, resultingFragmentName); /* * We need script fragment for objective handling for each stage, so when parsing the script fragments, * we"ll be marking them there, and intersecting this with stage. * This will give us an array of stages which don"t have script fragment, but will need it anyways * for objective handling. */ TES5ScriptHeader resultingScriptHeader = new TES5ScriptHeader(resultingFragmentName, TES5BasicType.T_QUEST, "", true); TES5BlockList resultingBlockList = new TES5BlockList(); TES5GlobalScope resultingGlobalScope = new TES5GlobalScope(resultingScriptHeader); /* * Add ReferenceAlias"es * At some point, we might port the conversion so it doesn"t use the directly injected property, * but instead has a map to aliases and we"ll map accordingly and have references point to aliases instead */ string sourcePath = target.GetSourceFromPath(resultingFragmentName); string scriptName = Path.GetFileNameWithoutExtension(sourcePath); string aliasesFile = Path.Combine(Path.GetDirectoryName(sourcePath), scriptName + ".aliases"); string[] aliasesLines = File.ReadAllLines(aliasesFile); Dictionary <string, bool> aliasesDeclared = new Dictionary <string, bool>(); foreach (var alias in aliasesLines) { string trimmedAlias = alias.Trim(); if (trimmedAlias == "") { continue; } try { aliasesDeclared.Add(trimmedAlias, true); } catch (ArgumentException) { continue; } resultingGlobalScope.AddProperty(new TES5Property(trimmedAlias, TES5BasicType.T_REFERENCEALIAS, trimmedAlias)); } Dictionary <int, bool> implementedStages = new Dictionary <int, bool>(); Dictionary <string, bool> propertiesNamesDeclared = new Dictionary <string, bool>(); foreach (var subfragment in subfragmentsTrees) { TES5Target subfragmentsTree = subfragment.Script; TES5Script subfragmentScript = subfragmentsTree.Script; TES5GlobalScope subfragmentGlobalScope = subfragmentScript.GlobalScope; foreach (TES5Property subfragmentProperty in subfragmentGlobalScope.Properties) { /* * Move over the properties to the new global scope */ string propertyName; if (propertiesNamesDeclared.ContainsKey(subfragmentProperty.Name)) { propertyName = GeneratePropertyName(subfragmentScript.ScriptHeader, subfragmentProperty); subfragmentProperty.Rename(propertyName); } else { propertyName = subfragmentProperty.Name; } propertiesNamesDeclared.Add(propertyName, true); resultingGlobalScope.AddProperty(subfragmentProperty); //WTM: Note: See QF_FGD03Viranus_0102d154. Since ViranusDontonREF is present in multiple of the original fragments, //ViranusDontonREF gets renamed by the above. So multiple ViranusDontonREF variables are output. //Below I tried not renaming, assuming instead that variables with matching names and types within a set of fragments were intended to be the same variable. //It had OK results, but I'm leaving it commented for now. /*string propertyNameWithSuffix = subfragmentProperty.PropertyNameWithSuffix; * TES5Property existingProperty = resultingGlobalScope.Properties.Where(p => p.PropertyNameWithSuffix == propertyNameWithSuffix).FirstOrDefault(); * if (existingProperty != null && TES5InheritanceGraphAnalyzer.isExtending(subfragmentProperty.PropertyType, existingProperty.PropertyType)) * { * existingProperty.PropertyType = subfragmentProperty.PropertyType; * } * else * { * bool add = true; * if (existingProperty != null) * { * if (TES5InheritanceGraphAnalyzer.isExtending(existingProperty.PropertyType, subfragmentProperty.PropertyType)) * { * add = false; * } * else * { * string generatedPropertyName = generatePropertyName(subfragmentScript.ScriptHeader, subfragmentProperty, i); * subfragmentProperty.Rename(generatedPropertyName); * } * } * if (add) * { * resultingGlobalScope.Add(subfragmentProperty); * } * }*/ } List <ITES5CodeBlock> subfragmentBlocks = subfragmentScript.BlockList.Blocks; if (subfragmentBlocks.Count != 1) { throw new ConversionException("Wrong QF fragment, actual function count: " + subfragmentBlocks.Count + ".."); } ITES5CodeBlock subfragmentBlock = subfragmentBlocks[0]; if (subfragmentBlock.FunctionScope.BlockName != "Fragment_0") { throw new ConversionException("Wrong QF fragment funcname, actual function name: " + subfragmentBlock.FunctionScope.BlockName + ".."); } string newFragmentFunctionName = "Fragment_" + subfragment.Stage.ToString(); if (subfragment.LogIndex != 0) { newFragmentFunctionName += "_" + subfragment.LogIndex; } subfragmentBlock.FunctionScope.Rename(newFragmentFunctionName); var objectiveCodeChunks = this.objectiveHandlingFactory.GenerateObjectiveHandling(subfragmentBlock, resultingGlobalScope, stageMap.GetStageTargetsMap(subfragment.Stage)); foreach (var newCodeChunk in objectiveCodeChunks) { subfragmentBlock.AddChunk(newCodeChunk); } resultingBlockList.Add(subfragmentBlock); implementedStages[subfragment.Stage] = true; } /* * Diff to find stages which we still need to mark */ int[] nonDoneStages = stageMap.StageIDs.Where(stageID => !implementedStages.ContainsKey(stageID)).ToArray(); foreach (int nonDoneStage in nonDoneStages) { TES5FunctionCodeBlock fragment = this.objectiveHandlingFactory.CreateEnclosedFragment(resultingGlobalScope, nonDoneStage, stageMap.GetStageTargetsMap(nonDoneStage)); resultingBlockList.Add(fragment); } this.mappedTargetsLogService.WriteScriptName(resultingFragmentName); foreach (var kvp in stageMap.MappedTargetsIndex) { var originalTargetIndex = kvp.Key; var mappedTargetIndexes = kvp.Value; this.mappedTargetsLogService.WriteLine(originalTargetIndex, mappedTargetIndexes); } TES5Script resultingTree = new TES5Script(resultingGlobalScope, resultingBlockList); string outputPath = target.GetTranspileToPath(resultingFragmentName); return(new TES5Target(resultingTree, outputPath)); }