public static bool DetectAndSkipMiniGameSeqRefs(ExportEntry exp, RandomizationOption option) { if (!CanApplySkip(exp, out var miniGameType)) { return(false); } if (miniGameType == EMinigameSkipType.SeqRef) { // Update the credits var minigameVarLinks = SeqTools.GetVariableLinksOfNode(exp); // Update the Out: Value Remaining to something random. var ovrNode = minigameVarLinks.FirstOrDefault(x => x.LinkDesc == "OUT: Value Remaining")?.LinkedNodes.FirstOrDefault(); if (ovrNode is ExportEntry ovr) { ovr.WriteProperty(new IntProperty(ThreadSafeRandom.Next(1, 2400), "IntValue")); } } else if (miniGameType == EMinigameSkipType.SeqAct) { // Update the credits var minigameVarLinks = SeqTools.GetVariableLinksOfNode(exp); // Update the Remaining Remaining to something random. var ovrNode = minigameVarLinks.FirstOrDefault(x => x.LinkDesc == "Remaining Resources")?.LinkedNodes.FirstOrDefault(); if (ovrNode is ExportEntry ovr) { ovr.WriteProperty(new IntProperty(ThreadSafeRandom.Next(1, 2400), "IntValue")); } } SeqTools.SkipSequenceElement(exp, "Success"); //Success is link 0 return(true); }
public static bool RandomizeExport(ExportEntry export, RandomizationOption option) { if (!CanRandomize(export)) { return(false); } var seqLinks = SeqTools.GetVariableLinksOfNode(export); // Make a new list of sequence vars based on the originals. We will null out items in this list to prevent randomization of them // before we merge back in. var randomizationMask = new List <SeqTools.VarLinkInfo>(seqLinks); for (int i = 0; i < randomizationMask.Count; i++) { var info = randomizationMask[i]; if (!info.LinkedNodes.Any() || !CanLinkBeRandomized(info)) { // blacklisted or no nodes randomizationMask[i] = null; // Null this object continue; } } // Get total amount of nodes that can be randomized. var allNodes = randomizationMask.Where(x => x != null).SelectMany(x => x.LinkedNodes).ToList(); if (allNodes.Count() < 2) { return(false); // Can't do anything as there is not enough nodes to randomize. } // Shuffle the list of nodes that will be installed allNodes.Shuffle(); // Iterate over the randomization mask and use it to determine which varlinks to update for (int i = 0; i < randomizationMask.Count; i++) { var info = randomizationMask[i]; if (info != null) { // Can be changed. As they are just references we can update this list directly info.LinkedNodes.Clear(); info.LinkedNodes.Add(allNodes.PullFirstItem()); } } // Write the updated links out. SeqTools.WriteVariableLinksToNode(export, seqLinks); return(true); }
public bool ApplyUpdate(IMEPackage package, ExportEntry targetExport, Mod installingMod) { if (Utilities.CalculateMD5(new MemoryStream(targetExport.Data)) == EntryMD5) { Log.Information($@"Applying sequence skip: Skipping {targetExport.InstancedFullPath} through on link {OutboundLinkNameToUse}"); SeqTools.SkipSequenceElement(targetExport, outboundLinkName: OutboundLinkNameToUse); } else { Log.Warning(@"Target export MD5 is incorrect. This may be the wrong target export, or it may be already patched. We are reporting that the mod installed, in the event the target was updated."); } return(true); }
public static bool SetupFastStartup(RandomizationOption option) { var entrymenuF = MERFileSystem.GetPackageFile("EntryMenu.pcc"); var entrymenuP = MEPackageHandler.OpenMEPackage(entrymenuF); var skipElem = entrymenuP.GetUExport(86); // should show splash if (!skipElem.ObjectFlags.HasFlag(UnrealFlags.EObjectFlags.DebugPostLoad)) { skipElem.ObjectFlags |= UnrealFlags.EObjectFlags.DebugPostLoad; // mark as modified so subsequent passes don't operate on this SeqTools.SkipSequenceElement(skipElem, outboundLinkIdx: 1); MERFileSystem.SavePackage(entrymenuP); } return(true); }
private static void MakeGarrusDeadly() { // Relay at the end of the DLC var garrusShootSeqFile = MERFileSystem.GetPackageFile(@"BioD_OmgGrA_100Leadup.pcc"); if (garrusShootSeqFile != null && File.Exists(garrusShootSeqFile)) { var garrusSeqP = MEPackageHandler.OpenMEPackage(garrusShootSeqFile); // Chance to shoot Shepard RandSeqVarInt(garrusSeqP.GetUExport(1043), 60, 100); //60 to 100 percent chance // Make garrus shoot faster so he can actually kill the player garrusSeqP.GetUExport(974).WriteProperty(new FloatProperty(3, "PlayRate")); // Lower the damage so its not instant kill garrusSeqP.GetUExport(34).WriteProperty(new FloatProperty(550, "DamageAmount")); garrusSeqP.GetUExport(34).WriteProperty(new FloatProperty(2, "MomentumScale")); // Do not reset the chance to shoot shepard again SeqTools.ChangeOutlink(garrusSeqP.GetUExport(33), 0, 0, 982); // Make garrus damage type very deadly var garrusDamageTypeProps = garrusSeqP.GetUExport(8).GetProperties(); garrusDamageTypeProps.Clear(); // Remove the old props garrusDamageTypeProps.AddOrReplaceProp(new BoolProperty(true, "bImmediateDeath")); garrusDamageTypeProps.AddOrReplaceProp(new BoolProperty(true, "bIgnoreShieldHitLimit")); garrusDamageTypeProps.AddOrReplaceProp(new BoolProperty(true, "bIgnoreShields")); garrusDamageTypeProps.AddOrReplaceProp(new BoolProperty(true, "bIgnoresBleedout")); garrusSeqP.GetUExport(8).WriteProperties(garrusDamageTypeProps); // Make shepard more vulnerable garrusSeqP.GetUExport(1005).WriteProperty(new IntProperty(60, "ValueB")); // Make garrus stand more often garrusSeqP.GetUExport(1039).WriteProperty(new IntProperty(60, "IntValue")); // Make shoot extra free bullets garrusSeqP.GetUExport(1037).WriteProperty(new IntProperty(75, "IntValue")); RandSeqVarInt(garrusSeqP.GetUExport(1042), 1, 4); MERFileSystem.SavePackage(garrusSeqP); } }
/// <summary> /// Finds variable connections that come to this node. /// </summary> /// <param name="node"></param> /// <param name="sequenceElements"></param> /// <returns></returns> public static List <ExportEntry> FindVariableConnectionsToNode(ExportEntry node, List <ExportEntry> sequenceElements) { List <ExportEntry> referencingNodes = new List <ExportEntry>(); foreach (var seqObj in sequenceElements) { if (seqObj == node) { continue; // Skip node pointing to itself } var linkSet = SeqTools.GetVariableLinksOfNode(seqObj); if (linkSet.Any(x => x.LinkedNodes.Any(y => y == node))) { referencingNodes.Add(seqObj); } } return(referencingNodes.Distinct().ToList()); }
/// <summary> /// Removes a sequence element from the graph, by repointing incoming references to the ones referenced by outgoing items on this export. This is a very basic utility, only use it for items with one input and potentially multiple outputs. /// </summary> /// <param name="elementToSkip">Th sequence object to skip</param> /// <param name="outboundLinkIdx">The 0-indexed outbound link that should be attached the preceding entry element, as if this one had fired that link.</param> public static void SkipSequenceElement(ExportEntry elementToSkip, string outboundLinkName = null, int outboundLinkIdx = -1) { if (outboundLinkIdx == -1 && outboundLinkName == null) { throw new Exception(@"SkipSequenceElement() must have an outboundLinkName or an outboundLinkIdx!"); } if (outboundLinkIdx == -1) { var outboundLinkNames = KismetHelper.GetOutboundLinkNames(elementToSkip); outboundLinkIdx = outboundLinkNames.IndexOf(outboundLinkName); } // List of outbound link elements on the specified item we want to skip. These will be placed into the inbound item Debug.WriteLine($@"Attempting to skip {elementToSkip.UIndex} in {elementToSkip.FileRef.FilePath}"); var outboundLinkLists = SeqTools.GetOutboundLinksOfNode(elementToSkip); var inboundToSkippedNode = SeqTools.FindOutboundConnectionsToNode(elementToSkip, SeqTools.GetAllSequenceElements(elementToSkip).OfType <ExportEntry>()); var newTargetNodes = outboundLinkLists[outboundLinkIdx]; foreach (var preNode in inboundToSkippedNode) { // For every node that links to the one we want to skip... var preNodeLinks = GetOutboundLinksOfNode(preNode); foreach (var ol in preNodeLinks) { var numRemoved = ol.RemoveAll(x => x.LinkedOp == elementToSkip); if (numRemoved > 0) { // At least one was removed. Repoint it ol.AddRange(newTargetNodes); } } WriteOutboundLinksToNode(preNode, preNodeLinks); } }
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); }
private static ActorLookup GetLookupInfo(ExportEntry entry, SeqTools.VarLinkInfo varilink) { ActorLookup lookupInfo = new ActorLookup(); if (entry.ClassName == "SeqVar_Player") { // We now look for 'Player' lookupInfo.FindActor = "Player"; entry.WriteProperty(new BoolProperty(true, "bReturnPawns")); // This is required for moved links to work. So just always add it } else { if (varilink.LinkDesc.StartsWith("Node")) { if (entry.ClassName == "BioSeqVar_ObjectFindByTag") { var tag = entry.GetProperty <StrProperty>("m_sObjectTagToFind"); if (tag != null) { lookupInfo.FindActor = tag.Value; } else { // ?? Debug.WriteLine("Could not find object by tag, tag was missing!"); } } else if (entry.ClassName == "SeqVar_Object") { // Pinned object. var resolvedEntry = entry.GetProperty <ObjectProperty>("ObjValue")?.ResolveToEntry(entry.FileRef) as ExportEntry; if (resolvedEntry != null) { // Look for it's tag and use that cause it's what will probably be used in the // conversation var tag = resolvedEntry.GetProperty <NameProperty>("Tag"); if (tag != null) { lookupInfo.FindActor = tag.Value; } else { //Debug.WriteLine("No tag on resolved object! Is it dynamic?"); //lookupInfo.FindActor = entry.GetProperty<NameProperty>("m_nmFindActor").Value; // keep the original value, I guess lookupInfo.CouldNotResolve = true; } } else { //Debug.WriteLine("Could not resolve object! Is it dynamic?"); //lookupInfo.FindActor = entry.GetProperty<NameProperty>("m_nmFindActor").Value; // keep the original value, I guess lookupInfo.CouldNotResolve = true; } } else if (entry.ClassName == "SeqVar_ScopedNamed") { // We have to find an object in the sequence that has the VarName // What a dumb system var findVarName = entry.GetProperty <NameProperty>("FindVarName"); if (findVarName == null) { Debugger.Break(); } var seqObjs = SeqTools.GetAllSequenceElements(entry).OfType <ExportEntry>(); foreach (var seqObj in seqObjs) { var props = seqObj.GetProperties(); var varname = props.GetProp <NameProperty>("VarName"); if (varname != null && varname.Value == findVarName.Value) { // Pinned object. var resolvedEntry = props.GetProp <ObjectProperty>("ObjValue")?.ResolveToEntry(entry.FileRef) as ExportEntry; if (resolvedEntry != null) { // Look for it's tag and use that cause it's what will probably be used in the // conversation var tag = resolvedEntry.GetProperty <NameProperty>("Tag"); if (tag != null) { lookupInfo.FindActor = tag.Value; } else { //Debug.WriteLine("No tag on resolved object! Is it dynamic?"); //lookupInfo.FindActor = entry.GetProperty<NameProperty>("m_nmFindActor").Value; // keep the original value, I guess lookupInfo.CouldNotResolve = true; } } else { //Debug.WriteLine("Could not resolve object! Is it dynamic?"); //lookupInfo.FindActor = entry.GetProperty<NameProperty>("m_nmFindActor").Value; // keep the original value, I guess lookupInfo.CouldNotResolve = true; } break; } } } else { Debug.WriteLine($"Unknown type on Node convo item: {entry.ClassName}"); } } else { switch (varilink.LinkDesc) { // We don't need to do owner as everything for Owner seems to be always pointing // to the input of the SfxSeqAct_StartConversation // So if we change it there, it... in theory should change case "Owner": lookupInfo.FindActor = "Owner"; // Special case. We should not change owner to something else. We should only update what owner now points to // or something... this shit is so confusing break; case "Puppet1_1": lookupInfo.FindActor = new NameReference("Pup1", 2); lookupInfo.FindMode = EActorTrackFindActorMode.ActorTrack_FindActorByNode; break; case "Puppet1_2": lookupInfo.FindActor = new NameReference("Pup1", 3); lookupInfo.FindMode = EActorTrackFindActorMode.ActorTrack_FindActorByNode; break; case "Puppet2_1": lookupInfo.FindActor = new NameReference("Pup2", 2); lookupInfo.FindMode = EActorTrackFindActorMode.ActorTrack_FindActorByNode; break; case "Puppet2_2": lookupInfo.FindActor = new NameReference("Pup2", 3); lookupInfo.FindMode = EActorTrackFindActorMode.ActorTrack_FindActorByNode; break; default: Debugger.Break(); break; } } } return(lookupInfo); }
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); }
/* * private static void VerifyGesturesWork(ExportEntry trackExport) * { * var gestures = RBioEvtSysTrackGesture.GetGestures(trackExport); * var defaultPose = RBioEvtSysTrackGesture.GetDefaultPose(trackExport); * * var gesturesToCheck = gestures.Append(defaultPose).ToList(); * * // Get the containing sequence * var owningSequence = SeqTools.GetParentSequence(trackExport); * while (owningSequence.ClassName != "Sequence") * { * owningSequence = owningSequence.Parent as ExportEntry; * var parSeq = SeqTools.GetParentSequence(owningSequence); * if (parSeq != null) * { * owningSequence = parSeq; * } * } * * var kismetBioDynamicAnimSets = owningSequence.GetProperty<ArrayProperty<ObjectProperty>>("m_aBioDynAnimSets"); * if (kismetBioDynamicAnimSets == null) * { * // We don't have any animsets! * throw new Exception("Track's sequence is missing animsets property!"); * } * * // Get a list of all supported animations * List<Gesture> supportedGestures = new List<Gesture>(); * foreach (var kbdas in kismetBioDynamicAnimSets) * { * var sequenceBioDynamicAnimSet = kbdas.ResolveToEntry(trackExport.FileRef) as ExportEntry; // I don't think these can be imports as they're part of the seq * var associatedset = sequenceBioDynamicAnimSet.GetProperty<ObjectProperty>("m_pBioAnimSetData").ResolveToEntry(trackExport.FileRef); * * } * * // Check all gestures * foreach (var gesture in gesturesToCheck) * { * var bioAnimSet = gesture.GetBioAnimSet(trackExport.FileRef); * * } * * * * } * * internal class TestingBioDynamicAnimSet * { * public NameReference OrigSetName { get; } * public List<string> SupportedGesturesFullPaths { get; } * public IEntry BioAnimSetData { get; } * * internal TestingBioDynamicAnimSet(ExportEntry export) * { * var props = export.GetProperties(); * OrigSetName = props.GetProp<NameProperty>("m_nmOrigSetName").Value; * BioAnimSetData = props.GetProp<ObjectProperty>("m_pBioAnimSetData").ResolveToEntry(export.FileRef); * SupportedGesturesFullPaths = props.GetProp<ArrayProperty<ObjectProperty>>("Sequences").Select(x => x.ResolveToEntry(export.FileRef).InstancedFullPath).ToList(); * } * } */ private static void InstallDynamicAnimSetRefForSeq(ref ExportEntry owningSequence, ExportEntry export, Gesture gesture) { // Find owning sequence if (owningSequence == null) { owningSequence = export; } while (owningSequence.ClassName != "Sequence") { owningSequence = owningSequence.Parent as ExportEntry; var parSeq = SeqTools.GetParentSequence(owningSequence); if (parSeq != null) { owningSequence = parSeq; } } // We have parent sequence data var kismetBioDynamicAnimSets = owningSequence.GetProperty <ArrayProperty <ObjectProperty> >("m_aBioDynAnimSets") ?? new ArrayProperty <ObjectProperty>("m_aBioDynamicAnimSets"); // Check to see if there is any item that uses our bioanimset var bioAnimSet = gesture.GetBioAnimSet(export.FileRef); if (bioAnimSet != null) { ExportEntry kismetBDAS = null; foreach (var kbdas in kismetBioDynamicAnimSets) { var kEntry = kbdas.ResolveToEntry(export.FileRef) as ExportEntry; // I don't think these can be imports as they're part of the seq var associatedset = kEntry.GetProperty <ObjectProperty>("m_pBioAnimSetData").ResolveToEntry(export.FileRef); if (associatedset == bioAnimSet) { // It's this one kismetBDAS = kEntry; break; } } if (kismetBDAS == null) { // We need to generate a new one PropertyCollection props = new PropertyCollection(); props.Add(new NameProperty(gesture.GestureSet, "m_nmOrigSetName")); props.Add(new ArrayProperty <ObjectProperty>("Sequences")); props.Add(new ObjectProperty(bioAnimSet, "m_pBioAnimSetData")); kismetBDAS = ExportCreator.CreateExport(export.FileRef, $"KIS_DYN_{gesture.GestureSet}", "BioDynamicAnimSet", owningSequence); kismetBDAS.indexValue = 0; // Write a blank count of 0 - we will update this in subsequent call // This must be here to ensure parser can read it kismetBDAS.WritePropertiesAndBinary(props, new byte[4]); kismetBioDynamicAnimSets.Add(new ObjectProperty(kismetBDAS)); // Add new export to sequence's list of biodynamicanimsets owningSequence.WriteProperty(kismetBioDynamicAnimSets); } var currentObjs = kismetBDAS.GetProperty <ArrayProperty <ObjectProperty> >("Sequences"); if (currentObjs.All(x => x.Value != gesture.Entry.UIndex)) { // We need to add our item to it currentObjs.Add(new ObjectProperty(gesture.Entry)); var bin = ObjectBinary.From <BioDynamicAnimSet>(kismetBDAS); bin.SequenceNamesToUnkMap[gesture.GestureAnim] = 1; // Not sure what the value should be, or if game actually reads this // FIX IT IF WE EVER FIGURE IT OUT! kismetBDAS.WriteProperty(currentObjs); kismetBDAS.WriteBinary(bin); } } }
public static void RunGame2EmailMerge(GameTarget target) { M3MergeDLC.RemoveMergeDLC(target); var loadedFiles = MELoadedFiles.GetFilesLoadedInGame(target.Game, gameRootOverride: target.TargetPath); // File to base modifications on using IMEPackage pcc = MEPackageHandler.OpenMEPackage(loadedFiles[@"BioD_Nor103_Messages.pcc"]); // Path to Message templates file - different files for ME2/LE2 string ResourcesFilePath = $@"MassEffectModManagerCore.modmanager.emailmerge.{target.Game}.103Message_Template_{target.Game}"; using IMEPackage resources = MEPackageHandler.OpenMEPackageFromStream(Utilities.GetResourceStream(ResourcesFilePath)); // Startup file to place conditionals and transitions into using IMEPackage startup = MEPackageHandler.OpenMEPackageFromStream(Utilities.GetResourceStream( $@"MassEffectModManagerCore.modmanager.mergedlc.{target.Game}.Startup_{M3MergeDLC.MERGE_DLC_FOLDERNAME}.pcc")); var emailInfos = new List <ME2EmailMergeFile>(); var jsonSupercedances = M3Directories.GetFileSupercedances(target, new[] { @".json" }); if (jsonSupercedances.TryGetValue(EMAIL_MERGE_MANIFEST_FILE, out var jsonList)) { jsonList.Reverse(); foreach (var dlc in jsonList) { var jsonFile = Path.Combine(M3Directories.GetDLCPath(target), dlc, target.Game.CookedDirName(), EMAIL_MERGE_MANIFEST_FILE); emailInfos.Add(JsonConvert.DeserializeObject <ME2EmailMergeFile>(File.ReadAllText(jsonFile))); } } // Sanity checks if (!emailInfos.Any() || !emailInfos.SelectMany(e => e.Emails).Any()) { return; } if (emailInfos.Any(e => e.Game != target.Game)) { throw new Exception("ME2 email merge manifest targets incorrect game"); } // Startup File // Could replace this with full instanced path in M3 implementation ExportEntry stateEventMapExport = startup.Exports .First(e => e.ClassName == "BioStateEventMap" && e.ObjectName == "StateTransitionMap"); BioStateEventMap StateEventMap = stateEventMapExport.GetBinaryData <BioStateEventMap>(); ExportEntry ConditionalClass = startup.FindExport($@"PlotManager{M3MergeDLC.MERGE_DLC_FOLDERNAME}.BioAutoConditionals"); #region Sequence Exports // Send message - All email conditionals are checked and emails transitions are triggered ExportEntry SendMessageContainer = pcc.FindExport(@"TheWorld.PersistentLevel.Main_Sequence.Send_Messages"); ExportEntry LastSendMessage = KismetHelper.GetSequenceObjects(SendMessageContainer).OfType <ExportEntry>() .FirstOrDefault(e => { var outbound = KismetHelper.GetOutboundLinksOfNode(e); return(outbound.Count == 1 && outbound[0].Count == 0); }); ExportEntry TemplateSendMessage = resources.FindExport(@"TheWorld.PersistentLevel.Main_Sequence.Send_MessageTemplate"); ExportEntry TemplateSendMessageBoolCheck = resources.FindExport(@"TheWorld.PersistentLevel.Main_Sequence.Send_MessageTemplate_BoolCheck"); // Mark Read - email ints are set to read // This is the only section that does not gracefully handle different DLC installations - DLC_CER is required atm ExportEntry MarkReadContainer = pcc.FindExport(@"TheWorld.PersistentLevel.Main_Sequence.Mark_Read"); ExportEntry LastMarkRead = pcc.FindExport(@"TheWorld.PersistentLevel.Main_Sequence.Mark_Read.DLC_CER"); ExportEntry MarkReadOutLink = KismetHelper.GetOutboundLinksOfNode(LastMarkRead)[0][0].LinkedOp as ExportEntry; KismetHelper.RemoveOutputLinks(LastMarkRead); ExportEntry TemplateMarkRead = resources.FindExport(@"TheWorld.PersistentLevel.Main_Sequence.Mark_ReadTemplate"); ExportEntry TemplateMarkReadTransition = resources.FindExport(@"TheWorld.PersistentLevel.Main_Sequence.Mark_Read_Transition"); // Display Messages - Str refs are passed through to GUI ExportEntry DisplayMessageContainer = pcc.FindExport(@"TheWorld.PersistentLevel.Main_Sequence.Display_Messages"); ExportEntry DisplayMessageOutLink = pcc.FindExport(@"TheWorld.PersistentLevel.Main_Sequence.Display_Messages.SeqCond_CompareBool_0"); ExportEntry LastDisplayMessage = SeqTools.FindOutboundConnectionsToNode(DisplayMessageOutLink, KismetHelper.GetSequenceObjects(DisplayMessageContainer).OfType <ExportEntry>())[0]; KismetHelper.RemoveOutputLinks(LastDisplayMessage); var DisplayMessageVariableLinks = LastDisplayMessage.GetProperty <ArrayProperty <StructProperty> >("VariableLinks"); ExportEntry TemplateDisplayMessage = resources.FindExport(@"TheWorld.PersistentLevel.Main_Sequence.Display_MessageTemplate"); // Archive Messages - Message ints are set to 3 ExportEntry ArchiveContainer = pcc.FindExport(@"TheWorld.PersistentLevel.Main_Sequence.Archive_Message"); ExportEntry ArchiveSwitch = pcc.FindExport(@"TheWorld.PersistentLevel.Main_Sequence.Archive_Message.SeqAct_Switch_0"); ExportEntry ArchiveOutLink = pcc.FindExport( @"TheWorld.PersistentLevel.Main_Sequence.Archive_Message.BioSeqAct_PMCheckConditional_1"); ExportEntry ExampleSetInt = KismetHelper.GetOutboundLinksOfNode(ArchiveSwitch)[0][0].LinkedOp as ExportEntry; ExportEntry ExamplePlotInt = SeqTools.GetVariableLinksOfNode(ExampleSetInt)[0].LinkedNodes[0] as ExportEntry; #endregion int messageID = KismetHelper.GetOutboundLinksOfNode(ArchiveSwitch).Count + 1; int currentSwCount = ArchiveSwitch.GetProperty <IntProperty>("LinkCount").Value; foreach (var emailMod in emailInfos) { string modName = "DLC_MOD_" + emailMod.ModName; foreach (var email in emailMod.Emails) { string emailName = modName + "_" + email.EmailName; // Create send transition int transitionId = WriteTransition(StateEventMap, email.StatusPlotInt); int conditionalId = WriteConditional(email.TriggerConditional); #region SendMessage ////////////// // SendMessage ////////////// // Create seq object var SMTemp = emailMod.InMemoryBool.HasValue ? TemplateSendMessageBoolCheck : TemplateSendMessage; EntryImporter.ImportAndRelinkEntries(EntryImporter.PortingOption.CloneTreeAsChild, SMTemp, pcc, SendMessageContainer, true, new RelinkerOptionsPackage(), out var outSendEntry); var newSend = outSendEntry as ExportEntry; // Set name, comment, add to sequence newSend.ObjectName = new NameReference(emailName); KismetHelper.AddObjectToSequence(newSend, SendMessageContainer); KismetHelper.SetComment(newSend, emailName); if (target.Game == MEGame.ME2) { newSend.WriteProperty(new StrProperty(emailName, "ObjName")); } // Set Trigger Conditional var pmCheckConditionalSM = newSend.GetChildren() .FirstOrDefault(e => e.ClassName == "BioSeqAct_PMCheckConditional" && e is ExportEntry); if (pmCheckConditionalSM is ExportEntry conditional) { conditional.WriteProperty(new IntProperty(conditionalId, "m_nIndex")); KismetHelper.SetComment(conditional, "Time for " + email.EmailName + "?"); } // Set Send Transition var pmExecuteTransitionSM = newSend.GetChildren() .FirstOrDefault(e => e.ClassName == "BioSeqAct_PMExecuteTransition" && e is ExportEntry); if (pmExecuteTransitionSM is ExportEntry transition) { transition.WriteProperty(new IntProperty(transitionId, "m_nIndex")); KismetHelper.SetComment(transition, "Send " + email.EmailName + " message."); } // Set Send Transition if (emailMod.InMemoryBool.HasValue) { var pmCheckStateSM = newSend.GetChildren() .FirstOrDefault(e => e.ClassName == "BioSeqAct_PMCheckState" && e is ExportEntry); if (pmCheckStateSM is ExportEntry checkState) { checkState.WriteProperty(new IntProperty(emailMod.InMemoryBool.Value, "m_nIndex")); KismetHelper.SetComment(checkState, "Is " + emailMod.ModName + " installed?"); } } // Hook up output links KismetHelper.CreateOutputLink(LastSendMessage, "Out", newSend); LastSendMessage = newSend; #endregion #region MarkRead /////////// // MarkRead /////////// // Create seq object var MRTemp = email.ReadTransition.HasValue ? TemplateMarkReadTransition : TemplateMarkRead; EntryImporter.ImportAndRelinkEntries(EntryImporter.PortingOption.CloneTreeAsChild, MRTemp, pcc, MarkReadContainer, true, new RelinkerOptionsPackage(), out var outMarkReadEntry); var newMarkRead = outMarkReadEntry as ExportEntry; // Set name, comment, add to sequence newMarkRead.ObjectName = new NameReference(emailName); KismetHelper.AddObjectToSequence(newMarkRead, MarkReadContainer); KismetHelper.SetComment(newMarkRead, emailName); if (target.Game == MEGame.ME2) { newMarkRead.WriteProperty(new StrProperty(emailName, "ObjName")); } // Set Plot Int var storyManagerIntMR = newMarkRead.GetChildren() .FirstOrDefault(e => e.ClassName == "BioSeqVar_StoryManagerInt" && e is ExportEntry); if (storyManagerIntMR is ExportEntry plotIntMR) { plotIntMR.WriteProperty(new IntProperty(email.StatusPlotInt, "m_nIndex")); KismetHelper.SetComment(plotIntMR, email.EmailName); } if (email.ReadTransition.HasValue) { var pmExecuteTransitionMR = newMarkRead.GetChildren() .FirstOrDefault(e => e.ClassName == "BioSeqAct_PMExecuteTransition" && e is ExportEntry); if (pmExecuteTransitionMR is ExportEntry transitionMR) { transitionMR.WriteProperty(new IntProperty(email.ReadTransition.Value, "m_nIndex")); KismetHelper.SetComment(transitionMR, "Trigger " + email.EmailName + " read transition"); } } // Hook up output links KismetHelper.CreateOutputLink(LastMarkRead, "Out", newMarkRead); LastMarkRead = newMarkRead; #endregion #region DisplayEmail //////////////// // Display Email //////////////// // Create seq object EntryImporter.ImportAndRelinkEntries(EntryImporter.PortingOption.CloneTreeAsChild, TemplateDisplayMessage, pcc, DisplayMessageContainer, true, new RelinkerOptionsPackage(), out var outDisplayMessage); var newDisplayMessage = outDisplayMessage as ExportEntry; // Set name, comment, variable links, add to sequence newDisplayMessage.ObjectName = new NameReference(emailName); KismetHelper.AddObjectToSequence(newDisplayMessage, DisplayMessageContainer); newDisplayMessage.WriteProperty(DisplayMessageVariableLinks); KismetHelper.SetComment(newDisplayMessage, emailName); if (target.Game == MEGame.ME2) { newDisplayMessage.WriteProperty(new StrProperty(emailName, "ObjName")); } var displayChildren = newDisplayMessage.GetChildren(); // Set Plot Int var storyManagerIntDE = displayChildren.FirstOrDefault(e => e.ClassName == "BioSeqVar_StoryManagerInt" && e is ExportEntry); if (storyManagerIntDE is ExportEntry plotIntDE) { plotIntDE.WriteProperty(new IntProperty(email.StatusPlotInt, "m_nIndex")); } // Set Email ID var emailIdDE = displayChildren.FirstOrDefault(e => e.ClassName == "SeqVar_Int" && e is ExportEntry); if (emailIdDE is ExportEntry EmailIDDE) { EmailIDDE.WriteProperty(new IntProperty(messageID, "IntValue")); } // Set Title StrRef var titleStrRef = displayChildren.FirstOrDefault(e => e.ClassName == "BioSeqVar_StrRef" && e is ExportEntry ee && ee.GetProperty <NameProperty>("VarName").Value == "Title StrRef"); if (titleStrRef is ExportEntry Title) { Title.WriteProperty(new StringRefProperty(email.TitleStrRef, "m_srValue")); } // Set Description StrRef var descStrRef = displayChildren.FirstOrDefault(e => e.ClassName == "BioSeqVar_StrRef" && e is ExportEntry ee && ee.GetProperty <NameProperty>("VarName").Value == "Desc StrRef"); if (descStrRef is ExportEntry Desc) { Desc.WriteProperty(new StringRefProperty(email.DescStrRef, "m_srValue")); } // Hook up output links KismetHelper.CreateOutputLink(LastDisplayMessage, "Out", newDisplayMessage); LastDisplayMessage = newDisplayMessage; #endregion #region ArchiveEmail //////////////// // Archive Email //////////////// var NewSetInt = EntryCloner.CloneEntry(ExampleSetInt); KismetHelper.AddObjectToSequence(NewSetInt, ArchiveContainer); KismetHelper.CreateOutputLink(NewSetInt, "Out", ArchiveOutLink); KismetHelper.CreateNewOutputLink(ArchiveSwitch, "Link " + (messageID - 1), NewSetInt); var NewPlotInt = EntryCloner.CloneEntry(ExamplePlotInt); KismetHelper.AddObjectToSequence(NewPlotInt, ArchiveContainer); NewPlotInt.WriteProperty(new IntProperty(email.StatusPlotInt, "m_nIndex")); NewPlotInt.WriteProperty(new StrProperty(emailName, "m_sRefName")); var linkedVars = SeqTools.GetVariableLinksOfNode(NewSetInt); linkedVars[0].LinkedNodes = new List <IEntry>() { NewPlotInt }; SeqTools.WriteVariableLinksToNode(NewSetInt, linkedVars); messageID++; currentSwCount++; #endregion } } KismetHelper.CreateOutputLink(LastMarkRead, "Out", MarkReadOutLink); KismetHelper.CreateOutputLink(LastDisplayMessage, "Out", DisplayMessageOutLink); ArchiveSwitch.WriteProperty(new IntProperty(currentSwCount, "LinkCount")); stateEventMapExport.WriteBinary(StateEventMap); // Save Messages file into DLC var cookedDir = Path.Combine(M3Directories.GetDLCPath(target), M3MergeDLC.MERGE_DLC_FOLDERNAME, target.Game.CookedDirName()); var outMessages = Path.Combine(cookedDir, @"BioD_Nor103_Messages.pcc"); pcc.Save(outMessages); // Save Startup file into DLC var startupF = Path.Combine(cookedDir, $@"Startup_{M3MergeDLC.MERGE_DLC_FOLDERNAME}.pcc"); startup.Save(startupF); }
/// <summary> /// Is the specified node assigned to a BioPawn or subclass of? /// </summary> /// <param name="sequenceObj">The object that we are checking that should be ignored for references (like conversation start)</param> /// <param name="vlNode">The node to check</param> /// <param name="sequenceElements">List of nodes in the sequence</param> /// <returns></returns> public static bool IsAssignedBioPawn(ExportEntry sequenceObj, ExportEntry vlNode, List <ExportEntry> sequenceElements) { var inboundConnections = SeqTools.FindVariableConnectionsToNode(vlNode, sequenceElements); foreach (var sequenceObject in inboundConnections) { if (sequenceObject == sequenceObj) { continue; // Obviously we reference this node } // Is this a 'SetObject' that is assigning the value? if (sequenceObject.InheritsFrom("SequenceAction") && sequenceObject.ClassName == "SeqAct_SetObject" && sequenceObject != sequenceObj) { //check if target is my node var referencingVarLinks = SeqTools.GetVariableLinksOfNode(sequenceObject); var targetLink = referencingVarLinks.FirstOrDefault(x => x.LinkDesc == "Target"); // What is the target node? if (targetLink != null) { //see if target is node we are investigating for setting. foreach (var potentialTarget in targetLink.LinkedNodes.OfType <ExportEntry>()) { if (potentialTarget == vlNode) { // There's a 'SetObject' with Target of the attached variable to our interp cutscene // That means something is 'setting' the value of this // We need to inspect what it is to see if we can shuffle it //Debug.WriteLine("Found a setobject to variable linked item on a sequence"); //See what value this is set to. If it inherits from BioPawn we can use it in the shuffling. var pointedAtValueLink = referencingVarLinks.FirstOrDefault(x => x.LinkDesc == "Value"); if (pointedAtValueLink != null && pointedAtValueLink.LinkedNodes.Count == 1) // Only 1 item being set. More is too complicated { var linkedNode = pointedAtValueLink.LinkedNodes[0] as ExportEntry; var linkedNodeType = linkedNode.GetProperty <ObjectProperty>("ObjValue"); if (linkedNodeType != null) { var linkedNodeData = sequenceObj.FileRef.GetUExport(linkedNodeType.Value); if (linkedNodeData.IsA("BioPawn")) { //We can shuffle this item. // We write the property to the node so if it's not assigned at runtime (like on gender check) it still can show something. // Cutscene will still be goofed up but will instead show something instead of nothing linkedNode.WriteProperty(linkedNodeType); //Debug.WriteLine("Adding shuffle item: " + objRef.Value); // Original value below was 'linkedNode' which i'm pretty sure is the value that would be assigned, not the actual object that holds that value oncea assigned 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); } }