Author Topic: How to Write a Soldat Script  (Read 27477 times)

0 Members and 1 Guest are viewing this topic.

Offline iDante

  • Veteran
  • *****
  • Posts: 1967
How to Write a Soldat Script
« 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...
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)
« Last Edit: July 10, 2013, 07:35:15 pm by FalconPL »

Offline Norbo

  • Camper
  • ***
  • Posts: 338
Re: How to Write a Soldat Script
« Reply #1 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...

Offline shantec

  • Soldier
  • **
  • Posts: 140
  • Get ANGREH!!
Re: How to Write a Soldat Script
« Reply #2 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
Also Known As REIMA


Lol Happles (happy apples)

Offline DorkeyDear

  • Veteran
  • *****
  • Posts: 1507
  • I also go by Curt or menturi
Re: How to Write a Soldat Script
« Reply #3 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."
« Last Edit: September 14, 2008, 07:40:35 pm by DorkeyDear »

Offline iDante

  • Veteran
  • *****
  • Posts: 1967
Re: How to Write a Soldat Script
« Reply #4 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).

Offline EnEsCe

  • Retired Soldat Developer
  • Flamebow Warrior
  • ******
  • Posts: 3101
  • http://enesce.com/
    • [eC] Official Website
Re: How to Write a Soldat Script
« Reply #5 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

Offline JFK

  • Camper
  • ***
  • Posts: 255
    • My TraxInSpace Account
Re: How to Write a Soldat Script
« Reply #6 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.
« Last Edit: March 30, 2010, 06:10:23 pm by JFK »
Come join: EliteCTF
Listen to: My Music

Offline DorkeyDear

  • Veteran
  • *****
  • Posts: 1507
  • I also go by Curt or menturi
Re: How to Write a Soldat Script
« Reply #7 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)

Offline Norbo

  • Camper
  • ***
  • Posts: 338
Re: How to Write a Soldat Script
« Reply #8 on: September 15, 2008, 05:46:36 am »
Wow, really, really thanks for the types. I always wanted to know wtf they are

Offline Vyka

  • Major
  • *
  • Posts: 59
Re: How to Write a Soldat Script
« Reply #9 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!

Offline zop

  • Major
  • *
  • Posts: 81
Re: How to Write a Soldat Script
« Reply #10 on: September 15, 2008, 10:41:34 pm »
Should mark this on top. Great thanks !

http://122.116.167.31:23238:23238/galavela/?inc=player&name=%5BTomato+Bird%5D+Cibo[/size=1]

Offline danmer

  • Camper
  • ***
  • Posts: 466
  • crabhead
Re: How to Write a Soldat Script
« Reply #11 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 ::)

Offline Leo

  • Soldat Beta Team
  • Veteran
  • ******
  • Posts: 1011
Re: How to Write a Soldat Script
« Reply #12 on: September 16, 2008, 04:32:08 am »
Sticky

Offline iDante

  • Veteran
  • *****
  • Posts: 1967
Re: How to Write a Soldat Script
« Reply #13 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...

Offline EnEsCe

  • Retired Soldat Developer
  • Flamebow Warrior
  • ******
  • Posts: 3101
  • http://enesce.com/
    • [eC] Official Website
Re: How to Write a Soldat Script
« Reply #14 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.

Offline iDante

  • Veteran
  • *****
  • Posts: 1967
Re: How to Write a Soldat Script
« Reply #15 on: September 16, 2008, 07:31:55 pm »
whaa thats strange. Does it do that for every one? (flagger, etc)

Offline DorkeyDear

  • Veteran
  • *****
  • Posts: 1507
  • I also go by Curt or menturi
Re: How to Write a Soldat Script
« Reply #16 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

Offline SyavX

  • Soldat Beta Team
  • Camper
  • ******
  • Posts: 338
Re: How to Write a Soldat Script
« Reply #17 on: September 17, 2008, 03:22:44 am »
gj
can i use it to complete my scripting manual (in russian)? ;)

Offline iDante

  • Veteran
  • *****
  • Posts: 1967
Re: How to Write a Soldat Script
« Reply #18 on: September 17, 2008, 09:13:08 am »
gj
can i use it to complete my scripting manual (in russian)? ;)
go ahead.

Offline Lt. Sprizz

  • Major
  • *
  • Posts: 79
  • I'm sorry if I offended you.
Re: How to Write a Soldat Script
« Reply #19 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!
"I am Jack's lack of surprise..." ~(Tyler Durden)
-=::SPOILER::=-
Edward Norton and Brad Pitt are the same person...:)
In-game: \Biz|Niz/