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
//
#include <string.h>
#include "fun.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;
CPlayers Players;
// ######## Utils:
void FUNUTIL_ResetPlayer(int index)
// native get_client_listen(receiver, sender)
static cell AMX_NATIVE_CALL get_client_listening(AMX *amx, cell *params)
{
//MF_PrintSrvConsole("Resetting player index %d! maxclients: %d\n", index, gpGlobals->maxClients);
for (int i = 1; i <= gpGlobals->maxClients; i++) {
g_bodyhits[index][i] = (char)((1<<HITGROUP_GENERIC) |
(1<<HITGROUP_HEAD) |
(1<<HITGROUP_CHEST) |
(1<<HITGROUP_STOMACH) |
(1<<HITGROUP_LEFTARM) |
(1<<HITGROUP_RIGHTARM)|
(1<<HITGROUP_LEFTLEG) |
(1<<HITGROUP_RIGHTLEG));
}
// Reset silent slippers
g_silent[index] = false;
enum args { arg_count, arg_receiver, arg_sender };
CHECK_PLAYER(params[arg_receiver]);
CHECK_PLAYER(params[arg_sender]);
return GETCLIENTLISTENING(params[arg_receiver], params[arg_sender]);
}
// ######## Natives:
static cell AMX_NATIVE_CALL get_client_listening(AMX *amx, cell *params) // get_client_listening(receiver, sender); = 2 params
// native set_client_listen(receiver, sender, listen)
static cell AMX_NATIVE_CALL set_client_listening(AMX *amx, cell *params)
{
// Gets who can listen to who.
// params[1] = receiver
// params[2] = sender
enum args { arg_count, arg_receiver, arg_sender, arg_listen };
// Check receiver and sender validity.
CHECK_PLAYER(params[1]);
CHECK_PLAYER(params[2]);
CHECK_PLAYER(params[arg_receiver]);
CHECK_PLAYER(params[arg_sender]);
// GET- AND SETCLIENTLISTENING returns "qboolean", an int, probably 0 or 1...
return GETCLIENTLISTENING(params[1], params[2]);
return SETCLIENTLISTENING(params[arg_receiver], params[arg_sender], params[arg_listen]);
}
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.
// params[1] = receiver
// params[2] = sender
// params[3] = listen
enum args { arg_count, arg_user, arg_godmode };
// Check receiver and sender validity.
CHECK_PLAYER(params[1]);
CHECK_PLAYER(params[2]);
CHECK_PLAYER(params[arg_user]);
// Make a check on params[3] here later, and call run time error when it's wrong.
// To do: find out the possible values to set (0, 1?)
const auto pPlayer = TypeConversion.id_to_edict(params[arg_user]);
// GET- AND SETCLIENTLISTENING returns "qboolean", an int, probably 0 or 1...
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?
}
pPlayer->v.takedamage = params[arg_godmode] != 0 ? DAMAGE_NO : DAMAGE_AIM;
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. */
// params[1] = index
enum args { arg_count, arg_user };
// Check index.
CHECK_PLAYER(params[1]);
CHECK_PLAYER(params[arg_user]);
// Get player pointer.
edict_t *pPlayer = TypeConversion.id_to_edict(params[1]);
const auto pPlayer = TypeConversion.id_to_edict(params[arg_user]);
int godmode = 0;
if (pPlayer->v.takedamage == 0.0) {
// God mode is enabled
godmode = 1;
}
return godmode;
return pPlayer->v.takedamage == DAMAGE_NO;
}
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
* with weapon_, ammo_ and item_. This event
* is announced with proper message to all players. */
// params[1] = index
// params[2] = item...
enum args { arg_count, arg_index, arg_item };
// Check index.
CHECK_PLAYER(params[1]);
CHECK_PLAYER(params[arg_index]);
// Get player pointer.
edict_t *pPlayer = TypeConversion.id_to_edict(params[1]);
auto itemLength = 0;
const auto item = MF_GetAmxString(amx, params[arg_item], 1, &itemLength);
// Create item entity pointer
edict_t *pItemEntity;
// Make an "intstring" out of 2nd parameter
int length;
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)
) {
if (!itemLength
||(strncmp(item, "weapon_", 7) != 0
&& strncmp(item, "ammo_", 5) != 0
&& strncmp(item, "item_", 5) != 0
&& strncmp(item, "tf_weapon_", 10) != 0))
{
return 0;
}
//string_t item = MAKE_STRING(szItem);
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);
auto pEntity = CREATE_NAMED_ENTITY(ALLOC_STRING(item));
if (FNullEnt(pItemEntity)) {
MF_LogError(amx, AMX_ERR_NATIVE, "Item \"%s\" failed to create", szItem);
if (FNullEnt(pEntity))
{
MF_LogError(amx, AMX_ERR_NATIVE, "Item \"%s\" failed to create", item);
return 0;
}
//VARS(pItemEntity)->origin = VARS(pPlayer)->origin; // nice to do VARS(ent)->origin instead of ent->v.origin? :-I
//I'm not sure, normally I use macros too =P
pItemEntity->v.origin = pPlayer->v.origin;
pItemEntity->v.spawnflags |= SF_NORESPAWN; //SF_NORESPAWN;
const auto pPlayer = TypeConversion.id_to_edict(params[arg_index]);
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
// 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);
}*/
MDLL_Touch(pEntity, pPlayer);
if (pItemEntity->v.solid == save) {
REMOVE_ENTITY(pItemEntity);
//the function did not fail - we're just deleting the item
if (pEntity->v.solid == oldSolid)
{
REMOVE_ENTITY(pEntity); // The function did not fail - we're just deleting the item
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? :-)
// params[1] = entity to spawn
enum args { arg_count, arg_index };
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;
}
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...
// params[1] = index
// params[2] = health
enum args { arg_count, arg_index, arg_health };
// Check index
CHECK_PLAYER(params[1]);
CHECK_PLAYER(params[arg_index]);
// Fetch player pointer
edict_t *pPlayer = TypeConversion.id_to_edict(params[1]);
const auto pPlayer = TypeConversion.id_to_edict(params[arg_index]);
const auto health = float(params[arg_health]);
// Kill if health too low.
if (params[2] > 0)
pPlayer->v.health = float(params[2]);
if (health > 0.0f)
{
pPlayer->v.health = health;
}
else
{
MDLL_ClientKill(pPlayer);
}
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.
// params[1] = index
// params[2] = frags
enum args { arg_count, arg_index, arg_frags };
// Check index
CHECK_PLAYER(params[1]);
CHECK_PLAYER(params[arg_index]);
// Fetch player pointer
edict_t *pPlayer = TypeConversion.id_to_edict(params[1]);
const auto pPlayer = TypeConversion.id_to_edict(params[arg_index]);
pPlayer->v.frags = params[2];
pPlayer->v.frags = float(params[arg_frags]);
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.
// params[1] = index
// params[2] = armor
enum args { arg_count, arg_index, arg_armor };
// Check index
CHECK_PLAYER(params[1]);
CHECK_PLAYER(params[arg_index]);
// Fetch player pointer
edict_t *pPlayer = TypeConversion.id_to_edict(params[1]);
const auto pPlayer = TypeConversion.id_to_edict(params[arg_index]);
pPlayer->v.armorvalue = params[2];
pPlayer->v.armorvalue = float(params[arg_armor]);
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.
// params[1] = index
// params[2] = origin
enum args { arg_count, arg_index, arg_origin };
// Check index
CHECK_PLAYER(params[1]);
CHECK_PLAYER(params[arg_index]);
// Fetch player pointer
edict_t *pPlayer = TypeConversion.id_to_edict(params[1]);
cell *newVectorCell = MF_GetAmxAddr(amx, params[2]);
auto pPlayer = TypeConversion.id_to_edict(params[arg_index]);
const auto pVector = MF_GetAmxAddr(amx, params[arg_origin]);
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;
}
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.
// params[1] = index
// params[2] = fx
// params[3] = r
// params[4] = g
// params[5] = b
// params[6] = render
// params[7] = amount
enum args { arg_count, arg_index, arg_fx, arg_red, arg_green, arg_blue, arg_render, arg_amount };
// Check index
CHECK_PLAYER(params[1]);
CHECK_PLAYER(params[arg_index]);
// Fetch player pointer
edict_t *pPlayer = TypeConversion.id_to_edict(params[1]);
auto pPlayer = TypeConversion.id_to_edict(params[arg_index]);
pPlayer->v.renderfx = params[2];
Vector newVector = Vector(float(params[3]), float(params[4]), float(params[5]));
pPlayer->v.rendercolor = newVector;
pPlayer->v.rendermode = params[6];
pPlayer->v.renderamt = params[7];
pPlayer->v.renderfx = params[arg_fx];
pPlayer->v.rendercolor = Vector(float(params[arg_red]), float(params[arg_green]), float(params[arg_blue]));
pPlayer->v.rendermode = params[arg_render];
pPlayer->v.renderamt = float(params[arg_amount]);
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.
// params[1] = index
// params[2] = fx
// params[3] = r
// params[4] = g
// params[5] = b
// params[6] = render
// params[7] = amount
enum args { arg_count, arg_index, arg_fx, arg_red, arg_green, arg_blue, arg_render, arg_amount };
// Check index
CHECK_PLAYER(params[1]);
CHECK_PLAYER(params[arg_index]);
// Fetch player pointer
edict_t *pPlayer = TypeConversion.id_to_edict(params[1]);
auto pPlayer = TypeConversion.id_to_edict(params[arg_index]);
*MF_GetAmxAddr(amx, params[2]) = pPlayer->v.renderfx;
*MF_GetAmxAddr(amx, params[3]) = pPlayer->v.rendercolor[0];
*MF_GetAmxAddr(amx, params[4]) = pPlayer->v.rendercolor[1];
*MF_GetAmxAddr(amx, params[5]) = pPlayer->v.rendercolor[2];
*MF_GetAmxAddr(amx, params[6]) = pPlayer->v.rendermode;
*MF_GetAmxAddr(amx, params[7]) = pPlayer->v.renderamt;
*MF_GetAmxAddr(amx, params[arg_fx]) = pPlayer->v.renderfx;
*MF_GetAmxAddr(amx, params[arg_red]) = pPlayer->v.rendercolor[0];
*MF_GetAmxAddr(amx, params[arg_green]) = pPlayer->v.rendercolor[1];
*MF_GetAmxAddr(amx, params[arg_blue]) = pPlayer->v.rendercolor[2];
*MF_GetAmxAddr(amx, params[arg_render]) = pPlayer->v.rendermode;
*MF_GetAmxAddr(amx, params[arg_amount]) = pPlayer->v.renderamt;
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.
// params[1] = index
// params[2] = speed (should be -1.0 if not specified) (JGHG: unspecified parameters seems to always be -1.0!)
enum args { arg_count, arg_index, arg_speed };
REAL fNewSpeed = amx_ctof(params[2]);
CHECK_PLAYER(params[arg_index]);
// Check index
CHECK_PLAYER(params[1]);
const auto pPlayer = TypeConversion.id_to_edict(params[arg_index]);
const auto newSpeed = amx_ctof(params[arg_speed]);
// Fetch player pointer
edict_t *pPlayer = TypeConversion.id_to_edict(params[1]);
SETCLIENTMAXSPEED(pPlayer, fNewSpeed);
pPlayer->v.maxspeed = fNewSpeed;
SETCLIENTMAXSPEED(pPlayer, newSpeed);
pPlayer->v.maxspeed = newSpeed;
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.
// params[1] = index
enum args { arg_count, arg_index };
// Check index
CHECK_PLAYER(params[1]);
CHECK_PLAYER(params[arg_index]);
// Fetch player pointer
edict_t *pPlayer = TypeConversion.id_to_edict(params[1]);
const auto pPlayer = TypeConversion.id_to_edict(params[arg_index]);
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.
// params[1] = index
// params[2] = gravity (=-1.0)
// Check index
CHECK_PLAYER(params[1]);
enum args { arg_count, arg_index, arg_gravity };
// Fetch player pointer
edict_t *pPlayer = TypeConversion.id_to_edict(params[1]);
CHECK_PLAYER(params[arg_index]);
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;
}
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.
// params[1] = index
enum args { arg_count, arg_index };
// Check index
CHECK_PLAYER(params[1]);
CHECK_PLAYER(params[arg_index]);
// Fetch player pointer
edict_t *pPlayer = TypeConversion.id_to_edict(params[1]);
const auto pPlayer = TypeConversion.id_to_edict(params[arg_index]);
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.
// params[1] = the one(s) who shoot(s), shooter
int shooter = params[1];
enum args { arg_count, arg_attacker, arg_target, arg_hitzones };
// params[2] = the one getting hit
int gettingHit = params[2];
const int attacker = params[arg_attacker];
const int target = params[arg_target];
const int hitzones = params[arg_hitzones];
// params[3] = specified hit zones
int hitzones = params[3];
//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;
//g_zones_getHit[i] = hitzones;
}
if (attacker == 0 && target == 0)
{
Players.SetEveryoneBodyHits(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 (attacker == 0 && target != 0)
{
CHECK_PLAYER(target);
Players.SetAttackersBodyHits(target, 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 if (attacker != 0 && target == 0)
{
CHECK_PLAYER(attacker);
Players.SetTargetsBodyHits(attacker, 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);
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
{
// Sets user to no clipping mode.
// params[1] = index
// params[2] = no clip or not...
// Check index
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
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;
}
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.
// params[1] = index
enum args { arg_count, arg_attacker, arg_target };
// Check index
CHECK_PLAYER(params[1]);
const auto attacker = params[arg_attacker];
// Fetch player pointer
edict_t *pPlayer = TypeConversion.id_to_edict(params[1]);
CHECK_PLAYER(attacker);
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;
}
// JustinHoMi made this one originally
static cell AMX_NATIVE_CALL set_user_footsteps(AMX *amx, cell *params) // set_user_footsteps(id, set = 1); = 2 params
// native set_user_footsteps(id, set = 1)
static cell AMX_NATIVE_CALL set_user_footsteps(AMX *amx, cell *params)
{
// Gives player silent footsteps.
// if set=0 it will return footsteps to normal
// params[1] = index of player
// params[2] = 0 = normal footstep sound, 1 = silent slippers
enum args { arg_count, arg_index, arg_footsteps };
// Check index
CHECK_PLAYER(params[1]);
const auto index = params[arg_index];
// Fetch player pointer
edict_t *pPlayer = TypeConversion.id_to_edict(params[1]);
CHECK_PLAYER(index);
if (params[2]) {
const auto pPlayer = TypeConversion.id_to_edict(index);
if (params[arg_footsteps] != 0)
{
pPlayer->v.flTimeStepSound = 999;
g_silent[params[1]] = true;
Players[index].SetSilentFootsteps(true);
g_pFunctionTable->pfnPlayerPreThink = PlayerPreThink;
}
else {
else
{
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;
}
// native get_user_footsteps(index)
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
static cell AMX_NATIVE_CALL strip_user_weapons(AMX *amx, cell *params) // index
// native strip_user_weapons(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");
edict_t *pent = CREATE_NAMED_ENTITY(item);
CHECK_PLAYER(index);
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;
}
MDLL_Spawn(pent);
MDLL_Use(pent, pPlayer);
REMOVE_ENTITY(pent);
MDLL_Spawn(pEntity);
MDLL_Use(pEntity, pPlayer);
REMOVE_ENTITY(pEntity);
*reinterpret_cast<int *>(MF_PlayerPropAddr(params[1], Player_CurrentWeapon)) = 0;
*reinterpret_cast<int *>(MF_PlayerPropAddr(index, Player_CurrentWeapon)) = 0;
return 1;
}
AMX_NATIVE_INFO fun_Exports[] = {
{"get_client_listen", get_client_listening},
{"set_client_listen", set_client_listening},
{"set_user_godmode", set_user_godmode},
{"get_user_godmode", get_user_godmode},
{"set_user_health", set_user_health},
{"give_item", give_item},
{"spawn", spawn},
{"set_user_frags", set_user_frags},
{"set_user_armor", set_user_armor},
{"set_user_origin", set_user_origin},
{"set_user_rendering", set_user_rendering},
{"get_user_rendering", get_user_rendering},
{"set_user_maxspeed", set_user_maxspeed},
{"get_user_maxspeed", get_user_maxspeed},
{"set_user_gravity", set_user_gravity},
{"get_user_gravity", get_user_gravity},
{"get_user_footsteps", get_user_footsteps},
{"set_user_hitzones", set_user_hitzones},
{"get_user_hitzones", get_user_hitzones},
{"set_user_noclip", set_user_noclip},
{"get_user_noclip", get_user_noclip},
{"set_user_footsteps", set_user_footsteps},
{"strip_user_weapons", strip_user_weapons},
/////////////////// <--- 19 chars max in current small version
{NULL, NULL}
AMX_NATIVE_INFO fun_Exports[] =
{
{ "get_client_listen" , get_client_listening },
{ "set_client_listen" , set_client_listening },
{ "set_user_godmode" , set_user_godmode },
{ "get_user_godmode" , get_user_godmode },
{ "set_user_health" , set_user_health },
{ "give_item" , give_item },
{ "spawn" , spawn },
{ "set_user_frags" , set_user_frags },
{ "set_user_armor" , set_user_armor },
{ "set_user_origin" , set_user_origin },
{ "set_user_rendering", set_user_rendering },
{ "get_user_rendering", get_user_rendering },
{ "set_user_maxspeed" , set_user_maxspeed },
{ "get_user_maxspeed" , get_user_maxspeed },
{ "set_user_gravity" , set_user_gravity },
{ "get_user_gravity" , get_user_gravity },
{ "get_user_footsteps", get_user_footsteps },
{ "set_user_hitzones" , set_user_hitzones },
{ "get_user_hitzones" , get_user_hitzones },
{ "set_user_noclip" , set_user_noclip },
{ "get_user_noclip" , get_user_noclip },
{ "set_user_footsteps", set_user_footsteps },
{ "strip_user_weapons", strip_user_weapons },
{ nullptr , nullptr }
};
/******************************************************************************************/
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;
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])
{
// Reset stuff:
FUNUTIL_ResetPlayer(ENTINDEX(pPlayer));
const auto index = TypeConversion.edict_to_id(pPlayer);
Players[index].Clear();
RETURN_META_VALUE(MRES_IGNORED, 0);
}
void TraceLine(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))
&& shooter && (shooter->v.flags & (FL_CLIENT | FL_FAKECLIENT)) ) {
int shooterIndex = ENTINDEX(shooter);
if ( !(g_bodyhits[shooterIndex][ENTINDEX(ptr->pHit)] & (1<<ptr->iHitgroup)) )
void TraceLine_Post(const float *v1, const float *v2, int fNoMonsters, edict_t *shooter, TraceResult *ptr)
{
if (ptr->pHit && (ptr->pHit->v.flags & (FL_CLIENT | FL_FAKECLIENT))
&& shooter && (shooter->v.flags & (FL_CLIENT | FL_FAKECLIENT)) )
{
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;
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()
{
MF_AddNatives(fun_Exports);
}
// The content of OnPluginsLoaded() was moved from OnAmxxAttach with AMXx 1.5 because for some reason gpGlobals->maxClients wasn't
// initialized to its proper value until some time after OnAmxxAttach(). In OnAmxxAttach() it always showed 0. /JGHG
void OnPluginsLoaded() {
// Reset stuff - hopefully this should
for (int i = 1; i <= gpGlobals->maxClients; i++) {
// Reset all hitzones
FUNUTIL_ResetPlayer(i);
}
void OnPluginsLoaded()
{
Players.Clear();
TypeConversion.init();
g_pFunctionTable->pfnPlayerPreThink = nullptr;
g_pengfuncsTable_Post->pfnTraceLine = nullptr;
}
/*
void ClientConnectFakeBot(int index)
void ServerDeactivate()
{
FUNUTIL_ResetPlayer(index);
//MF_Log("A bot connects, forwarded to fun! The bot is %d!", index);
//CPlayer* player;
g_pFunctionTable->pfnPlayerPreThink = nullptr;
g_pengfuncsTable_Post->pfnTraceLine = nullptr;
RETURN_META(MRES_IGNORED);
}
*/

View File

@ -11,7 +11,9 @@
// Fun Module
//
#include "amxxmodule.h"
#pragma once
#include <amxxmodule.h>
// Fun-specific defines below
#define GETCLIENTLISTENING (*g_engfuncs.pfnVoice_GetClientListening)
@ -29,19 +31,181 @@
#define HITGROUP_RIGHTARM 5 // 32
#define HITGROUP_LEFTLEG 6 // 64
#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) \
if (x < 0 || x > gpGlobals->maxEntities) { \
if ((x) < 0 || (x) > gpGlobals->maxEntities) { \
MF_LogError(amx, AMX_ERR_NATIVE, "Entity out of range (%d)", x); \
return 0; \
} else { \
if (x <= gpGlobals->maxClients) { \
if ((x) <= gpGlobals->maxClients) { \
if (!MF_IsPlayerIngame(x)) { \
MF_LogError(amx, AMX_ERR_NATIVE, "Invalid player %d (not in-game)", x); \
return 0; \
} \
} 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); \
return 0; \
} \
@ -49,7 +213,7 @@
}
#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); \
return 0; \
} 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_ClientUserInfoChanged ClientUserInfoChanged /* pfnClientUserInfoChanged() (wd) Client has updated their setinfo structure */
// #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_PlayerPreThink PlayerPreThink /* pfnPlayerPreThink() */
#define FN_ServerDeactivate ServerDeactivate /* pfnServerDeactivate() (wd) Server is leaving the map (shutdown or changelevel); SDK2 */
// #define FN_PlayerPreThink PlayerPreThink /* pfnPlayerPreThink() */
// #define FN_PlayerPostThink PlayerPostThink /* pfnPlayerPostThink() */
// #define FN_StartFrame StartFrame /* pfnStartFrame() */
// #define FN_ParmsNewLevel ParmsNewLevel /* pfnParmsNewLevel() */
@ -232,7 +232,7 @@
// #define FN_SetOrigin SetOrigin
// #define FN_EmitSound EmitSound
// #define FN_EmitAmbientSound EmitAmbientSound
#define FN_TraceLine TraceLine
// #define FN_TraceLine TraceLine
// #define FN_TraceToss TraceToss
// #define FN_TraceMonsterHull TraceMonsterHull
// #define FN_TraceHull TraceHull

View File

@ -21,6 +21,21 @@
#pragma loadlib fun
#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.
*
@ -169,26 +184,18 @@ native give_item(index, const item[]);
*
* @param index Client index
* @param target The target player
* @param body A bitsum of the body parts that can/can't be shot:
* 1 - generic
* 2 - head
* 4 - chest
* 8 - stomach
* 16 - left arm
* 32 - right arm
* 64 - left leg
* 128 - right leg
* @param body A bitsum of the body parts that can/can't be shot. See HITZONE* constants.
*
* @noreturn
* @error If player is not connected or not within the range
* 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.
*
* @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 target The target player