Compiled with: -I/opt/local/include/mysql5/mysql/ -L/opt/local/lib/mysql5/mysql/ -lmysqlclient -lconfig++ (boost/ is a symlink) Outputs: Stats Plugin: Loading config file (config)... loaded config version 0.10! Stats Plugin: Loaded Stats version 0.10. Stats Plugin: Connected to MySQL 5.0.27 server via protocol 10. SELECT svrid FROM servers WHERE hostport = 'bzflag.bounceme.net:5154' Stats Plugin: Found server number 1 in database () (Warning count: 0). UPDATE servers SET display = '1' WHERE hostport = 'bzflag.bounceme.net:5154' .........Stats Plugin: Getting player data for "test"... done! INSERT INTO sessions VALUES(null,1,'test',null,'127.0.0.1',NOW(),null,5,0) Stats Plugin: Player session ID is 0 () (Warning count: 0). ......^C.UPDATE servers SET display = '0' WHERE hostport = 'bzflag.bounceme.net:5154' Stats Plugin: Closed MySQL connection. Stats.h: // System #include #include // BZFS #include "bzfsAPI.h" #include "TextUtils.h" // MySQL #include // LibConfig #include // Boost libs #include #include using namespace std; using namespace libconfig; using boost::format; // The main beef class StatsHandler : public bz_EventHandler { public: StatsHandler() {}; // constructor ~StatsHandler() {}; // destructor virtual void process(bz_EventData *eventData); // shebang // Server info bool addServer(); bool updateServerInfo(); bool isServerPublic(); string getServerAddress(); string getServerDescription(); // Events bool addPlayer(int playerID); // Misc int convertTeamToInt(bz_eTeamType team); private: // Server info struct serverInfo { int id; bool publicServer; string publicAddress; string publicDescription; } srvInfo; // Player info bool updateCurrentPlayer(int playerID); struct currentPlayerInfo { int id; string callsign; string email; int team; string ip; } plrInfo; struct allPlayerInfo { int sessid; int id; string callsign; string email; int team; string ip; }; map plrIDtoSessID; // playerID to session ID map allPlrInfo; }; // BZFX Command class CommandHandler : public bz_CustomSlashCommandHandler, public StatsHandler { public: CommandHandler() {}; // constructor ~CommandHandler() {}; // destructor virtual bool handle(int playerID, bzApiString command, bzApiString message, bzAPIStringList *param); }; Stats.cpp: #include "Stats.h" BZ_GET_PLUGIN_VERSION bool pluginFailed = false; float pluginVersion = 0.1; StatsHandler statsHandle; CommandHandler commandHandle; MYSQL *mysql = NULL; MYSQL_RES *result; MYSQL_ROW row; string dbQuery; // libconfig Config cfg; BZF_PLUGIN_CALL int bz_Load(const char* commandLine) { // Register them events bz_registerCustomSlashCommand ("bzfx", &commandHandle); bz_registerEvent(bz_eTickEvent, &statsHandle); bz_registerEvent(bz_ePlayerJoinEvent, &statsHandle); bz_setMaxWaitTime(5.0f); // Read the config file try { printf("Stats Plugin: Loading config file (%s)...", commandLine); cfg.readFile(commandLine); printf(" loaded config version %.2f!\n", float(cfg.lookup("plugin.version"))); } catch (...) { printf("Stats Plugin: Could not read config file: %s\n", commandLine); } if (pluginVersion == float(cfg.lookup("plugin.version"))) { printf("Stats Plugin: Loaded Stats version %.2f.\n", pluginVersion); } else { printf("Stats Plugin: Plugin and config mismatch! Plugin version %.2f and config version %.2f. There may be unexpected results!\n", pluginVersion, float(cfg.lookup("plugin.version"))); } // Init MySQL mysql = mysql_init(mysql); if (!mysql) { pluginFailed = true; printf("Stats Plugin: MySQL init failed, out of memory? (%s) (Warning count: %i)\n", mysql_error(mysql), mysql_warning_count(mysql)); } if (!mysql_real_connect(mysql, cfg.lookup("mysql.host"), cfg.lookup("mysql.user"), cfg.lookup("mysql.password"), cfg.lookup("mysql.database"), 0, NULL, 0 )) { printf("Stats Plugin: MySQL connection failed (%s) (Warning count: %i).\n", mysql_error(mysql), mysql_warning_count(mysql)); } else { printf("Stats Plugin: Connected to MySQL %s server via protocol %i.\n", mysql_get_server_info(mysql), mysql_get_proto_info(mysql)); } statsHandle.addServer(); return 0; } BZF_PLUGIN_CALL int bz_Unload(void) { // Unregister the events bz_removeCustomSlashCommand("bzfx"); bz_removeEvent(bz_eTickEvent, &statsHandle); bz_removeEvent(bz_ePlayerJoinEvent, &statsHandle); // Disconnect from MySQL only if we are connected dbQuery = str(format("UPDATE servers SET display = '0' WHERE hostport = '%s'") % statsHandle.getServerAddress()); cout << dbQuery << endl; if (mysql_query(mysql, dbQuery.c_str())) { printf("Stats Plugin: Server shutdown query failed (%s) (Warning count: %i).\n", mysql_error(mysql), mysql_warning_count(mysql)); } if (mysql) { mysql_close(mysql); printf("Stats Plugin: Closed MySQL connection.\n"); } return 0; } void StatsHandler::process(bz_EventData *eventData) { if (eventData->eventType == bz_eTickEvent) { cout << "."; cout.flush(); return; } else if (eventData->eventType == bz_ePlayerJoinEvent) { statsHandle.addPlayer(((bz_PlayerJoinPartEventData*)eventData)->playerID); } else { // WTF? return; } } bool CommandHandler::handle(int playerID, bzApiString command, bzApiString message, bzAPIStringList *param) { printf("Stats Plugin: Got command. %s - %s - %i\n", statsHandle.getServerAddress().c_str(), statsHandle.getServerDescription().c_str(), statsHandle.isServerPublic()); return true; } bool StatsHandler::updateServerInfo() { this->srvInfo.publicServer = bz_getPublic(); this->srvInfo.publicAddress = bz_getPublicAddr().c_str(); this->srvInfo.publicDescription = bz_getPublicDescription().c_str(); return true; } bool StatsHandler::addServer() { this->updateServerInfo(); // DB dbQuery = str(format("SELECT svrid FROM servers WHERE hostport = '%s'") % this->getServerAddress()); cout << dbQuery << endl; if (mysql_query(mysql, dbQuery.c_str())) { printf("Stats Plugin: Server select query failed (%s) (Warning count: %i).\n", mysql_error(mysql), mysql_warning_count(mysql)); } else { result = mysql_store_result(mysql); row = mysql_fetch_row(result); if (!result) { // no rows found! pluginFailed = true; printf("Stats Plugin: Could not find server in database (%s) (Warning count: %i).\n", mysql_error(mysql), mysql_warning_count(mysql)); return false; } else { // set server info's id so we have it later // FIXME: I'm just asking for it if I don't check if this is really an int, fix later this->srvInfo.id = atoi(row[0]); printf("Stats Plugin: Found server number %d in database (%s) (Warning count: %i).\n", this->srvInfo.id, mysql_error(mysql), mysql_warning_count(mysql)); } } // OK, we're online if (!pluginFailed) { dbQuery = str(format("UPDATE servers SET display = '1' WHERE hostport = '%s'") % this->getServerAddress()); cout << dbQuery << endl; if (mysql_query(mysql, dbQuery.c_str())) { printf("Stats Plugin: Server SELECT query failed (%s) (Warning count: %i).\n", mysql_error(mysql), mysql_warning_count(mysql)); return false; } } return true; } bool StatsHandler::isServerPublic() { return (this->srvInfo.publicServer); } string StatsHandler::getServerAddress() { return (this->srvInfo.publicAddress); } string StatsHandler::getServerDescription() { return (this->srvInfo.publicDescription); } bool StatsHandler::updateCurrentPlayer(int playerID) { bz_PlayerRecord *data = bz_getPlayerByIndex(playerID); if (data) { printf("Stats Plugin: Getting player data for \"%s\"... ", data->callsign.c_str()); // Data this->allPlrInfo[playerID].id = playerID; this->allPlrInfo[playerID].callsign = data->callsign.c_str(); this->allPlrInfo[playerID].email = data->email.c_str(); this->allPlrInfo[playerID].ip = data->ipAddress.c_str(); this->allPlrInfo[playerID].team = this->convertTeamToInt(data->team); bz_freePlayerRecord(data); printf("done!\n"); return true; } else { bz_freePlayerRecord(data); printf("Stats Plugin: Player data is null! Aborting!\n"); return false; } // Really, WTF? return false; } bool StatsHandler::addPlayer(int playerID) { this->updateCurrentPlayer(playerID); // DB dbQuery = str(format("INSERT INTO sessions VALUES(null,%i,'%s',null,'%s',NOW(),null,%i,%i)") % this->srvInfo.id % this->allPlrInfo[playerID].callsign % this->allPlrInfo[playerID].ip % this->allPlrInfo[playerID].team % this->allPlrInfo[playerID].id); cout << dbQuery << endl; if (mysql_query(mysql, dbQuery.c_str())) { printf("Stats Plugin: Player session insert failed (%s) (Warning count: %i).\n", mysql_error(mysql), mysql_warning_count(mysql)); return false; } else { unsigned long lastID = mysql_insert_id(mysql); printf("Stats Plugin: Player session ID is %lu (%s) (Warning count: %i).\n", lastID, mysql_error(mysql), mysql_warning_count(mysql)); } return true; } int StatsHandler::convertTeamToInt(bz_eTeamType team) { if (team == eRogueTeam) return 0; else if (team == eRedTeam) return 1; else if (team == eGreenTeam) return 2; else if (team == eBlueTeam) return 3; else if (team == ePurpleTeam) return 4; else if (team == eObservers) return 5; else if (team == eHunterTeam) return 0; else if (team == eRabbitTeam) return 1; else return -1; }