SA-MP Forums

Go Back   SA-MP Forums > SA-MP Scripting and Plugins > Scripting Help > Discussion

Reply
 
Thread Tools Display Modes
Old 26/01/2011, 09:48 AM   #1
Slice
High-roller
 
Join Date: Mar 2008
Location: Sweden
Posts: 1,900
Reputation: 1586
Spray Tips & Tricks

hey,
I thought I'd share a couple things that I use/find useful a lot when it comes to SA-MP scripting.

I'll probably add a bunch of stuff to this topic. If you have anything to contribute, let me know!

Current topics I'll be discussing:
  • The ternary operator
  • Simple int -> bool conversion
  • Fastest string loop
  • Fastest plain player-loop
  • Short functions
  • Multiple actions in one statement
  • Running code just after a function finishes
  • Getting rid of stupid tag warnings
  • "Char-arrays"
  • Split up numeric literals
  • Bit-flags in enums (advanced)
  • Using logical operators for tiny if-statements (advanced)
  • Efficient memory management with stock const (advanced)

The ternary operator
The ternary operator is an operator that takes 3 arguments; if the first argument is true then the argument right next to it will be used, if not then the last argument will be used.

The structure of the operator is this:
condition ? true : false

Here are a couple of examples:
Code:
if ( a == b )
	c = d;
else
	c = e;
With the ternary operator:
Code:
c = ( a == b ) ? d : e;
//   if-^   then-^   ^-else
A couple examples:
Code:
SetPlayerColor( playerid, ( Team[ playerid ] == TEAM_ONE ) ? COLOR_RED : COLOR_BLUE );
// Set the color to red if the player is in TEAM_ONE, otherwise set it to blue.

GivePlayerWeapon( playerid, ( IsMadnessEnabled() ) ? WEAPON_MINIGUN : WEAPON_FLOWER, 5000 );
// If IsMadnessEnabled is true, give the player a minigun!

public OnPlayerSpawn( playerid )
{
	SetPlayerHealth( playerid, ( IsSuddenDeathEnabled() ) ? 1.0 : 100.0 );
	
	if ( ( IsPlayerAdmin( playerid ) ) ? SetPlayerPos( playerid, AdminSpawnX, AdminSpawnY, AdminSpawnZ ) : SetPlayerPos( playerid, PlayerSpawnX, PlayerSpawnY, PlayerSpawnZ ) ){}
	// I have to wrap this inside an if statement to avoid getting a warning from the PAWN compiler!
}

file = fopen( ( useSpecialFile ) ? ("special_file.txt") : ("normal_file.txt") );
// Strings need parentheses around them or the PAWN compiler will generate an error.

// You can also use ternary operators inside ternary operators!
file = fopen( ( useFile == 1 ) ? ("file1.txt") : ( ( useFile == 2 ) ? ("file2.txt") : ( ( useFile == 3 ) ? ("file3.txt") : ("file0.txt") ) ) );

// ..let's break that down
	new File:file = fopen(
		( useFile == 1 ) ? ("file1.txt")
			: ( ( useFile == 2 ) ? ("file2.txt")
				: ( ( useFile == 3 ) ? ("file3.txt")
					: ("file0.txt") ) )
	);
Simple int -> bool conversion
Sometimes you end up having to convert an int to a bool, the proper way to do that would be:
Code:
new myInt = 50;
new bool:myBool = !!myInt;

This will make myBool become 0 if myInt equals 0, otherwise 1.
Fastest string loop
This is, by my experience, the fastest way to loop through a string.

Code:
for ( new i, l = strlen( string ); i != l; i++ )
{
	// ..
}
Fastest plain player-loop
Note that foreach is faster than this - that's why the heading says plain player-loop.

Code:
for ( new slots = GetMaxPlayers( ), i; i < slots; i++ )
{
	if ( !IsPlayerConnected( i ) )
		continue;
	
	// code for connected players
}
Short functions
If your function has only one statement - you can declare them like this:
Code:
stock SomeFunction( someInput )
	return someArray[ someInput / 2 ];
