PDA

View Full Version : C Scripting For Non-Coders.



ShadowTiger
11-26-2006, 11:29 AM
Okay. This is a thread started by and directed towards those who are familiar with the concept of C++, and can potentially read it without too many problems, but when it comes to actually writing a script, hope flies out the window. It's like being able to read English but not being able to write it due to the myriad of minutia-related questions which seem to constantly pop up. As ridiculous as that situation seems, (Can READ English but not WRITE it? :odd: WTF!? .. etc.) it's a real issue. Some people can understand the basic premise/s of C++, but no amount of example following and personal try-outs seem to solve this delicate issue.


One particular issue at hand is, what is the difference between Pseudocode and actual code in the way it's written? For example, what is the difference in syntax and layout to go from the following: (This example is not necessarily in ZC Terms.)


if { the_number_of_goblins in treewood_forest_screen001 == 0
then
execute script 4052 and tell wooden_signpost_052 to change variable 05 to state "1" }

Or,


get object PC
If object PC has less than 10% Health
then {
play SFX_beep once every three seconds }


Note my fairly obvious misuse of Brackets. ( { } I hope these are brackets; I'm not sure if I've named them incorrectly. You don't need to know their names to type them, apparently. :p ) I wouldn't know where to place them relative to the script. Do they group commands? Do they group subsets of code? Do they separate one process from another? Again, if I saw it in an example, I'd know what it does, but clearly, I'm not 100% sure in the examples above if I'd even need them at all, and when to use them if I do. (But I wouldn't be sure if I needed them, so...)






@}---------------------------------------------{@




Here's a sample of the closest script I have available to me. It's from Neverwinter Nights.


void main( )
{
object oPC = GetEnteringObject( );
if(GetIsPC(oPC) &&
GetMaxHitPoints(oPC) == GetCurrentHitPoints(oPC))
{
ApplyEffectToObject(DURATION_TYPE_INSTANT,EffectDa mage(1),oPC);
}
}

Here's the bit of text they described it with:

By now, you should be able to recognize the basic structure of the script (the void main and the curly braces). However, the code inside introduces a few concepts not discussed, yet. So don't feel stupid if you couldn't figure it out.
Here's what's going on. FIrst, the script gets the object currently entering the area (using GetEnteringObject( )). The object is then checked to see if it is the PC (using GetIsPC(oPC)) and if the current HP of the object (still undermined but possibly the PC) are equal to the maximum HP of the object. If the object is a PC object, and the maximum HP is equal to the current HP, then the object is given a free point of damage (using ApplyEffectToObject( ) and EffectDamage( ))! Sounds like a nice little area to stay away from, doesn't it?
The StartingConditional type is commonly used in NPC conversations to setup conditions for the start of a conversation (as the name implies) with a PC. The return value can indicate if the NPC has spoken with the PC before or at what point in a complicated conversation they are currently at. For example, a conversation script can be written that only exposes a certain part of the NPC's conversation the first time a PC and an NPC meet.
The following script is taken from the initial conversation of an NPC you meet in the first part of the single-player campaign.

int StartingConditional( )
{
int bCondition = GetLocalInt(OBJECT_SELF, "NW_L_TALKTIMES") == 0;
return bCondition;
}

... Okay. You can read that and sort of understand what's going on. A few questions arise though:

1) bCondition is a variable that we define ourselves, rather than a game constant, right? We set it ourselves to an int with "int bCondition =" so that must mean that we're setting it up ourselves to be a custom variable that's referring immediately, right off the bat, to the local integer which refers to the number of times the "L" (Whatever) has been spoken with. This isn't so much a question as it is something to say "... ... ... Yes ShadowTiger. :rolleyes: " to. :p
2) The first line of that script was "int StartingConditional" rather than void main( ). I know that void main means that the script isn't "returning anything," (Though I still don't know where it would or wouldn't be returning it to, as when I look at scripts, it's always on an individual script snippet by script snippet basis; never at the whole.) So considering that it says "return bCondition" it must be sending that value somewhere. Where does it say where it's returning it to? Does it automatically assume that it's returning it to the nuclear script (Nuclear, as in nuclear family.) outside of the brackets the script snippet is contained within? Or would it be returned to the variable with the same name anywhere within the local script as the new value of that variable?
3)Referring to the first script under the centered dividor; (The one that gives you a point of damage for entering the area.) why is the syntax object oPC = GetEnteringObject( ); rather than GetEnteringObject( ) = object oPC;?
4) Would that object oPC be a variable? Would either of the two words be a variable? Why would they write oPC instead of just PC? Why would they write object oPC instead of just object PC, etc.
5) On the line "if(GetIsPC(oPC)" why would they say "GetIsPC" but mark the actual PC as being oPC? What would the "o" be doing there? I know this isn't related to ZC, but it's an interesting question in case there are these odd visually mismatching examples within ZScript that one might encounter in one's travels, such as the instance of "this->" etc.
6) On the line with the double ampersand, (&&) I think I can understand what that implies, though two brief questions arise:
Why are there two ampersands? Would one suffice? Or is that just general syntax, similar to != and ==?
There is nothing after the double ampersand on the same line. If the line underneath it is the second part to an "And" statement, could it be on the same line? Is it really perfectly alright to have it on the line underneath it? (and with any number of return carriages after the double ampersand?


Thank you very much for your patience. :) I'll be sure to get you some ointment for your forehead after slamming it on the head so very many times. :blah:

