Global Variable Init Problems
Q: Help! My global variables are not being initialized correctly, or ZC crashes on quest start!
There are two likely causes for this "bug." Both are caused by misunderstanding how global variables are initialized.
When you start a new quest, then, and only then, are the global variables initialized, and the order in which they are initialized is only partially specified, as described here.
Consider the following code:
Code:
ffc script foo {
void run() {}
int a = 0;
int b = a+10;
}
item script bar {
int c = a+1;
}
What's wrong here? The foo script is perfectly fine; a is guaranteed to be initialized before b, so b will be correctly initialized to 10. However, the problem occurs in script bar: even though it comes later in the file than foo's declaration, it is NOT guaranteed that c will be intialized after a, so there's no telling what value c will assume.
The other possible problem is illustrated in the following snippet:
Code:
item script foo {
void run() {}
int a = this->X;
}
Why is this snippet buggy? Well, recall that a is initialized immediately after quest start, so foo's "this" pointer is not yet pointing to anything meaningful. To correct the code, move the initialization of a inside the run method:
Code:
item script foo {
void run() {a=this->X;}
int a;
}
Interfacing ZScript and ASM
Q: Can I mix ASM and ZScript scripts?
A: Yes, if you're very, very careful.
In general adding ASM scripts in slots unused by ZScript is safe. Just realize that if you write to global or screen registers, you may be writing over values used by some of the ZScript scripts.
After every compilation the full ASM output of the compiler is written to allegro.log, so by inspecting this output you can determine what global variables are being used by ZScript, so that you can avoid (or not avoid!) using them in your ASM scripts.
On the other hand, directly modifying the ASM output of a ZScript compilation requires very, very great care, for several reasons:
1. Almost all of the D registers (currently D0-D6) are reserved as important scratch spaces for things like computing the stack frame offset of local variables, and should not be overwritten by foreign code.
2. ZScript-compiled code is heavily reliant on the stack for storing everything from local variables to parameters of function calls to this pointers. The state of the stack must thus also be preserved by foreign code.
3. Changing the line numbers of ZScript code requires a lot of work verifying that references to those line numbers (in things like GOTO) are modified accordingly. Even more tricky is that line numbers are sometimes indirectly stored in registers, pushed onto the stack, then popped off much later and used as return values via GOTOR.
4. If you ever decide to modify the ZScript code and recompile, you'll of course need to reapply your "patch."
Re: ZScript Technical FAQ (READ BEFORE POSTING BUGS)
Q: What is the entry point of a ZScript? How do I pass parameters in?
All scripts must implement a method called "run" which return void. This is the script's entry point, and is called automatically when a script it to be executed.
The run method can have any type signature. When assigning a script to an item or FFC in ZQuest, you have the option of specifying starting values for D0, D1, etc; these starting values are passed into any parameters of run(), in order. For instance, in the snippet
Code:
ffc script foo {
void run(int a) {}
}
a will be set to the intial value specified for D0 when run is called. If you have more parameters, as in, say
Code:
ffc script foo {
void run(int a, int b, int c) {}
}
then a, b, c will be set to the initial values of D0, D1, and D2, respectively.
The type signature of run() can be ANYTHING, so it is possible to pass in non-integer types like bool or even item and ffc. However, declaring a run() with non-simple parameter types is highly unsafe and discouraged.