Multiple actions in one statement
Statements in programming are what you'd call instructions - in PAWN these statements are all separated by the semicolon ( ; ). Sometimes you want to fit code on only one line for some reason, here's how you do that:
Code:
stock KickEx( playerid, reason[] )
	SendClientMessage( playerid, 0xC00000FF, "You got kicked!! Reason:" ), SendClientMessage( playerid, 0xC00000FF, reason ), Kick( playerid );
// Sends the two client messages then kicks the player.

stock DoStuff( playerid )
	return DoFirstThing( playerid ), DoSecondThing( playerid ), DoThirdThing( playerid ), DoLastThing( playerid );
// DoStuff will return what DoLastThing returns.

public OnPlayerRequestSpawn( playerid )
{
	if ( !IsPlayerLoggedIn( playerid ) )
		return SendClientMessage( playerid, 0xC00000FF, "You're not logged in!" ), 0;
	// Send the client message and return 0
	
	return 1;
}
Running code just after a function finishes
This really isn't anything special, but I just thought I'd mention it as I haven't seen a lot of people do this.
I simply set a timer on 0 ms with no repeat somewhere and that function will be called almost right after the current function finished.

Why?
Sometimes you want to run code right after the current function finishes, here's a short example of a really handy function:
Code:
stock DBResult:db_query_ex( DB:db, query[ ], bool:storeResult = true )
{
	new DBResult:dbrResult = db_query( db, query );
	
	if ( dbrResult )
	{
		if ( storeResult )
			SetTimerEx( "db_query_ex_free", 0, false, "i", _:dbrResult );
		else
			db_free_result( dbrResult );
	}
	
	return dbrResult;
}

forward db_query_ex_free( DBResult:dbrResult );
public  db_query_ex_free( DBResult:dbrResult )
	db_free_result( dbrResult );

// EXAMPLE:

public OnFilterScriptInit( )
{
	new DB:db, DBResult:dbrResult, buffer[ 16 ];
	
	db = db_open( "test.db" );
	
	dbrResult = db_query_ex( db, "SELECT 50" );
	
	db_get_field( dbrResult, 0, buffer, sizeof( buffer ) - 1 );
	
	print( buffer );
	
	// Even if the script would get some sort of error and abort running the current function,
	// the result will still get freed so you won't have a memory leak!
}

Using IsPlayerAdmin inside OnRconLoginAttempt doesn't work - the admin-status is set after that function executes. Example:
Code:
new
	bool:g_IsRconAdmin[ MAX_PLAYERS ]
;

public OnPlayerConnect( playerid )
	g_IsRconAdmin[ playerid ] = false;

public OnRconLoginAttempt( ip[ ], password[ ], success ) // IsPlayerAdmin returns false if you check it inside this function. :(
	SetTimer( "CheckNewRconAdmins", 0, false );

forward CheckNewRconAdmins( );
public  CheckNewRconAdmins( )
{
	for ( new slots = GetMaxPlayers( ), playerid; playerid < slots; playerid++ )
	{
		if ( !g_IsRconAdmin[ playerid ] && IsPlayerAdmin( playerid ) )
		{
			// IsPlayerAdmin always returns false for unconnected players so we can save some performance by only calling that function.
			
			OnPlayerRconLogIn( playerid );
			
			break;
			// There should be at most new admin each function call, so we can break out of the loop now.
		}
	}
}

OnPlayerRconLogIn( playerid )
{
	SendClientMessage( playerid, 0x0000C0FF, "Welcome, Mr. Rcon!" );
}

Here's a part of a post by Y_Less explaining how he uses this:
Quote:
Originally Posted by Y_Less View Post
I find this useful to apply a large set of operations at once. If you look in the YSI library YSI_td.own it can dynamically update textdraws, so you can move them about the screen or change the colour etc. If you have code which looks like this:

Code:
TD_Colour(td, 0xFF0000AA);
TD_SetShadow(td, 3);
TD_Font(td, 2);

That will change the textdraw for anyone looking at it to a red style 2 TD with a shadow, however because of the way the system used to work that would have redrawn the textdraw three times when it doesn't need to. The old method of fixing this was an extra parameter:

Code:
TD_Colour(td, 0xFF0000AA, false);
TD_SetShadow(td, 3, false);
TD_Font(td, 2);

