File Functions

From SA-MP Wiki

Jump to: navigation, search

File Functions in PAWN

Using the file functions seems quite hard to do correctly for the most people, although this isn't. I have made this tutorial to introduce people to these functions to get a nice file writing script (or anything else) together by theirselves, without something like Dini (Although Dini is a good solution )

The File Functions: How to start writing

Image:32px-Ambox_warning_orange.png

Note

There are faster alternatives than writing / reading files, such as DJSON, SII and Y-ini


Image:32px-Circle-style-warning.png

Important
Note

Invalid file handles used without checking may produce run-time errors or crashes.


Image:32px-Circle-style-warning.png

Important
Note

If a file is NOT found (That's used for opening) it'll result in a crash of the server, ensure all files used are properly there.


First off I'll show you a piece of tiny code and will later on explain -and improve- it.

public OnPlayerConnect(playerid)
{
    new pname[MAX_PLAYER_NAME], File:ftw=fopen("names.txt", io_write);
 
    if(ftw)
    {
        GetPlayerName(playerid, pname, 24);
        fwrite(ftw, pname);
        fclose(ftw);
    }
}

This tiny code will write the name of the player which joins into a file in your scriptfiles folder named names.txt . This isn't the best code though, because when we write it like this, with more people joined it will end up like:

Name1name2name3

Instead of

Name1
Name2
Etc..

To fix this we need to format the name into a string combined with some escape codes, it will end up like this:

new string[30];
format(string, 30, "%s\r\n", pname); // formatting the string with the escape codes
fwrite(ftw, string);

A little explanation about the escape codes; the "\n" will begin a new line, and the \r makes sure it starts at the beginning, not somewhere in the middle of a line, so it wont end up like

Name1
           Name2

Second, we now use the write io, which is not really good for this point of use, seeing it overwrites everything already written, which is not what we want. There are 4 io's we can use for file reading/writing:

Description
io_write Writes in a file, clears all earlier written text
io_read Reads the file, the file must exist, otherwise a crash will occur
io_append Appending to a file, writing only
io_readwrite Reads the file or makes a new one

So instead of the io_read we can better use the append io, so change

new pname[24], File:ftw=fopen("names.txt", io_write);

to

new pname[24], File:ftw=fopen("names.txt", io_append);

So now you made a tiny script which saves the names of players who joined your server into a file named names.txt , well done!

The File Functions: Saving a players position into an AddPlayerClass format

Now we are going to make a script like the debug tool provided within the SAMP (server) client. In the end our script will produce an AddPlayerClass line written into a file called positions.txt.

First we declare our variables inside the OnPlayerCommandText callback:

new string[128];
new Float:X, Float:Z, Float:Y, Float:Rotation; // Floats to save the pos in

Then we start our command:

if(strcmp(cmdtext, "/save", true)==0) 
{

Now we need to get the players position and their rotation. GetPlayerPos /Angle are exactly made for this.

if (strcmp(cmdtext, "/save", true)==0)
    {
        GetPlayerPos(playerid, X, Y, Z);
        GetPlayerFacingAngle(playerid,Rotation);
    }
    return 1;
}

Now we will endly format our string to an good AddPlayerClass format, Ill paste our whole command we have at the moment

if (strcmp(cmdtext, "/save", true)==0)
    {
        GetPlayerPos(playerid, X, Y, Z);
        GetPlayerFacingAngle(playerid, Rotation);
 
        new File:pos=fopen("positions.txt", io_append);
        format(string, 256, "AddPlayerClass(0, %f, %f, %f, %f, 0,0,0,0,0,0);", X, Y, Z,Rotation);
        fwrite(pos, string);
        fclose(pos);
 
        return 1;
    }

Not hard at all eh? Now will discuss reading files ;)

File Functions: Reading Files

Now I hope you understand writing, we can start with reading, seeing as this is quite useful when it comes to Admin scripts, par example. A user configurable script without digging into the source always comes in handy, and thus we are going to practise it.

We are going to make a script which will read and print some numbers written in a file, first off well use this function I often use for getting values out of files:

GetVal(numb, str[]) {
    new tmp[256], idx;
    for(new i=0; i<numb; i++) {
        tmp=strtok(str, idx);
    }
    return strval(tmp);
}

This one is requiring the function strtok to function correctly! Get it at the SA-MP Forums if you already haven't got it.

Now we make our function; ReadConf(). To read from a file we need to read io + the fread() function. Using fread one time will read the first line of the line, no further. If you want to read more lines/the whole file you need a for/while loop, well use a while loop here

ReadConf()
{
    new File:cfg=fopen("conf.cfg", io_read);
    new string[256], MyVal[256];
    while(fread(cfg, string)) 
    {
        if(strcmp(string, "mynumb ", true, 7)==0) 
        {
            MyVal=GetVal(2, string); 
        }
    }
}

