PDA

View Full Version : [TUT] Plugin API


Hawk552
07-11-2006, 17:16
Before you get into this, please note that this tutorial assumes you are intermediate level in Pawn or higher. Beginners should avoid this as it may confuse them or not make sense.

API stands for application programming interface. But what is an API? Basically it's an interface that allows multiple programs/scripts to trade data and perform the same functions without having direct access to each other.

There is API functionality in AMXX plugins, implemented through a set of natives. The following natives, built into the core, allow API capabilities:


CreateMultiForward
CreateOneForward
ExecuteForward
PrepareArray
DestroyForward


But what are they used for? Well, let's take a look at an example and then pull it apart to figure out how it works. Please keep in mind that an API is designed for multiple plugins, so every example will be 2 individual scripts instead of 1 (although it's possible to put them both in one)


#include <amxmodx>

new Float:g_flDelay

public plugin_init()
{
register_plugin("API Test - Core","1.0","Hawk552")

g_flDelay = random_float(0.1,0.5)
set_task(g_flDelay,"fnDoForward")
}

public fnDoForward()
{
new iForward = CreateMultiForward("plugin_init_delay",ET_IGNORE,FP_FLOAT),iReturn
if(iForward < 0)
return log_amx("Forward could not be created.")

if(!ExecuteForward(iForward,iReturn,g_flDelay ))
return log_amx("Could not execute forward.")

return DestroyForward(iForward)
}



#include <amxmodx>

public plugin_init()
register_plugin("API Test - Attachment","1.0","Hawk552")

public plugin_init_delay(Float:flDelay)
log_amx("Forward executed %f seconds after plugin_init.",flDelay)


Now, let's pick these two apart.

In the first script, the important part starts at fnDoForward. What we see is:


new iForward = CreateMultiForward("plugin_init_delay",ET_IGNORE,FP_FLOAT)


CreateMultiForward and CreateOneForward (which will be addressed later) return handles that can be used for ExecuteForward and DestroyForward.


NOTE: Difference between CreateMultiForward and CreateOneForward: multi accepts a return value and you must specify upon which condition it'll stop. Multi also sends to all plugins that have a public function after the name of const name[], as shown below. CreateOneForward requires a plugin id, and automatically ignores the return value (although you can still pass it byref)


But as for the parameters:

const name[] - this is the name of the public function you want to call from whatever plugin(s) specified
stop_type - this is what the plugin should obey in terms of stopping. For example, if you register a client command "kill" and then return PLUGIN_HANDLED, it'll stop right there. This parameter allows you to specify whether or not it will stop there, and whether or not the return value from plugins called matters. The possibilities are here:


#define ET_IGNORE 0 //ignore return val
#define ET_STOP 1 //stop on PLUGIN_HANDLED
#define ET_STOP2 2 //same, except return biggest
#define ET_CONTINUE 3 //no stop, return biggest


