
(v0.5)
Flow Control for Kismet
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.
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; }
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=" ") }
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; }
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; }
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; }
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) }
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." }