Official Soldat Forums

Server Talk => Scripting Discussions and Help => Topic started by: iDante on September 14, 2008, 01:16:05 pm

Title: How to Write a Soldat Script
Post by: iDante on September 14, 2008, 01:16:05 pm
How to Write a Soldat Script

This guide assumes that you know basic programming practices, such as how to use loops, if's, functions, arrays, etc. For good info on this stuff go to: http://www.taoyue.com/tutorials/pascal/

Instead of focusing on that stuff, I will primarily focus on the process of writing a script, as well as many issues that you will surely run across.

Part 1: Getting Set Up:
Firstly, if you haven't already, download Notepad++. http://notepad-plus.sourceforge.net/uk/site.htm
Use this to write your code in, the syntax highlighting is awesome.
Note: Notepad++ is not required, any text editor will do. However, the syntax highlighting in it, combined with its speed and ease of use, makes it my favorite.

Next open up a new tab and go to http://devs.soldat.pl/index.php/Server_Scripting
This is your home.

In here you can see all the events, functions, and variables that you can use. Warning: many of them are wrong or outdated, be careful.

In your dedicated server, open the scripts folder. Add a new folder to it, name it the name of the script (Yes the folder name is what the script is called, not the .pas file). In my case I'll just call it "MFS" which stands for "My first script."
Open up that folder and create a new text file in it (right click->new->text thing). Name it "Includes.txt" . Be sure that you capitalize the 'I', or it won't work on linux servers. Open it up once it's made and add in "script.pas". Save it.
Now create a new file called script.pas in that directory. Just to be sure it works, run the soldat server. Somewhere in the scripting part it should say:
Code: [Select]
[*] Compiling MFS -> script.pas...If not you did something wrong. Don't forget to set Scripting = 1 in server.ini.

Now its ready to go, open script.pas up with Notepad++, and we'll write our first script.

Part II: Writing a really simple script:
This script will do this:
When a player types /asplode then kill every player in the game.
Everything will happen in OnPlayerCommand.
Code: (Pascal) [Select]
function OnPlayerCommand(ID: Byte; Text: string): boolean;
begin

end;
Now check if they typed /asplode:
Code: (Pascal) [Select]
function OnPlayerCommand(ID: Byte; Text: string): boolean;
begin
     if Text = '/asplode' then begin

     end;
end;
Now loop through every player and check if they are active. If so, kill them. You will need to make an integer variable to do this. It goes before the "begin" thing.
Code: (Pascal) [Select]
function OnPlayerCommand(ID: Byte; Text: string): boolean;
var i: integer;
begin
     if Text = '/asplode' then begin
          for i := 1 to 32 do begin
               if GetPlayerStat(i, 'active') then Command('/kill '+inttostr(i));
          end;
     end;
end;
This compiles and works.

Part III: A Note on Threads:
Don't use them. This includes ThreadFunc and sleep. Just don't use it, or your server will go KAPOW!

Part IV: A Timer:
Now we need to improve on our last script by adding in a timer. When a player types /asplode make a timer count down from 5, then kill them all. There are several ways to do this; I will show my favorite.

To start out, make a variable to hold the current time.
Code: (Pascal) [Select]
var
     Timer: integer;
The idea is this:
Every second the scripting thing calls AppOnIdle. We can use this to decrement Timer. Here's our script:
Code: (Pascal) [Select]
var
     Timer: integer;
procedure AppOnIdle(Ticks: integer);
begin

end;
function OnPlayerCommand(ID: Byte; Text: string): boolean;
var i: integer;
begin
     if Text = '/asplode' then begin
          for i := 1 to 32 do begin
               if GetPlayerStat(i, 'active') then Command('/kill '+inttostr(i));
          end;
     end;
end;
Now we need to make a small leap. Here is the final script.
Code: (Pascal) [Select]
var
     Timer: integer;
procedure AppOnIdle(Ticks: integer);
var i: integer;
begin
     if Timer > 0 then
          Timer := Timer - 1;
     if Timer = 0 then begin
           for i := 1 to 32 do
               if GetPlayerStat(i, 'active') then Command('/kill '+inttostr(i));
           Timer := -1;
     end;
end;
function OnPlayerCommand(ID: Byte; Text: string): boolean;
begin
     if Text = '/asplode' then begin
          Timer := 5;
     end;
end;
Going through this:
When a player types /asplode Timer gets set to 5.
When AppOnIdle is next called (every second) it sees that Timer is > 0 so it takes off one.
This gets called every second until it hits 0.
If Timer hits 0 then it kills everyone and sets it to -1. When it's at -1 it won't be seen by any of the if loops in AppOnIdle, so nothing will happen.

Part V: Remembering Player Data:
Now lets say you want to do this:
When a player types /save, remember their position.
When the player types /load, move them to that position.