In this case, we use ET_IGNORE because plugin_init never stops calling, despite what plugins return (which is why it's useless to return PLUGIN_CONTINUE/_HANDLED in plugin_init), and we want to duplicate that functionality.

... - This is where the parameters of the header of the function called should be specified. In the above example, FP_FLOAT was specified. This is to let the AMXX core know that we want to send a floating point int to the functions called.

Here are the possibilities for the previous section (I will explain how to pass an array/string later):


#define FP_CELL 0
#define FP_FLOAT 1
#define FP_STRING 2
#define FP_ARRAY 4


Next we check if iForward > 0, or if it's not we stop there and inform the server console. As said in the funcwiki, "Results will be > 0 for success.".

Next, we execute the forward using ExecuteForward. Picking this function apart:

forward_handle - this is the forward to call. It's the handle returned from CreateMultiForward / CreateOneForward.

&ret - this is the return value, passed by reference. It is effectively the value that the function being called returns (ex. return PLUGIN_HANDLED -> &ret == 1) It usually is effected by the stop_type in CreateMultiForward.

... - this is the param(s) where you can input the data that will be passed onto the function header for the function being called. In the example above, g_flDelay is passed and in the second plugin, the plugin_init_delay function recieves it in the header as Float:flDelay. NOTE: You can theoretically have infinite parameters, but they must match with the types passed into CreateOneForward / CreateMultiForward.

If ExecuteForward returns 0 (false) then we stop there and inform the server of this error. Otherwise, we continue onward to DestroyForward.

The next part we find is DestroyForward. The functionality for this is quite obvious, and can be used on any forward but should be used by the time plugin_end is called (or the FM forward for server shutting down) otherwise memory leaks can occur.

Now, what was the point of that? Not really much, that was pretty damn useless. Here's something a little more useful:


#include <amxmodx>
#include <fakemeta>

new g_iForward
new g_iReturn

public plugin_init()
{
register_plugin("API Test 2 - Core","1.0","Hawk552")

g_iForward = CreateMultiForward("client_PreThink",ET_IGNORE,FP_CELL)
if(g_iForward < 0)
log_amx("Error creating forward")

register_forward(FM_PlayerPreThink,"fnForwardPlayerPreThink")
register_forward(FM_Sys_Error,"fnForwardSysError")
}

public fnForwardPlayerPreThink(id)
if(!ExecuteForward(g_iForward,g_iReturn,id))
log_amx("Could not execute forward")

public fnForwardSysError()
plugin_end()

public plugin_end()
DestroyForward(g_iForward)



#include <amxmodx>

public plugin_init()
register_plugin("API Test - Attachment 2","1.0","Hawk552")

public client_PreThink(id)
log_amx("PreThink called on %d",id)


We just allowed a plugin to use client_PreThink using fakemeta without having to even create the fakemeta forward in the other plugin. On top of this, if we added another plugin to the API, it would call client_PreThink for that one too. Be careful though, it'll call it twice if you have engine included.

But one thing remains unanswered: how do you pass an array/string?

A special native has been implemented for this, PrepareArray. Here's an example of how to use it:


#include <amxmodx>
#include <fakemeta>

new g_iForward
new g_iReturn

public plugin_init()
{
register_plugin("API Test 2 - Core","1.0","Hawk552")

g_iForward = CreateMultiForward("client_PreThink",ET_IGNORE,FP_ARRAY)
if(g_iForward < 0)
log_amx("Error creating forward")

register_forward(FM_PlayerPreThink,"fnForwardPlayerPreThink")
register_forward(FM_Sys_Error,"fnForwardSysError")
}

public fnForwardPlayerPreThink(id)
{
new iRand = random(5),iArray[2]
iArray[0] = id
iArray[1] = iRand

new iArrayPass = PrepareArray(iArray,2,0)

if(!ExecuteForward(g_iForward,g_iReturn,iArra yPass))
log_amx("Could not execute forward")
}

public fnForwardSysError()
plugin_end()

public plugin_end()
DestroyForward(g_iForward)



#include <amxmodx>

public plugin_init()
register_plugin("API Test - Attachment 2","1.0","Hawk552")

public client_PreThink(iArray[2])
log_amx("PreThink called on %d, random value is %d",iArray[0],iArray[1])


Regardless of the fact we could use 2 cells instead of an array, this allows client_PreThink to have a random value from 0-5 packed into the parameters (which is quite useless, again this is just an example).

Now, to pick this apart:

We created an array and then packed it with data. You should know how to already do this.

We then use:


new iArrayPass = PrepareArray(iArray,2,0)


The usage of PrepareArray is as follows:

array[] - this is the array to be prepared
size - this is the amount of cells in the array (the same amount as when declaring it, not the highest cell)
copyback=0 - this is whether or not changing the array will result in the calling function's array being changed as well. This defaults to 0.

PrepareArray returns a handle as well that can be passed into ExecuteForward under the param of FP_ARRAY or FP_STRING. The difference between these two is that FP_STRING stops reading at the null terminator (and does not need to be prepared using PrepareArray), but FP_ARRAY must be prepared and only stops reading at the "size" cell.

Well, there you have it, if you have any questions or think a statement here is wrong, please post.

Cheap_Suit
07-11-2006, 19:14
Your very resourcefull Hawk552 :). Thanks for the tutoral.

VEN
08-24-2006, 09:02
until ven gets done with the xp tutoriolI've never worked on the XP tutorial.

Lord_Destros
08-24-2006, 16:34
1. Your banned again so this post may be pointless :lol:
2. I think your thinking of v3x

Xanimos
08-24-2006, 20:32
Speaking of v3x I havn't seen him in a long while. How about you guys?

[EDIT] Yep, hes been missing for over a month now....

Last Activity: 07-19-06 02:20 PM

Hawk552
08-25-2006, 11:25
maybee it was v3x . I could have sworn that it was VEN tht was going to make an xp tutoiol to replace the old one :o

VEN is really beyond that stuff I think, he's more into the fakemeta hardcore stuff.

Hawk552
08-25-2006, 20:31
OMG did you just diss VEN ? lol. Hey hawk , I was wondering can the natives be used to return wether a plugin is on or off and if it could return cvars as values ? And So i should use the API in my core plugin to comunicate with the other supporting plugins? then have the supporting plugins use natives ?

No, I did not insult VEN, in fact that was more of a compliment.

Why not just use a name for a cvar and then a stock to check whether it's on or off? I suppose you could make a native that other plugins can use to check if your plugin is off, but if it has a cvar it's really better to avoid API / native functions.

