private static void MakeTubesSectionHarder() { var preReaperF = MERFileSystem.GetPackageFile("BioD_EndGm2_420CombatZone.pcc"); if (preReaperF != null && File.Exists(preReaperF)) { var preReaperP = MEPackageHandler.OpenMEPackage(preReaperF); // Open tubes on kills to start the attack (post platforms)---------------------- var seq = preReaperP.GetUExport(15190); var attackSw = MERSeqTools.InstallRandomSwitchIntoSequence(seq, 2); //50% chance // killed squad member -> squad still exists to 50/50 sw KismetHelper.CreateOutputLink(preReaperP.GetUExport(15298), "SquadStillExists", attackSw); // 50/50 to just try to do reaper attack KismetHelper.CreateOutputLink(attackSw, "Link 1", preReaperP.GetUExport(14262)); // Automate the platforms one after another KismetHelper.RemoveAllLinks(preReaperP.GetUExport(15010)); //B Plat01 Death KismetHelper.RemoveAllLinks(preReaperP.GetUExport(15011)); //B Plat02 Death // Sub automate - Remove attack completion gate inputs ---- KismetHelper.RemoveAllLinks(preReaperP.GetUExport(15025)); //Plat03 Attack complete KismetHelper.RemoveAllLinks(preReaperP.GetUExport(15029)); //Plat02 Attack complete //// Sub automate - Remove activate input into gate var cmb2activated = preReaperP.GetUExport(15082); var cmb3activated = preReaperP.GetUExport(15087); KismetHelper.RemoveAllLinks(cmb2activated); KismetHelper.CreateOutputLink(cmb2activated, "Out", preReaperP.GetUExport(2657)); // Delay the start of platform 3 by 4 seconds to give player a bit more time to handle first two platforms // Player will likely have decent weapons by now so they will be better than my testing for sure KismetHelper.RemoveAllLinks(cmb3activated); var newDelay = EntryCloner.CloneEntry(preReaperP.GetUExport(14307)); newDelay.WriteProperty(new FloatProperty(4, "Duration")); KismetHelper.AddObjectToSequence(newDelay, preReaperP.GetUExport(15183), true); KismetHelper.CreateOutputLink(cmb3activated, "Out", newDelay); KismetHelper.CreateOutputLink(newDelay, "Finished", preReaperP.GetUExport(2659)); // preReaperP.GetUExport(14451).RemoveProperty("bOpen"); // Plat03 gate - forces gate open so when reaper attack fires it passes through // preReaperP.GetUExport(14450).RemoveProperty("bOpen"); // Plat02 gate - forces gate open so when reaper attack fires it passes through // There is no end to Plat03 behavior until tubes are dead KismetHelper.CreateOutputLink(preReaperP.GetUExport(14469), "Completed", preReaperP.GetUExport(14374)); // Interp completed to Complete in Plat01 KismetHelper.CreateOutputLink(preReaperP.GetUExport(14470), "Completed", preReaperP.GetUExport(14379)); // Interp completed to Complete in Plat02 // if possession fails continue the possession loop on plat3 to end of pre-reaper combat KismetHelper.CreateOutputLink(preReaperP.GetUExport(16414), "Failed", preReaperP.GetUExport(14307)); MERFileSystem.SavePackage(preReaperP); } }
private static void RandomizeTheLongWalk(RandomizationOption option) { var prelongwalkfile = MERFileSystem.GetPackageFile("BioD_EndGm2_200Factory.pcc"); if (prelongwalkfile != null) { // Pre-long walk selection var package = MEPackageHandler.OpenMEPackage(prelongwalkfile); var bioticTeamSeq = package.GetUExport(8609); var activated = package.GetUExport(8484); KismetHelper.RemoveAllLinks(activated); // install new logic var randSwitch = MERSeqTools.InstallRandomSwitchIntoSequence(bioticTeamSeq, 13); // don't include theif or veteran as dlc might not be installed KismetHelper.CreateOutputLink(activated, "Out", randSwitch); // Outputs of random choice KismetHelper.CreateOutputLink(randSwitch, "Link 1", package.GetUExport(1420)); //thane KismetHelper.CreateOutputLink(randSwitch, "Link 2", package.GetUExport(1419)); //jack KismetHelper.CreateOutputLink(randSwitch, "Link 3", package.GetUExport(1403)); //garrus KismetHelper.CreateOutputLink(randSwitch, "Link 4", package.GetUExport(1399)); //legion KismetHelper.CreateOutputLink(randSwitch, "Link 5", package.GetUExport(1417)); //grunt KismetHelper.CreateOutputLink(randSwitch, "Link 6", package.GetUExport(1395)); //jacob KismetHelper.CreateOutputLink(randSwitch, "Link 7", package.GetUExport(1418)); //samara KismetHelper.CreateOutputLink(randSwitch, "Link 8", package.GetUExport(1415)); //mordin KismetHelper.CreateOutputLink(randSwitch, "Link 9", package.GetUExport(1405)); //tali KismetHelper.CreateOutputLink(randSwitch, "Link 10", package.GetUExport(1401)); //morinth KismetHelper.CreateOutputLink(randSwitch, "Link 11", package.GetUExport(1402)); //miranda // kasumi if (MERFileSystem.GetPackageFile("BioH_Thief_00.pcc") != null) { KismetHelper.CreateOutputLink(randSwitch, "Link 12", package.GetUExport(1396)); //kasumi } // zaeed if (MERFileSystem.GetPackageFile("BioH_Veteran_00.pcc") != null) { KismetHelper.CreateOutputLink(randSwitch, "Link 13", package.GetUExport(1416)); //zaeed } MERFileSystem.SavePackage(package); } var biodEndGm2F = MERFileSystem.GetPackageFile("BioD_EndGm2.pcc"); if (biodEndGm2F != null) { var package = MEPackageHandler.OpenMEPackage(biodEndGm2F); var ts = package.GetUExport(7); var ss = ts.GetProperty <ArrayProperty <StructProperty> >("StreamingStates"); // Make walk4 remain loaded while walk5 is active as enemeis may not yet be cleared out var conclusion = ss[8]; var visibleNames = conclusion.GetProp <ArrayProperty <NameProperty> >("VisibleChunkNames"); if (!visibleNames.Any(x => x.Value == "BioD_EndGm2_300Walk04")) { // This has pawns as part of the level so we must make sure it doesn't disappear or player will just see enemies disappear visibleNames.Add(new NameProperty("BioD_EndGm2_300Walk04")); } ts.WriteProperty(ss); MERFileSystem.SavePackage(package); } var longwalkfile = MERFileSystem.GetPackageFile("BioD_EndGm2_300LongWalk.pcc"); if (longwalkfile != null) { // automate TLW var package = MEPackageHandler.OpenMEPackage(longwalkfile); var seq = package.GetUExport(1629); var stopWalking = package.GetUExport(1569); // The auto walk delay on Stop Walking var delay = package.GetUExport(806).Clone(); package.AddExport(delay); delay.WriteProperty(new FloatProperty(ThreadSafeRandom.NextFloat(2, 7), "Duration")); // how long to wait until auto walk KismetHelper.AddObjectToSequence(delay, seq, true); KismetHelper.CreateOutputLink(delay, "Finished", package.GetUExport(156)); KismetHelper.CreateOutputLink(stopWalking, "Out", delay); // Do not allow targeting the escort package.GetUExport(1915).WriteProperty(new IntProperty(0, "bValue")); // stopped walking package.GetUExport(1909).WriteProperty(new IntProperty(0, "bValue")); // loading from save - we will auto start KismetHelper.CreateOutputLink(package.GetUExport(1232), "Out", delay); // post loaded from save init // Do not enable autosaves, cause it makes it easy to cheese this area. Bypass the 'savegame' item KismetHelper.RemoveOutputLinks(package.GetUExport(156)); KismetHelper.CreateOutputLink(package.GetUExport(156), "Out", package.GetUExport(1106)); // Pick a random henchman to go on a date with //var determineEscortLog = package.GetUExport(1118); //var spawnSeq = package.GetUExport(1598); //// disconnect old logic //KismetHelper.RemoveAllLinks(determineEscortLog); // install new logic /*var randSwitch = SeqTools.InstallRandomSwitchIntoSequence(spawnSeq, 12); // don't include theif or veteran as dlc might not be installed * KismetHelper.CreateOutputLink(determineEscortLog, "Out", randSwitch); * * * // Outputs of random choice * * * * KismetHelper.CreateOutputLink(randSwitch, "Link 1", package.GetUExport(1599)); //thane * KismetHelper.CreateOutputLink(randSwitch, "Link 2", package.GetUExport(1601)); //jack * KismetHelper.CreateOutputLink(randSwitch, "Link 3", package.GetUExport(1603)); //garrus * KismetHelper.CreateOutputLink(randSwitch, "Link 4", package.GetUExport(1605)); //legion * KismetHelper.CreateOutputLink(randSwitch, "Link 5", package.GetUExport(1607)); //grunt * KismetHelper.CreateOutputLink(randSwitch, "Link 6", package.GetUExport(1609)); //jacob * KismetHelper.CreateOutputLink(randSwitch, "Link 7", package.GetUExport(1611)); //samara * KismetHelper.CreateOutputLink(randSwitch, "Link 8", package.GetUExport(1613)); //mordin * KismetHelper.CreateOutputLink(randSwitch, "Link 9", package.GetUExport(1615)); //tali * KismetHelper.CreateOutputLink(randSwitch, "Link 10", package.GetUExport(1619)); //morinth * KismetHelper.CreateOutputLink(randSwitch, "Link 11", package.GetUExport(1624)); //miranda */ MERFileSystem.SavePackage(package); } //randomize long walk lengths. var endwalkexportmap = new Dictionary <string, int>() { { "BioD_EndGm2_300Walk01", 40 }, { "BioD_EndGm2_300Walk02", 5344 }, { "BioD_EndGm2_300Walk03", 8884 }, { "BioD_EndGm2_300Walk04", 6370 }, { "BioD_EndGm2_300Walk05", 3190 } }; foreach (var map in endwalkexportmap) { var file = MERFileSystem.GetPackageFile(map.Key + ".pcc"); if (file != null) { var package = MEPackageHandler.OpenMEPackage(file); var export = package.GetUExport(map.Value); export.WriteProperty(new FloatProperty(ThreadSafeRandom.NextFloat(.5, 2.5), "PlayRate")); MERFileSystem.SavePackage(package); } } /*foreach (var f in files) * { * var package = MEPackageHandler.OpenMEPackage(f); * var animExports = package.Exports.Where(x => x.ClassName == "InterpTrackAnimControl"); * foreach (var anim in animExports) * { * var animseqs = anim.GetProperty<ArrayProperty<StructProperty>>("AnimSeqs"); * if (animseqs != null) * { * foreach (var animseq in animseqs) * { * var seqname = animseq.GetProp<NameProperty>("AnimSeqName").Value.Name; * if (seqname.StartsWith("Walk_")) * { * var playrate = animseq.GetProp<FloatProperty>("AnimPlayRate"); * var oldrate = playrate.Value; * if (oldrate != 1) Debugger.Break(); * playrate.Value = ThreadSafeRandom.NextFloat(.2, 6); * var data = anim.Parent.Parent as ExportEntry; * var len = data.GetProperty<FloatProperty>("InterpLength"); * len.Value = len.Value * playrate; //this might need to be changed if its not 1 * data.WriteProperty(len); * } * } * } * anim.WriteProperty(animseqs); * } * SavePackage(package); * }*/ }
private static void GenericRandomizeFlyerSpawns(IMEPackage package, int maxNumNewEnemies, EPortablePawnClassification minClassification = EPortablePawnClassification.Mook, EPortablePawnClassification maxClassification = EPortablePawnClassification.Boss) { var flyerPrepSequences = package.Exports.Where(x => x.ClassName == "Sequence" && x.GetProperty <StrProperty>("ObjName") is StrProperty objName && objName == "REF_SpawnPrep_Flyer").ToList(); foreach (var flySeq in flyerPrepSequences) { var objectsInSeq = KismetHelper.GetSequenceObjects(flySeq).OfType <ExportEntry>().ToList(); var preGate = objectsInSeq.FirstOrDefault(x => x.ClassName == "SeqAct_Gate"); // should not be null var outbound = SeqTools.GetOutboundLinksOfNode(preGate); var aifactoryObj = outbound[0][0].LinkedOp as ExportEntry; //First out on first link. Should point to AIFactory assuming these are all duplicated flyers var availablePawns = PawnPorting.PortablePawns.Where(x => x.Classification >= minClassification && x.Classification <= maxClassification).ToList(); if (availablePawns.Any()) { // We can add new pawns to install List <PortablePawn> newPawnsInThisSeq = new List <PortablePawn>(); int numEnemies = 1; // 1 is the original amount. List <ExportEntry> aiFactories = new List <ExportEntry>(); aiFactories.Add(aifactoryObj); // the original one for (int i = 0; i < maxNumNewEnemies; i++) { var randPawn = availablePawns.RandomElement(); if (!newPawnsInThisSeq.Contains(randPawn)) { numEnemies++; PawnPorting.PortPawnIntoPackage(randPawn, package); newPawnsInThisSeq.Add(randPawn); // Clone the ai factory sequence object and add it to the sequence var newAiFactorySeqObj = EntryCloner.CloneTree(aifactoryObj); aiFactories.Add(newAiFactorySeqObj); KismetHelper.AddObjectToSequence(newAiFactorySeqObj, flySeq, false); // Update the backing factory object var backingFactory = newAiFactorySeqObj.GetProperty <ObjectProperty>("Factory").ResolveToEntry(package) as ExportEntry; backingFactory.WriteProperty(new ObjectProperty(package.FindExport(randPawn.ChallengeTypeFullPath), "ActorType")); var collection = backingFactory.GetProperty <ArrayProperty <ObjectProperty> >("ActorResourceCollection"); collection.Clear(); foreach (var asset in randPawn.AssetPaths) { collection.Add(new ObjectProperty(package.FindExport(asset))); } backingFactory.WriteProperty(collection); } } if (newPawnsInThisSeq.Any()) { // install the switch var randSw = MERSeqTools.InstallRandomSwitchIntoSequence(flySeq, numEnemies); outbound[0][0].LinkedOp = randSw; SeqTools.WriteOutboundLinksToNode(preGate, outbound); // Hook up switch to ai factories for (int i = 0; i < numEnemies; i++) { var linkDesc = $"Link {i + 1}"; KismetHelper.CreateOutputLink(randSw, linkDesc, aiFactories[i]); // switches are indexed at 1 } } } } }
private static void RandomizeAfterlifeShepDance() { var denDanceF = MERFileSystem.GetPackageFile(@"BioD_OmgHub_230DenDance.pcc"); if (denDanceF != null) { var loungeP = MEPackageHandler.OpenMEPackage(denDanceF); var sequence = loungeP.GetUExport(3924); MERPackageCache cache = new MERPackageCache(); List <InterpTools.InterpData> interpDatas = new List <InterpTools.InterpData>(); var interp1 = loungeP.GetUExport(3813); // Make 2 additional dance options by cloning the interp and the data tree var interp2 = MERSeqTools.CloneBasicSequenceObject(interp1); var interp3 = MERSeqTools.CloneBasicSequenceObject(interp1); // Clone the interp data for attaching to 2 and 3 var interpData1 = loungeP.GetUExport(1174); var interpData2 = EntryCloner.CloneTree(interpData1); var interpData3 = EntryCloner.CloneTree(interpData2); KismetHelper.AddObjectToSequence(interpData2, sequence); KismetHelper.AddObjectToSequence(interpData3, sequence); // Load ID for randomization interpDatas.Add(new InterpTools.InterpData(interpData1)); interpDatas.Add(new InterpTools.InterpData(interpData2)); interpDatas.Add(new InterpTools.InterpData(interpData3)); // Chance the data for interp2/3 var id2 = SeqTools.GetVariableLinksOfNode(interp2); id2[0].LinkedNodes[0] = interpData2; SeqTools.WriteVariableLinksToNode(interp2, id2); var id3 = SeqTools.GetVariableLinksOfNode(interp3); id3[0].LinkedNodes[0] = interpData3; SeqTools.WriteVariableLinksToNode(interp3, id3); // Add additional finished states for fadetoblack when done KismetHelper.CreateOutputLink(loungeP.GetUExport(958), "Finished", interp2, 2); KismetHelper.CreateOutputLink(loungeP.GetUExport(958), "Finished", interp3, 2); // Link up the random choice it makes var randSw = MERSeqTools.InstallRandomSwitchIntoSequence(sequence, 3); KismetHelper.CreateOutputLink(randSw, "Link 1", interp1); KismetHelper.CreateOutputLink(randSw, "Link 2", interp2); KismetHelper.CreateOutputLink(randSw, "Link 3", interp3); // Break the original output to start the interp, repoint it's output to the switch instead var sgm = loungeP.GetUExport(1003); //set gesture mode KismetHelper.RemoveOutputLinks(sgm); KismetHelper.CreateOutputLink(sgm, "Done", loungeP.GetUExport(960)); KismetHelper.CreateOutputLink(sgm, "Done", randSw); // Now install the dances foreach (var id in interpDatas) { var danceTrack = id.InterpGroups[0].Tracks[0]; OmegaHub.InstallShepardDanceGesture(danceTrack.Export, cache); } MERFileSystem.SavePackage(loungeP); } }
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); }
public static bool ShuffleCutscenePawns2(ExportEntry export, RandomizationOption option) { if (!CanRandomize(export, out var cutsceneName)) { return(false); } if (acceptableTagsForPawnShuffling == null) { LoadAsset(); } var variableLinks = SeqTools.GetVariableLinksOfNode(export); // Entries that can be shuffled. // This list must not to have items removed! List <ExportEntry> pawnsToShuffleDirectAttached = new List <ExportEntry>(); List <ExportEntry> pawnsToShuffleDynamicSet = new List <ExportEntry>(); var sequenceElements = SeqTools.GetAllSequenceElements(export).OfType <ExportEntry>().ToList();; foreach (var vl in variableLinks) { if (!BlackListedLinkDesc(vl) && (vl.ExpectedTypeName == "SeqVar_Object" || vl.ExpectedTypeName == "SeqVar_Player" || vl.ExpectedTypeName == "BioSeqVar_ObjectFindByTag")) { // It's a canidate for randomization // Some ObjectFindByTag have an attached ObjValue for some reason. // It's findobjectbytag but in same file // some leftover development thing foreach (var variableLinkNode in vl.LinkedNodes.OfType <ExportEntry>()) { bool addedToShuffler = false; if (variableLinkNode.GetProperty <ObjectProperty>("ObjValue")?.ResolveToEntry(export.FileRef) is ExportEntry linkedObjectEntry) { // It's a SeqVar_Object with a linked param if (linkedObjectEntry.IsA("BioPawn")) { // This might be leftover from ME1 var flyingpawn = linkedObjectEntry.GetProperty <BoolProperty>("bCanFly")?.Value; if (flyingpawn == null || flyingpawn == false) { pawnsToShuffleDirectAttached.Add(variableLinkNode); //can be shuffled. This is a var link so it must be added addedToShuffler = true; } } } // It's not a directly set object // WORK ON CODE BELOW else if (vl.ExpectedTypeName == "SeqVar_Object" && !addedToShuffler) { // Find what assigns if (MERSeqTools.IsAssignedBioPawn(export, variableLinkNode, sequenceElements)) { pawnsToShuffleDirectAttached.Add(variableLinkNode); addedToShuffler = true; } } if (!addedToShuffler) { // Check if is SeqVar_Player. We can always shuffle this one string className = variableLinkNode.ClassName; if (className == "SeqVar_Player") { variableLinkNode.WriteProperty(new BoolProperty(true, "bReturnsPawns")); // This is in ME1R, not sure it's needed, but let's just make sure it's true pawnsToShuffleDirectAttached.Add(variableLinkNode); //pointer to this node } else if (className == "BioSeqVar_ObjectFindByTag") { var tagToFind = variableLinkNode.GetProperty <StrProperty>("m_sObjectTagToFind")?.Value; //if (tagToFind == "hench_pilot") // Debugger.Break(); if (tagToFind != null && acceptableTagsForPawnShuffling.Contains(tagToFind)) { pawnsToShuffleDynamicSet.Add(variableLinkNode); //pointer to this node } else { //Debug.WriteLine($"Cannot shuffle tag: {tagToFind}"); } } } } } } var pawnsToShuffle = pawnsToShuffleDirectAttached; // If it's a dynamic object we only want to add it if it's not already attached somewhere else // This can occur if the value to set is also the value used directly foreach (var dp in pawnsToShuffleDynamicSet) { //if (!pawnsToShuffle.Contains(dp)) //{ pawnsToShuffle.Add(dp); //} } if (pawnsToShuffle.Count > 1) { // Now we have a list of all exports that can be shuffled var shufflerList = pawnsToShuffle.ToList(); shufflerList.Shuffle(); // Now we go through the list of var links and look if the linked node is in the list of pawnsToShuffle. // If it is we pull one off the shufflerList and replace the value with that one instead foreach (var vl in variableLinks) { for (int i = 0; i < vl.LinkedNodes.Count; i++) { var existingItem = vl.LinkedNodes[i]; if (pawnsToShuffle.Contains(existingItem)) { var newItem = shufflerList.PullFirstItem(); if (newItem == existingItem) { var prepickCount = shufflerList.Count; newItem = AttemptRepick(shufflerList, newItem, 4, pawnsToShuffle); var postpickCount = shufflerList.Count; if (prepickCount != postpickCount) { // Should not be any count change as we already drew an item Debugger.Break(); } } vl.LinkedNodes[i] = newItem; } } } // The linked nodes are now randomized // Write out the values if (shufflerList.Count != 0) { // This can occur in sequences that have weird duplicates //Debugger.Break(); } SeqTools.WriteVariableLinksToNode(export, variableLinks); Debug.WriteLine($"Randomized {pawnsToShuffle.Count} links in animcutscene in {cutsceneName}, file {Path.GetFileName(export.FileRef.FilePath)}"); return(true); } return(false); }
/// <summary> /// Technically this is not part of Nor (It's EndGm). But it takes place on normandy so users /// will think it is part of the normandy. /// </summary> /// <param name="random"></param> private static void RandomizeRomance() { // Romance is 2 pass: // Pass 1: The initial chances that are not ME1 or Miranda { var romChooserPackage = MEPackageHandler.OpenMEPackage(MERFileSystem.GetPackageFile("BioD_EndGm1_110Romance.pcc")); var romSeq = romChooserPackage.GetUExport(88); var outToRepoint = romChooserPackage.GetUExport(65); //repoint to our switch // Install random switch and point it at the romance log culminations for each // Miranda gets 2 as she has a 50/50 of miranda or lonely shep. var randomSwitch = MERSeqTools.InstallRandomSwitchIntoSequence(romSeq, 7); var outLinks = SeqTools.GetOutboundLinksOfNode(randomSwitch); outLinks[0].Add(new SeqTools.OutboundLink() { InputLinkIdx = 0, LinkedOp = romChooserPackage.GetUExport(28) }); // JACOB outLinks[1].Add(new SeqTools.OutboundLink() { InputLinkIdx = 0, LinkedOp = romChooserPackage.GetUExport(20) }); // GARRUS outLinks[2].Add(new SeqTools.OutboundLink() { InputLinkIdx = 0, LinkedOp = romChooserPackage.GetUExport(30) }); // TALI outLinks[3].Add(new SeqTools.OutboundLink() { InputLinkIdx = 0, LinkedOp = romChooserPackage.GetUExport(29) }); // THANE outLinks[4].Add(new SeqTools.OutboundLink() { InputLinkIdx = 0, LinkedOp = romChooserPackage.GetUExport(27) }); // JACK outLinks[5].Add(new SeqTools.OutboundLink() { InputLinkIdx = 0, LinkedOp = romChooserPackage.GetUExport(52) }); // MIRANDA--| -> Delay into teleport outLinks[6].Add(new SeqTools.OutboundLink() { InputLinkIdx = 0, LinkedOp = romChooserPackage.GetUExport(52) }); // ME1------| -> Delay into teleport SeqTools.WriteOutboundLinksToNode(randomSwitch, outLinks); // Repoint to our randomswitch var penultimateOutbound = SeqTools.GetOutboundLinksOfNode(outToRepoint); penultimateOutbound[0].Clear(); penultimateOutbound[0].Add(new SeqTools.OutboundLink() { InputLinkIdx = 0, LinkedOp = randomSwitch }); // DEBUG ONLY: FORCE LINK //penultimateOutbound[0].Add(new SeqTools.OutboundLink() { InputLinkIdx = 0, LinkedOp = romChooserPackage.GetUExport(27) }); SeqTools.WriteOutboundLinksToNode(outToRepoint, penultimateOutbound); MERFileSystem.SavePackage(romChooserPackage); } // Pass 2: ME1 or Miranda if Pass 1 fell through at runtime { var romChooserPackage = MEPackageHandler.OpenMEPackage(MERFileSystem.GetPackageFile("BioD_EndGm1_110ROMMirranda.pcc")); var romSeq = romChooserPackage.GetUExport(8468); var outToRepoint = romChooserPackage.GetUExport(2354); //repoint to our switch // Install random switch and point it at the romance log culminations for each // Miranda gets 2 as she has a 50/50 of miranda or lonely shep. var randomSwitch = MERSeqTools.InstallRandomSwitchIntoSequence(romSeq, 2); var outLinks = SeqTools.GetOutboundLinksOfNode(randomSwitch); outLinks[0].Add(new SeqTools.OutboundLink() { InputLinkIdx = 0, LinkedOp = romChooserPackage.GetUExport(8432) }); // MIRANDA outLinks[1].Add(new SeqTools.OutboundLink() { InputLinkIdx = 0, LinkedOp = romChooserPackage.GetUExport(8413) }); // ME1 SeqTools.WriteOutboundLinksToNode(randomSwitch, outLinks); // Repoint to our randomswitch var penultimateOutbound = SeqTools.GetOutboundLinksOfNode(outToRepoint); penultimateOutbound[0].Clear(); penultimateOutbound[0].Add(new SeqTools.OutboundLink() { InputLinkIdx = 0, LinkedOp = randomSwitch }); SeqTools.WriteOutboundLinksToNode(outToRepoint, penultimateOutbound); MERFileSystem.SavePackage(romChooserPackage); } }