2/4/14

[GTA IV/EFLC] Writing .NET scripts in C#

Hello. This is a great tutorial which got me into scripting for GTA. If you want to start creating your own scripts, read this!

Credits to Andrew from GTAforums.com




Making a .netscripthook mod using C#

Aims: To give you the basics in the C# programming language, so you can write your own scripts for the .netscript hook.


Requirements:
•A C# Devlopment Enviroment (Visual Studio 2005/2008, C# Express Editions 2005/2008). The express editions are free from micrsoft.
http://www.microsoft.com/Express/. If you're at a University and the University has registered with MS. Then you can get Studio 2008 Professional for free from MS via the dreamspark system.
- Lastest version of .netscripthook (0.86 at time of writing) -> http://gtaforums.com/topic/392325-beta-gtaiv-net-scripthook/


Intro
So you want to create your own script for GTA IV, this tutorial will introduce you to the basics of programming in C# andcreating a script for the scripthook. Before you can even begin to write scripts . You need to know the programming basics, and how a scripthook script is set up. We'll shall begin with the basics.


Programming with C#
Programming with C# is relatively easy, its syntax is similar to Java, but once you know the basics, loops, if statments, switches etc you can apply these quite easy to other programming languages. Lets start with Data types.



Data types
If you're going to be writing scripts, you're going to need to declare varibles to hold information or data, depending on what you're working with. I'm only going to show you the datatypes that you'll probably be using.


- int, signed 32bit whole number(ranges from -2,147,483,648 to 2,147,483,647)
- float, signed 32bit floating point number(ranges from ±1.5 × 10−45 to ±3.4 × 1038)
- bool, true/false (0 or 1)
- String, text.


The above are the datatypes you'll probably use when writing scripts. When to use these should be self explanatory. If you've got a whole number, and you know its always going to remain whole then use an int. If you've got a decimal number (3.14) then use a float datatype. If you use the wrong type then the complier with complain when compling the script.

To declare and use these datatypes, is fairly straight foward.


int i;
float f;
bool b;
string s;


