Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AMBuilder
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ for sdk_target in MMSPlugin.sdk_targets:
'src/entities.cpp',
'src/events.cpp',
'src/utils/entity.cpp',
'src/utils/weapon.cpp',
'src/cs2_sdk/schema.cpp',
'src/ctimer.cpp',
'src/panoramavote.cpp',
Expand Down
2 changes: 2 additions & 0 deletions CS2Fixes.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@
<ClCompile Include="src\utils\entity.cpp" />
<ClCompile Include="src\utils\plat_unix.cpp" />
<ClCompile Include="src\utils\plat_win.cpp" />
<ClCompile Include="src\utils\weapon.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="src\buttonwatch.h" />
Expand Down Expand Up @@ -287,6 +288,7 @@
<ClInclude Include="src\utils\plat.h" />
<ClInclude Include="src\utils\virtual.h" />
<ClInclude Include="src\netmessages.h" />
<ClInclude Include="src\utils\weapon.h" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets" />
Expand Down
6 changes: 6 additions & 0 deletions CS2Fixes.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,9 @@
<ClCompile Include="src\entwatch.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\utils\weapon.cpp">
<Filter>Source Files\utils</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="src\mempatch.h">
Expand Down Expand Up @@ -388,5 +391,8 @@
<ClInclude Include="src\cs2_sdk\entity\viewmodels.h">
<Filter>Header Files\cs2_sdk\entity</Filter>
</ClInclude>
<ClInclude Include="src\utils\weapon.h">
<Filter>Header Files\utils</Filter>
</ClInclude>
</ItemGroup>
</Project>
5 changes: 5 additions & 0 deletions gamedata/cs2fixes.games.txt
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,11 @@
"windows" "88"
"linux" "80"
}
"CBaseEntity::Precache"
{
"windows" "4"
"linux" "5"
}
"CBaseEntity::Use"
{
"windows" "140"
Expand Down
159 changes: 63 additions & 96 deletions src/commands.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/**
/**
* =============================================================================
* CS2Fixes
* Copyright (C) 2023-2025 Source2ZE
Expand Down Expand Up @@ -59,70 +59,27 @@ bool g_bEnableAdminCommands;
FAKE_BOOL_CVAR(cs2f_commands_enable, "Whether to enable chat commands", g_bEnableCommands, false, 0)
FAKE_BOOL_CVAR(cs2f_admin_commands_enable, "Whether to enable admin chat commands", g_bEnableAdminCommands, false, 0)

// clang-format off
WeaponMapEntry_t WeaponMap[] = {
{{"bizon"}, "weapon_bizon", "PP-Bizon", 1400, 26, GEAR_SLOT_RIFLE},
{{"mac10", "mac"}, "weapon_mac10", "MAC-10", 1050, 27, GEAR_SLOT_RIFLE},
{{"mp5sd", "mp5"}, "weapon_mp5sd", "MP5-SD", 1500, 23, GEAR_SLOT_RIFLE},
{{"mp7"}, "weapon_mp7", "MP7", 1500, 23, GEAR_SLOT_RIFLE},
{{"mp9"}, "weapon_mp9", "MP9", 1250, 34, GEAR_SLOT_RIFLE},
{{"p90"}, "weapon_p90", "P90", 2350, 19, GEAR_SLOT_RIFLE},
{{"ump45", "ump"}, "weapon_ump45", "UMP-45", 1200, 24, GEAR_SLOT_RIFLE},
{{"ak47", "ak"}, "weapon_ak47", "AK-47", 2700, 7, GEAR_SLOT_RIFLE},
{{"aug"}, "weapon_aug", "AUG", 3300, 8, GEAR_SLOT_RIFLE},
{{"famas"}, "weapon_famas", "FAMAS", 2050, 10, GEAR_SLOT_RIFLE},
{{"galilar", "galil"}, "weapon_galilar", "Galil AR", 1800, 13, GEAR_SLOT_RIFLE},
{{"m4a4"}, "weapon_m4a1", "M4A4", 3100, 16, GEAR_SLOT_RIFLE},
{{"m4a1-s", "m4a1"}, "weapon_m4a1_silencer", "M4A1-S", 2900, 60, GEAR_SLOT_RIFLE},
{{"sg553"}, "weapon_sg556", "SG 553", 3000, 39, GEAR_SLOT_RIFLE},
{{"awp"}, "weapon_awp", "AWP", 4750, 9, GEAR_SLOT_RIFLE},
{{"g3sg1"}, "weapon_g3sg1", "G3SG1", 5000, 11, GEAR_SLOT_RIFLE},
{{"scar20", "scar"}, "weapon_scar20", "SCAR-20", 5000, 38, GEAR_SLOT_RIFLE},
{{"ssg08", "ssg"}, "weapon_ssg08", "SSG 08", 1700, 40, GEAR_SLOT_RIFLE},
{{"mag7", "mag"}, "weapon_mag7", "MAG-7", 1300, 29, GEAR_SLOT_RIFLE},
{{"nova"}, "weapon_nova", "Nova", 1050, 35, GEAR_SLOT_RIFLE},
{{"sawedoff"}, "weapon_sawedoff", "Sawed-Off", 1100, 29, GEAR_SLOT_RIFLE},
{{"xm1014", "xm"}, "weapon_xm1014", "XM1014", 2000, 25, GEAR_SLOT_RIFLE},
{{"m249"}, "weapon_m249", "M249", 5200, 14, GEAR_SLOT_RIFLE},
{{"negev"}, "weapon_negev", "Negev", 1700, 28, GEAR_SLOT_RIFLE},
{{"deagle"}, "weapon_deagle", "Desert Eagle", 700, 1, GEAR_SLOT_PISTOL},
{{"dualberettas", "elite"}, "weapon_elite", "Dual Berettas", 300, 2, GEAR_SLOT_PISTOL},
{{"fiveseven"}, "weapon_fiveseven", "Five-SeveN", 500, 3, GEAR_SLOT_PISTOL},
{{"glock18", "glock"}, "weapon_glock", "Glock-18", 200, 4, GEAR_SLOT_PISTOL},
{{"p2000"}, "weapon_hkp2000", "P2000", 200, 32, GEAR_SLOT_PISTOL},
{{"p250"}, "weapon_p250", "P250", 300, 36, GEAR_SLOT_PISTOL},
{{"tec9"}, "weapon_tec9", "Tec-9", 500, 30, GEAR_SLOT_PISTOL},
{{"usp-s", "usp"}, "weapon_usp_silencer", "USP-S", 200, 61, GEAR_SLOT_PISTOL},
{{"cz75-auto", "cs75a", "cz"}, "weapon_cz75a", "CZ75-Auto", 500, 63, GEAR_SLOT_PISTOL},
{{"r8revolver", "revolver", "r8"}, "weapon_revolver", "R8 Revolver", 600, 64, GEAR_SLOT_PISTOL},
{{"hegrenade", "he"}, "weapon_hegrenade", "HE Grenade", 300, 44, GEAR_SLOT_GRENADES, 1},
{{"molotov"}, "weapon_molotov", "Molotov", 400, 46, GEAR_SLOT_GRENADES, 1},
{{"kevlar"}, "item_kevlar", "Kevlar Vest", 650, 50, GEAR_SLOT_UTILITY},
};
// clang-format on

bool g_bEnableWeapons = false;

FAKE_BOOL_CVAR(cs2f_weapons_enable, "Whether to enable weapon commands", g_bEnableWeapons, false, false)

int GetGrenadeAmmo(CCSPlayer_WeaponServices* pWeaponServices, WeaponMapEntry_t weaponEntry)
int GetGrenadeAmmo(CCSPlayer_WeaponServices* pWeaponServices, const WeaponInfo_t* pWeaponInfo)
{
if (!pWeaponServices || weaponEntry.iGearSlot != GEAR_SLOT_GRENADES)
if (!pWeaponServices || pWeaponInfo->m_eSlot != GEAR_SLOT_GRENADES)
return -1;

// TODO: look into molotov vs inc interaction
if (strcmp(weaponEntry.szClassName, "weapon_hegrenade") == 0)
if (strcmp(pWeaponInfo->m_pClass, "weapon_hegrenade") == 0)
return pWeaponServices->m_iAmmo[AMMO_OFFSET_HEGRENADE];
else if (strcmp(weaponEntry.szClassName, "weapon_molotov") == 0 || strcmp(weaponEntry.szClassName, "weapon_incgrenade") == 0)
if (strcmp(pWeaponInfo->m_pClass, "weapon_molotov") == 0 || strcmp(pWeaponInfo->m_pClass, "weapon_incgrenade") == 0)
return pWeaponServices->m_iAmmo[AMMO_OFFSET_MOLOTOV];
else if (strcmp(weaponEntry.szClassName, "weapon_decoy") == 0)
if (strcmp(pWeaponInfo->m_pClass, "weapon_decoy") == 0)
return pWeaponServices->m_iAmmo[AMMO_OFFSET_DECOY];
else if (strcmp(weaponEntry.szClassName, "weapon_flashbang") == 0)
if (strcmp(pWeaponInfo->m_pClass, "weapon_flashbang") == 0)
return pWeaponServices->m_iAmmo[AMMO_OFFSET_FLASHBANG];
else if (strcmp(weaponEntry.szClassName, "weapon_smokegrenade") == 0)
if (strcmp(pWeaponInfo->m_pClass, "weapon_smokegrenade") == 0)
return pWeaponServices->m_iAmmo[AMMO_OFFSET_SMOKEGRENADE];
else
return -1;
return -1;
}

int GetGrenadeAmmoTotal(CCSPlayer_WeaponServices* pWeaponServices)
Expand Down Expand Up @@ -152,32 +109,15 @@ void ParseWeaponCommand(const CCommand& args, CCSPlayerController* player)

VPROF("ParseWeaponCommand");

CCSPlayerPawn* pPawn = (CCSPlayerPawn*)player->GetPawn();
WeaponMapEntry_t weaponEntry;
bool foundWeapon = false;

for (int i = 0; i < sizeof(WeaponMap) / sizeof(*WeaponMap); i++)
{
if (foundWeapon)
break;

weaponEntry = WeaponMap[i];
const char* command = args[0];
const auto pPawn = reinterpret_cast<CCSPlayerPawn*>(player->GetPawn());

if (!V_strncmp("c_", command, 2))
command = command + 2;
const char* command = args[0];
if (!V_strncmp("c_", command, 2))
command = command + 2;

for (std::string alias : weaponEntry.aliases)
{
if (!V_stricmp(command, alias.c_str()))
{
foundWeapon = true;
break;
}
}
}
const auto pWeaponInfo = FindWeaponInfoByAlias(command);

if (!foundWeapon)
if (!pWeaponInfo || pWeaponInfo->m_nPrice == 0)
return;

if (pPawn->m_iHealth() <= 0 || pPawn->m_iTeamNum != CS_TEAM_CT)
Expand All @@ -195,29 +135,27 @@ void ParseWeaponCommand(const CCommand& args, CCSPlayerController* player)

int money = player->m_pInGameMoneyServices->m_iAccount;

if (money < weaponEntry.iPrice)
if (money < pWeaponInfo->m_nPrice)
{
ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "You can't afford %s! It costs $%i, you only have $%i", weaponEntry.szWeaponName, weaponEntry.iPrice, money);
ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "You can't afford %s! It costs $%i, you only have $%i", pWeaponInfo->m_pName, pWeaponInfo->m_nPrice, money);
return;
}

if (weaponEntry.iGearSlot == GEAR_SLOT_GRENADES)
if (pWeaponInfo->m_eSlot == GEAR_SLOT_GRENADES)
{
CUtlVector<CHandle<CBasePlayerWeapon>>* weapons = pWeaponServices->m_hMyWeapons();

// CONVAR_TODO
ConVar* cvar = g_pCVar->GetConVar(g_pCVar->FindConVar("ammo_grenade_limit_default"));
// HACK: values is actually the cvar value itself, hence this ugly cast.
int iGrenadeLimitDefault = *(int*)&cvar->values;
cvar = g_pCVar->GetConVar(g_pCVar->FindConVar("ammo_grenade_limit_total"));
int iGrenadeLimitTotal = *(int*)&cvar->values;

int iMatchingGrenades = GetGrenadeAmmo(pWeaponServices, weaponEntry);
int iMatchingGrenades = GetGrenadeAmmo(pWeaponServices, pWeaponInfo);
int iTotalGrenades = GetGrenadeAmmoTotal(pWeaponServices);

if (iMatchingGrenades >= iGrenadeLimitDefault)
{
ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "You cannot carry any more %ss (Max %i)", weaponEntry.szWeaponName, iGrenadeLimitDefault);
ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "You cannot carry any more %ss (Max %i)", pWeaponInfo->m_pName, iGrenadeLimitDefault);
return;
}

Expand All @@ -228,18 +166,18 @@ void ParseWeaponCommand(const CCommand& args, CCSPlayerController* player)
}
}

if (weaponEntry.maxAmount)
if (pWeaponInfo->m_nMaxAmount)
{
CUtlVector<WeaponPurchaseCount_t>* weaponPurchases = pPawn->m_pActionTrackingServices->m_weaponPurchasesThisRound().m_weaponPurchases;
bool found = false;
FOR_EACH_VEC(*weaponPurchases, i)
{
WeaponPurchaseCount_t& purchase = (*weaponPurchases)[i];
if (purchase.m_nItemDefIndex == weaponEntry.iItemDefIndex)
if (purchase.m_nItemDefIndex == pWeaponInfo->m_iItemDefinitionIndex)
{
if (purchase.m_nCount >= weaponEntry.maxAmount)
if (purchase.m_nCount >= pWeaponInfo->m_nMaxAmount)
{
ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "You cannot buy any more %s (Max %i)", weaponEntry.szWeaponName, weaponEntry.maxAmount);
ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "You cannot buy any more %s (Max %i)", pWeaponInfo->m_pName, pWeaponInfo->m_nMaxAmount);
return;
}
purchase.m_nCount += 1;
Expand All @@ -253,13 +191,13 @@ void ParseWeaponCommand(const CCommand& args, CCSPlayerController* player)
WeaponPurchaseCount_t purchase = {};

purchase.m_nCount = 1;
purchase.m_nItemDefIndex = weaponEntry.iItemDefIndex;
purchase.m_nItemDefIndex = pWeaponInfo->m_iItemDefinitionIndex;

weaponPurchases->AddToTail(purchase);
}
}

if (weaponEntry.iGearSlot == GEAR_SLOT_RIFLE || weaponEntry.iGearSlot == GEAR_SLOT_PISTOL)
if (pWeaponInfo->m_eSlot == GEAR_SLOT_RIFLE || pWeaponInfo->m_eSlot == GEAR_SLOT_PISTOL)
{
CUtlVector<CHandle<CBasePlayerWeapon>>* weapons = pWeaponServices->m_hMyWeapons();

Expand All @@ -270,17 +208,46 @@ void ParseWeaponCommand(const CCommand& args, CCSPlayerController* player)
if (!weapon)
continue;

if (weapon->GetWeaponVData()->m_GearSlot() == weaponEntry.iGearSlot)
if (weapon->GetWeaponVData()->m_GearSlot() == pWeaponInfo->m_eSlot)
{
pWeaponServices->DropWeapon(weapon);
break;
}
}
}

player->m_pInGameMoneyServices->m_iAccount = money - weaponEntry.iPrice;
pItemServices->GiveNamedItem(weaponEntry.szClassName);
ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "You have purchased %s for $%i", weaponEntry.szWeaponName, weaponEntry.iPrice);
CBasePlayerWeapon* pWeapon = pItemServices->GiveNamedItemAws(pWeaponInfo->m_pClass);

// Normally shouldn't be possible, but avoid crashes in some edge cases
if (!pWeapon)
return;

player->m_pInGameMoneyServices->m_iAccount = money - pWeaponInfo->m_nPrice;

// If the weapon spawn goes through AWS, it needs to be reselected with a 1 tick delay (some fuckery with team change?)
if (pWeaponInfo->m_eSlot == GEAR_SLOT_RIFLE || pWeaponInfo->m_eSlot == GEAR_SLOT_PISTOL)
{
CHandle<CBasePlayerWeapon> hWeapon = pWeapon->GetHandle();
CHandle<CCSPlayerPawn> hPawn = pPawn->GetHandle();

new CTimer(0.0f, false, false, [hWeapon, hPawn]() {
CBasePlayerWeapon* pWeapon = hWeapon.Get();
CCSPlayerPawn* pPawn = hPawn.Get();

if (!pWeapon || !pPawn)
return -1.0f;

CCSPlayer_WeaponServices* pWeaponServices = pPawn->m_pWeaponServices;

if (!pWeaponServices)
return -1.0f;

pWeaponServices->SelectItem(pWeapon);
return -1.0f;
});
}

ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "You have purchased %s for $%i", pWeaponInfo->m_pName, pWeaponInfo->m_nPrice);
}

void WeaponCommandCallback(const CCommandContext& context, const CCommand& args)
Expand All @@ -298,11 +265,11 @@ void WeaponCommandCallback(const CCommandContext& context, const CCommand& args)

void RegisterWeaponCommands()
{
for (int i = 0; i < sizeof(WeaponMap) / sizeof(*WeaponMap); i++)
{
WeaponMapEntry_t weaponEntry = WeaponMap[i];
const auto& weapons = GenerateWeaponCommands();

for (std::string alias : weaponEntry.aliases)
for (const auto& aliases : weapons | std::views::values)
{
for (const auto& alias : aliases)
{
new CChatCommand(alias.c_str(), ParseWeaponCommand, "- Buys this weapon", ADMFLAG_NONE, CMDFLAG_NOHELP);
ConCommandRefAbstract ref;
Expand Down
13 changes: 1 addition & 12 deletions src/commands.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/**
/**
* =============================================================================
* CS2Fixes
* Copyright (C) 2023-2025 Source2ZE
Expand Down Expand Up @@ -96,17 +96,6 @@ class CChatCommand
uint64 m_nCmdFlags;
};

struct WeaponMapEntry_t
{
std::vector<std::string> aliases;
const char* szClassName;
const char* szWeaponName;
int iPrice;
uint16 iItemDefIndex;
gear_slot_t iGearSlot;
int maxAmount = 0;
};

void RegisterWeaponCommands();
void ParseChatCommand(const char*, CCSPlayerController*);

Expand Down
8 changes: 8 additions & 0 deletions src/cs2_sdk/entity/cbaseentity.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ extern CGameConfig* g_GameConfig;
class CGameUI;
class CEnvHudHint;
class CPointViewControl;
class CBasePlayerWeapon;

class CGameSceneNode
{
Expand Down Expand Up @@ -319,6 +320,13 @@ class CBaseEntity : public CEntityInstance
return nullptr;
}

[[nodiscard]] CBasePlayerWeapon* AsBasePlayerWeapon()
{
if (V_StringHasPrefixCaseSensitive(GetClassname(), "weapon_"))
return reinterpret_cast<CBasePlayerWeapon*>(this);
return nullptr;
}

/* End Custom Entities Cast */
};