Hawk552
08-25-2006, 20:55
Ok well my cvars arnt for turning the plugins on and off . I hate cvars for that , there to muc of a pain in the ass . my cvars are in my core plugin , they hold values of the ranks , how many kills per rank can be changed by cvar. and i have a menu that has all the info and help of my whole mod . So im going to have my menu , get the wether a plugin is on or off , and the cvars . And if there on or off , it wil or wont display that part of the menu . so if my artilary strike plugin is off , the menu wont display it . also my ranks and values are in the menu too , so i want it to get the cvar values and display the current settings for the ranks . so my menu will be self suficiant auto detecting menu :)

Well what you can do is something like:


// ...
register_native("BF_PointerPrivateKills","_BF_PointerPrivateKills")
// ...

public _BF_PointerPrivateKills()
return p_PrivateKills // or whatever you call the pointer to the cvar for kills that a private must get


Then in another plugin


if(get_pcvar_num(BF_PointerPrivateKills()) > 15)
// do something


Your question is kinda unclear, but I think that's what you want. You can do the same thing with any cvar like that.

Hawk552
08-25-2006, 21:09
ok well heres my core plugin (in beta stages now)www.ampaste.net/3390 (http://www.ampaste.net/3390and)and heres my menu www.ampaste.net/3391 (http://www.ampaste.net/3391) i want them to get values from my core , and return the cvar to display in my menu . also display only items that are on . because it creates some bit of lag and havoc when they do comands and the plugins arnt on . plus its anoying having to tel people that they arnt on 50 milion times day . so i want my menu to be auto detecting.:)

Both of those give errors for me.

Hawk552
08-25-2006, 21:35
uhh


//battle field mod is not an open source plugins . You do not have permision to alter this code , or you will be sued . All rights are reserverd for Jared "RapHero2000" Chase//
// Jared Chase 6-24-2006 ///
good luck, I'm not helping

Hawk552
08-25-2006, 21:43
just there so people dont steal try to steal it and release it for them selves .

No offense, but I doubt that's ever going to happen ;]

Your comment about "being sued" is not only very barbaric, it's also kind of ironic considering making a plugin closed source means the AMXX team can prosecute you.

Hawk552
08-25-2006, 21:52
i could care less . . and i take no offense . it was jsut a way to protect my idaes .

If you want to add the GPL and repaste it then I will help you.

Hawk552
08-25-2006, 22:14
Now, what you probably want is something like,


#define MAX_OPTIONS 9
new g_MenuOptions[MAX_OPTIONS][32]
new g_MenuNum

//...

new Forward = CreateMultiForward("BF_Menu",ET_IGNORE),Return
if(Forward < 0)
return

if(!ExecuteForward(Forward,Return))
return

new Menu[512],Pos

Pos += format(Menu[Pos],511 - Pos,"Battlefield Menu^n^n")
for(new Count;Count < g_MenuNum;Count++)
Pos += format(Menu[Pos],511 - Pos,"%d. %s^n",Count + 1,g_MenuOptions[Count])
Pos += format(Menu[Pos],511 - Pos,"^n0. Exit")

show_menu(id,g_Keys,Menu,-1,"my_menu_name")

// ...

register_native("BF_AddMenuOption","_BF_AddMenuOption")

//...

public _BF_AddMenuOption(Plugin,Params)
{
if(Params != 1)
return 0

get_string(1,g_MenuOptions[g_MenuNum],31)

return 1
}


Then in another script:


public BF_Menu()
BF_AddMenuOption("EXPLOSIVE EXPLOSION")


Assuming you change a bit of that and implement it correctly, it should turn out like:


Battlefield Menu

1. EXPLOSIVE EXPLOSION

0. Exit


I'm not totally sure that's what you want, but that's my guess.

Hawk552
08-25-2006, 22:16
Sorry, that should be:


get_string(1,g_MenuOptions[g_MenuNum++],31)


I don't want to edit it, since the indentation gets all screwy if I do.

Hawk552
08-25-2006, 22:30
cool thanks HAWK! :)
EDIT also i have aquestion abot the include file ? ive never used one before . can you eplaine what it is an how to use it ?

Umm... alright.

An include file is generally a file where you insert data that a plugin as well as other plugins associated with it need. Things like an enum of classes, or any constant data as well as native and forwards should go in it.

In terms of technically what it is, an include file is essentially a header (which is the proper term for it) that, when included, has its contents directly inserted into the place where the including file has the reference.

Hawk552
08-27-2006, 10:02
can you explaine whats happeing in the above script ? I have no idea whats going on in there . Maybee its because my menus are set up diferant .

Basically, here's the sequence of events:

a) Plugin A throws out a forward notifying all plugins that it wants them to add menu items now
b) Plugin B gets this forward called on it and throws back a native saying that it wants to add "EXPLOSIVE EXPLOSION"
c) After the forward is done, Plugin A shows the menu with all the menu items.

