Posts posted by Dakar
-
-
Name: Sockets Async
Autor: Destro
Version: 1.3b
Description: Unlike the other modules, this one uses asynchronous sockets (non-blocking), which means that there is no danger of the server crashing while waiting for a response.
Installation:
- LINUX: Move the .so module to addons/amxmodx/modules
- WIN: Move the .dll module to addons/amxmodx/modules
In case you do not recognize:
- Add the name sockets_async_amxx_i386.so (if you are win) or sockets_async_amxx.dll (if you are win) in addons/amxmodx/configs/modules.ini
-
1
-
-
This is probably the first part or more will be added periodically.
charsmax access clamp arrayset get_weaponid - get_weaponname get_playersnum get_players get_gametime
charsmax:
This is very useful to avoid making errors when passing parameters, and to change the size of an array without having to change it everywhere in the code (for example a global array that is used in many sections). Above all it is useful to avoid making errors.Spoilernew MyArray[32] copy(MyArray, charsmax(MyArray), "Charsmax xd") // Note: charsmax is the same as sizeof -1, because if we have 32 cells, // we will be able to use 31 cells to store data since the other one must contain ^0
access:
Many times has_flag | get_user_flags is used in order to know if a user, for example, has the flag "ADMIN_BAN", being that with a simple function you can do the same.if(access(id, ADMIN_BAN)) // has the flag to ban
clamp;
This function is used to set a variable between a maximum and a minimum. It means that if the variable that we pass exceeds the maximum, it will be set to the maximum, and if it is less than the minimum, it will be set to the minimum.
Very useful for experience mods where there is a maximum number of levels or experience.clamp(g_exp[id], 0, g_MAXExp)
arrayset:
This is definitely one of the most useful functions that I don't see anyone using, it is very useful when for example a round starts and we want to set all the variables of an array to 0 or false, for example, suppose we have an array(bool) of 33 cells that indicates if a player jumped in that round, every time a new round starts we should set all the cells to false, but, why make a loop if we can do it with this function?arrayset(g_PlayerJumped, false, charsmax(g_PlayerJumped))
get_weaponid - get_weaponname
This function is used to go from "weapon_deagle" to CSW_DEAGLE and the reverse (or any other weapon...)Spoilernew deagle = get_weaponid("weapon_deagle") // CSW_DEAGLE new name[20] get_weaponname(CSW_DEAGLE, name, charsmax(name)) //weapon_deagle // NOTE: If we want to print the name, and we don't want "weapon_" to come out. // we can print from cell 7, like this: server_print("Deagle name: %s", name[7])
Example:
Spoiler#include <amxmodx> public plugin_init() { register_clcmd("say /weapon", "WeaponCmd") } public WeaponCmd(id) { if(!is_user_alive(id)) return PLUGIN_HANDLED new name[20] new weapon = get_user_weapon(id) get_weaponname(weapon, name, charsmax(name)) client_print(id, print_chat, "You carry weapon %s", name[7]) }
get_playersnum
This function returns the number of online players on the server.new players = get_playersnum()
It can be useful to execute certain functions when there are more or less than a certain number of players.
get_players:
The previous function was more than everything so that you don't get confused with this one or don't use it when it is not necessary. With this we can collect certain players and access them (know what they are) with flags and conditions, there is no need to do a for with the conditions because there is already a function that does that, for example if we want to grab all live players that are CT:new players[32], count get_players(players, count, "ae", "CT")
http://www.amxmodx.org/api/amxmodx/get_players
get_gametime:
Sometimes the set_task function and variables are used unnecessarily, the get_gametime function can be very useful for certain cases. This function returns the number of seconds passed since the start of the map (in float type). Let's see an example blocking a command (allowing to use it every 10 seconds)Spoiler#include <amxmodx>. new Float:g_LastRun public plugin_init() { register_clcmd("say /command", "Command") } public Command(id) { if(g_LastRun+20.0>get_gametime()) return PLUGIN_CONTINUE client_print(id, print_chat, "Hello!") return PLUGIN_HANDLED }
-
1
-
-
register_clcmd: Register a command for the client
register_srvcmd: Register a command for the server
register_concmd: Register a command for both client and serverParameters: (the first two you already know)
- flags: is only to know who to show it to in amx_help(get_concmd), it does not restrict access by itself.
- info: the information that will be shown in amx_help(get_concmd).
- FlagManage: if the cmdaccess.ini file is used to change the access.
In the handler (i.e. the function that is called when using the command) the three have the same parameters:(PlayerIndex, ComandLevel, ComandIndex) --> (id, level, cid)
Commands for admin/rcon:
If you are going to use it only for an admin use register_clcmd.
If you want to register a command that can be used by an admin and the server console or rcon use register_concmd.Check access:
For register_concmd:public command_console(id, level, cid) { // cmd_access(id, level, cid, num, bool:accesssilent = false) if(!cmd_access(id, level, cid, 1)) { return PLUGIN_HANDLED } // Only use console_print(...) to report/print messages }
For register_clcmd:
public client_command(id, level, cid) { /*The one I usually use*/ if(!(get_user_flags(id)&level)) { return PLUGIN_HANDLED } /* If you also want to check the arguments you can use: if(!cmd_access(id, level, cid, 1)) { return PLUGIN_HANDLED } This is also valid if(!(get_user_flags(id) & ADMIN_BAN)) // The flag of your choice { return PLUGIN_HANDLED } */ }
-
1
-
-
SET_TASK
native set_task(Float:time, const function[], id = 0, const any:parameter[] = "", len = 0, const flags[] = "", repeat = 0);
Float:time (NOT optional)
Time interval (in seconds [Float] ) that will have to pass for the function to be called [minimum value: 0.1 sec].function[] (NOT optional)
String containing the name of the function to be executed after the assigned time interval.
id (Optional)
Index (index , id , whatever you want to call it) unique to the Task.parameter (Optional)
Series of additional arguments to be transmitted to the function.len (Optional)
size of the array with the additional parameter.flags (Optional)
Optional flags to use:
"a" - Repeat the task a fixed number of times.
"b" - Infinite loop until the task is stopped.
"c" - The function will be called at the beginning of the map.
"d" - The function will be called just before the end of the map.repeat (Optional)
If flag "a" is set, the task will repeat as many times as set in this parameter.
DescriptionCalls a function when a specified time has elapsed.
Return
This function has no return value.
Error
If an invalid function is called it will give an error.
TASK ID
as I said above this parameter is optional, it depends on how the task is going to be used...
If we use several tasks with id's and then we are going to search or delete a specific one (task_exists() or remove_task() ) it is better to use id's of different value for each one, so you can identify the task without problems...
let's see the problem to which I refer:
set_task(1.0, "TaskFunction1", id); // use the same id as player's id set_task(2.0, "TaskFunction2", id); // use the same id as the player's id // suppose the player's id is "20". if(task_exists(id)) // does task with id 20 exist? { // Yes , there are 2 , but I can't tell which one.... } /////////////////// remove_task(id) // I remove both tasks since both have the id "20".
Well, I hope that the problem I am referring to is understood.
For cases like this it is better to use unique ID's for each task, so what I showed above does not happen.Let's see how the different task id's work.
#include <amxmodx>. #define TASK_C 3323 #define ID_TASK (taskid - TASK_C) new g_iSeconds[33] new g_iMinutes[33] new g_iHours[33] new g_HudC public plugin_init() { register_clcmd( "say /counter", "clcmd_counter" ) g_HudC = CreateHudSyncObj() } client_disconnect(id) remove_task(id+TASK_C) public clcmd_counter(id) { if( !task_exists(id+TASK_C) ) { g_iSeconds[id] = 0 g_iMinutes[id] = 0 g_iHours[id] = 0 set_task( 1.0, "counter", id+TASK_C, _, _, "b") } /*Let's say my id is 10 for example.... TASK_C is 3323 which above is defined , so.... id+TASK_C = 10+3323 --> 3333 */ return PLUGIN_HANDLED } public counter(taskid) { /*the parameter of the function above is called taskid taskid is equal to the count that was done before --> id+TASK_C which resulted in 3333 Here below is called ID_TASK which is the one defined above --> ID_TASK (taskid - TASK_C) Then we have all the numbers and the account ID_TASK (3333 - 3323) would be like this It ends up being ID_TASK = 10 (i.e. it gets the player id again). it would be like putting g_iSeconds[id]++ instead of g_iSeconds[ID_TASK]*/ g_iSeconds[ID_TASK]++ if(g_iSeconds[ID_TASK] > 60) { g_iMinutes[ID_TASK]++ g_iSeconds[ID_TASK] = 0 } if(g_iMinutes[ID_TASK] > 60) { g_iHours[ID_TASK]++ g_iMinutes[ID_TASK] = 0 g_iSeconds[ID_TASK] = 0 } set_hudmessage( 255,255,255, -1.0, 0.30, 0, 6.0, 1.0 ) ShowSyncHudMsg( ID_TASK, g_HudC, "Elapsed time: %d hour%s with %d minute%s and %d second%s",g_iHours[ID_TASK],(g_iHours[ID_TASK] == 1) ? "" : "s",g_iMinutes[ID_TASK],(g_iMinutes[ID_TASK] == 1) ? "" : "s",g_iSeconds[ID_TASK],(g_iSeconds[ID_TASK] == 1) ? "" : "s") } // As you can see this code only uses 1 task, so there is no need to use a specific id. // I just used this code because I already had it commented and it serves as an example!
Now we will be able to differentiate the task's
#define TASKID_1 1000 #define TASKID_2 2000 set_task(1.0, "TaskFunction1", id + TASKID_1); set_task(2.0, "TaskFunction2", id + TASKID_2); if(task_exists(id + TASKID_1)) { // task 1 existe } if(task_exists(id + TASKID_2)) { // task 2 existe }
Parameters
#include <amxmodx> public plugin_init() register_clcmd("say /parm", "PARM") public PARM (id) { new parm[5] parm[0] = id // in position 0 we will store the player's id parm[1] = 10 // at position 1 we will store the integer value 10 parm[2] = 20 // " " " " 2 " " " " " " " 20 parm[3] = 30 // " " " 3 " " " " " " " 30 parm[4] = 40 // " " " 4 " " " " " " " 40 set_task( 1.0, "parameters", _, parm, 5 ) /* we will read the task from right to left this "5" would be the parameter "len" that indicates the size of the array with the parameters that we will pass to the function to be called [ you could also put sizeof(parm) ]. "parm" is just the array containing the parameters to be passed to the function. we omit the id of the task because it will not be necessary the function "parameters" will be called after 1.0 second... Conclusion: This task will call after 1.0 second to the function "parameters" and will send the parameters the 5 data of the array parm[5] */ } public parametros(parm[]) // as we can see it is necessary to put brackets as we are receiving an array { set_hudmessage( 255,255,255, -1.0, 0.30, 0, 6.0, 12.0 ) show_hudmessage(parm[0], "parm[1]: %d^nparm[2]: %d^nparm[3]: %d^nparm[4]: %d",parm[1],parm[2],parm[3],parm[4]) // here we see that param[0] is used as index in the show_hudmessage since before calling this function it had been assigned the player id // in that position of the array // then the value of the other positions of the array is shown in the hud... }
Note:
/* There are 3 headers for functions called from the task */ { new params[2] params[0] = 1 params[1] = 2 set_task(1.0, "Task_function", 53, params, sizeof(params)) } // #1 - If only the task id is passed, the id comes as the only argument. public Task_function( TASK_INDEX ) { } // #2 - If only parameters are passed from the task, the parameters come as the only argument. public Task_function( Params[] ) { } // #3 - If parameters and id are passed from the task, the parameters are passed as the first argument and the id as the second argument. public Task_function( Params[] , TASK_INDEX ) { }
FLAGS
"a"
Spoiler#include <amxmodx>. new g_iCounter public plugin_init() { register_clcmd( "say /counter", "clcmd_counter" ) } public clcmd_counter( ) { if( !task_exists() ) // then I'll explain { g_iCounter = 600 // 10 minutes set_task( 1.0, "FlagA", _, _, _, _, "a", g_iCounter ) /* we will read the task from right to left g_iContador is in the parameter "repeat" and has a value of 600... the flag "a" is set which says that the task will repeat X times (in this case the 600 times of g_iCounter)... we omit parameters that we won't use... the "FlagA" function will be called every 1.0 seconds. Conclusion: After having called the "FlagA" function 600 times every 1.0 second, the task will not be repeated anymore. */ } return PLUGIN_HANDLED } public FlagA() { g_iCounter-- set_hudmessage( 255,255,255, -1.0, 0.30, 0, 6.0, 12.0 ) show_hudmessage( 0, "%02i:%02i", g_iCounter / 60, g_iCounter % 60 ) }
"b"
Spoiler#include <amxmodx> public plugin_init() register_clcmd("say /hud", "hudLTA") public hudLTA () set_task(1.0, "FlagB", _, _, _, _, "b") /* we will read the task from right to left we say that the task will repeat infinitely (flag "b")... we omit parameters that we won't use... the function "hud" will be called function will be called every 1.0 seconds... Conclusion: The function "FlagB" will be called indefinitely every 1.0 second.... */ public FlagB() { set_hudmessage(255, 255, 255, -1.0, 0.01, 0, 6.0, 1.1, 0.0, 0.0, 0.0, -1) show_hudmessage(0, "I am a permanent hud") }
"c"
Spoiler#include <amxmodx> public plugin_init() set_task(20.0, "FlagC", _, _, _, _, "c") /* we will read the task from right to left we say that the task will be executed at the "start of the map" since the "c" flag is set we omit parameters that we won't use... the function "FlagA" will be called after 20.0 seconds. Conclusion: The task will start counting the 20.0 seconds when it starts the map, after that time has passed the function "FlagC" will be executed. */ public FlagC(id) { set_hudmessage( 255,255,255, -1.0, 0.30, 0, 6.0, 12.0 ) show_hudmessage(0, "It has been 20sec since I started the map") }
"d"
Spoiler#include <amxmodx> public plugin_init() set_task(20.0, "FlagD", _, _, _, _, "d") /* we will read the task from right to left we say that the task will be executed "before the end of the map" since the flag "d" is set we omit parameters that we won't use... the function "FlagD" will be called 20.0 seconds before the end of the map. Conclusion: The task will execute the "FlagD" function 20 seconds before the end of the map (timeleft will be at 20 sec). ONCE ONLY */ public FlagD(id) { set_hudmessage( 255,255,255, -1.0, 0.30, 0, 6.0, 12.0 ) show_hudmessage(0, "Only 20 seconds left until the end of the map") }
TASK_EXIST
Spoilernative task_exists(id = 0, outside = 0);
How to use:
id (Optional)
id, index, index as you want to call it of the task to search foroutside (Optional)
Search for task's set by other plugins if this parameter is different from 0Description
Returns whether a task with the specified id exists.
Return
1 if the task has been found, 0 otherwise
set_task(1.0, "TaskFunction", id); // later we will see about the tasks, this is a simple example // this task is working if(task_exists(id)) // here it looks and asks if the task with index "id" exists { // if it exists it will do this }
REMOVE_TASK
Spoilernative remove_task(id = 0, outside = 0);
How to use:
id (Optional)
id, index, index as you want to call it of the task to search foroutside (Optional)
Will remove task's set by other plugins if this parameter is different from 0Description
Removes ALL tasks with the specified id.
Return
Number of removed tasks
// If the AMX version is greater than 1.8.3 client_disconnected(id) remove_task(id) // If AMX version is lower than 1.8.2 client_disconnect(id) remove_task(id) // Will remove all the tasks with that id value
-
1
-
-
The DHUD, can appear 8 times on screen at the same time, the first one that appeared disappears, leaving again 8 DHUDs. The good thing about this is that the DHUD on screen is bigger (only in some larger resolutions) and you can put 8, instead of 4 as is the normal Hud (by channels) and the bad thing is that it does not work with channels as is the common Hud that we all know.
Note:
In AMX versions higher than 1.8.3 it is not necessary to include <dhudmessage>, but if you have a lower version you must include it, you can find it in:
https://forums.alliedmods.net/showthread.php?t=149210?t=149210
set_dhudmessage(red = 0, green = 160, blue = 0, Float:x = -1.0, Float:y = 0.65, effects = 2, Float:fxtime = 6.0, Float:holdtime = 3.0, Float:fadeintime = 0.1, Float:fadeouttime = 1.5, bool:reliable = false)
- Red: Represents ALL the range of RED colors.
- Green: Represents ALL the range of GREEN colors.
- Blue: Represents ALL the range of BLUE colors.For more information on RGB colors, visit the Wikipedia article that explains everything about the subject itself:
https://en.wikipedia.org/wiki/RGB_color_model
Float:x: Represents the DHUD position from Left to Right. Float:y: Represents the DHUD position from Top to Bottom. effects: Represents the effects of the DHUD: ---> at "0" the Hud appears and disappears smoothly (no effects). ---> in "1" the Hud appears and disappears blinking, also when it is on screen it blinks. ---> in "2" the Hud appears letter by letter, but when it disappears it does it normally (when it is on screen it does not blink). Float:fxtime: This really only works when the effect is set to "2", but for something strange in DHUD it doesn't work, that's why it is set to "0.0" (in a normal Hud it works). Float:holdtime: This represents the time in seconds that the DHUD will remain on screen. Float:fadeintime: This represents the time in seconds that the DHUD will take to appear from the moment it is called. Float:fadeouttime: This represents the time in seconds that DHUD will take to disappear from the moment it is called, until it ends. bool:reliable: Explanation below.
true:
If id, then it will be MSG_ONE.
If 0, then MSG_ALL.false (default):
If id, then it shall be MSG_ONE_UNRELIABLE.
If 0, then MSG_BROADCAST.MSG_ONE, with MSG_ONE_UNRELIABLE have the particularity to send a message to a specific client, only with 2 differences: MSG_ONE "waits" for the message to arrive correctly to its destination, otherwise it can send an error or crash, or even freeze the server, I am not clear on that point. The advantage is that the message will never fail to be sent and received by the client, but in case of, it can bring consequences. MSG_ONE_UNRELIABLE is only in charge of sending the message, but it is not aware of knowing if it arrived correctly or not to its addressee, it just sends it and the server continues as normal. Now, if the client receives the message or not, it can vary by different factors. The same happens with MSG_ALL and MSG_BROADCAST respectively.
From there you will see that in "send_dhudMessage":
message_begin( __dhud_reliable ? ( index ? MSG_ONE : MSG_ALL ) : ( index ? MSG_ONE_UNRELIABLE : MSG_BROADCAST ), SVC_DIRECTOR, _, index );
By default it is set to false, which will use MSG_ONE_UNRELIABLE or MSG_BROADCAST.
show_dhudmessage(index, const message[], any:...)
index: This is to identify to whom the message will be shown, if it is "0" it will be shown to everyone.
const message[]: Used to write the message (in quotes "...")
any:....: Used to put variables, vectors, etc, that identify "something".Spoiler[Arkshine Say:] Sending empty string is the worst way you could use. This is not a solution, nor a worth trick, it will lead only to overflow issue and would be a nightmare to handle things properly, especially with 8 buffers. And no, dHud doesn't offer a way to clear/overlay specific buffer, it doesn't work with channel and it has not been designed to be used like normal hud. You have 8 stacked buffers. It means server will have to track 8 buffers per player. It means to clear one buffer, you will have to resend up to 8 client messages. You can imagine issues will be : - Broken message duration time - Broken message effect on refresh - Message flickering on refresh - Client overflow Of course, there are things you could do to handle some of these issues. Though the biggest issue is the client overflow as nowadays plugins send already a lot of messages. But at the end, just for a small functionality, you will have to deal with a lot of trouble/code.
-
1
-
-
register_event registers:
"Message IDs", "Message Indexes", "msgids", "msgid" (whatever you want to call it) of the game, various events that the server sends to the clients (in the form of packets [these are the ones that in excess cause the Reliable Channel Overflow error {only if sent on a "reliable" channel}]), register_message does the same task but has different purposes.
native register_event(const event[],const function[],const flags[],const cond[]="", ... );
const event[] is the name of the event or msgid, but in its name, not in its ID, in the CS there are a series of events, you can locate them here:
https://wiki.alliedmods.net/Half-Life_1_Game_Events
const function[] is the name of the function you want to call (callback function [as well as set_task, for those who didn't catch it]) for when the specific event is performed. The event will have a parameter (id) in case the message has been sent to only 1 player, this is defined in the flags[] parameter.
const flags[] here is already specific:
* "a" - global event, can be deduced when it is of type MSG_ALL, MSG_BROADCAST, and the others related to vision or hearing predictions. * "b" - specific event, when sent to only 1 player on 1 occasion, like when you spend money, the Money message is sent to only 1 player (the one who spent it). * "c" - specific event in quantity, when the same message is sent to several players but it is not global, ex: when there are players that are specting a player with Nightvision, and this one dies, to all the Specs that were watching that player the NVGToggle msg is sent with the first argument in 0, which will deactivate the nvg to the specs. * "d" - specific event, for when only the player is dead. * "e" - specific event, for when only the player is alive.
const cond[]="", ... these are the conditions you want to be applied for the function to be called, this depends on the event. The conditions are in strings, and logical operators similar to those of pawn are used. Among them
Used for discrete values: > < = // 'arg X' equal to ! // 'arg X' not equal to Specific for text: = // 'arg X' equals & // 'arg X' contains ! // 'arg X' does not contain
From the cond[] parameter you can add as many as you want (without exceeding the limit of 32 [sick would be the one that adds more than 32 conditions]) and a simple random example would be
register_event(..., "....", "...", "1=2", "2&blabla", "3>0") // argument 1 must be equal to 2 // argument 2 must contain blabla (knowing that it is a string that arg) // argument 3 must be greater than 0
I give you an example:
"Train" is the event that displays the HUD in the middle where one sees the speed at which a car is going. The bars are defined according to the first argument of the event (you can see them here: https://wiki.alliedmods.net/Half-Life_1_Game_Events#Train), so, if I want to hooke when the player is at maximum speed in a vehicle (func_vehicle), I just do:
public plugin_init() { register_event("Train", "Event_Train", "b", "1=4") } public Event_Train(id) // goes an index because the event is hooked for when called on 1 player only. { client_print(id, print_chat, "You are at maximum speed with your vehicle") }
"b" because I want to hooke when it is sent to a single player (or you deduce it, or check the SDK). The first argument can be: 0 (make it disappear), 1 (neutral), 2 (low speed), 3 (medium speed), 4 (maximum speed), 5 (reverse).
In the case of the famous HLTV event that we often see hooked, it is a specific event sent to proxy clients, Teles, or HLTV.
This event is sent several times, among those, 2 times in CHalfLifeMultiplay::RestartRound that is where the round starts, and in these 2 sends that the Server makes, it sends different arguments.
(from a semi-decompiled version of CS: )
// ... // content of the function CHalfLifeMultiplay::RestartRound // ... g_engfuncs.pfnMessageBegin(9 /* MSG_SPEC */, gmsgHLTV, 0, 0); g_engfuncs.pfnWriteByte(0); g_engfuncs.pfnWriteByte(228); g_engfuncs.pfnMessageEnd(); g_engfuncs.pfnMessageBegin(9 /* MSG_SPEC */, gmsgHLTV, 0, 0); g_engfuncs.pfnWriteByte(0); g_engfuncs.pfnWriteByte(0); g_engfuncs.pfnMessageEnd();
Then, to make it easy to deduce, the event is hooked for when the first argument is 0, and the second argument is 0.
register_event ( "HLTV", "event_round_start", "a", "1=0", "2=0" )
As I told you now, the way you can do this, you can also do this:
register_event ( "HLTV", "event_round_start", "a", "1=0", "2=228" )
Or not?
If you remove this argument detection, the forward would be called twice at the start of the round and at other times when the HLTV event is also sent. That is why this type of detection is done.
-
1
-
-
It is pointed out that to understand certain concepts one must first become familiar with the process of compiling high level code to low level (C/C++ -> machine code) and the subject of pointers in C/C++.
All those who have seen code in SDKs have noticed this particular way of manage strings, but are often confused because one does not understand when to use MAKE_STRING and ALLOC_STRING for example. All this is useful when programming modules as well.
They are defined in the following way, it is not necessary that you understand it at the first time.
#define STRING(offset) reinterpret_cast<const char *>(gpGlobals->pStringBase + (uintp)offset) #define MAKE_STRING(str) (reinterpret_cast<uintp>(str) - reinterpret_cast<uintp>(STRING(0))) #define ALLOC_STRING (*g_engfuncs.pfnAllocString)
I will give you a brief introduction of each of these 3 functions.
MAKE_STRING what it does mainly is to obtain the memory address of a String that is inside the program.
if (m_pActiveItem && !pev->viewmodel) { switch (m_pActiveItem->m_iId) { case WEAPON_AWP: pev->viewmodel = MAKE_STRING("models/v_awp.mdl"); break; case WEAPON_G3SG1: pev->viewmodel = MAKE_STRING("models/v_g3sg1.mdl"); break; case WEAPON_SCOUT: pev->viewmodel = MAKE_STRING("models/v_scout.mdl"); break; case WEAPON_SG550: pev->viewmodel = MAKE_STRING("models/v_sg550.mdl"); break; } }
pev->classname = MAKE_STRING("grenade");
CBaseEntity *CBaseEntity::Create(char *szName, const Vector &vecOrigin, const Vector &vecAngles, edict_t *pentOwner) { edict_t *pent = CREATE_NAMED_ENTITY(MAKE_STRING(szName)); // ... }
switch (m_iType) { case 44: pEntity = CBaseEntity::Create("item_battery", pev->origin, pev->angles); break; case 42: pEntity = CBaseEntity::Create("item_antidote", pev->origin, pev->angles); break; case 43: pEntity = CBaseEntity::Create("item_security", pev->origin, pev->angles); break; case 45: pEntity = CBaseEntity::Create("item_suit", pev->origin, pev->angles); break; }
CBaseEntity *pExplosion = CBaseEntity::Create("env_explosion", center, angles, pOwner);
The particularity of these Strings is that they are set in raw form.
How does this work? When I compile any code, with a String written directly in the mentioned code, this at the moment of being compiled and transformed to a low level language to be executed by an operating system, becomes a static piece of memory inside the code that is always in the same memory position, since this way it was left at the moment of being compiled. You can open a HEX Editor in a compiled program (a dll, an exe, a so) and you will notice that there are very nice texts placed throughout the program.
MAKE_STRING makes use of this feature, doing a smart matching and transforming this String into a memory address, so that when transporting this data it is from a pointer, instead of an undefined size of bytes that would make the work more tedious that is why, there is pev_viewmodel2 and pev_weaponmodel2 since these are integer data, they move with discrete values, they do not store strings of characters.
And how do I know how "long" a string is if I always just handle its address in memory?
Always remember that a String, are "character strings" that will ALWAYS end with a 0 at the end (0, '0' or EOS), where the C/C++ strlen function is involved; its main function is to iterate character-by-character until a 0 is found.
size_t strlen(const char *s) { size_t i; for (i = 0; s[i] != '\0'; i++) ; // hh return i; }
STRING does the reverse of MAKE_STRING. Starting from an address, it locates the String in memory by converting it to char*. This is the way to unpack the String obtained from a single memory address. In this way, strings are easily transported between the Engine and the Gamedll.
You have infinite examples of STRING in the SDK where they are associated directly to "pevs" that store Strings, that you should now understand with what I explained about MAKE_STRING.
Now, let's break down the code.
#define STRING(offset) reinterpret_cast<const char *>(gpGlobals->pStringBase + (uintp)offset) #define MAKE_STRING(str) (reinterpret_cast<uintp>(str) - reinterpret_cast<uintp>(STRING(0)))
reinterpret_cast<uintp>(str) what it does is to get the address of the String set. The cast is from char* to unsigned int* being the appropriate data type to store a memory address.
It is good to touch this topic. To get more into the subject, an offset can be related to a delta, a distance, between 2 specific points. That's why when you look for an "offset" to (for example) edit the team (m_iTeam = 114) using set_pdata_int, deep down, you get the class address (CBasePlayer) and add 114 (x 4), caste it to int, and voila, you have the team. Yes, it is not as simple as that, it could be another tutorial even.
What happens with MAKE_STRING and STRING is something similar. In MAKE_STRING you see that there is a difference between 2 values
reinterpret_cast<uintp>(str) - reinterpret_cast<uintp>(STRING(0)) reinterpret_cast<uintp>(str) - reinterpret_cast<uintp>(reinterpret_cast<const char *>(gpGlobals->pStringBase + (uintp)0)) reinterpret_cast<uintp>(str) - reinterpret_cast<uintp>(reinterpret_cast<const char *>(gpGlobals->pStringBase)) uintp - uintp = subtraction of pointers
Why are the Strings calculated using gpGlobals->pStringBase as a starting point?
gpGlobals->pStringBase is defined in the Engine. What this stores, is nothing more nor less than a table of strings that the engine stores. Or in a nicer way, Allocated Strings Table.
ALLOC_STRING is the way to store Strings that are not in static memory, but rather are stored in the Stack.
If I do for example the following:
uintp generateString(void) { char str[64]; sprintf(str, "Hello, this is my string of %u bytes", (sizeof str)/(sizeof char)); // same uses less space return MAKE_STRING(str); }
It would be invalid. Why?
Well, the memory of what str stores will disappear the moment the function finishes its execution, and the memory address that MAKE_STRING will yield will be invalid, it will point to anything else in the middle of the execution of the program and the creation of more memory in the stack. (stack is the memory of variables created in the middle of the execution).
That's why ALLOC_STRING was created, what this function does is to store the string in a table in the engine (which can be accessed through the pointer gpGlobals->pStringBase) where one can store Strings that are created in the middle of the program execution, which differs from the use of MAKE_STRING which is oriented to Strings that are inside the program code.
An example:
void CC4::KeyValue(KeyValueData *pkvd) { if (FStrEq(pkvd->szKeyName, "detonatedelay")) { pev->speed = atof(pkvd->szValue); pkvd->fHandled = TRUE; } else if (FStrEq(pkvd->szKeyName, "detonatetarget")) { pev->noise1 = ALLOC_STRING(pkvd->szValue); // * pkvd->fHandled = TRUE; } else if (FStrEq(pkvd->szKeyName, "defusetarget")) { pev->target = ALLOC_STRING(pkvd->szValue); // * pkvd->fHandled = TRUE; } else CBaseEntity::KeyValue(pkvd); }
pkvd is part of the Stack, since it is memory of the parameters of the functions. At the end of the function, this memory disappears, that's why here we use ALLOC_STRING to make a copy of this String, save it in the table, and keep its address.
________________________________________________________________________________________________________________________________________________________________________
It is concluded that ALLOC_STRING and MAKE_STRING draw a distance from the same point, which STRING converts from pointer to string to either static strings stored by MAKE_STRING or strings stored in the above table by ALLOC_STRING.
This table gives an abstraction to the above. The memory addresses used are obviously false.
0x0000 [START ENGINE BLOCK] 0x1234 [MEMDIR gpGlobals->pStringBase] 0x1250 [MEMDIR "example1"] 0x1260 [MEMDIR "example2"] 0xAAAA [END ENGINE BLOCK] 0xAAAB [START GAMEDLL BLOCK] 0xBBB0 [MEMDIR "env_explosion"] 0xBBC0 [MEMDIR "item_battery"] 0xBBB0 [MEMDIR "grenade"] 0xBBB0 [MEMDIR "models/v_sg550.mdl"] 0xFFFF [END GAMEDLL BLOCK]
In view of this and taking into account the unfinished example above
uintp ptr = MAKE_STRING("models/v_sg550.mdl")
#define STRING(offset) reinterpret_cast<const char *>(gpGlobals->pStringBase + (uintp)offset) #define MAKE_STRING(str) (reinterpret_cast<uintp>(str) - reinterpret_cast<uintp>(STRING(0)))
ptr would be:
(reinterpret_cast<uintp>(str) - reinterpret_cast<uintp>(reinterpret_cast<const char*>(gpGlobals->pStringBase))))
(0xBBB0 - 0x1234)
0xA97C*This OFFSET (which is a number value that only mentions a distance) if I pass it through STRING:
reinterpret_cast<const char *>(gpGlobals->pStringBase + (uintp)offset)
reinterpret_cast<const char *>(gpGlobals->pStringBase + 0xA97C)
reinterpret_cast<const char *>(0x1234 + 0xA97C)
reinterpret_cast<const char *>(0xBBB0)
"models/v_sg550.mdl"(Although it is really 'm' since it points to the first memory location of the specific address).
The same arithmetic applies with the dummy strings I invented ("example1" and "example").
-
1
-
-
Let's start from the basics, which is when starting a server.
> hlds.exe / hlds_run
The first thing we do is to start a server with its respective executable per OS. This is where the executable will use a specific library. On linux, this library is specified with the -binary parameter (if one is not specified, it will use a default one). On windows it is always the same.
In older versions, the libraries differed according to the OS
> linux (old)
engine_amd.so
engine_i486.so
engine_i686.so> linux (new)
engine_i486.so> windows
swds.dllThis library is the so famous and well known engine library (not to be confused with the amxx module). This would be the reference to the GoldSrc engine. Inside this library is the engine of all the GoldSrc games, which are the same for all the games.
> engine
The engine after being read takes care of initializing itself. The most important routine of the engine, is the reading of the gamedll that will start (yes, another specific library). The gamedll is what we know also as the game itself, the modification, the Half-Life sub-game, whatever you want to call it.
The gamedll is indicated in the liblist.gam file that the engine takes care of parsing. After you get the gamedll from the specific file, the engine will start it [and give you the list of engfuncs and dllfuncs (known as fakemeta)].
Once the gamedll is running, the engine will take care of parsing the connections, generating the physics, generating the frames, and so on. The gamedll, using the tools provided by the engine, will create the aesthetics and the environment.
► What is important about this?
> metamod
There is clearly a bridge between the engine connection and gamedll, and that is the liblist.gam file. Inside this file one defines the library to call to start the game, and that's where metamod appears. In a very clean way, without the need to tweak the libraries, one edits the liblist.gam file to call metamod instead of the original gamedll.
In specific words, what the engine does is to call the function GiveFnptrsToDll (Give function pointers to dynamic link library) of the gamedll library. metamod having these functions is a white point for the engine.
When these are originally called, metamod will call the original gamedll, but with metamod called, it will give way to create an interface of subplugins, among which we find amxmodx over time.
> hlsdk
It would be the original code of the Half-Life gamedll. Its original use is the study of the code, a useful tool to be able to make external libraries taking into account how the original gamedll is made. The hlsdk is supposed to be a tool for developers, so it is not compilable, *supposedly*.
> engfuncs
These are functions written in the engine which it gives to gamedll in order to be able to perform umpteen algorithms. These are sent to gamedll through the function GiveFnptrsToDll.
> globals
It is a structure written in the engine, a series of variables with useful information that are given to gamedll. These are sent to gamedll by the function GiveFnptrsToDll.
> dllfuncs
Unlike engfuncs, these functions are written in gamedll, but with a specific structure provided by the engine. The engine will sometimes notify events to the gamedll (e.g. Touch, KeyValue), or it will look for constraints (e.g. AddToFullPack, SetupVisibility, ClientConnect) through the dllfuncs.
_____________________________________________________________________________________________________________________________________________________________________
GiveFnptrsToDll: https://cpp.hotexamples.com/examples/-/HL_enginefuncs_t/initialise_interface/cpp-hl_enginefuncs_t-initialise_interface-method-examples.html
-
1
-
-
What will we see in this tutorial?
-String endings.
-Memory overflow.
-Sizeof operator.Null-terminated:
This is basic stuff and I take it for granted that 90% of you reading this already know it.For the few who do not know it:
Character strings always have an extra character indicating the end of the string, the character is '^0', or simply NULL (zero). That is the reason why an extra cell is always added to the array containing it.// Charsmax is an alias of sizeof that leaves an extra cell for the end of the string. #define charsmax(%1) (sizeof(%1)-1) // Valid new myvar[2] = "a" // sizeof = 2 - Value in memory: [ 97, 0 ] new myvar[] = "a" // sizeof = 2 - Value in memory: [ 97, 0 ] new myvar[3] = "a" // sizeof = 3 - Value in memory: [ 97, 0, 0 ] // ERROR, invalid ( does not compile ) new myvar[1] = "a" // sizeof = 3
// Is Valid new myvar[2] copy(myvar, charsmax(myvar), "a")
___________________________________________________________________________________________________________________________________________________________________________________
// ERROR, invalid ( there is only space for the NULL character ) new myvar[1] // In this case, charsmax returns 0, so it doesn't copy any data copy(myvar, charsmax(myvar), "a") // Value in memory: [ 0 ] // Force copy copy(string_buffer, 1, "a") // Value in memory: [ 97 ] /* At this point we get into the Memory overflow, since the NULL character indicating the end of the string was copied outside the allocated area, which can cause drastic failures. */
Buffer overflow (Memory overflow or buffer overrun):
This is an error that occurs when data is copied exceeding the memory area allocated to the buffer (the output variable). The excess bytes are stored in adjacent memory areas, overwriting their original content, which probably belonged to data or code stored in memory. (In other words, you can screw up badly).As we saw in the last example of End of strings, trying to copy data without having enough space causes a Memory overflow.
For the beginners it is difficult to understand only with words, I better leave you examples.
Let's take advantage of the fact that the memory area of the static variables are assigned consecutively according to the order in which they are created.
Simple Memory overflow:
static buffer[1] // sizeof = 1 - Value in memory: [ 0 ] static string[] = "hello" // sizeof = 5 - Value in memory: [ 104, 111, 108, 97, 0 ] /* In-memory value of 'buffer' + 'string' : [ ... ( 0 ), ( 104, 111, 108, 97, 0 ) ... ] */ server_print("[%s]", string) // Print "[hello]" copy(buffer, 1, "a") // NULL character exceeds the area allocated to 'buffer' and is written to the area allocated to 'string'. /* Values in memory: - 'buffer': [ 97 ] - 'string': [ *0, 111, 108, 97, 0 ] - 'buffer' + 'string': [ ... ( 97 ), ( 0, 111, 108, 97, 0 ) ... ] */ server_print("[%s]", string) // Print "[]" server_print("[%s]", buffer) // Print "[a]"
What would happen if we remove the NULL character manually?
// Remove the NULL character string[0] = 'h' // Value in memory: [ 104, 111, 108, 97, 0 ] /* Value in memory of 'buffer' + 'string' : [ ... ( 97 ), ( 104, 111, 108, 97, 0 ) ... ] */ server_print("[%s]", string) // Prints "[hello]" /* - It will print everything until it finds the NULL character in the memory area of 'string'. - 'buffer' + 'string' : [ (97), (104, 111, 108, 97, 97, 0) ] */ server_print("[%s]", buffer) // Print "[ahola]"
The importance of the NULL character in a string.
// We will try to re-create two strings character by character static bad_str[] = { 'h', 'o', 'l', 'a', ' ' } // Invalid, missing NULL character static str[] = { 'm', 'u', 'n', 'd', 'o', 0 } // Valid server_print("[%s]", str) // Print "[world]" /* Can you guess what would happen if we try to print 'bad_str' ? */ server_print("[%s]", bad_str) // Print "[hello world]" /* As you know, when it doesn't find the NULL character, it keeps going through the memory until it finds one */
So far the Memory overflows were controlled, but what could happen?
Just try something like this...new overflow[1] arrayset(overflow, 1, 9999)
Sizeof:
Sizeof is an operator that returns the number of cells in a variable/matrix/array/vector.Examples:
In normal variables
new var, var2[2], var3[999], var4[3][8] server_print("[%d]", sizeof var) // print [1] server_print("[%d]", sizeof var2) // print [2] server_print("[%d]", sizeof var3) // print [999] server_print("[%d]", sizeof var4) // print [3] server_print("[%d]", sizeof var4[]) // print [8]
In enum
enum _:_data_struct // = 10 { item1, // 0 +1 item2[2], // 1 +2 item3[6], // 3 +6 item_max // 9 +1 } new data[_data_struct] // Same as data[10]. server_print("[%d]", _data_struct) // Prints the total size of the enum "[10]" server_print("[%d]", sizeof data) // Prints "[10]" server_print("[%d]", sizeof g_data[item2]) // Prints "[2]" server_print("[%d]", sizeof g_data[item3]) // Print "[6]" server_print("[%d][%d][%d][%d][%d]", item1, item2, item3, item_max) // Print "[0][1][1][3][9]" /* Enumeration overflow */ copy(data, item_max, "123456789") server_print("data:[%s] - item2:[%s] - item3:[%s]", data, data[item2], data[item3]) /* data:[123456789] - item2:[23456789] - item3:[456789]" */
-
1
-
-
In this tutorial I will make you understand how FVault works and how to use it. Before we start, if you want to CREATE some saving system for your plugin/mod, consider also SQL and Adv Vault. Each has different advantages and FVault may not be the most suitable for your needs.
1. Let's understand how FVault saving works.
* FVault stores the data together with a key. To load this data, we indicate this key and it gives us the information associated to this key. Let's see an example of a FVault database:
"Mr.Love" "9aeaed51f2b0f6680c4ed4b07fb1a83c" 1403687918 "NewLifeProPlayer" "db78036eb6dbaf1d425338df125d5c71" 1403825292 "Mind-Shpere" "3760ee6b2c91c54411bc87df5adf6f48" 1403825848
All lines follow the format:
"password" "data" 0000000000
What is between the first " " is the key. With the key we can access the associated data.
Between the second pair of " " is the data. In this case, there are md5 encrypted passwords.
After the data, without quotes, is the timestamp. This is nothing less than the date and time when data was last stored in that key (in Unix Time format). If you don't know what this is, you can read more here: https://en.wikipedia.org/wiki/Unix_time2. We already know how to save data in FVault. Now, let's see how to save and get our data.
FVault makes our life easier with 2 main stocks: fvault_get_data and fvault_set_data.
Let's save our data with fvault_set_data:// fvault_set_data(const vaultname[], const key[], const data[]) // The first parameter is the name of our vault. // The second parameter is the key where we will store data. // The third parameter is the data we will save. SaveData(id) { // We will save the data using the player's name as the key. static szName[32], szData[100]; get_user_name(id, szName, 31); // Now let's put our data // FVault saves and loads STRINGS // We need to put our data into a string to save it. // In this case, I will save the player's IP in the data. static szIP[25]; get_user_ip(ip, szIP, charsmax(szIP)) // We put the data in a string formatex(szData, charsmax(szData), "%s", szIP); // Save the data fvault_set_data(our_vault, szName, szData); // What will happen if the key already exists? // The data will be overwritten, nothing else. }
Let's see how to use fvault_get_data:
// fvault_get_data(const vaultname[], const key[], data[], len, ×tamp=0) // The first parameter is the name of our vault. // The second is the key from which we will get the data. // The key has to be EXACTLY the same. // The third parameter is the array from which we will get the data. // The fourth parameter is the maximum length for the data. // The fifth parameter (optional) is to obtain the date and time // (Unix Time) of the last data save. // Returns 1 if data was loaded, 0 if not loaded. LoadData(id) { // In this case, we save the data using the name as a key static szName[32], szData[100] get_user_name(id, szName, 31); if (fvault_get_data(our_vault, szName, szData, charsmax(szData)) { // Load the data in the conditional if // Because 1 (true) is returned if data is loaded client_print(id, print_chat, "Your data: %s", szData); } else { // No data loaded client_print(id, print_chat, "You have no data"); } }
If we want to delete data:
PHP code: // fvault_remove_key(const vaultname[], const key[]). // The first parameter is our database. // The second is the key. DeleteData(id) { // The key is the name (IN THIS EXAMPLE) static szName[32]; get_user_name(id, szName, 31); fvault_remove_key(our_vault, szName); }
3. Playing with the Strings to store several data in FVault.
Before doing this, we must take into account that FVault has a limit of characters for the data (512) but in practice they are reduced to 509. If we will store numbers, in "theory" can enter at most 42 integers (10-digit numbers, with the sign - and leaving a space: 509/12) but in practice can enter many more.
Here we are going to use 2 very useful natives: formatex and parse.
Formatex() is already known by most of us, but let's quickly see how it works.new szName[32], szData[99999999999999999999999999999999999999999]; get_user_name(id, szName, 31); formatex(szData, charsmax(szData), "%d %d %d %s", get_user_frags(id), cs_get_user_money(id), szName) // Simple, first we indicate the array where we will put the text. // Second, the maximum length. (charsmax is the best option here) // Third, the text to format that we will save: // %d - %i serves us to insert integers. // %f - Floating point numbers (decimals). // %s - Strings. // %c - One character. // %L - Multilanguage of AMXX. We don't need it here // PS: Don't be so **** to create the array szData with 99999999999 cells, let's think a little bit. // You enter 2 numbers with no more than 10 digits (20 cells) // A string of maximum 31, 2 spaces; adding up we have 53 characters. // The maximum number of cells would be 53 + 1 of End of String = 54
Now let's look at parse():
// parse is used to split a string into as many parts as we can/want. // In this case, I formatted szData with 3 spaces, that is, it will give us 4 parts. // If we want parse to ignore any space, it has to be in quotes. // E.g. if our string contains -> hello howdy howdy "hello howdy" 5 // When we parse it, we will get it separately (in strings): // hello // that // so and so // hello that such // 5 static szData1[10], szData2[10], szData3[32]; parse(szData, szData1, charsmax(szData1), szData2, charsmax(szData2), szData3, charsmax(szData3)) // How we use parse: // The first parameter is the string we are going to split. // Then, the parameters are entered in this order: // 1. Array where the part of the string that was parsed will be stored. // Maximum length. // If 2 parts will come out, then we indicate 2 strings and 2 lengths, in order. // Now that we have the data split as strings, we can deliver them to the player. // But first they have to be converted to numbers, for that we use str_to_num() new frags = str_to_num(szData1) new money = str_to_num(szData2) // Data 3 is a string, so we don't have to convert it. client_print(id, print_chat, "Learning to use formatex and parse, you have %d frags, %d money and your name is %s", frags, money, szData3)
4. Let's look at other operations we can do with FVault.
FVault offers us other useful stocks:// Get a key by the line number of the file it is in. // fvault_get_keyname(const vaultname[], const keynum, key[], len) // First parameter: Our database. // Second parameter: The line of the file where is the key we will get. // The third parameter is the array where we will get the key. // It is superfluous to explain len. // Get the line number of the file where our key is. // fvault_get_keynum(const vaultname[], const key[]) // First parameter: Our database. // Second parameter: Our key. // Get the number of entries in our database. // fvault_size(const vaultname[]) // Parameter is our database. // Return the number of entries created.
What use can we give to these stocks? Let's see an example: I will delete the last entry created in our database.
new szKey[50], size = fvault_size(our_vault)-1; fvault_get_keyname(our_vault, size, szKey, charsmax(szKey)); fvaut_remove_key(our_vault, szKey);
Explanation:
We got the key name from the last entry created. If we have 2 entries, the first one will be line 0 and the second one line 1. If we have 3 entries, the first 0, second 1 and third 2. It is satisfied that the line of the last entry is equal to the number of entries - 1.
Now let's see how to iterate through a database.new szKey[64], szData[512], size = fvault_size(our_vault); // We loop the entire length of our vault for (new i = 0; i < size; i++) { // We get the key fvault_get_keyname(our_vault, i, szKey, charsmax(szKey)); // Now that we have the key, we can get the data. fvault_get_data(our_vault, szKey, szData, charsmax(szData)); console_print(0, "Key %d : %s Data: %s", i+1, szKey, szData); }
5. Editing saved data with FVault:
FVault databases can be edited with basic text editing programs, such as Windows Notepad, WordPad, etc. The keys and data are as they were saved, i.e. if you saved an IP address (123.456.789.0) you will find it as it is in the database.
Let's see an example of a database containing names in the keys and an IP address with an amount of money in the data:"Dratkhar" "190.12.72.50 16000" 1404080472 "Mr.Love" "255.255.255.255 99999" 946684799
Now, I will make Mr.love have $800 and its IP is 8.8.8.8.8
"Drakthar" "190.12.72.50 16000" 1404080472 "Mr.love" "8.8.8.8 800" 946684799
So easy? Yes, it is that easy, but the reason I am explaining this is because of the next point in this list.
If we look closely, we will see that the database has a blank line at the end. IT IS IMPORTANT NOT TO DELETE THIS LINE or we can bug our database.
6. Optimizing our FVault:
If you use FVault stocks a LOT in your code, consider editing the library (#include) fvault and changing the new to static.
A modified stock to use fvault more efficiently. Comment out or delete the #define USE_TIMESTAMP line if you are not going to use the timestamp with this stock, it will improve performance. It works very similar to fvault_get_keyname but also gets you the data.Spoiler#define USE_TIMESTAMP /** * Retrieves a key name and its data specified by its number * * @param vaultname Vault name to look in * @param keynum Key number within the vault to find key name and data * @param key String which key name will be copied to * @param len_key Length of key name * @param data String which data will be copied to * @param len_data Length of data * @param timestamp The unix time of when the data was last set ( -1 if permanent data, 0 if old fvault version ) ( optional param ) * @return Returns 1 on success, 0 on failue. */ fvault_get_key_and_data(const vaultname[], const keynum, key[], len_key, data[], len_data, ×tamp=0) { new _data[580]; _FormatVaultName(vaultname, _data, sizeof(_data) - 1); if( !file_exists(_data) ) { return 0; } new vault = fopen(_data, "rt"); new line = -1; while( !feof(vault) ) { fgets(vault, _data, sizeof(_data) - 1); if( ++line == keynum ) { parse(_data, key, len_key, data, len_data); #if defined USAR_TIMESTAMP new _time[32]; for( new i = strlen(data) - 1; i > 0; i-- ) { if( data[i] == '"' ) break; if( data[i] == ' ' && data[i - 1] == '"' ) { data[i - 1] = '^0'; copy(_time, sizeof(_time) - 1, data[i + 1]); timestamp = str_to_num(_time); break; } } #endif fclose(vault); return 1; } } fclose(vault); return 0; }
Link INC Fvault: https://forums.alliedmods.net/showthread.php?t=76453
-
1
-
-
1. Data flow through conditions.
The most important thing in programming languages is to control the flow of data through the if statements, these are very important in their way of interpreting the code, but as far as we know, making meaningless conditions only creates that the processor takes more time to read the string of statements and compare all its results to see if it should take the path of this statement.
An example would be:
Spoiler// THIS IS WRONG [X] // Why? you are checking if it is alive in a function that can only be called when the player is alive, the processor takes longer to read the statement when it is usually alive. // the player is alive, it takes longer for the processor to read the statement when it is usually // it will always be true (it can be false in some cases) not to mention that underneath the native ones // also hides code that depending on the cost of the operation, can have fatal results. if(is_user_alive(id) && !(pev(id, pev_flags) & FL_ONGROUND) && g_someglobalvariablepls[id]) // Some very very very long code... // Another code a little less long than the one above... }
This can easily be fixed by querying and/or reading the SDK to see what checks and other things a function does when programming your structure, to avoid putting useless statements.
Another thing that can also be a headache, is the bad structuring of the anchors in the statement, which causes it to throw false positives/negatives.
Example:
Spoilernew g_randomvar[3] g_randomvar[0] = 6 g_randomvar[1] = 26 g_randomvar[2] = -1 public fw_function(id, ent, Float:size[3]) { // This function according to us is this logic. // Si g_randomvar[0] is less than 6 ó g_randomvar[2] is negative y g_randomvar[1] is greater than 25 if(g_randomvar[0] < 6 || !g_randomvar[2] && g_randomvar[1] > 25) }
Here, this function would be giving wrong results, because -1 is not a number that is interpreted as negative in this language, apart from that, it can throw false errors the ANDing of the second sentence, to fix this, we must adjust to sentence exactly what we want.
// if g_randomvar[0] is less than 6 or g_randomvar[2] is negative (zero) and g_randomvar[1] is greater than 25 if(g_randomvar[0] < 6 || (g_randomvar[2] == 0 && g_randomvar[1] > 25))
There we are interpreting exactly what we want, so there should be no problems when reading the sentence.
Note: Attached sentences
I don't know exactly what this is called, but when you enclose several statements in a single parenthesis, they will give only 1 result in the same condition.
Example:
Spoilernew g_randomvar[3] g_randomvar[0] = 0 g_randomvar[1] = 1 g_randomvar[2] = 0 public fw_SomeFunction(id, ent, Float:magnitude[3]) { // This logic will give only 2 results in the interpreter and reads like this // if g_randomvar[0] is positive == true // if g_randomvar[1] and g_randomvar[2] are positive == true / otherwise if either of these are negative it is false if(g_randomvar[0] || (g_randomvar[1] && g_randomvar[2])) // @ Interpret if(true || false) // pls... // No anchors if(g_randomvar[0] || g_randomvar[1] && g_randomvar[2]) // @ Interpreter if(true || false && false) // false and false == false.. lol // pls... }
Flow Structure
As we know, some people design a solution to the problem they have by programming what comes out of their mind and develop it as they go debugging it, this is not bad, but it has never been a good way of programming, a better programming method is to make a flowchart, where you can see how the program will work, which routes it will take in each case, what problems could arise when trying to take each of the routes and finally how to solve those problems.
Here is an example:
Spoiler// Some random function public fw_EpicFunction() { // Let's say that according to its structure, it will follow a few paths if(/* condition that will always throw true */) { // Code pls... if(/* Condition that will always throw false*/) { // Code pls. // ... // .... } // A silly way to do something (imagine that) if(/* condition correctly used */) { // Code pls... // ... // .... } } }
In the functions that usually always throw true/false is usually where cvars are used whose preferences always remain in a single value, for that it is better to remove the flow of unnecessary conditions and remove the unused code.
--->
// Some random function public fw_EpicFunction() { /*Supposing we delete a stream that is not necessary here would be the code of what always threw true */ // A silly method of doing something (imagine that) /*Supposing that we delete a flow that is not necessary here we would DELETE the code of what always threw false */ if(/* condition correctly used */) { // Code pls... // ... // .... } }
There we are reducing its structure, therefore it is better because the processor reads only what it has to read, what I mean is that if the flow of the condition is always constant, it is better to delete it.
2. Memory allocation.
Pros and cons of new and static at the request of Gladius.
new is the form to declare a variable in this language, this variable according to where it is declared, it will happen to be private or global, when it is private, it can only be used in the function where it was declared and when it is global it can be used in any part, this when it is private, it is no longer referenced and the function finishes executing, it is practically eliminated of the memory, when it is global this is eliminated of the memory when the map is changed, this is good to be used in functions that practically are not called very often, because the memory will be freed when finishing to execute what is going to be done, in recursive functions it has a negative impact when the stack of memory that we declare is big, because it has to initialize each one of the times the stack of memory.
static is also another way to declare a variable, only that it is stored in memory and can load data even when the function is not running, when it is referenced, its value will be the same as it left in the previous execution, this is a keyword used for recursive functions, which usually require to be getting data, its pros are that it does not have to be initialized, because once created, it resides in memory and stays there until the map is changed, its cons is that when creating large stacks of memory, these are stored in RAM, giving a negative impact on VPS or PCs with very low performance, but that is very unlikely to happen.
Spoiler// Flow of variables for dummies (in a function). // NEW public func(you_daft_wanker, bloody_frog_would_be_more_use_than_you) { // Initialize (every time this function is called it will be called). new newtypevar // Gets data. get_something(newtypevar) // Sends it somewhere. send_to_somewhere(newtypevar) // Deletes it. } // STATIC public func(you_daft_wanker, bloody_frog_would_be_more_use_than_you) { // Initialize. static statictypevar // Gets data. get_something(newtypevar) // Sends it somewhere. send_to_somewhere(newtypevar) }
The 2 have different uses for what you want to do and the mistake of many is to allocate memory that they do not use, increasing the size of the plugin and the time in which the processor reads each ASCII value to interpret it as a letter (in the case of strings).
Example:
Spoiler// Message Hook 'SayText' -> register_clcmd("say", "clcmd_sayhook") // Function that is called every time the user types some public text. public clcmd_sayhook(id) { // It is correct to use static to store what it said in memory and replace it each time the function is called. // time the function is called. static examplexd[192] read_args(examplexd, charsmax(examplexd)) if(examplexd[0] == '&' || examplexd[0] == '(') // asd... // asd... }
There our error is to assign 192 memory cells when we are only checking only 1, this makes that the processor has to load a little more data when the variable is referenced (although these are in zeros), a solution to this would be to change it to new (to your taste and preference, because it is a very small array) and reduce the size of the array to 2, because there we are spending exactly 760 bytes of memory that we could use in another operation.
Another similar error is that we are interacting with something that needs to be checked with strings and we do this:
Spoilernew const MyString[] = "MyStringTROLOLOL" // <- MyString[17+1] the +1 is just in case. // called 5 times per second... public fw_SomeRandomFunc() { static customstring[64] SomeNativeWhichObtainsAString(customstring, charsmax(customstring)) if(equal(customstring, MyString)) // pls... // pls... }
Not necessarily some things need to be checked with strings, there are some methods like using their private values (pev) or even giving them an id, but here the case is that you are allocating and checking a string of 64 characters max with one that only has 16... you are referencing unnecessary memory, my advice is to only use the max characters of the string you are comparing... if you compare several precomputed strings, check which one is the biggest. you are referencing unnecessary memory, my advice is to use only the maximum characters of the strings you are comparing... if you compare several precomputed strings, look at which is the largest and set that value (+1 or +2 cells to avoid problems) and thus save a few kilobytes or even megabytes of memory.
Another little problem can be when making old style menus, is that you have to display a text yourself and detect the keys pressed.
Spoilerpublic show_menu_clasic1(id) { static menu[1000] // Menu pls.. } public show_menu_clasic2(id) { static menu[1000] // Menu pls.. } public show_menu_clasic3(id) { static menu[1000] // Menu pls.. }
Here we are allocating a huge and exaggerated amount of memory... as formatex does not create a copy-back... the most used is to use a lenght to determine from where to start writing new data (used in ZP), here we can optimize the code by creating a single global variable of 1000 cells and interleaving it between all the menus.
-->
new g_menu[1000] public show_menu_classic1(id) { new len // Menu pls.. } // etc...
The same can be with other functions that do the same thing but have to handle different texts, there they simply save 4kb of memory in texts and interleave between them, as long as they use the same method (the lenght, in this example), as it could be a motd (which can also use lenght) or a text file written with write_file() also uses lenght.
Another common mistake is that some people create a loop and inside it initialize variables, this is bad for large scale loops, since each time the loop is started, if you use new explained how it works above, it has the same impact as if it were an iterative function (similar to recursive, only this one is repeated x number of times). Each time the loop repeats, the variable will be created again, thus, wearing out the CPU depending on the size of the variables, the simple solution is to declare them only once OUTSIDE the loop.
Spoiler// Nasty form. forward func() { while(false) // Don't quote this, don't be captain obvious, what I explain is below v { new [CENSORED]mycpu[400] // NOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO // ... } } // More or less form forward func() { new softitalil[400] while(true) // ._. { //... } }
3. Theoretical structure
Note: This part is pure boring text without examples, because it is an individual section.
This is probably the most complicated block to go through, since this is everyone's, I don't want to make a fart because we all have different thinking, so respect other programmers' code, no matter how nice or disgusting it may be.
When you are going to make a plugin, more or less you get an idea of how you are going to do it, what functions of the engine you are going to hooke and what you are going to do with them, this is where your style comes in, because everyone thinks better or worse and has different ways of doing it, but the problem is that what we want is to improve, so what do we do?
After thinking deeply about how your plugin is going to be, then you already have a part done, the theoretical part, this comes referring to how your plugin is going to work in a thought out way, however we must think of all the possible things that can happen when doing something, because as we know, generating an answer generates many more questions.
When you have a theoretical plugin it has to take into account the following points (let me know if I miss any and NO, they are not ordered)
Method we are going to use
Here you think about how your plugin is going to work, for example: Let's say I want it to jump higher when I jump but without adjusting gravity, I'll do this, this and that...
Problems that could be caused by the method we are going to use
It is a pre-debugging... in that it could harm other plugins, using this example, it could harm the multijump.
Functions, variables and natives that we will use
Assuming we use the above, we'll use pev_velocity to set a stronger magnitude to the jump.
How we could optimize what we already have
If we can, how can we improve what is already done.
Continuous plugin flow
Eliminate the things that we won't use much and that affect the speed of the plugin, remember that it can happen to you like me, that the details always end up giving me a cancer.
Debugging (this could be said to be after making the plugin)
Fix the bugs that are reported either in the same game or in alliedmods, if it is public.
Once we have this we can move on to create the plugin, but be careful here for that is alliedmodders, for when you can not think on your own any of the following points, everyone in the community can help you either when you are doing it or when you are still in this phase.
4. Reducing instructions
When you already have a more or less clean code, another way to restructure it to make it more fluid is to try to reduce the lines of code by simple arguments and fewer lines of code, so that the readers of the source code are less stressed.
An example would be when using strings, we have a variable that will decide what will be displayed on the screen, but typically many people would use an if, when it is really a matter of using a statement attached to the native one that we will call.
Spoiler// Dummy way public somerandomjunk(id, ive_meet_smarter_donkeys_than_you_lot) { new derP // Junky code.. if(derP == 2) client_print(id, print_center, "What a plonker!") else if (derP == 5) client_print(id, print_center, "I've meet smarter donkeys than you lot!") else client_print(id, print_center, "You daft wanker!") }
This can be reduced to a simple line, a little long but easier to read.
Spoilerpublic somerandomjunk(id, ive_meet_smarter_donkeys_than_you_lot) { new derP // Junky code.. client_print(id, client_print, "%s", derP == 5 ? "I've meet smarter donkeys than you lot!" : derP == 2 ? "What a plonker!" : "You daft wanker!") }
With this we reduce lines and save a little bit of reading, in optimization it does not influence much, but it is more to make it a good habit.
-
1
-
-
The main objective is not that those who read this tutorial understand chatcolor perfectly, but that they know how to use it properly and stop using those awful chatcolor stocks that are out there.
At the end of the tutorial I will leave a compilation of different "optimal" Chatcolor stocks, for different uses.
Personally I use client_print_color because of the lang, if the lang does not appear with color it is because you must use ^4 not ^x04.
1.- What's in a chatcolor stock?
* Let's look at a classic poorly made chatcolor stock:
stock chatcolor(id, const input[], any:...) { static count, players[32], msg[191], msgSayText; vformat(msg, 190, input, 3) if (!msgSayText) msgSayText = get_user_msgid("SayText"); replace_all(msg, 190, "!g", "^4") replace_all(msg, 190, "!y", "^1") replace_all(msg, 190, "!team", "^3") if (id) { players[0] = id; count = 1; } else get_players(players, count, "c"); for (new i = 0; i < count; i++) { message_begin(MSG_ONE_UNRELIABLE, msgSayText, _, players[i]) write_byte(players[i]); write_string(msg); message_end(); } }
Parts of the stock
vformat(msg, 190, input, 3)
This native is used to format a text according to a reference (The reference is the any:...). We indicate the string where we will obtain the text with the format, the maximum length, the string of the text to be formatted and the number of the reference argument (the position of the any:...).
if (!msgSayText) msgSayText = get_user_msgid("SayText");
Here we get the ID of the SayText messages. This is done because the ID of the messages will not change in the middle of the game, it is not necessary to call the native every time we need this ID, once is enough.
replace_all(msg, 190, "!g", "^4");
This is totally useless in my opinion, but many use it. Simply replace all '!g' with '^4'. Messages sent by SayText can be displayed in color according to these characters: ^1 (cvar with_color, by default yellow), ^3 (color of the sender's team) and ^4 (green color).
get_players(players, count, "c")
Unfortunately this is present in many chatcolor stocks, I will explain later why it should not be.
message_begin(MSG_ONE_UNRELIABLE, msgSayText, _, players[i]) write_byte(players[i]); write_string(msg); message_end();
We come to the most important part. This is the chatcolor, this sends our message in color, everything else is for decoration.
2.- Now that we know the "real" chatcolor, let's see how it works.
- First, I am going to give you a basic understanding of messages in HL. Messages are the "communication language" between the HL server and the clients. What we do in chatcolor stock is to send a "SayText" message. There are many types of messages that HL uses, I know 2 that can print messages in the chat, but "SayText" is the one that will allow us to show color.
- This message has 2 arguments: the "sender" and the "message". (Yes, like a letter!). The sender is the index of a player (between 1 and 32, if we use 0 the message will not have color) (byte) and the message, the text to show ^^ (string).
- Returning to the topic of HL messages, messages can be sent to destinations predefined by HL. The main ones are: MSG_ONE, MSG_ALL and their counterparts in the unreliable channel: MSG_ONE_UNRELIABLE and MSG_BROADCAST. There is also MSG_SPEC and 4 others that I don't understand and won't explain.message_begin(MSG_ONE, g_msgSayText, _, id)
When we call native message_begin(), we tell it the destination of our message. I recommend using MSG_ONE_UNRELIABLE and MSG_BROADCAST, since the difference with their counterparts is that these messages can be lost on the way without generating "damage", while if the other messages are lost (by loss, disconnection) they generally cause the server to crash (CRASH).
If we set MSG_ONE_UNRELIABLE, we have to indicate an ID of a player to whom the message will be addressed. With MSG_BROADCAST, the message goes to EVERYONE O.O, yes, to everyone! That's why we shouldn't use get_players in the chatcolor stock if with MSG_BROADCAST we already send the message to everyone. We can also use MSG_SPEC, which will send the message to HLTV clients.
message_begin() also allows us to set the source of the message, although this is not necessary at all for sending chat messages.write_byte(id)
Let's go again with "SayText". The first argument in this message is a byte, which is the ID of the player sending the message. The color depends on the team of this player. The id can be an unconnected player, we won't have problems with that, in this case the color will be the color of the team of the last player who had that ID number.
INFO (Thanks Destro for the info): We can "make believe" to the players that the player (or entity) with ID 33, 34, 35, etc belongs to a team, so by using these IDs in this byte of the message we will get the desired colors. (More info in the stocks).write_string(msg)
The second argument is the message, with our color codes. The color code ^3 makes our message have the color of the team of the player sending the message (see write_byte above). If the player is not connected or is a spectator, it will show grey; if it is CT, it will show Blue and if it is TT, it will show Red.
Note: The length of the message cannot exceed 189 characters, or the server will crash.
message_end()
After specifying these arguments, we send the message with message_end().
3.- Compilation of Chatcolor
Simple chatcolor to display a message to all players, without formatting.
stock chatcolor_all(input[]) { static msgSayText; if (!msgSayText) msgSayText = get_user_msgid("SayText"); if (strlen(input) >= 190) input[190] = '^0'; message_begin(MSG_BROADCAST, msgSayText); write_byte(33); write_string(input); message_end(); }
Mode of use:
Spoilerchatcolor_all("^4[AMXX]^1 Welcome^3 Nickname") Green color - ^4 Yellow color - ^1 White color - ^3
Chatcolor with format, for one player or for all.
stock chatcolor(id, const input[], any:...) { static szMsg[191], msgSayText; if (!msgSayText) msgSayText = get_user_msgid("SayText"); vformat(szMsg, 190, input, 3); replace_all(szMsg, 190, "!g", "^4"); replace_all(szMsg, 190, "!y", "^1"); replace_all(szMsg, 190, "!team", "^3"); message_begin(id ? MSG_ONE_UNRELIABLE : MSG_BROADCAST, msgSayText, .player = id); write_byte(id ? id : 33); write_string(szMsg); message_end(); }
Mode of use:
Spoilernew name[32]; get_user_name(id, name, 31); chatcolor(id, "!g[AMXX] !yWelcome!team %s", name)
Green color - !g
Yellow color - !y
Team color - !teamIf the index is an ID, the color of !team will be the color of its team. If the index is 0, the color of !team will be white.
sffas
-
1
-
-
Note: The idea of this topic is not to be closed so I can answer doubts, help with installations and bugs.
First of all, I want to explain why REHLDS should be the first choice instead of using HLDS:
IMPORTANT: The links are always updated with the latest versions.
- If you are too lazy to go through the process, you can download everything and install the latest version, without errors.
https://github.com/AMXX-pl/BasePack (You just have to move the files to a blank Counter-Strike)
- Together with reverse engineering, many defects and (potential) bugs were found and corrected.
- Provide a more stable (than official) version of Half-Life dedicated server with extended API for mods and add-ons.
- Performance optimizations (use of SSE for vector math, for example) is another goal for the future.
1.- (If you are using windows you can skip this step, you only have to download Counter-Strike virgin)
First, if you have a panel like OPEN GAME PANEL, you have the possibility to download the STEAMCMD virgin server, nowadays there are panels that have this automatic installation, which by the way, I recommend buying the servers of this community for the good PING, good performance and ease of server reinstallation without counting the excellent support that fits your needs.
2.- After downloading the base server it is time to shut it down and start with the installations, first you will start by downloading REHLDS. (Once downloaded go to the "bin", "linux32" or "win32" folder depending on your operating system and copy the contents (including the VALVE folder) and paste it into the root directory of your server, replace and merge everything.)
Link: https://github.com/dreamstalker/rehlds/releases
3.- Now, go to liblist.gam in cstrike and put edit, in case you are linux you look for this line (gamedll_linux) and replace it with the following:
gamedll_linux "addons/metamod/dlls/metamod.so"In case you are a windows user look for gamedll:
gamedll "addons/metamod/dlls/metamod.dll"Otherwise you replace all liblist.gam with this:
Spoilergame "Counter-Strike"
url_info "www.counter-strike.net"
url_dl ""
version "1.6"
size "184000000"
svonly "0"
secure "1"
type "multiplayer_only"
cldll "1"
hlversion "1111"
nomodels "1"
nohimodel "1"
mpentity "info_player_start"
gamedll "addons/metamod/dlls/metamod.dll"
gamedll_linux "addons/metamod/dlls/metamod.so"
gamedll_osx "dlls/cs.dylib"
trainmap "tr_1"
edicts "1800"4.- Download the most recent and available version:
https://github.com/theAsmodai/metamod-r/releases
5.- Move the addons folder to cstrike and create the plugins.ini file (must be INI, don't confuse with plugins.ini.txt xd).
6.- Download reunion (this is the double protocol plus anti fake protections), when you enter there will be a reunion.cfg, move it to cstrike (important because if the reunion is not there it will not work). Then go to bin, select your version, go to addons, create a folder called reunion. Move the reunion file.
Then, go to addons, metamod and edit plugins.ini and add:
For Windows: win32 addons\reunion\reunion_mm.dll
For linux: linux addons\reunion\reunion_mm_i386.sohttps://dev-cs.ru/resources/68/
7.- Now it's time to install AMX, go to the page, select your version and download the base package and counter-strike.
It will leave you 2 Rar-ZIP-Tar, open them and move the addons folder to cstrike, now go to metamod and add in plugins.ini.
Remember not to include the 2, only 1 and not all in a row, but one below the other.
IMPORTANT:
- If you want to compile files you must download the windows version and use it on your desktop.
- Remember to upload the amxmodx base package and the amxmodx counter-strike rar.
For Windows: addons/amxmodx/dlls/amxmodx_mm.dll
For Linux: addons/amxmodx/dlls/amxmodx_mm_i386.sohttps://www.amxmodx.org/downloads-new.php?branch=master&all=1
8.- Well ReGame is basically a modification of the game libraries, which aims to provide a more stable version than the original itself and an API to make addons / plugins.
https://github.com/s1lentq/ReGameDLL_CS/releases
Once downloaded go to the "bin", "linux32" or "win32" directory depending on your operating system, "cstrike" and copy the contents (including the dlls folder) and paste it into the cstrike directory, replace and merge everything.
Note: if you use metamod p38 and have Linux as OS, you will probably have difficulties with this, go to the addons/metamod folder and open the config.ini file (if it is not created, create it) and add if it is not written, the following: gamedll dlls/cs.so
Now that you know how to install modules, here are a few that you can install.
[REAPI]https://github.com/s1lentq/reapi/releases (Installed in addons/amxmodx/modules)
[REAUTHCHECK] : ReAuthCheck is a Metamod add-on that additionally verifies player authorization, performs a series of validation checks, thus improving server protection against third-party programs (forgeries).
ReAuthChecker provides protection against xfakeplayers and other emulators, reunion does not incorporate this protection, that's why it is necessary.You should set the mp_consistency cvar to " 1 " (otherwise some detection methods will not work).
https://dev-cs.ru/resources/63/download
If the server does not start, with root commands: (Linux)
You can also do it manually from ftp if you have sufficient permissions.
Spoilersudo chmod 775 /home/ogp_agent/OGP_User_Files/server/core.so
sudo chmod 775 /home/ogp_agent/OGP_User_Files/server/demoplayer.so
sudo chmod 775 /home/ogp_agent/OGP_User_Files/server/engine_i486.so
sudo chmod 775 /home/ogp_agent/OGP_User_Files/server/filesystem_stdio.so
sudo chmod 775 /home/ogp_agent/OGP_User_Files/server/hlds_linux
sudo chmod 775 /home/ogp_agent/OGP_User_Files/server/hltv
sudo chmod 775 /home/ogp_agent/OGP_User_Files/server/proxy.so
sudo chmod 775 /home/ogp_agent/OGP_User_Files/server/valve/dlls/director.so-
1
-
-
If you have the CSBD 6.2 plugins it is impossible for us to help you, you must contact @Mr.Love to offer you the plugin. In case you have a zp and it is free to use as for example (Zp 4.3, zp 5.0) I can provide you the plugin, also as a last resort would be that you share your sma core and I make the corresponding editions.
I hope your answer or to have helped you, greetings.
-
1
-
[Plugin] Math Problem (Support ALL ZP)
in Plugins
Posted
Name: Math Problem
Autor: Dakar
Version: 1.2
Description: The famous question system, you win random ammopacks if you answer correctly.
Installation:
1- math_problem.amxx file, put it in addons / amxmodx / plugins
2- Go to addons / amxmodx / configs / plugins.ini and add the following information:
math_problem.amxx
Commands: -
Cvars: Frequency_MathProblem (40.0 default)
In case of changes/errors contact me privately to solve your problem.
MATH.AMXX IS ONLY ZP 6.2
MATH2.AMXX IS ALL ZP (ZP 4.3, ZP 4.3 F5ixed, ZP 5.0.8)
math.amxx
math2.amxx