Flow Control for Kismet

#Kismet
Modified @ 2025-09-10 11:09:51

A few nodes to make flow control easier within Kismet.


Disclaimer(s)


I may update this with more nodes in the future.

Do not connect multiple things to a single output at any point in your Loop Body. This will lead to undefined behaviour. If the two branches meet together again, it will crash the editor.

Technical Details

Whenever a flow control node is activated for the first time, it recursively searches Loop Body to find the end of the chain (or another flow control node, which it will then instruct to do the same). When a node has multiple outputs, both branches have an end, which causes both ends to be connected back to the flow control node. Ultimately, this may mean the node thinks an iteration has finished twice, when it has only done so once.

If the two branches meet again, the end node of the main branch may already be connected by the first search, causing the second search to process the flow control node again, resulting in an infinite loop. The editor detects this infinite loop and crashes with an empty message box.

Nodes


For Loop


Repeats Loop Body Last Index - First Index times, starting at First Index and ending at Last Index (inclusive). Has an Index output to get the index of the current iteration.

If Last Index < First Index or First Index > Last Index when the node is activated, Completed is activated immediately and the loop body isn't considered.

While Loop


Keeps running Loop Body repeatedly until Condition is false. A safety limit can be found in the properties (this is 100 by default). Set this safety limit to 0 to keep running the loop endlessly. If you do not put a delay in the loop or break out of the loop at some point, Kismet will complain about having reached the maximum execution count OR the editor will crash.

If Condition is false when the node is activated, Completed is activated immediately and the loop body isn't considered.

Continue


Ends this iteration and forces the upper loop to start a new iteration. If there is no upper loop, it does nothing.

Break


Ends this iteration and forces the upper loop to stop iterating and call Completed. If there is no upper loop, it does nothing.

=>


This node does nothing and simply exists as an organizational tool. Similar to the Continue node from basegame, but looks cleaner.

LK_SeqFlow_While.uc

[RAW] [Download]

class LK_SeqFlow_While extends LK_SeqFlow_Loop;
 
var() bool Condition;
var() int SafetyLimit; // 0 for no safety limit.
var int Counter;
 
public event Activated()
{
    Super.Activated();
 
    // break / abort is prioritized
    if(InputLinks[BreakInputIdx].bHasImpulse)
    {
        ForceActivateOutput(CompletedOutputIdx);
    }
    else if(InputLinks[MainInputIdx].bHasImpulse)
    {
        PublishLinkedVariableValues();
 
        if(!Condition)
        {
            ForceActivateOutput(CompletedOutputIdx);
            return;
        }
 
        Counter = 0;
 
        ForceActivateOutput(LoopBodyIdx);
    }
    else if(InputLinks[InternalLoopInputIdx].bHasImpulse)
    {
        PublishLinkedVariableValues();
        if((Counter < SafetyLimit || SafetyLimit == 0) && Condition)
        {
            Counter++;
            ForceActivateOutput(LoopBodyIdx);
        }
        else
        {
            ForceActivateOutput(CompletedOutputIdx);
        }
    }
}
 
defaultproperties
{
    ObjName = "While Loop"
 
    VariableLinks.Empty
    VariableLinks(0)=(LinkDesc="Condition",ExpectedType=class'SeqVar_Bool',bWriteable=false, PropertyName=Condition)
    SafetyLimit = 100;
}
LK_Redirector.uc

[RAW] [Download]

class LK_Redirector extends SequenceOp;
 
defaultproperties
{
    ObjName = "=>"
    ObjCategory = "Organization"
    DrawHeight = 1
    DrawWidth = 1
    VariableLinks.Empty
 
    //bCallHandler = false;
    ObjColor = (R=0,G=0,B=0,A=255);
    InputLinks.Empty
    InputLinks(0) = (LinkDesc=" ")
    OutputLinks.Empty
    OutputLinks(0) = (LinkDesc=" ")
}
LK_SeqAct_FlowControl.uc

[RAW] [Download]

class LK_SeqAct_FlowControl extends SequenceAction
    abstract;
 
var LK_SeqFlow_Loop UpperNode;
 
public function NotifiedByUpper(LK_SeqFlow_Loop Op)
{
    UpperNode = Op;
}
 
defaultproperties
{
    ObjColor = (R=46,G=46,B=46,A=255)
    ObjCategory = "Flow Control"
    bAutoActivateOutputLinks=false
 
    VariableLinks.Empty;
}
LK_SeqFlow_Break.uc

[RAW] [Download]

class LK_SeqFlow_Break extends LK_SeqAct_FlowControl;
 
public event Activated()
{
    if(UpperNode == None) return;
    UpperNode.ForceActivateInput(UpperNode.BreakInputIdx);
}
 
defaultproperties
{
   // ObjColor = (R=227,G=20,B=20,A=255)
    ObjName = "Break";
    OutputLinks.Empty;
}
LK_SeqFlow_Continue.uc

[RAW] [Download]

class LK_SeqFlow_Continue extends LK_SeqAct_FlowControl;
 
public event Activated()
{
    if(UpperNode == None) return;
    UpperNode.ForceActivateInput(UpperNode.InternalLoopInputIdx);
}
 
defaultproperties
{
    //ObjColor = (R=227,G=20,B=20,A=255)
    ObjCategory = "Flow Control"
    bAutoActivateOutputLinks=false
    OutputLinks.Empty; // Has no outputs, this node signifies the end of the chain
 
    ObjName = "Continue"
 
    VariableLinks.Empty;
}
LK_SeqFlow_For.uc