Now the script is going to get pretty complex. Lets start by making an array of X's and Y's to hold player spots in.
Code: (Pascal) [Select]
var
     x: array[1..32] of single;
     y: array[1..32] of single;
Now in OnPlayerCommand we shall add the part that saves it:
Code: (Pascal) [Select]
var
     x: array[1..32] of single;
     y: array[1..32] of single;
function OnPlayerCommand(ID: Byte; Text: string): boolean;
begin
     if Text = '/save' then GetPlayerXY(ID,x[ID],y[ID]);
end;
This saves the X and Y of player ID into x[ID] and y[ID]. Nice! I like pascal's pass-by-reference a lot more than c++...
Now we need to load the position:
Code: (Pascal) [Select]
var
     x: array[1..32] of single;
     y: array[1..32] of single;
function OnPlayerCommand(ID: Byte; Text: string): boolean;
begin
     if Text = '/save' then GetPlayerXY(ID,x[ID],y[ID]);
     if Text = '/load' then MovePlayer(ID,x[ID],y[ID]);
end;
This seems to work. Typing /save then moving then doing /load works. However, what happens if you load before you have saved? Try it.
The best way to fix this is to add another condition to the /load bit.
Code: (Pascal) [Select]
var
     x: array[1..32] of single;
     y: array[1..32] of single;
function OnPlayerCommand(ID: Byte; Text: string): boolean;
begin
     if Text = '/save' then GetPlayerXY(ID,x[ID],y[ID]);
     if (Text = '/load') and (x[ID] <> 0) then MovePlayer(ID,x[ID],y[ID]);
end;
Much better.
Just one more thing now, what happens if someone leaves the server, and someone else comes in and takes their ID spot? If the person that left did /save then the person that just joined can do /load and go to their spot :O. Heres a quick fix:
Code: (Pascal) [Select]
var
     x: array[1..32] of single;
     y: array[1..32] of single;
procedure OnJoinTeam(ID, Team: byte);
begin
     x[ID] := 0;
end;
function OnPlayerCommand(ID: Byte; Text: string): boolean;
begin
     if Text = '/save' then GetPlayerXY(ID,x[ID],y[ID]);
     if (Text = '/load') and (x[ID] <> 0) then MovePlayer(ID,x[ID],y[ID]);
end;
Now if they try to do /load after just joining it will notice that their x is 0, and not load.

Part VI: Types:
Look at that last script in part V. Notice that you have two arrays[1..32]. What happens if you want to store lots of player data, such as a boolean for each player, or maybe how many times they have said something?
Code: (Pascal) [Select]
var
     x, y: array[1..32] of single;
     bool: array[1..32] of boolean;
     talktimes: array[1..32] of integer;
Thats ugly and repetitive, and there is a much better way of showing it.
Enter types.
Code: (Pascal) [Select]
type tPlayer = record
     x, y: single;
     bool: boolean;
     talktimes: integer;
end;
var
     Players: array[1..32] of tPlayer;
Now lets say you want to get the x of player 14. Just do:
Code: (Pascal) [Select]
Players[14].x
For example, here is the script from part V with a type at the beginning.
Code: (Pascal) [Select]
type
     tPlayer = record
          x,y: single;
     end;
var
     Players: array[1..32] of tPlayer;
procedure OnJoinTeam(ID, Team: byte);
begin
     Players[ID].x := 0;
end;
function OnPlayerCommand(ID: Byte; Text: string): boolean;
begin
     if Text = '/save' then GetPlayerXY(ID,Players[ID].x,Players[ID].y);
     if (Text = '/load') and (Players[ID].x<> 0) then MovePlayer(ID,Players[ID].x,Players[ID].y);
end;

