[Tutorial] [Advanced]How to mimic Object-Oriented Programming in PAWN
#1

Object-Oriented Programming in PAWN


What is Object-Oriented Programming

Quote:

Object-oriented programming (OOP) is a programming paradigm based on the concept of "objects", which are data structures that contain data, in the form of fields, often known as attributes; and code, in the form of procedures, often known as methods. A distinguishing feature of objects is that an object's procedures can access and often modify the data fields of the object with which they are associated (objects have a notion of "this" or "self").

Quote from Wikipedia https://en.wikipedia.org/wiki/Object...ed_programming

If you are totally new to object-oriented programming I suggest you to read the whole wikipedia article.

Implementing it in PAWN

PAWN is not an object-oriented programming language, however it has a very useful feature that I will be using in this tutorial: tags.

Probably every PAWN scripter, even a beginner, knows the most used tag: Float:, which SA-MP native functions use for the 3D positioning system, health, etc.
However not everybody knows that you can define your own tags, and in fact this is a feature which is not very used by PAWN coders.
My PAWN class model is based on a few simple macro definitions and the use of tags (to prevent calling of methods on an object of a different class).

Downsides
  • No inheritance as well as no interfaces
  • No visibility setting (private, public, protected)
  • No constructor/method overloading (if you want to implement it, read this)
  • Only static-like syntax (e.g. ClassName.method(object, arg) instead of object.method(arg))


Let's get started!
PHP код:
/******************** class ClassExample *******************/
#define ClassExample. ClassExample_
#define ClassExample_max ClassExample:1024 
First, we want to keep a class clearly divided from other code to keep it tidy and to avoid confusion, so add a comment or something with the class name.
Then add these two definitions:
ClassExample. will now be precompiled as ClassExample_ to avoid compiler errors (as . is an undefined symbol).
Then we define the maxium number of instances for this class.


Fields
PHP код:
// Fields
stock ClassExample.myInteger[ClassExample.max];
stock Float:ClassExample.myFloat[ClassExample.max];
stock ClassExample.myString[ClassExample.max][64]; 
Let's define some class fields: we will use arrays for that (if we wanted to create a static field, then we wouldn't have used the [ClassExample.max] part).

Note: I used the "stock" keyword instead of "new" simply to avoid compiler warnings if later we aren't using those variables. I will always use "stock" vars and functions in this tutorial.


