Alright, let's get straight to the point. The following will teach you how to create your own damage recording system that will reset every round. If you've ever played in ESEA,
this is extremely similar (if not exactly) like the .dmg command.
Let's begin with the top.
Code: Select all
#include <amxmodx>
#include <hamsandwich>
#define MAX_PLAYERS 32
#define IsPlayer(%1) (1<=%1<=g_iMaxPlayers)
// Time after round start the stats reset
#define RESET_DELAY 5.0
/* To store the damage, we need 2
indexes to be saved in each variable */
new iDamageTaken[MAX_PLAYERS+1]/*took x damage from*/[MAX_PLAYERS+1]
new iDamageGiven[MAX_PLAYERS+1]/*did x damage to*/[MAX_PLAYERS+1]
new iHitsTaken[MAX_PLAYERS+1]/*took x hits from*/[MAX_PLAYERS+1]
new iHitsGiven[MAX_PLAYERS+1]/*did x hits to*/[MAX_PLAYERS+1]
new b_CanTypeWhileAlive
new g_iMaxPlayers We also have RESET_DELAY. This is the time after round start that the damage count will be reset.
Most importantly, we have our two variables that eventually will be holding the damage amount that tells the plugin how much damage was received and taken for each player. The two array sizes (MAX_PLAYERS+1) are necessary to hold player-specific data.
We also have the HitsGiven variable, pretty self-explanatory.
You will see b_CanTypeWhileAlive in use later on.
Finally, there's g_iMaxPlayers which will be used in accordance with IsPlayer.
Let's move on, shall we?
Code: Select all
public plugin_init()
{
register_plugin( "Damage Saving Example", "1.0", "Wrecked" ) // :avast:
RegisterHam( Ham_TakeDamage, "player", "HAM_TakeDamage_Post", 1 )
register_clcmd( "say .zomggaben", "CMD_ShowDamage" )
register_logevent( "LEV_RoundStart", 2, "1=Round_Start" )
g_iMaxPlayers = get_maxplayers()
} g_iMaxPlayers obtains the maximum players, which will be used with IsPlayer, as said earlier.
Next!
Code: Select all
public HAM_TakeDamage_Post( victim, inflictor, attacker, float:damage, damagebits )
{
if( !IsPlayer( attacker ) ) // make sure it's not generic damage
return HAM_IGNORED;
iDamageTaken[victim][attacker] += pev( victim, pev_dmg_take )
iDamageGiven[attacker][victim] += pev( victim, pev_dmg_take )
iHitsTaken[victim][attacker]++ // adds 1
iHitsGiven[attacker][victim]++ // ...
return HAM_IGNORED;
} First off, you'll notice the IsPlayer being put to use. This macro makes sure that the specified attacker is a player, and not the map or other entity. Without this check, there's a strong possibility that an excessive amount of babies problems would be created.
The macro up top makes sure the player is within the player range, since each player is given an imaginary number on connect (correct me if I'm wrong, anybody), and we check to see if they're between 0 and 32 (legitimate player range).
Next, we have our two variables. These two will be holding the amount of damage given and recieved. The two indexes hold the players involved in the damage.
Also, pev( victim, pev_dmg_take ) is getting the ACTUAL amount of damage taken after taken into consideration the armor and other factors of the victim (thanks to Connor, Arkshine, and Bugsy).
Finally, there's iHitsTaken and iHitsGiven to record the amount of hits given and taken, just to make your life more difficult.
Alright, the following two blocks of code are going to be a bit more difficult to comprehend, but I'll try my best to explain them in simple terms.
Code: Select all
public CMD_ShowDamage( id )
{
if( is_user_alive( id ) && !b_CanTypeWhileAlive )
{
client_print( id, print_chat, "Hey! You can't do that right now!" )
return PLUGIN_CONTINUE;
}
new iPlayers[32]
new iNum
new plrid
get_players( iPlayers, iNum )
static name[32]
static message[108]
new CsTeams:idteam = cs_get_user_team( id )
for( new i = 0; i < iNum; i++ )
{
plrid = iPlayers[i]
if( id == plrid || cs_get_user_team( plrid ) == idteam )
{
continue;
}
get_user_name( plrid, name, 31 )
formatex(
message, 107, "Given: ( %i Damage / %i Hits ) Taken: ( %i Damage / %i Hits )",
iDamageGiven[id][plrid],
iHitsGiven[id][plrid],
iDamageTaken[id][plrid],
iHitsTaken[id][plrid]
)
client_print( id, print_chat, "[%s] %s", name, message )
}
return PLUGIN_CONTINUE;
} Now, the rest of the code may come off as a bit confusing if you're new. What I'm doing at first is getting the amount of players in-game and then declaring a new variable that will hold the name and message that will be sent.
I then initiate the for loop, declaring a new variable, making the conditional, and then the increment. This declares the new variable, then checks the conditional. If the conditional is true, it executes the code inside the block. After that, it executes the third param of the loop, and repeats from the 2nd param. When the conditional is false, it ends the for loop.
Inside the for loop, I check if the current player it's looping through is the player we're display it to, and if the player's team is the same as the player we're looping through. If it is, we continue to the next iteration (go back to the conditional) of the loop.
Otherwise, we get their name, and format a message that we'll be displaying to the player. It will look something like
Code: Select all
[Wrecked] Given: ( 49 Damage / 2 Hits ) Taken: ( 100 Damage / 4 Hits )We then display the message to the user (%s means replace it with a string), and return PLUGIN_CONTINUE to let the say command run through and let everyone else see what the original client command was.
Phewf! Next!
Code: Select all
public LEV_RoundStart()
{
#if defined RESET_DELAY
set_task( RESET_DELAY, "TASK_ResetStats" )
#else
TASK_ResetStats()
#endif
b_CanTypeWhileAlive = true
}
public TASK_ResetStats()
{
new iPlayers[32]
new iNum
new plrid
new otherid
get_players( iPlayers, iNum )
for( new i = 0; i < iNum; i++ )
{
plrid = iPlayers[i]
for( new j = 0; j < iNum; j++ )
{
otherid = iPlayers[j]
if( otherid == plrid )
{
continue;
}
iDamageTaken[plrid][otherid] = 0
iDamageGiven[plrid][otherid] = 0
iHitsTaken[plrid][otherid] = 0
iHitsGiven[plrid][otherid] = 0
}
}
b_CanTypeWhileAlive = false
} The RoundStart function sets the delay to reset the player data if it's defined. If it's not, the function automatically calls TASK_ResetStats to reset the player damage / hit data.
The bool lets players type .damage until the player stats are reset in the following function.
So, as you can see, it's a quite extensive function, as it has 2 for loops, but it really isn't much code at all.
What I'm doing is;
- Looping through each player
- Looping through each player every time we loop through a different player (yes, very memory extensive, let me know if you have a better solution)
- Resetting the damage and hit data in relation to each player with every bi-player combo possible.
If you have any personal questions on this function, let me know and I'll explain it more in-depth.
So, our final plugin should look something like this:
Code: Select all
#include <amxmodx>
#include <hamsandwich>
#include <cstrike>
#define MAX_PLAYERS 32
#define IsPlayer(%1) (1<=%1<=g_iMaxPlayers)
// Time after round start the stats reset
#define RESET_DELAY 5.0
/* To store the damage, we need 2
indexes to be saved in each variable */
new iDamageTaken[MAX_PLAYERS+1]/*took x damage from*/[MAX_PLAYERS+1]
new iDamageGiven[MAX_PLAYERS+1]/*did x damage to*/[MAX_PLAYERS+1]
new iHitsTaken[MAX_PLAYERS+1]/*took x hits from*/[MAX_PLAYERS+1]
new iHitsGiven[MAX_PLAYERS+1]/*did x hits to*/[MAX_PLAYERS+1]
new bool:b_CanTypeWhileAlive
new g_iMaxPlayers
public plugin_init()
{
register_plugin( "Damage Saving Example", "1.0", "Wrecked" ) // :avast:
RegisterHam( Ham_TakeDamage, "player", "HAM_TakeDamage_Post", 1 )
register_clcmd( "say .damage", "CMD_ShowDamage" )
register_logevent( "LEV_RoundStart", 2, "1=Round_Start" )
g_iMaxPlayers = get_maxplayers()
}
public LEV_RoundStart()
{
#if defined RESET_DELAY
set_task( RESET_DELAY, "TASK_ResetStats" )
#else
TASK_ResetStats()
#endif
b_CanTypeWhileAlive = true
}
public TASK_ResetStats()
{
new iPlayers[32]
new iNum
new plrid
new otherid
get_players( iPlayers, iNum )
for( new i = 0; i < iNum; i++ )
{
plrid = iPlayers[i]
for( new j = 0; j < iNum; j++ )
{
otherid = iPlayers[j]
if( otherid == plrid )
{
continue;
}
iDamageTaken[plrid][otherid] = 0
iDamageGiven[plrid][otherid] = 0
iHitsTaken[plrid][otherid] = 0
iHitsGiven[plrid][otherid] = 0
}
}
b_CanTypeWhileAlive = false
}
public HAM_TakeDamage_Post( victim, inflictor, attacker, float:damage, damagebits )
{
if( !IsPlayer( attacker ) ) // make sure it's not generic damage
return HAM_IGNORED;
iDamageTaken[victim][attacker] += pev( victim, pev_dmg_take ) // record the damage
iDamageGiven[attacker][victim] += pev( victim, pev_dmg_take ) // ...
iHitsTaken[victim][attacker]++ // adds 1
iHitsGiven[attacker][victim]++ // ...
return HAM_IGNORED;
}
public CMD_ShowDamage( id )
{
if( is_user_alive( id ) && !b_CanTypeWhileAlive )
{
client_print( id, print_chat, "Hey! You can't do that right now!" )
return PLUGIN_CONTINUE;
}
new iPlayers[32]
new iNum
new plrid
get_players( iPlayers, iNum )
static name[32]
static message[108]
new CsTeams:idteam = cs_get_user_team( id )
for( new i = 0; i < iNum; i++ )
{
plrid = iPlayers[i]
if( id == plrid || cs_get_user_team( plrid ) == idteam )
{
continue;
}
get_user_name( plrid, name, 31 )
formatex(
message, 107, "Given: ( %i Damage / %i Hits ) Taken: ( %i Damage / %i Hits )",
iDamageGiven[id][plrid],
iHitsGiven[id][plrid],
iDamageTaken[id][plrid],
iHitsTaken[id][plrid]
)
client_print( id, print_chat, "[%s] %s", name, message )
}
return PLUGIN_CONTINUE;
} The plugin has been compiled and tested, it works.
I'm aware that creating a triearray for the values would be very efficient, but I felt triearrays would be a little controversial for new scripters and a little confusing.