So only the last update in a set would change the appearance, the new system however uses a timer in much the same way as you just described. All the functions contain this (or something similar):

Code:
if (YSI_g_sTimer[td] == -1)
{
    YSI_g_sTimer[td] = SetTimerEx("TD_Delay", 0, 0, "i", td);
}

That way the "TD_Delay" function is always called after the last current update is applied, without knowing a user's code in advance.
Getting rid of stupid tag warnings
When putting Text3Ds, DBResults, and stuff inside functions such as printf, format, SetTimerEx, CallLocalFunction, CallRemoteFunction you might notice you're getting a tag warning.
You're not doing anything wrong!
What you do to get rid of them is you clear the tag - clearing the tag is done by putting an underscore as a tag.
Example:
Code:
new Text3D:t3dTest = Create3DTextLabel( .. ), Text:txTest = TextDrawCreate( .. );

printf( "DEBUG: %d, %d", _:t3dTest, _:txTest );
"Char-arrays"
PAWN has a feature for accessing single bytes in arrays, intended for use with packed strings. Most SA-MP natives doesn't cover packed strings, though.
You can, however, utilize these arrays a lot for memory optimization. A normal array can store values between -2,147,483,648 and 2,147,483,647; you don't always need that capacity, do you?
With packed strings you can store values between 0-255 (yes, no negative values; -1 will wrap to 255). When you know you don't need negative values and won't ever exceed 255, why not save some memory?

Code:
new bool:g_IsPlayerSomething[ MAX_PLAYERS ]; // 500 cells * 4 bytes per cell = 2000 bytes!
new bool:g_IsPlayerSomething[ MAX_PLAYERS char ]; // 500 bytes = .. 500 bytes!

If you use "char arrays" instead of normal ones where needed 50 times you'll save 75,000 bytes (~73 kb).

Note!
When accessing these arrays, you need to use curly brackets (aka braces) as opposed to the normal square brackets.
Example:
Code:
public OnPlayerConnect( playerid )
{
	g_IsPlayerSomething{ playerid } = false;
//	                   ^          ^
}

public OnPlayerSpawn( playerid )
{
//	                        v          v
	if ( g_IsPlayerSomething{ playerid } )
	{
		// ..
	}
}
Split up numeric literals

Quote:
Originally Posted by Y_Less View Post
A very small thing I found out the other day, you can split up long numeric literals in a similar way to how you do in maths. Normal writing:

Code:
345,234,148
Here "," is used as a thousands separator (sometimes "." I believe, but PAWN uses that for decimal). You can also do this in PAWN using "'" instead:

Code:
345'234'148

And you can split HEX numbers up every 4, or binary numbers every 8:

Code:
0x12FD'39C5
0b00000000'11111111'01010101

As you can see, the highlighter doesn't like this.

Bit-flags in enums

Did you know that you can store 32 true/false values in one single variable? Not only do you save space, but you also get less clutter in your code.

You don't have to understand how the binary numeral system works; however, I recommend it. You can read more about it in this topic, if you're interested.

If you have, say, 100 true/false (bool) per-player variables you would use 195 KB of space. However, if you were to use 4 arrays with bit flags, only 8 KB of space would be used. The outcome would be exactly the same, but you would save 187 KB of space!

Here's an example also containing macros to simplify the usage.
Code:
// Usage for all macros: BitFlag_X(variable, flag)
#define BitFlag_Get(%0,%1)            ((%0) & (%1))   // Returns zero (false) if the flag isn't set.
#define BitFlag_On(%0,%1)             ((%0) |= (%1))  // Turn on a flag.
#define BitFlag_Off(%0,%1)            ((%0) &= ~(%1)) // Turn off a flag.
#define BitFlag_Toggle(%0,%1)         ((%0) ^= (%1))  // Toggle a flag (swap true/false).

enum PlayerFlags:(<<= 1) {
	// It's important that you don't forget to put "= 1" on the first flag. If you don't, all flags will be 0.
	
