829 lines
16 KiB
C++
829 lines
16 KiB
C++
// vim: set ts=4 sw=4 tw=99 noet:
|
|
//
|
|
// AMX Mod X, based on AMX Mod by Aleksander Naszko ("OLO").
|
|
// Copyright (C) The AMX Mod X Development Team.
|
|
//
|
|
// This software is licensed under the GNU General Public License, version 3 or higher.
|
|
// Additional exceptions apply. For full license details, see LICENSE.txt or visit:
|
|
// https://alliedmods.net/amxmodx-license
|
|
|
|
#include "amxmodx.h"
|
|
#include "format.h"
|
|
#include "datastructs.h"
|
|
|
|
//Adapted from Quake3's vsprintf
|
|
// thanks to cybermind for linking me to this :)
|
|
//I made the following changes:
|
|
// - Fixed spacing to be AMX Mod X standard
|
|
// - Added 'n' support, no buffer overflows
|
|
// - Templatized input/output buffers
|
|
|
|
#define ALT 0x00000001 /* alternate form */
|
|
#define HEXPREFIX 0x00000002 /* add 0x or 0X prefix */
|
|
#define LADJUST 0x00000004 /* left adjustment */
|
|
#define LONGDBL 0x00000008 /* long double */
|
|
#define LONGINT 0x00000010 /* long integer */
|
|
#define QUADINT 0x00000020 /* quad integer */
|
|
#define SHORTINT 0x00000040 /* short integer */
|
|
#define ZEROPAD 0x00000080 /* zero (as opposed to blank) pad */
|
|
#define FPT 0x00000100 /* floating point number */
|
|
#define UPPERDIGITS 0x00000200 /* make alpha digits uppercase */
|
|
#define to_digit(c) ((c) - '0')
|
|
#define is_digit(c) ((unsigned)to_digit(c) <= 9)
|
|
#define to_char(n) ((n) + '0')
|
|
#define CHECK_ARGS(n) \
|
|
if ((arg+n) > args) { \
|
|
LogError(amx, AMX_ERR_PARAMS, "String formatted incorrectly - parameter %d (total %d)", arg, args); \
|
|
return 0; \
|
|
}
|
|
|
|
template size_t atcprintf<cell, cell>(cell *, size_t, const cell *, AMX *, cell *, int *);
|
|
template size_t atcprintf<char, cell>(char *, size_t, const cell *, AMX *, cell *, int *);
|
|
template size_t atcprintf<cell, char>(cell *, size_t, const char *, AMX *, cell *, int *);
|
|
template size_t atcprintf<char, char>(char *, size_t, const char *, AMX *, cell *, int *);
|
|
|
|
THash<ke::AString, lang_err> BadLang_Table;
|
|
|
|
static cvar_t *amx_mldebug = nullptr;
|
|
static cvar_t *amx_cl_langs = nullptr;
|
|
|
|
const char *playerlang(const cell index)
|
|
{
|
|
const char *pLangName = nullptr;
|
|
|
|
if (index == LANG_PLAYER)
|
|
{
|
|
if (!amx_cl_langs)
|
|
{
|
|
amx_cl_langs = CVAR_GET_POINTER("amx_client_languages");
|
|
}
|
|
|
|
if (static_cast<int>(amx_cl_langs->value) == 0)
|
|
{
|
|
pLangName = amxmodx_language->string;
|
|
}
|
|
else
|
|
{
|
|
pLangName = ENTITY_KEYVALUE(GET_PLAYER_POINTER_I(g_langMngr.GetDefLang())->pEdict, "lang");
|
|
}
|
|
}
|
|
else if (index == LANG_SERVER)
|
|
{
|
|
pLangName = amxmodx_language->string;
|
|
}
|
|
else if (index >= 1 && index <= gpGlobals->maxClients)
|
|
{
|
|
if (!amx_cl_langs)
|
|
{
|
|
amx_cl_langs = CVAR_GET_POINTER("amx_client_languages");
|
|
}
|
|
|
|
if (static_cast<int>(amx_cl_langs->value) == 0)
|
|
{
|
|
pLangName = amxmodx_language->string;
|
|
}
|
|
else
|
|
{
|
|
pLangName = ENTITY_KEYVALUE(GET_PLAYER_POINTER_I(index)->pEdict, "lang");
|
|
}
|
|
}
|
|
|
|
return pLangName;
|
|
}
|
|
|
|
const char *translate(AMX *amx, const char *lang, const char *key)
|
|
{
|
|
auto pLangName = lang;
|
|
int status;
|
|
|
|
if (!pLangName || !isalpha(pLangName[0]))
|
|
{
|
|
pLangName = amxmodx_language->string;
|
|
}
|
|
|
|
auto def = g_langMngr.GetDef(pLangName, key, status);
|
|
|
|
if (!amx_mldebug)
|
|
{
|
|
amx_mldebug = CVAR_GET_POINTER("amx_mldebug");
|
|
}
|
|
|
|
auto debug = (amx_mldebug && amx_mldebug->string && (amx_mldebug->string[0] != '\0'));
|
|
|
|
if (debug)
|
|
{
|
|
int debug_status;
|
|
auto validlang = true;
|
|
auto testlang = amx_mldebug->string;
|
|
|
|
if (!g_langMngr.LangExists(testlang))
|
|
{
|
|
AMXXLOG_Error("[AMXX] \"%s\" is an invalid debug language", testlang);
|
|
validlang = false;
|
|
}
|
|
|
|
g_langMngr.GetDef(testlang, key, debug_status);
|
|
|
|
if (validlang && debug_status == ERR_BADKEY)
|
|
{
|
|
AMXXLOG_Error("[AMXX] Language key \"%s\" not found for language \"%s\", check \"%s\"", key, testlang, GetFileName(amx));
|
|
}
|
|
}
|
|
|
|
if (!def)
|
|
{
|
|
if (debug && status == ERR_BADLANG)
|
|
{
|
|
ke::AString langName(pLangName);
|
|
|
|
auto &err = BadLang_Table.AltFindOrInsert(ke::Move(langName));
|
|
|
|
if (err.last + 120.0f < gpGlobals->time)
|
|
{
|
|
AMXXLOG_Error("[AMXX] Language \"%s\" not found", pLangName);
|
|
err.last = gpGlobals->time;
|
|
}
|
|
}
|
|
|
|
if (strcmp(pLangName, amxmodx_language->string) != 0)
|
|
{
|
|
def = g_langMngr.GetDef(amxmodx_language->string, key, status);
|
|
}
|
|
|
|
if (!def && (strcmp(pLangName, "en") != 0 && strcmp(amxmodx_language->string, "en") != 0))
|
|
{
|
|
def = g_langMngr.GetDef("en", key, status);
|
|
}
|
|
}
|
|
|
|
return def;
|
|
}
|
|
|
|
template <typename U, typename S>
|
|
void AddString(U **buf_p, size_t &maxlen, const S *string, int width, int prec)
|
|
{
|
|
int size = 0;
|
|
U *buf;
|
|
static S nlstr[] = {'(','n','u','l','l',')','\0'};
|
|
|
|
buf = *buf_p;
|
|
|
|
if (string == NULL)
|
|
{
|
|
string = nlstr;
|
|
prec = -1;
|
|
}
|
|
|
|
if (prec >= 0)
|
|
{
|
|
for (size = 0; size < prec; size++)
|
|
{
|
|
if (string[size] == '\0')
|
|
break;
|
|
}
|
|
} else {
|
|
while (string[size++]) ;
|
|
size--;
|
|
}
|
|
|
|
if (size > (int)maxlen)
|
|
size = maxlen;
|
|
|
|
/* If precision is provided, make sure we don't truncate a multi-byte character */
|
|
if (prec >= size && (string[size - 1] & 1 << 7))
|
|
{
|
|
size -= UTIL_CheckValidChar((cell *)string + size - 1);
|
|
}
|
|
|
|
maxlen -= size;
|
|
width -= size;
|
|
|
|
while (size--)
|
|
*buf++ = static_cast<U>(*string++);
|
|
|
|
while (width-- > 0 && maxlen)
|
|
{
|
|
*buf++ = ' ';
|
|
maxlen--;
|
|
}
|
|
|
|
*buf_p = buf;
|
|
}
|
|
|
|
template <typename U>
|
|
void AddFloat(U **buf_p, size_t &maxlen, double fval, int width, int prec, int flags)
|
|
{
|
|
int digits; // non-fraction part digits
|
|
double tmp; // temporary
|
|
U *buf = *buf_p; // output buffer pointer
|
|
int val; // temporary
|
|
int sign = 0; // 0: positive, 1: negative
|
|
int fieldlength; // for padding
|
|
int significant_digits = 0; // number of significant digits written
|
|
const int MAX_SIGNIFICANT_DIGITS = 16;
|
|
|
|
// default precision
|
|
if (prec < 0)
|
|
{
|
|
prec = 6;
|
|
}
|
|
|
|
// get the sign
|
|
if (fval < 0)
|
|
{
|
|
fval = -fval;
|
|
sign = 1;
|
|
}
|
|
|
|
// compute whole-part digits count
|
|
digits = (int)log10(fval) + 1;
|
|
|
|
// Only print 0.something if 0 < fval < 1
|
|
if (digits < 1)
|
|
{
|
|
digits = 1;
|
|
}
|
|
|
|
// compute the field length
|
|
fieldlength = digits + prec + ((prec > 0) ? 1 : 0) + sign;
|
|
|
|
// minus sign BEFORE left padding if padding with zeros
|
|
if (sign && maxlen && (flags & ZEROPAD))
|
|
{
|
|
*buf++ = '-';
|
|
maxlen--;
|
|
}
|
|
|
|
// right justify if required
|
|
if ((flags & LADJUST) == 0)
|
|
{
|
|
while ((fieldlength < width) && maxlen)
|
|
{
|
|
*buf++ = (flags & ZEROPAD) ? '0' : ' ';
|
|
width--;
|
|
maxlen--;
|
|
}
|
|
}
|
|
|
|
// minus sign AFTER left padding if padding with spaces
|
|
if (sign && maxlen && !(flags & ZEROPAD))
|
|
{
|
|
*buf++ = '-';
|
|
maxlen--;
|
|
}
|
|
|
|
// write the whole part
|
|
tmp = pow(10.0, digits-1);
|
|
while ((digits--) && maxlen)
|
|
{
|
|
if (++significant_digits > MAX_SIGNIFICANT_DIGITS)
|
|
{
|
|
*buf++ = '0';
|
|
}
|
|
else
|
|
{
|
|
val = (int)(fval / tmp);
|
|
*buf++ = '0' + val;
|
|
fval -= val * tmp;
|
|
tmp *= 0.1;
|
|
}
|
|
maxlen--;
|
|
}
|
|
|
|
// write the fraction part
|
|
if (maxlen && prec)
|
|
{
|
|
*buf++ = '.';
|
|
maxlen--;
|
|
}
|
|
|
|
tmp = pow(10.0, prec);
|
|
|
|
fval *= tmp;
|
|
while (prec-- && maxlen)
|
|
{
|
|
if (++significant_digits > MAX_SIGNIFICANT_DIGITS)
|
|
{
|
|
*buf++ = '0';
|
|
}
|
|
else
|
|
{
|
|
tmp *= 0.1;
|
|
val = (int)(fval / tmp);
|
|
*buf++ = '0' + val;
|
|
fval -= val * tmp;
|
|
}
|
|
maxlen--;
|
|
}
|
|
|
|
// left justify if required
|
|
if (flags & LADJUST)
|
|
{
|
|
while ((fieldlength < width) && maxlen)
|
|
{
|
|
// right-padding only with spaces, ZEROPAD is ignored
|
|
*buf++ = ' ';
|
|
width--;
|
|
maxlen--;
|
|
}
|
|
}
|
|
|
|
// update parent's buffer pointer
|
|
*buf_p = buf;
|
|
}
|
|
|
|
template <typename U>
|
|
void AddBinary(U **buf_p, size_t &maxlen, unsigned int val, int width, int flags)
|
|
{
|
|
char text[32];
|
|
int digits;
|
|
U *buf;
|
|
|
|
digits = 0;
|
|
do
|
|
{
|
|
if (val & 1)
|
|
{
|
|
text[digits++] = '1';
|
|
}
|
|
else
|
|
{
|
|
text[digits++] = '0';
|
|
}
|
|
val >>= 1;
|
|
} while (val);
|
|
|
|
buf = *buf_p;
|
|
|
|
if (!(flags & LADJUST))
|
|
{
|
|
while (digits < width && maxlen)
|
|
{
|
|
*buf++ = (flags & ZEROPAD) ? '0' : ' ';
|
|
width--;
|
|
maxlen--;
|
|
}
|
|
}
|
|
|
|
while (digits-- && maxlen)
|
|
{
|
|
*buf++ = text[digits];
|
|
width--;
|
|
maxlen--;
|
|
}
|
|
|
|
if (flags & LADJUST)
|
|
{
|
|
while (width-- && maxlen)
|
|
{
|
|
*buf++ = (flags & ZEROPAD) ? '0' : ' ';
|
|
maxlen--;
|
|
}
|
|
}
|
|
|
|
*buf_p = buf;
|
|
}
|
|
|
|
template <typename U>
|
|
void AddUInt(U **buf_p, size_t &maxlen, unsigned int val, int width, int flags)
|
|
{
|
|
U text[32];
|
|
int digits;
|
|
U *buf;
|
|
|
|
digits = 0;
|
|
do {
|
|
text[digits++] = '0' + val % 10;
|
|
val /= 10;
|
|
} while (val);
|
|
|
|
buf = *buf_p;
|
|
|
|
if( !(flags & LADJUST) )
|
|
{
|
|
while (digits < width && maxlen)
|
|
{
|
|
*buf++ = (flags & ZEROPAD) ? '0' : ' ';
|
|
width--;
|
|
maxlen--;
|
|
}
|
|
}
|
|
|
|
while (digits-- && maxlen)
|
|
{
|
|
*buf++ = text[digits];
|
|
width--;
|
|
maxlen--;
|
|
}
|
|
|
|
if (flags & LADJUST)
|
|
{
|
|
while (width-- && maxlen)
|
|
{
|
|
*buf++ = (flags & ZEROPAD) ? '0' : ' ';
|
|
maxlen--;
|
|
}
|
|
}
|
|
|
|
*buf_p = buf;
|
|
}
|
|
|
|
template <typename U>
|
|
void AddInt(U **buf_p, size_t &maxlen, int val, int width, int flags)
|
|
{
|
|
U text[32];
|
|
int digits;
|
|
int signedVal;
|
|
U *buf;
|
|
unsigned int unsignedVal;
|
|
|
|
digits = 0;
|
|
signedVal = val;
|
|
if (val < 0)
|
|
{
|
|
/* we want the unsigned version */
|
|
unsignedVal = abs(val);
|
|
} else {
|
|
unsignedVal = val;
|
|
}
|
|
do {
|
|
text[digits++] = '0' + unsignedVal % 10;
|
|
unsignedVal /= 10;
|
|
} while (unsignedVal);
|
|
|
|
if (signedVal < 0)
|
|
text[digits++] = '-';
|
|
|
|
buf = *buf_p;
|
|
|
|
if( !(flags & LADJUST) )
|
|
{
|
|
while (digits < width && maxlen)
|
|
{
|
|
*buf++ = (flags & ZEROPAD) ? '0' : ' ';
|
|
width--;
|
|
maxlen--;
|
|
}
|
|
}
|
|
|
|
while (digits-- && maxlen)
|
|
{
|
|
*buf++ = text[digits];
|
|
width--;
|
|
maxlen--;
|
|
}
|
|
|
|
if (flags & LADJUST)
|
|
{
|
|
while (width-- && maxlen)
|
|
{
|
|
*buf++ = (flags & ZEROPAD) ? '0' : ' ';
|
|
maxlen--;
|
|
}
|
|
}
|
|
|
|
*buf_p = buf;
|
|
}
|
|
|
|
template <typename U>
|
|
void AddHex(U **buf_p, size_t &maxlen, unsigned int val, int width, int flags)
|
|
{
|
|
U text[32];
|
|
int digits;
|
|
U *buf;
|
|
U digit;
|
|
int hexadjust;
|
|
|
|
if (flags & UPPERDIGITS)
|
|
{
|
|
hexadjust = 'A' - '9' - 1;
|
|
} else {
|
|
hexadjust = 'a' - '9' - 1;
|
|
}
|
|
|
|
digits = 0;
|
|
do
|
|
{
|
|
digit = ('0' + val % 16);
|
|
if (digit > '9')
|
|
{
|
|
digit += hexadjust;
|
|
}
|
|
|
|
text[digits++] = digit;
|
|
val /= 16;
|
|
} while (val);
|
|
|
|
buf = *buf_p;
|
|
|
|
if( !(flags & LADJUST) )
|
|
{
|
|
while (digits < width && maxlen)
|
|
{
|
|
*buf++ = (flags & ZEROPAD) ? '0' : ' ';
|
|
width--;
|
|
maxlen--;
|
|
}
|
|
}
|
|
|
|
while (digits-- && maxlen)
|
|
{
|
|
*buf++ = text[digits];
|
|
width--;
|
|
maxlen--;
|
|
}
|
|
|
|
if (flags & LADJUST)
|
|
{
|
|
while (width-- && maxlen)
|
|
{
|
|
*buf++ = (flags & ZEROPAD) ? '0' : ' ';
|
|
maxlen--;
|
|
}
|
|
}
|
|
|
|
*buf_p = buf;
|
|
}
|
|
|
|
template <typename D, typename S>
|
|
size_t atcprintf(D *buffer, size_t maxlen, const S *format, AMX *amx, cell *params, int *param)
|
|
{
|
|
int arg;
|
|
int args = params[0] / sizeof(cell);
|
|
D *buf_p;
|
|
D ch;
|
|
int flags;
|
|
int width;
|
|
int prec;
|
|
int n;
|
|
//char sign;
|
|
const S *fmt;
|
|
size_t llen = maxlen;
|
|
|
|
buf_p = buffer;
|
|
arg = *param;
|
|
fmt = format;
|
|
|
|
while (true)
|
|
{
|
|
// run through the format string until we hit a '%' or '\0'
|
|
for (ch = static_cast<D>(*fmt);
|
|
llen && ((ch = static_cast<D>(*fmt)) != '\0' && ch != '%');
|
|
fmt++)
|
|
{
|
|
*buf_p++ = static_cast<D>(ch);
|
|
llen--;
|
|
}
|
|
if (ch == '\0' || llen <= 0)
|
|
goto done;
|
|
|
|
// skip over the '%'
|
|
fmt++;
|
|
|
|
// reset formatting state
|
|
flags = 0;
|
|
width = 0;
|
|
prec = -1;
|
|
//sign = '\0';
|
|
|
|
rflag:
|
|
ch = static_cast<D>(*fmt++);
|
|
reswitch:
|
|
switch(ch)
|
|
{
|
|
case '-':
|
|
flags |= LADJUST;
|
|
goto rflag;
|
|
case '.':
|
|
n = 0;
|
|
while( is_digit( ( ch = static_cast<D>(*fmt++)) ) )
|
|
n = 10 * n + ( ch - '0' );
|
|
prec = n < 0 ? -1 : n;
|
|
goto reswitch;
|
|
case '0':
|
|
flags |= ZEROPAD;
|
|
goto rflag;
|
|
case '1':
|
|
case '2':
|
|
case '3':
|
|
case '4':
|
|
case '5':
|
|
case '6':
|
|
case '7':
|
|
case '8':
|
|
case '9':
|
|
n = 0;
|
|
do {
|
|
n = 10 * n + ( ch - '0' );
|
|
ch = static_cast<D>(*fmt++);
|
|
} while( is_digit( ch ) );
|
|
width = n;
|
|
goto reswitch;
|
|
case 'c':
|
|
CHECK_ARGS(0);
|
|
*buf_p++ = static_cast<D>(*get_amxaddr(amx, params[arg]));
|
|
llen--;
|
|
arg++;
|
|
break;
|
|
case 'b':
|
|
CHECK_ARGS(0);
|
|
AddBinary(&buf_p, llen, *get_amxaddr(amx, params[arg]), width, flags);
|
|
arg++;
|
|
break;
|
|
case 'd':
|
|
case 'i':
|
|
CHECK_ARGS(0);
|
|
AddInt(&buf_p, llen, *get_amxaddr(amx, params[arg]), width, flags);
|
|
arg++;
|
|
break;
|
|
case 'u':
|
|
CHECK_ARGS(0);
|
|
AddUInt(&buf_p, llen, static_cast<unsigned int>(*get_amxaddr(amx, params[arg])), width, flags);
|
|
arg++;
|
|
break;
|
|
case 'f':
|
|
CHECK_ARGS(0);
|
|
AddFloat(&buf_p, llen, amx_ctof(*get_amxaddr(amx, params[arg])), width, prec, flags);
|
|
arg++;
|
|
break;
|
|
case 'X':
|
|
CHECK_ARGS(0);
|
|
flags |= UPPERDIGITS;
|
|
AddHex(&buf_p, llen, static_cast<unsigned int>(*get_amxaddr(amx, params[arg])), width, flags);
|
|
arg++;
|
|
break;
|
|
case 'x':
|
|
CHECK_ARGS(0);
|
|
AddHex(&buf_p, llen, static_cast<unsigned int>(*get_amxaddr(amx, params[arg])), width, flags);
|
|
arg++;
|
|
break;
|
|
case 'a':
|
|
{
|
|
CHECK_ARGS(0);
|
|
// %a is passed a pointer directly to a cell string.
|
|
cell* ptr=reinterpret_cast<cell*>(*get_amxaddr(amx, params[arg]));
|
|
if (!ptr)
|
|
{
|
|
LogError(amx, AMX_ERR_NATIVE, "Invalid vector string handle provided (%d)", *get_amxaddr(amx, params[arg]));
|
|
return 0;
|
|
}
|
|
|
|
AddString(&buf_p, llen, ptr, width, prec);
|
|
arg++;
|
|
break;
|
|
}
|
|
case 's':
|
|
CHECK_ARGS(0);
|
|
AddString(&buf_p, llen, get_amxaddr(amx, params[arg]), width, prec);
|
|
arg++;
|
|
break;
|
|
case 'L':
|
|
case 'l':
|
|
{
|
|
const char *lang;
|
|
int len;
|
|
if (ch == 'L')
|
|
{
|
|
CHECK_ARGS(1);
|
|
auto currParam = params[arg++];
|
|
lang = playerlang(*get_amxaddr(amx, currParam));
|
|
if (!lang)
|
|
lang = get_amxstring(amx, currParam, 2, len);
|
|
}
|
|
else
|
|
{
|
|
CHECK_ARGS(0);
|
|
lang = playerlang(g_langMngr.GetDefLang());
|
|
}
|
|
const char *key = get_amxstring(amx, params[arg++], 3, len);
|
|
const char *def = translate(amx, lang, key);
|
|
if (!def)
|
|
{
|
|
static char buf[255];
|
|
ke::SafeSprintf(buf, sizeof(buf), "ML_NOTFOUND: %s", key);
|
|
def = buf;
|
|
}
|
|
size_t written = atcprintf(buf_p, llen, def, amx, params, &arg);
|
|
buf_p += written;
|
|
llen -= written;
|
|
break;
|
|
}
|
|
case 'N':
|
|
{
|
|
CHECK_ARGS(0);
|
|
cell *addr = get_amxaddr(amx, params[arg]);
|
|
char buffer[255];
|
|
if (*addr)
|
|
{
|
|
CPlayer *player = NULL;
|
|
|
|
if (*addr >= 1 && *addr <= gpGlobals->maxClients)
|
|
{
|
|
player = GET_PLAYER_POINTER_I(*addr);
|
|
}
|
|
|
|
if (!player || !player->initialized)
|
|
{
|
|
LogError(amx, AMX_ERR_NATIVE, "Client index %d is invalid", *addr);
|
|
return 0;
|
|
}
|
|
|
|
const char *auth = GETPLAYERAUTHID(player->pEdict);
|
|
if (!auth || auth[0] == '\0')
|
|
{
|
|
auth = "STEAM_ID_PENDING";
|
|
}
|
|
|
|
int userid = GETPLAYERUSERID(player->pEdict);
|
|
ke::SafeSprintf(buffer, sizeof(buffer), "%s<%d><%s><%s>", player->name.chars(), userid, auth, player->team.chars());
|
|
}
|
|
else
|
|
{
|
|
ke::SafeSprintf(buffer, sizeof(buffer), "Console<0><Console><Console>");
|
|
}
|
|
|
|
AddString(&buf_p, llen, buffer, width, prec);
|
|
arg++;
|
|
break;
|
|
}
|
|
case 'n':
|
|
{
|
|
CHECK_ARGS(0);
|
|
cell *addr = get_amxaddr(amx, params[arg]);
|
|
const char *name = "Console";
|
|
|
|
if (*addr)
|
|
{
|
|
CPlayer *player = NULL;
|
|
|
|
if (*addr >= 1 && *addr <= gpGlobals->maxClients)
|
|
{
|
|
player = GET_PLAYER_POINTER_I(*addr);
|
|
}
|
|
|
|
if (!player || !player->initialized)
|
|
{
|
|
LogError(amx, AMX_ERR_NATIVE, "Client index %d is invalid", *addr);
|
|
return 0;
|
|
}
|
|
|
|
name = player->name.chars();
|
|
}
|
|
|
|
AddString(&buf_p, llen, name, width, prec);
|
|
arg++;
|
|
break;
|
|
}
|
|
case '%':
|
|
*buf_p++ = static_cast<D>(ch);
|
|
if (!llen)
|
|
goto done;
|
|
llen--;
|
|
break;
|
|
case '\0':
|
|
*buf_p++ = static_cast<D>('%');
|
|
if (!llen)
|
|
goto done;
|
|
llen--;
|
|
goto done;
|
|
break;
|
|
default:
|
|
*buf_p++ = static_cast<D>(ch);
|
|
if (!llen)
|
|
goto done;
|
|
llen--;
|
|
break;
|
|
}
|
|
}
|
|
|
|
done:
|
|
*buf_p = static_cast<D>(0);
|
|
*param = arg;
|
|
|
|
/* if max buffer length consumed, make sure we don't truncate a multi-byte character */
|
|
if (llen <= 0 && *(buf_p - 1) & 1 << 7)
|
|
{
|
|
llen += UTIL_CheckValidChar(buf_p - 1);
|
|
*(buf_p - llen) = static_cast<D>(0);
|
|
}
|
|
|
|
return maxlen-llen;
|
|
}
|
|
|
|
/**
|
|
* HACKHACK: The compiler will generate code for each case we need.
|
|
* Don't remove this, otherwise files that use certain code generations
|
|
* will have extern problems. For each case you need, add dummy code
|
|
* here.
|
|
*/
|
|
void __WHOA_DONT_CALL_ME_PLZ_K_lol_o_O()
|
|
{
|
|
//acsprintf
|
|
atcprintf((cell *)NULL, 0, (const char *)NULL, NULL, NULL, NULL);
|
|
//accprintf
|
|
atcprintf((cell *)NULL, 0, (cell *)NULL, NULL, NULL, NULL);
|
|
//ascprintf
|
|
atcprintf((char *)NULL, 0, (cell *)NULL, NULL, NULL, NULL);
|
|
}
|
|
|