749 lines
24 KiB
C
749 lines
24 KiB
C
/*****************************************************************************/
|
|
/* Logger plugin version 1.0 alpha. */
|
|
/* Contact: */
|
|
/*****************************************************************************/
|
|
/* That code is placed under the GNU General Public Licence (GPL) */
|
|
/* (C)2007 by Weeger Nicolas (Feel free to deliver your complaints) */
|
|
/*****************************************************************************/
|
|
/* CrossFire, A Multiplayer game for X-windows */
|
|
/* */
|
|
/* Copyright (C) 2000 Mark Wedel */
|
|
/* Copyright (C) 1992 Frank Tore Johansen */
|
|
/* */
|
|
/* This program is free software; you can redistribute it and/or modify */
|
|
/* it under the terms of the GNU General Public License as published by */
|
|
/* the Free Software Foundation; either version 2 of the License, or */
|
|
/* (at your option) any later version. */
|
|
/* */
|
|
/* This program is distributed in the hope that it will be useful, */
|
|
/* but WITHOUT ANY WARRANTY; without even the implied warranty of */
|
|
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
|
|
/* GNU General Public License for more details. */
|
|
/* */
|
|
/* You should have received a copy of the GNU General Public License */
|
|
/* along with this program; if not, write to the Free Software */
|
|
/* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
|
|
/* */
|
|
/*****************************************************************************/
|
|
|
|
/**
|
|
* @file cflogger.c
|
|
* This plugin will log events to an sqlite3 database named cflogger.db in the
|
|
* var directory.
|
|
*
|
|
* Log includes:
|
|
* @li players join/leave/creation/quit
|
|
* @li map load/unload/reset/enter/leave
|
|
* @li kills, whenever a player is concerned
|
|
* @li ingame/real time links
|
|
*
|
|
* @warning
|
|
* The plugin will not check the database size, which can grow a lot.
|
|
*
|
|
* @note
|
|
* Thanks to sqlite's locking, it's possible to access the database through the command
|
|
* line even while the server is running.
|
|
*/
|
|
|
|
#include <cflogger.h>
|
|
#ifndef __CEXTRACT__
|
|
#include <cflogger_proto.h>
|
|
#endif
|
|
/*#include <stdarg.h>*/
|
|
|
|
#include <sqlite3.h>
|
|
|
|
/** Current database format */
|
|
#define CFLOGGER_CURRENT_FORMAT 3
|
|
|
|
/** Pointer to the logging database. */
|
|
static sqlite3 *database;
|
|
|
|
/** To keep track of stored ingame/real time matching. */
|
|
static int last_stored_day = -1;
|
|
|
|
/**
|
|
* Simple callback to get an integer from a query.
|
|
*
|
|
* @param param
|
|
* user-supplied data.
|
|
* @param argc
|
|
* number of items.
|
|
* @param argv
|
|
* values.
|
|
* @param azColName
|
|
* column names.
|
|
*
|
|
* @return
|
|
* always returns 0 to continue the execution.
|
|
*/
|
|
static int check_tables_callback(void *param, int argc, char **argv, char **azColName) {
|
|
int *format = (int *)param;
|
|
|
|
*format = atoi(argv[0]);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Helper function to run a SQL query.
|
|
*
|
|
* Will LOG() an error if the query fails.
|
|
*
|
|
* @param sql
|
|
* query to run.
|
|
*
|
|
* @return
|
|
* SQLITE_OK if no error, other value if error.
|
|
*
|
|
* @note
|
|
* There is most likely no need to check return value unless you need to
|
|
* rollback a transaction or similar.
|
|
*/
|
|
static int do_sql(const char *sql) {
|
|
int err;
|
|
char *msg;
|
|
|
|
if (!database)
|
|
return -1;
|
|
|
|
err = sqlite3_exec(database, sql, NULL, NULL, &msg);
|
|
if (err != SQLITE_OK) {
|
|
cf_log(llevError, " [%s] error: %d [%s] for sql = %s\n", PLUGIN_NAME, err, msg, sql);
|
|
sqlite3_free(msg);
|
|
}
|
|
return err;
|
|
}
|
|
|
|
/**
|
|
* Updates a table to a new schema, used for when ALTER TABLE doesn't work.
|
|
* (Such as when changing column constraints.)
|
|
*
|
|
* @param table
|
|
* Name of table.
|
|
* @param newschema
|
|
* This is the new table format. Will be inserted into the parantheses of
|
|
* "create table table_name()".
|
|
* @param select_columns
|
|
* This is inserted into "INSERT INTO table_name SELECT _ FROM ..." to allow
|
|
* changing order of columns, or skipping some. Normally it should be "*".
|
|
*
|
|
* @warning
|
|
* This function should only be used in check_tables() below.
|
|
*
|
|
* No error checking is done. Also it is expected that appending an _old
|
|
* on the table name won't collide with anything.
|
|
*
|
|
* Note that columns are expected to have same (or compatible) type, and be in
|
|
* the same order. Further both tables should have the same number of columns.
|
|
*
|
|
* @return
|
|
* SQLITE_OK if no error, non-zero if error. This SHOULD be rollback any
|
|
* transaction this function is called in.
|
|
*/
|
|
static int update_table_format(const char *table, const char *newschema,
|
|
const char *select_columns) {
|
|
char *sql;
|
|
int err;
|
|
|
|
sql = sqlite3_mprintf("ALTER TABLE %s RENAME TO %s_old;", table, table);
|
|
err = do_sql(sql);
|
|
sqlite3_free(sql);
|
|
if (err != SQLITE_OK)
|
|
return err;
|
|
|
|
sql = sqlite3_mprintf("CREATE TABLE %s(%s);", table, newschema);
|
|
err = do_sql(sql);
|
|
sqlite3_free(sql);
|
|
if (err != SQLITE_OK)
|
|
return err;
|
|
|
|
sql = sqlite3_mprintf("INSERT INTO %s SELECT %s FROM %s_old;",
|
|
table, select_columns, table);
|
|
err = do_sql(sql);
|
|
sqlite3_free(sql);
|
|
if (err != SQLITE_OK)
|
|
return err;
|
|
|
|
sql = sqlite3_mprintf("DROP TABLE %s_old;", table, table);
|
|
err = do_sql(sql);
|
|
sqlite3_free(sql);
|
|
/* Final return. */
|
|
return err;
|
|
}
|
|
|
|
/**
|
|
* Helper macros for rolling back and returning if query failed.
|
|
* Used in check_tables().
|
|
*
|
|
* Yes they are quite messy. The alternatives seemed worse.
|
|
*/
|
|
#define DO_OR_ROLLBACK(sqlstring) \
|
|
if (do_sql(sqlstring) != SQLITE_OK) { \
|
|
do_sql("rollback transaction;"); \
|
|
cf_log(llevError, " [%s] Logger database format update failed! Couldn't upgrade from format %d to fromat %d!. Won't log.\n", PLUGIN_NAME, format, CFLOGGER_CURRENT_FORMAT);\
|
|
sqlite3_close(database); \
|
|
database = NULL; \
|
|
return; \
|
|
}
|
|
|
|
#define UPDATE_OR_ROLLBACK(tbl, newschema, select_columns) \
|
|
if (update_table_format((tbl), (newschema), (select_columns)) != SQLITE_OK) { \
|
|
do_sql("rollback transaction;"); \
|
|
cf_log(llevError, " [%s] Logger database format update failed! Couldn't upgrade from format %d to fromat %d!. Won't log.\n", PLUGIN_NAME, format, CFLOGGER_CURRENT_FORMAT);\
|
|
sqlite3_close(database); \
|
|
database = NULL; \
|
|
return; \
|
|
}
|
|
|
|
/**
|
|
* Checks the database format, and applies changes if old version.
|
|
*/
|
|
static void check_tables(void) {
|
|
int format;
|
|
int err;
|
|
format = 0;
|
|
|
|
err = sqlite3_exec(database, "select param_value from parameters where param_name = 'version';", check_tables_callback, &format, NULL);
|
|
|
|
/* Safety check. */
|
|
if (format > CFLOGGER_CURRENT_FORMAT) {
|
|
cf_log(llevError, " [%s] Logger database format (%d) is newer than supported (%d) by this binary!. Won't log.\n", PLUGIN_NAME, format, CFLOGGER_CURRENT_FORMAT);
|
|
/* This will disable using the db since do_sql() checks if database is
|
|
* NULL.
|
|
*/
|
|
sqlite3_close(database);
|
|
database = NULL;
|
|
}
|
|
|
|
/* Check if we need to upgrade/create database. */
|
|
if (format < 1) {
|
|
cf_log(llevDebug, " [%s] Creating logger database schema (format 1).\n", PLUGIN_NAME);
|
|
if (do_sql("BEGIN EXCLUSIVE TRANSACTION;") != SQLITE_OK) {
|
|
cf_log(llevError, " [%s] Logger database format update failed! Couldn't acquire exclusive lock to database when upgrading from format %d to fromat %d!. Won't log.\n", PLUGIN_NAME, format, CFLOGGER_CURRENT_FORMAT);
|
|
sqlite3_close(database);
|
|
database = NULL;
|
|
return;
|
|
}
|
|
DO_OR_ROLLBACK("create table living(liv_id integer primary key autoincrement, liv_name text, liv_is_player integer, liv_level integer);");
|
|
DO_OR_ROLLBACK("create table region(reg_id integer primary key autoincrement, reg_name text);");
|
|
DO_OR_ROLLBACK("create table map(map_id integer primary key autoincrement, map_path text, map_reg_id integer);");
|
|
DO_OR_ROLLBACK("create table time(time_real integer, time_ingame text);");
|
|
|
|
DO_OR_ROLLBACK("create table living_event(le_liv_id integer, le_time integer, le_code integer, le_map_id integer);");
|
|
DO_OR_ROLLBACK("create table map_event(me_map_id integer, me_time integer, me_code integer, me_living_id integer);");
|
|
DO_OR_ROLLBACK("create table kill_event(ke_time integer, ke_victim_id integer, ke_victim_level integer, ke_map_id integer , ke_killer_id integer, ke_killer_level integer);");
|
|
|
|
DO_OR_ROLLBACK("create table parameters(param_name text, param_value text);");
|
|
DO_OR_ROLLBACK("insert into parameters values( 'version', '1' );");
|
|
do_sql("COMMIT TRANSACTION;");
|
|
}
|
|
|
|
/* Must be able to handle update from format 1. If we are creating a new
|
|
* database, format 1 is still created first, then updated.
|
|
*
|
|
* This way is simpler than having to create two ways to make a format 2 db.
|
|
*/
|
|
if (format < 2) {
|
|
cf_log(llevDebug, " [%s] Upgrading logger database schema (to format 2).\n", PLUGIN_NAME);
|
|
if (do_sql("BEGIN EXCLUSIVE TRANSACTION;") != SQLITE_OK) {
|
|
cf_log(llevError, " [%s] Logger database format update failed! Couldn't acquire exclusive lock to database when upgrading from format %d to fromat %d!. Won't log.\n", PLUGIN_NAME, format, CFLOGGER_CURRENT_FORMAT);
|
|
sqlite3_close(database);
|
|
database = NULL;
|
|
return;
|
|
}
|
|
/* Update schema for various tables. Why so complex? Because ALTER TABLE
|
|
* can't add the "primary key" bit or other constraints...
|
|
*/
|
|
UPDATE_OR_ROLLBACK("living", "liv_id INTEGER PRIMARY KEY AUTOINCREMENT, liv_name TEXT NOT NULL, liv_is_player INTEGER NOT NULL, liv_level INTEGER NOT NULL", "*");
|
|
UPDATE_OR_ROLLBACK("region", "reg_id INTEGER PRIMARY KEY AUTOINCREMENT, reg_name TEXT UNIQUE NOT NULL", "*");
|
|
UPDATE_OR_ROLLBACK("map", "map_id INTEGER PRIMARY KEY AUTOINCREMENT, map_path TEXT NOT NULL, map_reg_id INTEGER NOT NULL, CONSTRAINT map_path_reg_id UNIQUE(map_path, map_reg_id)", "*");
|
|
#if 0
|
|
/* Turned out this was incorrect. And version 1 -> 3 directly works for this. */
|
|
UPDATE_OR_ROLLBACK("time", "time_real INTEGER PRIMARY KEY, time_ingame TEXT UNIQUE NOT NULL");
|
|
#endif
|
|
UPDATE_OR_ROLLBACK("living_event", "le_liv_id INTEGER NOT NULL, le_time INTEGER NOT NULL, le_code INTEGER NOT NULL, le_map_id INTEGER NOT NULL", "*");
|
|
UPDATE_OR_ROLLBACK("map_event", "me_map_id INTEGER NOT NULL, me_time INTEGER NOT NULL, me_code INTEGER NOT NULL, me_living_id INTEGER NOT NULL", "*");
|
|
UPDATE_OR_ROLLBACK("kill_event", "ke_time INTEGER NOT NULL, ke_victim_id INTEGER NOT NULL, ke_victim_level INTEGER NOT NULL, ke_map_id INTEGER NOT NULL, ke_killer_id INTEGER NOT NULL, ke_killer_level INTEGER NOT NULL", "*");
|
|
|
|
/* Handle changed parameters table format: */
|
|
/* Due to backward compatiblity "primary key" in SQLite doesn't imply
|
|
* "not null" (http://www.sqlite.org/lang_createtable.html), unless it
|
|
* is "integer primary key".
|
|
*
|
|
* We don't need to save anything stored in this in format 1, it was
|
|
* only used for storing what format was used.
|
|
*/
|
|
DO_OR_ROLLBACK("DROP TABLE parameters;");
|
|
DO_OR_ROLLBACK("CREATE TABLE parameters(param_name TEXT NOT NULL PRIMARY KEY, param_value TEXT);");
|
|
DO_OR_ROLLBACK("INSERT INTO parameters (param_name, param_value) VALUES( 'version', '2' );");
|
|
|
|
/* Create various indexes. */
|
|
DO_OR_ROLLBACK("CREATE INDEX living_name_player_level ON living(liv_name,liv_is_player,liv_level);");
|
|
|
|
/* Newspaper module could make use of some indexes too: */
|
|
DO_OR_ROLLBACK("CREATE INDEX kill_event_time ON kill_event(ke_time);");
|
|
DO_OR_ROLLBACK("CREATE INDEX map_reg_id ON map(map_reg_id);");
|
|
|
|
/* Finally commit the transaction. */
|
|
do_sql("COMMIT TRANSACTION;");
|
|
}
|
|
|
|
if (format < 3) {
|
|
cf_log(llevDebug, " [%s] Upgrading logger database schema (to format 3).\n", PLUGIN_NAME);
|
|
if (do_sql("BEGIN EXCLUSIVE TRANSACTION;") != SQLITE_OK) {
|
|
cf_log(llevError, " [%s] Logger database format update failed! Couldn't acquire exclusive lock to database when upgrading from format %d to fromat %d!. Won't log.\n", PLUGIN_NAME, format, CFLOGGER_CURRENT_FORMAT);
|
|
sqlite3_close(database);
|
|
database = NULL;
|
|
return;
|
|
}
|
|
UPDATE_OR_ROLLBACK("time", "time_ingame TEXT NOT NULL PRIMARY KEY, time_real INTEGER NOT NULL", "time_ingame, time_real");
|
|
DO_OR_ROLLBACK("UPDATE parameters SET param_value = '3' WHERE param_name = 'version';");
|
|
do_sql("COMMIT TRANSACTION;");
|
|
/* After all these changes better vacuum... The tables could have been
|
|
* huge, and since we recreated several of them above there could be a
|
|
* lot of wasted space.
|
|
*/
|
|
do_sql("VACUUM;");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns a unique identifier for specified object.
|
|
*
|
|
* Will insert an item in the table if required.
|
|
*
|
|
* If the object is a player, only name is taken into account to generate an id.
|
|
*
|
|
* Else, the object's level is taken into account, to distinguish monsters with
|
|
* the same name and different levels (special monsters, and such).
|
|
*
|
|
* @param living
|
|
* object to get identifier for.
|
|
* @return
|
|
* unique identifier in the 'living' table.
|
|
*/
|
|
static int get_living_id(object *living) {
|
|
char **line;
|
|
char *sql;
|
|
int nrow, ncolumn, id;
|
|
|
|
if (living->type == PLAYER)
|
|
sql = sqlite3_mprintf("select liv_id from living where liv_name='%q' and liv_is_player = 1", living->name);
|
|
else
|
|
sql = sqlite3_mprintf("select liv_id from living where liv_name='%q' and liv_is_player = 0 and liv_level = %d", living->name, living->level);
|
|
sqlite3_get_table(database, sql, &line, &nrow, &ncolumn, NULL);
|
|
|
|
if (nrow > 0)
|
|
id = atoi(line[ncolumn]);
|
|
else {
|
|
sqlite3_free(sql);
|
|
sql = sqlite3_mprintf("insert into living(liv_name, liv_is_player, liv_level) values('%q', %d, %d)", living->name, living->type == PLAYER ? 1 : 0, living->level);
|
|
do_sql(sql);
|
|
id = sqlite3_last_insert_rowid(database);
|
|
}
|
|
sqlite3_free(sql);
|
|
sqlite3_free_table(line);
|
|
return id;
|
|
}
|
|
|
|
/**
|
|
* Gets the unique identifier for a region.
|
|
*
|
|
* Will generate one if required.
|
|
*
|
|
* @param reg
|
|
* region for which an id is wanted
|
|
* @return
|
|
* unique region identifier, or 0 if reg is NULL.
|
|
*/
|
|
static int get_region_id(region *reg) {
|
|
char **line;
|
|
char *sql;
|
|
int nrow, ncolumn, id;
|
|
|
|
if (!reg)
|
|
return 0;
|
|
|
|
sql = sqlite3_mprintf("select reg_id from region where reg_name='%q'", reg->name);
|
|
sqlite3_get_table(database, sql, &line, &nrow, &ncolumn, NULL);
|
|
|
|
if (nrow > 0)
|
|
id = atoi(line[ncolumn]);
|
|
else {
|
|
sqlite3_free(sql);
|
|
sql = sqlite3_mprintf("insert into region(reg_name) values( '%q' )", reg->name);
|
|
do_sql(sql);
|
|
id = sqlite3_last_insert_rowid(database);
|
|
}
|
|
sqlite3_free(sql);
|
|
sqlite3_free_table(line);
|
|
return id;
|
|
}
|
|
|
|
/**
|
|
* Gets the unique identifier for a map.
|
|
*
|
|
* Will generate one if required.
|
|
*
|
|
* Maps starting with '/random/' will all share the same identifier for the same region.
|
|
*
|
|
* @param map
|
|
* map for which an id is wanted. Must not be NULL.
|
|
* @return
|
|
* unique map identifier.
|
|
*/
|
|
static int get_map_id(mapstruct *map) {
|
|
char **line;
|
|
char *sql;
|
|
int nrow, ncolumn, id, reg_id;
|
|
const char *path = map->path;
|
|
|
|
if (strncmp(path, "/random/", 7) == 0)
|
|
path = "/random/";
|
|
|
|
reg_id = get_region_id(map->region);
|
|
sql = sqlite3_mprintf("select map_id from map where map_path='%q' and map_reg_id = %d", path, reg_id);
|
|
sqlite3_get_table(database, sql, &line, &nrow, &ncolumn, NULL);
|
|
|
|
if (nrow > 0)
|
|
id = atoi(line[ncolumn]);
|
|
else {
|
|
sqlite3_free(sql);
|
|
sql = sqlite3_mprintf("insert into map(map_path, map_reg_id) values( '%q', %d)", path, reg_id);
|
|
do_sql(sql);
|
|
id = sqlite3_last_insert_rowid(database);
|
|
}
|
|
sqlite3_free(sql);
|
|
sqlite3_free_table(line);
|
|
|
|
return id;
|
|
}
|
|
|
|
/**
|
|
* Stores a line to match current ingame and real time.
|
|
*
|
|
* @return
|
|
* 1 if a line was inserted, 0 if the current ingame time was already logged.
|
|
*/
|
|
static int store_time(void) {
|
|
char **line;
|
|
char *sql;
|
|
int nrow, ncolumn;
|
|
char date[50];
|
|
time_t now;
|
|
timeofday_t tod;
|
|
|
|
cf_get_time(&tod);
|
|
now = time(NULL);
|
|
|
|
if (tod.day == last_stored_day)
|
|
return 0;
|
|
last_stored_day = tod.day;
|
|
|
|
snprintf(date, 50, "%10d-%2d-%2d %2d:%2d", tod.year, tod.month, tod.day, tod.hour, tod.minute);
|
|
|
|
sql = sqlite3_mprintf("select * from time where time_ingame='%q'", date);
|
|
sqlite3_get_table(database, sql, &line, &nrow, &ncolumn, NULL);
|
|
sqlite3_free(sql);
|
|
sqlite3_free_table(line);
|
|
if (nrow > 0)
|
|
return 0;
|
|
|
|
sql = sqlite3_mprintf("insert into time (time_ingame, time_real) values( '%s', %d )", date, now);
|
|
do_sql(sql);
|
|
sqlite3_free(sql);
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Logs an event for a living object.
|
|
*
|
|
* @param pl
|
|
* object for which to log an event.
|
|
* @param event_code
|
|
* arbitrary event code.
|
|
*/
|
|
static void add_player_event(object *pl, int event_code) {
|
|
int id = get_living_id(pl);
|
|
int map_id = 0;
|
|
char *sql;
|
|
|
|
if (pl->map)
|
|
map_id = get_map_id(pl->map);
|
|
|
|
sql = sqlite3_mprintf("insert into living_event values( %d, %d, %d, %d)", id, time(NULL), event_code, map_id);
|
|
do_sql(sql);
|
|
sqlite3_free(sql);
|
|
}
|
|
|
|
/**
|
|
* Logs an event for a map.
|
|
*
|
|
* @param map
|
|
* map for which to log an event.
|
|
* @param event_code
|
|
* arbitrary event code.
|
|
* @param pl
|
|
* object causing the event. Can be NULL.
|
|
*/
|
|
static void add_map_event(mapstruct *map, int event_code, object *pl) {
|
|
int mapid;
|
|
int playerid = 0;
|
|
char *sql;
|
|
|
|
if (pl && pl->type == PLAYER)
|
|
playerid = get_living_id(pl);
|
|
|
|
mapid = get_map_id(map);
|
|
sql = sqlite3_mprintf("insert into map_event values( %d, %d, %d, %d)", mapid, time(NULL), event_code, playerid);
|
|
do_sql(sql);
|
|
sqlite3_free(sql);
|
|
}
|
|
|
|
/**
|
|
* Logs a death.
|
|
*
|
|
* If either of the parameters is NULL, or if neither is a PLAYER, nothing is logged.
|
|
*
|
|
* @param victim
|
|
* who died.
|
|
* @param killer
|
|
* who killed.
|
|
*/
|
|
static void add_death(object *victim, object *killer) {
|
|
int vid, kid, map_id;
|
|
char *sql;
|
|
|
|
if (!victim || !killer)
|
|
return;
|
|
if (victim->type != PLAYER && killer->type != PLAYER) {
|
|
/* Killer might be a bullet, which might be owned by the player. */
|
|
object *owner = cf_object_get_object_property(killer, CFAPI_OBJECT_PROP_OWNER);
|
|
if (owner != NULL && owner->type == PLAYER)
|
|
killer = owner;
|
|
else
|
|
return;
|
|
}
|
|
|
|
vid = get_living_id(victim);
|
|
kid = get_living_id(killer);
|
|
map_id = get_map_id(victim->map);
|
|
sql = sqlite3_mprintf("insert into kill_event values( %d, %d, %d, %d, %d, %d)", time(NULL), vid, victim->level, map_id, kid, killer->level);
|
|
do_sql(sql);
|
|
sqlite3_free(sql);
|
|
}
|
|
|
|
/**
|
|
* Main plugin entry point.
|
|
*
|
|
* @param iversion
|
|
* server version.
|
|
* @param gethooksptr
|
|
* function to get hooks from.
|
|
* @return
|
|
* always 0.
|
|
*/
|
|
CF_PLUGIN int initPlugin(const char *iversion, f_plug_api gethooksptr) {
|
|
cf_init_plugin(gethooksptr);
|
|
cf_log(llevInfo, "%s init\n", PLUGIN_VERSION);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Gets a plugin property.
|
|
*
|
|
* @param type
|
|
* ignored.
|
|
* @return
|
|
* @li the name, if asked for 'Identification'.
|
|
* @li the version, if asked for 'FullName'.
|
|
* @li NULL else.
|
|
*/
|
|
CF_PLUGIN void *getPluginProperty(int *type, ...) {
|
|
va_list args;
|
|
const char *propname;
|
|
char *buf;
|
|
int size;
|
|
|
|
va_start(args, type);
|
|
propname = va_arg(args, const char *);
|
|
|
|
if (!strcmp(propname, "Identification")) {
|
|
buf = va_arg(args, char *);
|
|
size = va_arg(args, int);
|
|
va_end(args);
|
|
snprintf(buf, size, PLUGIN_NAME);
|
|
return NULL;
|
|
} else if (!strcmp(propname, "FullName")) {
|
|
buf = va_arg(args, char *);
|
|
size = va_arg(args, int);
|
|
va_end(args);
|
|
snprintf(buf, size, PLUGIN_VERSION);
|
|
return NULL;
|
|
}
|
|
va_end(args);
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Runs a plugin command. Doesn't do anything.
|
|
*
|
|
* @param op
|
|
* ignored.
|
|
* @param params
|
|
* ignored.
|
|
* @return
|
|
* -1.
|
|
*/
|
|
CF_PLUGIN int cflogger_runPluginCommand(object *op, char *params) {
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* Handles an object-related event. Doesn't do anything.
|
|
*
|
|
* @param type
|
|
* ignored.
|
|
* @return
|
|
* pointer to an int containing 0.
|
|
*/
|
|
void *eventListener(int *type, ...) {
|
|
static int rv = 0;
|
|
|
|
return &rv;
|
|
}
|
|
|
|
/**
|
|
* Handles a global event.
|
|
*
|
|
* @param type
|
|
* ignored.
|
|
* @return
|
|
* pointer to an int containing 0.
|
|
*/
|
|
CF_PLUGIN void *cflogger_globalEventListener(int *type, ...) {
|
|
va_list args;
|
|
static int rv = 0;
|
|
player *pl;
|
|
object *op;
|
|
int event_code;
|
|
mapstruct *map;
|
|
|
|
va_start(args, type);
|
|
event_code = va_arg(args, int);
|
|
|
|
switch (event_code) {
|
|
case EVENT_BORN:
|
|
case EVENT_PLAYER_DEATH:
|
|
case EVENT_REMOVE:
|
|
case EVENT_MUZZLE:
|
|
case EVENT_KICK:
|
|
op = va_arg(args, object *);
|
|
add_player_event(op, event_code);
|
|
break;
|
|
|
|
case EVENT_LOGIN:
|
|
case EVENT_LOGOUT:
|
|
pl = va_arg(args, player *);
|
|
add_player_event(pl->ob, event_code);
|
|
break;
|
|
|
|
case EVENT_MAPENTER:
|
|
case EVENT_MAPLEAVE:
|
|
op = va_arg(args, object *);
|
|
map = va_arg(args, mapstruct *);
|
|
add_map_event(map, event_code, op);
|
|
break;
|
|
|
|
case EVENT_MAPLOAD:
|
|
case EVENT_MAPUNLOAD:
|
|
case EVENT_MAPRESET:
|
|
map = va_arg(args, mapstruct *);
|
|
add_map_event(map, event_code, NULL);
|
|
break;
|
|
|
|
case EVENT_GKILL: {
|
|
object *killer;
|
|
op = va_arg(args, object *);
|
|
killer = va_arg(args, object *);
|
|
add_death(op, killer);
|
|
}
|
|
break;
|
|
|
|
case EVENT_CLOCK:
|
|
store_time();
|
|
break;
|
|
}
|
|
va_end(args);
|
|
|
|
return &rv;
|
|
}
|
|
|
|
/**
|
|
* Plugin was initialized, now to finish.
|
|
*
|
|
* Registers events, initializes the database.
|
|
*
|
|
* @return
|
|
* 0.
|
|
*/
|
|
CF_PLUGIN int postInitPlugin(void) {
|
|
char path[500];
|
|
const char *dir;
|
|
|
|
cf_log(llevInfo, "%s post init\n", PLUGIN_VERSION);
|
|
|
|
dir = cf_get_directory(4);
|
|
snprintf(path, sizeof(path), "%s/cflogger.db", dir);
|
|
cf_log(llevDebug, " [%s] database file: %s\n", PLUGIN_NAME, path);
|
|
|
|
if (sqlite3_open(path, &database) != SQLITE_OK) {
|
|
cf_log(llevError, " [%s] database error!\n", PLUGIN_NAME);
|
|
sqlite3_close(database);
|
|
database = NULL;
|
|
return 0;
|
|
}
|
|
|
|
check_tables();
|
|
|
|
store_time();
|
|
|
|
cf_system_register_global_event(EVENT_BORN, PLUGIN_NAME, cflogger_globalEventListener);
|
|
cf_system_register_global_event(EVENT_REMOVE, PLUGIN_NAME, cflogger_globalEventListener);
|
|
cf_system_register_global_event(EVENT_GKILL, PLUGIN_NAME, cflogger_globalEventListener);
|
|
cf_system_register_global_event(EVENT_LOGIN, PLUGIN_NAME, cflogger_globalEventListener);
|
|
cf_system_register_global_event(EVENT_LOGOUT, PLUGIN_NAME, cflogger_globalEventListener);
|
|
|
|
cf_system_register_global_event(EVENT_PLAYER_DEATH, PLUGIN_NAME, cflogger_globalEventListener);
|
|
|
|
cf_system_register_global_event(EVENT_MAPENTER, PLUGIN_NAME, cflogger_globalEventListener);
|
|
cf_system_register_global_event(EVENT_MAPLEAVE, PLUGIN_NAME, cflogger_globalEventListener);
|
|
cf_system_register_global_event(EVENT_MAPRESET, PLUGIN_NAME, cflogger_globalEventListener);
|
|
cf_system_register_global_event(EVENT_MAPLOAD, PLUGIN_NAME, cflogger_globalEventListener);
|
|
cf_system_register_global_event(EVENT_MAPUNLOAD, PLUGIN_NAME, cflogger_globalEventListener);
|
|
|
|
cf_system_register_global_event(EVENT_MUZZLE, PLUGIN_NAME, cflogger_globalEventListener);
|
|
cf_system_register_global_event(EVENT_KICK, PLUGIN_NAME, cflogger_globalEventListener);
|
|
|
|
cf_system_register_global_event(EVENT_CLOCK, PLUGIN_NAME, cflogger_globalEventListener);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Close the plugin.
|
|
*
|
|
* Closes the sqlite database.
|
|
*
|
|
* @return
|
|
* 0.
|
|
*/
|
|
CF_PLUGIN int closePlugin(void) {
|
|
cf_log(llevInfo, "%s closing.\n", PLUGIN_VERSION);
|
|
if (database) {
|
|
sqlite3_close(database);
|
|
database = NULL;
|
|
}
|
|
return 0;
|
|
}
|