Raised This Month: $ Target: $400
 0% 

First Plugin, Need Guidance


Post New Thread Reply   
 
Thread Tools Display Modes
Monkeys
Veteran Member
Join Date: Jan 2010
Old 01-16-2011 , 20:20   Re: First Plugin, Need Guidance
Reply With Quote #11

What they meant is that there is no global "main" function, one function to rule them all, or something of those sorts ;p

I would suggest using static for global variables, instead of new. (They're zeroed automaticly on plugin load anyway)

Some odd coding you have there, though.

PHP Code:
public Event_GGWin(Handle:event, const String:name[], bool:dontBroadcast)
{
    
//Handle stats recording on map end
    
if (viper_active_bool == true) { // You can just type if(viper_active_bool){, seeing how it's a boolean.
        
PrintToChatAll("DEBUG MSG: GG Win detected! Viper ON.")
    } else if (
viper_active_bool != true) { //No need for this check. It's IMPOSSIBLE for a boolean to be anything but false after your previous check.
        
PrintToChatAll("DEBUG MSG: GG Win detected! Viper OFF.")
    } else {
        
PrintToChatAll("DEBUG MSG: GG Win detected! Viper Boolean Not Accessible.")
    }

===>
PHP Code:
public Event_GGWin(Handle:event, const String:name[], bool:dontBroadcast)
{
    
//Handle stats recording on map end
    
if (viper_active_bool) { 
        
PrintToChatAll("DEBUG MSG: GG Win detected! Viper ON.")
    } else {
        
PrintToChatAll("DEBUG MSG: GG Win detected! Viper OFF.")
    }



And a helpful tip: if you use #pragma semicolon 1 after including whatever you need, the compiler will expect ; terminated lines. This helps you find syntax issues a lot easier.
__________________
Get a lid on that zombie,
he's never gonna be alri-i-ight.
Oooh get a lid on that zombie,
or he's gonna feed all night.
Monkeys is offline
thatguyyoulove
Member
Join Date: Nov 2010
Location: North Texas
Old 01-16-2011 , 21:50   Re: First Plugin, Need Guidance
Reply With Quote #12

Oh yes the coding was for testing, though I do for some reason always forget that for booleans you don't have to use == or !=. The last else was basically to see if the boolean was even in the scope of the function. I wasn't sure if the compiler would let me know that if it wasn't accessible or not. Thanks for the tip. I will look up the use of static variables and '#pragma semicolon 1' (which I have no idea on what that means. Google time )

EDIT:
Rereading what you wrote about the pragma semicolon made sense and I love it. Feels more familiar coming from PHP/Java. More organized.

Last edited by thatguyyoulove; 01-17-2011 at 05:57.
thatguyyoulove is offline
thatguyyoulove
Member
Join Date: Nov 2010
Location: North Texas
Old 01-17-2011 , 18:47   Re: First Plugin, Need Guidance
Reply With Quote #13

Trying to figure out the best way to pull a user's data but this is not exactly working. I'm getting an error on the steamid section and if I comment it all out then the Client Name returns the server's name. Help?

PHP Code:
new scoreclient;
    new 
max_clients GetMaxClients();
    for (new 
1<= max_clientsi++) {
        if (
IsClientInGame(i) && !IsFakeClient(i) && GetClientTeam(i) > 0) {

            
//Build Client Object
            
scoreclient GetClientOfUserId(i);
            
            
//Get Client's Name
            
decl String:client_name[80];
            
GetClientName(scoreclientclient_namesizeof(client_name));
            
            
            
//Get Client's Steam ID
            
decl String:client_steamid[24];
            
GetClientAuthString(scoreclientclient_steamidsizeof(client_steamid));
            

            
//Get Client's Kills
            
new client_kills GetClientFrags(i);

            
//Get Client's Deaths
            
new client_deaths GetClientDeaths(i);

            
//Echo Client Data to Chat
            
PrintToChatAll("Player %s (%s) has %d kills and %d deaths."client_nameclient_steamidclient_killsclient_deaths);
        }
    } 
thatguyyoulove is offline
Monkeys
Veteran Member
Join Date: Jan 2010
Old 01-18-2011 , 13:36   Re: First Plugin, Need Guidance
Reply With Quote #14

Quote:
Originally Posted by thatguyyoulove View Post
Trying to figure out the best way to pull a user's data but this is not exactly working. I'm getting an error on the steamid section and if I comment it all out then the Client Name returns the server's name. Help?
GetMaxClients() is deprecated. There's a new global variable called MaxClients you should use.
And Clients will always be between 1 - MaxClients, UserIds won't.
Meaning you're reading through Clients, not UserIds, making the conversion unneeded.
Here's what it should/could look like:
PHP Code:
for (new 1<= MaxClientsi++) {
        if (
IsClientInGame(i) && !IsFakeClient(i) && GetClientTeam(i) > 0) {
            
//Get Client's Name
            
decl String:client_name[80];
            
GetClientName(iclient_namesizeof(client_name));
            
/* The above is correct, but if you only need this in a formatting function (like PrintToChat and Format)
               you can just use the %N formatter, which converts a Client number into the player's name.
             */
            
            //Get Client's Steam ID
            
decl String:client_steamid[24];
            
GetClientAuthString(iclient_steamidsizeof(client_steamid));
            
            
//Get Client's Kills
            
new client_kills GetClientFrags(i);

            
//Get Client's Deaths
            
new client_deaths GetClientDeaths(i);

            
//Echo Client Data to Chat
            
PrintToChatAll("Player %N (%s) has %d kills and %d deaths."iclient_steamidclient_killsclient_deaths);
            
/* I used the mentioned %N here */
        
}

__________________
Get a lid on that zombie,
he's never gonna be alri-i-ight.
Oooh get a lid on that zombie,
or he's gonna feed all night.
Monkeys is offline
thatguyyoulove
Member
Join Date: Nov 2010
Location: North Texas
Old 01-18-2011 , 15:14   Re: First Plugin, Need Guidance
Reply With Quote #15

That worked a treat. Thank you very much Monkeys, you have been extremely helpful and explained things well. Looks like I'm off to the SQL stuff. I'm assuming %N works in SQL query formatting as well?
thatguyyoulove is offline
Monkeys
Veteran Member
Join Date: Jan 2010
Old 01-18-2011 , 15:22   Re: First Plugin, Need Guidance
Reply With Quote #16

If done within Sourcemod, yeah.
Any function where you find "String:Something[], any:..." in the prototype, it'll work (as those are the most common functions supporting formatting).

(EDIT: However, using it directly into a SQL query is FORBIDDEN. Please be warry of SQL injection)
__________________
Get a lid on that zombie,
he's never gonna be alri-i-ight.
Oooh get a lid on that zombie,
or he's gonna feed all night.

Last edited by Monkeys; 01-18-2011 at 15:44.
Monkeys is offline
thatguyyoulove
Member
Join Date: Nov 2010
Location: North Texas
Old 04-28-2012 , 17:54   Re: First Plugin, Need Guidance
Reply With Quote #17

I did end up finishing this plugin last year, but since I didn't use threaded queries it became lag inducing after each player death (there is an SQL query in the Event_PlayerDeath event).

So I have been re-writing it as a threaded plugin! However I am having a bit of trouble dealing with the asynchronous nature of the beast and believe I may be creating some race conditions which are causing errors. I say that namely because I had this plugin working yesterday, but today I am getting errors as soon as the plugin loads.

Namely, I am getting the FailState in the OnMapStart function each time now.

Any ideas on how to get this done in a reliable way?

PHP Code:
#pragma semicolon 1
#include <sourcemod.inc>
#include <sdktools.inc>

new Handle:viper_db_hndl;

new 
Handle:db INVALID_HANDLE;

new 
Handle:viper_table_name_hndl;

new 
Handle:viper_matches_table_name_hndl;
new 
Handle:viper_weapons_table_name_hndl;
new 
Handle:viper_players_table_name_hndl;

new 
viper_event_id;
new 
viper_event_match;

new 
Handle:viper_event_name_hndl;

new 
bool:viper_active_bool;

new 
bool:viper_stats_yet_recorded_bool;

new 
String:db_config[64];

new 
String:viper_table_name[64];

new 
String:viper_matches_table_name[64];
new 
String:viper_weapons_table_name[64];
new 
String:viper_players_table_name[64];

new 
String:eventname[128];
new 
String:eventnameescaped[196];

public 
Plugin:myinfo =
{
    
name "Viper Tournament System"author "Colt McCormack",
    
description "A tournament/event statistics plugin with MySQL upload and PHP interface",
    
version "0.5.2",
    
url "http://mccormackmediaonline.com"
};

public 
OnPluginStart()
{
    
//Create Commands
    
RegAdminCmd("sm_viperstart"Command_ViperStartADMFLAG_RCON"sm_viperstart [starts Viper system]");
    
RegAdminCmd("sm_viperstop"Command_ViperStopADMFLAG_RCON"sm_viperstop [stops Viper system]");
    
RegAdminCmd("sm_viperscores"Command_ViperScoresADMFLAG_RCON"sm_vipersscores [debug Viper system command to show scores]");

    
//Debug Commands
    
RegAdminCmd("sm_viperscores_save"Command_ViperScoresSaveADMFLAG_RCON,  "sm_vipersscores_save [debug command to submit scores to database]");

    
//Hook End Events
    
HookEvent("player_death"Event_PlayerDeath); HookEvent("gg_win"Event_GGWin); 
    
HookEvent("cs_win_panel_match"Event_CSWinPanelMatch);

    
//Create CVars
    
viper_db_hndl CreateConVar("viper_db_config""default""Database configuration to use for Viper Tournament System"FCVAR_PLUGIN);
    
viper_table_name_hndl CreateConVar("viper_table_config""viper_events""MySQL table to use for Viper Tournament System Events"FCVAR_PLUGIN);
    
viper_matches_table_name_hndl CreateConVar("viper_matches_table_config""viper_event_matches""MySQL table to use for Viper Tournament System Match Data. Don't change unless necessary."FCVAR_PLUGIN);
    
viper_weapons_table_name_hndl CreateConVar("viper_weapons_table_config""viper_event_weapons""MySQL table to use for Viper Tournament System Weapons Data. Don't change unless necessary."FCVAR_PLUGIN);
    
viper_players_table_name_hndl CreateConVar("viper_players_table_config""viper_event_players""MySQL table to use for Viper Tournament System Player Data. Don't change unless necessary."FCVAR_PLUGIN);
    
viper_event_name_hndl CreateConVar("viper_event_name""Viper Tournament""The human readable name for the event to be used."FCVAR_PLUGIN);

    
//Monitor CVar changes and execute accompanying functions
    
HookConVarChange(viper_db_hndlOnViperDBChange); 
    
HookConVarChange(viper_table_name_hndlOnViperDBChange); 
    
HookConVarChange(viper_matches_table_name_hndlOnViperDBChange); 
    
HookConVarChange(viper_weapons_table_name_hndlOnViperDBChange); 
    
HookConVarChange(viper_players_table_name_hndlOnViperDBChange); 
    
HookConVarChange(viper_event_name_hndlOnViperEventNameChange);

    
//Execute config file, or create if it does not exist.
    
AutoExecConfig(true"viper");

    
//Load table names into global vars for use by functions 
    
GetConVarString(viper_db_hndldb_configsizeof(db_config)); 
    
GetConVarString(viper_table_name_hndlviper_table_namesizeof(viper_table_name));
    
GetConVarString(viper_matches_table_name_hndlviper_matches_table_namesizeof(viper_matches_table_name));
    
GetConVarString(viper_weapons_table_name_hndlviper_weapons_table_namesizeof(viper_weapons_table_name));
    
GetConVarString(viper_players_table_name_hndlviper_players_table_namesizeof(viper_players_table_name));

    
//Set stats to recordable
    
viper_stats_yet_recorded_bool false;

    
//Connect to database
    
ConnectDB();
}

public 
ConnectDB()
{
    
// Verify that the configuration is defined in databases.cfg 
    
if (!SQL_CheckConfig(db_config)) {
        
Viper_LogError("Database configuration \"%s\" does not exist"db_config);
    }

    
// Attempt to establish a connection
    
SQL_TConnect(Callback_SQL_OnConnectdb_config);
}

public 
Callback_SQL_OnConnect(Handle:ownerHandle:hndl, const String:error[], any:data)
{
    
//Since we are not guaranteed a connection, we make sure it actually worked
    
if (hndl == INVALID_HANDLE)
    {
        
//It didn't work, so we log the error
        
Viper_LogError("Database failure: %s"error);

        
//Error if database may not be reached or caused error
        
Viper_LogError("FATAL: An error occurred while connecting to the database.");
        
SetFailState("An error occurred while connecting to the database.");
    } else {
        
//It worked, so we set the global to the handle the callback provided for this connection
        
Viper_LogMessage("Successfully connected to the database!");
        
db hndl;
        
PrepareDB();
    }
}

public 
PrepareDB() {
    
//Make sure that the user is using mysql
    
decl String:driver[32];
    
SQL_ReadDriver(dbdriversizeof(driver));

    if (
strcmp(driver"mysql"))
    {
        
Viper_LogError("Error establishing database connection: Database driver is \"%s\" and should be \"mysql\"."driver);
        
SetFailState("FATAL: Error establishing database connection: Database driver is \"%s\" and should be \"mysql\"."driver);
        return 
false;
    }

    
//Create SQL tables if needed 
    
decl String:query[256];
    
Format(querysizeof(query), "CREATE TABLE IF NOT EXISTS %s (event_id INT(4) NOT NULL auto_increment, event_name VARCHAR(128), current_match INT(4) NOT NULL DEFAULT '1', start_time DATETIME, end_time DATETIME, is_active TINYINT(1), PRIMARY KEY (event_id))"viper_table_name);
    
SQL_TQuery(dbCallback_SQLQueryFatalquery);

    
decl String:query2[256];
    
Format(query2sizeof(query2), "CREATE TABLE IF NOT EXISTS %s (event_id INT(4), event_match INT(4), client_steam VARCHAR(24), client_kills INT(4), client_deaths INT(4), gg_win TINYINT(1), PRIMARY KEY (event_id, event_match, client_steam))"viper_matches_table_name);
    
SQL_TQuery(dbCallback_SQLQueryFatalquery2);

    
decl String:query3[1300];
    
Format(query3sizeof(query3), "CREATE TABLE IF NOT EXISTS %s (event_id INT(4), client_steam VARCHAR(24), client_tks INT(4) NOT NULL, client_suicides INT(4) NOT NULL, knife_kills INT(4) NOT NULL, usp_kills INT(4) NOT NULL, glock_kills INT(4) NOT NULL, p228_kills INT(4) NOT NULL, deagle_kills INT(4) NOT NULL, fiveseven_kills INT(4) NOT NULL, elite_kills INT(4) NOT NULL, m3_kills INT(4) NOT NULL, xm1014_kills INT(4) NOT NULL, mp5navy_kills INT(4) NOT NULL, tmp_kills INT(4) NOT NULL, p90_kills INT(4) NOT NULL, mac10_kills INT(4) NOT NULL, ump45_kills INT(4) NOT NULL, galil_kills INT(4) NOT NULL, famas_kills INT(4) NOT NULL, m4a1_kills INT(4) NOT NULL, ak47_kills INT(4) NOT NULL, aug_kills INT(4) NOT NULL, sg552_kills INT(4) NOT NULL, sg550_kills INT(4) NOT NULL, g3sg1_kills INT(4) NOT NULL, scout_kills INT(4) NOT NULL, awp_kills INT(4) NOT NULL, m249_kills INT(4) NOT NULL, hegrenade_kills INT(4) NOT NULL, PRIMARY KEY (client_steam))"viper_weapons_table_name);
    
SQL_TQuery(dbCallback_SQLQueryFatalquery3);

    
decl String:query4[256];
    
Format(query4sizeof(query4), "CREATE TABLE IF NOT EXISTS %s (client_steam VARCHAR(24), client_name VARCHAR(80), PRIMARY KEY (client_steam))"viper_players_table_name);
    
SQL_TQuery(dbCallback_SQLQueryFatalquery4);
    
    
IsEventActive();
    
    return 
true;
}

public 
Callback_SQLQueryFatal(Handle:ownerHandle:hndl, const String:error[], any:data)
{
    if( 
hndl == INVALID_HANDLE )
    {
        
Viper_LogError("FATAL:[SQL] [ERROR] %s"error); 
        
SetFailState("[SQL] [ERROR] %s"error);
    }
}

public 
OnMapStart()
{
    if( 
db == INVALID_HANDLE )
    {
        
SetFailState("Invalid Handle in OnMapStart()");
    }
    
//Check for any live events and activate the system if found
    
IsEventActive();
    
    
//Set stats to recordable again at every new map
    
viper_stats_yet_recorded_bool false;
}

public 
IsEventActive()
{
    if( 
db == INVALID_HANDLE )
    {
        
SetFailState("Invalid Handle in IsEventActive()");
    }
    
decl String:query[256];
    
Format(querysizeof(query), "SELECT `event_id`, `current_match` FROM `%s` WHERE `is_active`=1"viper_table_name);
    
SQL_TQuery(dbCallback_IsEventActivequery);
}

public 
Callback_IsEventActive(Handle:ownerHandle:hndl, const String:error[], any:data)
{
    new 
eventactiverows SQL_GetRowCount(hndl);
    if (
eventactiverows >= 2) {
        
Viper_LogError("FATAL: Multiple events are set to active.");
        
SetFailState("Multiple events are set to active.");
        return;
    }
    if (
eventactiverows == 0){
        
Viper_LogMessage("No events are currently active.");
        
viper_active_bool false;
        return;
    }
    
//Set ID for active event
    
SQL_FetchRow(hndl);
    
viper_event_id SQL_FetchInt(hndl0);
    
viper_event_match SQL_FetchInt(hndl1); 
    
viper_active_bool true;
    
Viper_LogMessage("The event already exists and is now in use. The event id is: %d. The event is currently on match #%d."viper_event_idviper_event_match);

thatguyyoulove is offline
berni
SourceMod Plugin Approver
Join Date: May 2007
Location: Austria
Old 04-28-2012 , 18:48   Re: First Plugin, Need Guidance
Reply With Quote #18

It's not guranteed that the database connection is (already) ready when OnMapStart is called, so you should remove the checking there. The database is ready when Callback_SQL_OnConnect is called, not at any other time.
__________________
Why reinvent the wheel ? Download smlib with over 350 useful functions.

When people ask me "Plz" just because it's shorter than "Please" I feel perfectly justified to answer "No" because it's shorter than "Yes"
powered by Core i7 3770k | 32GB DDR3 1886Mhz | 2x Vertex4 SSD Raid0

Last edited by berni; 04-28-2012 at 18:49.
berni is offline
Dr. Greg House
Professional Troll,
Part-Time Asshole
Join Date: Jun 2010
Old 04-28-2012 , 19:34   Re: First Plugin, Need Guidance
Reply With Quote #19

Theoretically you could just use the non-threaded version SQL_Connect since you're only going to establish the connection to the database once, which is when the server is being started. Noone cares if it lags for a split second then because noone is on the server at that moment anyway.

Last edited by Dr. Greg House; 04-28-2012 at 19:39.
Dr. Greg House is offline
thatguyyoulove
Member
Join Date: Nov 2010
Location: North Texas
Old 04-30-2012 , 19:10   Re: First Plugin, Need Guidance
Reply With Quote #20

Thanks berni that took care of it, not sure why I added that check in the first place.

I believe I have another race condition created in my PrepareDB() function. It creates 4 threaded queries and is immediately followed by calling the IsEventActive() function which utilizes the databases in those queries. As such I can see a potential issue happening where IsEventActive() fires off it's queries before the databases are even fully created.

I would like to combine those 4 queries into a single one, and then call IsEventActive() from it's callback in order to make sure things are done in the proper order. However I get an Error:075 The input line is too long from the compiler when I combine them all. The only forum posts I could find related to this were for AMX Mod X, but I did what I thought would perform the same function in SM (replacing charsmax() with sizeof()-1) and got this:

PHP Code:
    //Create SQL tables if needed     
    
decl String:query[2100];
    static 
pos;
    
pos=0;
    
    
pos += FormatEx(query[pos], sizeof(query) - pos"CREATE TABLE IF NOT EXISTS %s (event_id INT(4) NOT NULL auto_increment, event_name VARCHAR(128), current_match INT(4) NOT NULL DEFAULT '1', start_time DATETIME, end_time DATETIME, is_active TINYINT(1), PRIMARY KEY (event_id))"viper_table_name);
    
    
pos += FormatEx(query[pos], sizeof(query) - pos"CREATE TABLE IF NOT EXISTS %s (event_id INT(4), event_match INT(4), client_steam VARCHAR(24), client_kills INT(4), client_deaths INT(4), gg_win TINYINT(1), PRIMARY KEY (event_id, event_match, client_steam))"viper_matches_table_name);
    
    
pos += FormatEx(query[pos], sizeof(query) - pos"CREATE TABLE IF NOT EXISTS %s (event_id INT(4), client_steam VARCHAR(24), client_tks INT(4) NOT NULL, client_suicides INT(4) NOT NULL, knife_kills INT(4) NOT NULL, usp_kills INT(4) NOT NULL, glock_kills INT(4) NOT NULL, p228_kills INT(4) NOT NULL, deagle_kills INT(4) NOT NULL, fiveseven_kills INT(4) NOT NULL, elite_kills INT(4) NOT NULL, m3_kills INT(4) NOT NULL, xm1014_kills INT(4) NOT NULL, mp5navy_kills INT(4) NOT NULL, tmp_kills INT(4) NOT NULL, p90_kills INT(4) NOT NULL, mac10_kills INT(4) NOT NULL, ump45_kills INT(4) NOT NULL, galil_kills INT(4) NOT NULL, famas_kills INT(4) NOT NULL, m4a1_kills INT(4) NOT NULL, ak47_kills INT(4) NOT NULL, aug_kills INT(4) NOT NULL, sg552_kills INT(4) NOT NULL, sg550_kills INT(4) NOT NULL, g3sg1_kills INT(4) NOT NULL, scout_kills INT(4) NOT NULL, awp_kills INT(4) NOT NULL, m249_kills INT(4) NOT NULL, hegrenade_kills INT(4) NOT NULL, PRIMARY KEY (client_steam))"viper_weapons_table_name);
    
    
pos += FormatEx(query[pos], sizeof(query) - pos"CREATE TABLE IF NOT EXISTS %s (client_steam VARCHAR(24), client_name VARCHAR(80), PRIMARY KEY (client_steam))"viper_players_table_name);
    
    
Viper_LogDebug("Prepare DB SQL: %s"query);
    
SQL_TQuery(dbCallback_PrepareDBquery); 
Unfortunately it spits this out in my debug log:
PHP Code:
Prepare DB SQLCREATE TABLE IF NOT EXISTS viper_events (event_id INT(4NOT NULL auto_incrementevent_name VARCHAR(128), current_match INT(4NOT NULL DEFAULT '1'start_time DATETIMEend_time DATETIMEis_active TINYINT(1), PRIMARY KEY (event_id))CREA 
So it appears that something is awry! Is there not a real charsmax replacement function for SM? I didn't see it in the scripting reference and using charsmax() said it was unknown.

EDIT: Oh geez! It was working fine, I just had a buffer of 256 characters in my custom logging function. No wonder the output made no sense!

Last edited by thatguyyoulove; 04-30-2012 at 21:36.
thatguyyoulove is offline
Reply



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

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

Forum Jump


All times are GMT -4. The time now is 06:23.


Powered by vBulletin®
Copyright ©2000 - 2024, vBulletin Solutions, Inc.
Theme made by Freecode