Beispiel #1
0
        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);
            }
        }
Beispiel #2
0
        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);
             * }*/
        }
Beispiel #3
0
        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
                        }
                    }
                }
            }
        }
Beispiel #4
0
        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);
            }
        }