386 lines
12 KiB
C++
386 lines
12 KiB
C++
// vim: set ts=4 sw=4 tw=99 noet:
|
|
//
|
|
// AMX Mod X, based on AMX Mod by Aleksander Naszko ("OLO").
|
|
// Copyright (C) The AMX Mod X Development Team.
|
|
//
|
|
// This software is licensed under the GNU General Public License, version 3 or higher.
|
|
// Additional exceptions apply. For full license details, see LICENSE.txt or visit:
|
|
// https://alliedmods.net/amxmodx-license
|
|
|
|
//
|
|
// Counter-Strike Module
|
|
//
|
|
|
|
#include "CstrikeDatas.h"
|
|
#include "CstrikeUtils.h"
|
|
#include "CDetour/detours.h"
|
|
#include <sm_stringhashmap.h>
|
|
|
|
void CtrlDetours_ClientCommand(bool set);
|
|
void CtrlDetours_BuyCommands(bool set);
|
|
|
|
int ForwardInternalCommand = -1;
|
|
int ForwardOnBuy = -1;
|
|
int ForwardOnBuyAttempt = -1;
|
|
|
|
int *UseBotArgs = NULL;
|
|
const char **BotArgs = NULL;
|
|
|
|
CDetour *ClientCommandDetour = NULL;
|
|
CDetour *GiveShieldDetour = NULL;
|
|
CDetour *GiveNamedItemDetour = NULL;
|
|
CDetour *AddAccountDetour = NULL;
|
|
|
|
int CurrentItemId = 0;
|
|
StringHashMap<int> ItemAliasList;
|
|
|
|
extern enginefuncs_t *g_pengfuncsTable;
|
|
|
|
void InitializeHacks()
|
|
{
|
|
#if defined AMD64
|
|
#error UNSUPPORTED
|
|
#endif
|
|
|
|
CtrlDetours_ClientCommand(true);
|
|
CtrlDetours_BuyCommands(true);
|
|
}
|
|
|
|
void ShutdownHacks()
|
|
{
|
|
CtrlDetours_ClientCommand(false);
|
|
CtrlDetours_BuyCommands(false);
|
|
}
|
|
|
|
#undef CMD_ARGV
|
|
|
|
const char *CMD_ARGV(int i)
|
|
{
|
|
if (*UseBotArgs)
|
|
{
|
|
if (i < 4)
|
|
{
|
|
return BotArgs[i];
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
return g_engfuncs.pfnCmd_Argv(i);
|
|
}
|
|
|
|
void OnEmitSound(edict_t *entity, int channel, const char *sample, float volume, float attenuation, int fFlags, int pitch)
|
|
{
|
|
// If shield is blocked with CS_OnBuy, we need to block the pickup sound ("items/gunpickup2.wav")
|
|
// as well played right after. Why this sound is not contained in GiveShield()?
|
|
|
|
g_pengfuncsTable->pfnEmitSound = NULL;
|
|
|
|
RETURN_META(MRES_SUPERCEDE);
|
|
}
|
|
|
|
DETOUR_DECL_STATIC1(C_ClientCommand, void, edict_t*, pEdict) // void ClientCommand(edict_t *pEntity)
|
|
{
|
|
const char *command = CMD_ARGV(0);
|
|
|
|
// A new command is triggered, reset variable, always.
|
|
CurrentItemId = 0;
|
|
|
|
// Purpose is to retrieve an item id based on alias name or selected item from menu,
|
|
// to be used in OnBuy* forwards.
|
|
if ((ForwardOnBuyAttempt != -1 || ForwardOnBuy != -1) && command && *command)
|
|
{
|
|
int itemId = 0;
|
|
|
|
// Handling buy via menu.
|
|
if (!strcmp(command, "menuselect"))
|
|
{
|
|
int slot = atoi(CMD_ARGV(1));
|
|
|
|
if (slot > 0 && slot < 9)
|
|
{
|
|
static const int menuItemsTe[][9] =
|
|
{
|
|
/* Menu_Buy */ { 0, 0, 0, 0, 0, 0, CSI_PRIMAMMO, CSI_SECAMMO, 0 },
|
|
/* Menu_BuyPistol */ { 0, CSI_GLOCK18, CSI_USP, CSI_P228, CSI_DEAGLE, CSI_ELITE, 0, 0, 0 },
|
|
/* Menu_BuyRifle */ { 0, CSI_GALI, CSI_AK47, CSI_SCOUT, CSI_SG552, CSI_AWP, CSI_G3SG1, 0, 0 },
|
|
/* Menu_BuyMachineGun */ { 0, CSI_M249, 0, 0, 0, 0, 0, 0, 0 },
|
|
/* Menu_BuyShotgun */ { 0, CSI_M3, CSI_XM1014, 0, 0, 0, 0, 0, 0 },
|
|
/* Menu_BuySubMachineGun */ { 0, CSI_MAC10, CSI_MP5NAVY, CSI_UMP45, CSI_P90, 0, 0, 0, 0 },
|
|
/* Menu_BuyItem */ { 0, CSI_VEST, CSI_VESTHELM, CSI_FLASHBANG, CSI_HEGRENADE, CSI_SMOKEGRENADE, CSI_NVGS, 0, 0 }
|
|
};
|
|
|
|
static const int menuItemsCt[][9] =
|
|
{
|
|
/* Menu_Buy */ { 0, 0, 0, 0, 0, 0, CSI_PRIMAMMO, CSI_SECAMMO, 0 },
|
|
/* Menu_BuyPistol */ { 0, CSI_GLOCK18, CSI_USP, CSI_P228, CSI_DEAGLE, CSI_FIVESEVEN, 0, 0, 0 },
|
|
/* Menu_BuyRifle */ { 0, CSI_FAMAS, CSI_SCOUT, CSI_M4A1, CSI_AUG, CSI_SG550, CSI_AWP, 0, 0 },
|
|
/* Menu_BuyMachineGun */ { 0, CSI_M249, 0, 0, 0, 0, 0, 0, 0 },
|
|
/* Menu_BuyShotgun */ { 0, CSI_M3, CSI_XM1014, 0, 0, 0, 0, 0, 0 },
|
|
/* Menu_BuySubMachineGun */ { 0, CSI_TMP, CSI_MP5NAVY, CSI_UMP45, CSI_P90, 0, 0, 0, 0 },
|
|
/* Menu_BuyItem */ { 0, CSI_VEST, CSI_VESTHELM, CSI_FLASHBANG, CSI_HEGRENADE, CSI_SMOKEGRENADE, CSI_NVGS, CSI_DEFUSER, CSI_SHIELDGUN }
|
|
};
|
|
|
|
int menuId = *((int *)pEdict->pvPrivateData + OFFSET_MENU);
|
|
if (menuId >= Menu_Buy && menuId <= Menu_BuyItem)
|
|
{
|
|
int team = *((int *)pEdict->pvPrivateData + OFFSET_TEAM);
|
|
switch (team)
|
|
{
|
|
case TEAM_T: itemId = menuItemsTe[menuId - 4][slot]; break; // -4 because array is zero-based and Menu_Buy* constants starts from 4.
|
|
case TEAM_CT:itemId = menuItemsCt[menuId - 4][slot]; break;
|
|
}
|
|
|
|
if (itemId)
|
|
{
|
|
CurrentItemId = itemId;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else // Handling buy via alias
|
|
{
|
|
if (ItemAliasList.retrieve(command, &itemId))
|
|
{
|
|
CurrentItemId = itemId;
|
|
}
|
|
}
|
|
}
|
|
|
|
int client = ENTINDEX(pEdict);
|
|
|
|
if (ForwardInternalCommand != -1 && *UseBotArgs)
|
|
{
|
|
const char *args = *BotArgs;
|
|
|
|
if (MF_ExecuteForward(ForwardInternalCommand, static_cast<cell>(client), args) > 0)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (ForwardOnBuyAttempt != -1 &&
|
|
CurrentItemId &&
|
|
MF_IsPlayerAlive(client) &&
|
|
MF_ExecuteForward(ForwardOnBuyAttempt, static_cast<cell>(client), static_cast<cell>(CurrentItemId)) > 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
DETOUR_STATIC_CALL(C_ClientCommand)(pEdict);
|
|
}
|
|
|
|
DETOUR_DECL_MEMBER1(GiveNamedItem, void, const char*, pszName) // void CBasePlayer::GiveNamedItem(const char *pszName)
|
|
{
|
|
// If the current item id is not null, this means player has triggers a buy command.
|
|
if (CurrentItemId)
|
|
{
|
|
int client = PrivateToIndex(this);
|
|
|
|
if (MF_IsPlayerAlive(client) && MF_ExecuteForward(ForwardOnBuy, static_cast<cell>(client), static_cast<cell>(CurrentItemId)) > 0)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
// From here, forward is not blocked, resetting this
|
|
// to ignore code in AddAccount which is called right after.
|
|
CurrentItemId = 0;
|
|
|
|
// Give me my item!
|
|
DETOUR_MEMBER_CALL(GiveNamedItem)(pszName);
|
|
}
|
|
|
|
DETOUR_DECL_MEMBER1(GiveShield, void, bool, bRetire) // void CBasePlayer::GiveShield(bool bRetire)
|
|
{
|
|
// Special case for shield. Game doesn't use GiveNamedItem() to give a shield.
|
|
if (CurrentItemId == CSI_SHIELDGUN)
|
|
{
|
|
int client = PrivateToIndex(this);
|
|
|
|
if (MF_IsPlayerAlive(client) && MF_ExecuteForward(ForwardOnBuy, static_cast<cell>(client), CSI_SHIELDGUN) > 0)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
// From here, forward is not blocked, resetting this
|
|
// to ignore code in AddAccount which is called right after.
|
|
CurrentItemId = 0;
|
|
|
|
// Give me my shield!
|
|
DETOUR_MEMBER_CALL(GiveShield)(bRetire);
|
|
}
|
|
|
|
DETOUR_DECL_MEMBER2(AddAccount, void, int, amount, bool, bTrackChange) // void CBasePlayer::AddAccount(int amount, bool bTrackChange)
|
|
{
|
|
// No buy command or forward not blocked.
|
|
// Resuming game flow.
|
|
if (!CurrentItemId)
|
|
{
|
|
DETOUR_MEMBER_CALL(AddAccount)(amount, bTrackChange);
|
|
}
|
|
// Shield is blocked.
|
|
// We need to hook EmitSound to block pickup sound played right after.
|
|
else if (CurrentItemId == CSI_SHIELDGUN)
|
|
{
|
|
g_pengfuncsTable->pfnEmitSound = OnEmitSound;
|
|
}
|
|
|
|
// Let's reset this right away to avoid issues.
|
|
CurrentItemId = 0;
|
|
}
|
|
|
|
|
|
void CtrlDetours_ClientCommand(bool set)
|
|
{
|
|
if (set)
|
|
{
|
|
void *target = (void *)MDLL_ClientCommand;
|
|
|
|
#if defined(WIN32)
|
|
|
|
UseBotArgs = *(int **)((unsigned char *)target + CS_CLICMD_OFFS_USEBOTARGS);
|
|
BotArgs = (const char **)*(const char **)((unsigned char *)target + CS_CLICMD_OFFS_BOTARGS);
|
|
|
|
#elif defined(__linux__) || defined(__APPLE__)
|
|
|
|
UseBotArgs = (int *)UTIL_FindAddressFromEntry(CS_IDENT_USEBOTARGS, CS_IDENT_HIDDEN_STATE);
|
|
BotArgs = (const char **)UTIL_FindAddressFromEntry(CS_IDENT_BOTARGS, CS_IDENT_HIDDEN_STATE);
|
|
|
|
#endif
|
|
ClientCommandDetour = DETOUR_CREATE_STATIC_FIXED(C_ClientCommand, target);
|
|
|
|
if (!ClientCommandDetour)
|
|
{
|
|
MF_Log("ClientCommand is not available - forward client_command has been disabled");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (ClientCommandDetour)
|
|
ClientCommandDetour->Destroy();
|
|
|
|
ItemAliasList.clear();
|
|
}
|
|
}
|
|
|
|
void ToggleDetour_ClientCommands(bool enable)
|
|
{
|
|
if (ClientCommandDetour)
|
|
(enable) ? ClientCommandDetour->EnableDetour() : ClientCommandDetour->DisableDetour();
|
|
|
|
if (enable)
|
|
{
|
|
// Build the item alias list.
|
|
// Used in ClientCommand to check and get fastly item id from alias name.
|
|
typedef struct
|
|
{
|
|
const char *alias;
|
|
int id;
|
|
|
|
} itemBuyAliasInfo;
|
|
|
|
itemBuyAliasInfo aliasToId[] =
|
|
{
|
|
{ "p228" , CSI_P228 }, { "228compact" , CSI_P228 },
|
|
{ "scout" , CSI_SCOUT }, { "hegren" , CSI_HEGRENADE },
|
|
{ "xm1014" , CSI_XM1014 }, { "autoshotgun", CSI_XM1014 },
|
|
{ "mac10" , CSI_MAC10 }, { "aug" , CSI_AUG },
|
|
{ "bullpup" , CSI_AUG }, { "sgren" , CSI_SMOKEGRENADE },
|
|
{ "elites" , CSI_ELITE }, { "fn57" , CSI_FIVESEVEN },
|
|
{ "fiveseven" , CSI_FIVESEVEN }, { "ump45" , CSI_UMP45 },
|
|
{ "sg550" , CSI_SG550 }, { "krieg550" , CSI_SG550 },
|
|
{ "galil" , CSI_GALI }, { "defender" , CSI_GALI },
|
|
{ "famas" , CSI_FAMAS }, { "clarion" , CSI_FAMAS },
|
|
{ "usp" , CSI_USP }, { "km45" , CSI_USP },
|
|
{ "glock" , CSI_GLOCK18 }, { "9x19mm" , CSI_GLOCK18 },
|
|
{ "awp" , CSI_AWP }, { "magnum" , CSI_AWP },
|
|
{ "mp5" , CSI_MP5NAVY }, { "smg" , CSI_MP5NAVY },
|
|
{ "m249" , CSI_M249 }, { "m3" , CSI_M3 },
|
|
{ "12gauge" , CSI_M3 }, { "m4a1" , CSI_M4A1 },
|
|
{ "tmp" , CSI_TMP }, { "mp" , CSI_TMP },
|
|
{ "g3sg1" , CSI_G3SG1 }, { "d3au1" , CSI_G3SG1 },
|
|
{ "flash" , CSI_FLASHBANG }, { "deagle" , CSI_DEAGLE },
|
|
{ "nighthawk" , CSI_DEAGLE }, { "sg552" , CSI_SG552 },
|
|
{ "krieg552" , CSI_SG552 }, { "ak47" , CSI_AK47 },
|
|
{ "cv47" , CSI_AK47 }, { "p90" , CSI_P90 },
|
|
{ "c90" , CSI_P90 }, { "vest" , CSI_VEST },
|
|
{ "vesthelm" , CSI_VESTHELM }, { "defuser" , CSI_DEFUSER },
|
|
{ "nvgs" , CSI_NVGS }, { "shield" , CSI_SHIELDGUN },
|
|
{ "buyammo1" , CSI_PRIMAMMO }, { "primammo" , CSI_PRIMAMMO },
|
|
{ "buyammo2" , CSI_SECAMMO }, { "secammo" , CSI_SECAMMO },
|
|
{ NULL , 0 }
|
|
};
|
|
|
|
for (size_t i = 0; aliasToId[i].alias != NULL; ++i)
|
|
{
|
|
ItemAliasList.insert(aliasToId[i].alias, aliasToId[i].id);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ItemAliasList.clear();
|
|
}
|
|
}
|
|
|
|
|
|
void CtrlDetours_BuyCommands(bool set)
|
|
{
|
|
if (set)
|
|
{
|
|
void *giveShieldAddress = UTIL_FindAddressFromEntry(CS_IDENT_GIVENSHIELD , CS_IDENT_HIDDEN_STATE);
|
|
void *giveNamedItemAddress = UTIL_FindAddressFromEntry(CS_IDENT_GIVENAMEDITEM, CS_IDENT_HIDDEN_STATE);
|
|
void *addAccountAddress = UTIL_FindAddressFromEntry(CS_IDENT_ADDACCOUNT , CS_IDENT_HIDDEN_STATE);
|
|
|
|
GiveShieldDetour = DETOUR_CREATE_MEMBER_FIXED(GiveShield, giveShieldAddress);
|
|
GiveNamedItemDetour = DETOUR_CREATE_MEMBER_FIXED(GiveNamedItem, giveNamedItemAddress);
|
|
AddAccountDetour = DETOUR_CREATE_MEMBER_FIXED(AddAccount, addAccountAddress);
|
|
|
|
if (!GiveShieldDetour || !GiveNamedItemDetour || !AddAccountDetour)
|
|
{
|
|
if (!GiveShieldDetour)
|
|
{
|
|
MF_Log("GiveShield is not available");
|
|
}
|
|
|
|
if (!GiveNamedItemDetour)
|
|
{
|
|
MF_Log("GiveNamedItem is not available");
|
|
}
|
|
|
|
if (!AddAccountDetour)
|
|
{
|
|
MF_Log("AddAccount is not available");
|
|
}
|
|
|
|
MF_Log("Some functions are not available - forward CS_OnBuyAttempt and CS_OnBuy have been disabled");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (GiveShieldDetour)
|
|
GiveShieldDetour->Destroy();
|
|
|
|
if (GiveNamedItemDetour)
|
|
GiveNamedItemDetour->Destroy();
|
|
|
|
if (AddAccountDetour)
|
|
AddAccountDetour->Destroy();
|
|
|
|
ItemAliasList.clear();
|
|
}
|
|
}
|
|
|
|
void ToggleDetour_BuyCommands(bool enable)
|
|
{
|
|
if (GiveShieldDetour)
|
|
(enable) ? GiveShieldDetour->EnableDetour() : GiveShieldDetour->DisableDetour();
|
|
|
|
if (GiveNamedItemDetour)
|
|
(enable) ? GiveNamedItemDetour->EnableDetour() : GiveNamedItemDetour->DisableDetour();
|
|
|
|
if (AddAccountDetour)
|
|
(enable) ? AddAccountDetour->EnableDetour() : AddAccountDetour->DisableDetour();
|
|
}
|