At the Limits of RuneScript

By Phule There are a few things the RuneScript engine can’t handle, in some cases, even though it is supposed to be able to do so. Fortunately, there are usually workarounds. For specific help, check at the Runesword forums: http://www.runesword.com/forums/

Having a Trigger Move Itself

There is no MoveTrigger statement, as there is with MoveItem and MoveCreature. You cannot directly move a trigger in an arbitrary fashion. You can move a prepared trigger with the CopyTrigger statement, but you cannot move TriggerA from CreatureA to CreatureB as you could with an item. There isn’t a statement for it. This isn't a bug; it's just a limitation of the script.

There is a workaround: triggers are almost always inside something, and you can call a trigger remotely. This can produce the seeming of moving a trigger in an arbitrary fashion: you place the trigger which does the work inside of a creature or item, place that item inside of a simple wrapping trigger which simply finds and calls the working trigger in a creature or item (as appropriate) that is under itself. You then give the working trigger a copy of the wrapper as a sub-trigger (minus the creature/item that holds the working trigger), copy the working trigger’s copy of the wrapper into the destination using CopyTrigger, find the wrapper in the destination object, find the wrapper in the active object, and move (or move with copy, as appropriet) the creature/item holding the working trigger into the destination wrapper. You may also want to destroy the original wrapper when you are done. It gets pretty complicated, but it is the only way to make a disease that will preserve itself intact through an infinite number of transmissions. For an example, download Myne and take a look at the were creatures, specifically the Infect trigger.

The Tick Limit

There is a limit to how many lines of a trigger can be called on any given invocation of that trigger: 5000. The count is from Debug’s perspective: if you have a trigger that says:
Let Local.IntegerA = Pos.0
While Local.IntegerA > Neg.1
Put Pos.1 + Local.IntegerA Into Local.IntegerA
Next

It will stop when Local.IntegerA is holding 2,499. Every line counts, just as it shows up in Debug. This includes lines that don’t do anything, such as comments. THIS IS NOT A BUG. It is a safety feature – it prevents infinite loops from freezing the game, by terminating them. However, this can be a problem if you are making a script that is unusually complicated.

The workarounds are primarily based on remembering that the 5000 tick limit is per invocation of the trigger. If you are bumping up against the tick limit, move whatever you can into sub-triggers, and call those. If that isn’t workable, do the unthinkable and eliminate the comments. If that still isn’t enough, then you can use persistent data fields, such as the global variables or a trigger’s comment field, to store where the trigger is, code a shortcut to that point in the working trigger, and then wrap the whole thing in another trigger which will continue to call the working trigger until it says it is finished. This can extend the Tick Limit considerably, but can become extremely complicated; this last solution is best suited for triggers where the trigger looks at something once and move on without looking back.

The Nesting Limit

There is a bug with loops that prevents them from operating perfectly when they are nested beyond degree 2. For some as yet unknown reason, when you go beyond that limit, it starts messing up if you have anything between the Next statement that closes one loop and the Next statement that closes the loop the previous loop is in.

There are a couple of workarounds – the first is sub-triggers; the loop nesting issue only applies when the loop nesting is contained within a single trigger. If you are doing something to every creature in the party, for instance, you can have the primary trigger loop through everyone in the party, set CreatureTarget to the creature you are currently working on, and call a sub-trigger to specifically deal with CreatureTarget. This works because CreatureTarget is a global pointer – most aren’t. For objects you don’t have a global pointer for, you can simulate it by counting under some system to the onject you are working with, and passing that number for the object to the trigger using one of the global integers, and then have the sub-trigger find the corresponding object, and work with it.

The second workaround is careful use of the Label, Branch, and If statements to duplicate a While loop artificially (be warned: you must “clean up” the EndIf statements from the If you are branching out of by branching into an If/EndIf pair at the same depth; if you don’t, you will fill the stack up with garbage, and it will crash the trigger if let run too long).

The third known workaround is to not put anything between the Next statements in the various loops. This can likely mean duplicating a fair amount of code, but it is often the easiest method. Without anything between the Next statements that close nested loops, I have successfully nested ForEach loops down to 9 levels of nesting.

Branch Cleanup

The Branch statement does not touch the stack at all, so it leaves garbage on the stack if you branch out of an If statement or a loop. It gets cleaned up for you when the trigger exits, but in the meanwhile you are limited to 15 entries on each internal stack. It is simple enough to take care of – essentially, you place the Label the Branch refers to at the same depth of the nesting. For example, if you branch out of:

ForEach CreatureA in Party
If Local.IntegerA = Pos.3
Branch Pos.100
EndIf
Next

you need to have the corresponding label in:

While Pos.1 = Pos.0
If Pos.1 = Pos.0
Label Pos.100
EndIf
Exit Loop
Next

The contradictions in the above checks prevent any of the code in your cleanup from being run when it shouldn’t, while the Exit Loop is needed to prevent the Next from forcing a jump back up to the original ForEach CreatureA in Party statement (if you had multiple nesting on the loops, you will need an Exit Loop with each Next on the cleanup). The only problem the above pairing will hit you with on multiple runs is the 5000 tick limit. That is, of course, just an example; if you are branching out of three nested loops and two nested If/EndIf pairings, you need to have the label in three nested loops and two nested If/EndIf pairings. The same solution is used for branching out of Select/Case/EndSelect sets.

Exit Loop Cleanup

The Exit Loop statement only deals with the loop stack when it is run; it will not touch the other internal stacks, such as the If stack. You have 15 stack slots, so normally this won’t matter. If it becomes an issue, use the Branch method above with the included Exit Loop statement rather than a simple Exit Loop.