Matinee Replacement Script

#Kismet
Modified @ 2024-11-01 09:59:21

A simple script that allows you to replace a Matinee's data, and its groups.


NOTE: This is not a kismet node, but this script is used for manipulating kismet-made cutscenes in existing maps.

To use this, make a class that extends the _Base class. In the Editor, copy your replacement's Matinee Data variable to clipboard and paste it in your class's DefaultProperties. Then assign this object to the ReplacementInterpData variable. When you've found your Matinee and are ready to replace it, just call the DoReplaceMatinee() static function on your new custom class.

Optionally, you can also change/add actors connected to your Matinee's groups, by overriding the DoReplaceMatinee() function. You can take a look at the attached example to see how this is done.

Finding the Matinee you want to replace:

This script expects you to provide a reference to the Matinee node you want to replace. Unfortuately, the method for finding a specific Matinee varies on a case-by-case basis. However, here's some tips you can loosely follow:

  1. The starting point for most kismet searches is WorldInfo.GetGameSequence(), which returns the root Sequence of the current map.

  2. Sequence::FindSeqObjectsByClass() returns all kismet nodes/variables of a specific class, while also being able to search through Sequences recursively. More often than not, this function will be your best friend - unless you're at map startup. If you need to run this at the VERY beginning of the level, I've made a custom version of it here.

  3. If that function is not enough, in the Sequence class you'll also find the SequenceObjects array, which has everything that sequence directly contains. This includes nodes or other sub-sequences. With this, you can make custom recursive searches for whatever specific needs you have.

  4. When dealing with maps that involve streaming, it's important to keep in mind that the root sequences of streamed levels are loaded as sub-sequences of the main level's. As such, the GetGameSequence() function mentioned above will contain ALL kismet from ALL levels involved:

Imgur

  1. One easy, though unstable way to identify a node is by using its object Name. I say unstable, because name uniqueness is only guaranteed against nodes in the same sequence, not the entire sequence tree! So if you want to use this method, try also comparing the name of the parent sequence, accessing it like so: SomeNode.ParentSequence.Name. Note that this assumes that all sequences in the tree have the distinct names, which isn't always the case!

  2. A more reliable, but significantly harder way of identifying a node is though context. Nodes are linked together via one-way connections in OutputLinks or VariableLinks, so searching for something that's placed earlier in a chain and/or advancing the chain to look for any more unique details helps. Careful with non-AlwaysLoaded classes, though!

  3. Named or External variable connections get optimized at runtime, by turning them into direct connections. So there's no use looking for those, unfortunately.

Drew_MatineeReplacement_Base.uc

[RAW] [Download]

class Drew_MatineeReplacement_Base extends Object;
 
var InterpData ReplacementInterpData;
 
// Returns: VariableLinks index.
static function int FindOrCreateMatineeGroupVariableLink(SeqAct_Interp Matinee, String Group)
{
    local int i;
    i = Matinee.VariableLinks.Find('LinkDesc', Group);
    if (i == INDEX_NONE)
    {
        i = Matinee.VariableLinks.Length;
        Matinee.VariableLinks.Add(1);
        Matinee.VariableLinks[i].LinkDesc = Group;
        Matinee.VariableLinks[i].ExpectedType = class'SeqVar_Object';
        Matinee.VariableLinks[i].MinVars = 0;
    }
    return i;
}
 
static function ClearObjectsForGroup(SeqAct_Interp Matinee, int VariableLink)
{
    Matinee.VariableLinks[VariableLink].LinkedVariables.Length = 0;
}
 
static function AppendObjectToGroup(SeqAct_Interp Matinee, int VariableLink, Object Obj)
{
    local SeqVar_Object SeqVar;
    SeqVar = new class'SeqVar_Object';
    SeqVar.SetObjectValue(Obj);
    Matinee.VariableLinks[VariableLink].LinkedVariables.AddItem(SeqVar);
}
 
static function SetMatineeData(SeqAct_Interp Matinee)
{
    local int i;
    i = Matinee.VariableLinks.Find('LinkDesc', "Data");
    if (i == INDEX_NONE) return;
    Matinee.VariableLinks[i].LinkedVariables[0] = default.ReplacementInterpData;
}
 
static function DoReplaceMatinee(SeqAct_Interp Matinee)
{
    SetMatineeData(Matinee);
}
 
Drew_MatineeReplacement_Example.uc

[RAW] [Download]

/**
 * This class was used in a mod that replaces the unused mail room Mission Intro, but I left it here as an example.
 * Note, this script does not automatically find the appropriate matinee, as the method for finding one varies on a
 * case-by-case basis. This script assumes you already have a pointer to the matinee you want to replace.
 */
class Drew_MatineeReplacement_Example extends Drew_MatineeReplacement_Base;
 