Obbin
09-02-2006, 12:17
What if I want to pass client_PreThink two arguments?
Like clent_PreThink(id, random_num)
I really need to know that ;)

Hawk552
09-02-2006, 20:09
What if I want to pass client_PreThink two arguments?
Like clent_PreThink(id, random_num)
I really need to know that ;)

I already covered that...

Freecode
09-03-2006, 00:22
great tutorial. +karma
This is defenatly a way to go for plugins. Creating a centralized API is a very effective way to connect other plugins together.

SweatyBanana
09-03-2006, 00:26
Its not very helpful not being able to read thru others posts when they no longer exist to the common user :|

But I like the tutorial and I hope I can use it some day.

Freecode
09-03-2006, 00:42
Its not very helpful not being able to read thru others posts when they no longer exist to the common user :|
THats because they were deleted for a purpose. And if they were helpfull they would stay.

SweatyBanana
09-03-2006, 00:42
Ok :up:

Dancing Bread
09-03-2006, 20:35
Do you have to use

public plugin_natives()
{
register_natives("whatever" , "whatever")
}

like you do with the API functions ?

Hawk552
09-03-2006, 22:42
Do you have to use
Code:
public plugin_natives() { register_natives("whatever" , "whatever") }


like you do with the API functions ?

register_native, but I don't understand your question

Dancing Bread
09-03-2006, 23:34
well in your API tutoriol you stated that you have to register the natvies inside a public plugin_natives function and dont try to put them in the regular plugin_init function . so here when we register the natives do we do it hte same way ?

Hawk552
09-04-2006, 10:18
well in your API tutoriol you stated that you have to register the natvies inside a public plugin_natives function and dont try to put them in the regular plugin_init function . so here when we register the natives do we do it hte same way ?

I assume you mean "where when we register the [forwards do we put them]?". But anyway, you can create a forward at any time. It really doesn't matter whether you create it, execute it, then destroy it, or create it on map start, execute it when needed, and then destroy it on map change.

Cheap_Suit
04-11-2008, 18:06
The next part we find is DestroyForward. The functionality for this is quite obvious, and can be used on any forward but should be used by the time plugin_end is called (or the FM forward for server shutting down) otherwise memory leaks can occur.

Is this true? Because I was told by a person that it wasnt needed.

Hawk552
04-11-2008, 19:21
AFAIK since I wrote this it was changed such that it frees all forwards on plugin_end. I suggest running it anyway as it's good practice.

Lee
04-13-2008, 07:17
Did AMXX have floating point integers back in 2006? :P

Hawk552
04-13-2008, 09:54
Umm, it never truly had floats, if that's what you mean. But it's always had some sort of basic support for it, done entirely through natives.

Lee
04-13-2008, 10:55
"This is to let the AMXX core know that we want to send a floating point int to the functions called."

I was trying to point out your small error. I wouldn't have mentioned something so small if it didn't offer a trace of comedy value. Coming up next; immutable variables.

ot_207
09-13-2009, 05:09
Can the PrepareArray function be used also for Float arrays?

Hawk552
09-13-2009, 13:39
Can the PrepareArray function be used also for Float arrays?

Since arrays tagged as floats are just that; tagged, you can prepare an array of any type. You might have to do some detagging, though, like this:


// ...

new Float:origin[3]
pev( id, pev_origin, origin )

new array = PrepareArray( _:origin, 3, 0 )

ExecuteForward( some_forward, some_return, array )

// ...

public some_forward_name( Float:origin[3] )
{
// ...

ot_207
09-14-2009, 03:22
Since arrays tagged as floats are just that; tagged, you can prepare an array of any type. You might have to do some detagging, though, like this:


// ...

new Float:origin[3]
pev( id, pev_origin, origin )

new array = PrepareArray( _:origin, 3, 0 )

ExecuteForward( some_forward, some_return, array )

// ...

public some_forward_name( Float:origin[3] )
{
// ...


Ok, thanks! At first I thought that it must be something like this.
But I needed someone to confirm it.
So basically the core just figures out the array cell length.

Hawk552
09-14-2009, 08:13
Ok, thanks! At first I thought that it must be something like this.
But I needed someone to confirm it.
So basically the core just figures out the array cell length.

Float arrays are the exact same size as cell arrays because all they use are tags. There is no concept of data types in Pawn.

11922911
04-21-2016, 10:35
Is it possible to modify forward parameter value by other plugin?

11922911
04-22-2016, 23:42
Is it possible to modify forward parameter value by other plugin?

I tried this

forward test(&arg);

public test(&arg)
{
arg = 369;
}

But it doesn't seems to work.