[TUT] Dynamic / Fake Natives

For scripting examples for learning how to use AMXX.
User avatar
Archive-Poster
Posts: 1347
Gender:
United States of America

[TUT] Dynamic / Fake Natives

Post by Archive-Poster »

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:

Code: Select all

#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
}

Code: Select all

#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)

Code: Select all

// 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.

Code: Select all

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:

Code: Select all

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.
User avatar
Archive-Poster
Posts: 1347
Gender:
United States of America

Re: [TUT] Dynamic / Fake Natives

Post by Archive-Poster »

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.

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.
User avatar
Archive-Poster
Posts: 1347
Gender:
United States of America

Re: [TUT] Dynamic / Fake Natives

Post by Archive-Poster »

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.
User avatar
Archive-Poster
Posts: 1347
Gender:
United States of America

Re: [TUT] Dynamic / Fake Natives

Post by Archive-Poster »

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?
For centralized setting and controlling all accounts to all used SQL servers in all plugins.
Check out this code. Requires CellTravTrie.
<to be added>

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().

Code: Select all

// 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,ClassHeader[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,CurTrie)
            
            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,Class))
            {
                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_STRING,FP_STRING),Return
            //new CurArray = PrepareArray(g_Query[1],Len)
            if(!Forward || !ExecuteForward(Forward,Return,_:CurTrie,ClassHeader,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,CurTrie)
    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_STRING,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,ClassName[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,Class)
            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_STRING,FP_STRING)
                if(!Forward || !ExecuteForward(Forward,Return,CurTrie,ClassName,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,ChangedNum
    
    // 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_Query)
        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,TravTrie: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)
}
Post Reply

Who is online

Users browsing this forum: No registered users and 0 guests