This tutorial covers basic use of ZScript. It is not written as a general introduction to programming. Instead, it will begin with the simplest functional scripts before moving on to more complex control flow and data manipulation in the intermediate and advanced tutorials.
You should already know how to load and use scripts. If not, that's covered in this tutorial: http://www.purezc.ne...showtopic=38376
By the end of the basic tutorial, you should know the language well enough to write useful scripts, even if only simple ones. However, this is only intended to teach the language itself. Learning how to make good use of it requires experience; it's best to write a lot of practice scripts and experiment to see how things work. Scripting requires a way of thinking that doesn't come naturally to most people, and you won't learn that just by reading a few tutorials.
It's important not rush through this. Each section builds on the ones before; if you move on before you're ready, it'll just make things more difficult.
Comments and style
A few things need to be mentioned before getting into actual scripting.
Comments are notes in the script that are ignored by the compiler. They exist only for the sake of human readers. A comment begins with two consecutive slashes and lasts for the rest of the line.
// This is a comment. The line below is not. int x=5; int y=10; // This is a comment, but the first part of the line is not.When you write scripts, it's generally a good idea to add comments to clarify what you're trying to do. They can also be used to temporarily remove code for debugging, but you shouldn't need to worry about that for a while. This tutorial will use comments extensively in code examples to explain what they're doing.
Whitespace is a collective term for spaces, tabs, and line breaks. The compiler doesn't care what you use for whitespace; any combination of the three is equivalent. The only exception is comments; a comment always ends at the end of a line. There are places where whitespace is required, places where it is optional, and places where it is forbidden. These are not really worth going into, as they're pretty intuitive. In general, as long as a human reader wouldn't be confused, the compiler won't, either.
To illustrate, here are three snippets of code that differ only in whitespace:
while (true) { Link -> X += 5; Waitframe (); }
while(true) { Link->X+=5; Waitframe(); }
while(true){Link->X+=5;Waitframe();}All three of these are identical, as far as the compiler is concerned.
Since whitespace doesn't matter to the compiler, the best way to use it is strictly a matter of opinion. There are many different ways of handling line breaks and braces. Wikipedia has an article describing several common styles: https://en.wikipedia...ki/Indent_style
Indent style is a very personal matter to many programmers and the subject of numerous flame wars. Different styles have their pros and cons, but all that really matters in the end is that you're comfortable with the style you use. However you like it, though, you should stick to it. Inconsistent spacing looks sloppy and is hard to read, much like English with no punctuation. It's especially important to indent properly when asking for help; if your scripts are hard to read, you'll find people much less willing to search them for errors.
Unlike whitespace, capitalization is important to the compiler. ZScript is strictly case-sensitive. If you try to use link->x instead of Link->X, your script won't work. Names you make up yourself can be capitalized any way you like; again, just be consistent with it.
Finally, a note on terminology. { and } are curly braces or just braces. [ and ] are square brackets or brackets. ( and ) are parentheses. They're all used for different purposes and are never interchangeable, so be sure not to get them confused.
Getting started
Generally, the first thing you should put in a script (after any instructional comments) is this line:
import "std.zh"This tells the compiler to include the contents of the file std.zh with your script. std.zh defines lots of helpful functions and constants, and almost every non-trivial script uses it in one way or another. Although you won't actually need it for a while, it's good to get into the habit of including it. However, when using multiple scripts, std.zh (or any script file, for that matter) should only be included once. Importing the same file more than once will cause errors.
The next thing you have to do is tell the compiler what kind of script you're writing. For an FFC script, you would write this:
import "std.zh" ffc script ScriptName { // Script body goes here }This part is pretty straightforward. The phrase ffc script indicates that you're creating, obviously, an FFC script. That's followed by the script's name, which is, in this case, ScriptName. There are three types of scripts you can create:
ffc script ScriptName // An FFC script global script ScriptName // A global script item script ScriptName // An item scriptScript names are made up of letters, numbers, and underscores ( _ ). The first character cannot be a number, and the name must not be a language keyword (like script, int, or return). Spaces are not allowed in script names.
Valid Names:
- CustomBoss2
- makelinkdostuff
- This_is_a__stupid_name
- RefillHP&MP // Contains an illegal character (&)
- int // "int" is a ZScript keyword
- 3Hearts // The first character is a numbe.
- ^__^ // Contains illegal characters (^)
ffc script BraceExample { // The opening brace of the script // The body of the script } // The closing brace of the scriptThat's all you need to define a script, but there's one more thing you have to add before it will work.
ffc script ScriptName { void run() { // Code goes here } }This defines the run() function. What exactly this means will have to wait. For now, just remember that the body of void run() is what the game will actually run.
A bit about the different types of scripts.
An FFC script is one that can be attached to a freeform combo. Normally, these will run when Link enters the screen (either before or after the screen scrolls, depending on the "Run Script at Screen Init" flag). It is also possible for a script to set up an FFC to run a script on demand, but this is a fairly advanced technique.
An item script can be attached to an item to run either when the item is picked up or when it is used. Any item script can be used in either way, but almost every script is specifically intended for one use or the other. Item scripts only run for a single frame, so they're commonly used in conjunction with FFC and global scripts to perform actions over time.
A global script is one that is run by the game itself. There are four global scripts. The Active script runs every time the quest is played and can run indefinitely. That's almost always the one you're interested in, but, for the sake of completeness, here are the others. The Init script runs when the quest is started for the first time, and the Continue script runs when it's reloaded after saving and quitting. The Exit script runs whenever Link dies, the game is won, or Game > End Game (F6) is used. The Init, Continue, and Exit scripts can only run for one frame each, so they're not useful for gameplay purposes. You can't load a script into the Init slot manually; instead, to create an Init script, the script must be named Init.
Pointers and variables
Pointers represent game objects, such as Link, FFCs, enemies, or the current screen. Variables represent properties of those objects, such as their HP, position, CSet, or tile. (These aren't really accurate definitions of the terms, but they'll do for now.)
For now, we'll only look at these special pointers:
- Link - Represents Link
- this - Represents the FFC or itemdata running the script; this isn't defined in global scripts
- Screen - Represents the current screen
- Game - Represents the game itself
Variables can be one of two types of data, but there are three different words to identify them.
One type of data is numbers. Numbers are identified with the terms int (short for "integer") or float (short for "floating-point number"). The reason there are two terms will be explained shortly. Numbers go to four decimal places and have a range of -214748.3648 to 214748.3647.
The other data type is bool. Bools have only two possible values: true and false. They represent data you would think of in terms of "yes or no." For example, Link->InputA, which indicates whether the A button is pressed, is a bool. Either the button is pressed (in which case the value is true) or it isn't (false).
The value of a variable is changed with an equals sign. Changing a variable's value is called assignment. An assignment statement must end with a semicolon.
Link->HP = 16; // Set Link's HP to 16 (one full heart) Link->InputB = true; // Press the B button Link = 32; // ERROR: You can't assign numbers to pointers directly; //you have to set their variables. It doesn't make sense to set Link himself to 32. // The variable you're setting must always be on the left side of the equals sign. 16 = Link->HP; // ERROR: The compiler thinks you're trying to change the value of the number 16 // You can also assign one variable the value of another. Link->HP = Link->MaxHP; // Sets the value of Link's HP to the value of his MaxHP, filling Link's HP this->X = this->X; // Set the FFC's X position to itself - this doesn't do anything, but it's legal Link->X = Link->InputUp; // ERROR: Link->InputUp is a bool, but this->X is a number. // Setting a number to "true" or "false" doesn't make sense. // Assigning one variable to another is a one-time thing. // If one changes later on, the other won't change with it. Link->MaxHP = 48; // Set Link's max HP to 48 (3 hearts) Link->HP = Link->MaxHP; // Now Link's current and max HP are both 48 Link->MaxHP = 64; // Link's max HP is now 64, but his current HP is still 48To see all of the different variables you can set, look at zscript.txt. The "class" or "namespace" tells you what pointer type something belong to. Those terms aren't meaningful in ZScript; the file is formatted the way it is to work with C++ documentation tools. The ones you should be looking at are under Link, Game, Screen, FFC (for FFC scripts only), and itemdata (for item scripts only).
As an example, one of Link's variables:
/** * The time, in frames, until Link regains use of his sword. -1 signifies * a permanent loss of the sword. */ int SwordJinx;The last line tells you the type and name of the variable. For now, if it has any type other than bool, int, or float, or if it has brackets or parentheses after the name, just ignore it. Those will be covered later.
The difference between int and float is that ints are whole numbers (i.e., integers), while floats can have fractional values. An FFC's CSet, for instance, is an int. There isn't, say, a CSet 4.62. This is a mostly a matter of convention; as far as the compiler is concerned, they're just different names for the same thing. However, when assigning a value to an int-type variable, it will be rounded down to an integer value.
Note also that variables will not always be the type you expect, and there are a few inconsistencies. For example, Link's X, Y, and Z coordinates are ints, but FFCs' coordinates are floats. FFCs are capable of sub-pixel positioning and movement, but Link is not.
You may notice that some variables have a note that "The effects of writing to this variable are undefined." These are meant to be read-only; you shouldn't try to change them. Assigning new values to these variables probably won't work as you expect, and even if it does, it's not guaranteed to work on every platform or in later versions.
One last note. Hexadecimal numbers can be written by prefixing them with 0x. This is mainly useful when dealing with screen numbers.
Game->ContinueScreen = 0x2F;At this point, you know enough ZScript to write useful scripts, albeit only extremely simple ones. Again, you are strongly encouraged to write practice scripts for every new concept. Being a good scripter requires experience, and there's only one way to get it.
Here are some sample scripts using what's been covered so far. For now, FFC scripts will take effect as soon as Link walks onto the screen. If you want to delay them, you'll need to delay the appearance of the FFC (by using a warp, perhaps). Also, these will only run once. If you want to do something again and again, such as every time a button is pressed, you'll need loops, which will be covered in the intermediate tutorial.
// Using an item with this script will refill Link's HP and MP. item script Recharge { void run() { Link->HP = Link->MaxHP; Link->MP = Link->MaxMP; } }
// This script will enable level 4 cheats. This won't work correctly if // cheats are disabled in the quest. item script CheatLevel4 { void run() { Game->Cheat = 4; } }
// This script will cause the screen to shake for one second and make Link jump into the air. // It will also disable the sword and items for as long as the screen shakes. ffc script Earthquake { void run() { // There are 60 frames in a second, so... Screen->Quake = 60; Link->Jump = 3; Link->SwordJinx = 60; Link->ItemJinx = 60; } }Also, here are a few suggestions for practice scripts. While you're not in any way required to complete these, you should be able to do so. If you can't figure out how to do these, you're probably not ready to move on. Possible answers are given in spoiler tags.
- Write an FFC script that moves Link to the FFC's position.Spoiler
- Write a script that sets Link's HP to 3 full hearts (16 HP per heart) and his MP to 3 full magic containers (32 MP per magic container).Spoiler
- Write an FFC script that makes the FFC move to the left and accelerate to the right.HintSpoiler
Arithmetic
You're probably already be familiar with the arithmetic operations used in ZScript.
- - Negation
- + Addition
- - Subtraction
- * Multiplication
- / Division
- % Modulus
The standard order of operations applies: multiplication, division, and modulus are done before addition and subtraction. You can override this with parentheses; parts of expressions in parentheses are evaluated first. In programming terminology, the order of operations is called operator precedence. Operators with higher precedence are evaluated before those with lower precedence.
You assign a variable the result of an arithmetic operation the same way as a single value.
this->CSet = 1 + 1; // Set the FFC's CSet to 2 Link->MP = Link->MaxMP / 2; // Set Link's MP to 50% of the max Link->HP = Link->HP + 8; // Add 8 to Link's current HP this->X = 16 * ((5 + 4) / 3); // Set the FFC's X position to 48; there's no reason to do it this way, but you can Link->HP + Link->MP = 16; // ERROR: The compiler can't figure this out; // arithmetic has to go on the right side of the equals sign // You can make both numbers and variables negative. To make a variable negative, // put the minus before the pointer. Link->HP = Link->HP + -16; // Reduce Link's HP by adding -16 HP this->Vx = -this->Vx; // Reverse the FFC's X movement by negating its velocityOften, arithmetic is used to adjust a variable's current value (as in the third example above) rather than assign it a completely new value. Because this is so common, there's a shorthand way of doing it by combining the arithmetic and assignment operators. For instance, A += B is equivalent to A = A + B,
and A *= B + C is equivalent to A = A * (B + C).
Some examples:
Link->MP += 8; // Equivalent to Link->MP = Link->MP + 8; this->Vx *= 1.5; // Equivalent to this->Vx = this->Vx * 1.5; Link->HP -= Link->HP % 16; // Equivalent to Link->HP = Link->HP - (Link->HP % 16); Link->MP -= -16; // Increase Link's MP by subtracting -16 Game->Cheat += 1; // Increase the current cheat level by 1 // Whatever operation you perform in this way, it's done last, // regardless of the usual order of operations. this->Vx *= this->Vy + 3; // Equivalent to this->Vx = this->Vx * (this->Vy + 3); // NOT this->Vx = this->Vx * this->Vy + 3;That's all there is to using arithmetic for now, but there's a quirk in division that it's important to be aware of. Because of the way ZC handles numbers internally, the results of division are rounded down, so they won't always be what you expect. In many cases, this isn't a problem, but there are times when seemingly equivalent equations will have completely different outcomes.
Link->HP *= 0.5; // Reduce Link's HP by half Link->HP /= 2; // Reduce Link's HP by half Link->HP *= 1 / 2; // Set Link's HP to 0, because 1/2 is rounded down to 0If you're familiar with other programming languages, you might expect that you can get the desired result by writing 1.0/2.0 instead. That's not the case; in ZScript, it makes no difference.
Once again, here are some complete sample scripts. Math is a big part of scripting, so be sure you understand how these work.
// This script reduces Link's current HP and MP by 25%. ffc script SeventyFive { void run() { Link->HP *= 0.75; Link->MP *= 0.75; } }
// This script will make the FFC aim at Link, but accelerate in the opposite direction. ffc script VeerAway { void run() { // Dividing by 60 means it will take one second to reach Link's position - // or it would, if it weren't accelerating away this->Vx = (Link->X - this->X) / 60; this->Vy = (Link->Y - this->Y) / 60; // Acceleration can be unintutive; you may want to try this one out. // The acceleration is based on the negative of the velocity, so if it // starts out moving up and left, it'll accelerate down and right. this->Ax = -this->Vx / 150; this->Ay = -this->Vy / 150; } }
// This script disables Link's sword for one second for every full heart and // disables items for one second for every full magic container. ffc script ProportionateDisability { void run() { // 16 HP in a heart, 60 frames in a second Link->SwordJinx = (Link->HP / 16) * 60; // And 32 MP in a magic container Link->ItemJinx = (Link->MP / 32) * 60; } }And a couple more practice exercises:
- Write a script that moves Link one tile (16 pixels) down and right.Spoiler
- Make an item that reduces Link's MP by 25% and increases his HP by 1 for every 2 MP taken. Think it through; this is simpler than it sounds.HintSpoiler