	PLAYER_IS_LOGGED_IN = 1,   // 0b00000000000000000000000000000001
	PLAYER_HAS_GANG,           // 0b00000000000000000000000000000010
	PLAYER_CAN_BUY_PROPERTIES, // 0b00000000000000000000000000000100
	PLAYER_BLABLA_1,           // 0b00000000000000000000000000001000
	PLAYER_BLABLA_2,           // 0b00000000000000000000000000010000
	PLAYER_BLABLA_3,           // 0b00000000000000000000000000100000
	PLAYER_BLABLA_4,           // 0b00000000000000000000000001000000
	PLAYER_BLABLA_5,           // 0b00000000000000000000000010000000
	PLAYER_BLABLA_6,           // 0b00000000000000000000000100000000
	PLAYER_BLABLA_7,           // 0b00000000000000000000001000000000
	PLAYER_BLABLA_8,           // 0b00000000000000000000010000000000
	PLAYER_BLABLA_9,           // 0b00000000000000000000100000000000
	PLAYER_BLABLA_10,          // 0b00000000000000000001000000000000
	PLAYER_BLABLA_11,          // 0b00000000000000000010000000000000
	PLAYER_BLABLA_12,          // 0b00000000000000000100000000000000
	PLAYER_BLABLA_13,          // 0b00000000000000001000000000000000
	PLAYER_BLABLA_14,          // 0b00000000000000010000000000000000
	PLAYER_BLABLA_15,          // 0b00000000000000100000000000000000
	PLAYER_BLABLA_16,          // 0b00000000000001000000000000000000
	PLAYER_BLABLA_17,          // 0b00000000000010000000000000000000
	PLAYER_BLABLA_18,          // 0b00000000000100000000000000000000
	PLAYER_BLABLA_19,          // 0b00000000001000000000000000000000
	PLAYER_BLABLA_20,          // 0b00000000010000000000000000000000
	PLAYER_BLABLA_21,          // 0b00000000100000000000000000000000
	PLAYER_BLABLA_22           // 0b00000001000000000000000000000000
};

new
	// Create an array with the same tag as the enum
	PlayerFlags:g_PlayerFlags[MAX_PLAYERS]
;

public OnPlayerConnect(playerid) {
	// 0 - All flags are off (false). You must include the tag to prevent a warning.
	g_PlayerFlags[playerid] = PlayerFlags:0;
}

public OnPlayerLogIn(playerid) {
	BitFlag_On(g_PlayerFlags[playerid], PLAYER_IS_LOGGED_IN);
	
//	Without macros:
//	g_PlayerFlags[playerid] |= PLAYER_IS_LOGGED_IN;
}

public OnPlayerJoinGang(playerid) {
	BitFlag_On(g_PlayerFlags[playerid], PLAYER_HAS_GANG);
	
//	Without macros:
//	g_PlayerFlags[playerid] |= PLAYER_HAS_GANG;
}

public OnPlayerLeaveGang(playerid) {
	BitFlag_Off(g_PlayerFlags[playerid], PLAYER_HAS_GANG);
	
//	Without macros:
//	g_PlayerFlags[playerid] &= ~PLAYER_HAS_GANG;
}

public OnPlayerUpdate(playerid) {
	// DoSomething every-other player update.
	
	BitFlag_Toggle(g_PlayerFlags[playerid], PLAYER_BLABLA_19);
	
	if (BitFlag_Get(g_PlayerFlags[playerid], PLAYER_BLABLA_19)) {
		DoSomething();
	}
	
//	Without macros:
//	g_PlayerFlags[playerid] ^= PLAYER_BLABLA_19;
//	
//	if (g_PlayerFlags[playerid] & PLAYER_BLABLA_19) {
//		DoSomething();
//	}
}
Using logical operators for tiny if-statements

You can use logical operators as if-statements, but do it all inside one single statement!

For example, I wrote a fixed version of valstr and I did it like this without thinking too much about it:
Code:
stock FIXES_valstr(dest[], value, bool:pack = false)
{
    static const cellmin_value[] = !"-2147483648";

    if (value == cellmin)
        pack && strpack(dest, cellmin_value, 12) || strunpack(dest, cellmin_value, 12);
    else
        format(dest, 12, "%d", value), pack && strpack(dest, dest, 12);
    // Notice the comma after format? See the section "Multiple actions in one statement" in this topic.
}