Constructor
PHP код:
// Constructor
stock ClassExample:ClassExample(iFloat:fs[])
{
    static 
ClassExample:this ClassExample:-1;
    
this++;
    
//
    
ClassExample.myInteger[this] = i;
    
ClassExample.myFloat[this] = f;
    
format(ClassExample.myString[this], 64s);
    
//
    
return this;

Let's create the constructor!
As before, we define it as a stock function.
First of all, we use the ClassExample: tag to make it return a value with that tag.
Then we call the constructor "ClassExample". You can call it whatever you like, but if you are tidy you will use the same constructor name for all your classes, for example I will always call it <NameOfTheClass>.
Then let's add some arguments to the constructor, in this example, an integer, a float and a string.
Inside the constructor we create a static local variable tagged ClassExample: to keep the instance (object) identifier, we initialize it to -1, then increment it. Every instance of ClassExample will now have a different id.
Finally, let's set the object fields to the arguments of the constructor and return the object handler.

Methods
PHP код:
// Methods
stock ClassExample.toString(ClassExample:this)
{
    new 
ret[128];
    
format(
        
ret
        
128
        
"ClassExample %d\n\t\
        myInteger: %d\n\t\
        myFloat: %.4f\n\t\
        myString: %s\n\n"

        
_:this// _: removes the ClassExample: tag, transforming it to a printable integer
        
ClassExample.myInteger[this],
        
ClassExample.myFloat[this],
        
ClassExample.myString[this]
    );
    return 
ret;

For the sake of tidiness, all the non-static methods that we create, will have as first parameter the object handler, and we will call it "this". In other programming languages, such as Java, the object on which the method is called is implicit, but in PAWN it's difficult to achieve (if you have suggestions please comment below).
This method, called "toString", will return a string containing the state of the object.



Getter/Setter Methods
PHP код:
// Getter/Setter methods
stock ClassExample.getMyInteger(ClassExample:this)
{
    return 
ClassExample.myInteger[this];
}
stock ClassExample.setMyInteger(ClassExample:thisi)
{
    
ClassExample.myInteger[this] = i;
}
stock Float:ClassExample.getMyFloat(ClassExample:this)
{
    return 
ClassExample.myFloat[this];
}
stock ClassExample.setMyFloat(ClassExample:thisFloat:f)
{
    
ClassExample.myFloat[this] = f;
}
stock ClassExample.getMyString(ClassExample:this)
{
    return 
ClassExample.myString[this];
}
stock ClassExample.setMyString(ClassExample:thiss[])
{
    
format(ClassExample.myString[this], 64s);
}
/************************************************************/ 
We can create getters and setters to access our fields (optional).

Instantiating, calling methods and setting field values
PHP код:
main()
{
    
// creating instances
    
new ClassExample:ClassExample(201525.524"Hello World!");
    new 
ClassExample:ClassExample(1257.52"Goodbye World!");
    new 
ClassExample:ClassExample(5212.52"Hello Again World!");
    
    
// calling toString
    
printf(ClassExample.toString(a));
    
printf(ClassExample.toString(b));
    
printf(ClassExample.toString(c));
    
    
// changing and testing fields directly
    
printf("We incremented a's integer directly\n");
    
ClassExample.myInteger[a] ++;
    
format(ClassExample.myString[a], 64"Happy new year!");
    
printf(ClassExample.toString(a));
    
    
// changing and testing fields by getter and setter methods
    
printf("b.getMyString: '%s'"ClassExample.getMyString(b));
    
    
printf("We are now setting b's string value by calling its setter method\n");
    
ClassExample.setMyString(b"Changed value!");
    
    
printf("b.getMyString: '%s'"ClassExample.getMyString(b));

Now let's test the class!

Output:

Код:
[15:08:16] ClassExample 0
	myInteger: 2015
	myFloat: 25.5240
	myString: Hello World!


[15:08:16] ClassExample 1
	myInteger: 125
	myFloat: 7.5199
	myString: Goodbye World!


[15:08:16] ClassExample 2
	myInteger: 521
	myFloat: 2.5199
	myString: Hello Again World!


[15:08:16] We incremented a's integer directly

[15:08:16] ClassExample 0
	myInteger: 2016
	myFloat: 25.5240
	myString: Happy new year!


[15:08:16] b.getMyString: 'Goodbye World!'
[15:08:16] We are now setting b's string value by calling its setter method

[15:08:16] b.getMyString: 'Changed value!'
Example usages

I want to show you a very basic, uncomplete and untested house system made with a House class for demostrating purposes.

PHP код:

/*
* This is an example on how to mimic a very basic Object-Oriented programming in the PAWN language
* by SaSiNO97_ Aka Sasinosoft
*/
#include <a_samp>
/******************** class House *******************/
#define House. House_
#define House_max House:256
// Fields
stock House.owner[House.max][MAX_PLAYER_NAME];
stock House.price[House.max];
stock Float:House.x[House.max];
stock Float:House.y[House.max];
stock Float:House.z[House.max];
// Static final field example
// - static because it's not depending on a single House object (no [House.max])
// - final because it's defined as "const" and can't be changed
stock const House.invalidOwnerName[MAX_PLAYER_NAME] = "#@!/*-";
// Constructor
stock House:House(priceFloat:xFloat:yFloat:z)
{
    static 
House:this House:-1;
    
this++;
    
//
    
format(House.owner[this], MAX_PLAYER_NAMEHouse.invalidOwnerName);
    
House.price[this] = price;
    
House.x[this] = x;
    
House.y[this] = y;
    
House.z[this] = z;
    
Create3DTextLabel("House"0x33AA33FFxyz5.00true);
    
//
    
return this;
}
// Methods
stock House.toString(House:this)
{
    new 
ret[128];
    
format(
        
ret
        
128
        
"House %d\n\t\
        Owner: %s\n\t\
        Price: $%d\n\t\
        Position: (%.4f, %.4f, %.4f)\n\n"

        
_:this,
        
House.owner[this],
        
House.price[this],
        
House.x[this],
        
House.y[this],
        
House.z[this]
    );
    return 
ret;
}
stock bool:House.buy(House:thisplayerid)
{
    if(!
IsPlayerConnected(playerid))
        return 
false;
    
    if(!
strcmp(House.owner[this], House.invalidOwnerName))
    {
        
SendClientMessage(playerid0xFFFFFFFF"[ERROR]This house is already owned.");
        return 
false;
    }
    
    if(
GetPlayerMoney(playerid) < House.price[this])
    {
        
SendClientMessage(playerid0xFFFFFFFF"[ERROR]You can't afford this house.");
        return 
false;
    }
    
    if(!
IsPlayerInRangeOfPoint(playerid1.5House.x[this], House.y[this], House.z[this]))
    {
        
SendClientMessage(playerid0xFFFFFFFF"[ERROR]You aren't close to this house.");
        return 
false;
    }
    
    new 
nickname[MAX_PLAYER_NAME];
    
GetPlayerName(playeridnicknameMAX_PLAYER_NAME);
    
GivePlayerMoney(playerid, -House.price[this]); // 
    
format(House.owner[this], MAX_PLAYER_NAMEnickname);
    
SendClientMessage(playerid0xFFFFFFFF"[CONGRATULATIONS]You now own this house!");
    return 
true;
}
stock bool:House.sell(House:thisplayerid)
{
    if(!
IsPlayerConnected(playerid))
        return 
false;
    
    
    new 
nickname[MAX_PLAYER_NAME];
    
GetPlayerName(playeridnicknameMAX_PLAYER_NAME);
    
    if(
strcmp(House.owner[this], nickname) != 0)
    {
        
SendClientMessage(playerid0xFFFFFFFF"[ERROR]This house is not yours.");
        return 
false;
    }
    
    if(!
IsPlayerInRangeOfPoint(playerid1.5House.x[this], House.y[this], House.z[this]))
    {
        
SendClientMessage(playerid0xFFFFFFFF"[ERROR]You aren't close to this house.");
        return 
false;
    }
    
    
GivePlayerMoney(playeridfloatround(House.price[this]*0.7floatround_ceil)); // 
    
format(House.owner[this], MAX_PLAYER_NAMEHouse.invalidOwnerName);
    
SendClientMessage(playerid0xFFFFFFFF"[CONGRATULATIONS]You sold your house!");
    return 
true;
}
// Example of a static method (a method that is not linked to a specific object of the class)
stock House.printAll()
{
    for(new 
House:i=House:0i<House.maxi++) // Loop through all Houses
    
{
        if(
House.x[i] != 0.0// check if it was created
        
{
            
printf(House.toString(i)); // prints it state
        
}
    }
}
/************************************************************/
main()
{
    
printf("House Class Example by SaSiNO97_!");
}
public 
OnGameModeInit()
{
    
// Let's create some anonymous objects of the class House (positions are totally random, so don't look for them inside the game :P)
    
House(150001520.0214.015.0);
    
House(250001620.0244.012.0);
    
House(500001120.0714.014.0);
    
House(360001120.0714.014.0);
    
House(140001220.0614.024.0);
    
House.printAll();
}
public 
OnPlayerCommandText(playeridcmdtext[])
{
    if(!
strcmp(cmdtext"/buy"true))
    {
        for(new 
House:i=House:0i<House.maxi++) // Loop through all Houses
        
{
            if(
IsPlayerInRangeOfPoint(playerid1.5House.x[i], House.y[i], House.z[i])) // If player is in range of a house
            
{
                
House.buy(iplayerid); // Call the method "buy"
                
break; // Exit the loop
            
}
        }
        return 
1;
    }
    
    if(!
strcmp(cmdtext"/sell"true)) // Similar to previous
    
{
        for(new 
House:i=House:0i<House.maxi++)
        {
            if(
IsPlayerInRangeOfPoint(playerid1.5House.x[i], House.y[i], House.z[i]))
            {
                
House.sell(iplayerid);
                break;
            }
        }
        return 
1;
    }
    return 
0;

Reply
#2

Awesome love to work on Oops. Thanks for this tutorial bro.
Reply
#3

That's a very good tutorial Sasino. Tidy and clean. I guess though that using OOP in PAWN is just for certain people who are used to Java scripting or they prefer the OOP way.
P.S: Of course you deserve a reputation for this tutorial.
Reply
#4

Thank you guys
Reply
#5

For those interested in function overloading, there is a tutorial https://sampforum.blast.hk/showthread.ph...pid3459023" target="_blank">here.

You can have syntax like C++ templates too, I have been working on STL plugin and managed to achieve syntax similar to the following by manipulations using defines:

Код:
list<Float> float_list; //equivalent to new new List:lst_float_list = STL_List_DT_Template_Float; 
//STL_List_DT_Template has a unique negative number which is of course invalid but it is used to identify the type when a method is used on the list for the first time

//Acts as a explicit constructor
list::float_list(); //has 3 overloads 

For local lists, one can have following syntax (Work in progress, having some issue with it)
list<String> strings(params); //For local variables, implicit constructor
//translates to new List:lst_strings = STL_List_DT_Template_String;
//list::strings(params) - basically calls the init function automatically

list::strings.push_back("String"); //This translates to _epl_list_push_back(lst_strings, "String");
Reply
#6

Thanks for a good tutorial!
Reply
#7

Quote:
Originally Posted by N0FeaR
Посмотреть сообщение
Thanks for a good tutorial!
Thank you for the positive feedback
Reply
#8

Thanks for the tutorial, keep going.
Reply
#9

Thanks for this. I still gotta search more about creating custom tags.
Reply
#10

True OOP in SA-MP.
https://sampforum.blast.hk/showthread.php?tid=511686
Reply
#11

Quote:
Originally Posted by NewbProgrammer
Посмотреть сообщение
I knew about that plugin, but my aim wasn't OOP in SA-MP, but OOP in PAWN.
Reply
#12

Hi, I used to play SA-MP 6+ years ago and am thinking of getting back into it + the scripting side. In my years since SA-MP i have coded entirely OOPy using mainly PHP, Java or Ruby, so it would be nice to have some sort of (limited) OOPiness in Pawn. I am also thinking about just using the Java plugin to code in.

I tried to make an OOP style using the define directives, but they're also kind of limited (or my knowledge is, I haven't touched Pawn in 6 years as I say). Anyway, if I could have your view on this code I would appreciate it.

PHP код:
#include <a_samp>
const MAX_STRING 256;
new 
lastId;
stock _getNewClassObjectId(objectIds[], size) {
    for(
lastId 0lastId sizelastId++) {
        if(!
objectIds[lastId]) {
            
objectIds[lastId] = true;
            return 
lastId;
        }
    }
    
#if defined ThrowError
        
new string[MAX_STRING];
        
format(stringsizeof string"Ran out of objects, max: %d"size);
        
ThrowError(string);
    
#endif
 
return -1;
}
stock _countInstances(objectIds[], size) {
 new 
counter 0;
    for(new 
0sizei++) {
        if(
objectIds[i]) {
   
counter++;
  }
 }
 return 
counter;
}
stock _getInstance(objectIds[], sizenumber) {
 new 
counter 0;
    for(new 
0sizei++) {
        if(
objectIds[i]) {
            if(
counter == number) {
                return 
i;
            }
   
counter++;
  }
 }
 return -
1;
}
#define class(%0,%1) \
 
stock _%0_tag_check(%0:objid) { return int:objid; } \
 new 
bool:_%0_objects[%1] = false
#define delete(%0,%1) _%0_objects[_%0_tag_check(%1)] = false
#define method(%0).%1(%2) _%0_%1(%0:this,%2)
#define methodb(%0).%1()  _%0_%1(%0:this)
#define var(%0){%1} _%0_%1[sizeof _%0_objects]
#define val(%0){%1} _%0_%1[_%0_tag_check(this)]
#define callb(%0,%1).%2()  _%0_%2(%1)
#define call(%0,%1).%2(%3) _%0_%2(%1, %3)
#define object(%0) %0:_getNewClassObjectId(_%0_objects, sizeof _%0_objects)
#define instances(%0) _countInstances(_%0_objects, sizeof _%0_objects)
#define instance(%0,%1) %0:_getInstance(_%0_objects, sizeof _%0_objects, %1)
class(User100);
new var(
User){name}[MAX_STRING];
method(User).init(name[]) {
 for(new 
0strlen(name); i++) {
     
val(User){name}[i] = name[i];
 }
}
methodb(User).getName() {
 return 
val(User){name};
}
main()
{
 new 
User:object(User);
 
printf("User id: %d"int:u);
 
call(Useru).init("foo bar");
 print(
callb(Useru).getName());
 
printf("User instances: %d"instances(User)); // 1
 
delete(Useru);
 
printf("User instances: %d"instances(User)); // 0

Reply
#13

I can't believe I never found this tutorial until now, god bless you, I can get some use to this.
Reply
#14

Quote:
Originally Posted by Eoussama
Посмотреть сообщение
I can't believe I never found this tutorial until now, god bless you, I can get some use to this.
Thanks for bumping this, so useful

(I'm serious^)
Reply
#15

this is kinda useless, we cant even really pack the functions into a class block
Reply
#16

beautiful tutorial.

I use it this way. I do not know if it's true

- sorry my bad english, I do not speak english!

PHP код:
#if defined function
    #undef function
#endif
#define function%0::%1(%2)                         %0_%1(%2)
#if defined call
    #undef call
#endif
#define call::%0->%1(%2)                        %0_%1(%2)
enum e_PLAYER_INFO
{
    
PlayerID,
    
Nome[MAX_PLAYERS],
    
//...
}
new 
Player[MAX_PLAYERS][e_PLAYER_INFO];
// ================== [ FUNCTIONS ] ================== //
function PLAYER::SetPlayerVarInt(playeride_PLAYER_INFO:paramvalue){
    
Jogador[playerid][param] = value;
    return 
true;
}
function 
PLAYER::GetPlayerVarInt(playeride_PLAYER_INFO:param){
    return 
Jogador[playerid][param];
}
// ================== [ PUBLICs ] ================== //
public OnPlayerConnect(playerid)
{
    
call::PLAYER->SetPlayerVarInt(playeridPlayerID1);
    
call::PLAYER->GetPlayerVarInt(playeridPlayerID);

Reply
#17

What the fk
Reply
#18

Its not useful at all, are you all drunk? I am telling you as a Java programmer, this is bullshit and mimicking a fucking paradigm that the language doesn't support doesn't give you any benefits, that is just dumb. Don't do this.
Reply


Forum Jump:


Users browsing this thread: 1 Guest(s)