Blocking weapon fire, including client-side sounds/animations
Blocking weapon fire, including client-side sounds/animations
So you want to block weapon fire, including client-side sounds/animations?...you've come to the right thread :)
This tutorial is meant for intermediate - advanced users
Introduction- I've been using this method in my metamod MLMod plugin for NS, for a while now, which is similar to the metamod Spawn Chat protection plugin by Jussi Kivilinna. I have not tested this is any other mod except NS, but it should work the same...:?:
- With the addition of UpdateClientData forward in the latest Amx Mod X, it is now possible to block weapon fire through fakemeta, including client-side sounds/animations.
- There are two methods to blocking weapon fire:
- Make the engine think the player has no weapon - for blocking weapon fire whenever you want to
- Set the time until next attack (used in the Spawn Chat protection plugin) - for blocking weapon fire for a specific amount of time
- Info on the UpdateClientData function...
- It appears to be called every frame and also before PreThink.
- Setting player properties like EV_ or pev_ do not work (I've tested with removing buttons from EV_INT_buttons and does not block it).
- It cannot block reload animations. Doing either of the methods above during a reload will not block the animation. However, weapon reload can be blocked (the anim and actual reload) by removing the buttons.
- Also, you MUST STILL remove the IN_ATTACK button mask to keep the weapon from using ammo and doing damage, etc. Like I said, you cannot do this in UpdateClientData, so it must be done in PreThink or CmdStart like before. However, I'm not sure if CmdStart is called every frame? I recommend PreThink because it is called specifically before Think and PostThink and every frame.
Now for the good part...
Registering forwards and hooks- Import the additional includes that you will need.
Code:
#include <fakemeta>
#include <engine>
- In plugin_init(), register FM_PlayerPreThink or use the default engine client_preThink. If registering through fakemeta, it does not matter if it is post or not. The engine forward is not post meaning it is called before the original is called.
Code:
public client_PreThink(id)
{
}
or
Code:
register_forward(FM_PlayerPreThink, "PlayerPreThink")
- And also in plugin_init(), register FM_UpdateClientData. It is important that it is hooked AFTER the original is called so your modifications are not changed. This is indicated by passing 1 as the third argument telling Amx Mod X that you want your function to get called after the original has been called.
Code:
register_forward(FM_UpdateClientData, "UpdateClientData_Post", 1)
- In client_preThink, if you're using the default engine forward, if using fakemeta method then whatever it is you named it. In my example it was named PlayerPreThink
Code:
//engine method
public client_PreThink( id )
{
//Do whatever checks you need to do and whatnot...
if( !is_user_alive(id) )
return PLUGIN_CONTINUE;
//Remove the attack button from their button mask
entity_set_int( id, EV_INT_button, entity_get_int(id,EV_INT_button) & ~IN_ATTACK );
return PLUGIN_CONTINUE;
}
or
Code:
//fakemeta method
public PlayerPreThink( id )
{
//Do whatever checks you need to do and whatnot...
if( !is_user_alive(id) )
return FMRES_IGNORED;
//Remove the attack button from their button mask
set_pev( id, pev_button, pev(id,pev_button) & ~IN_ATTACK );
return FMRES_HANDLED;
}
- In UpdateClientData_Post, there are 3 arguments sent:
- 1st argument is id (int) - player ID
- 2nd argument is sendweapons (int) - indication if client-side weapons are being used (cl_lw); not really important, can be ignored
- 3rd argument is cd_handle (clientdata type) - contains all the player info to be modified, which is accessed through get_cd, and set_cd
Code:
public UpdateClientData_Post( id, sendweapons, cd_handle )
{
}
Method 1 - Make the engine think the player has no weapon:- We do this by modifying CD_ID, which is the weapon id, and setting it to 0 thus making the engine think the player has no weapon and so no animation/sound can be played.
Code:
//Remember the 3 arguments sent:
//id - player id
//sendweapons - indication if client-side weapons are being used (cl_lw)
//cd_handle - client data which is accessed through get_cd, and set_cd
public UpdateClientData_Post( id, sendweapons, cd_handle )
{
//No sense in doing this for dead people?
//Add your additional checks and whatnot...
if ( !is_user_alive(id) )
return FMRES_IGNORED;
//We want to use the cd_handle passed to us
//unless you want this for all the players
//in which you would specify 0 instead
set_cd(cd_handle, CD_ID, 0);
//And finally return...
return FMRES_HANDLED;
}
Method 2 - Modify the time until the next attack:- We do this by modifying CD_flNextAttack, which is the specific time when the player can attack again. This cannot only be used to block attacks but to also modify ROF! (which I'll discuss elsewhere).
- Actually CD_flNextAttack has a value greater than zero ONLY when a player is reloading which is why it acts the way it does. So basically setting this value makes the engine think the player is in reload, however the reload animations are not played. Another way this could be used is possibly to make the player fire before the reload is done?
Code:
//Again the 3 arguments sent:
//id - player id
//sendweapons - indication if client-side weapons are being used (cl_lw)
//cd_handle - client data which is accessed through get_cd, and set_cd
public UpdateClientData_Post( id, sendweapons, cd_handle )
{
//No sense in doing this for dead people?
//Add your additional checks and whatnot...
if ( !is_user_alive(id) )
return FMRES_IGNORED;
//We want to use the cd_handle passed to us
//unless you want this for all the players
//in which you would specify 0 instead
//We get the current half-life time and set the next attack to a fraction of a sec later...
//This must be modified every time or else it won't block the attack
set_cd(cd_handle, CD_flNextAttack, halflife_time() + 0.001 );
//And finally return...
return FMRES_HANDLED;
}
- A snippet from the Spawn Chat protection plugin and how it uses this function. My comments start with four slashes ////.
Code:
void CPlayer::UpdateClientData_Post(int sendweapons, struct clientdata_s *cd)
{
//Fix clientside weapon animations
if(sendweapons && m_ProtectionActive)
{
////The first part of the if statement let's the player attack the next frame
////The second part of the if statement blocks the player's attack until a certain time
////If attack time is before the current time (meanning player can fire) then
if (cd->m_flNextAttack < gpGlobals->time)
////m_StartOfProtection is the time when the blocking of the player's attack started
////scp_off_delay.get() is how long to keep blocking the time
////m_StartOfProtection + scp_off_delay.get() is the time when the player can attack again
////(m_StartOfProtection + scp_off_delay.get()) - (gpGlobals->time + 0.001f)
////Think of it as:
////<time when the player can attack again> - <time now, basically> = now or next frame player can attack
////Pretty much, the player can attack the moment they want to and it won't be blocked
////The 0.001 is so player can fire a fraction of a second earlier to fix some weapon glitches or because it is checked by > instead of equal
cd->m_flNextAttack = (m_StartOfProtection + scp_off_delay.get()) - (gpGlobals->time + 0.001f);
////else if attack time is after the current time (meanning player can't fire) then
else
////Similar to above except in calculations...
////m_StartOfProtection is the time when the blocking of the player's attack started
////scp_off_delay.get() is how long to keep blocking the time
////m_StartOfProtection + scp_off_delay.get() is the time when the player can attack again
////(m_StartOfProtection + scp_off_delay.get()) - 0.001f
////Think of it as:
////<time when the player can attack again> - <fraction of a second> = specific time when player can attack
////The 0.001 is so player can fire a fraction of a second earlier to fix some weapon glitches or because it is checked by > instead of equal
cd->m_flNextAttack = (m_StartOfProtection + scp_off_delay.get()) - 0.001f;
SetMetaResult(MRES_HANDLED);
return;
}
SetMetaResult(MRES_IGNORED);
return;
}
Custom Weapon ROF (Rate-of-fire)- I did this part on-the-fly...anyways there are, of course, many ways to do this. This is just one way; there may be others methods that could be better suited for your situation; just take a look at the above snippet from the Spawn Chat protection plugin.
- Also the subtraction of the 0.001 is not neccessary but feel free to do it if needed. I believe it is done because it is checked with greater than (>) so the delay is slightly longer but only by a very miniscule amount.
- Create some variables to store some info:
Code:
new g_NextAttack[33]; //stores players' next attack times
new g_BlockAnim[33]; //stores players' variable for whether to block animation in UpdateClientData_Post
new CVAR_rof; //"pointer" to CVAR containing the rof; could also be define
- Using Method #1:
- In UpdateClientData_Post is where we initially decide whether the attack needs to be blocked
Code:
//Remember the 3 arguments sent:
//id - player id
//sendweapons - indication if client-side weapons are being used (cl_lw)
//cd_handle - client data which is accessed through get_cd, and set_cd
public UpdateClientData_Post( id, sendweapons, cd_handle )
{
//Do whatever checks you need to do and whatnot...
//Make sure player is holding the weapon you want to block
//Make sure player is alive
//etc.
//First check the ROF
//Store the variables
new Float:global_Time;
global_get(glb_time, global_Time);
new Float:ROF = get_pcvar_float(CVAR_rof);
//If current time hasn't yet reached the next allowed attack time, keep blocking the attack
//else let it go and reset the g_BlockAnim variable
if( global_Time < g_NextAttack[id] )
{
//Keep blocking attack
//Now you could just block if the player is pressing the button but just to be safe block it regardless
//Set player variable to block in PlayerPreThink also
g_BlockAnim[id] = 1;
//We want to use the cd_handle passed to us
//unless you want this for all the players
//in which you would specify 0 instead
set_cd(cd_handle, CD_ID, 0);
}
else
{
//Allow attack
//Reset player variable to block in PlayerPreThink
g_BlockAnim[id] = 0;
//Set next attack time at which player is allowed to attack
g_NextAttack[id] = global_Time + ROF;
}
return FMRES_HANDLED;
}
- Using Method #2:
- In UpdateClientData_Post is where we initially decide whether the attack needs to be blocked
Code:
//Remember the 3 arguments sent:
//id - player id
//sendweapons - indication if client-side weapons are being used (cl_lw)
//cd_handle - client data which is accessed through get_cd, and set_cd
public UpdateClientData_Post( id, sendweapons, cd_handle )
{
//Do whatever checks you need to do and whatnot...
//Make sure player is holding the weapon you want to block
//Make sure player is alive
//etc.
//First check the ROF
//Store the variables
new Float:global_Time;
global_get(glb_time, global_Time);
new Float:ROF = get_pcvar_float(CVAR_rof);
//If current time hasn't yet reached the next allowed attack time, keep blocking the attack
//else let it go and reset the g_BlockAnim variable
if( global_Time < g_NextAttack[id] )
{
//Keep blocking attack
//Now you could just block if the player is pressing the button but just to be safe block it regardless
//Set player variable to block in PlayerPreThink also
g_BlockAnim[id] = 1;
//We want to use the cd_handle passed to us
//unless you want this for all the players
//in which you would specify 0 instead
//If current time is equal or greater than (>=) g_NextAttack, the attack will be blocked
//else it will not
set_cd(cd_handle, CD_flNextAttack, g_NextAttack[id]);
}
else
{
//Allow attack
//Reset player variable to block in PlayerPreThink
g_BlockAnim[id] = 0;
//Set next attack time at which player is allowed to attack
g_NextAttack[id] = global_Time + ROF;
}
return FMRES_HANDLED;
}
- In PlayerPreThink we check the g_BlockAnim variable
Code:
//fakemeta method
public PlayerPreThink( id )
{
//Do whatever checks you need to do and whatnot...
//Make sure player is holding the weapon you want to block
//Make sure player is alive
//etc.
if( g_BlockAnim[id] )
{
//Remove the attack button from their button mask
set_pev( id, pev_button, pev(id,pev_button) & ~IN_ATTACK );
//Reset the variable
//It will be set again in UpdateClientData_Post before it comes here so it is set every frame
g_BlockAnim[id] = 0;
}
// else allow attack
return FMRES_HANDLED;
}
Quick Reference:- From fakemeta_const.inc & fakemeta.inc:
- ClientData enum
Code:
enum ClientData
{
CD_Origin, // float array[3]
CD_Velocity, // float array[3]
CD_ViewModel, // int
CD_PunchAngle, // float array[3]
CD_Flags, // int
CD_WaterLevel, // int
CD_WaterType, // int
CD_ViewOfs, // float array[3]
CD_Health, // float
CD_bInDuck, // int
CD_Weapons, // int
CD_flTimeStepSound, // int
CD_flDuckTime, // int
CD_flSwimTime, // int
CD_WaterJumpTime, // int
CD_MaxSpeed, // float
CD_FOV, // float
CD_WeaponAnim, // int
CD_ID, // int
CD_AmmoShells, // int
CD_AmmoNails, // int
CD_AmmoCells, // int
CD_AmmoRockets, // int
CD_flNextAttack, // float
CD_tfState, // int
CD_PushMsec, // int
CD_DeadFlag, // int
CD_PhysInfo, // string[256]
CD_iUser1, // int
CD_iUser2, // int
CD_iUser3, // int
CD_iUser4, // int
CD_fUser1, // float
CD_fUser2, // float
CD_fUser3, // float
CD_fUser4, // float
CD_vUser1, // float array[3]
CD_vUser2, // float array[3]
CD_vUser3, // float array[3]
CD_vUser4 // float array[3]
};
- ClientData functions
Code:
// These functions are used with the clientdata data structure (FM_UpdateClientData)
// Get: 0 extra params - Return integer; 1 extra param - by ref float or vector; 2 extra params - string and length
// Set: Use anything
// Use 0 for cd_handle to specify the global clientdata handle
native get_cd(cd_handle, ClientData:member, {Float,_}:...);
native set_cd(cd_handle, ClientData:member, {Float,_}:...);
- Forward Function Constants
Code:
enum {
FM_PrecacheModel = 1, // done
FM_PrecacheSound, // done
FM_SetModel, // done
FM_ModelIndex, // done
FM_ModelFrames, // done
FM_SetSize, // done
FM_ChangeLevel, // done
FM_VecToYaw, // done
FM_VecToAngles, // done
FM_MoveToOrigin, // done
FM_ChangeYaw, // done
FM_ChangePitch, // done
FM_FindEntityByString, // done
FM_GetEntityIllum, // done
FM_FindEntityInSphere, // done
FM_FindClientInPVS, // done
FM_EntitiesInPVS, // done
FM_MakeVectors, // done
FM_AngleVectors, // done
FM_CreateEntity, // done
FM_RemoveEntity, // done
FM_CreateNamedEntity, // done
FM_MakeStatic, // done
FM_EntIsOnFloor, // done
FM_DropToFloor, // done
FM_WalkMove, // int ) (edict_t *ent, float yaw, float dist, int iMode); -- does't work as of 0.20 RC2
FM_SetOrigin, // done
FM_EmitSound, // done
FM_EmitAmbientSound, // done
FM_TraceLine, // void ) (const float *v1, const float *v2, int fNoMonsters, edict_t *pentToSkip, TraceResult *ptr); -- does't work as of 0.20 RC2
FM_TraceToss, // void ) (edict_t* pent, edict_t* pentToIgnore, TraceResult *ptr); -- does't work as of 0.20 RC2
FM_TraceMonsterHull, // int ) (edict_t *pEdict, const float *v1, const float *v2, int fNoMonsters, edict_t *pentToSkip, TraceResult *ptr); -- does't work as of 0.20 RC2
FM_TraceHull, // void ) (const float *v1, const float *v2, int fNoMonsters, int hullNumber, edict_t *pentToSkip, TraceResult *ptr); -- does't work as of 0.20 RC2
FM_TraceModel, // void ) (const float *v1, const float *v2, int hullNumber, edict_t *pent, TraceResult *ptr); -- does't work as of 0.20 RC2
FM_TraceTexture, // const char *) (edict_t *pTextureEntity, const float *v1, const float *v2 ); -- does't work as of 0.20 RC2
FM_TraceSphere, // void ) (const float *v1, const float *v2, int fNoMonsters, float radius, edict_t *pentToSkip, TraceResult *ptr); -- does't work as of 0.20 RC2
FM_GetAimVector, // done
FM_ParticleEffect, // done
FM_LightStyle, // done
FM_DecalIndex, // done
FM_PointContents, // done
FM_MessageBegin, // done
FM_MessageEnd, // done
FM_WriteByte, // done
FM_WriteChar, // done
FM_WriteShort, // done
FM_WriteLong, // done
FM_WriteAngle, // done
FM_WriteCoord, // done
FM_WriteString, // done
FM_WriteEntity, // done
FM_CVarGetFloat, // done
FM_CVarGetString, // done
FM_CVarSetFloat, // done
FM_CVarSetString, // done
FM_FreeEntPrivateData, // done
FM_SzFromIndex, // done
FM_AllocString, // done
FM_RegUserMsg, // done
FM_AnimationAutomove, // done
FM_GetBonePosition, // void ) (const edict_t* pEdict, int iBone, float *rgflOrigin, float *rgflAngles ); -- does't work as of 0.20 RC2
FM_GetAttachment, // void ) (const edict_t *pEdict, int iAttachment, float *rgflOrigin, float *rgflAngles ); -- does't work as of 0.20 RC2
FM_SetView, // done
FM_Time, // done
FM_CrosshairAngle, // done
FM_FadeClientVolume, // done
FM_SetClientMaxspeed, // done
FM_CreateFakeClient, // done
FM_RunPlayerMove, // void ) (edict_t *fakeclient, const float *viewangles, float forwardmove, float sidemove, float upmove, unsigned short buttons, byte impulse, byte msec ); -- does't work as of 0.20 RC2
FM_NumberOfEntities, // done
FM_StaticDecal, // done
FM_PrecacheGeneric, // done
FM_BuildSoundMsg, // void ) (edict_t *entity, int channel, const char *sample, /*int*/float volume, float attenuation, int fFlags, int pitch, int msg_dest, int msg_type, const float *pOrigin, edict_t *ed); -- does't work as of 0.20 RC2
FM_GetPhysicsKeyValue, // done
FM_SetPhysicsKeyValue, // done
FM_GetPhysicsInfoString, // done
FM_PrecacheEvent, // done
FM_PlaybackEvent, // done
FM_CheckVisibility, //) (
FM_GetCurrentPlayer, // done
FM_CanSkipPlayer, // done
FM_SetGroupMask, // done
FM_Voice_GetClientListening, // done
FM_Voice_SetClientListening, // done
FM_InfoKeyValue, // done
FM_SetKeyValue, // done
FM_SetClientKeyValue, // done
FM_GetPlayerAuthId, // done
FM_GetPlayerWONId, // done
FM_IsMapValid, // done
FM_Spawn, // done
FM_Think, // done
FM_Use, // done
FM_Touch, // done
FM_Blocked, // done
FM_KeyValue, // void ) ( edict_t *pentKeyvalue, KeyValueData *pkvd ); -- does't work as of 0.20 RC2
FM_SetAbsBox, // done
FM_ClientConnect, // done
FM_ClientDisconnect, // done
FM_ClientKill, // done
FM_ClientPutInServer, // done
FM_ClientCommand, // done
FM_ServerDeactivate, // done
FM_PlayerPreThink, // done
FM_PlayerPostThink, // done
FM_StartFrame, // done
FM_ParmsNewLevel, // done
FM_ParmsChangeLevel, // done
// Returns string describing current .dll. E.g., TeamFotrress 2, Half-Life
FM_GetGameDescription, // done
// Spectator funcs
FM_SpectatorConnect, // done
FM_SpectatorDisconnect, // done
FM_SpectatorThink, // done
// Notify game .dll that engine is going to shut down. Allows mod authors to set a breakpoint.
FM_Sys_Error, // done
FM_PM_FindTextureType, // done
FM_RegisterEncoders, // done
// Enumerates player hulls. Returns 0 if the hull number doesn't exist, 1 otherwise
// Create baselines for certain "unplaced" items.
FM_CreateInstancedBaseline, // done
FM_AllowLagCompensation, // done
FM_AlertMessage, // done (at_type, message[])
// NEW_DLL_FUNCTIONS:
FM_OnFreeEntPrivateData,
FM_GameShutdown,
FM_ShouldCollide
};
Miscellaneous:- The method I use above is my own personal tried and true method I've been using. Feel free to look into other methods of blocking attack and manipulating weapon sequences:
- FM_AddToFullPack
- FM_CmdStart
Any Karma donations are welcomed! :up:
|