This is our tiny function, don't forget to declare the MyVal integer at the top of your script! What the code does is reading the whole file, and comparing each line with the word "mynumb", if this is true (eg, he will find it), the script will set the value of MyVal to the value given in the file.

Main() 
{
ReadConf();
printf("%d", MyVal); }

This will print the value of MyVal in the console :) !

File Functions: Making Read/Save Stats functions for a (primitive) statistics script

In this part of the tutorial we will use our knowledge to make functions like: SaveStats(playerid, k, d) and ReadStats(playerid, k, d) . With this we can make our own statistics script with saveable kills, deaths and a kd ratio!

First of all, you will need dutils and functions called 'fdeleteline' and 'fcreate' (by Sacky) . For these to work you will need the fdelete code and the fcreate code. Got them? Lets get on then.

First of all we go declare all the stuff we need, strings, the file itself, arrays, etc. At the top of your script you need to put some global vars;

new PKills[MAX_PLAYERS]; // Kill Tracker
new PDeaths[MAX_PLAYERS]; // Death tracker
#define FILE_NAME "stats.txt" // The name of the file
new File:gstats; // The file
new string[256];
new pname[24];
new str[256];

In OnPlayerDeath just higher them as you want. Now we will start with making our SaveStats function. First lets list the things we need in this file

  • It needs to write the stats to the file
  • It needs to check if the players name already exist in the file, thus not getting it twice in the file
  • It needs to check if the file exists, so the script wont crash.

Not really hard things, for the second thing we will just use strcmp, for the other one, look below.

if(!gstats) 
{ 
fcreate(FILE_NAME);
}

Will fix all our problems :)


// The function itself
SaveStats(playerid, d, k) 
{
    gstats=fopen(FILE_NAME, io_append);
    GetPlayerName(playerid, pname, 24); // The name of the player
 
    if(!gstats)// Our check
    {
        fcreate(FILE_NAME); 
    } 
 
    format(str, sizeof(str), "%s %d %d\n\r",pname, k, d);
    fwrite(gstats, string);
    fclose(gstats);
}

This is, in fact, our body, but as you see, it doesn't meet the requirements we set earlier, thus we will now expand it with an strcmp check and a while loop

// The function itself
SaveStats(playerid, d, k) 
{
    gstats=fopen(FILE_NAME, io_append);
    GetPlayerName(playerid, pname, 24); // The name of the player
    format(str, sizeof(str), "%s %d %d\n\r",pname, k, d); // format
 
    if(!gstats)// Our check
    {
        fcreate(FILE_NAME);
    } 
 
    while(fread(gstats, string)) 
    {
        if(strcmp(string, pname, false, strlen(pname))==0) 
        { // To check if the players name is in the file
            fdeleteline(FILE_NAME, string); // delete the line
            fwrite(gstats, str); // write the string
        }
    }
    fclose(gstats);
}

Were getting there, now well just need to add the part in which when the name is NOT in the file, it will just normally append it to the file.

// The function itself
SaveStats(playerid, d, k) {
 gstats=fopen(FILE_NAME, io_append);
 GetPlayerName(playerid, pname, 24); // The name of the player
 format(str, 256, "%s %d %d\n\r",pname, k, d); // format
 if(!gstats) { fcreate(FILE_NAME); } // Our check
  while(fread(gstats, string)) {
   if(strcmp(string, pname, false, strlen(pname))==0) { // To check if the players name is in the file
    fdeleteline(FILE_NAME, string); // delete the line
     fwrite(gstats, str); // write the string
   }
   else if(strcmp(string, pname, false, strlen(pname))!=0) { // Its NOT true
    fwrite(gstats, str);
  }
 }
 fclose(gstats);
}

And tada! We made our savestats function. The reading part is much easier, so ill just post it at once

ReadStats(playerid) {
 gstats=fopen(FILE_NAME, io_read);
 GetPlayerName(playerid, pname, 24);
 while(fread(gstats, string)) {
  if(strcmp(str, pname, false, strlen(pname))==0) {
   PKills[playerid]=GetVal(1, str);
   PDeaths[playerid]=GetVal(2, str);
  }
 }
 fclose(gstats);
}

Now, we got our functions, you'll just need to place them in the OnPlayerConnect and Disconnect events, if you cant even do that, stop scripting already ;) !

NOTE: Earlier I mentioned a kd ratio is possible, this doesn't need to be saved in the file, when you display the stats (eg with a "/stats" cmd) declare this;

new Float:ratio=floatdiv(PKills[playerid], PDeaths[playerid]);
format(str, 256, "%d %d %.2f", PKills[playerid], PDeaths[playerid], ratio);
Personal tools