As you can see, there are logical operators (&& and ||) just out in the open.

The code above, without this little trick, would look something like this:
Code:
stock FIXES_valstr(dest[], value, bool:pack = false)
{
    static const cellmin_value[] = !"-2147483648";

    if (value == cellmin) {
        if (pack)
			strpack(dest, cellmin_value, 12)
		else
			strunpack(dest, cellmin_value, 12);
    } else {
        format(dest, 12, "%d", value);
		
 		if (pack)
			strpack(dest, dest, 12);
	}
}

A few examples:
Code:
a && b();              // if a, run b.
a && b() || c();       // if a, run b. otherwise, run c.
a || b();              // if not a, run b.
a && b() || c && d();  // if a, run b. otherwise, if c, run d.
a && b() && c();       // if a, run b. if b isn't false, run c.
Efficient memory management with stock const

Written by Y_Less.

"stock const".

This is something I discovered a while ago, but forgot to ever write up:

Code:
#include <a_samp>

main()
{
	print("hi");
	print("hi");
}

If we compile this with "-a" we get:

Code:
CODE 0	; 0
;program exit point
	halt 0

	proc	; main
	; line 4
	; line 5
	push.c 0
	;$par
	push.c 4
	sysreq.c 0	; print
	stack 8
	;$exp
	; line 6
	push.c c
	;$par
	push.c 4
	sysreq.c 0	; print
	stack 8
	;$exp
	zero.pri
	retn


DATA 0	; 0
dump 68 69 0 68 69 0 

STKSIZE 1000
I have highlighted two lines in bold. The first pushes the number "0", the second pushes the number "12" ("c"). "print", for it's first (and only) parameter, takes the address of a string to print. All string literals are converted to data and stored in global memory (I won't go in to the details of the true implications of this, but sufficed to say you change them). "dump" is the current global memory for this tiny program:

Code:
dump 68 69 0 68 69 0
If we convert those numbers to ascii we suddenly see:

Code:
dump 'h' 'i' '\0' 'h' 'i' '\0'
I.e. our two "hi" strings - two copies of the same string isn't very efficient though, so let's improve this by explicitly managing the memory:

Code:
#include <a_samp>

stock const
	C_HI[3] = "hi";

main()
{
	print(C_HI);
	print(C_HI);
}

Code:
CODE 0	; 0
;program exit point
	halt 0


DATA 0	; 0
dump 68 69 0 

	proc	; main
	; line 7
	; line 8
	push.c 0
	;$par
	push.c 4
	sysreq.c 0	; print
	stack 8
	;$exp
	; line 9
	push.c 0
	;$par
	push.c 4
	sysreq.c 0	; print
	stack 8
	;$exp
	zero.pri
	retn


STKSIZE 1000
The location of "dump" has changed, but that's not important - a large program will have many "dump" statements spread throughout its code, all combined in the final stages of compilation. The important things to look at are the contents of "dump" and the two highlighted lines.

Despite the fact that we are using a variable, this is still EXACTLY as efficient code-wise as the original version because we are still using constant strings. However, the memory requirements for storing the two strings has halved, with both instances pointing to the same memory.

You can also use "new const" or "static stock const", but "stock const" is probably best for this code to avoid warnings and dupilcation of the memory we are trying not to duplicate.

Your assembly output will probably look different if you are not using "-d0".

Last edited by Slice; 29/06/2012 at 06:03 PM.
Slice is offline   Reply With Quote
Old 26/01/2011, 10:07 AM   #2
s0nic
Big Clucker
 
s0nic's Avatar
 
Join Date: Apr 2008
Location: Lennox, CA
Posts: 174
Reputation: 5
Default Re: Tips & Tricks

Thanks for posting..
I had personally forgotten about some of these, just learned a few more, and knew a few as well.
Great post nonetheless.
__________________
s0nic is offline   Reply With Quote
Old 26/01/2011, 10:31 AM   #3
Retardedwolf
High-roller
 
Retardedwolf's Avatar
 
Join Date: Jun 2009
Posts: 1,567
Reputation: 60
Default Re: Tips & Tricks

