Update Fun module (#421)

* Fun: Replace ENTINDEX with TypeConversion for consistency

* Fun: Add a class wrapping player's data

* Fun: Make TraceLine a post forward

Reason: as it is it breaks plugins hooking TraceLine because of the original game call is being superceded and other modules can't catch it. It looks like it's this way from the very start fun module has been introduced 13 years ago before. Fakemeta module comes a little later.

* Fun: Clean up code

* Fun: Toggle PlayerPreThink forward on demand

* Fun: Toggle TraceLine forward on demand

* Fun: Add HITZONE* constants for use with set/get_user_hitzone()

* Fun: Refactor a litte the player class

* Fun: Clean up a little more

* Fun: Fix typo in set_user_hitzones from previous commit
This commit is contained in:
Vincent Herbet 2018-08-30 19:16:19 +02:00 committed by GitHub
parent 6e9947b64f
commit 1a2dd9e7ea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 514 additions and 494 deletions

View File

@ -11,589 +11,463 @@
// Fun Module // Fun Module
// //
#include <string.h>
#include "fun.h" #include "fun.h"
#include <HLTypeConversion.h> #include <HLTypeConversion.h>
/*
JGHG says:
Ok this is what I use below, it may probably not be right with all natives etc but I try to maintain this style to natives.
Note that this is still very much subject to change, regarding return values etc!
(Ok I haven't checked all natives that they comply with this yet, this is just a model I'm working on and which I might implement soon.)
static cell AMX_NATIVE_CALL nativename(AMX *amx, cell *params) // nativename(argument1, argument2); = 2 params
{
// Description what this native does. <--- Description what this native does
// params[1] = argument1 <--- Description of each argument, so we don't have to allocate new variables and can
// params[2] = argument2 <--- use the ones in params[n] directly, to save some time.
// Check receiver and sender validity. <--- Check ents, maybe need to do this better and more proper later?
CHECK_PLAYER(params[1])
CHECK_PLAYER(params[2])
// Get * pointer.
edict_t *pPlayer = MF_GetPlayerEdict(params[1]); <--- Players require a different function than INDEXENT because of an HLSDK bug
return 1 <--- If native succeeded, return 1, if the native isn't supposed to return a specific value.
Note: Should be able to do: if (thenative()) and it should return false when it fails, and true when succeeds... is -1 treated as false, or is 0 a must?
}
*/
char g_bodyhits[33][33]; // where can the guy in the first dimension hit the people in the 2nd dimension? :-)
bool g_silent[33]; // used for set_user_footsteps()
HLTypeConversion TypeConversion; HLTypeConversion TypeConversion;
CPlayers Players;
// ######## Utils: // native get_client_listen(receiver, sender)
void FUNUTIL_ResetPlayer(int index) static cell AMX_NATIVE_CALL get_client_listening(AMX *amx, cell *params)
{ {
//MF_PrintSrvConsole("Resetting player index %d! maxclients: %d\n", index, gpGlobals->maxClients); enum args { arg_count, arg_receiver, arg_sender };
for (int i = 1; i <= gpGlobals->maxClients; i++) {
g_bodyhits[index][i] = (char)((1<<HITGROUP_GENERIC) | CHECK_PLAYER(params[arg_receiver]);
(1<<HITGROUP_HEAD) | CHECK_PLAYER(params[arg_sender]);
(1<<HITGROUP_CHEST) |
(1<<HITGROUP_STOMACH) | return GETCLIENTLISTENING(params[arg_receiver], params[arg_sender]);
(1<<HITGROUP_LEFTARM) |
(1<<HITGROUP_RIGHTARM)|
(1<<HITGROUP_LEFTLEG) |
(1<<HITGROUP_RIGHTLEG));
}
// Reset silent slippers
g_silent[index] = false;
} }
// ######## Natives: // native set_client_listen(receiver, sender, listen)
static cell AMX_NATIVE_CALL get_client_listening(AMX *amx, cell *params) // get_client_listening(receiver, sender); = 2 params static cell AMX_NATIVE_CALL set_client_listening(AMX *amx, cell *params)
{ {
// Gets who can listen to who. enum args { arg_count, arg_receiver, arg_sender, arg_listen };
// params[1] = receiver
// params[2] = sender
// Check receiver and sender validity. CHECK_PLAYER(params[arg_receiver]);
CHECK_PLAYER(params[1]); CHECK_PLAYER(params[arg_sender]);
CHECK_PLAYER(params[2]);
// GET- AND SETCLIENTLISTENING returns "qboolean", an int, probably 0 or 1... return SETCLIENTLISTENING(params[arg_receiver], params[arg_sender], params[arg_listen]);
return GETCLIENTLISTENING(params[1], params[2]);
} }
static cell AMX_NATIVE_CALL set_client_listening(AMX *amx, cell *params) // set_client_listening(receiver, sender, listen); = 3 params // native set_user_godmode(index, godmode = 0)
static cell AMX_NATIVE_CALL set_user_godmode(AMX *amx, cell *params)
{ {
// Sets who can listen to who. enum args { arg_count, arg_user, arg_godmode };
// params[1] = receiver
// params[2] = sender
// params[3] = listen
// Check receiver and sender validity. CHECK_PLAYER(params[arg_user]);
CHECK_PLAYER(params[1]);
CHECK_PLAYER(params[2]);
// Make a check on params[3] here later, and call run time error when it's wrong. const auto pPlayer = TypeConversion.id_to_edict(params[arg_user]);
// To do: find out the possible values to set (0, 1?)
// GET- AND SETCLIENTLISTENING returns "qboolean", an int, probably 0 or 1... pPlayer->v.takedamage = params[arg_godmode] != 0 ? DAMAGE_NO : DAMAGE_AIM;
return SETCLIENTLISTENING(params[1], params[2], params[3]);
}
static cell AMX_NATIVE_CALL set_user_godmode(AMX *amx, cell *params) // set_user_godmode(index, godmode = 0); = 2 params
{
/* Sets player godmode. If you want to disable godmode set only first parameter. */
// params[1] = index
// params[2] = godmode = 0
// Check index.
CHECK_PLAYER(params[1]);
// Get player pointer.
edict_t *pPlayer = TypeConversion.id_to_edict(params[1]);
if (params[2] == 1) {
// Enable godmode
pPlayer->v.takedamage = 0.0; // 0.0, the player doesn't seem to be able to get hurt.
}
else {
// Disable godmode
pPlayer->v.takedamage = 2.0; // 2.0 seems to be standard value?
}
return 1; return 1;
} }
static cell AMX_NATIVE_CALL get_user_godmode(AMX *amx, cell *params) // get_user_godmode(index); = 1 param // native get_user_godmode(index)
static cell AMX_NATIVE_CALL get_user_godmode(AMX *amx, cell *params)
{ {
/* Returns 1 if godmode is set. */ enum args { arg_count, arg_user };
// params[1] = index
// Check index. CHECK_PLAYER(params[arg_user]);
CHECK_PLAYER(params[1]);
// Get player pointer. const auto pPlayer = TypeConversion.id_to_edict(params[arg_user]);
edict_t *pPlayer = TypeConversion.id_to_edict(params[1]);
int godmode = 0; return pPlayer->v.takedamage == DAMAGE_NO;
if (pPlayer->v.takedamage == 0.0) {
// God mode is enabled
godmode = 1;
}
return godmode;
} }
static cell AMX_NATIVE_CALL give_item(AMX *amx, cell *params) // native give_item(index, const item[]); = 2 params // native give_item(index, const item[])
static cell AMX_NATIVE_CALL give_item(AMX *amx, cell *params)
{ {
/* Gives item to player, name of item can start enum args { arg_count, arg_index, arg_item };
* with weapon_, ammo_ and item_. This event
* is announced with proper message to all players. */
// params[1] = index
// params[2] = item...
// Check index. CHECK_PLAYER(params[arg_index]);
CHECK_PLAYER(params[1]);
// Get player pointer. auto itemLength = 0;
edict_t *pPlayer = TypeConversion.id_to_edict(params[1]); const auto item = MF_GetAmxString(amx, params[arg_item], 1, &itemLength);
// Create item entity pointer if (!itemLength
edict_t *pItemEntity; ||(strncmp(item, "weapon_", 7) != 0
&& strncmp(item, "ammo_", 5) != 0
// Make an "intstring" out of 2nd parameter && strncmp(item, "item_", 5) != 0
int length; && strncmp(item, "tf_weapon_", 10) != 0))
const char *szItem = MF_GetAmxString(amx, params[2], 1, &length); {
//check for valid item
if (strncmp(szItem, "weapon_", 7) &&
strncmp(szItem, "ammo_", 5) &&
strncmp(szItem, "item_", 5) &&
strncmp(szItem, "tf_weapon_", 10)
) {
return 0; return 0;
} }
//string_t item = MAKE_STRING(szItem); auto pEntity = CREATE_NAMED_ENTITY(ALLOC_STRING(item));
string_t item = ALLOC_STRING(szItem); // Using MAKE_STRING makes "item" contents get lost when we leave this scope! ALLOC_STRING seems to allocate properly...
// Create the entity, returns to pointer
pItemEntity = CREATE_NAMED_ENTITY(item);
if (FNullEnt(pItemEntity)) { if (FNullEnt(pEntity))
MF_LogError(amx, AMX_ERR_NATIVE, "Item \"%s\" failed to create", szItem); {
MF_LogError(amx, AMX_ERR_NATIVE, "Item \"%s\" failed to create", item);
return 0; return 0;
} }
//VARS(pItemEntity)->origin = VARS(pPlayer)->origin; // nice to do VARS(ent)->origin instead of ent->v.origin? :-I const auto pPlayer = TypeConversion.id_to_edict(params[arg_index]);
//I'm not sure, normally I use macros too =P
pItemEntity->v.origin = pPlayer->v.origin;
pItemEntity->v.spawnflags |= SF_NORESPAWN; //SF_NORESPAWN;
MDLL_Spawn(pItemEntity); pEntity->v.origin = pPlayer->v.origin;
pEntity->v.spawnflags |= SF_NORESPAWN;
int save = pItemEntity->v.solid; MDLL_Spawn(pEntity);
MDLL_Touch(pItemEntity, ENT(pPlayer)); const auto oldSolid = pEntity->v.solid;
//The problem with the original give_item was the MDLL_Touch(pEntity, pPlayer);
// item was not removed. I had tried this but it
// did not work. OLO's implementation is better.
/*
int iEnt = ENTINDEX(pItemEntity->v.owner);
if (iEnt > 32 || iEnt <1 ) {
MDLL_Think(pItemEntity);
}*/
if (pItemEntity->v.solid == save) { if (pEntity->v.solid == oldSolid)
REMOVE_ENTITY(pItemEntity); {
//the function did not fail - we're just deleting the item REMOVE_ENTITY(pEntity); // The function did not fail - we're just deleting the item
return -1; return -1;
} }
return ENTINDEX(pItemEntity); return TypeConversion.edict_to_id(pEntity);
} }
static cell AMX_NATIVE_CALL spawn(AMX *amx, cell *params) // spawn(id) = 1 param // native spawn(index)
static cell AMX_NATIVE_CALL spawn(AMX *amx, cell *params)
{ {
// Spawns an entity, this can be a user/player -> spawns at spawnpoints, or created entities seems to need this as a final "kick" into the game? :-) enum args { arg_count, arg_index };
// params[1] = entity to spawn
CHECK_ENTITY(params[1]); CHECK_ENTITY(params[arg_index]);
edict_t *pEnt = TypeConversion.id_to_edict(params[1]); const auto pEntity = TypeConversion.id_to_edict(params[arg_index]);
MDLL_Spawn(pEnt); MDLL_Spawn(pEntity);
return 1; return 1;
} }
static cell AMX_NATIVE_CALL set_user_health(AMX *amx, cell *params) // set_user_health(index, health); = 2 arguments // native set_user_health(index, health)
static cell AMX_NATIVE_CALL set_user_health(AMX *amx, cell *params)
{ {
// Sets user health. If health is 0 and below, also kill... enum args { arg_count, arg_index, arg_health };
// params[1] = index
// params[2] = health
// Check index CHECK_PLAYER(params[arg_index]);
CHECK_PLAYER(params[1]);
// Fetch player pointer const auto pPlayer = TypeConversion.id_to_edict(params[arg_index]);
edict_t *pPlayer = TypeConversion.id_to_edict(params[1]); const auto health = float(params[arg_health]);
// Kill if health too low. if (health > 0.0f)
if (params[2] > 0) {
pPlayer->v.health = float(params[2]); pPlayer->v.health = health;
}
else else
{
MDLL_ClientKill(pPlayer); MDLL_ClientKill(pPlayer);
}
return 1; return 1;
} }
static cell AMX_NATIVE_CALL set_user_frags(AMX *amx, cell *params) // set_user_frags(index, frags); = 2 arguments // native set_user_frags(index, frags)
static cell AMX_NATIVE_CALL set_user_frags(AMX *amx, cell *params)
{ {
// Sets user frags. enum args { arg_count, arg_index, arg_frags };
// params[1] = index
// params[2] = frags
// Check index CHECK_PLAYER(params[arg_index]);
CHECK_PLAYER(params[1]);
// Fetch player pointer const auto pPlayer = TypeConversion.id_to_edict(params[arg_index]);
edict_t *pPlayer = TypeConversion.id_to_edict(params[1]);
pPlayer->v.frags = params[2]; pPlayer->v.frags = float(params[arg_frags]);
return 1; return 1;
} }
static cell AMX_NATIVE_CALL set_user_armor(AMX *amx, cell *params) // set_user_armor(index, armor); = 2 arguments // native set_user_armor(index, armor)
static cell AMX_NATIVE_CALL set_user_armor(AMX *amx, cell *params)
{ {
// Sets user armor. enum args { arg_count, arg_index, arg_armor };
// params[1] = index
// params[2] = armor
// Check index CHECK_PLAYER(params[arg_index]);
CHECK_PLAYER(params[1]);
// Fetch player pointer const auto pPlayer = TypeConversion.id_to_edict(params[arg_index]);
edict_t *pPlayer = TypeConversion.id_to_edict(params[1]);
pPlayer->v.armorvalue = params[2]; pPlayer->v.armorvalue = float(params[arg_armor]);
return 1; return 1;
} }
static cell AMX_NATIVE_CALL set_user_origin(AMX *amx, cell *params) // set_user_origin(index, origin[3]); = 2 arguments // native set_user_origin(index, const origin[3])
static cell AMX_NATIVE_CALL set_user_origin(AMX *amx, cell *params)
{ {
// Sets user origin. enum args { arg_count, arg_index, arg_origin };
// params[1] = index
// params[2] = origin
// Check index CHECK_PLAYER(params[arg_index]);
CHECK_PLAYER(params[1]);
// Fetch player pointer auto pPlayer = TypeConversion.id_to_edict(params[arg_index]);
edict_t *pPlayer = TypeConversion.id_to_edict(params[1]); const auto pVector = MF_GetAmxAddr(amx, params[arg_origin]);
cell *newVectorCell = MF_GetAmxAddr(amx, params[2]);
SET_SIZE(pPlayer, pPlayer->v.mins, pPlayer->v.maxs); SET_SIZE(pPlayer, pPlayer->v.mins, pPlayer->v.maxs);
SET_ORIGIN(pPlayer, Vector((float)newVectorCell[0], (float)newVectorCell[1], (float)newVectorCell[2])); SET_ORIGIN(pPlayer, Vector(float(pVector[0]), float(pVector[1]), float(pVector[2])));
return 1; return 1;
} }
static cell AMX_NATIVE_CALL set_user_rendering(AMX *amx, cell *params) // set_user_rendering(index, fx = kRenderFxNone, r = 255, g = 255, b = 255, render = kRenderNormal, amount = 16); = 7 arguments // native set_user_rendering(index, fx = kRenderFxNone, r = 0, g = 0, b = 0, render = kRenderNormal, amount = 0)
static cell AMX_NATIVE_CALL set_user_rendering(AMX *amx, cell *params)
{ {
// Sets user rendering. enum args { arg_count, arg_index, arg_fx, arg_red, arg_green, arg_blue, arg_render, arg_amount };
// params[1] = index
// params[2] = fx
// params[3] = r
// params[4] = g
// params[5] = b
// params[6] = render
// params[7] = amount
// Check index CHECK_PLAYER(params[arg_index]);
CHECK_PLAYER(params[1]);
// Fetch player pointer auto pPlayer = TypeConversion.id_to_edict(params[arg_index]);
edict_t *pPlayer = TypeConversion.id_to_edict(params[1]);
pPlayer->v.renderfx = params[2]; pPlayer->v.renderfx = params[arg_fx];
Vector newVector = Vector(float(params[3]), float(params[4]), float(params[5])); pPlayer->v.rendercolor = Vector(float(params[arg_red]), float(params[arg_green]), float(params[arg_blue]));
pPlayer->v.rendercolor = newVector; pPlayer->v.rendermode = params[arg_render];
pPlayer->v.rendermode = params[6]; pPlayer->v.renderamt = float(params[arg_amount]);
pPlayer->v.renderamt = params[7];
return 1; return 1;
} }
static cell AMX_NATIVE_CALL get_user_rendering(AMX *amx, cell *params) // get_user_rendering(index, &fx = kRenderFxNone, &r = 0, &g = 0, &b = 0, &render = kRenderNormal, &amount = 0); = 7 arguments // get_user_rendering(index, &fx = kRenderFxNone, &r = 0, &g = 0, &b = 0, &render = kRenderNormal, &amount = 0);
static cell AMX_NATIVE_CALL get_user_rendering(AMX *amx, cell *params)
{ {
// Gets user rendering. enum args { arg_count, arg_index, arg_fx, arg_red, arg_green, arg_blue, arg_render, arg_amount };
// params[1] = index
// params[2] = fx
// params[3] = r
// params[4] = g
// params[5] = b
// params[6] = render
// params[7] = amount
// Check index CHECK_PLAYER(params[arg_index]);
CHECK_PLAYER(params[1]);
// Fetch player pointer auto pPlayer = TypeConversion.id_to_edict(params[arg_index]);
edict_t *pPlayer = TypeConversion.id_to_edict(params[1]);
*MF_GetAmxAddr(amx, params[2]) = pPlayer->v.renderfx; *MF_GetAmxAddr(amx, params[arg_fx]) = pPlayer->v.renderfx;
*MF_GetAmxAddr(amx, params[3]) = pPlayer->v.rendercolor[0]; *MF_GetAmxAddr(amx, params[arg_red]) = pPlayer->v.rendercolor[0];
*MF_GetAmxAddr(amx, params[4]) = pPlayer->v.rendercolor[1]; *MF_GetAmxAddr(amx, params[arg_green]) = pPlayer->v.rendercolor[1];
*MF_GetAmxAddr(amx, params[5]) = pPlayer->v.rendercolor[2]; *MF_GetAmxAddr(amx, params[arg_blue]) = pPlayer->v.rendercolor[2];
*MF_GetAmxAddr(amx, params[6]) = pPlayer->v.rendermode; *MF_GetAmxAddr(amx, params[arg_render]) = pPlayer->v.rendermode;
*MF_GetAmxAddr(amx, params[7]) = pPlayer->v.renderamt; *MF_GetAmxAddr(amx, params[arg_amount]) = pPlayer->v.renderamt;
return 1; return 1;
} }
static cell AMX_NATIVE_CALL set_user_maxspeed(AMX *amx, cell *params) // set_user_maxspeed(index, Float:speed = -1.0) = 2 arguments // native set_user_maxspeed(index, Float:speed = -1.0)
static cell AMX_NATIVE_CALL set_user_maxspeed(AMX *amx, cell *params)
{ {
// Sets user maxspeed. enum args { arg_count, arg_index, arg_speed };
// params[1] = index
// params[2] = speed (should be -1.0 if not specified) (JGHG: unspecified parameters seems to always be -1.0!)
REAL fNewSpeed = amx_ctof(params[2]); CHECK_PLAYER(params[arg_index]);
// Check index const auto pPlayer = TypeConversion.id_to_edict(params[arg_index]);
CHECK_PLAYER(params[1]); const auto newSpeed = amx_ctof(params[arg_speed]);
// Fetch player pointer SETCLIENTMAXSPEED(pPlayer, newSpeed);
edict_t *pPlayer = TypeConversion.id_to_edict(params[1]); pPlayer->v.maxspeed = newSpeed;
SETCLIENTMAXSPEED(pPlayer, fNewSpeed);
pPlayer->v.maxspeed = fNewSpeed;
return 1; return 1;
} }
static cell AMX_NATIVE_CALL get_user_maxspeed(AMX *amx, cell *params) // Float:get_user_maxspeed(index) = 1 argument // native Float:get_user_maxspeed(index)
static cell AMX_NATIVE_CALL get_user_maxspeed(AMX *amx, cell *params)
{ {
// Gets user maxspeed. enum args { arg_count, arg_index };
// params[1] = index
// Check index CHECK_PLAYER(params[arg_index]);
CHECK_PLAYER(params[1]);
// Fetch player pointer const auto pPlayer = TypeConversion.id_to_edict(params[arg_index]);
edict_t *pPlayer = TypeConversion.id_to_edict(params[1]);
return amx_ftoc(pPlayer->v.maxspeed); return amx_ftoc(pPlayer->v.maxspeed);
} }
static cell AMX_NATIVE_CALL set_user_gravity(AMX *amx, cell *params) // set_user_gravity(index, Float:gravity = 1.0) = 2 arguments // native set_user_gravity(index, Float:gravity = 1.0)
static cell AMX_NATIVE_CALL set_user_gravity(AMX *amx, cell *params)
{ {
// Sets user gravity. enum args { arg_count, arg_index, arg_gravity };
// params[1] = index
// params[2] = gravity (=-1.0)
// Check index
CHECK_PLAYER(params[1]);
// Fetch player pointer CHECK_PLAYER(params[arg_index]);
edict_t *pPlayer = TypeConversion.id_to_edict(params[1]);
pPlayer->v.gravity = amx_ctof(params[2]); const auto pPlayer = TypeConversion.id_to_edict(params[arg_index]);
pPlayer->v.gravity = amx_ctof(params[arg_gravity]);
return 1; return 1;
} }
static cell AMX_NATIVE_CALL get_user_gravity(AMX *amx, cell *params) // Float:get_user_gravity(index) = 1 argument // native Float:get_user_gravity(index)
static cell AMX_NATIVE_CALL get_user_gravity(AMX *amx, cell *params)
{ {
// Gets user gravity. enum args { arg_count, arg_index };
// params[1] = index
// Check index CHECK_PLAYER(params[arg_index]);
CHECK_PLAYER(params[1]);
// Fetch player pointer const auto pPlayer = TypeConversion.id_to_edict(params[arg_index]);
edict_t *pPlayer = TypeConversion.id_to_edict(params[1]);
return amx_ftoc(pPlayer->v.gravity); return amx_ftoc(pPlayer->v.gravity);
} }
static cell AMX_NATIVE_CALL set_user_hitzones(AMX *amx, cell *params) // set_user_hitzones(index = 0, target = 0, body = 255); = 3 arguments // native set_user_hitzones(index = 0, target = 0, body = HITZONES_DEFAULT)
static cell AMX_NATIVE_CALL set_user_hitzones(AMX *amx, cell *params)
{ {
// Sets user hitzones. enum args { arg_count, arg_attacker, arg_target, arg_hitzones };
// params[1] = the one(s) who shoot(s), shooter
int shooter = params[1];
// params[2] = the one getting hit const int attacker = params[arg_attacker];
int gettingHit = params[2]; const int target = params[arg_target];
const int hitzones = params[arg_hitzones];
// params[3] = specified hit zones if (attacker == 0 && target == 0)
int hitzones = params[3]; {
Players.SetEveryoneBodyHits(hitzones);
//set_user_hitzones(id, 0, 0) // Makes ID not able to shoot EVERYONE - id can shoot on 0 (all) at 0
//set_user_hitzones(0, id, 0) // Makes EVERYONE not able to shoot ID - 0 (all) can shoot id at 0
if (shooter == 0 && gettingHit == 0) {
for (int i = 1; i <= gpGlobals->maxClients; i++) {
for (int j = 1; j <= gpGlobals->maxClients; j++) {
g_bodyhits[i][j] = hitzones;
} }
//g_zones_toHit[i] = hitzones; else if (attacker == 0 && target != 0)
//g_zones_getHit[i] = hitzones; {
}
}
else if (shooter == 0 && gettingHit != 0) {
// "All" shooters, target (gettingHit) should be existing player id
CHECK_PLAYER(gettingHit);
// Where can all hit gettingHit?
for (int i = 1; i <= gpGlobals->maxClients; i++)
g_bodyhits[i][gettingHit] = hitzones;
}
else if (shooter != 0 && gettingHit == 0) {
// Shooter can hit all in bodyparts.
CHECK_PLAYER(shooter);
for (int i = 1; i <= gpGlobals->maxClients; i++)
g_bodyhits[shooter][i] = hitzones;
}
else {
// Specified, where can player A hit player B?
CHECK_PLAYER(shooter);
CHECK_PLAYER(gettingHit);
g_bodyhits[shooter][gettingHit] = hitzones;
}
return 1;
}
static cell AMX_NATIVE_CALL get_user_hitzones(AMX *amx, cell *params) // get_user_hitzones(index, target); = 2 arguments
{
int shooter = params[1];
CHECK_PLAYER(shooter);
int target = params[2];
CHECK_PLAYER(target); CHECK_PLAYER(target);
return g_bodyhits[shooter][target];
}
static cell AMX_NATIVE_CALL set_user_noclip(AMX *amx, cell *params) // set_user_noclip(index, noclip = 0); = 2 arguments Players.SetAttackersBodyHits(target, hitzones);
{ }
// Sets user to no clipping mode. else if (attacker != 0 && target == 0)
// params[1] = index {
// params[2] = no clip or not... CHECK_PLAYER(attacker);
// Check index Players.SetTargetsBodyHits(attacker, hitzones);
CHECK_PLAYER(params[1]); }
// Fetch player pointer
edict_t *pPlayer = TypeConversion.id_to_edict(params[1]);
if (params[2] == 1)
pPlayer->v.movetype = MOVETYPE_NOCLIP;
else else
pPlayer->v.movetype = MOVETYPE_WALK; {
CHECK_PLAYER(attacker);
CHECK_PLAYER(target);
Players.SetBodyHits(attacker, target, hitzones);
}
g_pengfuncsTable_Post->pfnTraceLine = Players.HaveBodyHits() ? TraceLine_Post : nullptr;
return 1; return 1;
} }
static cell AMX_NATIVE_CALL get_user_noclip(AMX *amx, cell *params) // get_user_noclip(index); = 1 argument // native get_user_hitzones(index, target)
static cell AMX_NATIVE_CALL get_user_hitzones(AMX *amx, cell *params)
{ {
// Gets user noclip. enum args { arg_count, arg_attacker, arg_target };
// params[1] = index
// Check index const auto attacker = params[arg_attacker];
CHECK_PLAYER(params[1]);
// Fetch player pointer CHECK_PLAYER(attacker);
edict_t *pPlayer = TypeConversion.id_to_edict(params[1]);
const auto target = params[arg_target];
CHECK_PLAYER(target);
return Players[attacker].GetBodyHits(target);
}
// native set_user_noclip(index, noclip = 0)
static cell AMX_NATIVE_CALL set_user_noclip(AMX *amx, cell *params)
{
enum args { arg_count, arg_index, arg_noclip };
CHECK_PLAYER(params[arg_index]);
const auto pPlayer = TypeConversion.id_to_edict(params[arg_index]);
pPlayer->v.movetype = params[arg_noclip] != 0 ? MOVETYPE_NOCLIP : MOVETYPE_WALK;
return 1;
}
// native get_user_noclip(index)
static cell AMX_NATIVE_CALL get_user_noclip(AMX *amx, cell *params)
{
enum args { arg_count, arg_index };
CHECK_PLAYER(params[arg_index]);
const auto pPlayer = TypeConversion.id_to_edict(params[arg_index]);
return pPlayer->v.movetype == MOVETYPE_NOCLIP; return pPlayer->v.movetype == MOVETYPE_NOCLIP;
} }
// JustinHoMi made this one originally // native set_user_footsteps(id, set = 1)
static cell AMX_NATIVE_CALL set_user_footsteps(AMX *amx, cell *params) // set_user_footsteps(id, set = 1); = 2 params static cell AMX_NATIVE_CALL set_user_footsteps(AMX *amx, cell *params)
{ {
// Gives player silent footsteps. enum args { arg_count, arg_index, arg_footsteps };
// if set=0 it will return footsteps to normal
// params[1] = index of player
// params[2] = 0 = normal footstep sound, 1 = silent slippers
// Check index const auto index = params[arg_index];
CHECK_PLAYER(params[1]);
// Fetch player pointer CHECK_PLAYER(index);
edict_t *pPlayer = TypeConversion.id_to_edict(params[1]);
if (params[2]) { const auto pPlayer = TypeConversion.id_to_edict(index);
if (params[arg_footsteps] != 0)
{
pPlayer->v.flTimeStepSound = 999; pPlayer->v.flTimeStepSound = 999;
g_silent[params[1]] = true; Players[index].SetSilentFootsteps(true);
g_pFunctionTable->pfnPlayerPreThink = PlayerPreThink;
} }
else { else
{
pPlayer->v.flTimeStepSound = STANDARDTIMESTEPSOUND; pPlayer->v.flTimeStepSound = STANDARDTIMESTEPSOUND;
g_silent[params[1]] = false; Players[index].SetSilentFootsteps(false);
if (g_pFunctionTable->pfnPlayerPreThink && !Players.HaveSilentFootsteps())
{
g_pFunctionTable->pfnPlayerPreThink = nullptr;
}
} }
return 1; return 1;
} }
// native get_user_footsteps(index)
static cell AMX_NATIVE_CALL get_user_footsteps(AMX *amx, cell *params) static cell AMX_NATIVE_CALL get_user_footsteps(AMX *amx, cell *params)
{ {
CHECK_PLAYER(params[1]); enum args { arg_count, arg_index };
return g_silent[params[1]]; const auto index = params[arg_index];
CHECK_PLAYER(index);
return Players[index].HasSilentFootsteps();
} }
// SidLuke // native strip_user_weapons(index)
static cell AMX_NATIVE_CALL strip_user_weapons(AMX *amx, cell *params) // index static cell AMX_NATIVE_CALL strip_user_weapons(AMX *amx, cell *params)
{ {
CHECK_PLAYER(params[1]); enum args { arg_count, arg_index };
edict_t* pPlayer = TypeConversion.id_to_edict(params[1]); const auto index = params[arg_index];
string_t item = MAKE_STRING("player_weaponstrip"); CHECK_PLAYER(index);
edict_t *pent = CREATE_NAMED_ENTITY(item);
if (FNullEnt(pent)) const auto pPlayer = TypeConversion.id_to_edict(index);
const auto pEntity = CREATE_NAMED_ENTITY(MAKE_STRING("player_weaponstrip"));
if (FNullEnt(pEntity))
{ {
return 0; return 0;
} }
MDLL_Spawn(pent); MDLL_Spawn(pEntity);
MDLL_Use(pent, pPlayer); MDLL_Use(pEntity, pPlayer);
REMOVE_ENTITY(pent); REMOVE_ENTITY(pEntity);
*reinterpret_cast<int *>(MF_PlayerPropAddr(params[1], Player_CurrentWeapon)) = 0; *reinterpret_cast<int *>(MF_PlayerPropAddr(index, Player_CurrentWeapon)) = 0;
return 1; return 1;
} }
AMX_NATIVE_INFO fun_Exports[] = {
{"get_client_listen", get_client_listening}, AMX_NATIVE_INFO fun_Exports[] =
{"set_client_listen", set_client_listening}, {
{"set_user_godmode", set_user_godmode}, { "get_client_listen" , get_client_listening },
{"get_user_godmode", get_user_godmode}, { "set_client_listen" , set_client_listening },
{"set_user_health", set_user_health}, { "set_user_godmode" , set_user_godmode },
{"give_item", give_item}, { "get_user_godmode" , get_user_godmode },
{"spawn", spawn}, { "set_user_health" , set_user_health },
{"set_user_frags", set_user_frags}, { "give_item" , give_item },
{"set_user_armor", set_user_armor}, { "spawn" , spawn },
{"set_user_origin", set_user_origin}, { "set_user_frags" , set_user_frags },
{"set_user_rendering", set_user_rendering}, { "set_user_armor" , set_user_armor },
{"get_user_rendering", get_user_rendering}, { "set_user_origin" , set_user_origin },
{"set_user_maxspeed", set_user_maxspeed}, { "set_user_rendering", set_user_rendering },
{"get_user_maxspeed", get_user_maxspeed}, { "get_user_rendering", get_user_rendering },
{"set_user_gravity", set_user_gravity}, { "set_user_maxspeed" , set_user_maxspeed },
{"get_user_gravity", get_user_gravity}, { "get_user_maxspeed" , get_user_maxspeed },
{"get_user_footsteps", get_user_footsteps}, { "set_user_gravity" , set_user_gravity },
{"set_user_hitzones", set_user_hitzones}, { "get_user_gravity" , get_user_gravity },
{"get_user_hitzones", get_user_hitzones}, { "get_user_footsteps", get_user_footsteps },
{"set_user_noclip", set_user_noclip}, { "set_user_hitzones" , set_user_hitzones },
{"get_user_noclip", get_user_noclip}, { "get_user_hitzones" , get_user_hitzones },
{"set_user_footsteps", set_user_footsteps}, { "set_user_noclip" , set_user_noclip },
{"strip_user_weapons", strip_user_weapons}, { "get_user_noclip" , get_user_noclip },
/////////////////// <--- 19 chars max in current small version { "set_user_footsteps", set_user_footsteps },
{NULL, NULL} { "strip_user_weapons", strip_user_weapons },
{ nullptr , nullptr }
}; };
/******************************************************************************************/
void PlayerPreThink(edict_t *pEntity) void PlayerPreThink(edict_t *pEntity)
{ {
if (g_silent[ENTINDEX(pEntity)]) { const auto index = TypeConversion.edict_to_id(pEntity);
if (Players[index].HasSilentFootsteps())
{
pEntity->v.flTimeStepSound = 999; pEntity->v.flTimeStepSound = 999;
RETURN_META(MRES_HANDLED); RETURN_META(MRES_HANDLED);
} }
@ -603,76 +477,51 @@ void PlayerPreThink(edict_t *pEntity)
int ClientConnect(edict_t *pPlayer, const char *pszName, const char *pszAddress, char szRejectReason[128]) int ClientConnect(edict_t *pPlayer, const char *pszName, const char *pszAddress, char szRejectReason[128])
{ {
// Reset stuff: const auto index = TypeConversion.edict_to_id(pPlayer);
FUNUTIL_ResetPlayer(ENTINDEX(pPlayer));
Players[index].Clear();
RETURN_META_VALUE(MRES_IGNORED, 0); RETURN_META_VALUE(MRES_IGNORED, 0);
} }
void TraceLine(const float *v1, const float *v2, int fNoMonsters, edict_t *shooter, TraceResult *ptr) { void TraceLine_Post(const float *v1, const float *v2, int fNoMonsters, edict_t *shooter, TraceResult *ptr)
TRACE_LINE(v1, v2, fNoMonsters, shooter, ptr); {
if ( ptr->pHit && (ptr->pHit->v.flags& (FL_CLIENT | FL_FAKECLIENT)) if (ptr->pHit && (ptr->pHit->v.flags & (FL_CLIENT | FL_FAKECLIENT))
&& shooter && (shooter->v.flags & (FL_CLIENT | FL_FAKECLIENT)) ) { && shooter && (shooter->v.flags & (FL_CLIENT | FL_FAKECLIENT)) )
int shooterIndex = ENTINDEX(shooter); {
if ( !(g_bodyhits[shooterIndex][ENTINDEX(ptr->pHit)] & (1<<ptr->iHitgroup)) ) const auto shooterIndex = TypeConversion.edict_to_id(shooter);
const auto targetIndex = TypeConversion.edict_to_id(ptr->pHit);
if (!(Players[shooterIndex].GetBodyHits(targetIndex) & (1 << ptr->iHitgroup)))
{
ptr->flFraction = 1.0; ptr->flFraction = 1.0;
RETURN_META(MRES_HANDLED);
} }
RETURN_META(MRES_SUPERCEDE); }
RETURN_META(MRES_IGNORED);
} }
//int g_hitIndex, g_canTargetGetHit, g_canShooterHitThere;
//void TraceLine(const float *v1, const float *v2, int fNoMonsters, edict_t *shooter, TraceResult *ptr) {
// if (!pentToSkip || (pentToSkip->v.flags & (FL_CLIENT | FL_FAKECLIENT)) == false || pentToSkip->v.deadflag != DEAD_NO)
// RETURN_META(MRES_IGNORED);
//
// TRACE_LINE(v1, v2, fNoMonsters, shooter, ptr); // Filter shooter
//
// if (!ptr->pHit || (ptr->pHit->v.flags & (FL_CLIENT | FL_FAKECLIENT)) == false )
// RETURN_META(MRES_SUPERCEDE);
//
// g_hitIndex = ENTINDEX(ptr->pHit);
// //bool blocked = false;
// g_canTargetGetHit = g_zones_getHit[g_hitIndex] & (1 << ptr->iHitgroup);
// g_canShooterHitThere = g_zones_toHit[ENTINDEX(shooter)] & (1 << ptr->iHitgroup);
//
// if (!g_canTargetGetHit || !g_canShooterHitThere) {
// ptr->flFraction = 1.0; // set to not hit anything (1.0 = shot doesn't hit anything)
// //blocked = true;
// }
// /*
// if (blocked) {
// MF_PrintSrvConsole("%s was blocked from hitting %s: %d and %d\n", MF_GetPlayerName(ENTINDEX(pentToSkip)), MF_GetPlayerName(hitIndex), canTargetGetHit, canShooterHitThere);
// }
// else {
// MF_PrintSrvConsole("%s was NOT blocked from hitting %s: %d and %d\n", MF_GetPlayerName(ENTINDEX(pentToSkip)), MF_GetPlayerName(hitIndex), canTargetGetHit, canShooterHitThere);
// }
// */
//
// RETURN_META(MRES_SUPERCEDE);
//}
void OnAmxxAttach() void OnAmxxAttach()
{ {
MF_AddNatives(fun_Exports); MF_AddNatives(fun_Exports);
} }
// The content of OnPluginsLoaded() was moved from OnAmxxAttach with AMXx 1.5 because for some reason gpGlobals->maxClients wasn't void OnPluginsLoaded()
// initialized to its proper value until some time after OnAmxxAttach(). In OnAmxxAttach() it always showed 0. /JGHG {
void OnPluginsLoaded() { Players.Clear();
// Reset stuff - hopefully this should
for (int i = 1; i <= gpGlobals->maxClients; i++) {
// Reset all hitzones
FUNUTIL_ResetPlayer(i);
}
TypeConversion.init(); TypeConversion.init();
g_pFunctionTable->pfnPlayerPreThink = nullptr;
g_pengfuncsTable_Post->pfnTraceLine = nullptr;
} }
/*
void ClientConnectFakeBot(int index) void ServerDeactivate()
{ {
FUNUTIL_ResetPlayer(index); g_pFunctionTable->pfnPlayerPreThink = nullptr;
//MF_Log("A bot connects, forwarded to fun! The bot is %d!", index); g_pengfuncsTable_Post->pfnTraceLine = nullptr;
//CPlayer* player;
RETURN_META(MRES_IGNORED);
} }
*/

View File

@ -11,7 +11,9 @@
// Fun Module // Fun Module
// //
#include "amxxmodule.h" #pragma once
#include <amxxmodule.h>
// Fun-specific defines below // Fun-specific defines below
#define GETCLIENTLISTENING (*g_engfuncs.pfnVoice_GetClientListening) #define GETCLIENTLISTENING (*g_engfuncs.pfnVoice_GetClientListening)
@ -29,19 +31,181 @@
#define HITGROUP_RIGHTARM 5 // 32 #define HITGROUP_RIGHTARM 5 // 32
#define HITGROUP_LEFTLEG 6 // 64 #define HITGROUP_LEFTLEG 6 // 64
#define HITGROUP_RIGHTLEG 7 // 128 #define HITGROUP_RIGHTLEG 7 // 128
#define HITGROUP_MAX 8
extern DLL_FUNCTIONS *g_pFunctionTable;
extern enginefuncs_t *g_pengfuncsTable_Post;
void PlayerPreThink(edict_t *pEntity);
void TraceLine_Post(const float *v1, const float *v2, int fNoMonsters, edict_t *shooter, TraceResult *ptr);
static const auto kHitGroupsBits = (1 << HITGROUP_MAX) - 1;
static const auto kMaxClients = 32u;
class CPlayer
{
public:
CPlayer()
{
Clear();
}
public:
bool HasBodyHits() const
{
for (auto i = 1; i <= gpGlobals->maxClients; ++i)
{
if (GetBodyHits(i) != kHitGroupsBits)
{
return true;
}
}
return false;
}
int GetBodyHits(const int other) const
{
return bodyHits_[other];
}
void SetBodyHits(const int other, const int flags)
{
bodyHits_[other] = flags;
}
void SetBodyHits(const int flags)
{
memset(bodyHits_, flags, sizeof bodyHits_);
}
public:
bool HasSilentFootsteps() const
{
return silentFootsteps_;
}
void SetSilentFootsteps(const bool state)
{
silentFootsteps_ = state;
}
public:
void Clear()
{
SetBodyHits(kHitGroupsBits);
SetSilentFootsteps(false);
}
private:
int bodyHits_[kMaxClients + 1] {};
bool silentFootsteps_ {};
};
class CPlayers
{
using Internal = CPlayer;
public:
bool HaveBodyHits() const
{
for (auto i = 1; i <= gpGlobals->maxClients; ++i)
{
if (players_[i].HasBodyHits())
{
return true;
}
}
return false;
}
void SetBodyHits(const int attacker, const int target, const int flags)
{
players_[attacker].SetBodyHits(target, flags);
}
void SetTargetsBodyHits(const int attacker, const int flags)
{
players_[attacker].SetBodyHits(flags);
}
void SetAttackersBodyHits(const int target, const int flags)
{
for (auto i = 1; i <= gpGlobals->maxClients; ++i)
{
players_[i].SetBodyHits(target, flags);
}
}
void SetEveryoneBodyHits(const int flags)
{
for (auto i = 1; i <= gpGlobals->maxClients; ++i)
{
players_[i].SetBodyHits(flags);
}
}
public:
bool HaveSilentFootsteps() const
{
for (auto i = 1; i <= gpGlobals->maxClients; ++i)
{
if (players_[i].HasSilentFootsteps())
{
return true;
}
}
return false;
}
public:
void Clear()
{
for (auto i = 1; i <= gpGlobals->maxClients; ++i)
{
players_[i].Clear();
}
}
public:
Internal& operator [](const size_t index)
{
return players_[index];
}
const Internal& operator [](const size_t index) const
{
return players_[index];
}
private:
Internal players_[kMaxClients + 1];
};
#define CHECK_ENTITY(x) \ #define CHECK_ENTITY(x) \
if (x < 0 || x > gpGlobals->maxEntities) { \ if ((x) < 0 || (x) > gpGlobals->maxEntities) { \
MF_LogError(amx, AMX_ERR_NATIVE, "Entity out of range (%d)", x); \ MF_LogError(amx, AMX_ERR_NATIVE, "Entity out of range (%d)", x); \
return 0; \ return 0; \
} else { \ } else { \
if (x <= gpGlobals->maxClients) { \ if ((x) <= gpGlobals->maxClients) { \
if (!MF_IsPlayerIngame(x)) { \ if (!MF_IsPlayerIngame(x)) { \
MF_LogError(amx, AMX_ERR_NATIVE, "Invalid player %d (not in-game)", x); \ MF_LogError(amx, AMX_ERR_NATIVE, "Invalid player %d (not in-game)", x); \
return 0; \ return 0; \
} \ } \
} else { \ } else { \
if (x != 0 && FNullEnt(TypeConversion.id_to_edict(x))) { \ if ((x) != 0 && FNullEnt(TypeConversion.id_to_edict(x))) { \
MF_LogError(amx, AMX_ERR_NATIVE, "Invalid entity %d", x); \ MF_LogError(amx, AMX_ERR_NATIVE, "Invalid entity %d", x); \
return 0; \ return 0; \
} \ } \
@ -49,7 +213,7 @@
} }
#define CHECK_PLAYER(x) \ #define CHECK_PLAYER(x) \
if (x < 1 || x > gpGlobals->maxClients) { \ if ((x) < 1 || (x) > gpGlobals->maxClients) { \
MF_LogError(amx, AMX_ERR_NATIVE, "Player out of range (%d)", x); \ MF_LogError(amx, AMX_ERR_NATIVE, "Player out of range (%d)", x); \
return 0; \ return 0; \
} else { \ } else { \

View File

@ -119,8 +119,8 @@
// #define FN_ClientCommand ClientCommand /* pfnClientCommand() (wd) Player has sent a command (typed or from a bind) */ // #define FN_ClientCommand ClientCommand /* pfnClientCommand() (wd) Player has sent a command (typed or from a bind) */
// #define FN_ClientUserInfoChanged ClientUserInfoChanged /* pfnClientUserInfoChanged() (wd) Client has updated their setinfo structure */ // #define FN_ClientUserInfoChanged ClientUserInfoChanged /* pfnClientUserInfoChanged() (wd) Client has updated their setinfo structure */
// #define FN_ServerActivate ServerActivate /* pfnServerActivate() (wd) Server is starting a new map */ // #define FN_ServerActivate ServerActivate /* pfnServerActivate() (wd) Server is starting a new map */
// #define FN_ServerDeactivate ServerDeactivate /* pfnServerDeactivate() (wd) Server is leaving the map (shutdown or changelevel); SDK2 */ #define FN_ServerDeactivate ServerDeactivate /* pfnServerDeactivate() (wd) Server is leaving the map (shutdown or changelevel); SDK2 */
#define FN_PlayerPreThink PlayerPreThink /* pfnPlayerPreThink() */ // #define FN_PlayerPreThink PlayerPreThink /* pfnPlayerPreThink() */
// #define FN_PlayerPostThink PlayerPostThink /* pfnPlayerPostThink() */ // #define FN_PlayerPostThink PlayerPostThink /* pfnPlayerPostThink() */
// #define FN_StartFrame StartFrame /* pfnStartFrame() */ // #define FN_StartFrame StartFrame /* pfnStartFrame() */
// #define FN_ParmsNewLevel ParmsNewLevel /* pfnParmsNewLevel() */ // #define FN_ParmsNewLevel ParmsNewLevel /* pfnParmsNewLevel() */
@ -232,7 +232,7 @@
// #define FN_SetOrigin SetOrigin // #define FN_SetOrigin SetOrigin
// #define FN_EmitSound EmitSound // #define FN_EmitSound EmitSound
// #define FN_EmitAmbientSound EmitAmbientSound // #define FN_EmitAmbientSound EmitAmbientSound
#define FN_TraceLine TraceLine // #define FN_TraceLine TraceLine
// #define FN_TraceToss TraceToss // #define FN_TraceToss TraceToss
// #define FN_TraceMonsterHull TraceMonsterHull // #define FN_TraceMonsterHull TraceMonsterHull
// #define FN_TraceHull TraceHull // #define FN_TraceHull TraceHull

View File

@ -21,6 +21,21 @@
#pragma loadlib fun #pragma loadlib fun
#endif #endif
/**
* Parts of body for hits, for use with set_user_hitzones().
*/
const HITZONE_GENERIC = (1 << HIT_GENERIC); // 1
const HITZONE_HEAD = (1 << HIT_HEAD); // 2
const HITZONE_CHEST = (1 << HIT_CHEST); // 4
const HITZONE_STOMATCH = (1 << HIT_STOMATCH); // 8
const HITZONE_LEFTARM = (1 << HIT_LEFTARM); // 16
const HITZONE_RIGHTARM = (1 << HIT_RIGHTARM); // 32
const HITZONE_LEFTLEG = (1 << HIT_LEFTLEG); // 64
const HITZONE_RIGHTLEG = (1 << HIT_RIGHTLEG); // 128
const HITZONES_DEFAULT = HITZONE_GENERIC | HITZONE_HEAD | HITZONE_CHEST | HITZONE_STOMATCH |
HITZONE_LEFTARM | HITZONE_RIGHTARM | HITZONE_LEFTLEG | HITZONE_RIGHTLEG; // 255
/** /**
* Tells whether receiver hears sender via voice communication. * Tells whether receiver hears sender via voice communication.
* *
@ -169,26 +184,18 @@ native give_item(index, const item[]);
* *
* @param index Client index * @param index Client index
* @param target The target player * @param target The target player
* @param body A bitsum of the body parts that can/can't be shot: * @param body A bitsum of the body parts that can/can't be shot. See HITZONE* constants.
* 1 - generic
* 2 - head
* 4 - chest
* 8 - stomach
* 16 - left arm
* 32 - right arm
* 64 - left leg
* 128 - right leg
* *
* @noreturn * @noreturn
* @error If player is not connected or not within the range * @error If player is not connected or not within the range
* of 1 to MaxClients. * of 1 to MaxClients.
*/ */
native set_user_hitzones(index = 0, target = 0, body = 255); native set_user_hitzones(index = 0, target = 0, body = HITZONES_DEFAULT);
/** /**
* Gets the set of hit zone "rules" between @index and @target players. * Gets the set of hit zone "rules" between @index and @target players.
* *
* @note For the body part bitsum take a look at the set_user_hitzones() native. * @note For the body part bitsum, see HITZONE* constants.
* *
* @param index Client index * @param index Client index
* @param target The target player * @param target The target player