Part VII: Conversions:
Pascal is strictly-typed, so converting between stuff is necessary very often.
Definitions: More complete numeric ones... (http://forums.soldat.pl/index.php?topic=29911.msg356638#msg356638)
byte: small numbers (0-255)
integer: use it for any numbers that might go above 255.
string: 'Hello'
char: 'H'
single: floating-point number (1.23 for example)

To convert between them:
integer to string: inttostr(integer)
integer to char: chr(integer)
integer to single*: strtofloat(inttostr(integer))
string to integer: strtoint(string)
string to char: string[0]
string to single: strtofloat(string)
char to integer: ord(char)
single to integer: round(single)
single to string: floattostr(single)

* there is probably a better way but idk what it is

These should cover just about anything you need.

Part IIX: Boolean Statements:
Good lord this drives me insane.
Would you do this?
Code: (Pascal) [Select]
if x > y = true then ...;
Lets say that x is greater than y. The computer evaluates it as "if true = true then ..." when it makes more sense for it to evaluate it as "if true then ..."
A boolean statement goes into the condition part of an if thing.
Therefore, DO NOT DO THIS:
Code: (Pascal) [Select]
var
     bool: boolean;
begin
     if bool = true then ...;
end;
THAT IS UGLY AND MEAN TO ME!
Code: (Pascal) [Select]
if bool then ...;
IS MUCH BETTER TY!
Same with stuff like this:
Code: (Pascal) [Select]
var i: integer;
begin
     for i := 1 to 32 do
          if GetPlayerStat(i, 'active') then ...;
end;
Note: "if GetPlayerStat(i, 'active') then ...;"
I see this written as "if GetPlayerStat(i, 'active) = true then ...;" over and over again and it bugs the living shneidikkies out of me.
Please for the love of good god's goldfish, get rid of those = true's.

Note: Some of GetPlayerStat's return values are borked, be sure to make sure things are working right.

Now lets say you want to test out whether that boolean statement is NOT true.
Code: (Pascal) [Select]
if not bool then ...;
not is the keyword to use here. This basically takes over = false. Its easier to read as well.


I'll add more if requested... (request stuff and I'll add)
Title: Re: How to Write a Soldat Script
Post by: Norbo on September 14, 2008, 01:31:53 pm
Nice Guide iDante :)
good for the beginers that want to create their first script
Unless they are too dumb to understand this...
Title: Re: How to Write a Soldat Script
Post by: shantec on September 14, 2008, 03:14:11 pm
Awsum work!! (if there would be medals i'd give one :D )

I might test it xD
Title: Re: How to Write a Soldat Script
Post by: DorkeyDear on September 14, 2008, 07:38:48 pm
Notepad++ is NOT required, so you may use whatever text editor you wish to use. Notepad++ along with Notepad2 are just some that include more features such as syntax highlighting, which is nice to have.
I don't believe the newline is required in Includes.txt
Make sure to point out that on case-sensitive OS', the I in Includes.txt is required to be capital (Good to always keep it capital just in case)
i personally prefer doing Timer := GetTickCount() + (60 * 5); and AppOnIdle check if (Timer > 0) and (Timer <= Ticks) then have it set Timer to 0 and do whatever action, but its a bit harder to understand
"When AppOnIdle is next called (every second) it sees that it is < 0 so it takes off one." -> "When AppOnIdle is next called (every second) it sees that it is > 0 so it takes off one."
Title: Re: How to Write a Soldat Script
Post by: iDante on September 15, 2008, 12:21:03 am
Changes made, ty.
About the timer... That method makes sense, but I don't see why to devote the brain power to using it over this (which is pretty simple and works fine).
Title: Re: How to Write a Soldat Script
Post by: EnEsCe on September 15, 2008, 12:53:11 am
Also, bytes are not -127 to 127. Only ShortInt is in that range.

Quote
var
   // Integer data types :
   Int1 : Byte;     //                        0 to 255
   Int2 : ShortInt; //                     -127 to 127
   Int3 : Word;     //                        0 to 65,535
   Int4 : SmallInt; //                  -32,768 to 32,767
   Int5 : LongWord; //                        0 to 4,294,967,295
   Int6 : Cardinal; //                        0 to 4,294,967,295
   Int7 : LongInt;  //           -2,147,483,648 to 2,147,483,647
   Int8 : Integer;  //           -2,147,483,648 to 2,147,483,647
   Int9 : Int64;  // -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807
 
   // Decimal data types :
   Dec1 : Single;   //  7  significant digits, exponent   -38 to +38
   Dec2 : Currency; // 50+ significant digits, fixed 4 decimal places
   Dec3 : Double;   // 15  significant digits, exponent  -308 to +308
   Dec4 : Extended; // 19  significant digits, exponent -4932 to +4932
Title: Re: How to Write a Soldat Script
Post by: JFK on September 15, 2008, 02:44:57 am
hmm.. maybe you wanna merge with me a bit?
http://elitectf.com/newaccess/index.php?article=4198.0
I kinda started rewriting it for a wiki.
Title: Re: How to Write a Soldat Script
Post by: DorkeyDear on September 15, 2008, 05:32:56 am
A not-so-important note: the size of datatypes.. like byte and shortint are 1 byte in size (8 bits)
Title: Re: How to Write a Soldat Script
Post by: Norbo on September 15, 2008, 05:46:36 am
Wow, really, really thanks for the types. I always wanted to know wtf they are
Title: Re: How to Write a Soldat Script
Post by: Vyka on September 15, 2008, 02:56:24 pm
Man! Thanks a lot. After read that, i know much more. Everything is described perfect. One more time thanks!
Title: Re: How to Write a Soldat Script
Post by: zop on September 15, 2008, 10:41:34 pm
Should mark this on top. Great thanks !
Title: Re: How to Write a Soldat Script
Post by: danmer on September 16, 2008, 03:28:23 am
always check if GetPlayerStat(i, 'active') = true because the function returns a variant, not a boolean and can sometimes break if you dont use =true/=false (also goes for 'human', 'flagger' etc).
Otherwise, needs a sticky ::)
Title: Re: How to Write a Soldat Script
Post by: Leo on September 16, 2008, 04:32:08 am
Sticky
Title: Re: How to Write a Soldat Script
Post by: iDante on September 16, 2008, 09:07:23 am
always check if GetPlayerStat(i, 'active') = true because the function returns a variant, not a boolean and can sometimes break if you dont use =true/=false (also goes for 'human', 'flagger' etc).
Otherwise, needs a sticky ::)
GetPlayerStat(ID, 'active') always returns boolean... Its never broken for me...
Title: Re: How to Write a Soldat Script
Post by: EnEsCe on September 16, 2008, 09:30:49 am
It returns a variant.