C-Dawg
11-26-2006, 12:53 PM
I'll add something to this.

First: Quick primer for non-C people. This is really easy. Really, really easy. Here's all you need to know to read the code: loops, functions, and if-else statements end in { and execute until another }. All other lines of code must end in a semi-colon (;). All user-defined functions have () at the end of them. You stick variable names into the () to send variables to the function. For instance, the function "distancesq(x1, y1, x2, y2)" returns the distance between coordinates x1,y1 and x2,y2. The prefix to the function, which must be a data type, tells the code what sort of value the function returns. That is, distancesq is actually called" int distancesq(x1, y1, x2, y2){" because it's going to return an integer. Other functions, like the ubiquitos "run," don't return anything and so get the "void" prefix.

ZScript is pretty much familar, object-oriented C code, but you need to be familar with what objects and pointers the language provides for you. Think of them as "handles" hanging off of the program. You can only manipulate things by attaching your code to these handles.

Currently, it appears you need to attach code to Free Form Combos. When Link is on a screen where there is a Free Form Combo with a script assigned, the game engine will run function in that script called, intuitively, "void run()." So every free form combo script you write has this as it's skeleton:

(words in caps describe what should be in their place; they do not necessarily tell you what exact characters should be there.)



ffc script NAME_OF_THIS_SCRIPT
{
void run()
{
} // end of void run
} // end of ffc script


Anything following a // is just a comment to help the reader, and is discarded when the code is compiled.

Now, you can add functions in addition to void run(), but they won't execute unless they are specifically called by the run() function. That is, if I have a function that moves the FFC to Link's location, I could call it void move_to_link(). Then, in my void run() code, I can just use the command: "move_to_link();" and that other function will run.

To accomplish anything, your functions can only change the value of "handles" accessible to the ZScript. Luckily, the developers have already made these handles pretty extensive. You can change the position, velocity, combo used, etc of any FFC on the screen. You can change Link's position and (eventually) his inventory. I believe you can also change the combos on a screen, but only on Layer 0 (someone correct me on this.)

Making interesting ZScripts is going to depend in large part on knowing how to manipulate the handles you HAVE to change things you DONT normally have control over. For instance, you can't directly change Link's graphics. But by adding dummy items to his inventory, you can take advantage of Link tile offset values to do just that. You can't detect if Link's weapon is hitting an FFC, but you can use a secret flag that changes the combo associated with the FFC. And from there, you CAN detect a changed combo.

There's a heap'n help'n of potential here. We just need a larger crack team of coders to start generating some interesting things. The ZScript showcase has been really slow lately. Let's change that.

eXodus
11-26-2006, 08:52 PM
If you can get a good grasp of the logic behind NWN scripts in general, then you're more than two thirds of the way there. I'll just quickly provide some answers to the particular questions asked at the end there and how they relate to ZScript, for anyone who happens to find themselves in the same boat:


... ... ... Yes ShadowTiger. :rolleyes: (sorry, I had to do it)
StartingConditional() is what's called a function. It get's called from somewhere else (e.g.: in an if statement) and returns a value to the exact point in code it was called from. The value doesn't have to be used by anything everytime call it, mind you, so they can also be used as if they were a procedure with a return type of void. Also note that ZScript's void run() gets called by ZC when you enter the screen, so it's like the void/int main() procedure of a C/C++ program.
oPC is just like bCondition - it's a variable of type object that gets set to the return value of GetEnteringObject() as soon as it's declared.
The o prefix is an artifact of one of the many naming conventions that are floating around out there - it just means that oPC is a variable of type object. Besides that, the name PC may already be used as a type or constant somewhere, so it's best to play it safe and distinguish your variables from everything else.
The thing is, bool GetIsPC(object) is being passed a variable of type object (that oPC one that was just created) and returning whether or not that object actually refers to the PC. This is a very important distinction and is indeed relevant to ZScript, as at some point you will probably want to pass an ffc, item or npc pointer to a function so that it can move or modify only that object. Oh, and the this in "this->" is like a pre-existing object/ffc/whatever variable that points to the thing that's calling it.
The answer is yes to the last things said in both bullet points. && (boolean AND) is a completely different operator to & (bitwise AND), just as == (is equal to) is not the same as = (left becomes right). Try not to feel stupid if/when you mistakenly use = in an if statement, as it is an easy and relatively common mistake that can be hard to spot - especially if you're also familiar with other programming languages. As for the multiple lines, C/C++ ignores extraneous whitespace in general, but not all languages are quite so forgiving.