This will declare the datatypes with their default values. (note that the ; is a end of line terminator, everyline (except for loops and if statments needs to have a ; at the end of it. You can declare and set the varible value at the same time.

int i = 25;
float f = 235.5f;
bool b =  false;
string s = "Some text here";

When setting a float value, you need to have a f at the end of the number. This is to designate it as 
a float and not a double. Any text for a string varible needs to be enclosed between ""


Arrays
Any datatype can be made into an array. An array is basically a collection of that datatype. 

int[] i = new int[]{0, 1, 6, 9, 2};

That creates an array ints, that can be expanded to include more.

int[5] i;

That creates an fixed array of size 5.
An item in an array can be accessed by its index number, arrays always start from index 0. So

i[0] returns 0 first element in the array
i[3] returns 9 forth element in the array

These type of arrays do not have any way of counting how many items are in the array, its up to you to keep track of how many are in it. 


Lists
Lists are basically an array, but gives you more control. To use lists within the scripthook, you'll need to add a refence to Systems.Collections.Generic.

List<int> lInts;
lInts = new List<int>();

That will create a new empty list of ints. To add something to the list, you can use the add method.
lInts.add(1);
lInts.add(5);


That will add 1 and 5 to the list. You can access a list item, the same as an array.
lInts[0] is 1;
lInts[1] is 5;


You can remove items from an list by using either remove(item) or removeAt(index). remove will remove an specific item from the list. Whereas removeAt will remove an item at a specifc index.
lInts.remove(1) //will remove 1 from the list
lInts.removeAt(1) //will remove 5 from the list



Operations on Data types
You can carry out various operations on data types (ie add, subtract, devide etc). 

Addition:
floats, ints and strings can all be added together, providing they are of the same datatype.
int i = 2; //declare & set i to 2
int j = 4; //declare & set j to 4
i = i + j; //Add i and J together.

float f = 2.0f; //declare & set f to 2.0f
float j = 2.2f; //declare & set j to 2.2f
f = f + j; //add f and j together


You can combine a operator and the eqauls together. So the addition statements above, become.
i += j;
f += j;


You can do the above for any operator, add(+) subtract(-), devide(/) and multiply(*).
int i = 2; //declare & set i to 2
int j = 4; //declare & set j to 4
i = i * j; //multiply i and J together.

float f = 2.0f; //declare & set f to 2.0f
float j = 2.2f; //declare & set j to 2.2f
f = f * j; //devide f and j together

int i = 2; //declare & set i to 2
int j = 4; //declare & set j to 4
i = i / j; //devide i and J together.

float f = 2.0f; //declare & set f to 2.0f
float j = 2.2f; //declare & set j to 2.2f
f = f / j; //devide f and j together

int i = 2; //declare & set i to 2
int j = 4; //declare & set j to 4
i = i - j; //subtract j from i

float f = 2.0f; //declare & set f to 2.0f
float j = 2.2f; //declare & set j to 2.2f
f = f - j; //subtract j from f


The String datatype, can only be Added together. This joins the two Strings together(concatenate).
String s1 = "This is a test string s1";
String s2 = "& This is a test string s2";
s1 = s1 + s2; //becomes "This is a test string s1& This is a test string s2"


You can reverse any datatype except for String. By using ! in front of it. 
int i = 2;
i = !i; //this will set i to -2;

bool b = false;
b = !b; //this will set b to true;


Now you know about some basic datatypes you'll be using, we can now move on to the conditonal statements.



Conditional Statements
Conditional statements are statements that can be excuted providing the conditions are met. There are 3 main types of conditional statements.

• if statements
• loops
• switch statements

Lets start with If statements.

IF
If statments can be used to only execute some code, when the condition is met. For example a light. If switch is on, then turn light on, if switch off, turn light off. An if statement is used by the word if and then the condition in brackets. Followed by the code to run enclosed in braces. You can then pecifiy an else condition or just an else, if the condition isn't met.
bool switch = false;
bool lightOn = false;

if(switch == true)
{
lightOn = true;
} else {
lightOn = false;
}


The above statement reads. if the switch is on(set to true), then switch the light on. Otherwise switch the light off. The else statement will execute if the condition isn't met. Can also check if a number is above or below a certain value etc.
int i = 40;
if(i < 35) //i less than 35
if(i > 35) //i greater than 35
if(i == 35) // i equal to 35
if(i <= 35) //i less than or eqal to 35
if(i >= 35) //i greater than or equal to 35


You can combine multiple ifs and if elses.
int i = 40;
if(i < 35)
{
do this;
} else if(i > 35)
{
do this
} else {
do this
}


Lets move on to loops.

Loops
There are a couple of different types of loops. for, while, do while and for each. Loops will execute a block of code whilst the condition is matched.

WHILE
Loops around until the condition is false. Evaluates the condition at the begining of the loop.
int i = 20;
int a = 0;
while(i < 20)
{
a += i;
i++;
}


The above will execute while i is less then 20.

DO WHILE. 
Same as a while loop, except the condition is evaluted at the end of the do cause. So the loop is always run once.
int i = 20;
int a = 0;
do
{
a += i;
i++;
} while(i < 20)


FOR. 
For loops, can be set to run a number of times, and until a certain point. 
int a = 0;
for(int i = 0; i < 20; i++)
{
a += i;
}


FOR EACH
Used to iterate through an array or collection of objects. 
int a = 0;
int[] i = new int[]{1, 2, 3, 4, 5, 6}
foreach(int b in i)
{
a += b;
}


Thats loops, used to excute a block of code while the condition is met. Lets move on to switch statements.

Switch
Switch statements are basically a block of if statements. Each case within a switch can be considered an if statement.
switch(i)
{
case 0:
//execute if i is 0
do something
break;
case 1:
//execute if i is 1
break;
case 25:
//execute if i is 25
case < 56:
//execute if i less than 56
break;
default:
//execute if none of the above is met
break;
}


Now thats pretty much the basics you'll need to know for C#. If you haven't programmed before, or even if you have the basics. I'd strongly advise people read a C# tutorial. But for now, the above is all you'll need for this tutorial.

Now we're going to move on to the .netscripthook, and how a script is structured. I'm only going to go through some of the baiscs / features that the scripthook has. If you want more examples then you can look at the example scripts that come with the scripthook.

Setting up the IDE for .net scripthook. 

Before you can start programming a .net scripthook. You'll need to set up a project for the script you're making, and you'll need add refernces to the hook. So you can access its features.

I'll use both Microsoft Visual Studio 2008 and Microsoft Visual C# Express 2005 as examples. Visual Studio 2008 will be similar to a 2008 Express Edition, and Express 2005 will be similar to Visual Studio 2005. (I cannot use C# Express 2008 since I need Express 2005 for University.)

Right lets setup a project and set up the project for the scripthook. Fire up your development enviroment. You'll see that both express editions 2005 and visual studio 2008 look very similar. 

We need to create a project. So under the recent projects box. Click the Create project link.

Note: Since some actions will differ between the two IDEs, I'll refer to Visual Studio 2008 as VS2008, and C# Express 2005 as C#E2005.

We need to set the project for the correct type, so it produces a dll file.

VS2008 - In the projects type box, we need to select a C# project, so expand other languages and choose Visual C#. In the templates window choose Class Library, and give the project a name and click OK.

C#E2005 - Ensure visual C# is selected in the project types, and choose Class Library in the templates 
window, and give it a name and click OK.

Now you should be faced with a blank script with some using statements and a constructor. 

We need to add using statements and references for the .netscripthook. In the solution explorer, right click References and choose add reference. On the references window. choose Browse, and browse to where you put the .netscripthook. In the folder scripts\For developers\bin\ should be the file ScriptHookDotNet.dll. Choose this file to add the reference. The references folder should of expanded 
to show that the scripthook reference has been added. We also need to add the System.Drawing and System.Windows.Forms refrences. These will be under the .net tab.

We now need to add the using statements to the top of the script, so that when we want to use anything from the scripthook. We don't have to put GTA in front of it. We also need the add a using statement for System.Windows.Forms Put: At the bottom of the last using item. put

using System.Windows.Forms;
using GTA;


So you should have:
VS2008 - 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using GTA;

C#E2005 -
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using GTA;

Before we can start to program a script, we need to set the scriptfile up correctly, so that it inherits the methods from the scripthook. Where the class constructor is.

Public class Class1.

Change Class1 to a much better name, and after the name put : Script . So you should now have.
public class tutScriptVS : Script
   {
   }


Before we dive in and start writing scripts for the scripthook. We need to know a few basics about the structure of a .netscripthook. As with any script it needs a constructor. This is where you can set varibles, arrays etc and bind events.The constructor is basically the classes name with no return type. So the constructor for my script is:
public tutScriptVS()
{
}


All the constructors code is enclosed within the braces { }.The constructor is only ran once at the script start up. (as soon as you've loaded the game). So if you want something to run all the time, it needs putting into the scripthooks tick object. To do this you need to bind a method to the tick event. Since we have our script inheriting from the Script object. We can access any properies or methods by using this. 
This.Interval = 5000;
this.Tick += new EventHandler(tickEvent);


The above sets the scripts tick handler, to the tickEvent method. (that we have to create) And sets the Interval to 5000miliseconds. So every 5 seconds, the tick event is run. So any code within the tickEvent method is ran.

When creating a method, it needs a access type (public or private) and a return type. For the tickEvent its void. Meaning it doesn't return anything. And in the parameters for the tickEvent, you need a object sender, and EventArgs e.
public void tickEvent(object sender, EventArgs e)
{
}


Thats all well, but it's pretty much useless for user interaction. The scripthook comes with a key press handler, so we can have the user interact with our script. Its the same as creating a tick handler, except we use GTA.KeyEventHandler, and set it to the KeyDown method.
this.KeyDown += new GTA.KeyEventHandler(kdHandler);
public void kdHandler(object sender, GTA.KeyEventsArgs e)
{
}


You can then check for a key press by using an if statement. the KeyEventsArg e, obtains what keys have been pressed. So to check for a press on the with the E key on its own you can use e.Key. Or e.keyWithModifiers to check for a key press using either ctrl, alt or shift. Or specifiy the individual modifier key. System.Windows.Forms.Keys is a list of all the keys in the system. Since we added the using statement for System.Windows.Forms, you can access it by just using Keys.
//check for single E key press
if(Keys.E == e.Key)

//check for e with any modifier
if(Keys.E == e.KeyWithModifiers)

//check for key press with alt, shift or control
if(Keys.E == e.Control)
f(Keys.E == e.Alt)
f(Keys.E == e.Shift)


Well now we can have a script that runs every x seconds and can detect key presses. What about drawing to the screen? Well we can only draw Text and Rectangles to the screen using the PerFrameDrawing method.

If you wish to draw a quick text message to the screen, you can use the Game.DisplayText("text here") method. This will show a quick message in the top left corner of the screen.

As with the Tick and KeyDown events, the perFrameDrawing needs to be registered to a method.

this.PerFrameDrawing += new GraphicsEventHandler(gfxDraw)
public void gfxDraw(object sender, GraphicsEventArgs e)
{
}


The methods used for drawing to the screen, are in e.graphics. Lets draw some simple text to the screen. using the e.graphics.DrawText(). We'll see it has 4 possible ways of being called.
//simply draws a string to the screen at the position given
e.graphics.DrawText(float x, float y, string text)

//same as above, but the size/colour can be controled by the font object
e.graphics.DrawText(float x, float y, string text, font f)

//same as the first one, but can specifiy the color
e.graphics.DrawText(float x, float y, string text, color c)

//all of the above
e.graphics.DrawText(float x, float y, string text, colorc, font f)


The X and Y values are float values, and for the screen range from 0 to 1. So X = 0.5f, and Y = 0.5f would draw something in the center of the screen.
e.graphics.DrawText(0.5f, 0.5f,"Test Text");

We can change the colour of the text by using the 3rd one, and adding a color value to the end of the call.

e.graphics.DrawText(0.5f, 0.5f, "Test Text", Color.LammyOrange);

If we really wanted to customise the text, we could use the second one, and create a font object to use within it.
Font f = new Font();
f.Color = Color.White;
f.Height = 0.25f;

e.graphics.DrawText(0.5f, 0.5f, "Test Text", f)


You can also use the graphics method to draw a rectangle to the screen. There are only two options for e.graphics.DrawRectangle().
DrawRectangle(RectangleF rect, System.Drawing.Color Color);
DrawRectangle(float CenterX, float CenterY, float Width, float Height, System.Drawing.Color Color);


You'll see that the first one takes a RectangleF as positioning and size. The second one takes the values directly into it. There are differences with the way these two work.The first using the RectangleF will start at the coordinates for the rectangle and draw left and down. Where as the second one, will draw from the center. up, down, left and right.


X ---->
Y
|
|
V


      ^
      |
<--XY-->
      |
      V


So:

//will draw a rectangle in the centre of the screen, 0.2 high and 0.2 wide, in red.
e.graphics.DrawRectangle(0.5f, 0.5f, 0.2f, 0.2f, Color.Red)

//Same as above, but rather then drawing from the center, it starts at 0.5f and draws down and left.
System.Drawing.RectangleF rf = new System.Drawing.RectangleF(0.5f, 0.5f, 0.2f, 0.2f);
e.graphics.DrawRectangle(rf, Color.CherryRed);



Right, so we have a Tick, KeyDown and Drawing. We're ready to start writing scripts. Lets write a script, that will every 20 seconds, set the players health/amour to max, and if in a vehicle, repair/wash the car. And on a key press, spawn a helicopter.

So in our prepared script, we need a constructor that will setup the Tick, and keyDown events.
public testScriptVS()
{

//set interval
Interval = 20000;

//bind tick event
this.Tick += new EventHandler(testTick);

//bind keydown event.
this.KeyDown += new GTA.KeyEventHandler(testKeyDown);
}


Right thats our constructor done, it sets the interval and binds the methods. Now we need the tick and key down methods.
//tick method, ran every 20 secs
public void testTick(object sender, EventArgs e)
{
}

//key down handler
public void testKeyDown(object sender, GTA.KeyEventArgs e)
{
}


Now your script should be looking something like this.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using GTA;

namespace tutorialScriptVS
{
   public class tutScriptVS : Script
   {

       public tutScriptVS()
       {
           //set interval
           Interval = 20000;

           //bind tick event
           this.Tick += new EventHandler(testTick);

           //bind keydown event.
           this.KeyDown += new GTA.KeyEventHandler(testKeyDown);

       }

           //tick method, ran every 20 secs
           public void testTick(object sender, EventArgs e)
           {
           }

           //key down handler
           public void testKeyDown(object sender, GTA.KeyEventArgs e)
           {
           }
       
   }
}



Next up is to set the players health and amour to their max, which is 1000. We can access the player using Player.Character.
//set health and amour
Player.Character.Health = 1000;
Player.Character.Armor = 1000;


Put that inside of the tick method, so now every 20 seconds. The players health and armor is being set to 1000 But we still need to repair/wash the players vehicle. The vehicle can be accessed by using Player.Character.CurrentVehicle. But if we try and access this and the player isn't in a vehicle then the script will crash. We can use the method Player.Character.isInVehicle to test if the player is in a vehicle.
if (Player.Character.isInVehicle())
{

//repair vehicle
Player.Character.CurrentVehicle.Repair();

//wash vehicle
Player.Character.CurrentVehicle.Wash();
}


So now we have the players health / amour being set to max, and the players car being repaired and washed. Lets allow the player to spawn a helicopter on key press. In the key down method. We can spawn a vehicle by using the World.CreateVehicle method.

World.CreateVehicle(Model Model, Vector3 Position);

The model can be created by using its name. ie "ANNIHILATOR". We can use the Around funciton of the position to get a random position within the players position.

if(Keys.Q == e.Key)
{

//get position to put vehicle
Vector3 vehPos = Player.Character.Position.Around(10.0f);

//create vehicle
World.CreateVehicle(new Model("ANNIHILATOR"), vehPos);
}


Whoo, your script should be looking something like this.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using GTA;

namespace tutorialScriptVS
{
   public class tutScriptVS : Script
   {

       public tutScriptVS()
       {
           //set interval
           Interval = 20000;

           //bind tick event
           this.Tick += new EventHandler(testTick);

           //bind keydown event.
           this.KeyDown += new GTA.KeyEventHandler(testKeyDown);

       }

           //tick method, ran every 20 secs
           public void testTick(object sender, EventArgs e)
           {
               //set health and amour
               Player.Character.Health = 1000;
               Player.Character.Armor= 1000;

               if (Player.Character.isInVehicle())
               {
                   //repair
                   Player.Character.CurrentVehicle.Repair();
                   //wash
                   Player.Character.CurrentVehicle.Wash();
               }

           }

           //key down handler
           public void testKeyDown(object sender, GTA.KeyEventArgs e)
           {
               if (Keys.Q == e.Key)
               {
                   //get position to put vehicle
                   Vector3 vehPos = Player.Character.Position.Around(10.0f);

                   //create vehicle
                   World.CreateVehicle(new Model("ANNIHILATOR"), vehPos);
               }
           }
       
   }
}


Right, lets set a few project properties, so that when the mod compiles. Its already set the correct file name. In the solution explorer, right click your project and choose properties. 

If we change the Assembly name to have .net on the end of it. The resulting file will end with .net.dll Which is the correct naming for a .net c# script. Clicking on Assembly Information, we can change the title, description and set the version number of the script. 

Right now we've set them. I think we're ready to compile and test our script.


VS2008 - press F7 to build, in the output window it should say:
========== Build: 1 succeeded or up-to-date, 0 failed, 0 skipped ==========


If it has an error, and the error box isn't showing. Go to View -> Other Windows -> Error List


C#E2005 - Press F6 to build, in the output box this should be shown.
========== Build: 1 succeeded or up-to-date, 0 failed, 0 skipped ==========


Deal with any errors, and when it compiles sucessfully. Copy the .net.dll file from the build folder. Default is the project folder debug\bin. To your gta folder /scripts. Now when you run the game, your health and amour should be set every 20 seconds, and when you're in a vehicle it should be repaired / washed every 20 seconds. And pressing Q should spawn an an helicopter.

Congratulations you've just sucessfully made your first script for the .net scripthook. You can stop reading here and go play around with it some more. Or you can continue, and I'll explain some more features that can be put into scripts such as catching called mobile numbers, console commands etc.

Right lets move on, you've got a pretty simple script that heals the player every 20 secs, and allows the player to spawn a helicopter. It's all well and good having a set Interval, but if we want to change it we have to recompile the script everytime. How about we have a ini file that has the time for us? That we can then access and set the time from that.

Luckily the scripthook, has a way of reading from a ini file. And already has a script settings propery. Lets start with making the ini file. The ini file must have the filename as the script. So if you're scripts called tutorialScripVS.net.dll then the ini must have the name tutorialScriptVS.net.ini

[SETTINGS]
INTERVAL = 10000
LOADFUEL = Control, W



Thats all that you need, the Key INTERVAL is in the SETTINGS catergory. Keeping to this format keeps the ini file easy to read when it gets cluttered. Now to access this in the scripthook. We use the Settings property. Just replace the line: Interval = 20000; with

Interval = Settings.GetValueInteger("INTERVAL", "SETTINGS", 10000);


Where the 10000 is the default value if it cannot load from the ini file. So now we've got a script that reads from an ini file. So we no longer have to recompile if we wish to change the time. All we have to do is change the time in the ini file, save it. Then use the console to reload the scripts.

We can also use the ini file to set the Key to look for. Using:

Settings.GetValueKey("KEYPRESS", "SETTINGS", Keys.Q);


Will allow us to load in a key type to check against. We need to create a Key varible above the constructor, to hold our keyPress key.

Keys keyP;


Then in the constructor we can set keyP to the settings value.

KeyP = Settings.GetValueKey("KEYPRESS", "SETTINGS", Keys.Q);


So now we can control the keyPress and the Interval time from the ini file. But we cannot save to the ini file. That is coming in a later version of the scripthook. Lets move on to catching mobile numbers. The phone feature in IV is pretty cool, but using the scripthook we can catch the numbers dailed. So we could dail "HEL 555 0100", and spawn a FIB car. 

To do this, we have to bind the number to a method that is called when the number is dailed. We can use the BindPhoneNumber function. We need to do this in the constructor, as we only need to bind the number once.

BindPhoneNumber("FIB 555 0100", new PhoneDialDelegate(callHandle));


Now we can create a method, add some spawn code to it.
public void callHandle()
{
//get position on street near player
Vector3 vehPos = World.GetNextPositionOnStreet(Player.Character.Position.Around(10.0f));
//create vehicle
World.CreateVehicle(new Model("FIB"), vehPos);
}


You'll notice I've used a method called GetNextPositionOnStreet. This method will get a position, so the vehicle is positioned on the street correctly. Now when you dial FIB 555 0100 on the phone, you'll get a FIB car. But you won't know its been spawned until you see it. Lets add a simple message that says "Vehicle Spawned" In the callHandle method add the line:

Game.DisplayText("Vehicle Spawned");


Your code at this point should look something like this:


http://pastebin.com/ww8GYYLY

Its looking good so far, we're reading from an ini file to set an interval and the key press. And we can now phone a number to get a FIB car. We can also do the same and spawn a police by using the console. We can bind a command for the console. Using the BindConsoleCommand method. Again in the constructor we need to put:

BindConsoleMethod("spawn polcar", new ConsoleCommandDelegate(consoleHandle));

And we can then add in the method and spawn code.

public void consoleHandle()
           {
               //get position on street near player
               Vector3 vehPos = World.GetNextPositionOnStreet(Player.Character.Position.Around(10.0f));
               //create vehicle
               World.CreateVehicle(new Model("POLICE"), vehPos);
           }


And the finished code.

http://pastebin.com/8026605m

And thats all for time being, I'll try and keep it update to date, and hopefully add some more guides to it. I hope this helps anyone who wishes to start writing .net scripts for IV. If there are any errors within this, please let me know.

My advice for anyone writing scripts, it just to play around with the properties and methods available to you. Also use the Class view option, its very useful for discovering what a reference can do.

No comments: