PDA

View Full Version : [TUT] Dynamic / Fake Natives


Hawk552
07-11-2006, 20:43
Please be aware that this tutorial is geared toward intermediate or higher level scripters. If you are a beginner, this may confuse you or make no sense.

Dynamic / Fake Natives (I will refer to them as dynamic from here onward, I don't like the word fake when they actually do something) allow you to have the functionality of a module (through natives) by having a plugin that acts like a module running. In theory, it is possible to port entire modules to fakemeta + plugins using this principle (fun has already been done, engine can be done with more difficulty). From my experiences, dynamic natives go hand and hand with API design as shown in my "Plugin API" tutorial.

Here are the natives that are used along for dynamic natives:


register_native
register_library
get_param
get_param_byref
get_param_f
get_string
get_array
get_array_f
set_array
set_array_f
set_string
param_convert


Please keep in mind that dynamic natives are pretty useless when not dealing with more than one plugin. As such, the following examples will always be at least 2 scripts, plus one include file.

Here is a simple example to show the basics of dynamic natives:


#include <amxmodx>
#include <fun>

public plugin_init()
register_plugin("Dynamic Native Test - Handler","1.0","Hawk552")

public plugin_natives()
{
register_library("dyn_test")

register_native("half_user_hp","_half_user_hp")
}

public _half_user_hp(iPlugin,iParams)
{
if(iParams != 1)
return PLUGIN_CONTINUE

new id = get_param(1)
if(!id)
return PLUGIN_CONTINUE

set_user_health(id,get_user_health(id) / 2)

return PLUGIN_HANDLED
}



#include <amxmodx>
#include <dyn_test>

public plugin_init()
register_plugin("Dynamic Native Test - Caller","1.0","Hawk552")

public client_putinserver(id)
if(is_user_alive(id))
half_user_hp(id)



// NOTE: This file MUST be called dyn_test.inc (or you have to rename the #include <dyn_test> in script 2)

#pragma reqlib "dyn_test"

native half_user_hp(id)


Now, what is this doing? Basically, on client_putinserver, if the user is alive (which is only true in mods like sven coop where you spawn right away) then it will slice their HP in half. Let's pick apart each thing.

Script 1

At first we look at the plugin_init, and we notice nothing special. We then look down and see "plugin_natives", a forward most people here have probably never seen before. This is where natives and libraries must be registered. DO NOT try to put them in other forwards, like plugin_init or plugin_precache.

Now, let's look at the first part of plugin_natives, which is register_library.

What does this do? It basically tells AMXX that this plugin provides native functionality to other plugins, and aids with "native not found" errors to check if the plugin is loaded before trying to call the dynamic native. In this case, we're calling our library "dyn_test" for "Dynamic Native Test". There are 2 types of libraries in AMXX core, as of 1.75: libraries and classes. A module is a library, while a plugin is a class. This information is useful for LibraryExists, which will be covered later.

Next, we see register_native.

const native[] - this is the name of the native to register. Pretty obvious.
const handler[] - this is the name of the function that will be called when this native is used. Remember, it must be public (as with almost all functions that are put in quotes or called by outside sources)
[ style = 0 ] - this is will be covered later

Now, as we look at the register_native call, we see the handler is _half_user_hp. So, we look down, and we see this function.


public _half_user_hp(iPlugin,iParams)


In all dynamic natives, unless registered with style=1, we see something to the effect of (id,params), which I decided to rename in the header. The first param is the plugin id - this is useful in case you want to send it back a callfunc or a CreateOneForward/ExecuteForward, or have an array that stores data for each plugin loaded. The next param is iParams. In the case of the above example, iParams must be 1, because the only param is 1. If it's anything different, someone decided to be stupid and modify the include file.

Next, inside the function we check if iParams == 1, and if it doesn't we stop there. Next, we get_param(1), which allows you to get the first parameter assuming it's a cell (which it is). We then check if id is a counting number, because we can't use 0 with get_user_health. The rest is pretty self explanatory.

In this example, I returned PLUGIN_CONTINUE because that evaluates to false, and PLUGIN_HANDLED because it evaluates to true. This allows you to do simple error checking, ie "if(!half_user_health(id)) log_amx("failed")".

Now that the handler is out of the way, we get down to the plugin that will use this dynamic native.

First, I'd like you to look at the include file (the 3rd example). The first thing we see is #pragma reqlib "dyn_test". This effectively tells AMXX "If dyn_test isn't loaded, error me right here." The register_library function in the first example is what lets you bypass this, because it tells AMXX that dyn_test does exist. Next, in this same file, we see native half_user_hp(id). This basically tells the compiler that half_user_hp will be handled in the VM layer (which is where all the C++ coding is), but the VM layer will pass it down to the function that handles the dynamic native. After it's handled, the VM layer will pass the result back to the calling function.

Onto the script itself, we see that we included this file through #include <dyn_test>. The rest is quite self explanatory.

As for the get_param section, the natives above (get_param_f, get_param_byref, etc.) can be used to replace this, depending upon what arguments are passed into the function.

Now, onto style = 1. This style basically forgets about the iPlugin,iParams part of the header, and then assumes the plugin will pass the parameters correctly. This means _half_user_hp would look like:


public _half_user_hp(id)


I personally don't like this way as you cannot guarantee that the calling function called it correctly, and also all arrays / strings / byref parameters must be converted using param_convert, however it is easier for simple natives such as the one above.

If you want to set a passed string, passed array, or passed byref parameter, use set_param, set_string, and set_array. This basically allows you to format the parameters like how format does, or in the case of set_param it allows you to set a number (like in cs_get_user_armor).

I don't really want to push this any longer so I'm just going to end it here. Like always, if you have any questions or if anything I said is wrong, feel free to post.

Hawk552
08-24-2006, 09:01
Ok well im making a mod (bfm_mod) and its pretty big , it already has 5 seperate plugins ive made in the pack . But id like them to be able to work together, and call functions from one another , instead of adding them all into one huge plugin . Would you recomend using the natives here , or the stuff from your API tutoriol ?
thanks

You really need both for a mod like that. Natives are used to inform the core plugin of new information, while API is used by the core to inform the other plugins of new information. If you need any examples, PM me (I've got something going that heavily uses this) or look at my Clan API plugin.

Emp`
12-17-2006, 03:12
in the include file it needs to be #pragma reqlib "dyn_test" and not #pragma reqclass "dyn_test"

Orangutanz
12-17-2006, 08:46
Changed :up:

Drak
03-16-2007, 16:07
I'm a little on confused on this part:

public _half_user_hp(iPlugin,iParams)
{
if(iParams != 1)
return PLUGIN_CONTINUE

new id = get_param(1)
if(!id)
return PLUGIN_CONTINUE

set_user_health(id,get_user_health(id) / 2)

return PLUGIN_HANDLED
}

Is this saying that if the ID is zero, continue? And is the "iParams" needed? Or is that only showing if you were gonna add extra perimeters?
Then again, what if you wanted to use a string? Like so:

public _get_user_rpmoney(iPlugin,where[]) // 1 = Wallet 2 = Balance
{
new id = get_param(1)
param_convert(2)
server_print("[AMXX] %s",where)

if(!id) return PLUGIN_CONTINUE

new money,buffer[64],authid[32];
get_user_authid(id,authid,31)
if(equal(where,"wallet")) {
select_string(id,"money","wallet","steamid",authid,buffer)
money = str_to_num(buffer)
}
if(equal(where,"balance")) {
select_string(id,"money","wallet","steamid",authid,buffer)
money = str_to_num(buffer)
}
// does the user have enough money?
/*
switch(where) {
case 1: {
select_string(id,"money","wallet","steamid",authid,buffer)
money = str_to_num(buffer)
server_print("[AMXX] Wallet called")
}
case 2: {
select_string(id,"money","balance","steamid",authid,buffer)
money = str_to_num(buffer)
}
}
*/
return money;
}

But this just spills out errors when used as so: get_user_rpmoney(id,"wallet")
Last thing, do dynamic natives take any performance away if being used in a large plugin?

Hawk552
03-16-2007, 19:57
stuff

iParams is necessary, and removing it from the header will cause problems if you try to check it (obviously). You can leave it out if it's never used. Also, if you're using style 1, remove the "iPlugin" parameter from the header. That's probably what's causing your problems. The other thing is that you can't use get_param in style 1. I'm still not sure what style you're using, though, since you mixed them up a lot.

Dynamic natives are extremely slow compared to any calling method other than the callfunc system, of which I'm sure it's either the same or very marginally faster. If possible, you should always use stocks or similar functions rather than dynamic native calls.

Drak
03-16-2007, 20:08
Ah, thank you. I was mainly concerned performance wise due to the fact, I would be querying a DB constitly, since i would rather of used dynamic natives instead of making more DB connections.

Orangutanz
03-18-2007, 12:29
Dynamic Natives:
Pro:
Can use less memory if the native is used quite a few times in multiple plugins.

Con:
Slower calling time due to passing through Virtual Machine twice.

Stocks:
Pro:
Faster calling times due to passing through Virtual Machine once.

Con:
Can use more memory since the whole function is copied to each plugin that uses it.


As you can see both have a good pro but also a bad con.
I'd personally go for dynamic natives, depending on how you weigh the pro/con from the above up. I prefer less memory being taken and sacrifice the speed, since you still won't really notice any difference.

Zefir
05-01-2010, 07:17
How get caller plugin_id with dynamic style 1?

Hawk552
05-01-2010, 11:41
How get caller plugin_id with dynamic style 1?

I never use style 1, so there may be a way that I'm not aware of, but I don't know of any.

Zefir
05-01-2010, 12:05
Tnx, Hawk552.

I writing wrapper for sqlx, and cannot emulate SQL_ThreadQuery() :(

Cannot get array data[], added some values, run SQL_ThreadQuery() with my callback function and call original callback function from caller plugin with original array data[]

Hawk552
05-01-2010, 13:17
Tnx, Hawk552.

I writing wrapper for sqlx, and cannot emulate SQL_ThreadQuery() :(

Cannot get array data[], added some values, run SQL_ThreadQuery() with my callback function and call original callback function from caller plugin with original array data[]

Yeah, I always just use style 0 because I like having direct access to the number of parameters and the plugin id.

Why are you making a wrapper for SQL_ThreadQuery(), though?

Zefir
05-01-2010, 13:56
Why are you making a wrapper for SQL_ThreadQuery(), though?
For centralized setting and controlling all accounts to all used SQL servers in all plugins. :)

Hawk552
05-01-2010, 14:10
Check out this code. Requires CellTravTrie (http://forums.alliedmods.net/showthread.php?t=74753).

A few notes:

This is basically a trie which saves itself into SQL for you.
It has some things unique to the environment it was written for. If you understand it, you can remove those things.
UTIL_ARP_CleverQuery() can be treated as though it were SQL_ThreadQuery().



// Generalized table query for MySQL:
/*
CREATE TABLE IF NOT EXISTS %s (classkey VARCHAR(64),value TEXT,UNIQUE KEY (classkey))
*/

// Generalized table query for SQLite:
/*
CREATE TABLE IF NOT EXISTS %s (classkey VARCHAR(64),value TEXT,UNIQUE (classkey))
*/

new TravTrie:g_ClassArray

// ...
public _ARP_ClassLoad(Plugin,Params)
{
if(Params != 4)
{
format(g_Query,4095,"Parameters do not match. Expected: 3, Found: %d",Params)
return UTIL_ARP_ThrowError(AMX_ERR_NATIVE,0,g_Query, Plugin)
}

new Param[64],Handler[64],Temp[128],Table[64],ClassName[128]//,Len = min(4095,get_param(4))
get_string(1,Param,63)
get_string(2,Handler,63)
get_string(3,g_Query,4095)
get_string(4,Table,63)

if(!Table[0])
copy(Table,63,g_DataTable)

format(ClassName,127,"%s|%s",Table,Param)

//g_Query[0] = Len

format(Temp,127,"%d|%s",Plugin,Handler)

new travTrieIter:Iter = GetTravTrieIterator(g_ClassArray),Cell,ClassH eader[64],Loaded,TravTrie:CurTrie,TravTrie:PluginTrie, Flag,ReadTable[64],Garbage[1]
while(MoreTravTrie(Iter))
{
ReadTravTrieKey(Iter,ClassHeader,63)
ReadTravTrieCell(Iter,Cell)

strtok(ClassHeader,ReadTable,63,Garbage,0,'|' )
if(Table[0] && equali(ReadTable,Table))
Flag = 1

if(equali(ClassName,ClassHeader))
{
TravTrieGetCell(g_ClassArray,ClassHeader,CurT rie)

TravTrieGetHCell(CurTrie,"/plugins",PluginTrie)
TravTrieSetCellEx(PluginTrie,Plugin,1)

TravTrieGetHCell(CurTrie,"/loaded",Loaded)
if(!Loaded)
{
new TravTrie:CallsTrie
TravTrieGetHCell(CurTrie,"/calls",CallsTrie)

TravTrieSetString(CallsTrie,Temp,g_Query)

return -1
}

/*new Forward = CreateOneForward(Plugin,"ARP_ClassLoaded",FP_CELL,FP_STRING),Return
if(!Forward || !ExecuteForward(Forward,Return,_:CurTrie,Clas s))
{
format(g_Query,4095,"Could not execute ARP_ClassLoaded forward")
return UTIL_ARP_ThrowError(AMX_ERR_NATIVE,0,g_Query, Plugin)
}
DestroyForward(Forward)*/

new Forward = CreateOneForward(Plugin,Handler,FP_CELL,FP_ST RING,FP_STRING),Return
//new CurArray = PrepareArray(g_Query[1],Len)
if(!Forward || !ExecuteForward(Forward,Return,_:CurTrie,Clas sHeader,g_Query))
{
format(g_Query,4095,"Could not execute %s forward to %d",Handler,Plugin)
return UTIL_ARP_ThrowError(AMX_ERR_NATIVE,0,g_Query, Plugin)
}
DestroyForward(Forward)

return PLUGIN_HANDLED
}
}
DestroyTravTrieIterator(Iter)

if(!Flag && !equali(g_DataTable,Table))
{
static Query[512]
switch(g_SqlMode)
{
case MYSQL:
format(Query,511,"CREATE TABLE IF NOT EXISTS %s (classkey VARCHAR(64),value TEXT,UNIQUE KEY (classkey))",Table)
case SQLITE:
format(Query,511,"CREATE TABLE IF NOT EXISTS %s (classkey VARCHAR(64),value TEXT,UNIQUE (classkey))",Table)
}
UTIL_ARP_CleverQuery(g_Plugin,g_SqlHandle,"IgnoreHandle",Query)
}

new Buffer[128] //,TravTrie:CallTrie = TravTrieCreate()
//SQL_QuoteString(g_SqlHandle,Buffer,127,Param)
copy(Buffer,126,Param)

//server_print("Setting array: %d | %s | %s | %d",CallTrie,Handler,g_Query,Len)
new TravTrie:CallTrie = TravTrieCreate()
TravTrieSetString(CallTrie,Temp,g_Query)

CurTrie = TravTrieCreate()
TravTrieSetCell(g_ClassArray,ClassName,CurTri e)
TravTrieSetHCell(CurTrie,"/loaded",0)
TravTrieSetHCell(CurTrie,"/saving",0)
TravTrieSetHCell(CurTrie,"/lastquery",0)
TravTrieSetHCell(CurTrie,"/plugins",TravTrieCreate())
TravTrieSetHCell(CurTrie,"/changed",TravTrieCreate())
TravTrieSetHCell(CurTrie,"/calls",CallTrie)
TravTrieSetHCell(CurTrie,"/savetrie",TravTrieCreate())
TravTrieSetString(CurTrie,"/table",Table)
//TravTrieSetString(CurTrie,"/table",Table)

Buffer[127] = _:CurTrie

format(g_Query,4095,"SELECT * FROM %s WHERE classkey LIKE '%s|%%'",Table,Buffer)
//UTIL_ARP_CleverQuery(g_Plugin,g_SqlHandle,"ClassLoadHandle",g_Query,Buffer,128)
SQL_ThreadQuery(g_SqlHandle,"ClassLoadHandle",g_Query,Buffer,128)

return PLUGIN_CONTINUE
}

public ClassLoadHandle(FailState,Handle:Query,Error[],Errcode,Data[],DataSize)
{
if(FailState == TQUERY_CONNECT_FAILED)
{
format(g_Query,4095,"Could not connect to database: %s",Error)
return UTIL_ARP_ThrowError(0,0,g_Query,0)
}
else if(FailState == TQUERY_QUERY_FAILED)
{
format(g_Query,4095,"Internal error: %s",Error)
return UTIL_ARP_ThrowError(0,0,g_Query,0)
}

if(Errcode)
{
format(g_Query,4095,"Error on query: %s",Error)
return UTIL_ARP_ThrowError(0,0,g_Query,0)
}

new ClassKey[128],Key[64],Value[128],Garbage[2],TravTrie:CurTrie = TravTrie:Data[127],TravTrie:CallsTrie
while(SQL_MoreResults(Query))
{
SQL_ReadResult(Query,0,ClassKey,127)
strtok(ClassKey,Garbage,1,Key,63,'|')
SQL_ReadResult(Query,1,Value,127)

TravTrieSetString(CurTrie,Key,Value)

SQL_NextRow(Query)
}

TravTrieSetHCell(CurTrie,"/loaded",1)

TravTrieGetHCell(CurTrie,"/calls",CallsTrie)
new travTrieIter:Iter = GetTravTrieIterator(CallsTrie),Handler[64],Forward,Return,Temp[64],PluginStr[10],Plugin
while(MoreTravTrie(Iter))
{
ReadTravTrieKey(Iter,Temp,63)
strtok(Temp,PluginStr,9,Handler,63,'|')
Plugin = str_to_num(PluginStr)
ReadTravTrieString(Iter,g_Query,4095)

Forward = CreateOneForward(Plugin,Handler,FP_CELL,FP_ST RING,FP_STRING)
//CurArray = PrepareArray(g_Query[1],g_Query[0])
if(!Forward || !ExecuteForward(Forward,Return,_:CurTrie,Data ,g_Query))
{
format(g_Query,4095,"Could not execute %s forward to %d",Handler,Plugin)
return UTIL_ARP_ThrowError(AMX_ERR_NATIVE,0,g_Query, 0)
}
DestroyForward(Forward)
}
DestroyTravTrieIterator(Iter)

TravTrieDestroy(CallsTrie)

Forward = CreateMultiForward("ARP_ClassLoaded",ET_IGNORE,FP_CELL,FP_STRING)
if(!Forward || !ExecuteForward(Forward,Return,_:CurTrie,Data ))
{
format(g_Query,4095,"Could not execute ARP_ClassLoaded forward")
return UTIL_ARP_ThrowError(AMX_ERR_NATIVE,0,g_Query, 0)
}
DestroyForward(Forward)

return PLUGIN_CONTINUE
}

public _ARP_ClassSave(Plugin,Params)
{
if(Params != 2)
{
format(g_Query,4095,"Parameters do not match. Expected: 2, Found: %d",Params)
return UTIL_ARP_ThrowError(AMX_ERR_NATIVE,0,g_Query, Plugin)
}

new TravTrie:ClassNum = TravTrie:get_param_byref(1),ProcClass[128],Close = get_param(2),travTrieIter:Iter = GetTravTrieIterator(g_ClassArray),TrieClass[64],TravTrie:CurTrie,TravTrie:PluginTrie,ClassNa me[64]
while(MoreTravTrie(Iter))
{
ReadTravTrieKey(Iter,TrieClass,63)
ReadTravTrieCell(Iter,CurTrie)

copy(ClassName,63,TrieClass[containi(TrieClass,"|") + 1])

if(CurTrie == ClassNum && !task_exists(_:CurTrie))
{
//SQL_QuoteString(g_SqlHandle,ProcClass,127,Cla ss)
copy(ProcClass[1],126,TrieClass)

//format(g_Query,4095,"DELETE FROM %s WHERE classkey LIKE '%s|%%'",g_DataTable,ProcClass)
//UTIL_ARP_CleverQuery(g_Plugin,g_SqlHandle,"IgnoreHandle",g_Query)

ProcClass[0] = _:CurTrie

new TravTrie:SaveTrie
TravTrieGetHCell(CurTrie,"/savetrie",SaveTrie)

new travTrieIter:Iter = GetTravTrieIterator(SaveTrie),Handler[64],Temp[128],PluginStr[10],Plugin,Forward,Return
//server_print("ITERATOR: %d, SAVETRIE: %d",Iter,SaveTrie)
while(MoreTravTrie(Iter))
{
ReadTravTrieKey(Iter,Temp,127)
ReadTravTrieString(Iter,g_Query,4095)

strtok(Temp,PluginStr,9,Handler,63,'|')
Plugin = str_to_num(PluginStr)

//server_print("Calling forward to: %d , %s",Plugin,Handler)

Forward = CreateOneForward(Plugin,Handler,FP_CELL,FP_ST RING,FP_STRING)
if(!Forward || !ExecuteForward(Forward,Return,CurTrie,ClassN ame,g_Query))
{
format(g_Query,4095,"Could not register forward")
return UTIL_ARP_ThrowError(AMX_ERR_NATIVE,0,g_Query, g_Plugin)
}
DestroyForward(Forward)
}
DestroyTravTrieIterator(Iter)

if(_CallEvent("Class_Save",ClassName,128))
return FAILED

if(Close)
{
TravTrieGetHCell(CurTrie,"/plugins",PluginTrie)
TravTrieDeleteKeyEx(PluginTrie,Plugin)

set_param_byref(1,_:Invalid_TravTrie)
}

SaveClass(CurTrie,ProcClass[1])

return SUCCEEDED
}
}
DestroyTravTrieIterator(Iter)

return SUCCEEDED
}

public SaveClass(TravTrie:CurTrie,ProcClass[])
{
new Saving
TravTrieGetHCell(CurTrie,"/saving",Saving)
if(Saving)
return SUCCEEDED

new TravTrie:ChangedTrie,Table[64],ClassName[64]//,Garbage[1]
TravTrieGetHCell(CurTrie,"/changed",ChangedTrie)
//TravTrieGetString(CurTrie,"/table",Table,63)
//TravTrieGetHCell(CurTrie,"/table",TableTrie)
//TravTrieGetStringEx(TableTrie,0,Table,63)

strtok(ProcClass,Table,63,ClassName,63,'|')

TravTrieSetHCell(CurTrie,"/saving",1)

new Key[128],Data[64],TrieClass[64]
Data[1] = _:CurTrie

copy(Data[2],60,ProcClass)

//format(g_Query,4095,"DELETE FROM %s WHERE classkey LIKE '%s|%%'",g_DataTable,ProcClass)
//UTIL_ARP_CleverQuery(g_Plugin,g_SqlHandle,"IgnoreHandle",g_Query)

new travTrieIter:Iter = GetTravTrieIterator(CurTrie),Changed,ChangedN um

// Run through it once to get the number
while(MoreTravTrie(Iter))
{
ReadTravTrieKey(Iter,TrieClass,63)
ReadTravTrieString(Iter,g_Query,4095)

TravTrieGetCell(ChangedTrie,TrieClass,Changed )

if(TrieClass[0] != '^n' && TrieClass[0] != '/' && Changed) ChangedNum++

Changed = 0
}

TravTrieSetHCell(CurTrie,"/lastquery",ChangedNum)
DestroyTravTrieIterator(Iter)

Iter = GetTravTrieIterator(CurTrie)
while(MoreTravTrie(Iter))
{
ReadTravTrieKey(Iter,TrieClass,63)
ReadTravTrieString(Iter,g_Query,4095)
TravTrieGetCell(ChangedTrie,TrieClass,Changed )

if(TrieClass[0] == '^0' || TrieClass[0] == '/' || !Changed) continue

TravTrieSetCell(ChangedTrie,TrieClass,0)

Changed = 0

//SQL_QuoteString(g_SqlHandle,Key,127,TrieClass )
copy(Key,127,TrieClass)
//SQL_QuoteString(g_SqlHandle,g_Cache,4095,g_Qu ery)
copy(g_Cache,4095,g_Query)

Data[0]++

ARP_SqlEscape(ClassName,127)
ARP_SqlEscape(Key,127)
ARP_SqlEscape(g_Cache,4095)
//replace_all(ClassName,127,"'","\'")
//replace_all(Key,127,"'","\'")
//replace_all(g_Cache,4095,"'","\'")

switch(g_SqlMode)
{
case MYSQL:
format(g_Query,4095,"INSERT INTO %s VALUES ('%s|%s','%s') ON DUPLICATE KEY UPDATE value='%s'",Table,ClassName,Key,g_Cache,g_Cache)
case SQLITE:
format(g_Query,4095,"REPLACE INTO %s VALUES ('%s|%s','%s')",Table,ClassName,Key,g_Cache)
}
UTIL_ARP_CleverQuery(g_Plugin,g_SqlHandle,"ClassSaveHandle",g_Query,Data,64)
}
DestroyTravTrieIterator(Iter)

new TravTrie:PluginTrie,TravTrie:SaveTrie
TravTrieGetHCell(CurTrie,"/plugins",PluginTrie)
TravTrieGetHCell(CurTrie,"/savetrie",SaveTrie)

if(!ChangedNum && !TravTrieSize(PluginTrie) && g_SqlMode == SQLITE)
{
TravTrieDestroy(PluginTrie)
TravTrieDestroy(CurTrie)
TravTrieDestroy(ChangedTrie)
TravTrieDestroy(SaveTrie)

TravTrieDeleteKey(g_ClassArray,ProcClass)
}

return SUCCEEDED
}

public ClassSaveHandle(FailState,Handle:Query,Error[],Errcode,Data[],DataSize)
{
if(FailState == TQUERY_CONNECT_FAILED)
{
format(g_Query,4095,"Could not connect to database: %s",Error)
return UTIL_ARP_ThrowError(0,0,g_Query,0)
}
else if(FailState == TQUERY_QUERY_FAILED)
{
SQL_GetQueryString(Query,g_Query,4095)
format(g_Query,4095,"Internal error: %s^nQuery: %s",Error,g_Query)
return UTIL_ARP_ThrowError(0,0,g_Query,0)
}

if(Errcode)
{
format(g_Query,4095,"Error on query: %s",Error)
return UTIL_ARP_ThrowError(0,0,g_Query,0)
}

new LastQuery,TravTrie:CurTrie = TravTrie:Data[1]
TravTrieGetHCell(CurTrie,"/lastquery",LastQuery)

if(Data[0] == LastQuery && g_SqlMode != SQLITE)
{
TravTrieSetHCell(CurTrie,"/saving",0)

new TravTrie:PluginTrie,TravTrie:ChangedTrie,Trav Trie:SaveTrie
TravTrieGetHCell(CurTrie,"/plugins",PluginTrie)
TravTrieGetHCell(CurTrie,"/changed",ChangedTrie)
TravTrieGetHCell(CurTrie,"/savetrie",SaveTrie)

if(!TravTrieSize(PluginTrie) && !g_PluginEnd)
{
TravTrieDestroy(PluginTrie)
TravTrieDestroy(CurTrie)
TravTrieDestroy(ChangedTrie)
TravTrieDestroy(SaveTrie)

TravTrieDeleteKey(g_ClassArray,Data[2])
}

//if(g_PluginEnd && g_LastOverallQuery == Data[2]) TravTrieDestroy(g_ClassArray)
}

return PLUGIN_CONTINUE
}

public _ARP_ClassSaveHook(Plugin,Params)
{
if(Params != 3)
{
format(g_Query,4095,"Parameters do not match. Expected: 3, Found: %d",Params)
return UTIL_ARP_ThrowError(AMX_ERR_NATIVE,0,g_Query, Plugin)
}

new TravTrie:CurTrie = TravTrie:get_param(1),Handler[64],Temp[128],TravTrie:SaveTrie
get_string(2,Handler,63)
get_string(3,g_Query,4095)

format(Temp,127,"%d|%s",Plugin,Handler)

TravTrieGetHCell(CurTrie,"/savetrie",SaveTrie)
TravTrieSetString(SaveTrie,Temp,g_Query)

return SUCCEEDED
}

public _ARP_ClassDeleteKey(Plugin,Params)
{
if(Params != 2)
{
format(g_Query,4095,"Parameters do not match. Expected: 2, Found: %d",Params)
return UTIL_ARP_ThrowError(AMX_ERR_NATIVE,0,g_Query, Plugin)
}

new TravTrie:ClassNum = TravTrie:get_param(1),Key[64]
get_string(2,Key,63)

if(Key[0] == '/' || Key[0] == '^n' || !Class)
return FAILED

new travTrieIter:Iter = GetTravTrieIterator(g_ClassArray),Cell,Name[64]
while(MoreTravTrie(Iter))
{
ReadTravTrieKey(Iter,Name,63)
ReadTravTrieCell(Iter,Cell)
if(Cell == _:ClassNum)
break
}
DestroyTravTrieIterator(Iter)

//if(!Name[0])
// return FAILED

format(g_Query,4095,"DELETE FROM %s WHERE classkey='%s|%s'",g_DataTable,Name,Key)
UTIL_ARP_CleverQuery(g_Plugin,g_SqlHandle,"IgnoreHandle",g_Query)

return TravTrieDeleteKey(ClassNum,Key)
}

Lt.RAT
05-01-2010, 15:29
The first thing we see is #pragma reqclass "dyn_test".

^_____^

Hawk552
05-01-2010, 17:36
^_____^

Oops, it was "#pragma reqlib" in the example. Dunno what happened there. Fixed.

drekes
05-02-2010, 18:54
[ style = 0 ] - this is will be covered later

Now, onto style = 1. This style basically forgets about the iPlugin,iParams part of the header, and then assumes the plugin will pass the parameters correctly. This means _half_user_hp would look like:

I see you'r talking about style but i read the code 4 times but can't seem to find it. Is'nt it there or am i blind?

wrecked_
05-02-2010, 19:36
I see you'r talking about style but i read the code 4 times but can't seem to find it. Is'nt it there or am i blind?
Just use the style he uses. It's generally better.

drekes
05-02-2010, 19:46
Just use the style he uses. It's generally better.

Okay, i'll do that

V I R U S
01-29-2012, 19:24
I have a question about returning a local value:


#include <amxmodx>

new g_iUserID[33];

public plugin_init()
register_plugin("Dynamic Native Test - Handler", "1.0", "Hawk552")

public plugin_natives() {
register_library("dyn_test")
register_native("getid","_getid")
}

// Here some other functions which set the UserID

public _getid(id) {
return g_iUserID[id];
}
#include <amxmodx>
#include <dyn_test>

public plugin_init()
register_plugin("Dynamic Native Test - Caller", "1.0", "Hawk552")

public client_putinserver(id) {
server_print("UserID: %i", getid(id));
}#pragma reqlib "dyn_test"

native getid(id)Sooooo, i can compile this without a single error. The trouble is, that the caller doesn't return a value. It's simple empty.

Some trick?

ConnorMcLeod
01-29-2012, 19:39
Because g_iUserID[] is never filled anywhere in the plugin, it's your job to do so...

fysiks
01-29-2012, 19:41
I have a question about returning a local value:


#include <amxmodx>

new g_iUserID[33];

public plugin_init()
register_plugin("Dynamic Native Test - Handler", "1.0", "Hawk552")

public plugin_natives() {
register_library("dyn_test")
register_native("getid","_getid")
}

// Here some other functions which set the UserID

public _getid(id) {
return g_iUserID[id];
}
#include <amxmodx>
#include <dyn_test>

public plugin_init()
register_plugin("Dynamic Native Test - Caller", "1.0", "Hawk552")

public client_putinserver(id) {
server_print("UserID: %i", getid(id));
}#pragma reqlib "dyn_test"

native getid(id)Sooooo, i can compile this without a single error. The trouble is, that the caller doesn't return a value. It's simple empty.

Some trick?

You need to re-look at the example in the first post. You are trying to pass "id" through the function which doesn't work like that (with the way you register the native).

V I R U S
01-29-2012, 20:17
fysiks,
you mean this part?
public _getid(id)

ConnorMcLeod,
take look at this part

// Here some other functions which set the UserID

There is an other function which uses MySQL.



If i call the g_iUserID[id]directly, it works like a charm...

fysiks
01-29-2012, 20:22
fysiks,
you mean this part?
public _getid(id)


Yes. Notice how it doesn't match the example.

V I R U S
01-29-2012, 20:26
But it seems to work if i just print the "id" in server console. Seems that the parameter "id" works...

public _getid(iPlugin, iParams) {
if(iParams != 1)
return PLUGIN_CONTINUE

new id = get_param(1)
if(!id)
return PLUGIN_CONTINUE

server_print("ID: %i", id);
server_print("UserID: %s", g_iUserID[id]);

return PLUGIN_HANDLED
}1st print is ok, 2nd is empty...

fysiks
01-29-2012, 20:34
You are trying to print a string with a scalar value (I'm assuming your variable name is accurate).

V I R U S
01-29-2012, 20:54
You are trying to print a string with a scalar value (I'm assuming your variable name is accurate).
I've tryed it with all possible types, string, integer, bool... No one works.
BTW, that was just a misstype =)

fysiks
01-29-2012, 21:28
I've tryed it with all possible types, string, integer, bool... No one works.
BTW, that was just a misstype =)

Then there is something wrong with the array or it doesn't contain anything. Show the full code.

ConnorMcLeod
01-30-2012, 01:20
Since we know that returning an integer via dynamic natives works fine, you may post in scripting/help forum.

Liverwiz
06-29-2012, 14:34
I'm having trouble loading my extension plugin. I think it has to do with the .inc file. I tried with #pragma reqlib FragsBrass with and without quotes. (the file name is FragsBrass.inc) But it just doesn't seem to work. Am i missing something?

Core Program:
/**************************
* // Native Defnies
**************************/
public plugin_natives()
{
register_library("FragsBrass.inc")

register_native("bkf_getLevel", "_getLevel")
register_native("bkf_setLevel", "_setLevel")
register_native("bkf_getFrags", "_getFrags")
register_native("bkf_setFrags", "_setFrags")
register_native("bkf_giveFrags", "_giveFrags")
register_native("bkf_hasBrass", "_hasBrass")
}

public _getLevel(plugin, iParamID)
return getLevel(get_param(iParamID) )
public _getFrags(plugin, iParamID)
return gi_playerFrags[ get_param(iParamID) ]
public _hasBrass(plugin, iParamID)
return g_bknuckles[ get_param(iParamID) ]
public _setLevel(plugin, iParamID, iParamLevel, iParamReason)
{
static id, level
id = get_param(iParamID)
level = get_param(iParamLevel)
new reason[16]; get_string(iParamReason, reason, charsmax(reason) )
return setLevel(id, level, reason)
}
public _setFrags(plugin, iParamID, iParamFrags, iParamReason)
{
static id, frags
id = get_param(iParamID)
frags = get_param(iParamFrags)
new reason[16]; get_string(iParamReason, reason, charsmax(reason) )
return setFrags(id, frags, reason)
}
public _giveFrags(plugin, iParamID, iParamFrags, iParamReason)
{
static id, frags
id = get_param(iParamID)
frags = get_param(iParamFrags)
new reason[16]; get_string(iParamReason, reason, charsmax(reason) )
return giveFrags(id, frags, reason)
}

FragsBrass.inc:
/* Header generated by AMXX-Studio

#if AMXX_VERSION_NUM >= 18
#if !defined AMXMODX_NOAUTOLOAD
#pragma defclasslib FragsBrass
#endif
#else
#pragma library FragsBrass
#endif
*/
// I messed with the above stuff for a bit, C/Ping from other .inc files, but to no avail
#define _FragsBrass_included
#pragma library FragsBrass // also added this, as seen in other .inc files
#pragma reqlib FragsBrass

native bkf_getLevel(id)
native bkf_setLevel(id, level, const reason[] = "NO_REASON")

native bkf_getFrags(id)
native bkf_setFrags(id, frags, const reason[] = "NO_REASON")
native bkf_giveFrags(id, frags, const reason[] = "NO_REASON")

native bkf_hasBrass(id)


My Extension:
/* Plugin generated by AMXX-Studio */

#include <amxmodx>
/*#include <amxmisc>
#include <cstrike>
#include <fun>*/
#include <FragsBrass>

#define PLUGIN "Brass Knuckles Powers"
#define VERSION "0.2"
#define AUTHOR "Liverwiz"



public plugin_init()
{
register_plugin(PLUGIN, VERSION, AUTHOR)


if(is_plugin_loaded("frags_brass.amxx") == -1)
{
log_amx("[BKP] Brass Knuckles plugin is not loaded. Thus causes errors, and this plugin is now disabled.")
//plugin_pcvar = set_cvar_num("pwr_plugin", 0)
}
}

public bkf_setPowers(id)
{
client_print(0, print_chat, "SetPowers Executed!")
client_print(0, print_chat, "SetPowers Executed!")
client_print(0, print_chat, "SetPowers Executed!")
client_print(0, print_console, "SetPowers Executed!")
client_print(0, print_console, "SetPowers Executed!")
client_print(0, print_console, "SetPowers Executed!")
static frags, level
frags = bkf_getFrags(id)
level = bkf_getLevel(id)
new does[6]; (bkf_hasBrass(id)) ? formatex(does, 5, "do") : formatex(does, 5, "don't")
client_print(id, print_chat, "[BKF] You %s have Brass Knuckles. Level: %d Frags: %d", level, frags)
return PLUGIN_CONTINUE
}


seen in brass_powers.sma:
bkf_setPowers(id) is a working forward defined in the core. It was tested before i included the header, and the workings thereof.

t3hNox
06-29-2012, 14:46
Check "core program".
You use iParamID incorrectly. iParamID returns the total amount of parameters passed to the native. For example, for bkf_setFrags you should pass 3 parameters and thus in native handle you should check whether iParamID is 3 (to avoid possibility that too many or not enough parameters are provided) and get the first parameter by using get_param(1) (id), get_param(2) (level), get_param(3, szReason, charsmax(szReason)) (reason). I'm also not sure if you can specify "a default" value for reasons.

ConnorMcLeod
06-29-2012, 16:29
register_library("FragsBrass.inc")

->

register_library("FragsBrass")

You dont register a file, but a library name, it has to match the one you pass with #reqlib in .inc file.
Also, you'd better to check for params num and when param is a player index to check range so you can fire an error that gonna appear from the 3rd plugin instead of from your core plugin.

Liverwiz
06-30-2012, 11:04
Thanks guys! All of your suggestions/corrections lead to a properly working API. i send love. <3