File Functions
From SA-MP Wiki
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
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);