Expand Down
11 changes: 9 additions & 2 deletions src/cs2_sdk/entity/cbaseplayerpawn.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ class CBasePlayerPawn : public CBaseModelEntity
return;

CUtlVector<CHandle<CBasePlayerWeapon>>* weapons = m_pWeaponServices()->m_hMyWeapons();
std::vector<CBasePlayerWeapon*> vecWeaponsToDrop;

FOR_EACH_VEC(*weapons, i)
{
Expand All @@ -58,8 +59,14 @@ class CBasePlayerPawn : public CBaseModelEntity

// If this is a map-spawned weapon (items), drop it
if (V_strcmp(pWeapon->m_sUniqueHammerID().Get(), "") && pWeapon->GetWeaponVData()->m_GearSlot() != GEAR_SLOT_KNIFE)
m_pWeaponServices()->DropWeapon(pWeapon);
{
// Queue for dropping after, don't modify this vector while we're iterating it
vecWeaponsToDrop.push_back(pWeapon);
}
}

for (CBasePlayerWeapon* pWeapon : vecWeaponsToDrop)
m_pWeaponServices()->DropWeapon(pWeapon);
}

void CommitSuicide(bool bExplode, bool bForce)
Expand All @@ -73,7 +80,7 @@ class CBasePlayerPawn : public CBaseModelEntity
if (pController)
EW_PlayerDeathPre(pController);
}

DropMapWeapons();
}

Expand Down
Loading
Loading