#if defined _INC_y_utils
#endinput
#endif
#define _INC_y_utils
/**
*
*
* Misc functions used throughout.
*
* 0.1.3
*
* Stock
*
* Convert a whole string to lower-case.
* Convert a whole string to upper-case.
* Generate a random number, optionally takes lower and upper bounds.
* Same as , but for floats.
* Strips the newline characters from the end of a string.
* Remove whitespace from the start of a string.
* Remove whitespace from both ends of a string.
* Check if the given position is the end of a string (ignoring whitespace).
* Return the first position (after ) of the given character.
* Like , but without the upper-bounds check.
* Generate the Bernstein hash of the given string.
* Is the given string hexadecimal?
* Version of that returns the result.
* Get the string passed as a variable argument from the given index.
* Like , formats a string and returns the result.
* Is the given string a number?
* Return the value of the given hexadecimal string.
* Return the value of the given boolean string.
* Return the value of the given binary string.
* Copy memory between two address, instead of two arrays.
* Set all of an array to a value.
* Set all of a given memory region to a value.
* Return a player's name.
* Ensures that a file exists, but nothing more.
* Get the colour (in 3D RGB space) between two other colours.
* Return the first position in a string of a non-whitespace character.
* Get the first and last positions of non-whitespace characters in the string. Like , but doesn't modify the string.
* Get the total (sum) of an array.
* Get the mathematical mean of an array.
* Get the mathematical mode of an array.
* Get the mathematical median of an array.
* Get the mathematical range of an array.
*
* Inline
*
* Unsigned compare.
* Check if a player ID is valid (in range).
* Check if a number is in range.
* Check if a number is outside a range.
* Divide two numbers and round up.
* Divide two numbers and round down.
* Checks if a string is NULL (\1\0 or \0).
* Checks if a number is odd.
* Checks if a number is even.
* Copy one string to another.
* Return the encoded (32-bit) version of a player's IP.
* (there are a lot)
* (there are a lot)
* (there are a lot)
* (there are a lot)
* (there are a lot)
*
*
*
*
* Global
*
* True hack for infinate loops.
* False hack for one-time loops.
* 1 long string for passing via Call(Remote|Local)Function.
*
*
*//** *//*
Legal:
Version: MPL 1.1
The contents of this file are subject to the Mozilla Public License Version
1.1 (the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.mozilla.org/MPL/
Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
for the specific language governing rights and limitations under the
License.
The Original Code is the YSI framework.
The Initial Developer of the Original Code is Alex "Y_Less" Cole.
Portions created by the Initial Developer are Copyright (C) 2011
the Initial Developer. All Rights Reserved.
Contributors:
Y_Less
koolk
JoeBullet/Google63
g_aSlice/Slice
Misiur
samphunter
tianmeta
maddinat0r
spacemud
Crayder
Dayvison
Ahmad45123
Zeex
irinel1996
Yiin-
Chaprnks
Konstantinos
Masterchen09
Southclaws
PatchwerkQWER
m0k1
paulommu
udan111
Thanks:
JoeBullet/Google63 - Handy arbitrary ASM jump code using SCTRL.
ZeeX - Very productive conversations.
koolk - IsPlayerinAreaEx code.
TheAlpha - Danish translation.
breadfish - German translation.
Fireburn - Dutch translation.
yom - French translation.
50p - Polish translation.
Zamaroht - Spanish translation.
Los - Portuguese translation.
Dracoblue, sintax, mabako, Xtreme, other coders - Producing other modes for
me to strive to better.
Pixels^ - Running XScripters where the idea was born.
Matite - Pestering me to release it and using it.
Very special thanks to:
Thiadmer - PAWN, whose limits continue to amaze me!
Kye/Kalcor - SA:MP.
SA:MP Team past, present and future - SA:MP.
Optional plugins:
Gamer_Z - GPS.
Incognito - Streamer.
Me - sscanf2, fixes2, Whirlpool.
*/
#include "..\YSI_Internal\y_compilerdata"
#include "..\YSI_Internal\y_version"
#include "..\YSI_Core\y_debug"
#include "..\YSI_Storage\y_amx"
//#tryinclude
#include "..\YSI_Internal\y_thirdpartyinclude"
#include "..\YSI_Internal\y_funcinc"
// Add new tags to the START of this list.
#include "..\YSI_Internal\y_globaltags"
#define YSI_MAX_STRING (144)
#define FUNCTION_LENGTH (32)
// Better handling of operator precedences and floating point numbers. This
// will now work for ALL regular numbers (including -0.5 which broke the old
// version). I don't know of any complex expressions that break it with
// operator precedences, but I'm not ruling it out. The brackets do try and
// account for that possibility, but I just don't know.
#define NO_VALUE(%0) ((2*%0-1)==2*(%0-1))
#if !defined TRUE
new stock
bool:TRUE = true;
#endif
#if !defined FALSE
new stock
bool:FALSE = false;
#endif
#if !defined NULL
new stock
NULL[2] = "\1";
#endif
#if !defined cellbytes
#define cellbytes (cellbits / 8)
#endif
#define UNSIGNED(%0) ((%0) - cellmin)
// Define "volatile" as nothing.
#if !defined volatile
#define volatile
#endif
#define YSIM_MASTER #M
#define YSIM_RETURN #R
#define YSIM_CALLER #C
#define YSIM_TEXT_D #T
#define YSIM_TXTFND #X
#define YSIM_TXTIND #I
#define YSIM_TXTLEN #E
#define YSIM_LOG_IN #U
#define YSIM_VARARG #V
#if !defined YSIM_STRING
#define YSIM_STRING (42)
#endif
#define FLOAT_INFINITY (Float:0x7F800000)
#define FLOAT_NEG_INFINITY (Float:0xFF800000)
#define FLOAT_NEGATIVE_INFINITY (Float:0xFF800000)
#define FLOAT_NAN (Float:0x7FFFFFFF)
#define FLOAT_NOT_A_NUMBER (Float:0x7FFFFFFF)
#define FLOAT_QNAN (Float:0x7FFFFFFF)
#define FLOAT_QUIET_NAN (Float:0x7FFFFFFF)
#define FLOAT_QUIET_NOT_A_NUMBER (Float:0x7FFFFFFF)
#define FLOAT_SNAN (Float:0x7FBFFFFF)
#define FLOAT_SIGNALING_NAN (Float:0x7FBFFFFF)
#define FLOAT_SIGNALING_NOT_A_NUMBER (Float:0x7FBFFFFF)
//#pragma unused TRUE, FALSE, NULL
#define __TY|||%0||| (1000000)
#define __TX:__TY|||%0,%1||| (%1)
#define __TF=fopen(%0,%2"%3",%4) __TF=fopen(%0".csv",%4)
#if !defined TIMING_ITERATIONS
#define TIMING_ITERATIONS (10)
#endif
/*-------------------------------------------------------------------------*//**
* The time in ms.
* The number of iterations completed in this time.
*
* Formats and returns a string representing the time taken for one iteration,
* given the time required for many iterations. This attempts to format the
* number using a reasonable fraction of a second.
*
*//*------------------------------------------------------------------------**/
stock __TU(t, iters)
{
// Number of times run. Accounts for the string and optional count.
new
ret[20];
if (iters > 1000000000)
format(ret, sizeof (ret), "%.2fps", float(t) / (float(iters) / 1000000000.0));
else if (iters == 1000000000)
format(ret, sizeof (ret), "%d.00ps", t);
else if (iters > 1000000)
format(ret, sizeof (ret), "%.2fns", float(t) / (float(iters) / 1000000.0));
else if (iters == 1000000)
format(ret, sizeof (ret), "%d.00ns", t);
else if (iters > 1000)
format(ret, sizeof (ret), "%.2fus", float(t) / (float(iters) / 1000.0));
else if (iters == 1000)
format(ret, sizeof (ret), "%d.00us", t);
else if (iters > 1)
format(ret, sizeof (ret), "%.2fms", float(t) / float(iters));
else
format(ret, sizeof (ret), "%d.00ms", t);
return ret;
}
#define RUN_TIMING(%0) \
for(new __TA[TIMING_ITERATIONS],__TC=sizeof __TA,__TE=_:__TX:__TY|||%0|||,__TS=printf("Timing \"%s\"...",%0);__TC; \
printf("\t Mean = %s\n\t Mode = %s\n\tMedian = %s\n\t Range = %s", \
__TU(Mean(__TA),__TE),__TU(Mode(__TA),__TE),__TU(Median(__TA),__TE),__TU(Range(__TA),__TE))) \
for(;(__TS=GetTickCount(),__TC);__TA[--__TC]=GetTickCount()-__TS) \
for(new __TI;__TI!=__TE;++__TI)
#define CSV_TIMING(%0) \
for(new __TA[TIMING_ITERATIONS],__TC=sizeof __TA,__TE=_:__TX:__TY|||%0|||,__TS=printf("Timing \"%s\"...",%0),File:__TF=fopen(%0".csv",io_append);__TF&&__TC;\
va_fprintf(__TF,"%d,%d,%s,%s,%s,%s\n",gettime(),__TE,__TU(Mean(__TA),__TE),__TU(Mode(__TA),__TE),__TU(Median(__TA),__TE),__TU(Range(__TA),__TE)),fclose(__TF))\
for(;(__TS=GetTickCount(),__TC);__TA[--__TC]=GetTickCount()-__TS)\
for(new __TI;__TI!=__TE;++__TI)
stock
YSI_gPlayerIP[MAX_PLAYERS + 1] = {-1, ...};
public OnPlayerConnect(playerid)
{
new
ip[16];
GetPlayerIp(playerid, ip, sizeof (ip)),
YSI_gPlayerIP[playerid] = IPToInt(ip);
#if defined _y_utils_OnPlayerConnect
_y_utils_OnPlayerConnect(playerid);
#endif
return 1;
}
#if defined _ALS_OnPlayerConnect
#undef OnPlayerConnect
#else
#define _ALS_OnPlayerConnect
#endif
#define OnPlayerConnect _y_utils_OnPlayerConnect
#if defined _y_utils_OnPlayerConnect
forward _y_utils_OnPlayerConnect(playerid);
#endif
public OnPlayerDisconnect(playerid, reason)
{
YSI_gPlayerIP[playerid] = -1;
#if defined _y_utils_OnPlayerDisconnect
_y_utils_OnPlayerDisconnect(playerid, reason);
#endif
return 1;
}
#if defined _ALS_OnPlayerDisconnect
#undef OnPlayerDisconnect
#else
#define _ALS_OnPlayerDisconnect
#endif
#define OnPlayerDisconnect _y_utils_OnPlayerDisconnect
#if defined _y_utils_OnPlayerDisconnect
forward _y_utils_OnPlayerDisconnect(playerid, reason);
#endif
/*-------------------------------------------------------------------------*//**
* The unsigned number to compare.
* The upper limit.
*
* An unsigned comparison between the two values.
*
*//*------------------------------------------------------------------------**/
P:D(bool:UCMP(value, upper));
#define UCMP(%0,%1) IS_IN_RANGE(%0,0,(%1))
/*-------------------------------------------------------------------------*//**
* The player to check.
*
* Is this a valid playerid (NOT, is the player connected).
*
*//*------------------------------------------------------------------------**/
P:D(bool:VALID_PLAYERID(playerid));
#define VALID_PLAYERID(%0) UCMP((%0), MAX_PLAYERS)
/*-------------------------------------------------------------------------*//**
* The number to compare.
* The lower limit.
* The upper limit.
*
* Is the value in the given range.
*
*
* Equivalent to:
*
* (%1) <= (%0) < (%2)
*
*
*//*------------------------------------------------------------------------**/
P:D(bool:IS_IN_RANGE(value, lower, upper));
#define IS_IN_RANGE(%0,%1,%2) (((%0)-((%1)+cellmin))<((%2)-((%1)+cellmin)))
/*-------------------------------------------------------------------------*//**
* The number to compare.
* The lower limit.
* The upper limit.
*
* Is the value outside the given range.
*
*
* Equivalent to:
*
* (%1) <= (%0) < (%2)
*
*
*//*------------------------------------------------------------------------**/
P:D(bool:NOT_IN_RANGE(value, lower, upper));
#define NOT_IN_RANGE(%0,%1,%2) (((%0)-((%1)+cellmin))>=((%2)-((%1)+cellmin)))
/*-------------------------------------------------------------------------*//**
* The top of the division.
* The bottom of the division.
*
* (numerator / denominator) rounded up.
*
*
* Normal integer division ALWAYS rounds down - this always rounds up.
*
*//*------------------------------------------------------------------------**/
P:D(ceildiv(numerator, denominator));
#define ceildiv(%0,%1) (((%0)-1)/(%1)+1)
/*-------------------------------------------------------------------------*//**
*
* floordiv(numerator, denominator);
*
* The top of the division.
* The bottom of the division.
*
* (numerator / denominator) rounded down.
*
*
* Normal integer division ALWAYS rounds down - this also always rounds down,
* making it a little pointless, but also more explicit in function.
*
*//*------------------------------------------------------------------------**/
P:D(floordiv(numerator, denominator));
#define floordiv(%0,%1) ((%0)/(%1))
/*-------------------------------------------------------------------------*//**
* String to check if is null.
*//*------------------------------------------------------------------------**/
P:D(bool:isnull(str[]));
#define isnull(%0) ((%0[(%0[0])=='\1'])=='\0')
/*-------------------------------------------------------------------------*//**
* Value to check if is odd.
*//*------------------------------------------------------------------------**/
P:D(bool:isodd(value));
#define isodd(%1) ((%1) & 1)
/*-------------------------------------------------------------------------*//**
* Value to check if is even.
*//*------------------------------------------------------------------------**/
P:D(bool:iseven(value));
#define iseven(%1) (!isodd(%1))
/*-------------------------------------------------------------------------*//**
* Destination string.
* Source string.
* (Implicit) maximum length of the destination.
*//*------------------------------------------------------------------------**/
P:D(strcpy(dest[], const src[], len = sizeof (dest)));
#define strcpy(%0,%1) strcat((%0[0] = '\0', %0), %1)
/*-------------------------------------------------------------------------*//**
* String to convert.
* How much of the string to convert.
*//*------------------------------------------------------------------------**/
stock StrToLower(str[], len = sizeof (str))
{
new
i = -1,
ch;
while ((ch = str[++i]) && len--)
str[i] = tolower(ch);
}
/*-------------------------------------------------------------------------*//**
* String to convert.
* How much of the string to convert.
*//*------------------------------------------------------------------------**/
stock StrToUpper(str[], len = sizeof (str))
{
new
i = -1,
ch;
while ((ch = str[++i]) && len--)
str[i] = toupper(ch);
}
/*-------------------------------------------------------------------------*//**
* Lower bound, or upper bound when only parameter.
* Upper bound.
*
* Generate a random float between the given numbers (min <= n < max).
* Default minimum is 0 (changes the parameter order).
*
*//*------------------------------------------------------------------------**/
stock Random(min, max = cellmin)
{
if (max == cellmin)
{
if (min < 0)
return -random(-min);
return random(min);
}
if (max < min)
return random(min - max) + max;
return random(max - min) + min;
}
/*-------------------------------------------------------------------------*//**
* Lower bound, or upper bound when only parameter.
* Upper bound.
* How small to make the differences
*
* Generate a random float between the given numbers (min <= n < max). Default
* minimum is 0.0 (changes the parameter order).
*
*//*------------------------------------------------------------------------**/
stock Float:RandomFloat(Float:min, Float:max = FLOAT_NAN, dp = 2)
{
new
Float:mul = floatpower(10.0, float(dp));
switch (dp)
{
case 0: mul = 1.0;
case 1: mul = 10.0;
case 2: mul = 100.0;
case 3: mul = 1000.0;
default: mul = floatpower(10.0, float(dp));
}
if (max != max)
{
if (min < 0.0)
return -(float(random(floatround(-min * mul))) / mul);
return float(random(floatround(min * mul))) / mul;
}
// Parameters are the wrong way around - do it anyway.
if (max < min)
return float(random(floatround(min * mul - max * mul))) / mul + max;
// NOT a silly check - "IsNaN".
return float(random(floatround(max * mul - min * mul))) / mul + min;
}
/*-------------------------------------------------------------------------*//**
* The string to remove whitespace from the end of.
*
* Updated from old versions, should be more efficient
*
*//*------------------------------------------------------------------------**/
stock StripNL(str[])
{
P:7("StripNL called: \"%s\"", str);
new
i = strlen(str);
while (i-- && str[i] <= ' ') str[i] = '\0';
}
#define StripR StripNL
/*-------------------------------------------------------------------------*//**
* The string to remove whitespace from the start of.
*//*------------------------------------------------------------------------**/
stock StripL(str[])
{
P:7("StripL called: \"%s\"", str);
new
len = strlen(str),
i = 0;
while ('\0' < str[i] <= ' ') ++i;
if (i) memcpy(str[0], str[i], 0, (len - i) * 4, len);
}
/*-------------------------------------------------------------------------*//**
* The string to remove whitespace from the start and end of.
*//*------------------------------------------------------------------------**/
stock Strip(str[])
{
P:7("Strip called: \"%s\"", str);
new
len = strlen(str),
i = len;
while (i-- && str[i] <= ' ') str[i] = '\0';
i = 0;
while ('\0' < str[i] <= ' ') ++i;
if (i) memcpy(str[0], str[i], 0, (len - i) * 4, len);
}
/*-------------------------------------------------------------------------*//**
* String to check.
* Postion to start from.
*
* Checks if the current point in a line is the end of non-whitespace data.
*
*//*------------------------------------------------------------------------**/
stock endofline(line[], pos)
{
P:7("endofline called: \"%s\", %i", line, pos);
if (NOT_IN_RANGE(pos, 0, strlen(line))) return 0;
//if (pos < 0 || pos > strlen(line)) return 0;
while (line[pos]) if (line[pos++] > ' ') return 0;
return 1;
}
/*-------------------------------------------------------------------------*//**
* The character to find.
* The string to find it in.
* The offset to start from.
*
* Fail - -1, Success - pos
*
*//*------------------------------------------------------------------------**/
stock chrfind(needle, haystack[], start = 0)
{
P:7("chrfind called: %c, \"%s\", %i", needle, haystack, start);
if (start < 0)
{
start = 0;
}
else if (start > strlen(haystack)) return -1;
while (haystack[start]) if (haystack[start++] == needle) return start - 1;
return -1;
}
/*-------------------------------------------------------------------------*//**
* The character to find.
* The string to find it in.
* The offset to start from.
*
* Fail - -1, Success - pos
*
*
* Like , but with no upper-bounds check on
* .
*
*//*------------------------------------------------------------------------**/
stock chrfindp(needle, haystack[], start = 0)
{
P:7("chrfind called: %c, \"%s\", %i", needle, haystack, start);
if (start < 0)
{
start = 0;
}
while (haystack{start}) if (haystack{start++} == needle) return start - 1;
return -1;
}
/*-------------------------------------------------------------------------*//**
* Dot notation IP to convert to an integer.
*//*------------------------------------------------------------------------**/
stock IPToInt(ip[])
{
new
ipv = strval(ip) << 24,
pos = 0;
while (pos < 15 && ip[pos++] != '.') {}
ipv += strval(ip[pos]) << 16;
while (pos < 15 && ip[pos++] != '.') {}
ipv += strval(ip[pos]) << 8;
while (pos < 15 && ip[pos++] != '.') {}
return ipv + strval(ip[pos]);
}
/*-------------------------------------------------------------------------*//**
* the string to hash.
*
* the bernstein hash of the input string
*
*
* This is a 32bit hash system so is not very secure, however we're only
* using this as a string enumerator to uniquely identify strings easilly
* and allow for a binary search of strings based on the hash of their name.
* crc32, then jenkins were originally used however this is far faster, if a
* little collision prone, but we're checking the strings manually anyway.
* This doesn't matter as it would be done regardless of hash method, so this
* doesn't need to be accounted for. Speed is all that matters with at
* least a bit of non collision (the number of strings we're dealing with,
* this should have none-few collisions).
*
* I modified it slightly from the original code pasted by aru, to code
* closer to the code
* and to work with PAWN (and shaved 0.2�s off the time for one call :D).
*
* Uber reduced version (just for fun):
* b(s[]){new h=-1,i,j;while((j=s[i++]))h=h*33+j;return h;}
*
* Update: Contrary to what I said above this is also used to identify colour
* strings for the updated text system involving file based styling and this
* is not checked for collisions as it's unimportant. But this doesn't affect
* the function at all, I just mentioned it here for "interest".
*
* Rewritten in self-generating assembly.
*
*//*------------------------------------------------------------------------**/
stock bernstein(string[] /* 12 */)
{
// Only shown the very first time this function is run.
P:7("bernstein called: \"%s\"", string);
#pragma unused string
new
base,
ctx[AsmContext];
// Get this function.
#emit CONST.pri bernstein
#emit LOAD.alt AMX_HEADER_COD
#emit ADD
#emit STOR.S.pri base
AsmInitPtr(ctx, base, 128), // Don't need any more than that.
// Setup.
@emit PROC
@emit CONST.alt -1
@emit PUSH.S 12 // string
@emit LREF.S.pri 12 // string
@emit JZER.rel 12 * 4 // bernstein_end
// bernstein_loop:
@emit XCHG
@emit SMUL.C 33
@emit ADD
@emit MOVE.alt
// Update the next pointer.
@emit POP.pri
@emit ADD.C 4
@emit PUSH.pri
// Load the data for the current pointer.
@emit LOAD.I
@emit JNZ.rel -(12 * 4) // bernstein_loop
// bernstein_end:
@emit MOVE.pri
@emit STACK 4
@emit RETN
// Now actually CALL the written function.
#emit LCTRL 5
#emit SCTRL 4
#emit CONST.pri bernstein
#emit ADD.C 4
#emit SCTRL 6
return 0; // Make the compiler happy.
}
/*-------------------------------------------------------------------------*//**
* String to check.
*
* true/false.
*
*//*------------------------------------------------------------------------**/
stock ishex(str[])
{
P:7("ishex called: \"%s\"", str);
new
i,
cur;
if (str[0] == '0' && (str[1] | 0x20) == 'x') i = 2;
do
{
cur = str[i++];
}
while (IS_IN_RANGE(cur, '0', '9' + 1) || IS_IN_RANGE(cur | 0x20, 'a', 'f' + 1));
//while (('0' <= cur <= '9') || ('a' <= (cur | 0x20) <= 'f'));
return !cur; // Valid if this is the end of the string.
}
/*-------------------------------------------------------------------------*//**
* String to unpack
*
* unpacked string
*
*
* Mainly used for debugging.
*
*//*------------------------------------------------------------------------**/
stock unpack(const str[])
{
P:7("unpack called: \"%s\"", str);
new
ret[YSI_MAX_STRING] = {0};
if (strlen(str) <= YSI_MAX_STRING)
{
strunpack(ret, str);
}
return ret;
}
/*-------------------------------------------------------------------------*//**
* Player to get IP of.
*
* IP as a 32bit int.
*
*//*------------------------------------------------------------------------**/
// Cunning macro only uses "%0" once, yet is still safe.
P:D(GetIP(playerid));
#define GetIP(%0) (YSI_gPlayerIP[min((%0) + cellmin, MAX_PLAYERS + cellmin) - cellmin])
/*-------------------------------------------------------------------------*//**
* Index of the string in the parameters.
*
* string
*
*
* Is passed the result of getarg, which will be the address of a string (in
* theory) and uses that for DMA to get the string.
*
*//*------------------------------------------------------------------------**/
#define getstring returnstringarg
#define GetString getstring
#define getstringarg va_getstring
#define GetStringArg va_getstring
#define ReturnStringArg returnstringarg
stock returnstringarg(idx)
{
static
scSize = YSI_MAX_STRING;
// Get the address of the previous function's stack. First get the index of
// the argument required.
#emit LOAD.S.pri idx
// Then convert that number to bytes from cells.
#emit SMUL.C 4
// Get the previous function's frame. Stored in variable 0 (in the current
// frame). Parameters are FRM+n+12, locals are FRM-n, previous frame is
// FRM+0, return address is FRM+4, parameter count is FRM+8. We could add
// checks that "idx * 4 < *(*(FRM + 0) + 8)", for the previous frame
// parameter count (in C pointer speak).
#emit LOAD.S.alt 0
// Add the frame pointer to the argument offset in bytes.
#emit ADD
// Add 12 to skip over the function header.
#emit ADD.C 12
// Load the address stored in the specified address.
#emit LOAD.I
// Push the length for "strcat".
#emit PUSH scSize
// Push the address we just determined was the source.
#emit PUSH.pri
// Load the address of the secret destination.
#emit LOAD.S.alt 16
// Blank the first cell so "strcat" behaves like "strcpy".
#emit CONST.pri 0
// Store the loaded number 0 to the loaded address.
#emit STOR.I
// Push the loaded address.
#emit PUSH.alt
// Push the number of parameters passed (in bytes) to the function.
#emit PUSH.C 12
// Call the function.
#emit SYSREQ.C strcat
// Restore the stack to its level before we called this native.
#emit STACK 16
// Return without more string copying.
#emit RETN
// Fake return to define the API.
new
ret[YSI_MAX_STRING];
return ret;
}
/*-------------------------------------------------------------------------*//**
* String format.
* Parameters.
*
* Formatted string.
*
*
* Just wraps `format` and returns a string instead.
*
* Has extra code to ensure that it works correct on the old compiler.
*
*//*------------------------------------------------------------------------**/
stock va_return(const fmat[], GLOBAL_TAG_TYPES:...)
{
#pragma unused fmat
static
sFrm,
sRet,
sCnt,
scSize = YSI_MAX_STRING;
// Store the function preamble.
#emit POP.pri
#emit STOR.pri sFrm
#emit STACK 0 // Load the stack pointer for later.
#emit POP.pri
#emit STOR.pri sRet
#emit POP.pri
#emit ADD.C 8
#emit STOR.pri sCnt
// Put the secret return parameter on the stack again to format in to.
#emit PUSH scSize
#emit ADD
#emit LOAD.I
#emit PUSH.pri
// Call the native.
#emit PUSH sCnt
#emit SYSREQ.C format
// Return directly. This will clean up our entire stack, even the extra
// parameters we put on it. The parameter count is already there!
#emit PUSH sRet
#emit PUSH sFrm
#emit RETN
// Fake return to define the API.
new
ret[YSI_MAX_STRING];
return ret;
}
/*-------------------------------------------------------------------------*//**
* String to check
*
* Checks if a given string is numeric.
*
*//*------------------------------------------------------------------------**/
stock isnumeric(str[])
{
P:7("isnumeric called: \"%s\"", str);
new
i;
while (IS_IN_RANGE(str[i], '0', '9' + 1)) ++i;
//while ((ch = str[i++])) if (!('0' <= ch <= '9')) return 0;
return !str[i];
}
#if !defined _inc_sscanf || 1
/*---------------------------------------------------------------------*//**
*
* String to convert to a number.
*
* value of the passed hex string.
*
*
* Now stops on invalid characters.
*
*//*--------------------------------------------------------------------**/
stock hexstr(string[])
{
new
ret,
val,
i;
if (string[0] == '0' && string[1] | 0x20 == 'x') i = 2;
for ( ; ; )
{
switch ((val = string[i++]))
{
case '0' .. '9':
{
val -= '0';
}
case 'a' .. 'f':
{
val -= 'a' - 10;
}
case 'A' .. 'F':
{
val -= 'A' - 10;
}
default: break;
}
ret = ret << 4 | val;
}
return ret;
}
/*---------------------------------------------------------------------*//**
*
* String to try convert to a boolean.
*
* bool: passed boolean.
*
*
* This can take a number of ways of representing booleans - 0, false and
* nothing there. Anything not one of those things (false is not case
* sensitive) is assumed true.
*
*//*--------------------------------------------------------------------**/
stock bool:boolstr(string[])
{
// Hooray for De Morgan's rules!
return string[0] && string[0] != '0' && strcmp(string, "false", true);
}
/*---------------------------------------------------------------------*//**
*
* String to try convert to a boolean.
*
* bool: passed boolean.
*
*
* This takes a value in 0110101 (boolean) format and returns it as a
* regular value.
*
*//*--------------------------------------------------------------------**/
stock binstr(string[])
{
new
pos = 0;
switch (string[0])
{
case '0':
{
if (string[1] | 0x20 == 'b')
{
pos = 2;
}
}
case '1':
{
}
default:
{
return 0;
}
}
new
value = 0;
for ( ; ; )
{
switch (string[pos++])
{
case '0':
{
value <<= 1;
}
case '1':
{
value = (value << 1) | 1;
}
default:
{
break;
}
}
}
return value;
}
#endif
/*-------------------------------------------------------------------------*//**
*
* rawMemcpy
*
* Destination address.
* Source data.
* Number of bytes to copy.
*
* Like memcpy, but takes addresses instead of arrays. Also far less secure.
*
*//*------------------------------------------------------------------------**/
stock rawMemcpy(dest, src, bytes)
{
// Don't use "MOVS" as these blocks might overlap.
#emit PUSH.S bytes
#emit PUSH.S bytes
#emit PUSH.C 0
#emit PUSH.S src
#emit PUSH.S dest
#emit PUSH.C 20
#emit SYSREQ.C memcpy
#emit STACK 24
#emit RETN
return 0;
}
/*-------------------------------------------------------------------------*//**
* Array or address to set to a value.
* What to set the cells to.
* Number of cells to fill.
*
* Based on code by Slice:
*
*
*
* Modified to use binary flags instead of a loop.
*
* "memset" takes an array, the size of the array, and a value to fill it with
* and sets the whole array to that value.
*
* "rawmemset" is similar, but takes an AMX data segment address instead and
* the size is in bytes, not cells. However, the size must still be a multiple
* of 4.
*
*//*------------------------------------------------------------------------**/
stock memset(arr[], val = 0, size = sizeof (arr))
{
new
addr;
#emit LOAD.S.pri arr
#emit STOR.S.pri addr
// Convert the size from cells to bytes.
return rawMemset(addr, val, size * 4);
}
/*-------------------------------------------------------------------------*//**
* Array or address to set to a value.
* What to set the cells to.
* Number of cells to fill.
*
* Based on code by Slice:
*
*
*
* Modified to use binary flags instead of a loop.
*
* "memset" takes an array, the size of the array, and a value to fill it with
* and sets the whole array to that value.
*
* "rawmemset" is similar, but takes an AMX data segment address instead and
* the size is in bytes, not cells. However, the size must still be a multiple
* of 4.
*
*//*------------------------------------------------------------------------**/
stock rawMemset(iAddress /* 12 */, iValue /* 16 */, iSize /* 20 */)
{
// They are really, trust me!
#pragma unused iAddress, iSize, iValue
// The first time this is called it rewrites itself. Any other times it is
// called it just uses the new code. This is like doing:
//
// static
// bInitialised = false;
// if (!bInitialised)
// {
// // Do something
// bInitialised = true;
// }
// // Do rest.
//
// But better (though FAR more complex).
// There is NO checking here that we don't write the function bigger than
// the space available, or even that we don't overwrite "CIP", which would
// be bad. The only way to make sure that doesn't happen is write a little
// with a lot of code!
new
base,
ctx[AsmContext];
// Get this function.
#emit CONST.pri rawMemset
#emit LOAD.alt AMX_HEADER_COD
#emit ADD
#emit STOR.S.pri base
AsmInitPtr(ctx, base, 80), // Don't need any more than that.
// Frankly by this point we have probably already written more code than
// will be generated!
@emit PROC
@emit LOAD.S.pri 20
@emit CONST.alt 0xFFFFFFC
@emit AND
@emit STOR.pri (ctx[AsmContext_buffer] + 13 * 4)
// The documentation says "PRI" should be a pointer, but that's not true!
@emit LOAD.S.alt 12
@emit LOAD.S.pri 16
@emit FILL 0
// Return the bytes filled.
@emit LOAD.pri (ctx[AsmContext_buffer] + 13 * 4)
@emit RETN
// Do the second version.
#emit CONST.pri memset
#emit LOAD.alt AMX_HEADER_COD
#emit ADD
#emit STOR.S.pri base
AsmInitPtr(ctx, base, 80),
@emit PROC
@emit LOAD.S.pri 20
@emit SHL.C.pri 2
@emit STOR.pri (ctx[AsmContext_buffer] + 12 * 4)
@emit LOAD.S.alt 12
@emit LOAD.S.pri 16
@emit FILL 0
// Return the bytes filled.
@emit LOAD.pri (ctx[AsmContext_buffer] + 12 * 4)
@emit RETN
// Call this function again (the new version), but don't let the compiler
// know... First clear the stack.
#emit LCTRL 5
#emit SCTRL 4
#emit CONST.pri rawMemset
#emit ADD.C 4
#emit SCTRL 6
// Never hit because of going to an earlier "RETN".
return 0; //memset("", 0, 0);
}
/*-------------------------------------------------------------------------*//**
*
* ReturnPlayerName
*
* Player whose name you want to get.
*
* Now uses a global array to avoid repeated function calls. Actually doesn't
* because that causes issues with multiple scripts.
*
*//*------------------------------------------------------------------------**/
stock ReturnPlayerName(playerid)
{
new
str[MAX_PLAYER_NAME];
GetPlayerName(playerid, str, sizeof (str));
return str;
}
/*-------------------------------------------------------------------------*//**
*
* ftouch(filename);
*
* The file to "touch".
*
* 0 - File already exists.
* 1 - File was created.
* -1 - File was not created.
*
*
* This "touches" a file in the Unix sense of creating it but not opening or
* editing it in any way.
*
*//*------------------------------------------------------------------------**/
stock ftouch(const filename[])
{
if (fexist(filename))
{
return 0;
}
else
{
new
File:f = fopen(filename, io_write);
if (f)
{
fclose(f);
return 1;
}
else
{
return -1;
}
}
}
/*-------------------------------------------------------------------------*//**
*
* InterpolateColour(startcolor, endcolor, value, maxvalue, minvalue = 0);
*
* One of the two colours.
* The other of the two colours.
* The interpolation value between the endpoints.
* One of the two numbers.
* The other of the two numbers.
*
* This function takes two endpoint values (minvalue and maxvalue, with
* minvalue defaulting to 0), along with a third value (value) whose distance
* between the two endpoints is calculated (as a percentage). This percentage
* value is then applied to the two colours given to find a third colour at
* some point between those two colours.
*
* For example, if the endpoints given are "0" and "10", and the value given is
* "3", then that is "30%" of the way between the two endpoints. We therefore
* want to find a colour that is 30% of the way between the two given colours.
*
*//*------------------------------------------------------------------------**/
// "Interpolation" is the technical name for what you are doing here.
#define InterpolateColor InterpolateColour
stock InterpolateColour(startcolor, endcolor, value, maxvalue, minvalue = 0)
{
if (value >= maxvalue) return endcolor;
if (value <= minvalue) return startcolor;
static r, g, b, a;
new
time = maxvalue - minvalue,
stage = value - minvalue;
return
// Step 1: Get the starting colour components.
r = startcolor >>> 24 ,
g = startcolor >>> 16 & 0xFF,
b = startcolor >>> 8 & 0xFF,
a = startcolor & 0xFF,
// Step 2: Interpolate between the end points, and add to the start.
r += ((endcolor >>> 24 ) - r) * stage / time,
g += ((endcolor >>> 16 & 0xFF) - g) * stage / time,
b += ((endcolor >>> 8 & 0xFF) - b) * stage / time,
a += ((endcolor & 0xFF) - a) * stage / time,
// Step 3: Combine the individual components.
(r << 24) | ((g & 0xFF) << 16) | ((b & 0xFF) << 8) | (a & 0xFF);
}
/*-------------------------------------------------------------------------*//**
* The string to skip over part of.
* The start of the whitespace.
*
* The end of the whitespace.
*
*
* Doesn't skip over NULL terminators.
*
*//*------------------------------------------------------------------------**/
stock SkipWhitespace(const str[], pos)
{
while (IS_IN_RANGE(str[pos], '\0' + 1, ' ' + 1)) ++pos;
//while ('\0' < str[pos] <= ' ') ++pos;
return pos;
}
/*-------------------------------------------------------------------------*//**
* The string to trim.
* Start of the substring.
* End of the substring.
*
* Modifies "start" and "end" to be tight on text in "str".
*
*//*------------------------------------------------------------------------**/
stock Trim(const str[], &start, &end)
{
while (IS_IN_RANGE(str[start], '\0' + 1, ' ' + 1)) ++start;
//while ('\0' < str[start] <= ' ') ++start;
if (str[start])
{
while (end-- > start && str[end] <= ' ') {}
++end;
}
else
{
end = start;
}
}
/*-------------------------------------------------------------------------*//**
* The array to sort.
* The size of the array.
*
* Sorts the array in place. Uses bubble sort because it is easy and fast for
* pre-sorted arrays (which the callers are likely to be).
*
*//*------------------------------------------------------------------------**/
//#define ftell(%0) fseek((%0), 0, seek_current)
static stock Utils_PreSort(arr[], num = sizeof (arr))
{
// Very simple bubble sort (fast for pre-sorted arrays).
new
bool:sort;
do
{
sort = false;
for (new j = 1, temp; j != num; ++j)
{
if ((temp = arr[j]) < arr[j - 1])
{
arr[j] = arr[j - 1],
arr[j - 1] = temp,
sort = true;
}
}
}
while (sort);
}
/*-------------------------------------------------------------------------*//**
* The array whose values need summing.
* The size of the array.
*
* All the values in the array added together.
*
*//*------------------------------------------------------------------------**/
stock Sum(const arr[], num = sizeof (arr))
{
new
tot;
while (num) tot += arr[--num];
return tot;
}
/*-------------------------------------------------------------------------*//**
* The array whose values need averaging.
* The size of the array.
*
* The mathematical mean value of the array.
*
*//*------------------------------------------------------------------------**/
stock Mean(const arr[], num = sizeof (arr))
{
return Sum(arr, num) / num;
}
/*-------------------------------------------------------------------------*//**
* The array whose values need averaging.
* The size of the array.
*
* The mathematical modal value of the array.
*
*//*------------------------------------------------------------------------**/
stock Mode(arr[], num = sizeof (arr))
{
Utils_PreSort(arr, num);
new
ret,
count = 0,
cn,
cc;
for (new i = 0; i != num; ++i)
{
if (arr[i] == cn) ++cc;
else
{
if (cc > count) count = cc, ret = cn;
cc = 1, cn = arr[i];
}
}
if (cc > count) return cn;
else return ret;
}
/*-------------------------------------------------------------------------*//**
* The array whose values need averaging.
* The size of the array.
*
* The mathematical median value of the array.
*
*//*------------------------------------------------------------------------**/
stock Median(arr[], num = sizeof (arr))
{
Utils_PreSort(arr, num);
new
idx = num >>> 1;
if (num & 1) return arr[idx];
else return (arr[idx] + arr[idx - 1]) / 2;
}
/*-------------------------------------------------------------------------*//**
* The array whose values need averaging.
* The size of the array.
*
* The mathematical range of the values of the array.
*
*//*------------------------------------------------------------------------**/
stock Range(arr[], num = sizeof (arr))
{
new
min = cellmax,
max = cellmin,
cur;
while (num)
{
cur = arr[--num];
if (cur < min)
min = cur;
if (cur > max)
max = cur;
}
return max - min;
}
#include "..\YSI_Coding\y_va"
#include "..\YSI_Internal\y_shortfunc"
#if defined YSI_TESTS
#include "y_testing"
#include "y_utils/tests"
#endif