Once you know the basics of C and a little about objects and pointers, ZScript is essentially a subset of that. Then you just need to be aware of the current nuances of ZScript itself (interchangable ints and floats, Waitframe() invalidating pointers, the bug where this-> always refers to FFC 1, etc.)

C-Dawg
12-06-2006, 06:26 PM
Here's an easy thing to start with. This code will make an FFC move right.



ffc script moving_right
{
void run()
{
while(true)
{
this->X = this->X+1;
Waitframe();
}
} // end of void run
} // end of ffc script


See how that works? The code has a while loop that executes infinitely many times. (Why? Because it executes whenever the statment in parentheses is true. True is always true. So it always executes.) Each time through the loop, the X coordinate of the FFC gets increased by one. this refers to the FFC that the script is attached to. this->X points to the X coordinate of that FFC. So by putting a number into the X coordinate, Zquest takes care of moving the FFC on the screen. Nifty, huh?

The Waitframe command needs to be in there to tell the program to wait for a tic before repeating the loop. Without it, you'll create an infinite loop, because the while loop will never end and it will never wait for a tic. It'll freeze ZClassic at runtime.

Now, let's add an extra feature: let's make it move LEFT when Link pushes left.



ffc script moving_right
{
void run()
{
while(true)
{
if(Link->InputLeft)
{
this->X = this->X-1;
}
else
{
this->X = this->X+1;
}
Waitframe();
}
} // end of void run
} // end of ffc script


See what changed? In this script, there is an [if-else] statement. The script will hit the "if" and check to see if what you put in parentheses is true. Link->InputLeft is, as far as I recall, a boolean that is true if any only if the player is pushing left. In that case, the program goes ahead and executes what is in the brackets for that if statement.

If the player is NOT pushing left, the statement in the parentheses is false and so the script won't execute whats in the if brackets. In this case, it will hop over to the else immediately following the end of the brackets and do that instead.

NOTE: You don't always have to have an else part of your if statement. If you don't have an else part, the code will just do nothing if the if statement isn't true, and skip on to the next command after the end if bracket.

That's all there is to coding. Honestly. Loops and if-else statements are all that you need to know to make complicated ZScript effects. The documentation (stickied at the top of this forum) provide you with the "handles" that let you manipulate Zquest data. For instance, one of the "handles" is the X coordinate of an FFC (this->X) and another is whether or not the player is pushing an input (Link->InputLeft).

I guess there are two more things: custom variables and functions, but I'll get to those later. Or someone else can explain them.

C-Dawg
12-11-2006, 09:57 PM
I'll keep adding to this thread as people ask questions about ZScript I can answer. A question that has popped up many times is how to load completed ZScripts into your game. Here's the answer on that.

ZScript and ZASM are two different scripting languages for ZClassic. I'm only familiar with ZScript.

With ZScript, you need to save the text file (using Wordpad; nothing fancy, must be a .txt file) with a .z extension. Then, in ZQuest, go to Quest->Compile (I think it's there.) Click on "import" and select the ZScript you want. That will put the code into the buffer. Next, click on "compile." So long as the code has not errors, that'll apply the buffer's code to your game.

The next screen will ask you to assign FFC Scripts to slots. See, ZScript is programmed in modules that attach themselves to Freeform Combos. The buffer can hold many, many different FFC scripts. When you assign them to slots, you're choosing a number that will correspond to that script.

Finally, go to the screen on which you want the script to run. Create an FFC on that screen, ensure the combo selected for it is not the first combo (as this indicates to the engine that the FFC is not in use) and select which number script you want to load up into that FFC. (Bottom right corner of the first tab in the FFC editor.)

And thats all there is to it. Well, except for coding yourself.

Gleeok
11-04-2007, 02:27 AM
This thread is too buried for my taste. ;)


----------SETTING UP VARIABLES-------------


First thing you might see in a script is this:




ffc script test{

void run(){

this->Vx = -1;

while(true){

if(this->X > 200){

this->Vx = -1;
}
if(this->X < 40){

this->Vx = 1;
}
Waitframe();
}
}
}

But now let's add variables:




ffc script test{

void run(int speed_left, int speed_right){

this->Vx = -speed_left;

while(true){

if(this->X > 200){

this->Vx = -speed_left;
}
if(this->X < 40){

this->Vx = speed_right;
}
Waitframe();
}
}
}
See what we did there?

Instead of always making velocity a constant 1, we now customized it so it moves at
a different speed every time you use it in a quest.
Those are set up within the void run( variables go here){

How those variables are is set up in Zquest is under FFC arguments under D0 - D7. For speed_left and speed_right it would be D0 and D1 in that order. To make it an exact replica of the first script we would put 1.0 and 1.0 in those data slots. Exept now we have the option of making it move like a typewriter. :p All we'd have to do is set D0 to 0.5 and D1 to 3 maybe. ..Ching!



-Also if your wondering what this script does: It will make an FFC move back and forth the
length of the screen. Pretty neat huh?


Now thread...Return to the life!!!!!! Mwuhahaha!!