var Vector Cam1Loc, Cam2Loc;
var Rotator Cam1Rot, Cam2Rot;
var float Cam1FOV, Cam2FOV;
 
static function DoReplaceMatinee(SeqAct_Interp Matinee)
{
    local WorldInfo WorldInfo;
    local CameraActor Cam;
    local int VariableLink;
 
    Super.DoReplaceMatinee(Matinee);
 
    WorldInfo = class'WorldInfo'.static.GetWorldInfo();
 
    // Spawn a camera, and assign it to the "Cam" group.
    Cam = WorldInfo.Spawn(class'DynamicCameraActor',,, default.Cam1Loc, default.Cam1Rot,, True);
    Cam.FOVAngle = default.Cam1FOV;
    VariableLink = FindOrCreateMatineeGroupVariableLink(Matinee, "Cam");
    ClearObjectsForGroup(Matinee, VariableLink);
    AppendObjectToGroup(Matinee, VariableLink, Cam);
 
    // Spawn a camera, and assign it to the "Cam2" group.
    Cam = WorldInfo.Spawn(class'DynamicCameraActor',,, default.Cam2Loc, default.Cam2Rot,, True);
    Cam.FOVAngle = default.Cam2FOV;
    VariableLink = FindOrCreateMatineeGroupVariableLink(Matinee, "Cam2");
    ClearObjectsForGroup(Matinee, VariableLink);
    AppendObjectToGroup(Matinee, VariableLink, Cam);
}
 
defaultproperties
{
    Cam1Loc = (X=1597.237671,Y=1684.752075,Z=482.639191)
    Cam1Rot = (Pitch=-3872,Yaw=-644752,Roll=0)
    Cam1FOV = 60
    Cam2Loc = (X=1403.385864,Y=1869.951660,Z=476.325104)
    Cam2Rot = (Pitch=-1120,Yaw=-4848,Roll=0)
    Cam2FOV = 90
 
    // Copied from Ed, with a few touch-ups: removing redundant data like Name and ObjectArchetype, and other edits to make VSCode happy.
    Begin Object Class=InterpData Name=InterpData_0
        Begin Object Class=InterpGroup Name=InterpGroup_1
            Begin Object Class=InterpTrackMove Name=InterpTrackMove_1
                PosTrack=(Points=((InVal=1.000000,OutVal=(X=0.000000,Y=-0.000244,Z=0.000000),InterpMode=CIM_CurveBreak),(InVal=6.000000,OutVal=(X=-19.538086,Y=-138.447021,Z=-0.381470),InterpMode=CIM_CurveBreak)))
                EulerTrack=(Points=((InVal=1.000000,InterpMode=CIM_CurveBreak),(InVal=6.000000,OutVal=(X=-1.565552,Y=4.537354,Z=-14.144897),InterpMode=CIM_CurveBreak)))
                LookupTrack=(Points=((Time=1.000000),(Time=6.000000)))
                MoveFrame=IMF_RelativeToInitial
            End Object
            InterpTracks(0)=InterpTrackMove_1
            GroupName="Cam2"
            GroupColor=(B=151,G=186,R=0,A=255)
        End Object
        Begin Object Class=InterpGroupDirector Name=InterpGroupDirector_0
            Begin Object Class=InterpTrackDirector Name=InterpTrackDirector_0
                CutTrack(0)=(TargetCamGroup="Cam",ShotNumber=10)
                CutTrack(1)=(Time=2.500000,TargetCamGroup="Cam2",ShotNumber=20)
            End Object
            InterpTracks(0)=InterpTrackDirector_0
            GroupColor=(B=109,G=0,R=212,A=255)
        End Object
        Begin Object Class=InterpGroup Name=InterpGroup_0
            Begin Object Class=InterpTrackMove Name=InterpTrackMove_0
                PosTrack=(Points=((InterpMode=CIM_CurveBreak),(InVal=2.500000,OutVal=(X=-52.000732,Y=0.000244,Z=-0.000122),InterpMode=CIM_CurveBreak)))
                EulerTrack=(Points=((InterpMode=CIM_CurveBreak),(InVal=2.500000,InterpMode=CIM_CurveBreak)))
                LookupTrack=(Points=((Time=0),(Time=2.500000)))
                MoveFrame=IMF_RelativeToInitial
            End Object
            InterpTracks(0)=InterpTrackMove_0
            GroupName="Cam"
            GroupColor=(B=145,G=191,R=0,A=255)
        End Object
        InterpLength=10.000000
        InterpGroups(0)=InterpGroup_0
        InterpGroups(1)=InterpGroupDirector_0
        InterpGroups(2)=InterpGroup_1
        //SelectedFilter=InterpFilter'Engine.Default__InterpData:FilterAll'
        EdSectionEnd=6.000000
        ObjInstanceVersion=1
    End Object
    ReplacementInterpData = InterpData_0
}