If you do:
if GetPlayerStat(32,'active') then WriteLn('ITS TRUE!');

Your console will say "ITS TRUE!" when in fact, player 32 isn't active.
Title: Re: How to Write a Soldat Script
Post by: iDante on September 16, 2008, 07:31:55 pm
whaa thats strange. Does it do that for every one? (flagger, etc)
Title: Re: How to Write a Soldat Script
Post by: DorkeyDear on September 16, 2008, 08:05:02 pm
whaa thats strange. Does it do that for every one? (flagger, etc)
probably, same concept so i would guess so
Title: Re: How to Write a Soldat Script
Post by: SyavX on September 17, 2008, 03:22:44 am
gj
can i use it to complete my scripting manual (http://forum.soldat2d.ru/index.php?showtopic=5506) (in russian)? ;)
Title: Re: How to Write a Soldat Script
Post by: iDante on September 17, 2008, 09:13:08 am
gj
can i use it to complete my scripting manual (http://forum.soldat2d.ru/index.php?showtopic=5506) (in russian)? ;)
go ahead.
Title: Re: How to Write a Soldat Script
Post by: Lt. Sprizz on March 27, 2009, 03:24:13 pm
HOLY CRAP! I understand Everything now! I'm so happy ;D ;D ;D.

Thank you so much guys you helped out a lot. I'm going to work on my very first script now on my own.

Thanks, iDante!
Title: Re: How to Write a Soldat Script
Post by: DorkeyDear on March 27, 2009, 03:51:19 pm
HOLY CRAP! I understand Everything now! I'm so happy ;D ;D ;D.

Thank you so much guys you helped out a lot. I'm going to work on my very first script now on my own.

Thanks, iDante!
If you ever run into issues, feel free to join us at #soldat.devs at Quakenet on IRC or posting on the forums or something.
Title: Re: How to Write a Soldat Script
Post by: Lt. Sprizz on March 27, 2009, 07:38:25 pm
Cool, thx.
Title: Re: How to Write a Soldat Script
Post by: Irlandec on April 18, 2009, 10:01:20 am
[REQUEST] Add something about using cases (for commands like /ping ID) and what variables can be use in them pl0x (please)
Title: Re: How to Write a Soldat Script
Post by: kappO on May 23, 2009, 03:03:43 pm
Hello!

I followed your steps to create the /asplode script, however, even though I followed your instructions carefully, the server didn't show me:

Code: [Select]
[*] Compiling MFS -> script.pas...

Given that this thread was made a while ago, I don't know if this still applies (were changes made?)

Thanks!

edit: Sorry, I found the problem. I forgot to change "Allow Scripting" @ server.ini
Title: Re: How to Write a Soldat Script
Post by: iDante on May 24, 2009, 03:21:20 pm
edit: Sorry, I found the problem. I forgot to change "Allow Scripting" @ server.ini
I forget about that too sometimes. Added to part 1.
Title: Re: How to Write a Soldat Script
Post by: frosty on April 29, 2010, 04:01:16 am
iDante, would u mind fixing the link?

Quote
http://www.learn-programming.za.net/learn_pascal_programming.html

would be useful for a newbie coder who just decided to read this, currently the link returns a 404

sry for the bump but that needs fixing ;)
Title: Re: How to Write a Soldat Script
Post by: iDante on April 29, 2010, 04:31:44 am
That link seems to have died, updated with another.
Title: Re: How to Write a Soldat Script
Post by: Furai on September 22, 2010, 04:20:04 am
This topic is a must be for beginners like me. :) Good work.
Title: Re: How to Write a Soldat Script
Post by: Dr.Thrax on September 08, 2011, 10:26:55 am
Is there any updated reference manual with the new functions for Soldat 1.6 ?
Thrax
Title: Re: How to Write a Soldat Script
Post by: Falcon` on September 08, 2011, 03:12:03 pm
http://devs.soldat.pl/wiki/index.php?title=Server_Scripting