Thank you for the ternary and IntBool examples.
Retardedwolf is offline   Reply With Quote
Old 26/01/2011, 10:43 AM   #4
cessil
High-roller
 
cessil's Avatar
 
Join Date: Apr 2009
Posts: 2,751
Reputation: 295
Default Re: Tips & Tricks

thanks for these!
cessil is offline   Reply With Quote
Old 26/01/2011, 12:18 PM   #5
SkizzoTrick
High-roller
 
SkizzoTrick's Avatar
 
Join Date: Aug 2010
Posts: 1,861
Reputation: 37
Default Re: Tips & Tricks

Wow,nice tut xD
SkizzoTrick is offline   Reply With Quote
Old 26/01/2011, 01:50 PM   #6
Vince
Spam Machine
 
Vince's Avatar
 
Join Date: Sep 2007
Location: Belgium
Posts: 11,016
Reputation: 2647
Default Re: Tips & Tricks

The ternary operator is called the triadic operator on the wiki.
I don't know what its real name is, but I've been using it for quite a while and it's very useful.

Also, something I have been doing lately is using packed arrays for player vars.
These are 1/4th of the size of a regular array and work just as good.

Example:
Code:
new
    SomePlayerVar[MAX_PLAYERS char];

// This will create an array with 125 cells, instead of 500
// To use:

SomePlayerVar{playerid} = 1;

if(SomePlayerVar{playerid})
{
    // do stuff
}

The only downside: You can't store a value greater than 254 (defined as charmax) in it, but it's great if you only need to store true (1) or false (0) or something.
__________________
Vince is offline   Reply With Quote
Old 26/01/2011, 02:03 PM   #7
Kwarde
High-roller
 
Join Date: Nov 2009
Location: The Netherlands
Posts: 3,977
Reputation: 1585
Default Re: Tips & Tricks

It's nice/usefull blabla. Thanks. I think I can use the things that I didn't use yet :')
__________________
Assassin's Creed: SAMP website: www.acsamp.com
Mail: kwarde@acsamp.com

Latest 3 changelogs of ACSAMP
Code:
19/02/2018 11:16 - Added server config and install option: Max log fails (v0.0.102-A)
19/02/2018 11:28 - Added login dialogs with pass and max log fails checking (v0.0.103-A)
19/02/2018 12:59 - Abstergo's main map (animus chamber) added (v0.1.110-A)
Kwarde is offline   Reply With Quote
Old 26/01/2011, 02:12 PM   #8
[03]Garsino
Guest
 
Posts: n/a
Default Re: Tips & Tricks

Thank you for your contribution, Slice! Hopefully most of this stuff will come in handy for me.
  Reply With Quote
Old 26/01/2011, 02:48 PM   #9
Slice
High-roller
 
Join Date: Mar 2008
Location: Sweden
Posts: 1,900
Reputation: 1586
Default Re: Tips & Tricks

@Vince: In maths ternary/triadic relations are the same things. I don't often see the operator referred to as a triadic operator very often, however.
Covering the packed strings sounds like a good idea, I use those all the time for bit flags and small values. I'll see what I can come up with. The max value is 255, actually (0 - FF).

Edit: Added a section on this at the bottom.
Slice is offline   Reply With Quote
Old 26/01/2011, 02:59 PM   #10
Sergei
High-roller
 
Sergei's Avatar
 
Join Date: Mar 2008
Location: Slovenia
Posts: 2,913
Reputation: 230
Default Re: Tips & Tricks

Very useful!
__________________
Watch out, scouts everywhere!
Sergei is offline   Reply With Quote
Reply

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
Needs Scripting Tips! KomplettMaster General 12 26/12/2012 05:04 PM
[Tutorial] [InfiniTuts - Tutorials for beginners] Tips and Tricks regarding 'logical thinking' - Making your own script Infinity Tutorials 6 30/04/2012 12:34 AM
Motor Tricks by D12 D12Mr Screenshots and Videos 6 24/05/2009 07:04 PM


All times are GMT. The time now is 02:12 PM.


Powered by vBulletin® Version 3.8.6
Copyright ©2000 - 2018, Jelsoft Enterprises Ltd.