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