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.