[RAW] [Download]

class LK_SeqFlow_For extends LK_SeqFlow_Loop;
 
var int Index;
var() int FirstIndex;
var() int LastIndex;
 
public event Activated()
{
    Super.Activated();
 
    // Break / abort is prioritized
    if(InputLinks[BreakInputIdx].bHasImpulse)
    {
        ForceActivateOutput(CompletedOutputIdx);
    }
    else if(InputLinks[MainInputIdx].bHasImpulse)
    {
        if(FirstIndex > LastIndex || LastIndex < FirstIndex || OutputLinks[0].Links.Length == 0)
        {
            ForceActivateOutput(CompletedOutputIdx);
            return;
        }
 
        Index = FirstIndex;
 
        PopulateLinkedVariableValues();
 
        ForceActivateOutput(LoopBodyIdx);
    }
    else if(InputLinks[InternalLoopInputIdx].bHasImpulse)
    {
        if(Index < LastIndex)
        {
            Index++;
            PopulateLinkedVariableValues();
            ForceActivateOutput(LoopBodyIdx);
        }
        else
        {
            ForceActivateOutput(CompletedOutputIdx);
        }
    }
}
 
defaultproperties
{
    ObjName = "For Loop"
    VariableLinks.Empty
    VariableLinks(0) = (LinkDesc="Index", ExpectedType=class'SeqVar_Int', bWriteable=true, PropertyName=Index, MaxVars=1)
    VariableLinks(1) = (LinkDesc="First Index", ExpectedType=class'SeqVar_Int', bWriteable=false, PropertyName=FirstIndex, bHidden=true)
    VariableLinks(2) = (LinkDesc="Last Index", ExpectedType=class'SeqVar_Int', bWriteable=false, PropertyName=LastIndex, bHidden=true)
}
LK_SeqFlow_Loop.uc

[RAW] [Download]

class LK_SeqFlow_Loop extends LK_SeqAct_FlowControl
    abstract;
 
var const int InternalLoopInputIdx;
var const int MainInputIdx;
var const int CompletedOutputIdx;
var const int LoopBodyIdx;
var const int BreakInputIdx;
var bool bHasProcessedChain;
 
public event Activated()
{
    if(InputLinks[MainInputIdx].bHasImpulse && !bHasProcessedChain)
    {
        ProcessLoopBody();
        bHasProcessedChain = true;
    }
}
 
function ProcessLoopBody()
{
    local int i;
 
    for(i = 0; i < OutputLinks[LoopBodyIdx].Links.Length; i++)
    {
        RecursiveProcessChain(OutputLinks[LoopBodyIdx].Links[i].LinkedOp, self);
    }
}
 
function RecursiveProcessChain(SequenceOp Op, LK_SeqFlow_Loop UpperCaller)
{
    local int i, j;
    local SeqOpOutputInputLink l;
 
    if(LK_SeqAct_FlowControl(Op) != None && Op != self)
    {
        LK_SeqAct_FlowControl(Op).NotifiedByUpper(self);
        return;
    }
 
    for(i = 0; i < Op.OutputLinks.Length; i++)
    {
        if(Op.OutputLinks[i].Links.Length == 0)
        {
            l.LinkedOp = UpperCaller;
            l.InputLinkIdx = UpperCaller.InternalLoopInputIdx;
            Op.OutputLinks[i].Links.AddItem(l);
            continue;
        }
 
        for(j = 0; j < Op.OutputLinks[i].Links.Length; j++)
        {
            RecursiveProcessChain(Op.OutputLinks[i].Links[j].LinkedOp, UpperCaller);
        }
    }
}
 
public function NotifiedByUpper(LK_SeqFlow_Loop Op)
{
    local SeqOpOutputInputLink l;
    local int i;
 
    Super.NotifiedByUpper(Op);
 
    ProcessLoopBody();
    bHasProcessedChain = true;
 
    if(OutputLinks[CompletedOutputIdx].Links.Length == 0)
    {
        l.LinkedOp = Op;
        l.InputLinkIdx = Op.InternalLoopInputIdx;
        OutputLinks[CompletedOutputIdx].Links.AddItem(l);
    }
    else
    {
        for(i = 0; i < OutputLinks[CompletedOutputIdx].Links.Length; i++)
        {
            RecursiveProcessChain(OutputLinks[CompletedOutputIdx].Links[i].LinkedOp, UpperNode);
        }
    }
}
 
defaultproperties
{
    OutputLinks.Empty
    OutputLinks(0) = (LinkDesc="Loop Body")
    OutputLinks(1) = (LinkDesc="Completed")
    LoopBodyIdx = 0;
    CompletedOutputIdx = 1;
 
    InputLinks.Empty;
    InputLinks(0) = (LinkDesc="In")
    InputLinks(1) = (LinkDesc="Loop Input (DO NOT USE)", bHidden=true)
    InputLinks(2) = (LinkDesc="Break", bHidden=true) // Don't connect to this, use the Break node instead. (Or do and experience the pain of kismetti)
    InternalLoopInputIdx = 1;
    MainInputIdx = 0;
    BreakInputIdx = 2;
 
    ObjComment = "Use the Continue and Break nodes for additional control within this scope."
}