private static bool CanRandomize(ExportEntry export, bool isArchetypeCheck, out ExportEntry hairMeshExp) { hairMeshExp = null; if (export.IsDefaultObject) { return(false); } if (export.ClassName == "BioPawn" && export.GetProperty <ObjectProperty>("m_oHairMesh") is ObjectProperty op && op.ResolveToEntry(export.FileRef) is ExportEntry hairExp) { hairMeshExp = hairExp; return(true); } if (export.ClassName == "SFXSkeletalMeshActorMAT") { if (export.GetProperty <ObjectProperty>("HairMesh") is ObjectProperty opSKM && opSKM.ResolveToEntry(export.FileRef) is ExportEntry hairExpSKM) { // Check if skeletal mesh is set locally or if it's done in the archetype // if set to zero we should not add hair cause it will look bad var skeletalMesh = hairExpSKM.GetProperty <ObjectProperty>("SkeletalMesh"); if (skeletalMesh == null) { // Look in archetype if (export.Archetype != null) { ExportEntry arch = export.Archetype as ExportEntry; if (export.Archetype is ImportEntry imp) { // oof arch = EntryImporter.ResolveImport(imp, MERFileSystem.GetGlobalCache()); } hairMeshExp = hairExpSKM; var result = export.ObjectFlags.Has(UnrealFlags.EObjectFlags.ArchetypeObject) == isArchetypeCheck && CanRandomize(arch, true, out _); // look at archetype if (result && !isArchetypeCheck) { Debug.WriteLine($"Running on {export.ObjectName.Instanced}"); } return(result); } } else { hairMeshExp = hairExpSKM; var result = export.ObjectFlags.Has(UnrealFlags.EObjectFlags.ArchetypeObject) == isArchetypeCheck && skeletalMesh.Value != 0; if (result && !isArchetypeCheck) { Debug.WriteLine($"Running on {export.ObjectName.Instanced}"); } return(result); } } } return(false); }
public static void TestAllImportsInMERFS() { var dlcModPath = Path.Combine(MEDirectories.GetDefaultGamePath(MERFileSystem.Game), "BioGame", "DLC", $"DLC_MOD_{MERFileSystem.Game}Randomizer", "CookedPC"); var packages = Directory.GetFiles(dlcModPath); var globalCache = MERFileSystem.GetGlobalCache(); //var globalP = Path.Combine(dlcModPath, "BioP_Global.pcc"); //if (File.Exists(globalP)) //{ // // This is used for animation lookups. // globalCache.InsertIntoCache(MEPackageHandler.OpenMEPackage(globalP)); //} foreach (var p in packages) { if (p.RepresentsPackageFilePath()) { MERPackageCache localCache = new MERPackageCache(); Debug.WriteLine($"Checking package file {p}"); var pack = MEPackageHandler.OpenMEPackage(p); foreach (var imp in pack.Imports) { if (imp.InstancedFullPath.StartsWith("Core.") || imp.InstancedFullPath.StartsWith("Engine.")) { continue; // These have some natives are always in same file. } if (imp.InstancedFullPath == "BioVFX_Z_TEXTURES.Generic.Glass_Shards_Norm" && MERFileSystem.Game == MEGame.ME2) { continue; // This is... native for some reason? } var resolvedExp = EntryImporter.ResolveImport(imp, globalCache, localCache); if (resolvedExp == null) { Debug.WriteLine($"Could not resolve import: {imp.InstancedFullPath}"); } } } } Debug.WriteLine("Done checking imports"); }
public static bool RandomizePackageActorsInConversation(IMEPackage package, RandomizationOption option) { var conversationStartExports = package.Exports.Where(CanRandomizeSeqActStartConvo).ToList(); if (!conversationStartExports.Any()) { return(false); } MERPackageCache localCache = new MERPackageCache(); foreach (var convStart in conversationStartExports) { //if (convStart.UIndex < 13638) // continue; if (!CanRandomizeConversationStart(convStart)) { continue; } var bioConvImportProp = convStart.GetProperty <ObjectProperty>("Conv"); if (bioConvImportProp == null) { continue; // Some conversation starts are just filler stubs and are missing data like Nor_340a/bZaeed } var bioConvImport = bioConvImportProp.ResolveToEntry(package) as ImportEntry; List <string> inputTags = new(); // Shuffle the inputs to the conversation. We will store these and then have to update the Owner and Player tags // I think...... var seqLinks = SeqTools.GetVariableLinksOfNode(convStart); // Dictionary of linked nodes, and var links that point to them. List <ExportEntry> shufflableNodes = new List <ExportEntry>(); List <SeqTools.VarLinkInfo> applicableLinks = new(); ExportEntry ownerEntry = null; ExportEntry playerEntry = null; var sequenceObjects = SeqTools.GetAllSequenceElements(convStart).OfType <ExportEntry>().ToList(); foreach (var varilink in seqLinks) { if (CanLinkBeRandomized(varilink, out _)) { var connectedItem = varilink.LinkedNodes[0] as ExportEntry; if (!shufflableNodes.Contains(connectedItem)) { // Check to make sure node has a value if (connectedItem.ClassName == "SeqVar_Object") { var objValue = connectedItem.GetProperty <ObjectProperty>("ObjValue"); if (objValue == null && !MERSeqTools.IsAssignedBioPawn(convStart, connectedItem, sequenceObjects)) { continue; // This is not a shufflable node, it is never assigned anything } } shufflableNodes.Add(connectedItem); } if (varilink.LinkedNodes[0].ClassName == "SeqVar_Player") { playerEntry = varilink.LinkedNodes[0] as ExportEntry; } else if (varilink.LinkDesc == "Owner") { ownerEntry = varilink.LinkedNodes[0] as ExportEntry; } applicableLinks.Add(varilink); } } if (shufflableNodes.Count <= 1) { continue; // We cannot edit this one } if (shufflableNodes.Count == 2 && playerEntry != null && ownerEntry != null) { continue; // We can't swap owner and player due to hardcoded owner and player tags } // Build shuffle map bool retry = true; Dictionary <int, int> shuffleMap = null; while (retry) { shuffleMap = new Dictionary <int, int>(); var reAssigned = shufflableNodes.ToList(); reAssigned.Shuffle(); for (int i = 0; i < shufflableNodes.Count; i++) { var sourceEntry = shufflableNodes[i]; var remapEntry = reAssigned.PullFirstItem(); int retryCount = reAssigned.Count; while (retryCount > 0 && (sourceEntry == remapEntry || (ownerEntry == sourceEntry && remapEntry == ownerEntry))) { reAssigned.Add(remapEntry); sourceEntry = reAssigned.PullFirstItem(); retryCount--; } if (sourceEntry == ownerEntry && remapEntry == playerEntry) { // Cannot set player to owner break; // We must force a retry } shuffleMap[sourceEntry.UIndex] = remapEntry.UIndex; } if (shuffleMap.Count != shufflableNodes.Count) { continue; // Try again } // We did it retry = false; } // Apply the shuffle map Dictionary <NameReference, ActorLookup> findActorMap = new(); foreach (var varilink in applicableLinks) { var repointedItem = shuffleMap[varilink.LinkedNodes[0].UIndex]; //if (varilink.LinkedNodes[0] == playerEntry || repointedItem == playerEntry.UIndex) //{ // Build FindActor mapping for player // it should be: // Player -> [Some actor, like Pup1_1] // Pup1_1 -> Player // When parsing the interpdatas, change the findactor's // by this methodology BuildActorMap(findActorMap, varilink, repointedItem); //} // Actor map for updating the bioconversation //Debug.WriteLine($"Shuffle actor on varilink in convo: {varilink.LinkedNodes[0].ObjectName.Instanced} => {package.GetUExport(repointedItem).ObjectName.Instanced}"); varilink.LinkedNodes[0] = package.GetUExport(repointedItem); } //SeqTools.PrintVarLinkInfo(seqLinks); // Write the updated links out. SeqTools.WriteVariableLinksToNode(convStart, seqLinks); // Update the localizations foreach (var loc in Localizations) { var bioConversation = EntryImporter.ResolveImport(bioConvImport, MERFileSystem.GetGlobalCache(), localCache, loc); var conv = new ConversationExtended(bioConversation); conv.LoadConversation(null, true); // Step 1. Update tags via map var allConvEntries = conv.ReplyList.ToList(); allConvEntries.AddRange(conv.EntryList); foreach (var convNode in allConvEntries) { // Update speaker if (convNode.IsReply) { // Player. We can't do anything (or can we?) } else { // Non-player node. We can change the tag var speakerTag = convNode.SpeakerTag; // Even though it is dictionary, since it is NameRef, it is considered case sensitive. We have to use case insensitive check var newName = findActorMap.FirstOrDefault(x => x.Key.Instanced.Equals(speakerTag.SpeakerName, StringComparison.InvariantCultureIgnoreCase)).Value; if (newName != null && newName.FindActor.Name != null) { var newTagIdx = conv.Speakers.FirstOrDefault(x => x.SpeakerName.Equals(newName.FindActor.Instanced, StringComparison.InvariantCultureIgnoreCase)); if (newTagIdx != null) { convNode.SpeakerIndex = newTagIdx.SpeakerID; } else { var newSpeaker = new SpeakerExtended(conv.Speakers.Count - 3, new NameReference(newName.FindActor.Name.ToLower(), newName.FindActor.Number)); newSpeaker.FaceFX_Male = speakerTag.FaceFX_Male; newSpeaker.FaceFX_Female = speakerTag.FaceFX_Male; conv.Speakers.Add(newSpeaker); convNode.SpeakerIndex = newSpeaker.SpeakerID; //Debugger.Break(); } } else { //Debugger.Break(); } } // Update interpolation data if (convNode.Interpdata == null) { continue; } var interpData = convNode.Interpdata; InterpData id = new InterpData(interpData); var convo = id.InterpGroups.FirstOrDefault(x => x.GroupName == "Conversation"); if (convo != null) { foreach (var it in convo.Tracks) { var props = it.Export.GetProperties(); var findActor = props.GetProp <NameProperty>("m_nmFindActor"); if (findActor != null && findActor.Value.Name != "Owner" && findActorMap.TryGetValue(findActor.Value, out var newInfo) && newInfo.FindActor.Name != null) { //Debug.WriteLine($"Updating find actor info: {findActor.Value.Instanced} -> {newInfo.FindActor.Instanced}"); findActor.Value = newInfo.FindActor; if (newInfo.FindMode != null) { props.AddOrReplaceProp(new EnumProperty(newInfo.FindMode.ToString(), "EActorTrackFindActorMode", MERFileSystem.Game, "m_eFindActorMode")); } else { props.RemoveNamedProperty("m_eFindActorMode"); } } var lookAtKeys = props.GetProp <ArrayProperty <StructProperty> >("m_aLookAtKeys"); if (lookAtKeys != null) { foreach (var lookAtS in lookAtKeys) { var lookAt = lookAtS.GetProp <NameProperty>("nmFindActor"); if (lookAt.Value.Name != "Owner" && findActorMap.TryGetValue(lookAt.Value, out var newInfoLA) && newInfoLA.FindActor.Name != null) { //Debug.WriteLine($"Updating lookat find actor info: {lookAt.Value.Instanced} -> {newInfoLA.FindActor.Instanced}"); if (newInfoLA.FindActor.Name == null) { Debugger.Break(); } lookAt.Value = newInfoLA.FindActor; var lookatFindMode = newInfoLA.FindMode?.ToString(); lookatFindMode ??= "ActorTrack_FindActorByTag"; // if it's null, set it to the default. As this is struct, the property must exist lookAtS.Properties.AddOrReplaceProp(new EnumProperty(lookatFindMode, "EActorTrackFindActorMode", MERFileSystem.Game, "eFindActorMode")); } } } it.Export.WriteProperties(props); //if (IsAllowedFindActor(findActor)) //{ // if (actorsToFind.All(x => x.FindActor.Instanced != findActor.Value.Instanced)) // { // ActorLookup al = new ActorLookup() { FindActor = findActor.Value }; // var lookupType = it.Export.GetProperty<EnumProperty>("m_eFindActorMode"); // if (lookupType != null && Enum.TryParse<EActorTrackFindActorMode>(lookupType.Value.Name, out var result)) // { // al.FindMode = result; // } // actorsToFind.Add(al); // } // // add track // tracksToUpdate.Add(it); //} } } } conv.SerializeNodes(); // Write the updated info back MERFileSystem.SavePackage(bioConversation.FileRef); } } //// Step 2. Build the remapping //if (actorsToFind.Count <= 1) // return false; // Nothing to randomize //// Instanced name to NameReference //Dictionary<string, ActorLookup> actorRemap = new Dictionary<string, ActorLookup>(); //var shuffledActors = actorsToFind.ToList(); //var unshuffledActors = actorsToFind.Select(x => x.FindActor.Instanced).ToList(); //shuffledActors.Shuffle(); //Debug.WriteLine(""); //while (shuffledActors.Count > 0) //{ // var sourceItem = unshuffledActors.PullFirstItem(); // var remappedItem = shuffledActors.PullFirstItem(); // actorRemap[sourceItem] = remappedItem; // Debug.WriteLine($"Remapping actor: {sourceItem} => {remappedItem.FindActor.Instanced}"); //} //// Step 3. Update all tracks with new remap //foreach (var track in tracksToUpdate) //{ // var props = track.Export.GetProperties(); // var findActor = props.GetProp<NameProperty>("m_nmFindActor"); // if (IsAllowedFindActor(findActor)) // { // if (actorRemap[findActor.Value.Instanced].FindMode != null) // { // props.AddOrReplaceProp(new EnumProperty(actorRemap[findActor.Value.Instanced].FindMode.ToString(), "EActorTrackFindActorMode", MERFileSystem.Game, "m_eFindActorMode")); // } // else // { // props.RemoveNamedProperty("m_eFindActorNode"); // } // findActor.Value = actorRemap[findActor.Value.Instanced].FindActor; // track.Export.WriteProperties(props); // } //} return(true); }
/// <summary> /// Ports an export into a package. Checks if the export already exists, and if it does, returns that instead. /// </summary> /// <param name="targetPackage">The target package to port into.</param> /// <param name="sourceExport">The source export to port over, including all dependencies and references.</param> /// <param name="targetLink">The target link UIndex. Only used if createParentPackages is false.</param> /// <param name="createParentPackages">If the export should be ported in the same way as it was cooked into the package natively, e.g. create the parent package paths. The export must directly sit under a Package or an exception will be thrown.</param> /// <param name="ensureMemoryUniqueness">If this object is an instance, such as a sequence object, and should be made memory-unique so it is properly used</param> /// <returns></returns> public static ExportEntry PortExportIntoPackage(IMEPackage targetPackage, ExportEntry sourceExport, int targetLink = 0, bool createParentPackages = true, bool ensureMemoryUniqueness = false, bool useMemorySafeImport = false, PackageCache cache = null) { #if DEBUG // in preprocessor to prevent this from running in release mode if (sourceExport.FileRef.FilePath != null && targetPackage.FilePath != null) { Debug.WriteLine($"Porting {sourceExport.InstancedFullPath} from {Path.GetFileName(sourceExport.FileRef.FilePath)} into {Path.GetFileName(targetPackage.FilePath)}"); } #endif var existing = targetPackage.FindExport(sourceExport.InstancedFullPath); if (existing != null) { return(existing); } // Create parent hierarchy IEntry newParent = null; if (createParentPackages) { List <IEntry> parents = new List <IEntry>(); var parent = sourceExport.Parent; while (parent != null) { if (parent.ClassName != "Package") { throw new Exception("Parent is not package!"); } parents.Add(parent); parent = parent.Parent; } // Create the parents parents.Reverse(); foreach (var p in parents) { var sourceFullPath = p.InstancedFullPath; var matchingParent = targetPackage.FindEntry(sourceFullPath); if (matchingParent != null) { newParent = matchingParent; continue; } newParent = ExportCreator.CreatePackageExport(targetPackage, p.ObjectName, newParent); } } else { newParent = targetPackage.GetEntry(targetLink); } IEntry newEntry; if (!useMemorySafeImport) { Dictionary <IEntry, IEntry> crossPCCObjectMap = new Dictionary <IEntry, IEntry>(); // Not sure what this is used for these days. Should probably just be part of the method var relinkResults = EntryImporter.ImportAndRelinkEntries(EntryImporter.PortingOption.CloneAllDependencies, sourceExport, targetPackage, newParent, true, out newEntry, crossPCCObjectMap); if (relinkResults.Any()) { Debugger.Break(); } } else { // Memory safe, fixes upstream var relinkedResults = EntryExporter.ExportExportToPackage(sourceExport, targetPackage, out newEntry, MERFileSystem.GetGlobalCache(), cache); if (relinkedResults.Any()) { Debugger.Break(); } } #if DEBUG //(sourceExport.FileRef as MEPackage).CompareToPackageDetailed(targetPackage); #endif // Helps ensure we don't have memory duplicates if (ensureMemoryUniqueness) { newEntry.ObjectName = targetPackage.GetNextIndexedName(newEntry.ObjectName); } return(newEntry as ExportEntry); }