diff --git a/cl_dll/hl/hl_baseentity.cpp b/cl_dll/hl/hl_baseentity.cpp index 7c5ea948..25fff6e3 100644 --- a/cl_dll/hl/hl_baseentity.cpp +++ b/cl_dll/hl/hl_baseentity.cpp @@ -324,7 +324,7 @@ Vector CBasePlayer :: AutoaimDeflection( Vector &vecSrc, float flDist, float flD void CBasePlayer :: ResetAutoaim( ) { } void CBasePlayer :: SetCustomDecalFrames( int nFrames ) { } int CBasePlayer :: GetCustomDecalFrames( void ) { return -1; } -void CBasePlayer::DropPlayerItem ( char *pszItemName ) { } +void CBasePlayer::DropPlayerItem ( const char *pszItemName ) { } BOOL CBasePlayer::HasPlayerItem( CBasePlayerItem *pCheckItem ) { return FALSE; } BOOL CBasePlayer :: SwitchWeapon( CBasePlayerItem *pWeapon ) { return FALSE; } Vector CBasePlayer :: GetGunPosition( void ) { return g_vecZero; } diff --git a/dlls/hooks/hlds_hooks.cpp b/dlls/hooks/hlds_hooks.cpp index 1dd6ac22..f42d2c56 100644 --- a/dlls/hooks/hlds_hooks.cpp +++ b/dlls/hooks/hlds_hooks.cpp @@ -558,81 +558,6 @@ void ServerDeactivate( void ) #include "lagcomp.h" -void PrecacheWeapons() { - if (g_mapWeapons.find("weapon_shotgun") != g_mapWeapons.end()) { - UTIL_PrecacheOther("weapon_shotgun"); - UTIL_PrecacheOther("ammo_buckshot"); - } - if (g_mapWeapons.find("weapon_crowbar") != g_mapWeapons.end()) { - UTIL_PrecacheOther("weapon_crowbar"); - } - if (g_mapWeapons.find("weapon_9mmhandgun") != g_mapWeapons.end()) { - UTIL_PrecacheOther("weapon_9mmhandgun"); - UTIL_PrecacheOther("ammo_9mmclip"); - } - if (g_mapWeapons.find("weapon_9mmAR") != g_mapWeapons.end()) { - UTIL_PrecacheOther("weapon_9mmAR"); - UTIL_PrecacheOther("ammo_9mmAR"); - UTIL_PrecacheOther("ammo_ARgrenades"); - } - if (g_mapWeapons.find("weapon_357") != g_mapWeapons.end()) { - UTIL_PrecacheOther("weapon_357"); - UTIL_PrecacheOther("ammo_357"); - } - if (g_mapWeapons.find("weapon_gauss") != g_mapWeapons.end()) { - UTIL_PrecacheOther("weapon_gauss"); - UTIL_PrecacheOther("ammo_gaussclip"); - } - if (g_mapWeapons.find("weapon_rpg") != g_mapWeapons.end()) { - UTIL_PrecacheOther("weapon_rpg"); - UTIL_PrecacheOther("ammo_rpgclip"); - } - if (g_mapWeapons.find("weapon_crossbow") != g_mapWeapons.end()) { - UTIL_PrecacheOther("weapon_crossbow"); - UTIL_PrecacheOther("ammo_crossbow"); - } - if (g_mapWeapons.find("weapon_egon") != g_mapWeapons.end()) { - UTIL_PrecacheOther("weapon_egon"); - UTIL_PrecacheOther("ammo_gaussclip"); - } - if (g_mapWeapons.find("weapon_tripmine") != g_mapWeapons.end()) { - UTIL_PrecacheOther("weapon_tripmine"); - } - if (g_mapWeapons.find("weapon_satchel") != g_mapWeapons.end()) { - UTIL_PrecacheOther("weapon_satchel"); - } - if (g_mapWeapons.find("weapon_handgrenade") != g_mapWeapons.end()) { - UTIL_PrecacheOther("weapon_handgrenade"); - } - if (g_mapWeapons.find("weapon_snark") != g_mapWeapons.end()) { - UTIL_PrecacheOther("weapon_snark"); - } - if (g_mapWeapons.find("weapon_hornetgun") != g_mapWeapons.end()) { - UTIL_PrecacheOther("weapon_hornetgun"); - } - if (g_mapWeapons.find("weapon_grapple") != g_mapWeapons.end()) { - UTIL_PrecacheOther("weapon_grapple"); - } - if (g_mapWeapons.find("weapon_pipewrench") != g_mapWeapons.end()) { - UTIL_PrecacheOther("weapon_pipewrench"); - } - if (g_mapWeapons.find("weapon_displacer") != g_mapWeapons.end()) { - UTIL_PrecacheOther("weapon_displacer"); - } - if (g_mapWeapons.find("weapon_shockrifle") != g_mapWeapons.end()) { - UTIL_PrecacheOther("weapon_shockrifle"); - } - if (g_mapWeapons.find("weapon_sporelauncher") != g_mapWeapons.end()) { - UTIL_PrecacheOther("weapon_sporelauncher"); - } - if (g_mapWeapons.find("weapon_medkit") != g_mapWeapons.end()) { - UTIL_PrecacheOther("weapon_medkit"); - } - if (g_mapWeapons.find("weapon_inventory") != g_mapWeapons.end()) { - UTIL_PrecacheOther("weapon_inventory"); - } -} - void PrecacheTextureSounds() { if (g_textureStats.tex_concrete) { PRECACHE_FOOTSTEP_SOUNDS(g_stepSoundsConcrete) @@ -690,6 +615,41 @@ void PrecacheTextureSounds() { } } +int g_weaponSlotMasks[MAX_WEAPONS]; + +void MarkWeaponSlotConflicts() { + for (int i = 0; i < MAX_WEAPONS; i++) + { + ItemInfo& II = CBasePlayerItem::ItemInfoArray[i]; + + if (!II.iId) + continue; + + int mask = 1 << II.iId; + + for (int i = 0; i < MAX_WEAPONS; i++) + { + ItemInfo& II2 = CBasePlayerItem::ItemInfoArray[i]; + + if (!II2.iId) + continue; + + // when multiple weapons share a slot, the client may not show any weapon in the menu + // if only one weapon is held from that slot (depending on weapon ID order). For example, + // if picking up ID 1, the menu slot is filled as expected, but then immediately removed + // because ID 2 isn't held, which shares the same slot (see WeaponsResource::DropWeapon). + // So, the client will be told that it holds every possible weapon for that slot, if any + // any weapon is held for that slot. A network message controls which weapon is rendered + // in the shared slot. + if (II.iSlot == II2.iSlot && II.iPosition == II2.iPosition) { + mask |= 1 << II2.iId; + } + } + + g_weaponSlotMasks[II.iId] = mask; + } +} + void ServerActivate( edict_t *pEdictList, int edictCount, int clientMax ) { int i; @@ -732,9 +692,14 @@ void ServerActivate( edict_t *pEdictList, int edictCount, int clientMax ) AddMapPluginEquipment(); - PrecacheWeapons(); + for (std::string wepName : g_mapWeapons) { + UTIL_PrecacheOther(wepName.c_str()); + } + PrecacheTextureSounds(); + MarkWeaponSlotConflicts(); + if (mp_antiblock.value) PRECACHE_SOUND_ENT(NULL, "weapons/xbow_hitbod2.wav"); @@ -1866,6 +1831,14 @@ void UpdateClientData ( const edict_t *ent, int sendweapons, struct clientdata_s cd->watertype = pev->watertype; cd->weapons = pev->weapons; + for (int i = 0; i < MAX_WEAPONS; i++) { + if (pev->weapons & (1 << i)) { + // weapons that share slots occupy multiple bits so that the menu renders correctly + // (client may think the slot is empty if only one weapon is held from a shared slot) + cd->weapons |= g_weaponSlotMasks[i]; + } + } + if (pl->m_fakeSuit) { cd->weapons |= 1 << WEAPON_SUIT; } diff --git a/dlls/player/CBasePlayer.cpp b/dlls/player/CBasePlayer.cpp index 19fe6392..244408f8 100644 --- a/dlls/player/CBasePlayer.cpp +++ b/dlls/player/CBasePlayer.cpp @@ -1277,7 +1277,7 @@ void CBasePlayer::TabulateWeapons(void) { CBasePlayerItem* pPlayerItem = (CBasePlayerItem*)m_rgpPlayerItems[i].GetEntity(); while (pPlayerItem) { - pev->weapons |= (1 << pPlayerItem->m_iId); + pev->weapons |= g_weaponSlotMasks[pPlayerItem->m_iId]; pPlayerItem = (CBasePlayerItem*)pPlayerItem->m_pNext.GetEntity(); } } @@ -4241,6 +4241,8 @@ int CBasePlayer::AddPlayerItem( CBasePlayerItem *pItem ) SwitchWeapon( pItem ); } + ResolveWeaponSlotConflict(pItem->m_iId); + return TRUE; } else if (gEvilImpulse101) @@ -4657,32 +4659,38 @@ void CBasePlayer :: UpdateClientData( void ) for (i = 0; i < MAX_WEAPONS; i++) { - ItemInfo& II = CBasePlayerItem::ItemInfoArray[i]; + ItemInfo* II = &CBasePlayerItem::ItemInfoArray[i]; + + int conflictId = GetCurrentIdForConflictedSlot(i); + if (conflictId != -1 && conflictId != i) { + // all weapon infos that share a slot must point to currently held weapon in the shared slot + // or else the client won't render the menu correctly + II = &CBasePlayerItem::ItemInfoArray[conflictId]; + } - if ( !II.iId ) + if ( !II->iId ) continue; const char *pszName; - if (!II.pszName) + if (!II->pszName) pszName = "Empty"; else - pszName = II.pszName; - - MESSAGE_BEGIN( MSG_ONE, gmsgWeaponList, NULL, pev ); - WRITE_STRING(pszName); // string weapon name - WRITE_BYTE(GetAmmoIndex(II.pszAmmo1)); // byte Ammo Type - WRITE_BYTE(II.iMaxAmmo1); // byte Max Ammo 1 - WRITE_BYTE(GetAmmoIndex(II.pszAmmo2)); // byte Ammo2 Type - WRITE_BYTE(II.iMaxAmmo2); // byte Max Ammo 2 - WRITE_BYTE(II.iSlot); // byte bucket - WRITE_BYTE(II.iPosition); // byte bucket pos - WRITE_BYTE(II.iId); // byte id (bit index into pev->weapons) - WRITE_BYTE(II.iFlags); // byte Flags + pszName = II->pszName; + + MESSAGE_BEGIN(MSG_ONE, gmsgWeaponList, NULL, pev); + WRITE_STRING(II->pszName); // string weapon name + WRITE_BYTE(GetAmmoIndex(II->pszAmmo1)); // byte Ammo Type + WRITE_BYTE(II->iMaxAmmo1); // byte Max Ammo 1 + WRITE_BYTE(GetAmmoIndex(II->pszAmmo2)); // byte Ammo2 Type + WRITE_BYTE(II->iMaxAmmo2); // byte Max Ammo 2 + WRITE_BYTE(II->iSlot); // byte bucket + WRITE_BYTE(II->iPosition); // byte bucket pos + WRITE_BYTE(i); // byte id (bit index into pev->weapons) + WRITE_BYTE(II->iFlags); // byte Flags MESSAGE_END(); } } - SendAmmoUpdate(); // Update all the items @@ -5140,7 +5148,7 @@ void CBasePlayer::CleanupWeaponboxes(void) // DropPlayerItem - drop the named item, or if no name, // the active item. //========================================================= -void CBasePlayer::DropPlayerItem ( char *pszItemName ) +void CBasePlayer::DropPlayerItem ( const char *pszItemName ) { if ( !g_pGameRules->IsMultiplayer() ) { @@ -5169,10 +5177,13 @@ void CBasePlayer::DropPlayerItem ( char *pszItemName ) while ( pWeapon ) { + ItemInfo info; + pWeapon->GetItemInfo(&info); + if ( pszItemName ) { - // try to match by name. - if ( !strcmp( pszItemName, STRING( pWeapon->pev->classname ) ) ) + // try to match by classname or name. + if ( !strcmp( pszItemName, STRING( pWeapon->pev->classname ) ) || !strcmp(pszItemName, info.pszName)) { // match! break; @@ -6331,4 +6342,62 @@ void CBasePlayer::LoadScore() { m_iDeaths = 0; m_scoreMultiplier = 1.0f; } +} + +void CBasePlayer::ResolveWeaponSlotConflict(int wepId) { + int mask = g_weaponSlotMasks[wepId]; + + if (count_bits_set(mask) <= 1) { + return; // impossible for there to be a conflict + } + + ItemInfo& II = CBasePlayerItem::ItemInfoArray[wepId]; + + for (int i = 0; i < MAX_WEAPONS; i++) { + int bit = (1 << i); + if (mask & bit) { + if ((pev->weapons & bit) && i != wepId) { + // player is already holding a weapon that fills this slot. + // Drop this held weapon because the player won't be able to choose which + // weapon they want from that slot. + ItemInfo& dropInfo = CBasePlayerItem::ItemInfoArray[i]; + DropPlayerItem(dropInfo.pszName); + } + + // redirect all item info to the weapon with the given ID + MESSAGE_BEGIN(MSG_ONE, gmsgWeaponList, NULL, pev); + WRITE_STRING(II.pszName); // string weapon name + WRITE_BYTE(GetAmmoIndex(II.pszAmmo1)); // byte Ammo Type + WRITE_BYTE(II.iMaxAmmo1); // byte Max Ammo 1 + WRITE_BYTE(GetAmmoIndex(II.pszAmmo2)); // byte Ammo2 Type + WRITE_BYTE(II.iMaxAmmo2); // byte Max Ammo 2 + WRITE_BYTE(II.iSlot); // byte bucket + WRITE_BYTE(II.iPosition); // byte bucket pos + WRITE_BYTE(i); // byte id (bit index into pev->weapons) + WRITE_BYTE(II.iFlags); // byte Flags + MESSAGE_END(); + ALERT(at_console, "acktually, ID %d is now for %s\n", i, II.pszName); + } + } +} + +int CBasePlayer::GetCurrentIdForConflictedSlot(int wepId) { + int mask = g_weaponSlotMasks[wepId]; + + ItemInfo& II = CBasePlayerItem::ItemInfoArray[wepId]; + + if (count_bits_set(mask) <= 1) { + return wepId; // impossible for there to be a conflict + } + + for (int i = 0; i < MAX_WEAPONS; i++) { + int bit = (1 << i); + if (mask & bit) { + if ((pev->weapons & bit) && i != wepId) { + return i; + } + } + } + + return -1; } \ No newline at end of file diff --git a/dlls/player/CBasePlayer.h b/dlls/player/CBasePlayer.h index 328a1f2e..831577e0 100644 --- a/dlls/player/CBasePlayer.h +++ b/dlls/player/CBasePlayer.h @@ -367,7 +367,7 @@ class EXPORT CBasePlayer : public CBaseMonster void AddPointsToTeam( int score, BOOL bAllowNegativeScore ); BOOL AddPlayerItem( CBasePlayerItem *pItem ); BOOL RemovePlayerItem( CBasePlayerItem *pItem ); - void DropPlayerItem ( char *pszItemName ); + void DropPlayerItem ( const char *pszItemName ); void DropAmmo(bool secondary); BOOL HasPlayerItem( CBasePlayerItem *pCheckItem ); CBasePlayerItem* GetNamedPlayerItem(const char* pszItemName); @@ -535,6 +535,15 @@ class EXPORT CBasePlayer : public CBaseMonster // load score from global state, or initialize to 0 void LoadScore(); + + // tell the client which weapon belongs in a slot which multiple weapons can fill + void ResolveWeaponSlotConflict(int wepId); + + // if a weapon slot can be filled by multiple weapons, this returns the weapon ID + // that is currently held in that slot. If no weapon is filling the slot + // that queryWepId fills, then -1 is returned. + // queryWepId can be any weapon that fills the slot in question + int GetCurrentIdForConflictedSlot(int queryWepId); // for sven-style monster info //void UpdateMonsterInfo(); diff --git a/dlls/util/util.cpp b/dlls/util/util.cpp index 08bb0074..65718d15 100644 --- a/dlls/util/util.cpp +++ b/dlls/util/util.cpp @@ -3255,3 +3255,14 @@ const char* cstr(string_t s) { return STRING(s); } +uint32_t count_bits_set(uint32_t v) { + // https://graphics.stanford.edu/~seander/bithacks.html + uint32_t c; // c accumulates the total bits set in v + + for (c = 0; v; c++) { + v &= v - 1; // clear the least significant bit set + } + + return c; +} + diff --git a/dlls/util/util.h b/dlls/util/util.h index 136ddd91..07f82eec 100644 --- a/dlls/util/util.h +++ b/dlls/util/util.h @@ -49,6 +49,7 @@ extern EXPORT globalvars_t *gpGlobals; extern std::unordered_set g_weaponNames; // names given by weapons (may have a prefix: "hlcoop/weapon_grapple") extern std::unordered_set g_weaponClassnames; // valid weapon classnames +extern int g_weaponSlotMasks[MAX_WEAPONS]; // for handling slot conflict extern int g_serveractive; // 1 if ServerActivate was called (no longer safe to precache) extern int g_edictsinit; // 1 if all edicts were allocated so that relocations can begin @@ -869,6 +870,8 @@ EXPORT bool folderExists(const std::string& path); EXPORT uint64_t getFreeSpace(const std::string& path); +EXPORT uint32_t count_bits_set(uint32_t v); + EXPORT void UTIL_BeamFollow(int entindex, int modelIdx, int life, int width, RGBA color, int msgMode=MSG_BROADCAST, const float* msgOrigin=NULL, edict_t* targetEnt=NULL); EXPORT void UTIL_Fizz(int eidx, int modelIdx, uint8_t density, int msgMode=MSG_BROADCAST, const float* msgOrigin=NULL, edict_t* targetEnt=NULL); EXPORT void UTIL_ELight(int entindex, int attachment, Vector origin, float radius, RGBA color, int life, float decay, int msgMode=MSG_BROADCAST, const float* msgOrigin=NULL, edict_t* targetEnt=NULL); @@ -879,4 +882,4 @@ EXPORT void UTIL_KillBeam(int entindex, int msgMode = MSG_BROADCAST, const float EXPORT void UTIL_BSPDecal(int entindex, Vector origin, int decalIdx, int msgMode=MSG_BROADCAST, const float* msgOrigin=NULL, edict_t* targetEnt=NULL); EXPORT void UTIL_PlayerDecal(int entindex, int playernum, Vector origin, int decalIdx, int msgMode = MSG_BROADCAST, const float* msgOrigin = NULL, edict_t* targetEnt = NULL); EXPORT void UTIL_GunshotDecal(int entindex, Vector origin, int decalIdx, int msgMode = MSG_BROADCAST, const float* msgOrigin = NULL, edict_t* targetEnt = NULL); -EXPORT void UTIL_Decal(int entindex, Vector origin, int decalIdx, int msgMode = MSG_BROADCAST, const float* msgOrigin = NULL, edict_t* targetEnt = NULL); \ No newline at end of file +EXPORT void UTIL_Decal(int entindex, Vector origin, int decalIdx, int msgMode = MSG_BROADCAST, const float* msgOrigin = NULL, edict_t* targetEnt = NULL); diff --git a/dlls/weapon/CGlock.cpp b/dlls/weapon/CGlock.cpp index 4b7efd1c..ab9d6aad 100644 --- a/dlls/weapon/CGlock.cpp +++ b/dlls/weapon/CGlock.cpp @@ -69,6 +69,7 @@ void CGlock::Precache( void ) PRECACHE_SOUND ("weapons/pl_gun3.wav");//handgun UTIL_PrecacheOther("ammo_9mm"); + UTIL_PrecacheOther("ammo_9mmclip"); PrecacheEvents(); } diff --git a/dlls/weapon/weapons.cpp b/dlls/weapon/weapons.cpp index a5c772d0..4e747fd3 100644 --- a/dlls/weapon/weapons.cpp +++ b/dlls/weapon/weapons.cpp @@ -342,7 +342,7 @@ ItemInfo UTIL_RegisterWeapon( const char *szClassname ) } if (info.iPosition < 0) { - ALERT(at_error, "Failed to register weapon '%s' (slot %d has too many weapons)\n", + ALERT(at_error, "Failed to register weapon '%s' (slot %d has too many weapons for automatic assignment)\n", szClassname, info.iSlot); goto cleanup; } @@ -352,14 +352,6 @@ ItemInfo UTIL_RegisterWeapon( const char *szClassname ) goto cleanup; } - if (g_filledWeaponSlots[info.iSlot][info.iPosition]) { - ALERT(at_error, "Failed to register weapon '%s' (slot %d position %d is filled by '%s')\n", - szClassname, info.iSlot, info.iPosition, g_filledWeaponSlots[info.iSlot][info.iPosition]); - goto cleanup; - } - - g_filledWeaponSlots[info.iSlot][info.iPosition] = szClassname; - CBasePlayerItem::ItemInfoArray[info.iId] = info; if (info.pszAmmo1 && *info.pszAmmo1) { @@ -380,10 +372,15 @@ ItemInfo UTIL_RegisterWeapon( const char *szClassname ) if (g_registeringCustomWeps) { PRECACHE_HUD_FILES(("sprites/" + std::string(info.pszName) + ".txt").c_str()); - ALERT(at_console, "Registered custom weapon '%s' (ID %d) to slot %d position %d\n", - szClassname, info.iId, info.iSlot, info.iPosition); + const char* conflictWep = g_filledWeaponSlots[info.iSlot][info.iPosition]; + std::string conflict = conflictWep ? UTIL_VarArgs(" (conflicts with %s)", conflictWep) : ""; + + ALERT(at_console, "Registered custom weapon '%s' (ID %d) to slot %d position %d%s\n", + szClassname, info.iId, info.iSlot, info.iPosition, conflict.c_str()); } + g_filledWeaponSlots[info.iSlot][info.iPosition] = szClassname; + cleanup: REMOVE_ENTITY(pent); return info;