Online Logger

Online Logger 1.0.3

Нет прав для скачивания
Сообщения
1,176
Реакции
2,144
Помог
57 раз(а)
BlackSignature добавил(а) новый ресурс:

Online Logger - Логирование онлайна игроков, имеющих заданные флаги

Данный плагин позволит вам вести учёт онлайна игроков, имеющих заданные флаги. В начале каждого месяца производится автоматическое создание файла-списка с информацией за прошедший месяц, после чего плагин начинает работу "с чистого листа". При необходимости вы можете досрочно сгенерировать список при помощи специальной консольной команды. Так же имеется чат-команда, при помощи которой игроки могут проверять свой онлайн за текущий месяц.

Пример списка:
* Лог админ-онлайна (ручной...
Узнать больше об этом ресурсе...
 
Сообщения
2,713
Реакции
2,995
Помог
59 раз(а)
Есть некоторые пожелания к улучшению.

1. client_authorized обновился 22.12.15 г.
Считаю, что проверка по типу:
Код:
#if AMXX_VERSION_NUM < 183
public client_authorized(id)
#else
public client_authorized(id, const szAuthID[])
#endif
тут не совсем корректна. Возможно, получим ошибку:
error 025: function heading differs from prototype при компиляции на AMXX 1.8.3 версии раньше git4970

Предлагаю более конкретно указать препроцессору условие, дабы не получать спонтанных "недопониманий" у народа.
Код:
#if !defined client_connectex
public client_authorized(id)
#else
public client_authorized(id, const szAuthID[])
#endif
{
    SetOneBit(g_iAuthPlayers, id);
    if(CheckBit(g_iConnPlayers, id))
    #if !defined client_connectex
        get_user_authid(id, g_szAuthID[id], chx(g_szAuthID[]))
    #else
        copy(g_szAuthID[id], chx(g_szAuthID[]), szAuthID)
    #endif
}

2. Не большие косячки выравнивания тут:
Код:
plugin.sma(501) : warning 217: loose indentation
plugin.sma(504) : warning 217: loose indentation
 
Сообщения
48
Реакции
4
Неверный раздел форума
Обратите внимание, если вы хотите заключить сделку с этим пользователем, он заблокирован
Can you tell me why not compile where is wrong ?
 

Download all Attachments

  • 15.3 KB Просмотры: 35
  • 11.6 KB Просмотры: 35
Сообщения
2,713
Реакции
2,995
Помог
59 раз(а)
franc, change some code to:

Код:
#if AMXX_VERSION_NUM < 183
    #include <colorchat>
    #define MAX_NAME_LENGTH 32
#endif
>>
Код:
#if !defined client_print_color
    #include <colorchat>
#endif

#if !defined MAX_NAME_LENGTH
const MAX_NAME_LENGTH = 32
#endif
And don't use online compilators.
 
Сообщения
48
Реакции
4
Обратите внимание, если вы хотите заключить сделку с этим пользователем, он заблокирован
Again appears same message can you check you if its good ?
 

Вложения

Сообщения
1,176
Реакции
2,144
Помог
57 раз(а)
franc, change some code to:
Код:
#if !defined client_print_color
    #include <colorchat>
#endif
Компилируется? У меня ошибками сыпет на 1.8.2 stable + тот колорчат, который я использовал (на который ведёт ссылка в требованиях). На всех плагинах так. Я потому условие с 183 и выставил.
 
Сообщения
1,176
Реакции
2,144
Помог
57 раз(а)
wopox1337, error 004: function "client_print_color" is not implemented

Компилирую то что сейчас отдаётся через кнопку скачать, колорчат, как уже сказал, отсюда
Компилятор
#define AMXX_VERSION_NUM 182
 
Сообщения
1,176
Реакции
2,144
Помог
57 раз(а)
Обновлен ресурс Online Logger новой записью:

Online Logger 06.04.18

* Теперь записи заносятся в файл начиная с учётки с самым большим онлайном, и далее по убыванию
* Теперь плагин так же заносит в список все активные учётки (с авторизацией по steamid), имеющие нулевой онлайн
* Теперь к имени файла списка будет добавляться текущий год
* Добавлена поддержка MultiLang (хардкод клиентских сообщений убран, серверных, - нет)
* Небольшие правки кода и комментариев, пара незначительных багфиксов
Узнать больше об этом обновлении...
 
Сообщения
213
Реакции
71
Помог
2 раз(а)
Translated Plugin to English, took help from Google Translate.
Lang file is already present in [en], so not updating it but since many things were integrated in plugin too and hence updating it only [English Translated].
Sorry it might be poor translated since I can't speak Russian and its all help taken from Google Translate but corrections and improvements by anyone would be appreciated.

Thanks !!

P.S: Special Thanks to w0w, who made me aware of this plugin.
Код:
// When opening and saving this file, it is necessary to use the encoding 'UTF-8 without BOM'

/* Requirements:
* AMX Mod X version 1.8.1 or later
* NVault module
* colorchat.inc for AMX Mod X to version 1.8.3-dev [https://dev-cs.ru/threads/222/page-2#post-12669]
*/

#define PLUGIN_NAME "Online Logger"
#define PLUGIN_DATE "06.04.18"
#define PLUGIN_AUTHOR "mx?!"

/* ---------------------- SETTINGS ---------------------- */

// MaxPlayers + 1
const MAXSIZE = 33

// Limit the selection of records from the repository when generating the list
const ACCOUNT_LIMIT = 64 // Increase if the corresponding request that appears in the list

// Flag for access to the manual log creation function (later you can change it in amxmodx/configs/cmdaccess.ini)
#define ACCESS_FLAG ADMIN_RCON // ADMIN_RCON == 'l', see amxconst.inc

// If any of these flags is present, the player Will be tracked by the logger
#define LOG_FLAGS (ADMIN_IMMUNITY|ADMIN_RESERVATION|ADMIN_BAN|ADMIN_LEVEL_H)

// If this flag is available (you can specify several, see above), the player Will NOT be tracked by the logger
//#define IGNORE_FLAGS ADMIN_CFG // Comment out to disable.

// Say command mode
// 0 or less - Disable the command
// 1 - Flag FLAG_TO_CHECK does not matter
// 2 - Block the use of players who DO NOT have the FLAG_TO_CHECK flag
// 3 or more - Block the use of players who have the flag FLAG_TO_CHECK
// ATTENTION! Players that are not tracked by the logger, in any case, do not have access to the say-command
#define SAYCMD_MODE 1

// Flag for SAYCMD_MODE (if value <2, it does not play the last role)
// By analogy with LOG_FLAGS, you can specify several flags.
// If the player detects any of them, the tolerance / clipping will work (depending on the value of SAYCMD_MODE)
#define FLAG_TO_CHECK ADMIN_IMMUNITY

// Register the say command in various variations: say, say_team, '/', and '.'
// When SAYCMD_MODE 0 does not play a role
#define EXTENDED_SAYCMD 1 // (less than 1 - disable)

// Template name (without extension) for created admin-online logs
new const LOG_PREFIX[] = "" // // with this option, the name will be %YEAR%_% MONTH_NUMBER%.% LOG_EXT%
//new const LOG_PREFIX[] = "month_" // month_%YEAR%_%MONTH_ROOM%.% LOG_EXT%

// Postfix added after the month number when generating the list in manual mode
new const MANUAL_POSTFIX[] = "_manual" // %YEAR%_%MONTH_ROOM%_manual.%LOG_EXT%

// Extension for admin online logs
// When FASTDL> 0: Theoretically, FASTDL may not allow you to download specific files
// extensions from specific directories. Experiment with combinations, choose for yourself.
new const LOG_EXT[] = ".txt"
//new const LOG_EXT[] = ".dat" //Powered by MultiPlay with #define LOGS_DIR "online_logs"
//new const LOG_EXT[] = ".bsp" // Ps guy do you want to trick FASTDL?

// Path + name of the folder where admin logs will be placed
// The folder is created automatically at the time of creating the log, however, I strongly
// I recommend to create it manually, and immediately set the necessary access flags
new const LOGS_DIR[] = "online_logs" // At the root of fashion (cstrike)
//new const LOGS_DIR[] = "addons/amxmodx/logs/online_logs"
//new const LOGS_DIR[] = "maps/online_logs"

// Store Name (addons/amxmodx/data/vault/%NAME%.vault)
new const VAULT_NAME[] = "online_logs"

// Interval (in minutes) of safety preservation
// It will be useful to owners of often "falling" servers on which the same map is played for a long time
// Without special need, it is not recommended to use
#define SAVE_INTERVAL 0 // (less than 1 - disable)

// Name of the file (with extension) storing the current month ordinal (addons/amxmodx/data/%NAME%)
#define DATE_FILE_NAME "ol_date.txt"

// Plugin prefix for console messages and error messages (log file)
new const g_szPluginPrefix[] = "[OL]"

// Name of the log file (with extension) for recording errors (addons/amxmodx/logs/%NAME%)
#define ERROR_LOG_NAME "ERROR-LOG.log"

/* --- LISTENESS RESULTS VIA FASTDL --- */

// Issue a link to download the list when it is manually generated
// Requirements: FASTDL of your server should work in "mirror mode". What I mean:
// For example, on MultiPlay hosting you just need to upload a new map to the maps folder on the server, and after
// of this, it automatically becomes available through FASTDL. If on your hosting you need
// upload resources to FASTDL yourself, then the download link will not work for you
// Where exactly does the list feed through FASTDL work: MultiPlay
// Where it definitely doesn't work: Ignore host (but you can try to give out a list as a game resource, like .bsp)
// ATTENTION, IMPORTANT!
// Most likely, the settings of the FASTDL server will not allow downloading anything from addons / ..., therefore it is recommended
// place LOGS_DIR in the root of the mod (i.e., in cstrike), for example like this: #define LOGS_DIR "online_logs"
#define FASTDL 0 // (less than 1 - disable)

// Link to the root of the FASTDL server
// When FASTDL <1 does not play a role
#define FASTDL_LINK "http://www.localhost.ru" // No slash ('/') is necessary at the end!

/* --- SOUND SETTINGS --- */

// If you are going to use non-standard sounds, then do not forget that
// that they must be placed in prekesh. To do this, set USE_PRECACHE 1 and
// uncomment (delete '//') next to the sound you need in plugin_precache ().
// You can find this function at the very bottom of this file.
#define USE_PRECACHE 0 // (less than 1, - disable)

new const NOTICE_SOUND[] = "buttons/blip2"
new const ERROR_SOUND[] = "buttons/button2"

/* ---------------------- SETTINGS ---------------------- */

#include <amxmodx>
#include <nvault>

#if AMXX_VERSION_NUM < 183
    #include <colorchat>
    #define MAX_NAME_LENGTH 32
#endif

#define CheckBit(%0,%1) (%0 & (1 << %1))
#define SetOneBit(%0,%1) (%0 |= (1 << %1))
#define ClearBit(%0,%1) (%0 &= ~(1 << %1))
#define chx charsmax

#define FILE_READ false
#define FILE_WRITE true
#define JUST_PRINT false
#define SAVE_DATA true
#define MANUAL_GEN false
#define AUTO_GEN true

enum { GET_MONTHDAY, GET_YEARDAY, GET_YEAR }

#define TASKID_SAVE_VAULT 69135

enum NAME_STRUCT { FIRST_NAME, LAST_NAME }
enum DAYS_STRUCT { UNIQUE_DAYS, FIRST_DAY, LAST_DAY }
enum { ERR_DATE_FILE, ERR_VAULT, ERR_FOLDER, ERR_LIST }

const g_iLogFlags = LOG_FLAGS
stock const g_iIgnoreFlags = IGNORE_FLAGS
stock const g_iFlagToCheck = FLAG_TO_CHECK

// Do not change without urgent need and without understanding the consequences
const NAME_LENGTH = MAX_NAME_LENGTH
const STRING_SIZE = 128
const TIME_STRLEN = 9
const YEAR_STRLEN = 5
const DAY_STRLEN = 3
const AUTHID_STRLEN = 24
new const DATA_DIR[] = "amxx_datadir"

new g_iTime[MAXSIZE], g_szSmBuff[NAME_LENGTH], g_szBigBuff[NAME_LENGTH * 2], g_szString[STRING_SIZE],
    g_szAuthID[MAXSIZE][AUTHID_STRLEN], g_szDateBuff[YEAR_STRLEN], g_szDays[DAYS_STRUCT][DAY_STRLEN], g_szTime[TIME_STRLEN]

new g_iVault, g_iConnPlayers, g_iAuthPlayers, g_iCurMonth, g_szErrorLog[NAME_LENGTH * 4], g_szNames[NAME_STRUCT][NAME_LENGTH]

/* -------------------- */

public plugin_init()
{
    register_plugin(PLUGIN_NAME, PLUGIN_DATE, PLUGIN_AUTHOR)

    register_dictionary("online_logger.txt")

    register_concmd("amx_make_log", "func_MakeLog", ACCESS_FLAG)

#if SAYCMD_MODE > 0
    #if EXTENDED_SAYCMD > 0
        register_saycmd("myonline", "func_CheckOnline")
    #else
        register_clcmd("say /myonline", "func_CheckOnline")
    #endif
#endif

    get_localinfo(DATA_DIR, g_szBigBuff, chx(g_szBigBuff))
    formatex(g_szString, chx(g_szString), "%s/%s", g_szBigBuff, DATE_FILE_NAME)

    get_time("%m", g_szDateBuff, chx(g_szDateBuff))
    g_iCurMonth = str_to_num(g_szDateBuff)

    new iFile = func_OpenDateFile(FILE_READ)

    if(iFile < 1)
    {
        if(iFile != INVALID_HANDLE)
            func_WriteDateFile()
    }
    else
    {
        fgets(iFile, g_szDateBuff, chx(g_szDateBuff))
        fclose(iFile)

        new iPrevMonth = str_to_num(g_szDateBuff)

        if(g_iCurMonth != iPrevMonth && func_WriteDateFile() > 0)
        {
            formatex(g_szString, chx(g_szString), "%s/vault/%s_%d.vault", g_szBigBuff, VAULT_NAME, iPrevMonth)

            if(file_exists(g_szString))
                func_GenerateLog(AUTO_GEN, iPrevMonth)
        }
    }

    func_OpenVault()

#if SAVE_INTERVAL > 0
    set_task(SAVE_INTERVAL * 60.0, "task_SaveVault", TASKID_SAVE_VAULT)
#endif
}

/* -------------------- */

#if SAVE_INTERVAL > 0
public task_SaveVault()
{
    nvault_close(g_iVault)
    func_OpenVault()
}
#endif

/* -------------------- */

func_OpenDateFile(bool:bMode)
{
    new iFile = fopen(g_szString, (bMode == FILE_READ) ? "r" : "w")
    if(!iFile && (bMode == FILE_WRITE || file_exists(g_szString)))
    {
        func_ErrorHandler(ERR_DATE_FILE, bMode)
        iFile = INVALID_HANDLE
    }
    return iFile
}

/* -------------------- */

func_WriteDateFile()
{
    new iFile = func_OpenDateFile(FILE_WRITE)
    if(iFile > 0)
    {
        fprintf(iFile, "%d", g_iCurMonth)
        fclose(iFile)
    }
    return iFile
}

/* -------------------- */

#if SAYCMD_MODE > 0
public func_CheckOnline(id)
{
    if(!CheckBit(g_iAuthPlayers, id))
        return PLUGIN_HANDLED

    new iFlags = get_user_flags(id)

#if defined IGNORE_FLAGS
    if((~iFlags & g_iLogFlags) || (iFlags & g_iIgnoreFlags))
#else
    if(~iFlags & g_iLogFlags)
#endif
    {
        client_cmd(id, "spk %s", ERROR_SOUND)
        client_print_color(id, print_team_red, "^1%L", id, "OL_NO_LOGGING_MSG")
        return PLUGIN_HANDLED
    }

#if SAYCMD_MODE > 1
    #if SAYCMD_MODE == 2
        if(~iFlags & g_iFlagToCheck)
    #endif
    #if SAYCMD_MODE > 2
        if(iFlags & g_iFlagToCheck)
    #endif
        {
            client_cmd(id, "spk %s", ERROR_SOUND)
            client_print_color(id, print_team_red, "^1%L", id, "OL_NO_ACCESS_MSG")
            return PLUGIN_HANDLED
        }
#endif

    if(!g_iTime[id])
        return func_LoadData(id, JUST_PRINT)

    return func_PrintTime(id)
}
#endif

/* -------------------- */

func_PrintTime(id)
{
    client_cmd(id, "spk %s", NOTICE_SOUND)
    client_print_color(id, print_team_default, "^1%L", id, "OL_ONLINE_MSG", get_time_length(id, max(0, g_iTime[id] + get_user_time(id, 1))))
    return PLUGIN_HANDLED
}

/* -------------------- */

func_LoadData(id, bool:bMode)
{
    new iTimeStamp
    if(nvault_lookup(g_iVault, g_szAuthID[id], g_szString, chx(g_szString), iTimeStamp))
        return func_ParseData(id, bMode)
    //else ->
    if(bMode == JUST_PRINT)
    {
        g_iTime[id] = -1
        return func_PrintTime(id)
    }
    //else ->
    new iMonthDay = func_GetDate(GET_MONTHDAY)
    get_user_name(id, g_szNames[FIRST_NAME], chx(g_szNames[]))
    copy(g_szNames[LAST_NAME], chx(g_szNames[]), g_szNames[FIRST_NAME])
    func_SaveData(id, func_GetDate(GET_YEARDAY), 1, iMonthDay, iMonthDay, 0)
    return PLUGIN_HANDLED
}

/* -------------------- */

func_ParseData(id, bool:bMode)
{
    parse(g_szString, g_szDateBuff, chx(g_szDateBuff), g_szDays[UNIQUE_DAYS],
        chx(g_szDays[]), g_szDays[FIRST_DAY], chx(g_szDays[]), g_szDays[LAST_DAY],
        chx(g_szDays[]), g_szTime, chx(g_szTime), g_szNames[FIRST_NAME], chx(g_szNames[])
    )

    new iYearDay = str_to_num(g_szDateBuff)
    new iActualYearDay = func_GetDate(GET_YEARDAY)
    new iUniqueDays = str_to_num(g_szDays[UNIQUE_DAYS])

    if(iYearDay != iActualYearDay)
    {
        iUniqueDays++
        iYearDay = iActualYearDay
    }

    if(bMode == JUST_PRINT)
    {
        g_iTime[id] = str_to_num(g_szTime)
        return func_PrintTime(id)
    }
    //else ->
    get_user_name(id, g_szNames[LAST_NAME], chx(g_szNames[]))
    return func_SaveData(id, iYearDay, iUniqueDays, str_to_num(g_szDays[FIRST_DAY]), func_GetDate(GET_MONTHDAY), str_to_num(g_szTime))
}

/* -------------------- */

func_SaveData(id, iYearDay, iUniqueDays, iFirstDay, iCurrentDay, iTime)
{
    get_flags(get_user_flags(id), g_szSmBuff, chx(g_szSmBuff))

    formatex(g_szString, chx(g_szString),
        "%d %d %d %d %d ^"%s^" ^"%s^" %s",
        iYearDay, iUniqueDays, iFirstDay, iCurrentDay,
        iTime + get_user_time(id, 1), g_szNames[FIRST_NAME], g_szNames[LAST_NAME], g_szSmBuff
    )

    nvault_set(g_iVault, g_szAuthID[id], g_szString)
    return PLUGIN_HANDLED
}

/* -------------------- */

func_OpenVault()
{
    formatex(g_szSmBuff, chx(g_szSmBuff), "%s_%d", VAULT_NAME, g_iCurMonth)
    if((g_iVault = nvault_open(g_szSmBuff)) == INVALID_HANDLE)
        set_fail_state("Error opening vault!")
}

/* -------------------- */

#if AMXX_VERSION_NUM < 183
public client_disconnect(id)
#else
public client_disconnected(id)
#endif
{
    if(CheckBit(g_iConnPlayers, id) && CheckBit(g_iAuthPlayers, id))
    {
        g_iTime[id] = 0
    #if defined IGNORE_FLAGS
        new iFlags = get_user_flags(id)
        if((iFlags & g_iLogFlags) && (~iFlags & g_iIgnoreFlags) && (g_szAuthID[id][0] == 'S' || g_szAuthID[id][0] == 'V'))
    #else
        if((get_user_flags(id) & g_iLogFlags) && (g_szAuthID[id][0] == 'S' || g_szAuthID[id][0] == 'V'))
    #endif
            func_LoadData(id, SAVE_DATA)
    }
    ClearBit(g_iConnPlayers, id);
    ClearBit(g_iAuthPlayers, id);
}

/* -------------------- */

func_GetDate(iMode)
{
    new szDateType[][] = { "%d", "%j", "%Y" }
    get_time(szDateType[iMode], g_szDateBuff, chx(g_szDateBuff))
    return str_to_num(g_szDateBuff)
}

/****************************************************************************************
*****************************************************************************************
************************************ Log Generation *************************************
*****************************************************************************************
****************************************************************************************/

public func_MakeLog(id, iAccess)
{
    if(id)
    {
        if(~get_user_flags(id) & iAccess) return PLUGIN_HANDLED

        static Float:fGenTime; new Float:fGameTime = get_gametime()
        if(fGameTime - fGenTime < 10.0)
        {
            client_cmd(id, "spk %s", ERROR_SOUND)
            console_print(id, "%s %L", g_szPluginPrefix, id, "OL_NO_SPAM")
            return PLUGIN_HANDLED
        }
        //else ->
        fGenTime = fGameTime
    }

    if(id)
        console_print(id, "%s %L", g_szPluginPrefix, id, "OL_START_GEN")
    else
        server_print("%s Vault blocked, generating has started...", g_szPluginPrefix)

    nvault_close(g_iVault)

    get_localinfo(DATA_DIR, g_szBigBuff, chx(g_szBigBuff))
    formatex(g_szString, chx(g_szString), "%s/vault/%s_%d.vault", g_szBigBuff, VAULT_NAME, g_iCurMonth)

    if(!func_GenerateLog(MANUAL_GEN, g_iCurMonth))
    {
        if(id)
        {
            console_print(id, "%s %L", g_szPluginPrefix, id, "OL_CRIT_ERROR")
            client_cmd(id, "spk %s", ERROR_SOUND)
        }
        else
            server_print("%s Critical error, see plugin error log!", g_szPluginPrefix)

        func_OpenVault()
        return PLUGIN_HANDLED
    }
    //else ->
#if FASTDL > 0
    new szLink[STRING_SIZE * 2]
    formatex(szLink, chx(szLink), "%s/%s/%s%d_%s%d%s%s", FASTDL_LINK, LOGS_DIR, LOG_PREFIX, func_GetDate(GET_YEAR), g_iCurMonth > 9 ? "" : "0", g_iCurMonth, MANUAL_POSTFIX, LOG_EXT)

    if(id)
    {
        console_print(id, "%s %L", g_szPluginPrefix, id, "OL_GEN_DONE_WITH_LINK", szLink)
        client_cmd(id, "spk %s", NOTICE_SOUND)
    }
    else
        server_print("%s Done, link: %s", g_szPluginPrefix, szLink)
#else
    if(id)
    {
        console_print(id, "%s %L", g_szPluginPrefix, id, "OL_GEN_DONE_WO_LINK")
        client_cmd(id, "spk %s", NOTICE_SOUND)
    }
    else
        server_print("%s Done. Link not defined (ask admin for file)", g_szPluginPrefix)
#endif

    return PLUGIN_HANDLED
}

/* -------------------- */

#define MAX_KEY_LEN AUTHID_STRLEN    // Max 255
#define MAX_VAL_LEN STRING_SIZE        // Max 65535
#define DATA_BUFFER STRING_SIZE        // Make this the greater of (MAX_KEY_LEN / 4) or (MAX_VAL_LEN / 4)

enum _:DATA_STRUCT
{
    AUTHID[AUTHID_STRLEN + 1],
    FLAGS[NAME_LENGTH],
    F_NAME[NAME_LENGTH],
    L_NAME[NAME_LENGTH],
    F_DAY[DAY_STRLEN],
    L_DAY[DAY_STRLEN],
    U_DAYS[DAY_STRLEN],
    TIME
}

enum _:POINTER_STRUCT { PTR_ID, PTR_TIME }

bool:func_GenerateLog(bool:bMode, iMonth)
{
    new iVaultFile = fopen(g_szString, "rb")

    if(!iVaultFile)
        return func_ErrorHandler(ERR_VAULT, FILE_READ)

    new szString[STRING_SIZE]

    formatex(szString, chx(szString), "%s/%s%d_%s%d%s%s", LOGS_DIR, LOG_PREFIX, func_GetDate(GET_YEAR), iMonth > 9 ? "" : "0", iMonth, (bMode == AUTO_GEN) ? "" : MANUAL_POSTFIX, LOG_EXT)

    if(!dir_exists(LOGS_DIR) && mkdir(LOGS_DIR))
        return func_ErrorHandler(ERR_FOLDER, FILE_WRITE)

    new iFile = fopen(szString, "w")

    if(!iFile)
    {
        fclose(iVaultFile)
        return func_ErrorHandler(ERR_LIST, FILE_WRITE)
    }

    new iVaultEntries, iKeyLen, iValLen, szVal[MAX_VAL_LEN + 1];

    new
        Trie:tTrie = TrieCreate(),
            Array:aArray = ArrayCreate(DATA_STRUCT),
                eData[DATA_STRUCT],
                    iPtrData[ACCOUNT_LIMIT][POINTER_STRUCT];

    fread_raw(iVaultFile, szString, 1, BLOCK_INT)
    fread_raw(iVaultFile, szString, 1, BLOCK_SHORT)
    fread_raw(iVaultFile, szString, 1, BLOCK_INT)
    iVaultEntries = szString[0]

    get_time("%Y (Generated %d/%m - %H:%M:%S)", szString, chx(szString))
    fprintf(iFile, "* Online log (%s mode) per %s%d/%s", (bMode == AUTO_GEN) ? "auto" : "manual", iMonth > 9 ? "" : "0", iMonth, szString)
    fprintf(iFile, "^n * Number of players in the list: %d (Limit: %d)^n", iVaultEntries, ACCOUNT_LIMIT)

    if(iVaultEntries > ACCOUNT_LIMIT)
    {
        fputs(iFile, "^nATTENTION! Record fetch limit has been reached.^nIncrease the value of the ACCOUNT_LIMIT variable in the plugin source.^n")
        iVaultEntries = ACCOUNT_LIMIT
    }

    for(new i = 1, a; i <= iVaultEntries; i++)
    {
        // TimeStamp
        fread_raw(iVaultFile, szString, 1, BLOCK_INT)

        // Key Length
        fread_raw(iVaultFile, szString, 1, BLOCK_BYTE)
        iKeyLen = szString[0] & 0xFF

        // Val Length
        fread_raw(iVaultFile, szString, 1, BLOCK_SHORT)
        iValLen = szString[0] & 0xFFFF

        // Key Data
        fread_raw(iVaultFile, szString, iKeyLen, BLOCK_CHAR)
        func_ReadString(eData[AUTHID], iKeyLen, chx(eData[AUTHID]), szString)

        // Val Data
        fread_raw(iVaultFile, szString, iValLen, BLOCK_CHAR)
        func_ReadString(szVal, iValLen, chx(szVal), szString)

        parse(szVal,
            g_szDateBuff,         chx(g_szDateBuff), // not used here
            eData[U_DAYS],         chx(eData[U_DAYS]),
            eData[F_DAY],         chx(eData[F_DAY]),
            eData[L_DAY],         chx(eData[L_DAY]),
            g_szTime,             chx(g_szTime),
            eData[F_NAME],         chx(eData[F_NAME]),
            eData[L_NAME],     chx(eData[L_NAME]),
            eData[FLAGS],         chx(eData[FLAGS])
        );

        iPtrData[a][PTR_TIME] = eData[TIME] = str_to_num(g_szTime)
        iPtrData[a][PTR_ID] = a++

        ArrayPushArray(aArray, eData)
        TrieSetCell(tTrie, eData[AUTHID], 0)
    }

    fclose(iVaultFile)

    SortCustom2D(iPtrData, sizeof(iPtrData), "func_SortTime")

    new const szDivider[] = "^n==================================================================================="

    for(new i; i < iVaultEntries; i++)
    {
        ArrayGetArray(aArray, iPtrData[i][PTR_ID], eData)

        fputs(iFile, szDivider)

        fprintf(iFile,
            "^n[#%d] %s --- Flags ---> %s^n\
            First/Last nickname seen:: ' %s ' <---------> ' %s '^n\
            First/Last Day Visit: %s/%s^n\
            Daily Visits (+1 times per day on first visit): %s^n\
            Total Online: %s",
            i + 1, eData[AUTHID], eData[FLAGS], eData[F_NAME], eData[L_NAME],
            eData[F_DAY], eData[L_DAY], eData[U_DAYS], get_time_length(0, eData[TIME])
        )
    }

    fputs(iFile, szDivider)

    fputs(iFile, "^nList of entries with SteamID authorization type that have zero online:")

    for(new i, a, szAuthID[AUTHID_STRLEN], iCount = admins_num(); i < iCount; i++)
    {
        if(~admins_lookup(i, AdminProp_Flags) & FLAG_AUTHID) continue

        admins_lookup(i, AdminProp_Auth, szAuthID, chx(szAuthID))

        if(TrieKeyExists(tTrie, szAuthID)) continue

        get_flags(admins_lookup(i, AdminProp_Access), g_szSmBuff, chx(g_szSmBuff))

        fprintf(iFile, "^n[#%d] %s --- Flags ---> %s", ++a, szAuthID, g_szSmBuff)
    }

    fputs(iFile, szDivider)

    fclose(iFile)
    TrieDestroy(tTrie)
    ArrayDestroy(aArray)

    if(bMode == AUTO_GEN)
        delete_file(g_szString)
    else
        func_OpenVault()

    return true
}

/* -------------------- */

public func_SortTime(eElement1[], eElement2[])
{
    if(eElement1[PTR_TIME] < eElement2[PTR_TIME])
        return 1

    if(eElement1[PTR_TIME] > eElement2[PTR_TIME])
        return -1

    return 0
}

/* -------------------- */

func_ReadString(szDestString[], iLen, iMaxLen, SourceData[])
{
    new iStrPos = -1, iRawPos

    while((++iStrPos < iLen) && (iStrPos < iMaxLen) && (iRawPos < DATA_BUFFER))
    {
        szDestString[iStrPos] = (SourceData[iRawPos] >> ((iStrPos % 4) * 8)) & 0xFF

        if(iStrPos && ((iStrPos % 4) == 3))
            iRawPos++
    }

    szDestString[iStrPos] = EOS
}

/* -------------------- */

#if !defined client_connectex
public client_authorized(id)
#else
public client_authorized(id, const szAuthID[])
#endif
{
    SetOneBit(g_iAuthPlayers, id);
    if(CheckBit(g_iConnPlayers, id))
    #if !defined client_connectex
        get_user_authid(id, g_szAuthID[id], chx(g_szAuthID[]))
    #else
        copy(g_szAuthID[id], chx(g_szAuthID[]), szAuthID)
    #endif
}

/* -------------------- */

public client_putinserver(id)
{
    SetOneBit(g_iConnPlayers, id);
    if(CheckBit(g_iAuthPlayers, id))
        get_user_authid(id, g_szAuthID[id], chx(g_szAuthID[]))
}

/* -------------------- */

stock register_saycmd(szSayCmd[], szFunc[]) // Batch register say commands
{
    new const szPrefix[][] = { "say /", "say_team /", "say .", "say_team ." }

    for(new i; i < sizeof(szPrefix); i++)
    {
        formatex(g_szSmBuff, chx(g_szSmBuff), "%s%s", szPrefix[i], szSayCmd)
        register_clcmd(g_szSmBuff, szFunc)
    }
}

/* -------------------- */

// Defines from 'time.inc' as constants
const SECONDS_IN_WEEK = 604800
const SECONDS_IN_DAY = 86400
const SECONDS_IN_HOUR = 3600
const SECONDS_IN_MINUTE = 60

enum _:TIME_TYPES { TYPE_WEEK, TYPE_DAY, TYPE_HOUR, TYPE_MIN, TYPE_SEC }
const TIME_ELEMENT_STRLEN = 12

get_time_length(id, iSec)
{
    new iCount, iType[TIME_TYPES], szElement[TIME_TYPES][TIME_ELEMENT_STRLEN]

    iType[TYPE_WEEK] = iSec / SECONDS_IN_WEEK
    iSec -= (iType[TYPE_WEEK] * SECONDS_IN_WEEK)

    iType[TYPE_DAY] = iSec / SECONDS_IN_DAY
    iSec -= (iType[TYPE_DAY] * SECONDS_IN_DAY)

    iType[TYPE_HOUR] = iSec / SECONDS_IN_HOUR
    iSec -= (iType[TYPE_HOUR] * SECONDS_IN_HOUR)

    iType[TYPE_MIN] = iSec / SECONDS_IN_MINUTE
    iType[TYPE_SEC] = iSec -= (iType[TYPE_MIN] * SECONDS_IN_MINUTE)

    new const szLang[][] = { "OL_TIME_WEEK", "OL_TIME_DAY", "OL_TIME_HOUR", "OL_TIME_MIN", "OL_TIME_SEC" }

    for(new i; i < sizeof(iType); i++)
        if(iType[i] > 0)
            formatex(szElement[iCount++], chx(szElement[]), "%d %L", iType[i], id, szLang[i])

    new const szAndChar[] = "OL_TIME_AND"

    switch(iCount)
    {
        case 0: formatex(g_szBigBuff, chx(g_szBigBuff), "0 %L", id, szLang[TYPE_SEC])
        case 1: copy(g_szBigBuff, chx(g_szBigBuff), szElement[0])
        case 2: formatex(g_szBigBuff, chx(g_szBigBuff), "%s %L %s", szElement[0], id, szAndChar, szElement[1])
        case 3: formatex(g_szBigBuff, chx(g_szBigBuff), "%s, %s, %L %s", szElement[0], szElement[1], id, szAndChar, szElement[2])
        case 4: formatex(g_szBigBuff, chx(g_szBigBuff), "%s, %s, %s, %L %s", szElement[0], szElement[1], szElement[2], id, szAndChar, szElement[3])
        case 5: formatex(g_szBigBuff, chx(g_szBigBuff), "%s, %s, %s, %s, %L %s", szElement[0], szElement[1], szElement[2], szElement[3], id, szAndChar, szElement[4])
    }

    return g_szBigBuff
}

/* -------------------- */

bool:func_ErrorHandler(iMode, bool:bAccessMode)
{
    static bool:bErrorPath

    if(!bErrorPath)
    {
        bErrorPath = true
        get_localinfo("amxx_logs", g_szErrorLog, chx(g_szErrorLog))
        format(g_szErrorLog, chx(g_szErrorLog), "%s/%s", g_szErrorLog, ERROR_LOG_NAME)
    }

    static const szMode[][] = { "Data file", "Vault", "Folder", "List" }
    log_to_file(g_szErrorLog, "%s %s: %s error (wrong chmod?)",
        g_szPluginPrefix, szMode[iMode], (bAccessMode == FILE_READ) ? "read" : "write")
    return false
}

/* -------------------- */

public plugin_end()
    nvault_close(g_iVault)

/* -------------------- */

#if USE_PRECACHE > 0
public plugin_precache() {
    precache_sound(NOTICE_SOUND)
    precache_sound(ERROR_SOUND)
}
#endif
 
Последнее редактирование модератором:
Сообщения
213
Реакции
71
Помог
2 раз(а)
amx_make_log

Код:
[PSL-OL] Vault blocked, generating has started...
[PSL-OL] Done. Link not defined (ask admin for file)
Where will I find log file?

Код:
// When opening and saving this file, it is necessary to use the encoding 'UTF-8 without BOM'

/* Requirements:
* AMX Mod X version 1.8.1 or later
* NVault module
* colorchat.inc for AMX Mod X to version 1.8.3-dev [https://dev-cs.ru/threads/222/page-2#post-12669]
*/

#define PLUGIN_NAME "Online Logger"
#define PLUGIN_DATE "06.04.18"
#define PLUGIN_AUTHOR "mx?!"

/* ---------------------- SETTINGS ---------------------- */

// MaxPlayers + 1
const MAXSIZE = 33

// Limit the selection of records from the repository when generating the list
const ACCOUNT_LIMIT = 150 // Increase if the corresponding request that appears in the list

// Flag for access to the manual log creation function (later you can change it in amxmodx/configs/cmdaccess.ini)
#define ACCESS_FLAG ADMIN_RCON // ADMIN_RCON == 'l', see amxconst.inc

// If any of these flags is present, the player Will be tracked by the logger
#define LOG_FLAGS (ADMIN_IMMUNITY|ADMIN_RESERVATION|ADMIN_BAN|ADMIN_LEVEL_H)

// If this flag is available (you can specify several, see above), the player Will NOT be tracked by the logger
//#define IGNORE_FLAGS ADMIN_CFG // Comment out to disable.

// Say command mode
// 0 or less - Disable the command
// 1 - Flag FLAG_TO_CHECK does not matter
// 2 - Block the use of players who DO NOT have the FLAG_TO_CHECK flag
// 3 or more - Block the use of players who have the flag FLAG_TO_CHECK
// ATTENTION! Players that are not tracked by the logger, in any case, do not have access to the say-command
#define SAYCMD_MODE 1

// Flag for SAYCMD_MODE (if value <2, it does not play the last role)
// By analogy with LOG_FLAGS, you can specify several flags.
// If the player detects any of them, the tolerance / clipping will work (depending on the value of SAYCMD_MODE)
#define FLAG_TO_CHECK ADMIN_IMMUNITY

// Register the say command in various variations: say, say_team, '/', and '.'
// When SAYCMD_MODE 0 does not play a role
#define EXTENDED_SAYCMD 1 // (less than 1 - disable)

// Template name (without extension) for created admin-online logs
new const LOG_PREFIX[] = "month_" // // with this option, the name will be %YEAR%_% MONTH_NUMBER%.% LOG_EXT%
//new const LOG_PREFIX[] = "month_" // month_%YEAR%_%MONTH_ROOM%.% LOG_EXT%

// Postfix added after the month number when generating the list in manual mode
new const MANUAL_POSTFIX[] = "_manual" // %YEAR%_%MONTH_ROOM%_manual.%LOG_EXT%

// Extension for admin online logs
// When FASTDL> 0: Theoretically, FASTDL may not allow you to download specific files
// extensions from specific directories. Experiment with combinations, choose for yourself.
new const LOG_EXT[] = ".txt"
//new const LOG_EXT[] = ".dat" //Powered by MultiPlay with #define LOGS_DIR "online_logs"
//new const LOG_EXT[] = ".bsp" // Ps guy do you want to trick FASTDL?

// Path + name of the folder where admin logs will be placed
// The folder is created automatically at the time of creating the log, however, I strongly
// I recommend to create it manually, and immediately set the necessary access flags
new const LOGS_DIR[] = "online_logs" // At the root of fashion (cstrike)
//new const LOGS_DIR[] = "addons/amxmodx/logs/online_logs"
//new const LOGS_DIR[] = "maps/online_logs"

// Store Name (addons/amxmodx/data/vault/%NAME%.vault)
new const VAULT_NAME[] = "online_logs"

// Interval (in minutes) of safety preservation
// It will be useful to owners of often "falling" servers on which the same map is played for a long time
// Without special need, it is not recommended to use
#define SAVE_INTERVAL 0 // (less than 1 - disable)

// Name of the file (with extension) storing the current month ordinal (addons/amxmodx/data/%NAME%)
#define DATE_FILE_NAME "ol_date.txt"

// Plugin prefix for console messages and error messages (log file)
new const g_szPluginPrefix[] = "[PSL-OL]"

// Name of the log file (with extension) for recording errors (addons/amxmodx/logs/%NAME%)
#define ERROR_LOG_NAME "ERROR-LOG.log"

/* --- LISTENESS RESULTS VIA FASTDL --- */

// Issue a link to download the list when it is manually generated
// Requirements: FASTDL of your server should work in "mirror mode". What I mean:
// For example, on MultiPlay hosting you just need to upload a new map to the maps folder on the server, and after
// of this, it automatically becomes available through FASTDL. If on your hosting you need
// upload resources to FASTDL yourself, then the download link will not work for you
// Where exactly does the list feed through FASTDL work: MultiPlay
// Where it definitely doesn't work: Ignore host (but you can try to give out a list as a game resource, like .bsp)
// ATTENTION, IMPORTANT!
// Most likely, the settings of the FASTDL server will not allow downloading anything from addons / ..., therefore it is recommended
// place LOGS_DIR in the root of the mod (i.e., in cstrike), for example like this: #define LOGS_DIR "online_logs"
#define FASTDL 0 // (less than 1 - disable)

// Link to the root of the FASTDL server
// When FASTDL <1 does not play a role
#define FASTDL_LINK "http://www.localhost.ru" // No slash ('/') is necessary at the end!

/* --- SOUND SETTINGS --- */

// If you are going to use non-standard sounds, then do not forget that
// that they must be placed in prekesh. To do this, set USE_PRECACHE 1 and
// uncomment (delete '//') next to the sound you need in plugin_precache ().
// You can find this function at the very bottom of this file.
#define USE_PRECACHE 0 // (less than 1, - disable)

new const NOTICE_SOUND[] = "buttons/blip2"
new const ERROR_SOUND[] = "buttons/button2"

/* ---------------------- SETTINGS ---------------------- */

#include <amxmodx>
#include <nvault>

#if AMXX_VERSION_NUM < 183
    #include <colorchat>
    #define MAX_NAME_LENGTH 32
#endif

#define CheckBit(%0,%1) (%0 & (1 << %1))
#define SetOneBit(%0,%1) (%0 |= (1 << %1))
#define ClearBit(%0,%1) (%0 &= ~(1 << %1))
#define chx charsmax

#define FILE_READ false
#define FILE_WRITE true
#define JUST_PRINT false
#define SAVE_DATA true
#define MANUAL_GEN false
#define AUTO_GEN true

enum { GET_MONTHDAY, GET_YEARDAY, GET_YEAR }

#define TASKID_SAVE_VAULT 69135

enum NAME_STRUCT { FIRST_NAME, LAST_NAME }
enum DAYS_STRUCT { UNIQUE_DAYS, FIRST_DAY, LAST_DAY }
enum { ERR_DATE_FILE, ERR_VAULT, ERR_FOLDER, ERR_LIST }

const g_iLogFlags = LOG_FLAGS
stock const g_iIgnoreFlags = IGNORE_FLAGS
stock const g_iFlagToCheck = FLAG_TO_CHECK

// Do not change without urgent need and without understanding the consequences
const NAME_LENGTH = MAX_NAME_LENGTH
const STRING_SIZE = 128
const TIME_STRLEN = 9
const YEAR_STRLEN = 5
const DAY_STRLEN = 3
const AUTHID_STRLEN = 24
new const DATA_DIR[] = "amxx_datadir"

new g_iTime[MAXSIZE], g_szSmBuff[NAME_LENGTH], g_szBigBuff[NAME_LENGTH * 2], g_szString[STRING_SIZE],
    g_szAuthID[MAXSIZE][AUTHID_STRLEN], g_szDateBuff[YEAR_STRLEN], g_szDays[DAYS_STRUCT][DAY_STRLEN], g_szTime[TIME_STRLEN]

new g_iVault, g_iConnPlayers, g_iAuthPlayers, g_iCurMonth, g_szErrorLog[NAME_LENGTH * 4], g_szNames[NAME_STRUCT][NAME_LENGTH]

/* -------------------- */

public plugin_init()
{
    register_plugin(PLUGIN_NAME, PLUGIN_DATE, PLUGIN_AUTHOR)

    register_dictionary("online_logger.txt")

    register_concmd("amx_make_log", "func_MakeLog", ACCESS_FLAG)

#if SAYCMD_MODE > 0
    #if EXTENDED_SAYCMD > 0
        register_saycmd("myonline", "func_CheckOnline")
    #else
        register_clcmd("say /myonline", "func_CheckOnline")
    #endif
#endif

    get_localinfo(DATA_DIR, g_szBigBuff, chx(g_szBigBuff))
    formatex(g_szString, chx(g_szString), "%s/%s", g_szBigBuff, DATE_FILE_NAME)

    get_time("%m", g_szDateBuff, chx(g_szDateBuff))
    g_iCurMonth = str_to_num(g_szDateBuff)

    new iFile = func_OpenDateFile(FILE_READ)

    if(iFile < 1)
    {
        if(iFile != INVALID_HANDLE)
            func_WriteDateFile()
    }
    else
    {
        fgets(iFile, g_szDateBuff, chx(g_szDateBuff))
        fclose(iFile)

        new iPrevMonth = str_to_num(g_szDateBuff)

        if(g_iCurMonth != iPrevMonth && func_WriteDateFile() > 0)
        {
            formatex(g_szString, chx(g_szString), "%s/vault/%s_%d.vault", g_szBigBuff, VAULT_NAME, iPrevMonth)

            if(file_exists(g_szString))
                func_GenerateLog(AUTO_GEN, iPrevMonth)
        }
    }

    func_OpenVault()

#if SAVE_INTERVAL > 0
    set_task(SAVE_INTERVAL * 60.0, "task_SaveVault", TASKID_SAVE_VAULT)
#endif
}

/* -------------------- */

#if SAVE_INTERVAL > 0
public task_SaveVault()
{
    nvault_close(g_iVault)
    func_OpenVault()
}
#endif

/* -------------------- */

func_OpenDateFile(bool:bMode)
{
    new iFile = fopen(g_szString, (bMode == FILE_READ) ? "r" : "w")
    if(!iFile && (bMode == FILE_WRITE || file_exists(g_szString)))
    {
        func_ErrorHandler(ERR_DATE_FILE, bMode)
        iFile = INVALID_HANDLE
    }
    return iFile
}

/* -------------------- */

func_WriteDateFile()
{
    new iFile = func_OpenDateFile(FILE_WRITE)
    if(iFile > 0)
    {
        fprintf(iFile, "%d", g_iCurMonth)
        fclose(iFile)
    }
    return iFile
}

/* -------------------- */

#if SAYCMD_MODE > 0
public func_CheckOnline(id)
{
    if(!CheckBit(g_iAuthPlayers, id))
        return PLUGIN_HANDLED

    new iFlags = get_user_flags(id)

#if defined IGNORE_FLAGS
    if((~iFlags & g_iLogFlags) || (iFlags & g_iIgnoreFlags))
#else
    if(~iFlags & g_iLogFlags)
#endif
    {
        client_cmd(id, "spk %s", ERROR_SOUND)
        client_print_color(id, print_team_red, "^1%L", id, "OL_NO_LOGGING_MSG")
        return PLUGIN_HANDLED
    }

#if SAYCMD_MODE > 1
    #if SAYCMD_MODE == 2
        if(~iFlags & g_iFlagToCheck)
    #endif
    #if SAYCMD_MODE > 2
        if(iFlags & g_iFlagToCheck)
    #endif
        {
            client_cmd(id, "spk %s", ERROR_SOUND)
            client_print_color(id, print_team_red, "^1%L", id, "OL_NO_ACCESS_MSG")
            return PLUGIN_HANDLED
        }
#endif

    if(!g_iTime[id])
        return func_LoadData(id, JUST_PRINT)

    return func_PrintTime(id)
}
#endif

/* -------------------- */

func_PrintTime(id)
{
    client_cmd(id, "spk %s", NOTICE_SOUND)
    client_print_color(id, print_team_default, "^1%L", id, "OL_ONLINE_MSG", get_time_length(id, max(0, g_iTime[id] + get_user_time(id, 1))))
    return PLUGIN_HANDLED
}

/* -------------------- */

func_LoadData(id, bool:bMode)
{
    new iTimeStamp
    if(nvault_lookup(g_iVault, g_szAuthID[id], g_szString, chx(g_szString), iTimeStamp))
        return func_ParseData(id, bMode)
    //else ->
    if(bMode == JUST_PRINT)
    {
        g_iTime[id] = -1
        return func_PrintTime(id)
    }
    //else ->
    new iMonthDay = func_GetDate(GET_MONTHDAY)
    get_user_name(id, g_szNames[FIRST_NAME], chx(g_szNames[]))
    copy(g_szNames[LAST_NAME], chx(g_szNames[]), g_szNames[FIRST_NAME])
    func_SaveData(id, func_GetDate(GET_YEARDAY), 1, iMonthDay, iMonthDay, 0)
    return PLUGIN_HANDLED
}

/* -------------------- */

func_ParseData(id, bool:bMode)
{
    parse(g_szString, g_szDateBuff, chx(g_szDateBuff), g_szDays[UNIQUE_DAYS],
        chx(g_szDays[]), g_szDays[FIRST_DAY], chx(g_szDays[]), g_szDays[LAST_DAY],
        chx(g_szDays[]), g_szTime, chx(g_szTime), g_szNames[FIRST_NAME], chx(g_szNames[])
    )

    new iYearDay = str_to_num(g_szDateBuff)
    new iActualYearDay = func_GetDate(GET_YEARDAY)
    new iUniqueDays = str_to_num(g_szDays[UNIQUE_DAYS])

    if(iYearDay != iActualYearDay)
    {
        iUniqueDays++
        iYearDay = iActualYearDay
    }

    if(bMode == JUST_PRINT)
    {
        g_iTime[id] = str_to_num(g_szTime)
        return func_PrintTime(id)
    }
    //else ->
    get_user_name(id, g_szNames[LAST_NAME], chx(g_szNames[]))
    return func_SaveData(id, iYearDay, iUniqueDays, str_to_num(g_szDays[FIRST_DAY]), func_GetDate(GET_MONTHDAY), str_to_num(g_szTime))
}

/* -------------------- */

func_SaveData(id, iYearDay, iUniqueDays, iFirstDay, iCurrentDay, iTime)
{
    get_flags(get_user_flags(id), g_szSmBuff, chx(g_szSmBuff))

    formatex(g_szString, chx(g_szString),
        "%d %d %d %d %d ^"%s^" ^"%s^" %s",
        iYearDay, iUniqueDays, iFirstDay, iCurrentDay,
        iTime + get_user_time(id, 1), g_szNames[FIRST_NAME], g_szNames[LAST_NAME], g_szSmBuff
    )

    nvault_set(g_iVault, g_szAuthID[id], g_szString)
    return PLUGIN_HANDLED
}

/* -------------------- */

func_OpenVault()
{
    formatex(g_szSmBuff, chx(g_szSmBuff), "%s_%d", VAULT_NAME, g_iCurMonth)
    if((g_iVault = nvault_open(g_szSmBuff)) == INVALID_HANDLE)
        set_fail_state("Error opening vault!")
}

/* -------------------- */

#if AMXX_VERSION_NUM < 183
public client_disconnect(id)
#else
public client_disconnected(id)
#endif
{
    if(CheckBit(g_iConnPlayers, id) && CheckBit(g_iAuthPlayers, id))
    {
        g_iTime[id] = 0
    #if defined IGNORE_FLAGS
        new iFlags = get_user_flags(id)
        if((iFlags & g_iLogFlags) && (~iFlags & g_iIgnoreFlags) && (g_szAuthID[id][0] == 'S' || g_szAuthID[id][0] == 'V'))
    #else
        if((get_user_flags(id) & g_iLogFlags) && (g_szAuthID[id][0] == 'S' || g_szAuthID[id][0] == 'V'))
    #endif
            func_LoadData(id, SAVE_DATA)
    }
    ClearBit(g_iConnPlayers, id);
    ClearBit(g_iAuthPlayers, id);
}

/* -------------------- */

func_GetDate(iMode)
{
    new szDateType[][] = { "%d", "%j", "%Y" }
    get_time(szDateType[iMode], g_szDateBuff, chx(g_szDateBuff))
    return str_to_num(g_szDateBuff)
}

/****************************************************************************************
*****************************************************************************************
************************************ Log Generation *************************************
*****************************************************************************************
****************************************************************************************/

public func_MakeLog(id, iAccess)
{
    if(id)
    {
        if(~get_user_flags(id) & iAccess) return PLUGIN_HANDLED

        static Float:fGenTime; new Float:fGameTime = get_gametime()
        if(fGameTime - fGenTime < 10.0)
        {
            client_cmd(id, "spk %s", ERROR_SOUND)
            console_print(id, "%s %L", g_szPluginPrefix, id, "OL_NO_SPAM")
            return PLUGIN_HANDLED
        }
        //else ->
        fGenTime = fGameTime
    }

    if(id)
        console_print(id, "%s %L", g_szPluginPrefix, id, "OL_START_GEN")
    else
        server_print("%s Vault blocked, generating has started...", g_szPluginPrefix)

    nvault_close(g_iVault)

    get_localinfo(DATA_DIR, g_szBigBuff, chx(g_szBigBuff))
    formatex(g_szString, chx(g_szString), "%s/vault/%s_%d.vault", g_szBigBuff, VAULT_NAME, g_iCurMonth)

    if(!func_GenerateLog(MANUAL_GEN, g_iCurMonth))
    {
        if(id)
        {
            console_print(id, "%s %L", g_szPluginPrefix, id, "OL_CRIT_ERROR")
            client_cmd(id, "spk %s", ERROR_SOUND)
        }
        else
            server_print("%s Critical error, see plugin error log!", g_szPluginPrefix)

        func_OpenVault()
        return PLUGIN_HANDLED
    }
    //else ->
#if FASTDL > 0
    new szLink[STRING_SIZE * 2]
    formatex(szLink, chx(szLink), "%s/%s/%s%d_%s%d%s%s", FASTDL_LINK, LOGS_DIR, LOG_PREFIX, func_GetDate(GET_YEAR), g_iCurMonth > 9 ? "" : "0", g_iCurMonth, MANUAL_POSTFIX, LOG_EXT)

    if(id)
    {
        console_print(id, "%s %L", g_szPluginPrefix, id, "OL_GEN_DONE_WITH_LINK", szLink)
        client_cmd(id, "spk %s", NOTICE_SOUND)
    }
    else
        server_print("%s Done, link: %s", g_szPluginPrefix, szLink)
#else
    if(id)
    {
        console_print(id, "%s %L", g_szPluginPrefix, id, "OL_GEN_DONE_WO_LINK")
        client_cmd(id, "spk %s", NOTICE_SOUND)
    }
    else
        server_print("%s Done. Link not defined (ask admin for file)", g_szPluginPrefix)
#endif

    return PLUGIN_HANDLED
}

/* -------------------- */

#define MAX_KEY_LEN AUTHID_STRLEN    // Max 255
#define MAX_VAL_LEN STRING_SIZE        // Max 65535
#define DATA_BUFFER STRING_SIZE        // Make this the greater of (MAX_KEY_LEN / 4) or (MAX_VAL_LEN / 4)

enum _:DATA_STRUCT
{
    AUTHID[AUTHID_STRLEN + 1],
    FLAGS[NAME_LENGTH],
    F_NAME[NAME_LENGTH],
    L_NAME[NAME_LENGTH],
    F_DAY[DAY_STRLEN],
    L_DAY[DAY_STRLEN],
    U_DAYS[DAY_STRLEN],
    TIME
}

enum _:POINTER_STRUCT { PTR_ID, PTR_TIME }

bool:func_GenerateLog(bool:bMode, iMonth)
{
    new iVaultFile = fopen(g_szString, "rb")

    if(!iVaultFile)
        return func_ErrorHandler(ERR_VAULT, FILE_READ)

    new szString[STRING_SIZE]

    formatex(szString, chx(szString), "%s/%s%d_%s%d%s%s", LOGS_DIR, LOG_PREFIX, func_GetDate(GET_YEAR), iMonth > 9 ? "" : "0", iMonth, (bMode == AUTO_GEN) ? "" : MANUAL_POSTFIX, LOG_EXT)

    if(!dir_exists(LOGS_DIR) && mkdir(LOGS_DIR))
        return func_ErrorHandler(ERR_FOLDER, FILE_WRITE)

    new iFile = fopen(szString, "w")

    if(!iFile)
    {
        fclose(iVaultFile)
        return func_ErrorHandler(ERR_LIST, FILE_WRITE)
    }

    new iVaultEntries, iKeyLen, iValLen, szVal[MAX_VAL_LEN + 1];

    new
        Trie:tTrie = TrieCreate(),
            Array:aArray = ArrayCreate(DATA_STRUCT),
                eData[DATA_STRUCT],
                    iPtrData[ACCOUNT_LIMIT][POINTER_STRUCT];

    fread_raw(iVaultFile, szString, 1, BLOCK_INT)
    fread_raw(iVaultFile, szString, 1, BLOCK_SHORT)
    fread_raw(iVaultFile, szString, 1, BLOCK_INT)
    iVaultEntries = szString[0]

    get_time("%Y (Generated %d/%m - %H:%M:%S)", szString, chx(szString))
    fprintf(iFile, "* Online log (%s mode) per %s%d/%s", (bMode == AUTO_GEN) ? "auto" : "manual", iMonth > 9 ? "" : "0", iMonth, szString)
    fprintf(iFile, "^n * Number of players in the list: %d (Limit: %d)^n", iVaultEntries, ACCOUNT_LIMIT)

    if(iVaultEntries > ACCOUNT_LIMIT)
    {
        fputs(iFile, "^nATTENTION! Record fetch limit has been reached.^nIncrease the value of the ACCOUNT_LIMIT variable in the plugin source.^n")
        iVaultEntries = ACCOUNT_LIMIT
    }

    for(new i = 1, a; i <= iVaultEntries; i++)
    {
        // TimeStamp
        fread_raw(iVaultFile, szString, 1, BLOCK_INT)

        // Key Length
        fread_raw(iVaultFile, szString, 1, BLOCK_BYTE)
        iKeyLen = szString[0] & 0xFF

        // Val Length
        fread_raw(iVaultFile, szString, 1, BLOCK_SHORT)
        iValLen = szString[0] & 0xFFFF

        // Key Data
        fread_raw(iVaultFile, szString, iKeyLen, BLOCK_CHAR)
        func_ReadString(eData[AUTHID], iKeyLen, chx(eData[AUTHID]), szString)

        // Val Data
        fread_raw(iVaultFile, szString, iValLen, BLOCK_CHAR)
        func_ReadString(szVal, iValLen, chx(szVal), szString)

        parse(szVal,
            g_szDateBuff,         chx(g_szDateBuff), // not used here
            eData[U_DAYS],         chx(eData[U_DAYS]),
            eData[F_DAY],         chx(eData[F_DAY]),
            eData[L_DAY],         chx(eData[L_DAY]),
            g_szTime,             chx(g_szTime),
            eData[F_NAME],         chx(eData[F_NAME]),
            eData[L_NAME],     chx(eData[L_NAME]),
            eData[FLAGS],         chx(eData[FLAGS])
        );

        iPtrData[a][PTR_TIME] = eData[TIME] = str_to_num(g_szTime)
        iPtrData[a][PTR_ID] = a++

        ArrayPushArray(aArray, eData)
        TrieSetCell(tTrie, eData[AUTHID], 0)
    }

    fclose(iVaultFile)

    SortCustom2D(iPtrData, sizeof(iPtrData), "func_SortTime")

    new const szDivider[] = "^n==================================================================================="

    for(new i; i < iVaultEntries; i++)
    {
        ArrayGetArray(aArray, iPtrData[i][PTR_ID], eData)

        fputs(iFile, szDivider)

        fprintf(iFile,
            "^n[#%d] %s --- Flags ---> %s^n\
            First/Last nickname seen:: ' %s ' <---------> ' %s '^n\
            First/Last Day Visit: %s/%s^n\
            Daily Visits (+1 times per day on first visit): %s^n\
            Total Online: %s",
            i + 1, eData[AUTHID], eData[FLAGS], eData[F_NAME], eData[L_NAME],
            eData[F_DAY], eData[L_DAY], eData[U_DAYS], get_time_length(0, eData[TIME])
        )
    }

    fputs(iFile, szDivider)

    fputs(iFile, "^nList of entries with SteamID authorization type that have zero online:")

    for(new i, a, szAuthID[AUTHID_STRLEN], iCount = admins_num(); i < iCount; i++)
    {
        if(~admins_lookup(i, AdminProp_Flags) & FLAG_AUTHID) continue

        admins_lookup(i, AdminProp_Auth, szAuthID, chx(szAuthID))

        if(TrieKeyExists(tTrie, szAuthID)) continue

        get_flags(admins_lookup(i, AdminProp_Access), g_szSmBuff, chx(g_szSmBuff))

        fprintf(iFile, "^n[#%d] %s --- Flags ---> %s", ++a, szAuthID, g_szSmBuff)
    }

    fputs(iFile, szDivider)

    fclose(iFile)
    TrieDestroy(tTrie)
    ArrayDestroy(aArray)

    if(bMode == AUTO_GEN)
        delete_file(g_szString)
    else
        func_OpenVault()

    return true
}

/* -------------------- */

public func_SortTime(eElement1[], eElement2[])
{
    if(eElement1[PTR_TIME] < eElement2[PTR_TIME])
        return 1

    if(eElement1[PTR_TIME] > eElement2[PTR_TIME])
        return -1

    return 0
}

/* -------------------- */

func_ReadString(szDestString[], iLen, iMaxLen, SourceData[])
{
    new iStrPos = -1, iRawPos

    while((++iStrPos < iLen) && (iStrPos < iMaxLen) && (iRawPos < DATA_BUFFER))
    {
        szDestString[iStrPos] = (SourceData[iRawPos] >> ((iStrPos % 4) * 8)) & 0xFF

        if(iStrPos && ((iStrPos % 4) == 3))
            iRawPos++
    }

    szDestString[iStrPos] = EOS
}

/* -------------------- */

#if !defined client_connectex
public client_authorized(id)
#else
public client_authorized(id, const szAuthID[])
#endif
{
    SetOneBit(g_iAuthPlayers, id);
    if(CheckBit(g_iConnPlayers, id))
    #if !defined client_connectex
        get_user_authid(id, g_szAuthID[id], chx(g_szAuthID[]))
    #else
        copy(g_szAuthID[id], chx(g_szAuthID[]), szAuthID)
    #endif
}

/* -------------------- */

public client_putinserver(id)
{
    SetOneBit(g_iConnPlayers, id);
    if(CheckBit(g_iAuthPlayers, id))
        get_user_authid(id, g_szAuthID[id], chx(g_szAuthID[]))
}

/* -------------------- */

stock register_saycmd(szSayCmd[], szFunc[]) // Batch register say commands
{
    new const szPrefix[][] = { "say /", "say_team /", "say .", "say_team ." }

    for(new i; i < sizeof(szPrefix); i++)
    {
        formatex(g_szSmBuff, chx(g_szSmBuff), "%s%s", szPrefix[i], szSayCmd)
        register_clcmd(g_szSmBuff, szFunc)
    }
}

/* -------------------- */

// Defines from 'time.inc' as constants
const SECONDS_IN_WEEK = 604800
const SECONDS_IN_DAY = 86400
const SECONDS_IN_HOUR = 3600
const SECONDS_IN_MINUTE = 60

enum _:TIME_TYPES { TYPE_WEEK, TYPE_DAY, TYPE_HOUR, TYPE_MIN, TYPE_SEC }
const TIME_ELEMENT_STRLEN = 12

get_time_length(id, iSec)
{
    new iCount, iType[TIME_TYPES], szElement[TIME_TYPES][TIME_ELEMENT_STRLEN]

    iType[TYPE_WEEK] = iSec / SECONDS_IN_WEEK
    iSec -= (iType[TYPE_WEEK] * SECONDS_IN_WEEK)

    iType[TYPE_DAY] = iSec / SECONDS_IN_DAY
    iSec -= (iType[TYPE_DAY] * SECONDS_IN_DAY)

    iType[TYPE_HOUR] = iSec / SECONDS_IN_HOUR
    iSec -= (iType[TYPE_HOUR] * SECONDS_IN_HOUR)

    iType[TYPE_MIN] = iSec / SECONDS_IN_MINUTE
    iType[TYPE_SEC] = iSec -= (iType[TYPE_MIN] * SECONDS_IN_MINUTE)

    new const szLang[][] = { "OL_TIME_WEEK", "OL_TIME_DAY", "OL_TIME_HOUR", "OL_TIME_MIN", "OL_TIME_SEC" }

    for(new i; i < sizeof(iType); i++)
        if(iType[i] > 0)
            formatex(szElement[iCount++], chx(szElement[]), "%d %L", iType[i], id, szLang[i])

    new const szAndChar[] = "OL_TIME_AND"

    switch(iCount)
    {
        case 0: formatex(g_szBigBuff, chx(g_szBigBuff), "0 %L", id, szLang[TYPE_SEC])
        case 1: copy(g_szBigBuff, chx(g_szBigBuff), szElement[0])
        case 2: formatex(g_szBigBuff, chx(g_szBigBuff), "%s %L %s", szElement[0], id, szAndChar, szElement[1])
        case 3: formatex(g_szBigBuff, chx(g_szBigBuff), "%s, %s, %L %s", szElement[0], szElement[1], id, szAndChar, szElement[2])
        case 4: formatex(g_szBigBuff, chx(g_szBigBuff), "%s, %s, %s, %L %s", szElement[0], szElement[1], szElement[2], id, szAndChar, szElement[3])
        case 5: formatex(g_szBigBuff, chx(g_szBigBuff), "%s, %s, %s, %s, %L %s", szElement[0], szElement[1], szElement[2], szElement[3], id, szAndChar, szElement[4])
    }

    return g_szBigBuff
}

/* -------------------- */

bool:func_ErrorHandler(iMode, bool:bAccessMode)
{
    static bool:bErrorPath

    if(!bErrorPath)
    {
        bErrorPath = true
        get_localinfo("amxx_logs", g_szErrorLog, chx(g_szErrorLog))
        format(g_szErrorLog, chx(g_szErrorLog), "%s/%s", g_szErrorLog, ERROR_LOG_NAME)
    }

    static const szMode[][] = { "Data file", "Vault", "Folder", "List" }
    log_to_file(g_szErrorLog, "%s %s: %s error (wrong chmod?)",
        g_szPluginPrefix, szMode[iMode], (bAccessMode == FILE_READ) ? "read" : "write")
    return false
}

/* -------------------- */

public plugin_end()
    nvault_close(g_iVault)

/* -------------------- */

#if USE_PRECACHE > 0
public plugin_precache() {
    precache_sound(NOTICE_SOUND)
    precache_sound(ERROR_SOUND)
}
#endif
 
Сообщения
1,176
Реакции
2,144
Помог
57 раз(а)
DrStrange,
Код:
// Path + name of the folder where admin logs will be placed
// The folder is created automatically at the time of creating the log, however, I strongly
// I recommend to create it manually, and immediately set the necessary access flags
new const LOGS_DIR[] = "online_logs" // At the root of fashion (cstrike)
cstrike/online_logs/...
 
Сообщения
213
Реакции
71
Помог
2 раз(а)
DrStrange,
Код:
// Path + name of the folder where admin logs will be placed
// The folder is created automatically at the time of creating the log, however, I strongly
// I recommend to create it manually, and immediately set the necessary access flags
new const LOGS_DIR[] = "online_logs" // At the root of fashion (cstrike)
cstrike/online_logs/...
Yes, I saw that in code and checked there but there is no such file.

Edited: Oh, Sorry, there is folder with that name, I thought it would be file and was looking for it, Thanks its there. BlackSignature
 
Сообщения
106
Реакции
8
Помог
2 раз(а)
BlackSignature , хотелось бы увидеть апдейт плагина, чтобы писал в логи какой админ сколько отыграл за определенную команду, в том числе и спектров. Полезно допустим для джаил модов, а то у меня админчики днями в спектрах сидят :с
 
Сообщения
1,176
Реакции
2,144
Помог
57 раз(а)
BLACKNAHOY, в архиве есть real_game_time.sma, поставь. Тогда будет учитываться только время в игре (ТТ/КТ).
 

RockTheStreet

Саппорт года
Сообщения
1,743
Реакции
346
Помог
40 раз(а)
Обратите внимание, если вы хотите заключить сделку с этим пользователем, он заблокирован
Сообщения
1,176
Реакции
2,144
Помог
57 раз(а)
melfyk, спасибо, я в курсе. хочешь сделать за меня? сделай. нет - разберёмся без тебя.
 
Сообщения
453
Реакции
255
Помог
9 раз(а)
melfyk,
Код:
    switch(szNewTeam[0]) {
        case 'U', 'S': {
            if(g_iGameStartTime[pPlayer]) {
                g_iSavedGameTime[pPlayer] += get_systime() - g_iGameStartTime[pPlayer]
                g_iGameStartTime[pPlayer] = 0
            }
        }
        case 'T', 'C': {
            if(!g_iGameStartTime[pPlayer]) {
                g_iGameStartTime[pPlayer] = get_systime()
            }
        }
    }
на
Код:
    switch(szNewTeam[0]) {
        case 'U': {
            if(g_iGameStartTime[pPlayer]) {
                g_iSavedGameTime[pPlayer] += get_systime() - g_iGameStartTime[pPlayer]
                g_iGameStartTime[pPlayer] = 0
            }
        }
        case 'T', 'C', 'S': {
            if(!g_iGameStartTime[pPlayer]) {
                g_iGameStartTime[pPlayer] = get_systime()
            }
        }
    }
 
Сообщения
1,015
Реакции
818
Помог
10 раз(а)
malniata, я лично так понял, что ему надо отдельно считать время за кт и отдельно за тт
 

Пользователи, просматривающие эту тему

Сейчас на форуме нет ни одного пользователя.
Сверху Снизу