Disclaimers...
I'm not going to talk about how to get your scripts to do wicked cool totally 1337 things. I'm also not going to comment much on what a script actually looks like, but I do have an example script file that can be loaded that shows example scripts. I've also listed some references at the end of this post. Everything here is valid for 2.5 beta Build 704...I'm not sure about earlier builds. I'm assuming you have some familiarity with ZQuest in general, such as what combos are, how to draw screens, etc. Lastly, I'm concentrating on ZScript, but I don't think that it really matters for most the content of this post. Void where prohibited. Tax, Title, and License fees not included. Do not eat this post.
*Please keep in mind that this tutorial is meant to be a very basic "getting started" type tutorial.*
OK, enough with the chit-chat.
Why should you use scripts?
Honestly, you could probably make a pretty decent quest without knowing a thing about scripts (obviously many people have done it, since scripts weren't around in ZC 2.10). However, using scripts allows you to do some very impressive things that will make people remember your quests forever. You can use scripts to make custom items and enemies, render complex screens with all kinds of moving combos (you don't even have to move them in a straight line...) and so forth.
Types of scripts
You have at your disposal three types of scripts. They are:
- FFC (freeform combo)
- Global
- Item
You use FFC scripts to control how freeform combos --- combos that aren't really "tied" to the screen in any particular way --- move and behave on a screen. For instance, you can create a tree that follows link around, or an enemy that always blocks Link's path, and a million other things. An impressive example of what FFC scripts allow you to do is shown in _L_'s Revolution demo.
Global scripts are executed at various points in a quest, and can be used to set up game play or clean up upon quitting a quest.
Item scripts are assigned to particular items to alter how an item works or behaves. They allow you to customize a weapon or item.
We'll talk about each of these in a little more detail later, but first...
How to compile and assign Zscripts
Before we go on, let's talk about how to compile and assign ZScripts to events and items, because it's not horribly intuitive. In a nutshell, you have to compile scripts (ZScripts, at least) and assign them to individual "slots" in ZQuest where Zelda Classic (ZC) can find them later. It's kind of like you're filling a Soda vending machine. You've got to put the Coke here, the Sprite there, and so forth so when someone else wants a soda, they know where to look. Further setup is required for Item and FFC scripts.
If you don't have a script of your own, copy the one I've attached and paste it into a text file (using Notepad, for instance). Be sure you save it with a .z extension (or rename it after you save it). Also, save it in (or move it to) the same directory as ZQuest (you don't actually have to do this, but it will same you some time when you have to find the script later).
In ZQuest, select "Tools > Scripts > Compile ZScript...". You'll see the Compile ZScript window, which has a few buttons on it. Since we have a script already, you're going to IMPORT it. Click the Import button. Browse to your script file using the controls and click OK when you've found and selected the script file. When you return to the Compile ZScript window, you'll see something like "4325 bytes in buffer" shown. If you click the Edit button, you'll see the script we just loaded. At this point, the script is loaded in ZQuest's memory.
Ah, but it's not ready yet. We have to compile it --- this simply "translates" your ZScript to something that ZC can understand better. To compile the script, click the Compile button. ZQuest will throw up a status window. If everything goes well, the last two lines should be something like:
Pass 6: Assembling
-- Press A Key --
This is good. It means our script compiled without any errors. Note that you can press any key, not just the "A" key. If the script didn't compile, you'd see an error code.
After you press a key, you finally get to the Assign Compiled Script window (assuming the script compiled without any errors). Here you see three tabs, one for each of the script types we've introduced: FFC, Global, and Item. Take a look around - you'll see that each tab has a list of Slots. Each Slot can hold one script. If you're using my script, you'll notice that when you select the Global tab, you see a list of scripts on the right. And if you pick the Item tab, you see a different list of scripts on the right. ZQuest knows how we've declared our scripts, and displays only the ones that are valid for the selected script type (a little bit more on this later...).
To assign a script to a slot, pick the slot in the left list, pick the script name in the list on the right, then click the "<<" button.
That's it as far as assigning scripts go. There is one more caveat for Item and FFC scripts - you have to remember which script is in what slot (more on this later).
There's also a distinction that needs to be made here...a "script" is not the same thing as a function. Each script must have at least one function in it --- the "run()" function. You can have more functions (for example, common functions that the run() function calls). The Assign Compiled Script window displays the name of the script, not the name of any functions in that script, though the run() function is what actually gets called by ZC.
Global scripts
ZC gives you the ability to use 3 global scripts. While it's not readily apparent from the menus in ZQuest, these three scripts will be executed upon certain events in a game. As far as I can tell, these events are:
- Initialization - after you've selected a quest to play, but before all of the game data is initialized and ready to go, and before the "Activation" event below.
- Activation - after the "initialization" event, and just before you can start playing the quest. With a little trick, you can get this script to run constantly during gameplay (until Link dies or you quit), even though it's only actually started by ZC once.
- Exit - either when Link dies, or when you quit the game by selecting "File > Quit" from the menu. Note that the script associated with this event is not executed if you quit by selecting "File > Reset" or "File > Exit". This script doesn't seem to get called until the "Contintue/Save/Retry" screen shows up, but it's hard to tell.
You can assign one script to each one of these events. For reference, when using the Assign Compiled Script window, and the Global tab:
- Slot 1 corresponds to the Initialization event. In Build 704, I don't know if this can be changed - ZQuest says it's reserved (there's an "~Init" script automatically loaded to it).
- Slot 2 corresponds to the Activation event.
- Slot 3 corresponds to the Exit event.
Each of these scripts is called only once by ZC. However, you can "extend" the Activation event by including a "while(true)" loop in the script. If you do this, you must make calls to the Waitframe() function in the appropriate spots, otherwise you may freeze the entire game. The Waitframe() function allows other stuff in the game to happen while your script is running. You can see in my example I've used this approach to count how many times the player presses the A (Alt) button (the Trace() function writes the results to the allegro.log file). If I hadn't included the Waitframe() calls, the game would have just frozen up, counting button presses forever and not letting other things in the game get processed. (Go ahead and try it...comment out the Waitframe() lines by putting "//" at the beginning of the line, recompile and reassign the scripts. Be prepared to CTRL+ALT+DEL the game, though. ).
I haven't thought up any wonderful things to do with the Exit script, but I suppose you could use it to save data for the next time Link starts the quest, in combination with global variables (discussed briefly later).
Also note that it doesn't make sense to call some ZC functions depending on which script is running. For example, you can't write messages to the screen using "Screen->Message(1);" in the Exit script, because the screen isn't really there anymore at that time.
Global scripts are declared in your script file using the "global" keyword, as follows:
void run(){
// This function is the one that actually gets called by ZC.
// ... do whatever here ...
}
// You can create other functions to be used by the run()
// function.
//...other functions here...
}
Item scripts
Item scripts allow you to perform various actions when Link first gets an item, and then when he uses that item. Item scripts are declared using the "item" keyword, as follows:
void run(){
// This function is the one that actually gets called by ZC.
// ... do whatever here ...
}
// You can create other functions to be used by the run()
// function.
//...other functions here...
}
Note that you can have Item scripts in the same file as Global scripts (you should, probably).
You assign Item scripts to slots as described above, but there are a few extra steps required to actually use the scripts. These extra steps are performed by editing an item's properties. Select "Quest > Items" in ZQuest. In the Select Item window, choose the item you want to assign scripts to. You can choose an existing one, or you can make a new item by scrolling to the bottom of the list and picking one of the Items starting with "z". Once you've selected an Item, click the "Edit" button. You'll see the Item Properties window.
In the Item Properties window there are two tabs we're interested in: the Pickup tab and the Action tab. Go to the Pickup tab. You'll see a "Script" property. Here, you enter the Slot number of the Item script you want to run when Link first gets this item. Similarly, in the Action tab you enter the Slot number of the Item script you want to run when Link uses the item (such as attacking with a sword or using a bomb).
My example file has a very simple Item script in it...whenever link uses the "Suicide Sword" to attack, he loses half of his hitpoints. There are obviously better things to do with an Item script --- I'm sure others have gotten lightning to shoot out the end of their sword and so forth by also using freeform combos --- but I'll leave that up to you to experiment with. At least you know how to assign the scripts now. Go ahead and assign the Item scripts to a sword and see how it works.
Note that some of the script functions associated with Items may become unavailable once Link actually has the item (for instance, you can't change the item's position on the screen anymore). Also note that not all the item properties are available to edit through scripting. For instance, there doesn't appear to be any way to change a weapon's Damage value using a script.
...continued in next post...
...from previous post...
FFC scripts
Last, but certainly not least, let's quickly talk about freeform combo scripts. Like the other scripts, you have to assign them to particular slots before you can use them. To set up a FFC to use a script, select "Data > Freeform Combos" from the ZQuest menu. You can customize up to 32 FFCs on each screen; for our example here, just click the Edit button to edit the first FFC's properties. On the Data tab, at the bottom, is where you assign a script to this FFC. Here, you're lucky in that (in Build 704, at least) you can see the name of the script when you select it. The script gets executed when the FFC is first drawn on the screen, unless you also check the "Run script at screen init" box on the Flag tab.
FFC scripts are declared in your script file using the "FFC" keyword, as follows:
void run(){
// This function is the one that actually gets called by ZC.
// ... do whatever here ...
}
// You can create other functions to be used by the run()
// function.
//...other functions here...
}
Unfortunately, being a beginner myself, I haven't had time yet to fool around with FFC scripting, so I don't have any real examples here. I've included FFC script declaration in my sample script file, though, so you can see how it shows up in the Assign Compiled Script and Edit FFC windows. There are also plenty of examples (see References below).
Also, I've read a few posts that indicate there may some limitations on how FFC scripts interact with each other, but I'm still too "new" to the FFC scripting to have an idea of what this really means. Maybe someone else can chime in with some general info on how FFC scripts interact with each other, or other types of scripts.
A note on global variables
I'm slightly unsure how the mechanics of all this work, but variables declared outside any "script" declarations (usually at the top of script file, as in my example) will be global. This means that any script in the same file can use that variable (for instance, one of the Global scripts could set the value, and then an Item script could read that value and even update it). Additionally, when you save your progress as you play a quest, the values of those variables also get saved. Note that if you have a declaration like "int x = 0;" at the file level, the " = 0" part only applies when the quest is first started. In other words, the variable will always be available, but it only gets initialized (to 0, in this case) the very first time you start the associated quest.
There is some information on global variables in the Wiki, but I think it's slightly outdated, because you can declare variables at the file level now (at least in Build 704). See this deprecation notice at AGN.
One aspect of this that is slightly confusing is that when you load and compile a ZScript as I described above, there's really no indication that your global variables are "working", other than the compiler didn't crash. For instance, you can see all the scripts that are available for use in the Assign Compiled Script window, but you can't see your global variables anywhere except by clicking the Edit button in the Compile ZScript window. Rest assured that they're ready for use, though (assuming you successfully compiled the script!).
General gripes (warning: complaining ahead!)
When starting out with this stuff, I - and other beginners - rely heavily on the documentation that's "out there". This is unfortunate, because in my opinion the documentation on scripting leaves much to be desired...I'm not sure if I just haven't found the "jackpot" yet, or if it just doesn't exist. Various tidbits are sprinkled throughout multiple forums and websites, which makes it difficult to find. The Wiki doesn't have much information (I'd help, but check out my puny post-count!).
The "zscript.txt" file that is provided with the beta releases (and explains the built-in functions available for scripting) is somewhat helpful, but beginners may be stumped by a few things (some of these things aren't so bad if you've programmed in C or a similar language before):
- Despite searching forums, the Wiki, and so forth, I have yet to find an actual "User's Guide" that describes what data types are really available (int, float, bool, etc), what operators can be used (for instance, you can increment an integer using the C-like "x++" approach, but you evidently cannot do something like "++x", as you can in C), and so forth.
- It's not evident from the zscript documentation which properties are read-only and which are not. Can we set the CSet of a FFC "on the fly" in a script, or is it read-only? You just have to try it to find out in some cases.
- While the functions and properties of various objects are listed in the zscript file, beginners may be confused because the objects to which they apply aren't shown. For instance, looking at the "Link Functions and Variables", we see we can get Link's position by checking the "X" variable. ...But just writing "if(X == 16)..." in a script isn't going to get us anywhere. We have to write "if(Link->X == 16)...". This is probably obvious to people who have used scripting languages before, but beginners may be confused.
Example script
My example script file contains example scripts for each type of script (though the FCC example is quite lacking). You can import this into a quest as described above.
// Import the standard header so we can
// use pre-defined constants and so on...
import "std.zh"
// Global script variables.
// Variables declared here are accessable to all
// scripts (and functions) in this file. Also,
// the values will be saved with Link's quest progress.
//
// NOTE THAT GLOBAL VARIABLES ARE ONLY INITIALIZED
// ONCE, WHEN A NEW QUEST BEGINS!!!
//
int NumTimesAButtonPressed = 0;
// For demonstration purposes, these scripts attempt
// to display strings on the screen. You can define
// strings in a sample quest if you want, but ZC
// didn't seem to mind if the strings were actually
// available or not....
//*****************************************************
// GLOBAL TYPE SCRIPTS
//*****************************************************
//-----------------------------------------------------
// Script : onInitialize_Quest
// Purpose : This is called when Zelda Classic loads
// the quest. It only gets called once.
// Load this script into Global Slot 1.
// Notes :
// 1) Apparently Global Slot 1 is "reserved". At
// least in 2.5 Beta 704.
//-----------------------------------------------------
global script onInitialize_Quest{
void run(){
// Try to display a string on the screen.
Screen->Message(1);
} // run
} // onInitialize_Quest
//-----------------------------------------------------
// Script : onActivate_Quest
// Purpose : This is called after Zelda Classic loads
// the quest. It only gets called once.
// Load this script into Global Slot 2.
// Notes :
// 1) While the script itself only gets called once,
// you can use a "while(true)" loop to keep the
// script running throughout the entire quest.
// If you do this, you must use Waitframe smartly
// otherwise the script will utilize all the
// processor time to run, and you'll simply
// freeze the game .
//-----------------------------------------------------
global script onActivate_Quest{
void run(){
// Try to display a string on the screen.
Screen->Message(2);
// Anything in the following while loop will be
// executed the whole time the quest is being played.
// NOTE THAT YOU MUST USE THE Waitframe FUNCTION
// INTELLIGENTLY OR YOU WILL FREEZE THE GAME.
while(true){
// See if the A Button has been pressed.
// For the sake of this example, a "press" is
// when the A Button has been pressed and then
// released.
//
// Note that since we're storing the button press
// count in a global variable, the value will be
// the totol number of times the A Button has been
// pressed since the quest was first started (and not
// just the count for the current "session" of play).
if(Link->InputA){
// The A Button has been pressed, but we
// don't know if it's been released yet.
// Just loop while the button is pressed.
// When we exit the while loop, we know
// the button has been released.
while(Link->InputA){
Waitframe();
}
// Button has been released at this point.
// Update the press count and write it to
// the logfile.
NumTimesAButtonPressed++;
Trace(NumTimesAButtonPressed);
}
Waitframe();
} // while true
} // run
} // onActivate_Quest
//-----------------------------------------------------
// Script : onExit_Quest
// Purpose : This is called upon one of the following
// conditions:
// 1 - Link dies, or
// 2 - the player quits by selecting
// "file > Quit" from the menu.
// Load this script into Global Slot 3.
// Notes :
// 1) This script does NOT get called if the player
// ends the game by using "File > Reset" or
// "File > Exit".
//-----------------------------------------------------
global script onExit_Quest{
void run(){
// Try to display a string on the screen.
Screen->Message(3);
} // run
} // onExit_Quest
//*****************************************************
// ITEM TYPE SCRIPTS
//*****************************************************
//-----------------------------------------------------
// SUICIDE SWORD
//-----------------------------------------------------
// DESCRIPTION:
// Powerful, but at a price. When Link
// uses the Suicide Sword, his hitpoints
// are halved.
//
// Link loses half his hitpoints when
// he first gets the item, too.
//
// USAGE:
// Set an item's Pickup Script
// to onGet_SuicideSword.
//
// Set an item's Action Script
// to onUse_SuicideSword.
item script onGet_SuicideSword{
void run(){
// Take half of Link's hitpoints away.
Link->HP = Link->HP / 2;
} // run
} // onGet_SuicideSword
item script onUse_SuicideSword{
void run(int UseCSet){
// Cut Link's hitpoints in half.
Link->HP = Link->HP / 2;
// Make Link scream....
Game->PlaySound(SFX_OUCH);
} // run
} // onUse_SuicideSword
//*****************************************************
// FFC TYPE SCRIPTS
//*****************************************************
ffc script movingBlock{
void run(){
// Try to display a string on the screen. Normally
// you'd do something way more cool here.
Screen->Message(4);
}
} // movingBlock
References
Some threads on general ZScript syntax:Freeform combo help:AGN's Script Showcase, where you can find all sorts of cool scripts: AGN Script Showcase