OFP Scripttutorial
Seite 1 von 1
OFP Scripttutorial
Da die OFP-Seiten längst die gefürchtete "404 Seuche" haben, hier alles abgesavet:
Johan Gustafsson’s
Scripting
Tutorial
2nd edition
Foreword
Welcome to my
second tutorial, the last tutorial I did, focused on basic understanding of the
editor. This time I will focus on the beautiful world of scripting. You don’t
have to print this document since you will be forced to switch between Notepad
and Operation Flashpoint all the time anyway. I assume you know enough
about the editor, using triggers, waypoints and setting up units/groups.
If you have any
questions whatsoever or just would like to send me some feedback don’t hesitate
to email me at goldsword@swipnet.se.
All the scripts
listed in this tutorial have been tested and should work fine, if you
experience problems anyway just email me. In order to answer all the emails I
would appreciate if you only asked questions regarding this tutorial. Not
everything I have written in this file might be valid, if you find anything
that doesn’t speak truth just email me and I will fix it.
Before we start I
would like to thank LustyPooh and Niosop for
their excellent work with the reference guide. I would like to give a BIG thank
you to Jaakko Jokela, Rob Cunningham, Justin Powles, Joakim
Brodin, and Mats
Johansson who have helped me creating this tutorial, thanks a
lot guys! I would also like to thank the staff at AxleOnline for
publishing this, specially Luke Jones Sexton, many
thanks! Lastly I would like to thank you for
reading this and everyone else I forgot to mention.
I’m currently a
member of the axleonline staff so you
alternatively reach me via the forum which I visit daily.
- Johan Gustafsson
aka Goldsword
Legal Notice
© Copyright2001 – 20002, Johan Gustafsson,
All Rights Reserved.
This
document may be freely distributed in any format desired so long as
the
contents are not modified beyond non-obtrusive formatting and you’ve received
permission from all the entitled authors.
Please
note that although I've made a reasonable effort to verify the
material
contained in this tutorial, I make no guarantees. The things written here are as true as I know them to be and
should not be interpreted as anything more.
You
may not use any of the files included in this package for your own economic
gain. All the files are free for the public and should stay that way.
The Codemasters Software Company and Bohemia
Interactive Studio are copyrights/trademarks of their respective owners.
Intro
I
strongly advice you to turn off words auto-spelling, you do this under “Tools”, “Spelling and Grammar” select “Options”
and uncheck the “Check for spelling while typing”. I’m swedish thus using a
swedish version of word so I just translated word by word.
What’s new?
Welcome to the 2nd
edition of the Scripting Tutorial, this version mainly contains typo fixes
since the last version but I also added two new
scripting examples.
Index
1. Setting
up external scripts
2. Getting
started
3. Moving
on
4. Advancing
5. Camera
scripting
6. Examples
Please report any error you may find so I can fix them
for future versions! I would also like you to email me and tell me what you
would like to be included in the next version.
Setting up external scripts
The first chapter
will show you how to run your scripts from the editor and where to put them. If
you already know how to implement scripts for your missions you may skip this
entire chapter.
What is a script?
Some of you have
never dealt with scripts before and perhaps wonder what a script really is. If
you are one of those guys be sure you understand this, if you already know what
a script is you can just skip this section. A script is an external file, just
like a music track, which is written in a certain language, which the game
engine can understand. A script really is like a blueprint, it will tell the
engine to do a specific task with an object at a specific time. Objects in
operation flashpoint can be anything from a weapon to a tank. When I refer to
any object I mainly talk about soldiers (units) or vehicles. So in big terms a
script is a message containing information for the game.
Why should I use
scripts when the editor lets me use triggers and waypoints?
Hey, think a
little. What really does the editor let you do? Can you tell the editor to make
a unit sit? Can you tell the editor to make a unit die? Can you tell the editor
to make a unit go kill another specific unit? Can you tell the editor you want
a RPG instead of a puny M16? Ok, some of you might say something like. “Hell
yeah, I just type in some code in the init fields or activation fields.” That’s
true, but the things you type is actually small scripts! However there are some
things that require you to use external scripting, and some scripts can get
quite large which makes it hard to deal with these in the editor. Another
drawback is the fact that you must rewrite every time (or copy/paste). It is
here the external scripting comes in handy. I prefer external scripts rather
then 5 lines of code in the init/activation fields. You also cut the amount of
triggers in your map that I think is a good thing.
Maybe I could try
scripting a little then, but how do I load an external script in the editor?
In order to get
your scripts available for the editor you must put them in the same directory
as the mission you are currently making. Say you have a script called killme.sqs
and the mission you are currently making is called ambush. Simply
put the script in the same directory as the mission file (mission.sqm)
in this case the mission file is located in the ambush.cain since you
are making a Kolgujev mission. If it had been an Everon mission the name would
have been ambush.eden and a Malden mission would be located in the ambush.abel
subdirectory. Note that these directories will end up under users//missions/
where is your name. If you summarize this it would go something
like this.
1. Make
the script. (I prefer using notepad)
2. Save
the script in your mission directory
Mission name
Eden
Malden
Kolgujev
Destert Island
Inprison
Inprison.Eden
Inprison.Abel
Inprison.Cain
Inprison.Intro
Ambush
Ambush.Eden
Ambush.Abel
Ambush.Cain
Ambush.Intro
Test
test.Eden
test.Abel
test.Cain
test.Intro
Stalker
Stalker.Eden
Stalker.Abel
Stalker.Cain
Stalker.Intro
*Here is a small
table over the directory names for the different islands. To the left is the
name of the mission, the grey cells shows the name your mission directory will
get.
That’s
easy, how do I run my script?
This can be
a little harder. Since you will make different scripts you can’t use the same
syntax. Some scripts will not take any values or objects (I will explain later)
while others will take multiples. The basic syntax however goes like this.
[arg1, arg2, argX] exec “scriptname.sqs”
This will
send the arg1, arg2 and argX to the script called scriptname.sqs. The
args can be an object, a value, a string or even an argument such as not(alive
ap) which will send the value 1 or 0 to the script (true or false). I will
explain everything later when we get to Passing values and objects to
scripts. The keyword I want you to learn however is the exec
keyword, which is short for, execute. You should use the init and activation
fields when executing your scripts.
What
exactly are the .sqs and .sqm files?
Any script
you will make has to be a .sqs file. The mission file however will be
named .sqm. Do not confuse these similar names. A rule of thumb is:
Script
starts with a s and a script file ends with a s. .sq(s)
Mission
starts with a m and a mission file ends with a m. .sq(m)
Getting
started
In this chapter we will learn how to write small
basic scripts also taking a look at some basic keywords and commands. We will
learn how to call different types of scripts from the editor. I highly
recommend you to write all the scripts yourself and not to use the ones I gave
you since it will be easier to learn this way.
Your
first script!
We will
begin by creating a small script named hello.sqs; the script will not
take any values or objects. It will simply print a message on the screen
saying: “Hello!”
Open
notepad and write the following. (Please do not cut & paste, since you will
learn better when writing yourself, trust me on this one)
Hello.sqs
; My first script will show a message for the
user.
; Say Hello!
TitleText [“Hello!”, ”plain down”]
; Exit
Exit
That’s it!
The first you should learn is the ;. This symbol will let you print
remarks and notes. This will only work when the ; is placed in the
beginning of a line. Everything after the symbol will be ignored. It is a good
scripting practice putting remarks since it will make the code much easier to
understand for others. If you write really big scripts it is also a good thing
to add divide mark. However, when you put the ; after a command, such as
the TitleText [“Hello!”,”plain down”] you will tell the reader (the part
of the game engine which reads the script) that you would like to begin a new
command. So the following code would produce the exact same result.
; My first script will show a message for the
user.
; Say hello and exit
TitleText
[“Hello!”,”plain down”];exit
The reader will not complain about uppercase
and lowercase, so you could write the command TitleText as TiTLeTExt. This
doesn’t look good though and is very hard to read so stick with the TitleText,
or titleText versions. This rule also applies to variable names and strings. I
often put an uppercase character for each word in the keyword and stick with
lowercase for my variables. Now lets take a look at the TitleText keyword; this
is a keyword that will print text on different parts of the screen supplied by
the last statement “plain down”. I strongly recommend you to have Lustypooh’s
and Niosop’s editing reference at hand when reading this tutorial since I won’t
explain all the different commands you can give the keywords. “plain down”
However will make the text appear at the lower portion of the screen.
Ok, now the script is done and ready to be
executed. Start up ofp, select any of the islands and put down a single unit,
this should be the player. Now in the init field type:
[] exec “hello.sqs”
Before you
try the mission don’t forget to put the script file in the same directory! Save
the game and look up the mission directory. Put the hello.sqs file here
and return to the editor. Now press Preview, cool huh! Well not exactly, but it
is your first script and you should be proud about it. The same effect could be achieved by using
the Effects options under triggers. It is not a good practice putting
this kind of script under an unit. It is much better if you create a trigger
and set the condition to true, and then type the same code in the activation
field. Now lets discuss the code we wrote. Note the [] which is used for
vectors (arrays) here I left it blank which tells the reader that the script
doesn’t take any arguments. You could have placed nearly anything here since
the script won’t do anything with that anyway. However this would require more
code and make the mission harder to understand. The exec keyword will
tell the reader to execute our script hello.sqs.
Passing strings to a script
A script
that doesn’t take anything doesn’t really make you that horny (or does it?). So
let’s add some code to the script that will enable the user to control what to
print.
Print.sqs
; A script that will print a string
; Get the message to print
_msg = _this select 0
; print it!
TitleText [_msg,"plain down"]
exit
Okay, first
we will take a look at the _msg = _this select 0 line. _msg is
our variable which is and must be a string since the TitleText keyword only
handles strings (There is a way to get around this which we will learn later)
It will be created as soon as the reader detects it for the first time. The _
is a way to tell the reader that the variable we whish to create should be a
local one. A local variable is only visible within the scope where it got
declared. In this case the whole script is the scope. If you didn’t had the _
sign before the variable name it would be created as a global variable, which
makes it visible for the entire game. The name msg is just a name I
choose, you could have named it message or even dgshjdhk but that
would not make life easier for you or the ones reading your scripts later on.
The keyword _this is a special one. It contains the object, which you
are currently dealing with in this case the vector, which we will pass to the
script later on. When you create a trigger the this keyword refers to the
trigger’s condition. The select keyword is really useful. It will
retrieve a certain value or object from an array. When the reader reads the
execute line it will create a vector containing all the arguments and send it
to the script. The number after the select keyword will tell the reader which
element in the array to fetch. I made an illustration of the _msg = _this
select 0 line below. The other new thing we wrote is _msg instead of “Hello!”
within the TitleText command. Since _msg should contain a string this will make
the TitleText print the contents of _msg. If msg contained a number this
wouldn’t work since TitleText expects a string. Also note that if you stuck
with the “ signs and printed TitleText [“_msg”,”plain down] you would
get quite angry when the script would print
“_msg “
instead of the contents of the _msg string. If you are new to this kind of
stuff you might get really confused. Don’t worry though; you will understand
more and more the more scripts we write.
From here on I will not tell you to put
the script files into the mission directory you will have to do that yourself.
Now switch
back to ofp by using the alt + tab command and open up the player unit
options. Locate the init field and change the line to.
[“Hello,
I’m so Cool!”] exec “print.sqs”
Now save
and run! Wasn’t that just great? Try changing the command to
[100] exec
“print.sqs”
That would
result in an error, look in the top left portion of your screen and note what
is says. It should say something like expected string. Remember the
thing that I said earlier about Titletext only working with strings? This is
the reason why we can’t print numbers. I will teach you a way around this later
on though. Meanwhile try studying this little flow chart I made.
[“Hello,
I’m so Cool!”] exec “print.sqs”
Send
the contents of _msg to the function Titletext.
_msg =
“Hello, I’m so Cool!”
_msg =
_this select 0
Moving
on
Here comes the funny part, in this chapter we will
be focusing on scripts that won’t close until a certain thing has happened. We
will take a look at passing objects to scripts, creating loops and checking
values.
Passing an object to a script
Passing
objects to a script works exactly the same way as passing strings instead of
passing a string though you simply type in the name of the object you wish to
pass. If you would like to pass a value you just type in that value. Take a
look at this script and it will become clear.
Objpass.sqs
; Sample of passing an object to a script
; Get the object
_obj = _this select 0
; Make the unit do a push up
_obj SwitchMove “FXStandDip”
exit
This script
will take one object, a soldier since I’m using the SwitchMove command. SwitchMove
by the way is a keyword that will play different animations or set different
poses. Notice the _obj = _this select 0. It’s exactly the same line used
when getting a string. How does the reader know it’s a string or not then? When
you pass a string you use the quote (“) sign, when passing an object you
use the object name. Lets test the script by creating a map where the player is
close enough to another soldier. Name the soldier guy and use the
following code to invoke the script.
[guy] exec “objpass.sqs”
This will
pass the object named guy to the script, here he will get a new name, which is
_obj. There is only one guy though, but there may be many _obj objects
since they are local. Another good thing about passing objects in the init
field is the fact that you can use the this keyword. Since this in this
case (hehe) points to the guy named guy (Stop! ok) the following code would
work fine. Actually it’s better, this way you don’t have to name the unit and
you can use paste if you wanted to run the script for another soldier.
[this] exec “objpass.sqs”
Passing
objects to a script is really easy and you should not have any problems with
it. One thing to keep in mind though is naming the objects and strings in the
script in a way that will help you remember what is an object and what is not
an object. I often start object names with an uppercase letter (no need in
names like _object and _obj) and only use lowercase for strings and variables.
In time you will develop your own script style.
Multiple argument passing and loops
Now lets
continue by creating our first script that won’t exit right away. The script we
will create us quite fun. The script will blow up a certain vehicle whenever a
unit gets into it. The script must therefore take two objects, the unit and the
vehicle. It most also stay active until the unit gets into the vehicle and then
destroy it. Take a look at this.
Blowup.sqs
; A small script that will blow up a vehicle as
soon as a specific unit gets into it
; Get the unit that will be affected
_Unit = _this select 0
; Get the vehicle that will be affected
_Vehicle = _this select 1
; Main loop
#Update
; Check if the unit is in the vehicle
? (_Unit in _Vehicle) : goto "BlowUp"
; Wait 2 seconds
~2
goto "Update"
; The unit must be in the vehicle
#BlowUp
; Destroy the vehicle
_Vehicle setdammage 1
exit
Ok, where
do I start? A good place would be explaining the top lines. So far you have
only seen how to get one object or value, but here I get two. _Unit gets to be
the first element in the passed array and _Vehicle gets to be the second. Like
in most programming languages you start to count at 0. Next comes a new thingy:
#Update this is a so-called Label, if you are an ASM programmer
or a Basic freak you probably already knows about these (we c++ dudes never use
this kind of ugly code, right?) A label is like a tag you assign a specific
line in your script. This way you can jump to this spot whenever you would like
to, by using the goto keyword. Note that the label is defined by a #
and no quotes while the goto demands you to put the label’s name in quotes and
cut the #. The good thing about this is you can repeat code. In this
case we jump between the #Update and goto “Update” lines, until the unit steps
into the vehicle. The line ? (_Unit in _Vehicle) : goto “BlowUp” is a little
more advanced. The basic syntax is.
? Condition : On Activation
The ? works
just like the word if and checks weather a condition is true or not. If
it is true the code after the : will be executed. The in keyword
has nothing to do with the two operators ? : instead it will check weather a
unit is in a vehicle (or in a vector). In this case we check if the object
named _Unit is in the object named _Vehicle. I put the condition in parentheses
since this makes the code easier to read. So this line will make the line goto
“BlowUp” execute first and only when the condition (_Unit in _Vehicle) return
true. You could invert this by using the ! operator. It works exactly
like the English word NOT and will invert the condition. This would not
be any good here since we want the car to blow-up when the unit is within it,
not when it isn’t. Anyway here is what you should have typed if you wanted to
execute the goto “BlowUp” line whenever the unit wasn’t in the car.
? !(_Unit in _Vehicle) : goto “BlowUp”
In order to
check values you must also learn the different check operators. Take a look at
the following table.
Operator
==
equal to
!=
not equal to
>
greater then
>=
greater or equal to
<
lesser then
=<
lesser or equal to
Test
A == B
A != B
A > B
A => B
A < B
A <= B
If a > b
False
True
True
True
False
False
If b > a
False
True
False
False
True
True
If a = b
True
False
False
True
False
True
Invert =
A =! B
A == B
A <= B
A < B
A >= B
A > B
* The blue text will be the value the check
will return.
You may
also use logical operators such as and and or. If you like
English the keyword is and for and, and or for or (Didn’t I say
stop it?) Other peoples such as C++/C dudes might as well use the cooler
looking && and || operators. Where &&
stands for and, and || stands for or. Using logical operators should be
second nature because they will give you the possibility to do some really
complex stuff. Next up is the ~2 line. This will simply wait 2 seconds
before continuing with the script. This is really important since if we didn’t
have this then the script would get stuck in an infinitive loop and never let
the control over to the game engine. The last new thing for us is the setdammage
command, which is really easy to handle. It will set the health (armour) for a
specific soldier or vehicle. In this case it will set damage level 1 to the _Vehicle
object. Since the range for damage is 0 – 1 where 1 being dead and 0 being not
taken a scratch the _Vehicle will get destroyed.
When
testing the script first create your map and put down a player, name it ap.
Next create a M1A1 (good explosion) as an empty object close to the player. In
the init field for the tank put the code.
[ap, this] exec “blowup.sqs”
Play the
map and have fun!
Scripting Challenges
Create a script called carbomb.sqs which will take two values.
First the unit which must get into the vehicle and second the vehicle which
will be equipped with a bomb. The script should detect weather anybody is in
the car and if the car has a speed greater then 5 (> 5). When it does it
should be a 5 second delay before the car (or tank) blows-up.
Hint: Use the logical and operator (&& or and)
together with the in and speed keyword. The speed keyword has the
following syntax and will return the current speed for a vehicle. The speed is
in km/h.
speed _object
Advancing
This chapter will teach you have to call other
scripts from another script. This way you can chain different script, I will
also show you a way to use vectors as argument passing. It will also cover some
new keywords such as format that will let you show variables!
Chaining scripts
Sometimes you might want a script to kick in
when another script is done, this is very easy to implement since you can use
the exec keyword in scripts as well. I will show you an example.
The script is called countdown.sqs and
will show a countdown timer. When the timer reaches zero it will call another
script that takes no parameters.
Countdown.sqs
; Get the different passed data
_timeleft = _this select 0
_caption = _this select 1
_location = _this select 2
_script = _this select 3
#Update
; Wait 1 second
~1
; Decrease the timer
_timeleft = _timeleft - 1
; Check whether the time is 0
? (_timeleft == 0) : goto "Done"
; Show the time left
TitleText [format [_caption + " %1",
_timeleft], _location]
goto "Update"
#Done
; Execute the script
[] exec _script
exit
The main
part I want you to focus on is the second last line and the TitleText line. The
TitleText command looks really awful and is quite messy, this is because of the
other keyword in it which name is format. This is a really important
keyword that will let you to convert variables (numbers) to strings (text). The
syntax is as follows.
Format [“Text %1 %2 %X”, var1, var2, varX]
Where
“Text” can be a normal string, inside it you can put %x (x = a number)
which means it will convert the x’th variable defined to a string and show it.
You put the variable you want to show after the string. So if you would like to
show a variable named _test you could do like this.
Titletext [Format [“Test is: %1”, _test],”plain down”]
Note - you
put the %x within the quotes. Format doesn’t print anything itself instead it
returns the complete string. In this case the keyword TitleText uses it for
showing “Test is: 5” if _test had the value of 5. This is really good when
making multiplayer levels and you want to show scores or something like that.
Another thing is the _location string we use instead of the “plain down”
this will let the user decide where to put the text by sending for instance
“plain” or “plain down” as the argument.
The line []
exec _script will execute the script named _script, which was passed by the
user and stored into the string _script. In order to test our script we must
use a script that does not take anything. Do we have one? Yes the hello.sqs
will be perfect.
Create a
new map, place the player and add the following code in the init field.
[10, “Time Left:”, “plain”, “hello.sqs”] exec “countdown.sqs”
Test the
mission, you should see a countdown, starting from 10 (you will only see 9 to 1
though) and counting down to 0. When it reaches 0 the text “Hello!” should be
displayed.
Passing parameters in chains
What if we
needed to call another script, maybe one that takes parameters? This could be
achieved by passing a vector with the first script. Take a look at this and you
will see what I mean.
Chain.sqs
_script = _this select 0
_vektor = _this select 1
~2
TitleText ["Look!", "Plain
Down"]
~2
; Get the number of elements in the vektor
_elements = count _vektor
; Print how many arguments you passed
TitleText [Format["You passed: %1 arguments.",
_elements],"Plain Down"]
~3
; Passing the show over to the script, also
passing arguments!
_vektor exec _script
exit
This script
will use a new keyword, and that is the count keyword that will return
the number of elements within an array. Here we assign it to the variable _elements
that we use to show the user how many elements he passed (Just for fun sake).
The neat part about this script however is the last line before the exit _vector
exec _script. This will pass the whole vector to the script. Since the
vector will contain something like [“This is just a test”, 100] and the _script
will contain a string like “test.sqs” the reader will read the following.
[“This is
just a test”, 100] exec “test.sqs”
Which is
perfectly legal. This way you can chain every script you got with another, not
mattering weather the script wants 6 arguments or none at all. We can test this
by creating a mission. Plot your player and add the following in the init
field.
[“countdown.sqs”,[10,”Time Left:”, ”plain”, ”hello.sqs”]] exec
“chain.sqs”
It looks
kinda complex but the additional []’s
is just another array we pass, perfectly safe since the chain script wants
a vector as its second argument. The fact that the vector looks exactly like a
call to the countdown script makes things even better. Remember that the
countdown script also takes a script that it will run when done? Well, here I
choose to run the hello script again. When you test the level it should first
execute the chain script that will print “Look!” and then execute the countdown.sqs
file which we created earlier. The chain script then passes the entire array to
countdown, which uses this as its arguments and countdown from 10. When done it
will use the last element in the vector we passed to execute the script named hello.sqs.
This script will print “Hello!”. If you don’t really understand the logic about
this don’t worry, try experimenting with the chain script. Testing it with
different scripts we’ve done, sooner or later you will say “Aha!” and you will
start making your own chaining scripts.
Scripting
Challenges
Implement a
chain handler for the countdown script. Name the new countdown to countdown2.sqs.
It should work exactly like the old countdown but that supports chained scripts
with arguments like the chain.sqs. So when you call the countdown2
script you should be able to do the following.
[10,”Time Left To Say Hello:”, ”plain”, ”hello.sqs”,[]] exec
“countdown2.sqs”
or
[10,”Time Left:”, ”plain”,
”countdown.sqs”,[10,”Yupp:”,”plain”,”hello.sqs”]] exec “countdown2.sqs”
Camera
scripting
In this chapter we will learn how to use the camera
in Operation flashpoint. The chapter will show us how to initialise and use our
own cameras. It does also contain many examples on how to use the camera for
creating small scripted scenes.
Re: OFP Scripttutorial
The camera
Operation
flashpoint took a route I think was great, instead of using 100 mb AVI films
they choose to use the powerful scripting language for the movies. This is
probably together with the ai-voice engine the reason why the game fitted into
1 cd! Making cut scenes is where scripting comes in handy. You get total
control over the camera and can do whatever you like. Before we rush ahead and
start create our own cut scenes we must first learn the different camera
commands. I will start with the most important ones. The script I will
demonstrate this on is called fcam.sqs and will create a camera that
will look at the player.
Fcam.sqs
_Object = _this select 0
_camx = getpos _Object select 0
_camy = getpos _Object select 1
_camz = getpos _Object select 2
; Create a camera and place it 5 meters from
the object 1 meter above the ground
_cam = "camera" CamCreate
[_camx,_camy+5,_camz+1]
; Point the camera to the passed object
_cam CamSetTarget _Object
; Set an effect for the camera
_cam CameraEffect
["Internal","Back"]
; Apply the changes!
_cam CamCommit 0
~10
_cam CameraEffect
["Terminate","Back"]
CamDestroy _cam
Exit
As you can
see there are many new keywords and commands to learn from this script. We will
begin with the first, CamCreate. CamCreate works in a quite strange way,
it will take 3 numbers in a vector and a string. The string must be written
before the command and should contain either Camera or Seagull.
The string will tell the CamCreate function which camera type to create.
“Camera” will tell CamCreate to create a normal camera, if you use “Seagull” it
will create a bird type of camera, which I won’t take up in this tutorial. The
vector you supply after the command must contain the x,y and z coordinates for
the camera. Here I really mean the camera and not where it will point. In this
script I used the coordinates for the passed object. We will take a look at the
GetPos command later. I also used a little bit of algebra to set the
camera 5 meters away from the player. Remember that y is the vertical
coordinate (North and South) thus the camera will be placed 5 meters to the
north of the object. I also added 1 to the z coordinate so the camera won’t be
placed on the ground. Lastly I created a new local object named _cam
which will be our camera object, you could think of this as our new NIKON F5
(a camera).
Maybe we
should take a look at the GetPos keyword before you starting to get all
crazy. GetPos will simply return a vector containing the x,y and z coordinates
for an object. Here we used it to get the coordinates for the passed object
which we named _object. We stored each one into a variable which we then used
to set the cameras position. Since it will return a vector we used the select
keyword to get the element we wanted. A very useful command which you will use
daily.
Lets get
back to the camera keywords, next up is the CamSetTarget keyword which
comes in two versions. The one we used took an object as the argument, when we
pass objects to the CamSetTarget function it will automatically locate the x,y
and z coordinates for the object. If this wasn’t enough it will also update the
values if the object moves! The other version will take a vector containing x,y
and z coordinates for a spot. (Yes you could use the variables we created
earlier since they would point to our object.) What does CamSetTarget do with
the coordinates then? It will automatically point the camera to these
coordinates, it will not move the camera if something is in the way so don’t
expect too much.
Next up is
the CameraEffect command which I don’t really know that much about. The
only arguments which seems to work are “Internal” and “Terminate”. I think
Internal will place the view-port inside the camera (as we would like) but I’m
not sure. Terminate will delete the effect and hand over the camera control to
the player. The second argument we pass is the position of the camera relative
to our target. However this doesn’t seem to do very much if the camera got a
target. I think the CameraEffect function is quite useless when you are
scripting your own movies, in the editor it works fine though. The bad thing is
we cant delete this, it is essential for our script to work at all. Just place
the _CameraName CameraEffect [“Internal”, “Back”] every time you whish to do
something with the camera (BIs did it).
CamCommit we simply apply all the changes we
have done to the camera, if we didn’t use this nothing would have happened.
Remember this, when you change something with the camera, position, target or
whatever you must use the CamCommit keyword. There is one exception though
which has to do with the CamSetTarget funtion. When we pass an object to the
CamSetTarget function it will automatically keep track of the position of it and
thus change the location of the cameras focus. So we don’t have to call
CamCommit as soon as an object has moved. The number specified after CamCommit
is the delay in seconds before the camera moves into position. Zero will make
an immediate jump.
One last
thing about the CameraEffect, you might noticed that we used “Terminate” at the
bottom lines, this will tell the reader to terminate the current camera and
return the control back to the player camera. It will not delete the
camera object though, so we could use the _cam CameraEffect [“Internal”,”Back”]
after the termination to bring the control back to our camera (we must also use
a CamComitt though).
The last
keyword is the CamDestroy which will simply destroy our camera object.
This keyword makes my head go crazy because I can still use the camera
afterwards. I thought It would destroy the camera. Probably it has something to
do with returning memory, but why can we use the camera afterwards then? And
shouldn’t a local variable get destroyed when the scope ends? If it’s a class
shouldn’t a de-constructor kick in? If you have the answer please email me so I
could fix this. Anyway, it is better to call this function since they created
it, it must do something right?
So, lets
test our script. Create your player and put this line in the init field.
[this] exec “fcam.sqs”
You should
now see your player from a good 5 meters away. If you like to script cut scenes
please stay tuned for their will be more to come!
Another camera script
Lets
continue our journey in the beautiful scripting language with a script that
actually does something fancy. The script we will create will introduce to you
some new keywords which might be good to know when dealing with cameras. The
script will simulate a recon soldier that spots a soldier. The soldier is of
the friendly type and will start to make some pushups when the player spots
him. (Of course you can use an enemy soldier) The script is fairly large but
nothing compared to what real cut scene scripts would be.
Camera.sqs
_CameraMan = _this select 0
_Actor = _this select 1
; Get the Cameraman's postions
_CameraPos = getpos _CameraMan
_cmx = _CameraPos select 0
_cmy = _CameraPos select 1
_cmz = _CameraPos select 2
; Set the camera at the Cameraman
_cam = "camera" CamCreate
[_cmx,_cmy,_cmz+2]
; Point the camera to a unit
_cam CamSetTarget _Actor
_cam CameraEffect
["Internal","Back"]
; Set the active camera
_cam CamCommit 0
@CamCommitted _cam
; Add Binocular view
CutRSC ["Binocular","Plain
Down",100]
_Zoom = 0.7
#ZoomIn
; Zoom in
_zoom = _zoom - 0.01
; Set the Field of view
_cam CamSetFov _zoom
; Apply changes
_cam CamCommit 0
~0.01
? _zoom < 0.1 : goto "ZoomOutWait"
goto "ZoomIn"
#ZoomOutWait
; Force the unit to do a pushup!
_Actor PlayMove "FXStandDip"
~8
#ZoomOut
; Zoom out
_zoom = _zoom + 0.01
_cam CamSetFov _zoom
_cam CamCommit 0
~0.01
? _zoom > 0.7 : goto "Done"
goto "ZoomOut"
#Done
~2
; Give the control back to the player, and
remove the binocular view
CutRSC ["Default","Plain
Down",100]
_cam CameraEffect ["Terminate","Back"]
CamDestroy _cam
Exit
Ok, before
I explain what the script does and how it does please go a head and run the
script. Place your player somewhere on the map, name him ap. Place another
Western soldier somewhere else within the line of sight of the player. It can
be a good distance between the two though. In the init field of the other
soldier add.
[ap, this] exec “camera.sqs”
Did you
like what you saw? If you didn’t I’m sorry I couldn’t make a funnier script.
Now, I will
start explaining what the script will do in regular terms. When you call the
script it will take two parameters, one being an object acting as a camera man
(Location for the camera) the other being the target which the camera will
point to. The script will then set up a camera and make it point to the
soldier, it will then start zooming a little in on the soldier which starts to
do a pushup. (Here you could have used the objpass.sqs, passing the _Actor)
When the
zoom in is done the script will zoom out and exit. Now lets explain how it is
done.
Firstly you
might notice a slight change I made when fetching the position of the camera
man. Here I created a vector called CameraPos, since the GetPos keyword
will return a vector the CameraPos vector will get the current positions of the
camera man. Then when I fetch the x,y and z values for the camera man I use the
vector instead of the GetPos function. This way I can do the same thing a
little faster since I don’t have to use that many function calls to GetPos.
Here I use 1 instead of 3.
Next is the
@CamCommitted keyword which might be new to you. The keyword is actually
just CamCommitted and will return true or false depending on the status
of the camera. If the camera is ready (in place) it will return true otherwise
false. Since I used CamCommit 0 it will return true directly since their will
be no delay. The @ (at sign) will make the script wait until the
CamCommitted function return true. So @ works exactly in the same manner as ~
except it will check for a condition rather then keeping track of some time.
Here I used it only for show. You can use @ with any other boolean function
such as in,alive or regular checks like _adam == _eve. A
handy little thing it is.
Now comes
the part where I set the screen to look as using binoculars. This is achieved
by using the CutRSC (Cut Resource) keyword. I send to it the Resource I
would like to use. In this case “Binocular”. The other thing I send with it
isn’t really used here since I use a
resource. If you just passed a string, however the next argument will decide
where to write the text. I have no clue what the last argument does. Just give
it a 100 and it will be happy (Bis did it).
Next I
create a local variable which I give the name _zoom. This will be used
later on when we zoom the view.
The label
called “ZoomIn” works as a for loop, it will do three basic things.
First it will decrease the _zoom variable with 0.01 which gives a smooth zoom.
Second it will check weather _zoom has reached the lower limit 0.1, if it has
it will exit the loop. Lastly it will update the camera, it will only update
the camera after we apply the zooming using the CamSetFov (Field of
view) keyword. CamSetFov will take 1 parameter, the FOV value or zoom value to
use. I think the range is 0 - >1. Where a value less then 1 will zoom in and
higher will zoom out. The regular FOV for a soldier is 0.7, that’s why we gave
that value when we created the _zoom variable. When the ZoomIn loops ends the
script will pause for 8 seconds and tell the _Actor to do a pushup. Then it
will enter the “ZoomOut” loop which will do the opposite of the “ZoomIn” loop.
At the end
of the script we will set the view-port back to normal by passing “Default” to
the CutRSC function. It will also Terminate the camera so the control goes back
to the player.
Another script
Before we
move on with this tutorial I will show you one last cut scene script. This
script will show a soldier speaking in Russian. The thing he will say is not
important, actually I just took some random sounds from the stringtable. So if
you understand Russian this script will probably show you some quite weird and
funny results. Anyway, the script will require you to pass two objects. The
first being the unit that will do the talking the other will be the camera. The
script will demonstrate three new keywords, SetMimic, say and CutObj.
Here is the script.
Cutscene.sqs
_CameraObj = _this select 0
_Actor = _this select 1
_CamPos = GetPos _CameraObj
_cx = _CamPos select 0
_cy = _CamPos select 1
_cz = _CamPos select 2
; Makes the screen look like a tv
CutObj ["TvSet","Plain
Down",100]
; Initialize our camera
_Camera = "Camera" CamCreate
[_cx,_cy,_cz+1.25]
_Camera CamSetTarget _Actor
_Camera CameraEffect
["Internal","Back"]
_Camera CamCommit 0
@CamCommitted _Camera
; The lines below will make the actor talk and
change his face expression
_Actor PlayMove "StandStraight"
_Actor SetMimic "Happy"
_Actor Say "RUS2"
~2
_Actor Say "RUS7"
_Actor SetMimic "Angry"
~3
_Actor Say "RUS12"
~2
_Actor Say "RUS9"
~2
_Actor SetMimic "Ironic"
_Actor Say "RUS20"
~3
_Actor SetMimic "Angry"
_Actor Say "RUS14"
~5
; Remove the camera
CutRSC ["Default","Plain
Down",100]
_Camera CameraEffect
["Terminate","Back"]
CamDestroy
_Camera
exit
As you can
see the CutObj keyword looks exactly like the CutRsc keyword but
will enable you to set the screen to look as a TV. You can’t use the “Default”
command to CutObj though you will have to use CutRSC to bring the screen back
to normal.
You might
wonder why I increment the z value for the camera with 1.25. This is because we
will use this script using a static camera object as the camera, 1.25 is the
approximate height of that object.
The keyword
SetMimic is really useful when creating cut scenes because it will allow
us to set different face expressions for an unit. Here I use the “angry” and
“ironic” command which will of course set the expression to angry and ironic.
Say is
a little harder to use though. There is no such keyword as Said or SayDone. So we
can’t time different speeches with the @ command. You will simply have to tweak
until you get a good pause between the sounds. When you use say you will pass a
string containing the
Identifier
for the string. I use pre-made sounds which I looked up in the stringtable.csv
file. If you scroll down a little in Excel or whatever program you are using
for viewing the file you should see something like:
STR_CFG_SOUNDS_RUS5,Russian -
Damn,Russe - Bon sang,Russo - Dannazione,Ruso: ¡Maldita sea!,Russisch - erdammt,Russian - Damn
STR_CFG_SOUNDS_RUS5 is the identifier for the sound.
The things after is only remarks for helping you to remember what the sound
will sound like. You should not use the whole identifier when referring to the
sound file. If you take a look at my script I only included the last little snippet
RUS5 or whatever number I used. You can also create your own sound
files. However I will not take up this subject here because it really doesn’t
have anything to do with scripting and second I don’t really know how. The good
thing about Say is that the unit which will speak will automatically move his
lips!
Okay, now
lets test our script. Start by creating your player. Next create a “Tent Open”
object somewhere on some flat ground.
Add a “Camera” object within the tent, name it “Camera”. Add a Russian officer
somewhere in the tent and make the Officer face the camera and the camera face
him. In his init field of the officer add the line.
[Camera, this] exec “cutscene.sqs”
Now press
the preview button and watch the little stupid movie. You may have to tweak
quite a bit before getting a good looking movie. As you can see you can create
some quite good cut scenes with the help of scripting. Try making this in the
regular editor! That’s about all I had to say about cut scene scripting.
Examples
This chapter is full of different scripting examples, each teaching you
something new. This chapter was also written to give you a hint of when to use
scripts and how to implement them. At the end of this chapter there will be a
couple of scripting challenges for you to write. Happy scripting!
The scripts
you will encounter during this chapter will come in different versions. We
begin by writing the first version which we name1.sqs. We discuss
the script and try finding something we could improve. When we do, we write
another script and name that one to2.sqs until we get to the last
version which will be named.sqs. This way you will learn how to
improve your scripts, what is a good habit and what is not. You will also get
more time together with the examples which is a good thing.
Inzone.sqs
The first
script we will study is the Inzone.sqs the script will check weather a
specific unit is within a defined zone. As soon as the unit is, the script will
run another script using vector passing. When using the script you must create
2 objects, one being the unit to keep track of and the other being the centre
of the zone.
Inzone1.sqs
_Object = _this select 0
_Centre = _this select 1
_radius = _this select 2
_script = _this select 3
_vektor = _this select 4
#Update
? (_Object Distance _Centre) <= _radius :
_vektor exec _script; exit
~2
goto
"Update"
This is our
first version of the inzone.sqs. This version uses a new keyword, Distance.
This keyword will return the distance in meters of two objects. We use this to
check weather the _Object is within a certain amount of meters from the center
object. If it is, the passed script will be executed. Their shouldn’t be any
problem understanding this script so we will begin improving immediately. We
start off by asking ourselves one thing.
- What does the script currently
decide itself?
The answer
to this question my friend lies in the 9th line of the script. ~2
is the only constant in this script. The script will always check every 2
seconds weather the passed object is within the zone. This can be quite
annoying if the user only needs to do the check every 10th seconds
and is forced to call the script 5 times per 10 seconds instead of one. This
can really slow down the performance. What to do? Well this is easily fixed! We
simply add another argument to the script which will be the updating frequency.
Inzone2.sqs will show you the way.
Inzone2.sqs
_Object = _this select 0
_Centre = _this select 1
_radius = _this select 2
_freq = _this select 3
_script = _this select 4
_vektor = _this select 5
#Update
? (_Object Distance _Centre) <= _radius :
_vektor exec _script; exit
~_freq
goto
"Update"
Here you
see that we fetch another value, the _freq variable will hold the passed delay
between the check and use it in conjunction with the ~ operator. There is still
a problem with this script though. The scrip will terminate itself when the
condition is met. This could cause a problem if the user wanted use this script
to add points for the player or something else like that. Lets fix this by
adding yet another argument.
Inzone3.sqs
_Object = _this select 0
_Centre = _this select 1
_radius = _this select 2
_freq = _this select 3
_script = _this select 4
_vektor = _this select 5
_life = _this select 6
#Update
~_freq
? (_Object Distance _Centre) <= _radius :
goto "InZone"
goto "Update"
#InZone
? (_life == "LOOP") : _vektor exec
_script; goto "Update"
? (_life == "ONCE") : _vektor exec
_script
exit
Okay, this
script is a little longer but a little better. First of all we created another
string which we named _life. This is later on used to determine weather
or not to exit the script. The #InZone label will check if the user passed
“ONCE” or “LOOP”. If the user didn’t pass any of these the script will exit
without even calling the other script. This script will work, but it will be a
little slow and a little less flexible then what we want. Lets rewrite it the
right way! The script you will see next is actually the finished version and is
therefore named InZone.sqs
Inzone.sqs
_Object = _this select 0
_Centre = _this select 1
_radius = _this select 2
_freq = _this select 3
_script = _this select 4
_vektor = _this select 5
_life = _this select 6
#Update
~_freq
? (_Object Distance _Centre) <= _radius :
goto "InZone"
goto "Update"
#InZone
_vektor exec _script
? (_life == "LOOP") : goto
"Update"
exit
Hey, wait a
little. You didn’t change anything except those last lines! Yes, but take a
deep look and you will notice that I accomplished two things by just this
slight change. First of all I made it to work even if the user didn’t pass
“ONCE”. The other script will be executed and the script will be terminated. I
also took out an additionally check which will speed up the script a little.
Remember to read your scripts careful, you might find something hidden
somewhere.
Lets test
our script shall we? Create a player and a Flagpole. In the init field of the
flagpole type in the following.
[Player, this, 100, 1, “print.sqs”, [“In Zone!”], “LOOP”] exec
“inzone.sqs”
The object
named Player is a predefined object that points to the player. It is a
global one so you can use Player anywhere! No more passing the player value!
Place the player about 120 meters from the flagpole (Use a trigger to check the
range) and press preview.
Barrage.sqs
The next
script we will take a close look upon will be really fun to make. The script
will create an artillery barrage. To use the script the user must create a
marker, designating where to fire, an object used to create the explosion. Here
is the first version of the script.
Barrage1.sqs
_Actor = _this select 0
_Centre = _this select 1
_oldx = GetPos _Actor select 0
_oldy = GetPos _Actor select 1
_oldz = GetPos _Actor select 2
#Update
_cx = GetMarkerPos _Centre select 0
_cy = GetMarkerPos _Centre select 1
_cz = GetMarkerPos _Centre select 2
_cx = _cx + Random(200) - 100
_cy = _cy + Random(200) - 100
_Actor SetPos [_cx,_cy,_cz]
_Actor SetDammage 1
~0.01
_Actor SetPos [_oldx,_oldy,_oldz]
_Actor SetDammage 0
~1
goto
"Update"
This will
create a nice artillery fire over a 200 m2 area (Max 100 meters from
the centre). The script needs a little polish up though. First of all the
script is way to slow, I don’t even know if this script would be run-able in a well
populated mission. Before I go on and fix this though we should take a quick
look at the new keyword I use. GetMarkerPos works exactly like the
GetPos, except it will use markers instead of objects. We could not use markers
in the last script because the Distance keyword only works with objects
and I don’t know how to convert these two. GetMarkerPos will simply return a
vector containing the x,y and z coordinates for the marker. Now when you know
what that keyword was lets speed up the code a little bit. Ooops, almost
forgot, hehe. You might wonder what Random does. Well random is a great
function that will return a random number ranging from 1 to whatever you like.
Here I use 200, so the number returned would be 1 – 200. I also subtract 100 so
the range now goes from. –99 to 100. Hmm, that makes a 99 * 100 “square” which
I must confess isn’t 200 m2 but it is close enough.
Barrage2.sqs
_Actor = _this select 0
_Centre = _this select 1
_ActorPos = GetPos _Actor
_oldx = _ActorPos select 0
_oldy = _ActorPos select 1
_oldz = _ActorPos select 2
_CentrePos = GetMarkerPos _Centre
_cx = _CentrePos select 0
_cy = _CentrePos select 1
#Update
_Actor SetPos [_cx + Random(200) - 100,_cy +
Random(200) - 100, 0]
_Actor SetDammage 1
~0.01
_Actor SetPos [_oldx,_oldy,_oldz]
_Actor SetDammage 0
~1
goto
"Update"
Okay, study
this closely. First of all I deleted the _cz variable since we will only
be working on the ground anyway. This will remove 1 function call. I also
placed all the GetMarkerPos statements above the loop since we only need to get
the centre once. This will really speed up things. Be sure you don’t change the
_cx and _cy variables in the loop. We also created two new
objects called _ActorPos and _CentrePos, they don’t really speed
up things much but a little, and a little can sometimes be enough.
Do you know
the big problem with our script? If you don try asking yourself the question I
mentioned earlier. You will probably come to the conclusion that the script
will force the user to create an unlimited barrage, firing every 1.01 seconds
and always within a 200 m2 area. Lets change this!
Barrage.sqs
_Actor = _this select
0
_Centre = _this
select 1
_area = _this select
2
_freq = _this select
3
_rounds = _this
select 4
_ActorPos = GetPos
_Actor
_oldx = _ActorPos
select 0
_oldy = _ActorPos
select 1
_oldz = _ActorPos
select 2
_CentrePos =
GetMarkerPos _Centre
_cx = _CentrePos
select 0
_cy = _CentrePos
select 1
_areaX2 = _area * 2
#Update
_rounds = _rounds - 1
_Actor SetPos [_cx +
Random(_areaX2) - _area,_cy + Random(_areaX2) - _area, 0]
_Actor SetDammage 1
~0.01
_Actor SetPos
[_oldx,_oldy,_oldz]
_Actor SetDammage 0
~_freq
? (_rounds > 0) :
goto "Update"
exit
This is the
final version of the barrage.sqs. This script uses the variable named _freq
to control the delay between each round. It uses _rounds to keep track
of how many shells to fire and how many that is left. If you study the line
which jumps to Update you will see that I check if rounds is greater then 0, if
it is continue to bombard else exit the script. You will also notice that I
added a variable named _area in order to specify the maximum divergence
from the centre each round could get. You might also notice that I choose not
to multiply _area with 2 each time I fire a round, instead I created another
local variable named _areaX2 which is only calculated once. This will
speed up things a little. In these kinds of scripts speed is almost everything.
That’s it! Now lets test our barrage, shall we?
Start off
by creating an empty BMP or something which
we will use as an detonator. Name it explo. (You just create your player first)
Continue by creating a marker somewhere and name it Here. In the players init
field type something like this.
[explo, “here”, 50, 0.5, 100] exec “barrage.sqs”
This will
set up an artillery barrage with 100 rounds which will fire 2 rounds each
second. The bombardment will stay within a good 50 metres from the marker. You
should place the detonator vehicle somewhere where the player won’t see it. On
some computers a BMP might be a little to big and complex to use, you might get
a slight glimpse at it when it detonates. If you experience this please use a
smaller detonator such as a M2 machine gun. I tested this script a little
longer then the other ones (wonder why? Hehe!) and I found a funny but strange
thing. When you drive with a tank trough the barrage it will not take any
damage from big vehicles such as BMP, T80, M1A1 or T60 instead it will suffer
damage from small static objects such as M2 or M2 (east) this is really strange,
but it is a good thing because M2’s will not stay visible as much as a BMP, it
is so small. It will also create the same size of explosion so that won’t be a
problem either. Just a thing to keep in mind.
Eject.sqs
Now we will
take a look at a script that will force units to eject. The first version of
the script can be found here.
Eject1.sqs
_Egrp = _this select 0
_Object = _this select 1
_GrpVektor = Units _Egrp
_numEl = count _GrpVektor
_i = 0
#Update
(_GrpVektor select _i)
action["EJECT",_Object]; UnassignVehicle (_GrpVektor select _i)
~1
_i = _i + 1
? (_i < _numEl) : goto "Update"
exit
The first
keyword we will take a look at is the Units keyword. It will take one
parameter and return a vector. The returned vector will contain all the objects
within the group we passed. Here I give all the objects within the group named _Egrp
to a vector called _GrpVektor. The other new keyword is the action
keyword that will take x parameters. The number of parameters you should pass
depends on the command you which to perform. In this case we perform the
command named “EJECT” which will make an object eject from a vehicle. When we
use eject is also require us to pass another value, the name of the vehicle.
This is why we must pass the name of the vehicle to this script.
The update
loop will simply go through each of the objects in the _GrpVektor and make it
eject. The loop will stop when all units have been ejected.
Is there a
way to improve this already tight script? Yes, sometimes you don’t want all the
objects in the group to eject, perhaps the player. So we will make up three
different constant the user must pass to the script to work.
“PLAYER” (only eject the player)
“AI” (only eject the computer
controlled players)
“ALL” (Eject everybody)
eject2.sqs
will show how to implement this.
eject2.sqs
_Egrp = _this select 0
_Object = _this select 1
_Who = _this select 2
_GrpVektor = Units _Egrp
_numEl = count _GrpVektor
_i = 0
? (_Who == "PLAYER") : Player
action["EJECT",_Object]; UnassignVehicle Player; exit
#Update
? (_Who == "AI") &&
((_GrpVektor select _i) == Player) : _i = _i + 1
? (_i == _numEl) : exit
(_GrpVektor select _i)
action["EJECT",_Object]; UnassignVehicle (_GrpVektor select _i)
_i = _i + 1
~1
? (_i < _numEl) : goto "Update"
exit
As you can
see we have added a new string named _Who, if the script detects that
_Who contains “PLAYER” it will only execute the first condition line. If the
script conatins “AI” and the current unit who is being ejected is the player
then the script won’t eject him. Instead it will increase the counter and check
weather or not the counter is still valid.
There is
one little problem left, the user can’t really decide the interval between each
drop. There is always 1 second between the drops. The last version of the script
listed below will show you have to do this.
Eject.sqs
_Egrp = _this select 0
_Object = _this select 1
_freq = _this select 2
_Who = _this select 3
_GrpVektor = Units _Egrp
_numEl = count _GrpVektor
_i = 0
? (_Who == "PLAYER") : Player
action["EJECT",_Object]; UnassignVehicle Player; exit
#Update
? (_Who == "AI") &&
((_GrpVektor select _i) == Player) : _i = _i + 1
? (_i == _numEl) : exit
(_GrpVektor select _i)
action["EJECT",_Object]; UnassignVehicle (_GrpVektor select _i)
_i = _i + 1
~_freq
? (_i < _numEl) : goto "Update"
exit
The only
real difference is the new variable named _freq, this variable will keep
track of the wanted delay between each drop.
It is now
time to test the finished script. Start by creating a group. Make one of the
group members a player and name the group to grp. Create a chopper and name
that one to Heli. Select the Heli and create a move waypoint somewhere on the
map. In the init field of the chopper type.
This
FlyInHeight 100
This will
simply make the chopper to fly to the waypoint at a altitude of 100 metres.
Create a trigger about 50x50 and place it somewhere in the path of the chopper.
In the activation field type in the text below. The trigger should have a WEST
or ANYBODY present condition.
[grp, Heli, 0.5, “AI”] exec “eject.sqs”
Now play
the mission.
There is
one important lesson to learn from this script (I learned it while writing it)
If you look at eject2.sqs and eject.sqs you will see that I abuse
the code a little by checking at the wrong place. Take a look at this little
snippet and you will see what I mean.
? (_Who == "AI")
&& ((_GrpVektor select _i) == Player) : _i = _i + 1
? (_i == _numEl) : exit
What we
would like to do is the following.
? (_Who == "AI")
&& ((_GrpVektor select _i) == Player) : _i = _i + 1; ? (_i == _numEl) :
exit
This way
the check would only be applied if the last statement was true, however on my
machine I would get an error message telling me I used a Reserved Variable.
I made the conclusion that the script can’t handle multiple conditions in one
line. This is really annoying, we could had written the script in another way
though to get around this problem, but an eject script doesn’t have to be that
fast really. Keep this in mind when making your own scripts though.
Minelay.sqs
The
next script we will take a look at is a script I named minelay.sqs it
will take four arguments, the object who will lay the mines, how many mines to
lay and the max and minimum distance from his current position he will lay the
mines. It is up to the user to make sure the object is equipped with mines
before calling this script. The first version of the script is listed below.
Minelay.sqs
_Object = _this select 0
_numMines = _this select 1
_minDist = _this select 2
_maxDist = _this select 3
; Save the current coordinates
_ObjPos = GetPos _Object
_ox = _ObjPos select 0
_oy = _ObjPos select 1
_maxDistX2 = _maxDist * 2
#Update
; Get some random positions
_nx = _ox + Random(_maxDistX2) -
_minDist
_ny = _oy + Random(_maxDistX2) -
_minDist
; Move him
_Object move [_nx, _ny, 0]
; Wait until the object is in
position
@UnitReady _Object
; Lay a mine
_Object fire ["Put",
"Mine"]
_numMines = _numMines - 1
? (_numMines > 0) : goto
"Update"
exit
Okay,
the script will start by getting the positions the unit had when the script got
executed, we then use these coordinates as the centre. The script will then
generate some random locations within a certain range area using the _minDist
and _maxDist variables. The new keyword we see here is the UnitReady
keyword which will return true or false depending on the objects current
status. If he is done with the last order, in this case the move command it
will return true. Since we use the @ keyword the script will be halted until he
reaches his designated position. Once there he will lay a mine using the fire
keyword. Fire works in a bit like the action keyword except it will only takes
strings containing firing constants. In this case we pass the strings “Put” and
“Mine”. The first string will contain the fire mode, since we often put down a
mine we use “Put” here. The second one will contain the weapon to fire.
If
you would like to test the script you will have to do the following. Create
your player in a place you will see all the action. Create an Engineer named
MineLayer, create a flagpole about 30 metres away from the minelayer. Select
the minelayer and create a move waypoint where the flagpole is located. We only
added the flagpole so you would be able to see weather the script only lay
mines within a certain range. The waypoint we is about to create should have
the following commands in the activation field.
[MineLayer, 3, 10, 20] exec “minelay.sqs”
Now
run the script and watch how the engineer put down 3 mines.
Laymine.sqs
This
script is another version of the minelay.sqs script, instead of placing
down mines at some random locations within a specified area though, this script
will let us pinpoint the locations of the mines and put them exactly where we
want them. The script will only take two arguments. The object to lay the mines
and a vector containing the locations for the mines. The script will use
markers for the locations. As the last script this script will only come in the
finished version sine I couldn’t find another way to write it. Here it is.
Laymine.sqs
_Object = _this select 0
_vektor = _this select 1
; Get the number of waypoints
_index = 0
_numEl = count _vektor
#Update
; Order the unit to move
_Object move GetMarkerPos (_vektor
select _index)
; Has the unit reached the marker?
@UnitReady _Object
; Lay a mine!
_Object Fire
["Put","Mine"]
_index = _index + 1
? (_index < _numEl) : goto
"Update"
exit
This
is a quite powerful and smart script. It will automatically calculate the
number of mines to put by using the count keyword. Then it will enter a
loop and increase the _index variable which is used to pick out the
waypoints. The script should be self explanatory and I don’t think I need to
tell you that much more.
We
will now test the script by creating our player and an engineer. In the init
field of the engineer type in the following.
[this, [“First”, “Second”, “Third”]] exec “laymine.sqs”
Before
you go ahead and run the script, create three markers and name them “First”,
“Second” and “Third”. Don’t place them to close to each other or it will make
the script bug. 3-5 Metres should be good. Now run the script and watch how the
engineer will put down the mines one after another.
Scripting Challenges
* Create a
script that will execute another script, name it stayclose.sqs. When you call the script it should look like
this.
[object1,object2, x, freq, [vector], “scriptname”] exec “stayclose.sqs”
Where
object1 should be the unit that must stay close to the unit (object2) and x the
distance in metres he must hold. Freq will be the desired update frequency and
vector will hold the arguments for the script to run which.
Hint: Use the Distance keyword
together with a loop. The loop should detect if the first object is not within
the required distance.
*
Create a script that will be named flakgun.sqs, the script should work
in the exact same way as the barrage.sqs, instead of firing on the
ground though the script should fire within a specific cube in the air. This
will simulate AA guns. When you call the script it should take the following
parameters.
[Object, “Centre”, Coverage, minHeight, maxHeight, Rounds, Delay] exec
“flakgun.sqs”
Here
Object is the detonator, Centre is the marker name where to fire. Coverage is
the maximum metres from the centre a shell can land. MinHeight and maxHeight
will tell the which height borders the barrage should stay within. Rounds is
the total number of shells to fire and Delay the time in seconds between each
round.
Hint: When changing the height of
the barrage simply add a z variable and use that instead of the 0 constant we
used in barrage. When defining the z variable you must use the Random keyword
and generate a random value that will never be great then height maxHeight and
never lower then minHeight.
Quelle
Seite 1 von 1
Befugnisse in diesem Forum
Sie können in diesem Forum nicht antworten