From a546997723a0d3eb18820999804042fde8588cfd Mon Sep 17 00:00:00 2001 From: s1lentq Date: Sun, 31 Mar 2024 22:46:06 +0700 Subject: [PATCH 01/50] CWeaponBox::Touch: Fixed a hang when touching a weaponbox if multiple items are packed in a slot --- regamedll/dlls/weapons.cpp | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/regamedll/dlls/weapons.cpp b/regamedll/dlls/weapons.cpp index 2c035c746..b44b288dd 100644 --- a/regamedll/dlls/weapons.cpp +++ b/regamedll/dlls/weapons.cpp @@ -1974,6 +1974,7 @@ void CWeaponBox::Touch(CBaseEntity *pOther) if (!m_rgpPlayerItems[i]) continue; + CBasePlayerItem *pPrev = NULL; CBasePlayerItem *pItem = m_rgpPlayerItems[i]; // have at least one weapon in this slot @@ -2070,13 +2071,13 @@ void CWeaponBox::Touch(CBaseEntity *pOther) } else if (i == GRENADE_SLOT) { - CBasePlayerWeapon *pGrenade = static_cast(m_rgpPlayerItems[i]); + CBasePlayerWeapon *pGrenade = static_cast(pItem); if (pGrenade && pGrenade->IsWeapon()) { int playerGrenades = pPlayer->m_rgAmmo[pGrenade->m_iPrimaryAmmoType]; #ifdef REGAMEDLL_FIXES - CBasePlayerItem *pNext = m_rgpPlayerItems[i]->m_pNext; + CBasePlayerItem *pNext = pItem->m_pNext; // Determine the max ammo capacity for the picked-up grenade int iMaxPickupAmmo = pGrenade->iMaxAmmo1(); @@ -2094,7 +2095,11 @@ void CWeaponBox::Touch(CBaseEntity *pOther) playerGrenades, pGrenade->pszAmmo1(), iMaxPickupAmmo, &givenItem)) { // unlink this weapon from the box - m_rgpPlayerItems[i] = pItem = pNext; + if (pPrev) + pPrev->m_pNext = pItem = pNext; + else + m_rgpPlayerItems[i] = pItem = pNext; + continue; } #else @@ -2143,7 +2148,8 @@ void CWeaponBox::Touch(CBaseEntity *pOther) } else { - auto pNext = m_rgpPlayerItems[i]->m_pNext; + CBasePlayerItem *pNext = pItem->m_pNext; + if (pPlayer->AddPlayerItem(pItem)) { pItem->AttachToPlayer(pPlayer); @@ -2155,12 +2161,17 @@ void CWeaponBox::Touch(CBaseEntity *pOther) } // unlink this weapon from the box - m_rgpPlayerItems[i] = pItem = pNext; + if (pPrev) + pPrev->m_pNext = pNext; + else + m_rgpPlayerItems[i] = pItem = pNext; + continue; } bRemove = false; - pItem = m_rgpPlayerItems[i]->m_pNext; + pPrev = pItem; + pItem = pItem->m_pNext; } } From f711276d8a722d6811a62de5415a3ea0db0a310d Mon Sep 17 00:00:00 2001 From: s1lentq Date: Thu, 4 Apr 2024 19:31:27 +0700 Subject: [PATCH 02/50] ignore kill assist when victim killed himself while changing team --- regamedll/dlls/player.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/regamedll/dlls/player.cpp b/regamedll/dlls/player.cpp index 06c550890..3638f2618 100644 --- a/regamedll/dlls/player.cpp +++ b/regamedll/dlls/player.cpp @@ -10658,6 +10658,11 @@ bool CBasePlayer::Kill() // have the player kill himself pev->health = 0.0f; + +#ifdef REGAMEDLL_API + CSPlayer()->ResetAllStats(); // reset damage stats on killed himself or team change +#endif + Killed(pev, GIB_NEVER); if (CSGameRules()->m_pVIP == this) From 1ae0091947c09b2bb39a7e5b5c2d654cf36da250 Mon Sep 17 00:00:00 2001 From: s1lentq Date: Sun, 7 Apr 2024 16:41:56 +0700 Subject: [PATCH 03/50] Added CVar mp_location_area_info for display location area info in HUDs radio chat and below radar --- README.md | 1 + dist/game.cfg | 10 ++++++++++ regamedll/dlls/client.cpp | 3 +++ regamedll/dlls/game.cpp | 2 ++ regamedll/dlls/game.h | 1 + regamedll/dlls/player.cpp | 6 +++++- 6 files changed, 22 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 93cd795d2..2654bcf70 100644 --- a/README.md +++ b/README.md @@ -116,6 +116,7 @@ This means that plugins that do binary code analysis (Orpheu for example) probab | mp_freezetime_duck | 1 | 0 | 1 | Allow players to duck during freezetime.
`0` disabled
`1` enabled | | mp_freezetime_jump | 1 | 0 | 1 | Allow players to jump during freezetime.
`0` disabled
`1` enabled | | mp_defuser_allocation | 0 | 0 | 2 | Give defuser on player spawn.
`0` disabled
`1` Random players.
`2` All players. | +| mp_location_area_info | 0 | 0 | 1 | Enable location area info.
`0` disabled
`1` enabled.
Usually displayed in HUDs below radar or in radio chat as a prefix.
`NOTE`: Navigation `maps/.nav` file required and should contain place names | ## How to install zBot for CS 1.6? diff --git a/dist/game.cfg b/dist/game.cfg index 91009dd00..f426c00b9 100644 --- a/dist/game.cfg +++ b/dist/game.cfg @@ -575,3 +575,13 @@ mp_freezetime_jump "1" // // Default value: "0" mp_defuser_allocation "0" + +// Enable location area info +// Usually displayed in HUDs below radar or in radio chat as a prefix +// 0 - disabled (default behavior) +// 1 - enabled +// +// NOTE: Navigation maps/.nav file required and should contain place names +// +// Default value: "0" +mp_location_area_info "0" diff --git a/regamedll/dlls/client.cpp b/regamedll/dlls/client.cpp index 2a58443d7..0564bedd6 100644 --- a/regamedll/dlls/client.cpp +++ b/regamedll/dlls/client.cpp @@ -3723,6 +3723,9 @@ void EXT_FUNC ServerActivate(edict_t *pEdictList, int edictCount, int clientMax) #ifdef REGAMEDLL_ADD CSGameRules()->ServerActivate(); + + if (location_area_info.value) + LoadNavigationMap(); #endif } diff --git a/regamedll/dlls/game.cpp b/regamedll/dlls/game.cpp index 4ab375d8b..d9d756771 100644 --- a/regamedll/dlls/game.cpp +++ b/regamedll/dlls/game.cpp @@ -178,6 +178,7 @@ cvar_t legacy_vehicle_block = { "mp_legacy_vehicle_block", "1", 0, cvar_t dying_time = { "mp_dying_time", "3.0", 0, 3.0f, nullptr }; cvar_t defuser_allocation = { "mp_defuser_allocation", "0", 0, 0.0f, nullptr }; +cvar_t location_area_info = { "mp_location_area_info", "0", 0, 0.0f, nullptr }; void GameDLL_Version_f() { @@ -441,6 +442,7 @@ void EXT_FUNC GameDLLInit() CVAR_REGISTER(&freezetime_duck); CVAR_REGISTER(&freezetime_jump); CVAR_REGISTER(&defuser_allocation); + CVAR_REGISTER(&location_area_info); // print version CONSOLE_ECHO("ReGameDLL version: " APP_VERSION "\n"); diff --git a/regamedll/dlls/game.h b/regamedll/dlls/game.h index 706e1c2af..73cb77dd2 100644 --- a/regamedll/dlls/game.h +++ b/regamedll/dlls/game.h @@ -201,6 +201,7 @@ extern cvar_t assist_damage_threshold; extern cvar_t freezetime_duck; extern cvar_t freezetime_jump; extern cvar_t defuser_allocation; +extern cvar_t location_area_info; #endif diff --git a/regamedll/dlls/player.cpp b/regamedll/dlls/player.cpp index 3638f2618..b5134ea82 100644 --- a/regamedll/dlls/player.cpp +++ b/regamedll/dlls/player.cpp @@ -10083,7 +10083,11 @@ void CBasePlayer::UpdateLocation(bool forceUpdate) const char *placeName = ""; - if (pev->deadflag == DEAD_NO && AreBotsAllowed()) + if (pev->deadflag == DEAD_NO && ( +#ifdef REGAMEDLL_ADD + location_area_info.value || +#endif + AreBotsAllowed())) { // search the place name where is located the player Place playerPlace = TheNavAreaGrid.GetPlace(&pev->origin); From 8c14d0516838bdc4011fd5989cb600d3140055c8 Mon Sep 17 00:00:00 2001 From: s1lentq Date: Sun, 7 Apr 2024 18:49:08 +0700 Subject: [PATCH 04/50] Add fallback place names if BotChatter.db is missing --- regamedll/dlls/bot/cs_bot_chatter.cpp | 5 + regamedll/dlls/bot/cs_bot_chatter.h | 2 + regamedll/dlls/player.cpp | 13 ++- regamedll/game_shared/bot/nav_area.cpp | 133 +++++++++++++++++++++++++ regamedll/game_shared/bot/nav_area.h | 2 + regamedll/game_shared/bot/nav_file.cpp | 18 +++- 6 files changed, 169 insertions(+), 4 deletions(-) diff --git a/regamedll/dlls/bot/cs_bot_chatter.cpp b/regamedll/dlls/bot/cs_bot_chatter.cpp index 31bd03bbd..2c6b3336e 100644 --- a/regamedll/dlls/bot/cs_bot_chatter.cpp +++ b/regamedll/dlls/bot/cs_bot_chatter.cpp @@ -540,8 +540,13 @@ bool BotPhraseManager::Initialize(const char *filename, int bankIndex) else if (!Q_stricmp("UNDEFINED", token)) placeCriteria = UNDEFINED_PLACE; else + { placeCriteria = TheBotPhrases->NameToID(token); + if (!TheBotPhrases->IsValid() && placeCriteria == UNDEFINED_PLACE) + placeCriteria = TheNavAreaGrid.NameToID(token); + } + continue; } diff --git a/regamedll/dlls/bot/cs_bot_chatter.h b/regamedll/dlls/bot/cs_bot_chatter.h index f8d7f45b2..4a4591cf6 100644 --- a/regamedll/dlls/bot/cs_bot_chatter.h +++ b/regamedll/dlls/bot/cs_bot_chatter.h @@ -255,6 +255,8 @@ class BotPhraseManager Place NameToID(const char *name) const; const char *IDToName(Place id) const; + bool IsValid() const { return !m_placeList.empty(); } + // given a name, return the associated phrase collection const BotPhrase *GetPhrase(const char *name) const; diff --git a/regamedll/dlls/player.cpp b/regamedll/dlls/player.cpp index b5134ea82..b5e21eb03 100644 --- a/regamedll/dlls/player.cpp +++ b/regamedll/dlls/player.cpp @@ -415,7 +415,11 @@ void EXT_FUNC CBasePlayer::__API_HOOK(Radio)(const char *msg_id, const char *msg { // search the place name where is located the player const char *placeName = nullptr; - if (AreRunningCZero() && TheBotPhrases) + if (( +#ifdef REGAMEDLL_ADD + location_area_info.value || +#endif + AreRunningCZero()) && TheBotPhrases) { Place playerPlace = TheNavAreaGrid.GetPlace(&pev->origin); const BotPhraseList *placeList = TheBotPhrases->GetPlaceList(); @@ -427,7 +431,11 @@ void EXT_FUNC CBasePlayer::__API_HOOK(Radio)(const char *msg_id, const char *msg break; } } + + if (!placeName[0]) + placeName = TheNavAreaGrid.IDToName(playerPlace); } + if (placeName) ClientPrint(pEntity->pev, HUD_PRINTRADIO, NumAsString(entindex()), "#Game_radio_location", STRING(pev->netname), placeName, msg_verbose); else @@ -10100,6 +10108,9 @@ void CBasePlayer::UpdateLocation(bool forceUpdate) break; } } + + if (!placeName[0]) + placeName = TheNavAreaGrid.IDToName(playerPlace); } if (!placeName[0] || (m_lastLocation[0] && !Q_strcmp(placeName, &m_lastLocation[1]))) diff --git a/regamedll/game_shared/bot/nav_area.cpp b/regamedll/game_shared/bot/nav_area.cpp index 1ff3ef8c5..1cac88ce9 100644 --- a/regamedll/game_shared/bot/nav_area.cpp +++ b/regamedll/game_shared/bot/nav_area.cpp @@ -3815,6 +3815,9 @@ void EditNavAreas(NavEditCmdType cmd) if (area->GetPlace()) { const char *name = TheBotPhrases->IDToName(area->GetPlace()); + if (!TheBotPhrases->IsValid() && !name) + name = TheNavAreaGrid.IDToName(area->GetPlace()); + if (name) Q_strcpy(locName, name); else @@ -4810,3 +4813,133 @@ Place CNavAreaGrid::GetPlace(const Vector *pos) const return UNDEFINED_PLACE; } + +static const char *g_pszDefaultPlaceNames[] = +{ + "BombsiteA", + "BombsiteB", + "BombsiteC", + "Hostages", + "HostageRescueZone", + "VipRescueZone", + "CTSpawn", + "TSpawn", + "Bridge", + "Middle", + "House", + "Apartment", + "Apartments", + "Market", + "Sewers", + "Tunnel", + "Ducts", + "Village", + "Roof", + "Upstairs", + "Downstairs", + "Basement", + "Crawlspace", + "Kitchen", + "Inside", + "Outside", + "Tower", + "WineCellar", + "Garage", + "Courtyard", + "Water", + "FrontDoor", + "BackDoor", + "SideDoor", + "BackWay", + "FrontYard", + "BackYard", + "SideYard", + "Lobby", + "Vault", + "Elevator", + "DoubleDoors", + "SecurityDoors", + "LongHall", + "SideHall", + "FrontHall", + "BackHall", + "MainHall", + "FarSide", + "Windows", + "Window", + "Attic", + "StorageRoom", + "ProjectorRoom", + "MeetingRoom", + "ConferenceRoom", + "ComputerRoom", + "BigOffice", + "LittleOffice", + "Dumpster", + "Airplane", + "Underground", + "Bunker", + "Mines", + "Front", + "Back", + "Rear", + "Side", + "Ramp", + "Underpass", + "Overpass", + "Stairs", + "Ladder", + "Gate", + "GateHouse", + "LoadingDock", + "GuardHouse", + "Entrance", + "VendingMachines", + "Loft", + "Balcony", + "Alley", + "BackAlley", + "SideAlley", + "FrontRoom", + "BackRoom", + "SideRoom", + "Crates", + "Truck", + "Bedroom", + "FamilyRoom", + "Bathroom", + "LivingRoom", + "Den", + "Office", + "Atrium", + "Entryway", + "Foyer", + "Stairwell", + "Fence", + "Deck", + "Porch", + "Patio", + "Wall" +}; + +// Return fallback place name for given place id +const char *CNavAreaGrid::IDToName(Place place) const +{ + if (place <= 0 || place > ARRAYSIZE(g_pszDefaultPlaceNames)) + return nullptr; + + return g_pszDefaultPlaceNames[place - 1]; +} + +// Return place id for given place name +Place CNavAreaGrid::NameToID(const char *name) const +{ + for (unsigned int place = 0; place < ARRAYSIZE(g_pszDefaultPlaceNames); place++) + { + const char *placeName = g_pszDefaultPlaceNames[place]; + if (!Q_stricmp(placeName, name)) + return place + 1; + } + + return UNDEFINED_PLACE; +} diff --git a/regamedll/game_shared/bot/nav_area.h b/regamedll/game_shared/bot/nav_area.h index d1ed024ec..c4cf8a1ff 100644 --- a/regamedll/game_shared/bot/nav_area.h +++ b/regamedll/game_shared/bot/nav_area.h @@ -505,6 +505,8 @@ class CNavAreaGrid bool IsValid() const; Place GetPlace(const Vector *pos) const; // return radio chatter place for given coordinate + Place NameToID(const char *name) const; + const char *IDToName(Place id) const; private: const float m_cellSize; diff --git a/regamedll/game_shared/bot/nav_file.cpp b/regamedll/game_shared/bot/nav_file.cpp index ad04f52c9..c9f206fd9 100644 --- a/regamedll/game_shared/bot/nav_file.cpp +++ b/regamedll/game_shared/bot/nav_file.cpp @@ -85,7 +85,10 @@ void PlaceDirectory::Save(int fd) // store entries for (auto &id : m_directory) { - auto placeName = TheBotPhrases->IDToName(id); + const char *placeName = TheBotPhrases->IDToName(id); + + if (!TheBotPhrases->IsValid() && !placeName) + placeName = TheNavAreaGrid.IDToName(id); // store string length followed by string itself unsigned short len = (unsigned short)Q_strlen(placeName) + 1; @@ -110,7 +113,11 @@ void PlaceDirectory::Load(SteamFile *file) file->Read(&len, sizeof(unsigned short)); file->Read(placeName, len); - AddPlace(TheBotPhrases->NameToID(placeName)); + Place place = TheBotPhrases->NameToID(placeName); + if (!TheBotPhrases->IsValid() && place == UNDEFINED_PLACE) + place = TheNavAreaGrid.NameToID(placeName); + + AddPlace(place); } } @@ -652,7 +659,12 @@ void LoadLocationFile(const char *filename) for (int i = 0; i < dirSize; i++) { locData = SharedParse(locData); - directory.push_back(TheBotPhrases->NameToID(SharedGetToken())); + + Place place = TheBotPhrases->NameToID(SharedGetToken()); + if (!TheBotPhrases->IsValid() && place == UNDEFINED_PLACE) + place = TheNavAreaGrid.NameToID(SharedGetToken()); + + directory.push_back(place); } // read places for each nav area From 279bd6421ba014d5499ac8e5111a552147b6c52a Mon Sep 17 00:00:00 2001 From: s1lentq Date: Mon, 8 Apr 2024 12:41:22 +0700 Subject: [PATCH 05/50] Fix crash --- regamedll/dlls/player.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/regamedll/dlls/player.cpp b/regamedll/dlls/player.cpp index b5e21eb03..d4da397d7 100644 --- a/regamedll/dlls/player.cpp +++ b/regamedll/dlls/player.cpp @@ -10113,7 +10113,7 @@ void CBasePlayer::UpdateLocation(bool forceUpdate) placeName = TheNavAreaGrid.IDToName(playerPlace); } - if (!placeName[0] || (m_lastLocation[0] && !Q_strcmp(placeName, &m_lastLocation[1]))) + if (!placeName || !placeName[0] || (m_lastLocation[0] && !Q_strcmp(placeName, &m_lastLocation[1]))) { return; } From 5117374a276016000b944b398e8f8ee5ccdf2d82 Mon Sep 17 00:00:00 2001 From: s1lentq Date: Mon, 8 Apr 2024 16:42:08 +0700 Subject: [PATCH 06/50] Separate mp_location_area_info from chat and radar Add fallback location message for chat --- README.md | 2 +- dist/game.cfg | 6 ++++-- regamedll/dlls/client.cpp | 19 ++++++++++++++++--- regamedll/dlls/game.cpp | 2 ++ regamedll/dlls/game.h | 1 + regamedll/dlls/player.cpp | 24 +++++++++++++++++------- 6 files changed, 41 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 2654bcf70..e5b55c0cf 100644 --- a/README.md +++ b/README.md @@ -116,7 +116,7 @@ This means that plugins that do binary code analysis (Orpheu for example) probab | mp_freezetime_duck | 1 | 0 | 1 | Allow players to duck during freezetime.
`0` disabled
`1` enabled | | mp_freezetime_jump | 1 | 0 | 1 | Allow players to jump during freezetime.
`0` disabled
`1` enabled | | mp_defuser_allocation | 0 | 0 | 2 | Give defuser on player spawn.
`0` disabled
`1` Random players.
`2` All players. | -| mp_location_area_info | 0 | 0 | 1 | Enable location area info.
`0` disabled
`1` enabled.
Usually displayed in HUDs below radar or in radio chat as a prefix.
`NOTE`: Navigation `maps/.nav` file required and should contain place names | +| mp_location_area_info | 0 | 0 | 3 | Enable location area info.
`0` disabled
`1` show location below HUD radar.
`2` show location in HUD chat. `NOT RECOMMENDED!` [:speech_balloon:](## "Not all client builds are compatible")
`3` both displayed. `NOT RECOMMENDED!` [:speech_balloon:](## "Not all client builds are compatible")

`NOTE`: Navigation `maps/.nav` file required and should contain place names
`NOTE`: If option `2` or `3` is enabled, be sure to enable `mp_chat_loc_fallback 1` | ## How to install zBot for CS 1.6? diff --git a/dist/game.cfg b/dist/game.cfg index f426c00b9..e6510f345 100644 --- a/dist/game.cfg +++ b/dist/game.cfg @@ -577,11 +577,13 @@ mp_freezetime_jump "1" mp_defuser_allocation "0" // Enable location area info -// Usually displayed in HUDs below radar or in radio chat as a prefix // 0 - disabled (default behavior) -// 1 - enabled +// 1 - show location below HUD radar +// 2 - show location in HUD chat (NOT RECOMMENDED! Not all client builds are compatible) +// 3 - both displayed (NOT RECOMMENDED! Not all client builds are compatible) // // NOTE: Navigation maps/.nav file required and should contain place names +// NOTE: If option 2 or 3 is enabled, be sure to enable mp_chat_loc_fallback 1 // // Default value: "0" mp_location_area_info "0" diff --git a/regamedll/dlls/client.cpp b/regamedll/dlls/client.cpp index 0564bedd6..6b8fdf02e 100644 --- a/regamedll/dlls/client.cpp +++ b/regamedll/dlls/client.cpp @@ -836,7 +836,11 @@ void Host_Say(edict_t *pEntity, BOOL teamonly) // team only if (teamonly) { - if (AreRunningCZero() && (pPlayer->m_iTeam == CT || pPlayer->m_iTeam == TERRORIST)) + if (( +#ifdef REGAMEDLL_ADD + location_area_info.value >= 2 || +#endif + AreRunningCZero()) && (pPlayer->m_iTeam == CT || pPlayer->m_iTeam == TERRORIST)) { // search the place name where is located the player Place playerPlace = TheNavAreaGrid.GetPlace(&pPlayer->pev->origin); @@ -850,8 +854,17 @@ void Host_Say(edict_t *pEntity, BOOL teamonly) break; } } + + if (!placeName) + placeName = TheNavAreaGrid.IDToName(playerPlace); } + bool bUseLocFallback = false; +#ifdef REGAMEDLL_ADD + if (chat_loc_fallback.value) + bUseLocFallback = true; +#endif + if (pPlayer->m_iTeam == CT) { if (bSenderDead) @@ -861,7 +874,7 @@ void Host_Say(edict_t *pEntity, BOOL teamonly) } else if (placeName) { - pszFormat = "#Cstrike_Chat_CT_Loc"; + pszFormat = bUseLocFallback ? "\x1(Counter-Terrorist) \x3%s1\x1 @ \x4%s3\x1 : %s2" : "#Cstrike_Chat_CT_Loc"; pszConsoleFormat = "*(Counter-Terrorist) %s @ %s : %s"; consoleUsesPlaceName = true; } @@ -880,7 +893,7 @@ void Host_Say(edict_t *pEntity, BOOL teamonly) } else if (placeName) { - pszFormat = "#Cstrike_Chat_T_Loc"; + pszFormat = bUseLocFallback ? "\x1(Terrorist) \x3%s1\x1 @ \x4%s3\x1 : %s2" : "#Cstrike_Chat_T_Loc"; pszConsoleFormat = "(Terrorist) %s @ %s : %s"; consoleUsesPlaceName = true; } diff --git a/regamedll/dlls/game.cpp b/regamedll/dlls/game.cpp index d9d756771..8d781724d 100644 --- a/regamedll/dlls/game.cpp +++ b/regamedll/dlls/game.cpp @@ -179,6 +179,7 @@ cvar_t legacy_vehicle_block = { "mp_legacy_vehicle_block", "1", 0, cvar_t dying_time = { "mp_dying_time", "3.0", 0, 3.0f, nullptr }; cvar_t defuser_allocation = { "mp_defuser_allocation", "0", 0, 0.0f, nullptr }; cvar_t location_area_info = { "mp_location_area_info", "0", 0, 0.0f, nullptr }; +cvar_t chat_loc_fallback = { "mp_chat_loc_fallback", "1", 1, 0.0f, nullptr }; void GameDLL_Version_f() { @@ -443,6 +444,7 @@ void EXT_FUNC GameDLLInit() CVAR_REGISTER(&freezetime_jump); CVAR_REGISTER(&defuser_allocation); CVAR_REGISTER(&location_area_info); + CVAR_REGISTER(&chat_loc_fallback); // print version CONSOLE_ECHO("ReGameDLL version: " APP_VERSION "\n"); diff --git a/regamedll/dlls/game.h b/regamedll/dlls/game.h index 73cb77dd2..dcd712f5e 100644 --- a/regamedll/dlls/game.h +++ b/regamedll/dlls/game.h @@ -202,6 +202,7 @@ extern cvar_t freezetime_duck; extern cvar_t freezetime_jump; extern cvar_t defuser_allocation; extern cvar_t location_area_info; +extern cvar_t chat_loc_fallback; #endif diff --git a/regamedll/dlls/player.cpp b/regamedll/dlls/player.cpp index d4da397d7..63ea1d060 100644 --- a/regamedll/dlls/player.cpp +++ b/regamedll/dlls/player.cpp @@ -417,7 +417,7 @@ void EXT_FUNC CBasePlayer::__API_HOOK(Radio)(const char *msg_id, const char *msg const char *placeName = nullptr; if (( #ifdef REGAMEDLL_ADD - location_area_info.value || + location_area_info.value >= 2 || #endif AreRunningCZero()) && TheBotPhrases) { @@ -432,14 +432,24 @@ void EXT_FUNC CBasePlayer::__API_HOOK(Radio)(const char *msg_id, const char *msg } } - if (!placeName[0]) + if (!placeName) placeName = TheNavAreaGrid.IDToName(playerPlace); } - if (placeName) - ClientPrint(pEntity->pev, HUD_PRINTRADIO, NumAsString(entindex()), "#Game_radio_location", STRING(pev->netname), placeName, msg_verbose); + if (placeName && placeName[0]) + { + bool bUseLocFallback = false; +#ifdef REGAMEDLL_ADD + if (chat_loc_fallback.value) + bUseLocFallback = true; +#endif + + ClientPrint(pEntity->pev, HUD_PRINTRADIO, NumAsString(entindex()), bUseLocFallback ? "\x3%s1\x1 @ \x4%s2\x1 (RADIO): %s3" : "#Game_radio_location", STRING(pev->netname), placeName, msg_verbose); + } else + { ClientPrint(pEntity->pev, HUD_PRINTRADIO, NumAsString(entindex()), "#Game_radio", STRING(pev->netname), msg_verbose); + } } // icon over the head for teammates @@ -10089,11 +10099,11 @@ void CBasePlayer::UpdateLocation(bool forceUpdate) if (!forceUpdate && m_flLastUpdateTime >= gpGlobals->time + 2.0f) return; - const char *placeName = ""; + const char *placeName = nullptr; if (pev->deadflag == DEAD_NO && ( #ifdef REGAMEDLL_ADD - location_area_info.value || + (location_area_info.value == 1 || location_area_info.value == 3) || #endif AreBotsAllowed())) { @@ -10109,7 +10119,7 @@ void CBasePlayer::UpdateLocation(bool forceUpdate) } } - if (!placeName[0]) + if (!placeName) placeName = TheNavAreaGrid.IDToName(playerPlace); } From f24cccfe73e0e13bdffd6377fadb98cf2944852a Mon Sep 17 00:00:00 2001 From: s1lentq Date: Mon, 8 Apr 2024 16:47:02 +0700 Subject: [PATCH 07/50] Fix linux build --- regamedll/dlls/client.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/regamedll/dlls/client.cpp b/regamedll/dlls/client.cpp index 6b8fdf02e..1ac3dd562 100644 --- a/regamedll/dlls/client.cpp +++ b/regamedll/dlls/client.cpp @@ -829,7 +829,7 @@ void Host_Say(edict_t *pEntity, BOOL teamonly) return; const char *placeName = nullptr; - char *pszFormat = nullptr; + const char *pszFormat = nullptr; char *pszConsoleFormat = nullptr; bool consoleUsesPlaceName = false; From d5eb9c34d2acc2f771110044721b76538b05db3c Mon Sep 17 00:00:00 2001 From: Vaqtincha <51029683+Vaqtincha@users.noreply.github.com> Date: Mon, 15 Apr 2024 13:49:44 +0500 Subject: [PATCH 08/50] don't send radio message to teammate (if freeforall 1) (#958) * don't send radio message to teammate (if freeforall 1) * don't send message zero length --- regamedll/dlls/player.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/regamedll/dlls/player.cpp b/regamedll/dlls/player.cpp index 63ea1d060..0e400bc51 100644 --- a/regamedll/dlls/player.cpp +++ b/regamedll/dlls/player.cpp @@ -382,7 +382,7 @@ void EXT_FUNC CBasePlayer::__API_HOOK(Radio)(const char *msg_id, const char *msg continue; // is this player on our team? (even dead players hear our radio calls) - if (pPlayer->m_iTeam == m_iTeam) + if (g_pGameRules->PlayerRelationship(this, pPlayer) == GR_TEAMMATE) bSend = true; } // this means we're a spectator @@ -396,7 +396,7 @@ void EXT_FUNC CBasePlayer::__API_HOOK(Radio)(const char *msg_id, const char *msg if (FNullEnt(pPlayer->m_hObserverTarget)) continue; - if (pPlayer->m_hObserverTarget && pPlayer->m_hObserverTarget->m_iTeam == m_iTeam) + if (pPlayer->m_hObserverTarget && g_pGameRules->PlayerRelationship(this, pPlayer->m_hObserverTarget) == GR_TEAMMATE) { bSend = true; } @@ -411,7 +411,7 @@ void EXT_FUNC CBasePlayer::__API_HOOK(Radio)(const char *msg_id, const char *msg MESSAGE_END(); // radio message icon - if (msg_verbose) + if (msg_verbose && msg_verbose[0] != 0) { // search the place name where is located the player const char *placeName = nullptr; From 0626800fbb82f602f45e80658e67a6de06623be8 Mon Sep 17 00:00:00 2001 From: Javekson <132286351+Javekson@users.noreply.github.com> Date: Wed, 8 May 2024 11:50:53 +0400 Subject: [PATCH 09/50] Implement RemoveAllItems hook (#960) --- regamedll/dlls/API/CAPI_Impl.cpp | 1 + regamedll/dlls/API/CAPI_Impl.h | 6 ++++++ regamedll/dlls/player.cpp | 4 +++- regamedll/dlls/player.h | 1 + regamedll/public/regamedll/regamedll_api.h | 5 +++++ 5 files changed, 16 insertions(+), 1 deletion(-) diff --git a/regamedll/dlls/API/CAPI_Impl.cpp b/regamedll/dlls/API/CAPI_Impl.cpp index 5bb6b3940..41175c838 100644 --- a/regamedll/dlls/API/CAPI_Impl.cpp +++ b/regamedll/dlls/API/CAPI_Impl.cpp @@ -335,6 +335,7 @@ GAMEHOOK_REGISTRY(CSGameRules_SendDeathMessage); GAMEHOOK_REGISTRY(CBasePlayer_PlayerDeathThink); GAMEHOOK_REGISTRY(CBasePlayer_Observer_Think); +GAMEHOOK_REGISTRY(CBasePlayer_RemoveAllItems); int CReGameApi::GetMajorVersion() { return REGAMEDLL_API_VERSION_MAJOR; diff --git a/regamedll/dlls/API/CAPI_Impl.h b/regamedll/dlls/API/CAPI_Impl.h index 0548a841f..2635be5b9 100644 --- a/regamedll/dlls/API/CAPI_Impl.h +++ b/regamedll/dlls/API/CAPI_Impl.h @@ -745,6 +745,10 @@ typedef IHookChainRegistryClassImpl CReGameHookRegistry_CBase typedef IHookChainClassImpl CReGameHook_CBasePlayer_Observer_Think; typedef IHookChainRegistryClassImpl CReGameHookRegistry_CBasePlayer_Observer_Think; +// CBasePlayer::RemoveAllItems hook +typedef IHookChainClassImpl CReGameHook_CBasePlayer_RemoveAllItems; +typedef IHookChainRegistryClassImpl CReGameHookRegistry_CBasePlayer_RemoveAllItems; + class CReGameHookchains: public IReGameHookchains { public: // CBasePlayer virtual @@ -905,6 +909,7 @@ class CReGameHookchains: public IReGameHookchains { CReGameHookRegistry_CBasePlayer_PlayerDeathThink m_CBasePlayer_PlayerDeathThink; CReGameHookRegistry_CBasePlayer_Observer_Think m_CBasePlayer_Observer_Think; + CReGameHookRegistry_CBasePlayer_RemoveAllItems m_CBasePlayer_RemoveAllItems; public: virtual IReGameHookRegistry_CBasePlayer_Spawn *CBasePlayer_Spawn(); @@ -1064,6 +1069,7 @@ class CReGameHookchains: public IReGameHookchains { virtual IReGameHookRegistry_CBasePlayer_PlayerDeathThink *CBasePlayer_PlayerDeathThink(); virtual IReGameHookRegistry_CBasePlayer_Observer_Think *CBasePlayer_Observer_Think(); + virtual IReGameHookRegistry_CBasePlayer_RemoveAllItems *CBasePlayer_RemoveAllItems(); }; extern CReGameHookchains g_ReGameHookchains; diff --git a/regamedll/dlls/player.cpp b/regamedll/dlls/player.cpp index 0e400bc51..875f085bb 100644 --- a/regamedll/dlls/player.cpp +++ b/regamedll/dlls/player.cpp @@ -1733,7 +1733,9 @@ void EXT_FUNC CBasePlayer::__API_HOOK(GiveDefaultItems)() #endif } -void CBasePlayer::RemoveAllItems(BOOL removeSuit) +LINK_HOOK_CLASS_VOID_CHAIN(CBasePlayer, RemoveAllItems, (BOOL removeSuit), removeSuit) + +void EXT_FUNC CBasePlayer::__API_HOOK(RemoveAllItems)(BOOL removeSuit) { int i; diff --git a/regamedll/dlls/player.h b/regamedll/dlls/player.h index 8908e44c9..ced1fe973 100644 --- a/regamedll/dlls/player.h +++ b/regamedll/dlls/player.h @@ -448,6 +448,7 @@ class CBasePlayer: public CBaseMonster { edict_t *EntSelectSpawnPoint_OrigFunc(); void PlayerDeathThink_OrigFunc(); void Observer_Think_OrigFunc(); + void RemoveAllItems_OrigFunc(BOOL removeSuit); CCSPlayer *CSPlayer() const; #endif // REGAMEDLL_API diff --git a/regamedll/public/regamedll/regamedll_api.h b/regamedll/public/regamedll/regamedll_api.h index 4aeb12545..8009c14ed 100644 --- a/regamedll/public/regamedll/regamedll_api.h +++ b/regamedll/public/regamedll/regamedll_api.h @@ -624,6 +624,10 @@ typedef IHookChainRegistryClass IReGameHookRegistry_CBa typedef IHookChainClass IReGameHook_CBasePlayer_Observer_Think; typedef IHookChainRegistryClass IReGameHookRegistry_CBasePlayer_Observer_Think; +// CBasePlayer::RemoveAllItems hook +typedef IHookChainClass IReGameHook_CBasePlayer_RemoveAllItems; +typedef IHookChainRegistryClass IReGameHookRegistry_CBasePlayer_RemoveAllItems; + class IReGameHookchains { public: virtual ~IReGameHookchains() {} @@ -785,6 +789,7 @@ class IReGameHookchains { virtual IReGameHookRegistry_CBasePlayer_PlayerDeathThink *CBasePlayer_PlayerDeathThink() = 0; virtual IReGameHookRegistry_CBasePlayer_Observer_Think *CBasePlayer_Observer_Think() = 0; + virtual IReGameHookRegistry_CBasePlayer_RemoveAllItems *CBasePlayer_RemoveAllItems() = 0; }; struct ReGameFuncs_t { From 2a0e54bbf73b43d558b05c9770e1ebb670b5f7fa Mon Sep 17 00:00:00 2001 From: s1lentq Date: Wed, 8 May 2024 14:55:21 +0700 Subject: [PATCH 10/50] Bump minor version up to 27 --- regamedll/public/regamedll/regamedll_api.h | 2 +- regamedll/version/version.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/regamedll/public/regamedll/regamedll_api.h b/regamedll/public/regamedll/regamedll_api.h index 8009c14ed..d655ee656 100644 --- a/regamedll/public/regamedll/regamedll_api.h +++ b/regamedll/public/regamedll/regamedll_api.h @@ -38,7 +38,7 @@ #include #define REGAMEDLL_API_VERSION_MAJOR 5 -#define REGAMEDLL_API_VERSION_MINOR 26 +#define REGAMEDLL_API_VERSION_MINOR 27 // CBasePlayer::Spawn hook typedef IHookChainClass IReGameHook_CBasePlayer_Spawn; diff --git a/regamedll/version/version.h b/regamedll/version/version.h index 0f32ed96e..fc64686b4 100644 --- a/regamedll/version/version.h +++ b/regamedll/version/version.h @@ -6,5 +6,5 @@ #pragma once #define VERSION_MAJOR 5 -#define VERSION_MINOR 26 +#define VERSION_MINOR 27 #define VERSION_MAINTENANCE 0 From 8cd9086937a2ea8e530cba11f08d86970036765d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=82=AF=E5=AE=9A=E9=BE=99?= <52111952+overl4y@users.noreply.github.com> Date: Wed, 8 May 2024 13:59:27 +0000 Subject: [PATCH 11/50] Implement `game_round_end` and `game_round_freeze_end` triggers (#949) * Implement `game_round_end` and `game_round_freeze_end` triggers --- regamedll/dlls/multiplay_gamerules.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/regamedll/dlls/multiplay_gamerules.cpp b/regamedll/dlls/multiplay_gamerules.cpp index e0d27dfb2..dc7bde074 100644 --- a/regamedll/dlls/multiplay_gamerules.cpp +++ b/regamedll/dlls/multiplay_gamerules.cpp @@ -267,6 +267,10 @@ void CHalfLifeMultiplay::EndRoundMessage(const char *sentence, ScenarioEventEndR } UTIL_LogPrintf("World triggered \"Round_End\"\n"); + +#ifdef REGAMEDLL_ADD + FireTargets("game_round_end", nullptr, nullptr, USE_TOGGLE, 0.0); +#endif } void CHalfLifeMultiplay::ReadMultiplayCvars() @@ -2852,6 +2856,10 @@ void EXT_FUNC CHalfLifeMultiplay::OnRoundFreezeEnd() { TheCareerTasks->HandleEvent(EVENT_ROUND_START); } + +#ifdef REGAMEDLL_ADD + FireTargets("game_round_freeze_end", nullptr, nullptr, USE_TOGGLE, 0.0); +#endif } void CHalfLifeMultiplay::CheckFreezePeriodExpired() From 75f142e18a7eeee6139908b76b33fc800eece44c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=AA=20H=C3=A0n=20Minh=20Khang?= Date: Wed, 8 May 2024 10:52:41 -0400 Subject: [PATCH 12/50] Add trigger_teleport landmark (#952) * Add trigger_teleport landmark --- regamedll/dlls/triggers.cpp | 44 ++++++++++++++++++- regamedll/dlls/triggers.h | 6 +++ .../GameDefinitionFile/regamedll-cs.fgd | 2 + 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/regamedll/dlls/triggers.cpp b/regamedll/dlls/triggers.cpp index c4af258c4..49e4d14c4 100644 --- a/regamedll/dlls/triggers.cpp +++ b/regamedll/dlls/triggers.cpp @@ -1763,8 +1763,30 @@ void CBaseTrigger::TeleportTouch(CBaseEntity *pOther) if (pOther->IsPlayer()) { +#ifdef REGAMEDLL_ADD + // If a landmark was specified, offset the player relative to the landmark + if (m_iszLandmarkName) + { + edict_t *pentLandmark = FIND_ENTITY_BY_TARGETNAME(nullptr, STRING(m_iszLandmarkName)); + + if (!FNullEnt(pentLandmark)) + { + Vector diff = pevToucher->origin - VARS(pentLandmark)->origin; + tmp += diff; + tmp.z--; // offset by +1 because -1 will run out of this scope. + } + else + { + // fallback, shouldn't happen but anyway. + tmp.z -= pOther->pev->mins.z; + } + } + else +#endif // make origin adjustments in case the teleportee is a player. (origin in center, not at feet) - tmp.z -= pOther->pev->mins.z; + { + tmp.z -= pOther->pev->mins.z; + } } tmp.z++; @@ -1817,6 +1839,26 @@ void CTriggerTeleport::Spawn() SetTouch(&CTriggerTeleport::TeleportTouch); } +void CTriggerTeleport::KeyValue(KeyValueData *pkvd) +{ +#ifdef REGAMEDLL_ADD + if (FStrEq(pkvd->szKeyName, "landmark")) + { + if (Q_strlen(pkvd->szValue) > 0) + { + m_iszLandmarkName = ALLOC_STRING(pkvd->szValue); + } + + // If empty, handle it in the teleport touch instead + pkvd->fHandled = TRUE; + } + else +#endif + { + CBaseTrigger::KeyValue(pkvd); + } +} + LINK_ENTITY_TO_CLASS(info_teleport_destination, CPointEntity, CCSPointEntity) LINK_ENTITY_TO_CLASS(func_buyzone, CBuyZone, CCSBuyZone) diff --git a/regamedll/dlls/triggers.h b/regamedll/dlls/triggers.h index c16f969c8..b21a6bde5 100644 --- a/regamedll/dlls/triggers.h +++ b/regamedll/dlls/triggers.h @@ -204,6 +204,11 @@ class CBaseTrigger: public CBaseToggle void EXPORT CounterUse(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value); void EXPORT ToggleUse(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value); void InitTrigger(); + +#ifdef REGAMEDLL_ADD + // For trigger_teleport TriggerTouch + int m_iszLandmarkName = 0; +#endif }; #define SF_TRIGGER_HURT_TARGETONCE BIT(0) // Only fire hurt target once @@ -393,6 +398,7 @@ class CTriggerTeleport: public CBaseTrigger { public: virtual void Spawn(); + virtual void KeyValue(KeyValueData *pkvd); }; class CBuyZone: public CBaseTrigger diff --git a/regamedll/extra/Toolkit/GameDefinitionFile/regamedll-cs.fgd b/regamedll/extra/Toolkit/GameDefinitionFile/regamedll-cs.fgd index 0e6838732..79fe8333a 100644 --- a/regamedll/extra/Toolkit/GameDefinitionFile/regamedll-cs.fgd +++ b/regamedll/extra/Toolkit/GameDefinitionFile/regamedll-cs.fgd @@ -1934,6 +1934,7 @@ bombradius(integer) : "Bomb Radius" : 500 ] +@PointClass base(Targetname) iconsprite("sprites/CS/info_target.spr") = info_landmark : "Transition/Relative teleport Landmark" [] @PointClass base(Targetname) iconsprite("sprites/CS/info_target.spr") = info_null : "info_null (spotlight target)" [] @PointClass iconsprite("sprites/CS/info_player_deathmatch.spr") base(PlayerClass) = info_player_deathmatch : "Terrorist start" [] @PointClass iconsprite("sprites/CS/info_player_start.spr") base(PlayerClass) = info_player_start : "Counter-terrorist start" [] @@ -2264,6 +2265,7 @@ @SolidClass base(Trigger) = trigger_teleport : "Trigger teleport" [ + landmark(string) : "Landmark name" spawnflags(flags) = [ 256: "Keep angles" : 0 From d7f22ae3ecf9b895f1f9addf9dc4b7d1fa3cdd08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=82=AF=E5=AE=9A=E9=BE=99?= <52111952+overl4y@users.noreply.github.com> Date: Wed, 8 May 2024 15:18:24 +0000 Subject: [PATCH 13/50] Restart trigger_multiple (#935) --- regamedll/dlls/multiplay_gamerules.cpp | 1 + regamedll/dlls/triggers.cpp | 8 ++++++++ regamedll/dlls/triggers.h | 4 ++++ 3 files changed, 13 insertions(+) diff --git a/regamedll/dlls/multiplay_gamerules.cpp b/regamedll/dlls/multiplay_gamerules.cpp index dc7bde074..c70a86526 100644 --- a/regamedll/dlls/multiplay_gamerules.cpp +++ b/regamedll/dlls/multiplay_gamerules.cpp @@ -652,6 +652,7 @@ void EXT_FUNC CHalfLifeMultiplay::__API_HOOK(CleanUpMap)() UTIL_RestartOther("env_beam"); UTIL_RestartOther("env_laser"); UTIL_RestartOther("trigger_auto"); + UTIL_RestartOther("trigger_multiple"); #endif // Remove grenades and C4 diff --git a/regamedll/dlls/triggers.cpp b/regamedll/dlls/triggers.cpp index 49e4d14c4..01e29bad8 100644 --- a/regamedll/dlls/triggers.cpp +++ b/regamedll/dlls/triggers.cpp @@ -996,6 +996,14 @@ void CTriggerMultiple::Spawn() } } +#ifdef REGAMEDLL_FIXES +void CTriggerMultiple::Restart() +{ + pev->nextthink = -1; + Spawn(); +} +#endif + LINK_ENTITY_TO_CLASS(trigger_once, CTriggerOnce, CCSTriggerOnce) void CTriggerOnce::Spawn() diff --git a/regamedll/dlls/triggers.h b/regamedll/dlls/triggers.h index b21a6bde5..90a7daef3 100644 --- a/regamedll/dlls/triggers.h +++ b/regamedll/dlls/triggers.h @@ -283,6 +283,10 @@ class CTriggerMultiple: public CBaseTrigger { public: virtual void Spawn(); + +#ifdef REGAMEDLL_FIXES + virtual void Restart(); +#endif }; // Variable sized trigger. Triggers once, then removes itself. You must set the key "target" to the name of another object in the level that has a matching From 4ecf42799dba741a8cf0af57ee134522aa640753 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20Mu=C3=B1oz?= Date: Wed, 8 May 2024 11:18:36 -0400 Subject: [PATCH 14/50] Fix ApplyMultiDamage duplicated call on MultiDamage routine (#946) --- regamedll/dlls/weapons.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/regamedll/dlls/weapons.cpp b/regamedll/dlls/weapons.cpp index b44b288dd..4ea949128 100644 --- a/regamedll/dlls/weapons.cpp +++ b/regamedll/dlls/weapons.cpp @@ -107,8 +107,14 @@ void EXT_FUNC __API_HOOK(AddMultiDamage)(entvars_t *pevInflictor, CBaseEntity *p if (pEntity != gMultiDamage.pEntity) { - // UNDONE: wrong attacker! - ApplyMultiDamage(pevInflictor, pevInflictor); +#ifdef REGAMEDLL_FIXES + if (gMultiDamage.pEntity) // avoid api calls with null default pEntity +#endif + { + // UNDONE: wrong attacker! + ApplyMultiDamage(pevInflictor, pevInflictor); + } + gMultiDamage.pEntity = pEntity; gMultiDamage.amount = 0; } From 8ec5b6cd0f1a02c3f6a2fdf4c4458e3220604906 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Federico=20Mat=C3=ADas?= <41979395+FEDERICOMB96@users.noreply.github.com> Date: Wed, 8 May 2024 12:59:40 -0300 Subject: [PATCH 15/50] Fix `SendDeathMessage` kill rarity flags and transform to VFUNC (#943) * implement VFUNC to `SendDeathMessage` * fix use KILLRARITY_HEADSHOT flag instead of member * fix rare crash when pAssister is nullptr * fix KILLRARITY_DOMINATION_BEGAN flag This flag was never valid in "SendDeathMessage" hook * set iDeathMessageFlags before call function --- regamedll/dlls/gamerules.h | 2 +- regamedll/dlls/multiplay_gamerules.cpp | 37 +++++++++++--------------- 2 files changed, 17 insertions(+), 22 deletions(-) diff --git a/regamedll/dlls/gamerules.h b/regamedll/dlls/gamerules.h index e7dc35085..8d15e9ee4 100644 --- a/regamedll/dlls/gamerules.h +++ b/regamedll/dlls/gamerules.h @@ -741,7 +741,7 @@ class CHalfLifeMultiplay: public CGameRules VFUNC bool HasRoundTimeExpired(); VFUNC bool IsBombPlanted(); - void SendDeathMessage(CBaseEntity *pKiller, CBasePlayer *pVictim, CBasePlayer *pAssister, entvars_t *pevInflictor, const char *killerWeaponName, int iDeathMessageFlags, int iRarityOfKill); + VFUNC void SendDeathMessage(CBaseEntity *pKiller, CBasePlayer *pVictim, CBasePlayer *pAssister, entvars_t *pevInflictor, const char *killerWeaponName, int iDeathMessageFlags, int iRarityOfKill); int GetRarityOfKill(CBaseEntity *pKiller, CBasePlayer *pVictim, CBasePlayer *pAssister, const char *killerWeaponName, bool bFlashAssist); CBasePlayer *CheckAssistsToKill(CBaseEntity *pKiller, CBasePlayer *pVictim, bool &bFlashAssist); diff --git a/regamedll/dlls/multiplay_gamerules.cpp b/regamedll/dlls/multiplay_gamerules.cpp index c70a86526..724dab34f 100644 --- a/regamedll/dlls/multiplay_gamerules.cpp +++ b/regamedll/dlls/multiplay_gamerules.cpp @@ -4130,6 +4130,16 @@ void EXT_FUNC CHalfLifeMultiplay::__API_HOOK(DeathNotice)(CBasePlayer *pVictim, iDeathMessageFlags |= PLAYERDEATH_KILLRARITY; } +#ifdef REGAMEDLL_ADD + iDeathMessageFlags &= UTIL_ReadFlags(deathmsg_flags.string); // leave only allowed bitsums for extra info + + // Send the victim's death position only + // 1. if it is not a free for all mode + // 2. if the attacker is a player and they are not teammates + if (IsFreeForAll() || !pKiller || PlayerRelationship(pKiller, pVictim) == GR_TEAMMATE) + iDeathMessageFlags &= ~PLAYERDEATH_POSITION; // do not send a position +#endif + SendDeathMessage(pKiller, pVictim, pAssister, pevInflictor, killer_weapon_name, iDeathMessageFlags, iRarityOfKill); // Updates the stats of who has killed whom @@ -5317,6 +5327,10 @@ int CHalfLifeMultiplay::GetRarityOfKill(CBaseEntity *pKiller, CBasePlayer *pVict int iKillsUnanswered = pVictim->CSPlayer()->m_iNumKilledByUnanswered[iAttackerEntityIndex - 1] + 1; if (iKillsUnanswered == CS_KILLS_FOR_DOMINATION || pKillerPlayer->CSPlayer()->IsPlayerDominated(pVictim->entindex() - 1)) { + // Sets the beginning of domination over the victim until he takes revenge + if (iKillsUnanswered == CS_KILLS_FOR_DOMINATION) + iRarity |= KILLRARITY_DOMINATION_BEGAN; + // this is the Nth unanswered kill between killer and victim, killer is now dominating victim iRarity |= KILLRARITY_DOMINATION; @@ -5352,32 +5366,13 @@ LINK_HOOK_CLASS_VOID_CUSTOM_CHAIN(CHalfLifeMultiplay, CSGameRules, SendDeathMess // void EXT_FUNC CHalfLifeMultiplay::__API_HOOK(SendDeathMessage)(CBaseEntity *pKiller, CBasePlayer *pVictim, CBasePlayer *pAssister, entvars_t *pevInflictor, const char *killerWeaponName, int iDeathMessageFlags, int iRarityOfKill) { - CBasePlayer *pKillerPlayer = (pKiller && pKiller->IsPlayer()) ? static_cast(pKiller) : nullptr; - - // Only the player can dominate the victim - if ((iRarityOfKill & KILLRARITY_DOMINATION) && pKillerPlayer && pVictim != pKillerPlayer) - { - // Sets the beginning of domination over the victim until he takes revenge - int iKillsUnanswered = pVictim->CSPlayer()->m_iNumKilledByUnanswered[pKillerPlayer->entindex() - 1] + 1; - if (iKillsUnanswered == CS_KILLS_FOR_DOMINATION) - iRarityOfKill |= KILLRARITY_DOMINATION_BEGAN; - } - MESSAGE_BEGIN(MSG_ALL, gmsgDeathMsg); WRITE_BYTE((pKiller && pKiller->IsPlayer()) ? pKiller->entindex() : 0); // the killer WRITE_BYTE(pVictim->entindex()); // the victim - WRITE_BYTE(pVictim->m_bHeadshotKilled); // is killed headshot + WRITE_BYTE((iRarityOfKill & KILLRARITY_HEADSHOT)); // is killed headshot WRITE_STRING(killerWeaponName); // what they were killed by (should this be a string?) #ifdef REGAMEDLL_ADD - iDeathMessageFlags &= UTIL_ReadFlags(deathmsg_flags.string); // leave only allowed bitsums for extra info - - // Send the victim's death position only - // 1. if it is not a free for all mode - // 2. if the attacker is a player and they are not teammates - if (IsFreeForAll() || !pKillerPlayer || PlayerRelationship(pKillerPlayer, pVictim) == GR_TEAMMATE) - iDeathMessageFlags &= ~PLAYERDEATH_POSITION; // do not send a position - if (iDeathMessageFlags > 0) { WRITE_LONG(iDeathMessageFlags); @@ -5393,7 +5388,7 @@ void EXT_FUNC CHalfLifeMultiplay::__API_HOOK(SendDeathMessage)(CBaseEntity *pKil // Writes the index of the teammate who assisted in the kill if (iDeathMessageFlags & PLAYERDEATH_ASSISTANT) - WRITE_BYTE(pAssister->entindex()); + WRITE_BYTE((pAssister && pAssister->IsPlayer()) ? pAssister->entindex() : 0); // Writes the rarity classification of the kill if (iDeathMessageFlags & PLAYERDEATH_KILLRARITY) From 8ff1bf1232b39fea48f6aee279bfc482aeb0705b Mon Sep 17 00:00:00 2001 From: s1lentq Date: Wed, 8 May 2024 23:01:02 +0700 Subject: [PATCH 16/50] Fix typo --- regamedll/dlls/multiplay_gamerules.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/regamedll/dlls/multiplay_gamerules.cpp b/regamedll/dlls/multiplay_gamerules.cpp index 724dab34f..53501ac0f 100644 --- a/regamedll/dlls/multiplay_gamerules.cpp +++ b/regamedll/dlls/multiplay_gamerules.cpp @@ -5290,7 +5290,6 @@ int CHalfLifeMultiplay::GetRarityOfKill(CBaseEntity *pKiller, CBasePlayer *pVict if (pVictim->m_bHeadshotKilled) iRarity |= KILLRARITY_HEADSHOT; - // The killer player was blind CBasePlayer *pKillerPlayer = static_cast(pKiller); if (pKillerPlayer && pKillerPlayer->IsPlayer()) { @@ -5304,6 +5303,7 @@ int CHalfLifeMultiplay::GetRarityOfKill(CBaseEntity *pKiller, CBasePlayer *pVict if (pVictim->GetDmgPenetrationLevel() > 0) iRarity |= KILLRARITY_PENETRATED; + // The killer player was blind if (pKillerPlayer->IsFullyBlind()) iRarity |= KILLRARITY_KILLER_BLIND; From cabdc254c7fe945e10e58511cba75c0f0baf5692 Mon Sep 17 00:00:00 2001 From: s1lentq Date: Thu, 9 May 2024 00:20:22 +0700 Subject: [PATCH 17/50] ActiveGrenade: Fixed a potential leak by checking entities smokegrens life if RemoveGrenade call is somehow missing or blocked --- regamedll/game_shared/bot/bot.cpp | 10 ++++++++-- regamedll/game_shared/bot/bot_manager.cpp | 13 ++++++++++++- regamedll/game_shared/bot/bot_manager.h | 7 ++++--- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/regamedll/game_shared/bot/bot.cpp b/regamedll/game_shared/bot/bot.cpp index e5849b9b4..33f0d8083 100644 --- a/regamedll/game_shared/bot/bot.cpp +++ b/regamedll/game_shared/bot/bot.cpp @@ -491,9 +491,15 @@ void ActiveGrenade::OnEntityGone() m_entity = nullptr; } +void ActiveGrenade::CheckOnEntityGone() +{ + if (m_dieTimestamp == 0 && !m_entity.IsValid()) + OnEntityGone(); +} + bool ActiveGrenade::IsValid() const { - if (!m_entity) + if (!m_entity.IsValid()) { if (gpGlobals->time > m_dieTimestamp) return false; @@ -502,7 +508,7 @@ bool ActiveGrenade::IsValid() const return true; } -const Vector *ActiveGrenade::GetPosition() const +const Vector *ActiveGrenade::GetPosition() { return &m_entity->pev->origin; } diff --git a/regamedll/game_shared/bot/bot_manager.cpp b/regamedll/game_shared/bot/bot_manager.cpp index 285a26a36..7e10f1bcc 100644 --- a/regamedll/game_shared/bot/bot_manager.cpp +++ b/regamedll/game_shared/bot/bot_manager.cpp @@ -147,6 +147,8 @@ void CBotManager::StartFrame() ActiveGrenade *ag = (*iter); // lazy validation + ag->CheckOnEntityGone(); + if (!ag->IsValid()) { delete ag; @@ -203,6 +205,8 @@ void CBotManager::StartFrame() pBot->BotThink(); } } + + ValidateActiveGrenades(); } // Return the filename for this map's "nav map" file @@ -278,13 +282,16 @@ void CBotManager::RemoveGrenade(CGrenade *grenade) } // Destroy any invalid active grenades -NOXREF void CBotManager::ValidateActiveGrenades() +void CBotManager::ValidateActiveGrenades() { auto iter = m_activeGrenadeList.begin(); while (iter != m_activeGrenadeList.end()) { ActiveGrenade *ag = (*iter); + // lazy validation + ag->CheckOnEntityGone(); + if (!ag->IsValid()) { delete ag; @@ -314,6 +321,8 @@ bool CBotManager::IsInsideSmokeCloud(const Vector *pos) ActiveGrenade *ag = (*iter); // lazy validation + ag->CheckOnEntityGone(); + if (!ag->IsValid()) { delete ag; @@ -358,6 +367,8 @@ bool CBotManager::IsLineBlockedBySmoke(const Vector *from, const Vector *to) ActiveGrenade *ag = (*iter); // lazy validation + ag->CheckOnEntityGone(); + if (!ag->IsValid()) { delete ag; diff --git a/regamedll/game_shared/bot/bot_manager.h b/regamedll/game_shared/bot/bot_manager.h index dfcfd8edc..a985fc1d7 100644 --- a/regamedll/game_shared/bot/bot_manager.h +++ b/regamedll/game_shared/bot/bot_manager.h @@ -42,16 +42,17 @@ class ActiveGrenade ActiveGrenade(int weaponID, CGrenade *grenadeEntity); void OnEntityGone(); + void CheckOnEntityGone(); bool IsValid() const; - bool IsEntity(CGrenade *grenade) const { return (grenade == m_entity) ? true : false; } + bool IsEntity(CGrenade *grenade) const { return grenade == m_entity; } int GetID() const { return m_id; } const Vector *GetDetonationPosition() const { return &m_detonationPosition; } - const Vector *GetPosition() const; + const Vector *GetPosition(); private: int m_id; - CGrenade *m_entity; + EntityHandle m_entity; Vector m_detonationPosition; float m_dieTimestamp; }; From 8706cc5a10c7b06f9da96a24609e11b406a3c15d Mon Sep 17 00:00:00 2001 From: s1lentq Date: Thu, 9 May 2024 00:30:57 +0700 Subject: [PATCH 18/50] KillRarity Domination: Don't consider suicides to be unanswered killing --- regamedll/dlls/multiplay_gamerules.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/regamedll/dlls/multiplay_gamerules.cpp b/regamedll/dlls/multiplay_gamerules.cpp index 53501ac0f..ba71bcebb 100644 --- a/regamedll/dlls/multiplay_gamerules.cpp +++ b/regamedll/dlls/multiplay_gamerules.cpp @@ -5291,13 +5291,12 @@ int CHalfLifeMultiplay::GetRarityOfKill(CBaseEntity *pKiller, CBasePlayer *pVict iRarity |= KILLRARITY_HEADSHOT; CBasePlayer *pKillerPlayer = static_cast(pKiller); - if (pKillerPlayer && pKillerPlayer->IsPlayer()) + if (pKillerPlayer && pKillerPlayer->IsPlayer() && pKillerPlayer != pVictim) { WeaponClassType weaponClass = AliasToWeaponClass(killerWeaponName); - if (pKillerPlayer != pVictim - && weaponClass != WEAPONCLASS_NONE - && weaponClass != WEAPONCLASS_KNIFE - && weaponClass != WEAPONCLASS_GRENADE) + if (weaponClass != WEAPONCLASS_NONE && + weaponClass != WEAPONCLASS_KNIFE && + weaponClass != WEAPONCLASS_GRENADE) { // The killer player kills the victim through the walls if (pVictim->GetDmgPenetrationLevel() > 0) From 9f662645623fcafb2fbb6d840e13360edac27b2f Mon Sep 17 00:00:00 2001 From: s1lentq Date: Thu, 9 May 2024 02:38:12 +0700 Subject: [PATCH 19/50] KillOfRarity: Add feature in-air kill --- regamedll/dlls/gamerules.h | 3 ++- regamedll/dlls/multiplay_gamerules.cpp | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/regamedll/dlls/gamerules.h b/regamedll/dlls/gamerules.h index 8d15e9ee4..cc6e9ec8f 100644 --- a/regamedll/dlls/gamerules.h +++ b/regamedll/dlls/gamerules.h @@ -252,7 +252,8 @@ enum KillRarity KILLRARITY_ASSISTEDFLASH = 0x020, // Assister helped with a flash KILLRARITY_DOMINATION_BEGAN = 0x040, // Killer player began dominating the victim (NOTE: this flag is set once) KILLRARITY_DOMINATION = 0x080, // Continues domination by the killer - KILLRARITY_REVENGE = 0x100 // Revenge by the killer + KILLRARITY_REVENGE = 0x100, // Revenge by the killer + KILLRARITY_INAIR = 0x200 // Killer was in the air (skill to deal with high inaccuracy) }; enum diff --git a/regamedll/dlls/multiplay_gamerules.cpp b/regamedll/dlls/multiplay_gamerules.cpp index ba71bcebb..f9aaa8c30 100644 --- a/regamedll/dlls/multiplay_gamerules.cpp +++ b/regamedll/dlls/multiplay_gamerules.cpp @@ -5314,6 +5314,10 @@ int CHalfLifeMultiplay::GetRarityOfKill(CBaseEntity *pKiller, CBasePlayer *pVict const Vector inEyePos = pKillerPlayer->EyePosition(); if (TheCSBots()->IsLineBlockedBySmoke(&inEyePos, &pVictim->pev->origin)) iRarity |= KILLRARITY_THRUSMOKE; + + // The killer player kills the victim while in air + if (!(pKillerPlayer->pev->flags & FL_ONGROUND)) + iRarity |= KILLRARITY_INAIR; } // Calculate # of unanswered kills between killer & victim From c08e6d0180b19c6d434f8acabde388b30cd9bef9 Mon Sep 17 00:00:00 2001 From: Sergey Shorokhov Date: Sun, 12 May 2024 17:17:02 +0300 Subject: [PATCH 20/50] `CNavArea::ComputeApproachAreas()`: fix hang on `*.nav` generating (#913) --- regamedll/game_shared/bot/nav_area.cpp | 178 +++++++++++++++---------- 1 file changed, 108 insertions(+), 70 deletions(-) diff --git a/regamedll/game_shared/bot/nav_area.cpp b/regamedll/game_shared/bot/nav_area.cpp index 1cac88ce9..8e4baf0da 100644 --- a/regamedll/game_shared/bot/nav_area.cpp +++ b/regamedll/game_shared/bot/nav_area.cpp @@ -4431,6 +4431,7 @@ inline bool IsAreaVisible(const Vector *pos, const CNavArea *area) // Determine the set of "approach areas". // An approach area is an area representing a place where players // move into/out of our local neighborhood of areas. +// @todo Optimize by search from eye outward and modifying pathfinder to treat all links as bi-directional void CNavArea::ComputeApproachAreas() { m_approachCount = 0; @@ -4452,95 +4453,132 @@ void CNavArea::ComputeApproachAreas() enum { MAX_PATH_LENGTH = 256 }; CNavArea *path[MAX_PATH_LENGTH]; - // In order to enumerate all of the approach areas, we need to - // run the algorithm many times, once for each "far away" area - // and keep the union of the approach area sets - for (auto farArea : goodSizedAreaList) + enum SearchType { - BlockedIDCount = 0; + FROM_EYE, ///< start search from our eyepoint outward to farArea + TO_EYE, ///< start search from farArea beack towards our eye + SEARCH_FINISHED + }; - // if we can see 'farArea', try again - the whole point is to go "around the bend", so to speak - if (IsAreaVisible(&eye, farArea)) - continue; + // In order to *completely* enumerate all of the approach areas, we + // need to search from our eyepoint outward, as well as from outwards + // towards our eyepoint + for (int searchType = FROM_EYE; searchType != SEARCH_FINISHED; searchType++) + { + // In order to enumerate all of the approach areas, we need to + // run the algorithm many times, once for each "far away" area + // and keep the union of the approach area sets + for (auto farArea : goodSizedAreaList) + { + BlockedIDCount = 0; - // make first path to far away area - ApproachAreaCost cost; - if (NavAreaBuildPath(this, farArea, nullptr, cost) == false) - continue; + // if we can see 'farArea', try again - the whole point is to go "around the bend", so to speak + if (IsAreaVisible(&eye, farArea)) + continue; - // - // Keep building paths to farArea and blocking them off until we - // cant path there any more. - // As areas are blocked off, all exits will be enumerated. - // - while (m_approachCount < MAX_APPROACH_AREAS) - { - // find number of areas on path - int count = 0; - CNavArea *area; - for (area = farArea; area; area = area->GetParent()) - count++; - - if (count > MAX_PATH_LENGTH) - count = MAX_PATH_LENGTH; - - // build path in correct order - from eye outwards - int i = count; - for (area = farArea; i && area; area = area->GetParent()) - { - path[--i] = area; - } + ApproachAreaCost cost; - // traverse path to find first area we cannot see (skip the first area) - for (i = 1; i < count; i++) + // + // Keep building paths to farArea and blocking them off until we + // cant path there any more. + // As areas are blocked off, all exits will be enumerated. + // + while (m_approachCount < MAX_APPROACH_AREAS) { - // if we see this area, continue on - if (IsAreaVisible(&eye, path[i])) - continue; + CNavArea *from, *to; - // we can't see this area. - // mark this area as "blocked" and unusable by subsequent approach paths - if (BlockedIDCount == MAX_BLOCKED_AREAS) + if (searchType == FROM_EYE) { - CONSOLE_ECHO("Overflow computing approach areas for area #%d.\n", m_id); - return; + // find another path *to* 'farArea' + // we must pathfind from us in order to pick up one-way paths OUT OF our area + from = this; + to = farArea; + } + else // TO_EYE + { + // find another path *from* 'farArea' + // we must pathfind to us in order to pick up one-way paths INTO our area + from = farArea; + to = this; } - // if the area to be blocked is actually farArea, block the one just prior - // (blocking farArea will cause all subsequent pathfinds to fail) - int block = (path[i] == farArea) ? i - 1 : i; + // build the actual path + if (NavAreaBuildPath(from, to, NULL, cost) == false) + break; - BlockedID[BlockedIDCount++] = path[block]->GetID(); + // find number of areas on path + int count = 0; + CNavArea *area; + for (area = to; area; area = area->GetParent()) + count++; - if (block == 0) + if (count > MAX_PATH_LENGTH) + count = MAX_PATH_LENGTH; + + // if the path is only two areas long, there can be no approach points + if (count <= 2) break; - // store new approach area if not already in set - int a; - for (a = 0; a < m_approachCount; a++) - if (m_approach[a].here.area == path[block - 1]) - break; + // build path starting from eye + int i = 0; - if (a == m_approachCount) + if (searchType == FROM_EYE) { - m_approach[m_approachCount].prev.area = (block >= 2) ? path[block-2] : nullptr; - m_approach[m_approachCount].here.area = path[block - 1]; - m_approach[m_approachCount].prevToHereHow = path[block - 1]->GetParentHow(); - m_approach[m_approachCount].next.area = path[block]; - m_approach[m_approachCount].hereToNextHow = path[block]->GetParentHow(); - m_approachCount++; + for(area = to; i < count && area; area = area->GetParent()) + { + path[count - i - 1] = area; + ++i; + } + } + else // TO_EYE + { + for(area = to; i < count && area; area = area->GetParent()) + path[i++] = area; } - // we are done with this path - break; - } + // traverse path to find first area we cannot see (skip the first area) + for (i = 1; i < count; i++) + { + // if we see this area, continue on + if (IsAreaVisible(&eye, path[i])) + continue; - // find another path to 'farArea' - ApproachAreaCost cost; - if (NavAreaBuildPath(this, farArea, nullptr, cost) == false) - { - // can't find a path to 'farArea' means all exits have been already tested and blocked - break; + // we can't see this area. + // mark this area as "blocked" and unusable by subsequent approach paths + if (BlockedIDCount == MAX_BLOCKED_AREAS) + { + CONSOLE_ECHO("Overflow computing approach areas for area #%d.\n", m_id); + return; + } + + // if the area to be blocked is actually farArea, block the one just prior + // (blocking farArea will cause all subsequent pathfinds to fail) + int block = (path[i] == farArea) ? i - 1 : i; + + if (block == 0) + continue; + + BlockedID[BlockedIDCount++] = path[block]->GetID(); + + // store new approach area if not already in set + int a; + for (a = 0; a < m_approachCount; a++) + if (m_approach[a].here.area == path[block-1]) + break; + + if (a == m_approachCount) + { + m_approach[m_approachCount].prev.area = (block >= 2) ? path[block-2] : nullptr; + m_approach[m_approachCount].here.area = path[block - 1]; + m_approach[m_approachCount].prevToHereHow = path[block - 1]->GetParentHow(); + m_approach[m_approachCount].next.area = path[block]; + m_approach[m_approachCount].hereToNextHow = path[block]->GetParentHow(); + m_approachCount++; + } + + // we are done with this path + break; + } } } } From 7372573c89e0bbc5068c90cb8ac5b7357adf595a Mon Sep 17 00:00:00 2001 From: s1lentq Date: Tue, 28 May 2024 22:44:29 +0700 Subject: [PATCH 21/50] Add UTIL_IsValidPlayer Ignore dormant players Minor refactoring --- regamedll/dlls/bot/cs_bot.cpp | 10 +-- regamedll/dlls/bot/cs_bot_chatter.cpp | 10 +-- regamedll/dlls/bot/cs_bot_manager.cpp | 18 +++-- regamedll/dlls/bot/cs_bot_pathfind.cpp | 5 +- regamedll/dlls/bot/cs_bot_vision.cpp | 7 +- regamedll/dlls/career_tasks.cpp | 6 +- regamedll/dlls/client.cpp | 33 +++++++- regamedll/dlls/cmdhandler.cpp | 7 +- regamedll/dlls/combat.cpp | 6 +- regamedll/dlls/hostage/hostage.cpp | 4 +- regamedll/dlls/hostage/hostage_improv.cpp | 10 +-- regamedll/dlls/multiplay_gamerules.cpp | 90 ++++++++++++--------- regamedll/dlls/player.cpp | 98 ++++++++++++++--------- regamedll/dlls/player.h | 13 +++ regamedll/dlls/triggers.cpp | 2 +- regamedll/dlls/tutor.cpp | 5 +- regamedll/dlls/tutor_cs_tutor.cpp | 14 +++- regamedll/dlls/util.cpp | 41 +++++++--- regamedll/dlls/weapons.cpp | 7 +- regamedll/game_shared/bot/bot.cpp | 8 +- regamedll/game_shared/bot/bot_manager.cpp | 7 +- regamedll/game_shared/bot/bot_util.cpp | 66 ++++----------- 22 files changed, 263 insertions(+), 204 deletions(-) diff --git a/regamedll/dlls/bot/cs_bot.cpp b/regamedll/dlls/bot/cs_bot.cpp index 838387a95..dd85448c6 100644 --- a/regamedll/dlls/bot/cs_bot.cpp +++ b/regamedll/dlls/bot/cs_bot.cpp @@ -43,10 +43,7 @@ int GetBotFollowCount(CBasePlayer *pLeader) { CBasePlayer *pPlayer = UTIL_PlayerByIndex(i); - if (!pPlayer) - continue; - - if (FNullEnt(pPlayer->pev)) + if (!UTIL_IsValidPlayer(pPlayer)) continue; if (FStrEq(STRING(pPlayer->pev->netname), "")) @@ -685,10 +682,7 @@ CBasePlayer *CCSBot::GetImportantEnemy(bool checkVisibility) const { CBasePlayer *pPlayer = UTIL_PlayerByIndex(i); - if (!pPlayer) - continue; - - if (FNullEnt(pPlayer->pev)) + if (!UTIL_IsValidPlayer(pPlayer)) continue; if (FStrEq(STRING(pPlayer->pev->netname), "")) diff --git a/regamedll/dlls/bot/cs_bot_chatter.cpp b/regamedll/dlls/bot/cs_bot_chatter.cpp index 2c6b3336e..1476d8d58 100644 --- a/regamedll/dlls/bot/cs_bot_chatter.cpp +++ b/regamedll/dlls/bot/cs_bot_chatter.cpp @@ -65,10 +65,7 @@ void BotMeme::Transmit(CCSBot *pSender) const { CBasePlayer *pPlayer = UTIL_PlayerByIndex(i); - if (!pPlayer) - continue; - - if (FNullEnt(pPlayer->pev)) + if (!UTIL_IsValidPlayer(pPlayer)) continue; if (FStrEq(STRING(pPlayer->pev->netname), "")) @@ -1525,10 +1522,7 @@ BotStatement *BotChatterInterface::GetActiveStatement() { CBasePlayer *pPlayer = UTIL_PlayerByIndex(i); - if (!pPlayer) - continue; - - if (FNullEnt(pPlayer->pev)) + if (!UTIL_IsValidPlayer(pPlayer)) continue; if (FStrEq(STRING(pPlayer->pev->netname), "")) diff --git a/regamedll/dlls/bot/cs_bot_manager.cpp b/regamedll/dlls/bot/cs_bot_manager.cpp index 78ce0cf5d..fc1d50c88 100644 --- a/regamedll/dlls/bot/cs_bot_manager.cpp +++ b/regamedll/dlls/bot/cs_bot_manager.cpp @@ -337,6 +337,10 @@ void CCSBotManager::ClientDisconnect(CBasePlayer *pPlayer) pPlayer = GetClassPtr((CBasePlayer *)pevTemp); AddEntityHashValue(pPlayer->pev, STRING(pPlayer->pev->classname), CLASSNAME); pPlayer->pev->flags = FL_DORMANT; + +#ifdef REGAMEDLL_FIXES + pPlayer->has_disconnected = true; +#endif } void PrintAllEntities() @@ -396,10 +400,8 @@ void CCSBotManager::ServerCommand(const char *pcmd) for (int i = 1; i <= gpGlobals->maxClients; i++) { CBasePlayer *pPlayer = UTIL_PlayerByIndex(i); - if (!pPlayer) - continue; - if (FNullEnt(pPlayer->pev)) + if (!UTIL_IsValidPlayer(pPlayer)) continue; const char *name = STRING(pPlayer->pev->netname); @@ -425,10 +427,8 @@ void CCSBotManager::ServerCommand(const char *pcmd) for (int i = 1; i <= gpGlobals->maxClients; i++) { CBasePlayer *pPlayer = UTIL_PlayerByIndex(i); - if (!pPlayer) - continue; - if (FNullEnt(pPlayer->pev)) + if (!UTIL_IsValidPlayer(pPlayer)) continue; const char *name = STRING(pPlayer->pev->netname); @@ -665,6 +665,9 @@ void CCSBotManager::ServerCommand(const char *pcmd) CBaseEntity *pEntity = nullptr; while ((pEntity = UTIL_FindEntityByClassname(pEntity, "player"))) { + if (FNullEnt(pEntity->edict())) + break; + if (!pEntity->IsPlayer()) continue; @@ -1580,7 +1583,8 @@ void CCSBotManager::OnFreeEntPrivateData(CBaseEntity *pEntity) for (int i = 1; i <= gpGlobals->maxClients; i++) { CBasePlayer *pPlayer = UTIL_PlayerByIndex(i); - if (!pPlayer || pPlayer->IsDormant()) + + if (!UTIL_IsValidPlayer(pPlayer)) continue; if (pPlayer->IsBot()) diff --git a/regamedll/dlls/bot/cs_bot_pathfind.cpp b/regamedll/dlls/bot/cs_bot_pathfind.cpp index 00386be7b..f418b56cb 100644 --- a/regamedll/dlls/bot/cs_bot_pathfind.cpp +++ b/regamedll/dlls/bot/cs_bot_pathfind.cpp @@ -1118,10 +1118,7 @@ bool CCSBot::IsFriendInTheWay(const Vector *goalPos) const { CBasePlayer *pPlayer = UTIL_PlayerByIndex(i); - if (!pPlayer) - continue; - - if (FNullEnt(pPlayer->pev)) + if (!UTIL_IsValidPlayer(pPlayer)) continue; if (!pPlayer->IsAlive()) diff --git a/regamedll/dlls/bot/cs_bot_vision.cpp b/regamedll/dlls/bot/cs_bot_vision.cpp index 8fc811ed2..db9e2143c 100644 --- a/regamedll/dlls/bot/cs_bot_vision.cpp +++ b/regamedll/dlls/bot/cs_bot_vision.cpp @@ -253,7 +253,7 @@ bool CCSBot::IsVisible(CBasePlayer *pPlayer, bool testFOV, unsigned char *visPar if ((pPlayer->pev->flags & FL_NOTARGET) || (pPlayer->pev->effects & EF_NODRAW)) return false; #endif - + Vector spot = pPlayer->pev->origin; unsigned char testVisParts = NONE; @@ -701,10 +701,7 @@ CBasePlayer *CCSBot::FindMostDangerousThreat() { CBasePlayer *pPlayer = UTIL_PlayerByIndex(i); - if (!pPlayer) - continue; - - if (FNullEnt(pPlayer->pev)) + if (!UTIL_IsValidPlayer(pPlayer)) continue; // is it a player? diff --git a/regamedll/dlls/career_tasks.cpp b/regamedll/dlls/career_tasks.cpp index 200c67558..972ccca9b 100644 --- a/regamedll/dlls/career_tasks.cpp +++ b/regamedll/dlls/career_tasks.cpp @@ -545,7 +545,11 @@ void CCareerTaskManager::HandleDeath(int team, CBasePlayer *pAttacker) for (int i = 1; i <= gpGlobals->maxClients; i++) { CBasePlayer *pPlayer = UTIL_PlayerByIndex(i); - if (pPlayer && pPlayer->m_iTeam == enemyTeam && pPlayer->IsAlive()) + + if (!UTIL_IsValidPlayer(pPlayer)) + continue; + + if (pPlayer->m_iTeam == enemyTeam && pPlayer->IsAlive()) numEnemies++; } diff --git a/regamedll/dlls/client.cpp b/regamedll/dlls/client.cpp index 1ac3dd562..7d3fb659e 100644 --- a/regamedll/dlls/client.cpp +++ b/regamedll/dlls/client.cpp @@ -438,6 +438,9 @@ NOXREF int CountTeams() if (FNullEnt(pEntity->edict())) break; + if (pEntity->IsDormant()) + continue; + CBasePlayer *pPlayer = GetClassPtr((CBasePlayer *)pEntity->pev); if (pPlayer->m_iTeam == UNASSIGNED) @@ -499,7 +502,8 @@ int CountTeamPlayers(int iTeam) if (pEntity->IsDormant()) continue; - if (GetClassPtr((CBasePlayer *)pEntity->pev)->m_iTeam == iTeam) + CBasePlayer *pPlayer = GetClassPtr((CBasePlayer *)pEntity->pev); + if (pPlayer->m_iTeam == iTeam) { nCount++; } @@ -534,6 +538,9 @@ void ProcessKickVote(CBasePlayer *pVotingPlayer, CBasePlayer *pKickPlayer) if (FNullEnt(pTempEntity->edict())) break; + if (pTempEntity->IsDormant()) + continue; + pTempPlayer = GetClassPtr((CBasePlayer *)pTempEntity->pev); if (!pTempPlayer || pTempPlayer->m_iTeam == UNASSIGNED) @@ -571,6 +578,9 @@ void ProcessKickVote(CBasePlayer *pVotingPlayer, CBasePlayer *pKickPlayer) if (FNullEnt(pTempEntity->edict())) break; + if (pTempEntity->IsDormant()) + continue; + pTempPlayer = GetClassPtr((CBasePlayer *)pTempEntity->pev); if (!pTempPlayer || pTempPlayer->m_iTeam == UNASSIGNED) @@ -976,6 +986,9 @@ void Host_Say(edict_t *pEntity, BOOL teamonly) if (pReceiver->edict() == pEntity) continue; + if (pReceiver->IsDormant()) + continue; + // Not a client ? (should never be true) if (!pReceiver->IsNetClient()) continue; @@ -2343,6 +2356,9 @@ CBaseEntity *EntityFromUserID(int userID) if (FNullEnt(pTempEntity->edict())) break; + if (pTempEntity->IsDormant()) + continue; + CBasePlayer *pTempPlayer = GetClassPtr((CBasePlayer *)pTempEntity->pev); if (pTempPlayer->m_iTeam != UNASSIGNED && userID == GETPLAYERUSERID(pTempEntity->edict())) @@ -2363,6 +2379,9 @@ NOXREF int CountPlayersInServer() if (FNullEnt(pTempEntity->edict())) break; + if (pTempEntity->IsDormant()) + continue; + CBasePlayer *pTempPlayer = GetClassPtr((CBasePlayer *)pTempEntity->pev); if (pTempPlayer->m_iTeam != UNASSIGNED) @@ -3343,7 +3362,11 @@ void EXT_FUNC InternalCommand(edict_t *pEntity, const char *pcmd, const char *pa for (int i = 1; i <= gpGlobals->maxClients; i++) { CBasePlayer *pObserver = UTIL_PlayerByIndex(i); - if (pObserver && pObserver->IsObservingPlayer(pPlayer)) + + if (!UTIL_IsValidPlayer(pObserver)) + continue; + + if (pObserver->IsObservingPlayer(pPlayer)) { EMIT_SOUND(ENT(pObserver->pev), CHAN_ITEM, "items/nvg_off.wav", RANDOM_FLOAT(0.92, 1), ATTN_NORM); @@ -3368,7 +3391,11 @@ void EXT_FUNC InternalCommand(edict_t *pEntity, const char *pcmd, const char *pa for (int i = 1; i <= gpGlobals->maxClients; i++) { CBasePlayer *pObserver = UTIL_PlayerByIndex(i); - if (pObserver && pObserver->IsObservingPlayer(pPlayer)) + + if (!UTIL_IsValidPlayer(pObserver)) + continue; + + if (pObserver->IsObservingPlayer(pPlayer)) { EMIT_SOUND(ENT(pObserver->pev), CHAN_ITEM, "items/nvg_on.wav", RANDOM_FLOAT(0.92, 1), ATTN_NORM); diff --git a/regamedll/dlls/cmdhandler.cpp b/regamedll/dlls/cmdhandler.cpp index d715de641..73bfe7cfd 100644 --- a/regamedll/dlls/cmdhandler.cpp +++ b/regamedll/dlls/cmdhandler.cpp @@ -37,7 +37,10 @@ void SV_Continue_f() { CBasePlayer *pPlayer = UTIL_PlayerByIndex(i); - if (pPlayer && !pPlayer->IsBot()) + if (!UTIL_IsValidPlayer(pPlayer)) + continue; + + if (!pPlayer->IsBot()) { // at the end of the round is showed window with the proposal surrender or continue // now of this time HUD is completely hidden @@ -96,7 +99,7 @@ void SV_Career_EndRound_f() { CBasePlayer *pPlayer = UTIL_PlayerByIndex(i); - if (!pPlayer || FNullEnt(pPlayer->pev)) + if (!UTIL_IsValidPlayer(pPlayer)) continue; if (pPlayer->IsBot() && pPlayer->m_iTeam == pLocalPlayer->m_iTeam) diff --git a/regamedll/dlls/combat.cpp b/regamedll/dlls/combat.cpp index 4cbc485dc..17fe84e27 100644 --- a/regamedll/dlls/combat.cpp +++ b/regamedll/dlls/combat.cpp @@ -9,7 +9,11 @@ void PlayerBlind(CBasePlayer *pPlayer, entvars_t *pevInflictor, entvars_t *pevAt for (int i = 1; i <= gpGlobals->maxClients; i++) { CBasePlayer *pObserver = UTIL_PlayerByIndex(i); - if (pObserver && pObserver->IsObservingPlayer(pPlayer)) + + if (!UTIL_IsValidPlayer(pObserver)) + continue; + + if (pObserver->IsObservingPlayer(pPlayer)) { UTIL_ScreenFade(pObserver, color, fadeTime, fadeHold, alpha, 0); } diff --git a/regamedll/dlls/hostage/hostage.cpp b/regamedll/dlls/hostage/hostage.cpp index c9ff79725..7ef34b222 100644 --- a/regamedll/dlls/hostage/hostage.cpp +++ b/regamedll/dlls/hostage/hostage.cpp @@ -1242,7 +1242,7 @@ void CHostage::SendHostagePositionMsg() if (!pEntity->IsPlayer()) continue; - if (pEntity->pev->flags == FL_DORMANT) + if (pEntity->IsDormant()) continue; CBasePlayer *pTempPlayer = GetClassPtr((CBasePlayer *)pEntity->pev); @@ -1271,7 +1271,7 @@ void CHostage::SendHostageEventMsg() if (!pEntity->IsPlayer()) continue; - if (pEntity->pev->flags == FL_DORMANT) + if (pEntity->IsDormant()) continue; CBasePlayer *pTempPlayer = GetClassPtr((CBasePlayer *)pEntity->pev); diff --git a/regamedll/dlls/hostage/hostage_improv.cpp b/regamedll/dlls/hostage/hostage_improv.cpp index a70d2c76a..9cdba7777 100644 --- a/regamedll/dlls/hostage/hostage_improv.cpp +++ b/regamedll/dlls/hostage/hostage_improv.cpp @@ -358,10 +358,7 @@ bool CHostageImprov::IsFriendInTheWay(const Vector &goalPos) const { CBasePlayer *pPlayer = UTIL_PlayerByIndex(i); - if (!pPlayer) - continue; - - if (FNullEnt(pPlayer->pev)) + if (!UTIL_IsValidPlayer(pPlayer)) continue; if (!pPlayer->IsAlive() || pPlayer->m_iTeam == TERRORIST) @@ -675,10 +672,7 @@ void CHostageImprov::UpdateVision() { CBasePlayer *pPlayer = UTIL_PlayerByIndex(i); - if (!pPlayer) - continue; - - if (FNullEnt(pPlayer->pev)) + if (!UTIL_IsValidPlayer(pPlayer)) continue; if (FStrEq(STRING(pPlayer->pev->netname), "")) diff --git a/regamedll/dlls/multiplay_gamerules.cpp b/regamedll/dlls/multiplay_gamerules.cpp index f9aaa8c30..7651527be 100644 --- a/regamedll/dlls/multiplay_gamerules.cpp +++ b/regamedll/dlls/multiplay_gamerules.cpp @@ -34,7 +34,10 @@ bool IsBotSpeaking() { CBasePlayer *pPlayer = UTIL_PlayerByIndex(i); - if (!pPlayer || !pPlayer->IsBot()) + if (!UTIL_IsValidPlayer(pPlayer)) + continue; + + if (!pPlayer->IsBot()) continue; CCSBot *pBot = static_cast(pPlayer); @@ -704,7 +707,7 @@ CBasePlayer *EXT_FUNC CHalfLifeMultiplay::__API_HOOK(GiveC4)() { CBasePlayer *pPlayer = UTIL_PlayerByIndex(i); - if (!pPlayer || FNullEnt(pPlayer->edict())) + if (!UTIL_IsValidPlayer(pPlayer)) continue; if (pPlayer->pev->deadflag != DEAD_NO || pPlayer->m_iTeam != TERRORIST) @@ -1079,10 +1082,8 @@ bool EXT_FUNC CHalfLifeMultiplay::NeededPlayersCheck() if (IsCareer()) { CBasePlayer *pPlayer = UTIL_PlayerByIndex(gpGlobals->maxClients); - if (!pPlayer || !pPlayer->IsBot()) - { + if (!UTIL_IsValidPlayer(pPlayer) || !pPlayer->IsBot()) return true; - } } return OnRoundEnd_Intercept(WINSTATUS_DRAW, ROUND_GAME_COMMENCE, IsCareer() ? 0 : 3); @@ -1815,10 +1816,11 @@ void EXT_FUNC CHalfLifeMultiplay::__API_HOOK(RestartRound)() for (int i = 1; i <= gpGlobals->maxClients; i++) { CBasePlayer *pPlayer = UTIL_PlayerByIndex(i); - if (pPlayer && !FNullEnt(pPlayer->pev)) - { - pPlayer->Reset(); - } + + if (!UTIL_IsValidPlayer(pPlayer)) + continue; + + pPlayer->Reset(); } if (TheBots) @@ -1986,7 +1988,7 @@ void EXT_FUNC CHalfLifeMultiplay::__API_HOOK(RestartRound)() if (FNullEnt(pEntity->edict())) break; - if (pEntity->pev->flags == FL_DORMANT) + if (pEntity->IsDormant()) continue; CBasePlayer *pPlayer = GetClassPtr((CBasePlayer *)pEntity->pev); @@ -2097,12 +2099,17 @@ void EXT_FUNC CHalfLifeMultiplay::__API_HOOK(RestartRound)() BOOL CHalfLifeMultiplay::IsThereABomber() { - CBasePlayer *pPlayer = nullptr; - while ((pPlayer = UTIL_FindEntityByClassname(pPlayer, "player"))) + CBaseEntity *pEntity = nullptr; + while ((pEntity = UTIL_FindEntityByClassname(pEntity, "player"))) { - if (FNullEnt(pPlayer->edict())) + if (FNullEnt(pEntity->edict())) break; + if (pEntity->IsDormant()) + continue; + + CBasePlayer *pPlayer = GetClassPtr((CBasePlayer *)pEntity->pev); + if (pPlayer->m_iTeam != CT && pPlayer->IsBombGuy()) { // There you are. @@ -2517,7 +2524,10 @@ void EXT_FUNC CHalfLifeMultiplay::__API_HOOK(Think)() { CBasePlayer *pPlayer = UTIL_PlayerByIndex(i); - if (pPlayer && !pPlayer->IsBot()) + if (!UTIL_IsValidPlayer(pPlayer)) + continue; + + if (!pPlayer->IsBot()) { MESSAGE_BEGIN(MSG_ONE, gmsgCZCareerHUD, nullptr, pPlayer->pev); WRITE_STRING("ROUND"); @@ -2727,9 +2737,9 @@ bool CHalfLifeMultiplay::CheckFragLimit() // check if any player is over the frag limit for (int i = 1; i <= gpGlobals->maxClients; i++) { - auto pPlayer = UTIL_PlayerByIndex(i); + CBasePlayer *pPlayer = UTIL_PlayerByIndex(i); - if (!pPlayer || pPlayer->has_disconnected) + if (!UTIL_IsValidPlayer(pPlayer) || pPlayer->has_disconnected) continue; if (pPlayer->pev->frags >= fraglimit.value) @@ -2822,8 +2832,14 @@ void EXT_FUNC CHalfLifeMultiplay::OnRoundFreezeEnd() for (int i = 1; i <= gpGlobals->maxClients; i++) { CBasePlayer *plr = UTIL_PlayerByIndex(i); - if (!plr || plr->pev->flags == FL_DORMANT) + + if (!UTIL_IsValidPlayer(plr)) + continue; + +#ifndef REGAMEDLL_FIXES + if (plr->pev->flags == FL_DORMANT) continue; +#endif if (plr->m_iJoiningState == JOINED) { @@ -3200,7 +3216,7 @@ void CHalfLifeMultiplay::MarkLivingPlayersOnTeamAsNotReceivingMoneyNextRound(int { CBasePlayer *pPlayer = UTIL_PlayerByIndex(i); - if (!pPlayer || FNullEnt(pPlayer->pev)) + if (!UTIL_IsValidPlayer(pPlayer)) continue; if (pPlayer->m_iTeam == iTeam) @@ -3238,7 +3254,7 @@ void CHalfLifeMultiplay::CareerRestart() { CBasePlayer *pPlayer = UTIL_PlayerByIndex(i); - if (!pPlayer || FNullEnt(pPlayer->pev)) + if (!UTIL_IsValidPlayer(pPlayer)) continue; if (!pPlayer->IsBot()) @@ -3439,13 +3455,9 @@ void CHalfLifeMultiplay::InitHUD(CBasePlayer *pl) { // FIXME: Probably don't need to cast this just to read m_iDeaths CBasePlayer *plr = UTIL_PlayerByIndex(i); - if (!plr) + if (!UTIL_IsValidPlayer(plr)) continue; -#ifdef REGAMEDLL_FIXES - if (plr->IsDormant()) - continue; -#endif MESSAGE_BEGIN(MSG_ONE, gmsgScoreInfo, nullptr, pl->edict()); WRITE_BYTE(i); // client number WRITE_SHORT(int(plr->pev->frags)); @@ -3484,13 +3496,9 @@ void CHalfLifeMultiplay::InitHUD(CBasePlayer *pl) for (i = 1; i <= gpGlobals->maxClients; i++) { CBasePlayer *plr = UTIL_PlayerByIndex(i); - if (!plr) - continue; -#ifdef REGAMEDLL_FIXES - if (plr->IsDormant()) + if (!UTIL_IsValidPlayer(plr)) continue; -#endif MESSAGE_BEGIN(MSG_ONE, gmsgTeamInfo, nullptr, pl->edict()); WRITE_BYTE(plr->entindex()); @@ -3502,7 +3510,7 @@ void CHalfLifeMultiplay::InitHUD(CBasePlayer *pl) if (pl->entindex() != i) { #ifndef REGAMEDLL_FIXES - if (plr->pev->flags == FL_DORMANT) + if (plr->IsDormant()) continue; #endif if (plr->pev->deadflag == DEAD_NO @@ -3663,6 +3671,9 @@ void CHalfLifeMultiplay::ClientDisconnected(edict_t *pClient) if (!pObserver->pev || pObserver == pPlayer) continue; + if (pObserver->IsDormant()) + continue; + // If a spectator was chasing this player, move him/her onto the next player if (pObserver->m_hObserverTarget == pPlayer) { @@ -4641,10 +4652,11 @@ int CountPlayers() for (int i = 1; i <= gpGlobals->maxClients; i++) { CBasePlayer *pPlayer = UTIL_PlayerByIndex(i); - if (pPlayer) - { - nCount++; - } + + if (!UTIL_IsValidPlayer(pPlayer) || pPlayer->IsBot()) + continue; + + nCount++; } return nCount; @@ -4746,6 +4758,9 @@ void CHalfLifeMultiplay::ResetAllMapVotes() if (FNullEnt(pEntity->edict())) break; + if (pEntity->IsDormant()) + continue; + CBasePlayer *pPlayer = GetClassPtr((CBasePlayer *)pEntity->pev); if (pPlayer->m_iTeam != UNASSIGNED) { @@ -4849,6 +4864,9 @@ void CHalfLifeMultiplay::ProcessMapVote(CBasePlayer *pPlayer, int iVote) if (FNullEnt(pEntity->edict())) break; + if (pEntity->IsDormant()) + continue; + CBasePlayer *pPlayer = GetClassPtr((CBasePlayer *)pEntity->pev); if (pPlayer->m_iTeam != UNASSIGNED) @@ -5228,7 +5246,7 @@ CBasePlayer *CHalfLifeMultiplay::CheckAssistsToKill(CBaseEntity *pKiller, CBaseP continue; // dealt no damage CBasePlayer *pAttackerPlayer = UTIL_PlayerByIndex(i); - if (!pAttackerPlayer || pAttackerPlayer->IsDormant()) + if (!UTIL_IsValidPlayer(pAttackerPlayer)) continue; // ignore idle clients CCSPlayer *pCSAttackerPlayer = pAttackerPlayer->CSPlayer(); @@ -5412,7 +5430,7 @@ void CHalfLifeMultiplay::GiveDefuserToRandomPlayer() for (int i = 1; i <= gpGlobals->maxClients; i++) { CBasePlayer *pPlayer = UTIL_PlayerByIndex(i); - if (!pPlayer || FNullEnt(pPlayer->edict())) + if (!UTIL_IsValidPlayer(pPlayer)) continue; if (!pPlayer->IsAlive() || pPlayer->m_iTeam != CT) diff --git a/regamedll/dlls/player.cpp b/regamedll/dlls/player.cpp index 875f085bb..52eaeaea6 100644 --- a/regamedll/dlls/player.cpp +++ b/regamedll/dlls/player.cpp @@ -332,7 +332,10 @@ CBasePlayer *CBasePlayer::GetNextRadioRecipient(CBasePlayer *pStartPlayer) continue; CBasePlayer *pTarget = CBasePlayer::Instance(pPlayer->m_hObserverTarget->pev); - if (pTarget && pTarget->m_iTeam == m_iTeam) + if (!pTarget || pTarget->IsDormant()) + continue; + + if (pTarget->m_iTeam == m_iTeam) { bSend = true; } @@ -365,6 +368,9 @@ void EXT_FUNC CBasePlayer::__API_HOOK(Radio)(const char *msg_id, const char *msg if (FNullEnt(pEntity->edict())) break; + if (pEntity->IsDormant()) + continue; + bool bSend = false; CBasePlayer *pPlayer = GetClassPtr((CBasePlayer *)pEntity->pev); @@ -396,7 +402,11 @@ void EXT_FUNC CBasePlayer::__API_HOOK(Radio)(const char *msg_id, const char *msg if (FNullEnt(pPlayer->m_hObserverTarget)) continue; - if (pPlayer->m_hObserverTarget && g_pGameRules->PlayerRelationship(this, pPlayer->m_hObserverTarget) == GR_TEAMMATE) + CBasePlayer *pTarget = CBasePlayer::Instance(pPlayer->m_hObserverTarget->pev); + if (!pTarget || pTarget->IsDormant()) + continue; + + if (g_pGameRules->PlayerRelationship(this, pTarget) == GR_TEAMMATE) { bSend = true; } @@ -1029,7 +1039,10 @@ BOOL EXT_FUNC CBasePlayer::__API_HOOK(TakeDamage)(entvars_t *pevInflictor, entva { CBasePlayer *pPlayer = UTIL_PlayerByIndex(i); - if (!pPlayer || pPlayer->m_hObserverTarget != this) + if (!UTIL_IsValidPlayer(pPlayer)) + continue; + + if (pPlayer->m_hObserverTarget != this) continue; MESSAGE_BEGIN(MSG_ONE, gmsgSpecHealth, nullptr, pPlayer->edict()); @@ -1092,6 +1105,9 @@ BOOL EXT_FUNC CBasePlayer::__API_HOOK(TakeDamage)(entvars_t *pevInflictor, entva if (FNullEnt(pEntity->edict())) break; + if (pEntity->IsDormant()) + continue; + CBasePlayer *pPlayer = GetClassPtr((CBasePlayer *)pEntity->pev); if (pPlayer->m_iTeam == m_iTeam) @@ -1278,7 +1294,7 @@ BOOL EXT_FUNC CBasePlayer::__API_HOOK(TakeDamage)(entvars_t *pevInflictor, entva { CBasePlayer *pPlayer = UTIL_PlayerByIndex(i); - if (!pPlayer) + if (!UTIL_IsValidPlayer(pPlayer)) continue; if (pPlayer->m_hObserverTarget == this) @@ -1876,6 +1892,9 @@ void CBasePlayer::SetProgressBarTime(int time) if (FNullEnt(pEntity->edict())) break; + if (pEntity->IsDormant()) + continue; + CBasePlayer *pPlayer = GetClassPtr((CBasePlayer *)pEntity->pev); if (pPlayer->GetObserverMode() == OBS_IN_EYE && pPlayer->pev->iuser2 == playerIndex) @@ -1916,6 +1935,9 @@ void CBasePlayer::SetProgressBarTime2(int time, float timeElapsed) if (FNullEnt(pEntity->edict())) break; + if (pEntity->IsDormant()) + continue; + CBasePlayer *pPlayer = GetClassPtr((CBasePlayer *)pEntity->pev); if (pPlayer->GetObserverMode() == OBS_IN_EYE && pPlayer->pev->iuser2 == playerIndex) @@ -2188,7 +2210,7 @@ void EXT_FUNC CBasePlayer::__API_HOOK(Killed)(entvars_t *pevAttacker, int iGib) { CBasePlayer *pPlayer = UTIL_PlayerByIndex(i); - if (!pPlayer) + if (!UTIL_IsValidPlayer(pPlayer)) continue; bool killedByHumanPlayer = (!pPlayer->IsBot() && pPlayer->pev == pevAttacker && pPlayer->m_iTeam != m_iTeam); @@ -2219,7 +2241,7 @@ void EXT_FUNC CBasePlayer::__API_HOOK(Killed)(entvars_t *pevAttacker, int iGib) { CBasePlayer *pObserver = UTIL_PlayerByIndex(i); - if (!pObserver) + if (!UTIL_IsValidPlayer(pObserver)) continue; if (pObserver->IsObservingPlayer(this)) @@ -4443,7 +4465,10 @@ void EXT_FUNC CBasePlayer::__API_HOOK(AddPointsToTeam)(int score, BOOL bAllowNeg { CBasePlayer *pPlayer = UTIL_PlayerByIndex(i); - if (pPlayer && i != index) + if (!UTIL_IsValidPlayer(pPlayer)) + continue; + + if (i != index) { if (g_pGameRules->PlayerRelationship(this, pPlayer) == GR_TEAMMATE) { @@ -5830,7 +5855,10 @@ void EXT_FUNC CBasePlayer::__API_HOOK(Spawn)() { CBasePlayer *pObserver = UTIL_PlayerByIndex(i); - if (pObserver && pObserver->IsObservingPlayer(this)) + if (!UTIL_IsValidPlayer(pObserver)) + continue; + + if (pObserver->IsObservingPlayer(this)) { MESSAGE_BEGIN(MSG_ONE, gmsgNVGToggle, nullptr, pObserver->pev); WRITE_BYTE(0); @@ -5959,8 +5987,10 @@ void CBasePlayer::SetScoreboardAttributes(CBasePlayer *destination) { CBasePlayer *pPlayer = UTIL_PlayerByIndex(i); - if (pPlayer && !FNullEnt(pPlayer->edict())) - SetScoreboardAttributes(pPlayer); + if (!UTIL_IsValidPlayer(pPlayer)) + continue; + + SetScoreboardAttributes(pPlayer); } } @@ -6509,10 +6539,8 @@ void CBasePlayer::ForceClientDllUpdate() for (int i = 1; i <= gpGlobals->maxClients; i++) { CBasePlayer *pPlayer = UTIL_PlayerByIndex(i); - if (!pPlayer || FNullEnt(pPlayer->edict())) - continue; - if (pPlayer->IsDormant()) + if (!UTIL_IsValidPlayer(pPlayer)) continue; if (pev->deadflag == DEAD_NO) @@ -7663,14 +7691,14 @@ void EXT_FUNC CBasePlayer::__API_HOOK(UpdateClientData)() { CBaseEntity *pEntity = UTIL_PlayerByIndex(i); - if (!pEntity || i == entindex()) + if (!UTIL_IsValidPlayer(pEntity)) continue; - CBasePlayer *pPlayer = GetClassPtr((CBasePlayer *)pEntity->pev); - - if (pPlayer->pev->flags == FL_DORMANT) + if (i == entindex()) continue; + CBasePlayer *pPlayer = GetClassPtr((CBasePlayer *)pEntity->pev); + if (pPlayer->pev->deadflag != DEAD_NO) continue; @@ -7704,16 +7732,11 @@ void EXT_FUNC CBasePlayer::__API_HOOK(UpdateClientData)() { CBaseEntity *pEntity = UTIL_PlayerByIndex(playerIndex); - if (!pEntity) + if (!UTIL_IsValidPlayer(pEntity)) continue; CBasePlayer *pPlayer = GetClassPtr((CBasePlayer *)pEntity->pev); -#ifdef REGAMEDLL_FIXES - if (pPlayer->IsDormant()) - continue; -#endif // REGAMEDLL_FIXES - #ifdef REGAMEDLL_FIXES if (scoreboard_showhealth.value != -1.0f) #endif @@ -8237,24 +8260,21 @@ CBaseEntity *EXT_FUNC CBasePlayer::__API_HOOK(DropPlayerItem)(const char *pszIte if (FNullEnt(pEntity->edict())) break; - if (!pEntity->IsPlayer()) + if (!pEntity->IsPlayer() || pEntity->IsDormant()) continue; - if (pEntity->pev->flags != FL_DORMANT) - { - CBasePlayer *pOther = GetClassPtr((CBasePlayer *)pEntity->pev); + CBasePlayer *pOther = GetClassPtr((CBasePlayer *)pEntity->pev); - if (pOther->pev->deadflag == DEAD_NO && pOther->m_iTeam == TERRORIST) - { - ClientPrint(pOther->pev, HUD_PRINTCENTER, "#Game_bomb_drop", STRING(pev->netname)); - - MESSAGE_BEGIN(MSG_ONE, gmsgBombDrop, nullptr, pOther->pev); - WRITE_COORD(pev->origin.x); - WRITE_COORD(pev->origin.y); - WRITE_COORD(pev->origin.z); - WRITE_BYTE(BOMB_FLAG_DROPPED); - MESSAGE_END(); - } + if (pOther->pev->deadflag == DEAD_NO && pOther->m_iTeam == TERRORIST) + { + ClientPrint(pOther->pev, HUD_PRINTCENTER, "#Game_bomb_drop", STRING(pev->netname)); + + MESSAGE_BEGIN(MSG_ONE, gmsgBombDrop, nullptr, pOther->pev); + WRITE_COORD(pev->origin.x); + WRITE_COORD(pev->origin.y); + WRITE_COORD(pev->origin.z); + WRITE_BYTE(BOMB_FLAG_DROPPED); + MESSAGE_END(); } } } @@ -10137,7 +10157,7 @@ void CBasePlayer::UpdateLocation(bool forceUpdate) { CBasePlayer *pPlayer = UTIL_PlayerByIndex(i); - if (!pPlayer) + if (!UTIL_IsValidPlayer(pPlayer)) continue; if (pPlayer->m_iTeam == m_iTeam || pPlayer->m_iTeam == SPECTATOR) diff --git a/regamedll/dlls/player.h b/regamedll/dlls/player.h index ced1fe973..82e81aa6b 100644 --- a/regamedll/dlls/player.h +++ b/regamedll/dlls/player.h @@ -986,6 +986,19 @@ inline CBasePlayer *UTIL_PlayerByIndex(int playerIndex) return GET_PRIVATE(INDEXENT(playerIndex)); } +// return true if the given player is valid +inline bool UTIL_IsValidPlayer(CBaseEntity *pPlayer) +{ + return pPlayer && !FNullEnt(pPlayer->pev) && !pPlayer->IsDormant(); +} + +#else + +inline bool UTIL_IsValidPlayer(CBaseEntity *pPlayer) +{ + return pPlayer && !FNullEnt(pPlayer->pev); +} + #endif inline CBasePlayer *UTIL_PlayerByIndexSafe(int playerIndex) diff --git a/regamedll/dlls/triggers.cpp b/regamedll/dlls/triggers.cpp index 01e29bad8..82bf6907a 100644 --- a/regamedll/dlls/triggers.cpp +++ b/regamedll/dlls/triggers.cpp @@ -2029,7 +2029,7 @@ void CEscapeZone::EscapeTouch(CBaseEntity *pOther) { CBasePlayer *pPlayer = UTIL_PlayerByIndex(i); - if (!pPlayer || FNullEnt(pPlayer->pev)) + if (!UTIL_IsValidPlayer(pPlayer)) continue; if (pPlayer->m_iTeam == pEscapee->m_iTeam) diff --git a/regamedll/dlls/tutor.cpp b/regamedll/dlls/tutor.cpp index 5f2dbeca0..0f83d4812 100644 --- a/regamedll/dlls/tutor.cpp +++ b/regamedll/dlls/tutor.cpp @@ -75,7 +75,10 @@ void MonitorTutorStatus() { CBasePlayer *pPlayer = UTIL_PlayerByIndex(i); - if (pPlayer && !pPlayer->IsBot()) + if (!UTIL_IsValidPlayer(pPlayer)) + continue; + + if (!pPlayer->IsBot()) numHumans++; } diff --git a/regamedll/dlls/tutor_cs_tutor.cpp b/regamedll/dlls/tutor_cs_tutor.cpp index f8ca0aef7..18e5d2b67 100644 --- a/regamedll/dlls/tutor_cs_tutor.cpp +++ b/regamedll/dlls/tutor_cs_tutor.cpp @@ -2040,7 +2040,11 @@ void CCSTutor::GetNumPlayersAliveOnTeams(int &numT, int &numCT) for (int i = 1; i <= gpGlobals->maxClients; i++) { CBasePlayer *pPlayer = UTIL_PlayerByIndex(i); - if (!pPlayer || !pPlayer->IsAlive()) + + if (!UTIL_IsValidPlayer(pPlayer)) + continue; + + if (!pPlayer->IsAlive()) continue; switch (pPlayer->m_iTeam) @@ -2132,7 +2136,11 @@ void CCSTutor::CheckForBombViewable() for (int i = 1; i <= gpGlobals->maxClients; i++) { CBasePlayer *pPlayer = UTIL_PlayerByIndex(i); - if (pPlayer && pPlayer->m_bHasC4) + + if (!UTIL_IsValidPlayer(pPlayer)) + continue; + + if (pPlayer->m_bHasC4) { pBombCarrier = pPlayer; break; @@ -2819,7 +2827,7 @@ void CCSTutor::ConstructRecentDeathsList(TeamName team, char *buf, int buflen, T { CBasePlayer *pPlayer = UTIL_PlayerByIndex(i); - if (!pPlayer) + if (!UTIL_IsValidPlayer(pPlayer)) continue; // ignore alive players diff --git a/regamedll/dlls/util.cpp b/regamedll/dlls/util.cpp index 7c3c3db6c..7841b006b 100644 --- a/regamedll/dlls/util.cpp +++ b/regamedll/dlls/util.cpp @@ -506,7 +506,11 @@ void UTIL_ScreenShake(const Vector ¢er, float amplitude, float frequency, fl for (i = 1; i <= gpGlobals->maxClients; i++) { CBaseEntity *pPlayer = UTIL_PlayerByIndex(i); - if (!pPlayer || !(pPlayer->pev->flags & FL_ONGROUND)) + + if (!UTIL_IsValidPlayer(pPlayer)) + continue; + + if (!(pPlayer->pev->flags & FL_ONGROUND)) continue; localAmplitude = 0; @@ -552,7 +556,10 @@ void UTIL_ScreenFadeBuild(ScreenFade &fade, const Vector &color, float fadeTime, void UTIL_ScreenFadeWrite(const ScreenFade &fade, CBaseEntity *pEntity) { - if (!pEntity || !pEntity->IsNetClient()) + if (!UTIL_IsValidPlayer(pEntity)) + return; + + if (!pEntity->IsNetClient()) return; MESSAGE_BEGIN(MSG_ONE, gmsgFade, nullptr, pEntity->edict()); @@ -634,10 +641,11 @@ void UTIL_HudMessageAll(const hudtextparms_t &textparms, const char *pMessage) for (int i = 1; i <= gpGlobals->maxClients; i++) { CBaseEntity *pPlayer = UTIL_PlayerByIndex(i); - if (pPlayer) - { - UTIL_HudMessage(pPlayer, textparms, pMessage); - } + + if (!UTIL_IsValidPlayer(pPlayer)) + continue; + + UTIL_HudMessage(pPlayer, textparms, pMessage); } } @@ -843,8 +851,11 @@ void UTIL_ShowMessageAll(const char *pString, bool isHint) for (int i = 1; i <= gpGlobals->maxClients; i++) { CBaseEntity *pPlayer = UTIL_PlayerByIndex(i); - if (pPlayer) - UTIL_ShowMessage(pString, pPlayer, isHint); + + if (!UTIL_IsValidPlayer(pPlayer)) + continue; + + UTIL_ShowMessage(pString, pPlayer, isHint); } } @@ -1749,10 +1760,11 @@ int UTIL_GetNumPlayers() for (int i = 1; i <= gpGlobals->maxClients; i++) { CBasePlayer *pPlayer = UTIL_PlayerByIndex(i); - if (pPlayer) - { - nNumPlayers++; - } + + if (!UTIL_IsValidPlayer(pPlayer)) + continue; + + nNumPlayers++; } return nNumPlayers; @@ -1837,7 +1849,10 @@ int UTIL_CountPlayersInBrushVolume(bool bOnlyAlive, CBaseEntity *pBrushEntity, i { CBasePlayer *pPlayer = UTIL_PlayerByIndex(i); - if (!pPlayer || !pPlayer->IsInWorld()) + if (!UTIL_IsValidPlayer(pPlayer)) + continue; + + if (!pPlayer->IsInWorld()) continue; if (bOnlyAlive && !pPlayer->IsAlive()) diff --git a/regamedll/dlls/weapons.cpp b/regamedll/dlls/weapons.cpp index 4ea949128..a472e0030 100644 --- a/regamedll/dlls/weapons.cpp +++ b/regamedll/dlls/weapons.cpp @@ -1460,9 +1460,12 @@ void CBasePlayerWeapon::ReloadSound() CBasePlayer *pPlayer = nullptr; while ((pPlayer = UTIL_FindEntityByClassname(pPlayer, "player"))) { - if (pPlayer->IsDormant()) + if (FNullEnt(pPlayer->edict())) break; + if (pPlayer->IsDormant()) + continue; + if (pPlayer == m_pPlayer) continue; @@ -2043,7 +2046,7 @@ void CWeaponBox::Touch(CBaseEntity *pOther) if (!pEntity->IsPlayer()) continue; - if (pEntity->pev->flags == FL_DORMANT) + if (pEntity->IsDormant()) continue; CBasePlayer *pTempPlayer = GetClassPtr((CBasePlayer *)pEntity->pev); diff --git a/regamedll/game_shared/bot/bot.cpp b/regamedll/game_shared/bot/bot.cpp index 33f0d8083..f53266bdb 100644 --- a/regamedll/game_shared/bot/bot.cpp +++ b/regamedll/game_shared/bot/bot.cpp @@ -352,10 +352,8 @@ int CBot::GetEnemiesRemaining() const for (int i = 1; i <= gpGlobals->maxClients; i++) { CBaseEntity *pPlayer = UTIL_PlayerByIndex(i); - if (!pPlayer) - continue; - if (FNullEnt(pPlayer->pev)) + if (!UTIL_IsValidPlayer(pPlayer)) continue; if (FStrEq(STRING(pPlayer->pev->netname), "")) @@ -380,10 +378,8 @@ int CBot::GetFriendsRemaining() const for (int i = 1; i <= gpGlobals->maxClients; i++) { CBaseEntity *pPlayer = UTIL_PlayerByIndex(i); - if (!pPlayer) - continue; - if (FNullEnt(pPlayer->pev)) + if (!UTIL_IsValidPlayer(pPlayer)) continue; if (FStrEq(STRING(pPlayer->pev->netname), "")) diff --git a/regamedll/game_shared/bot/bot_manager.cpp b/regamedll/game_shared/bot/bot_manager.cpp index 7e10f1bcc..7ccd32814 100644 --- a/regamedll/game_shared/bot/bot_manager.cpp +++ b/regamedll/game_shared/bot/bot_manager.cpp @@ -196,7 +196,7 @@ void CBotManager::StartFrame() { CBasePlayer *pPlayer = UTIL_PlayerByIndex(i); - if (!pPlayer) + if (!UTIL_IsValidPlayer(pPlayer)) continue; if (pPlayer->IsBot() && IsEntityValid(pPlayer)) @@ -229,10 +229,7 @@ void CBotManager::__API_HOOK(OnEvent)(GameEventType event, CBaseEntity* pEntity, { CBasePlayer *pPlayer = UTIL_PlayerByIndex(i); - if (!pPlayer) - continue; - - if (FNullEnt(pPlayer->pev)) + if (!UTIL_IsValidPlayer(pPlayer)) continue; if (FStrEq(STRING(pPlayer->pev->netname), "")) diff --git a/regamedll/game_shared/bot/bot_util.cpp b/regamedll/game_shared/bot/bot_util.cpp index 9b5e2013e..449b7e373 100644 --- a/regamedll/game_shared/bot/bot_util.cpp +++ b/regamedll/game_shared/bot/bot_util.cpp @@ -11,10 +11,7 @@ bool UTIL_IsNameTaken(const char *name, bool ignoreHumans) { CBasePlayer *pPlayer = UTIL_PlayerByIndex(i); - if (!pPlayer) - continue; - - if (FNullEnt(pPlayer->pev)) + if (!UTIL_IsValidPlayer(pPlayer)) continue; if (FStrEq(STRING(pPlayer->pev->netname), "")) @@ -46,14 +43,12 @@ bool UTIL_IsNameTaken(const char *name, bool ignoreHumans) int UTIL_ClientsInGame() { int iCount = 0; + for (int iIndex = 1; iIndex <= gpGlobals->maxClients; iIndex++) { CBaseEntity *pPlayer = UTIL_PlayerByIndex(iIndex); - if (!pPlayer) - continue; - - if (FNullEnt(pPlayer->pev)) + if (!UTIL_IsValidPlayer(pPlayer)) continue; if (FStrEq(STRING(pPlayer->pev->netname), "")) @@ -68,14 +63,12 @@ int UTIL_ClientsInGame() int UTIL_ActivePlayersInGame() { int iCount = 0; + for (int iIndex = 1; iIndex <= gpGlobals->maxClients; iIndex++) { CBasePlayer *pPlayer = UTIL_PlayerByIndex(iIndex); - if (!pPlayer) - continue; - - if (FNullEnt(pPlayer->pev)) + if (!UTIL_IsValidPlayer(pPlayer)) continue; if (FStrEq(STRING(pPlayer->pev->netname), "")) @@ -102,10 +95,7 @@ int UTIL_HumansInGame(bool ignoreSpectators) { CBasePlayer *pPlayer = UTIL_PlayerByIndex(iIndex); - if (!pPlayer) - continue; - - if (FNullEnt(pPlayer->pev)) + if (!UTIL_IsValidPlayer(pPlayer)) continue; if (FStrEq(STRING(pPlayer->pev->netname), "")) @@ -138,12 +128,9 @@ int UTIL_SpectatorsInGame() for (int iIndex = 1; iIndex <= gpGlobals->maxClients; iIndex++) { - CBasePlayer* pPlayer = UTIL_PlayerByIndex(iIndex); - - if (!pPlayer) - continue; + CBasePlayer *pPlayer = UTIL_PlayerByIndex(iIndex); - if (FNullEnt(pPlayer->pev)) + if (!UTIL_IsValidPlayer(pPlayer)) continue; if (FStrEq(STRING(pPlayer->pev->netname), "")) @@ -167,14 +154,12 @@ int UTIL_SpectatorsInGame() int UTIL_HumansOnTeam(int teamID, bool isAlive) { int iCount = 0; + for (int iIndex = 1; iIndex <= gpGlobals->maxClients; iIndex++) { CBasePlayer *pPlayer = UTIL_PlayerByIndex(iIndex); - if (!pPlayer) - continue; - - if (FNullEnt(pPlayer->pev)) + if (!UTIL_IsValidPlayer(pPlayer)) continue; if (FStrEq(STRING(pPlayer->pev->netname), "")) @@ -203,10 +188,7 @@ int UTIL_BotsInGame() { CBasePlayer *pPlayer = UTIL_PlayerByIndex(iIndex); - if (!pPlayer) - continue; - - if (FNullEnt(pPlayer->pev)) + if (!UTIL_IsValidPlayer(pPlayer)) continue; if (FStrEq(STRING(pPlayer->pev->netname), "")) @@ -230,10 +212,7 @@ bool UTIL_KickBotFromTeam(TeamName kickTeam) { CBasePlayer *pPlayer = UTIL_PlayerByIndex(i); - if (!pPlayer) - continue; - - if (FNullEnt(pPlayer->pev)) + if (!UTIL_IsValidPlayer(pPlayer)) continue; const char *name = STRING(pPlayer->pev->netname); @@ -256,10 +235,7 @@ bool UTIL_KickBotFromTeam(TeamName kickTeam) { CBasePlayer *pPlayer = UTIL_PlayerByIndex(i); - if (!pPlayer) - continue; - - if (FNullEnt(pPlayer->pev)) + if (!UTIL_IsValidPlayer(pPlayer)) continue; const char *name = STRING(pPlayer->pev->netname); @@ -283,19 +259,17 @@ bool UTIL_KickBotFromTeam(TeamName kickTeam) bool UTIL_IsTeamAllBots(int team) { int botCount = 0; + for (int i = 1; i <= gpGlobals->maxClients; i++) { CBasePlayer *pPlayer = UTIL_PlayerByIndex(i); - if (!pPlayer) + if (!UTIL_IsValidPlayer(pPlayer)) continue; if (pPlayer->m_iTeam != team) continue; - if (FNullEnt(pPlayer->pev)) - continue; - if (FStrEq(STRING(pPlayer->pev->netname), "")) continue; @@ -403,10 +377,7 @@ bool UTIL_IsVisibleToTeam(const Vector &spot, int team, float maxRange) { CBasePlayer *pPlayer = UTIL_PlayerByIndex(i); - if (!pPlayer) - continue; - - if (FNullEnt(pPlayer->pev)) + if (!UTIL_IsValidPlayer(pPlayer)) continue; if (FStrEq(STRING(pPlayer->pev->netname), "")) @@ -443,10 +414,7 @@ CBasePlayer *UTIL_GetLocalPlayer() { CBasePlayer *pPlayer = UTIL_PlayerByIndex(iIndex); - if (!pPlayer) - continue; - - if (FNullEnt(pPlayer->pev)) + if (!UTIL_IsValidPlayer(pPlayer)) continue; if (FStrEq(STRING(pPlayer->pev->netname), "")) From c7be8bfe7cbf2e4b09416af12e8a74887919d5f2 Mon Sep 17 00:00:00 2001 From: s1lentq Date: Fri, 31 May 2024 23:21:37 +0700 Subject: [PATCH 22/50] MULTIDAMAGE: make check entity safe ApplyMultiDamage: Fixed potential crash when in TakeDamage hook causes another additional damage --- regamedll/dlls/weapons.cpp | 18 +++++++++++------- regamedll/dlls/weapons.h | 2 +- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/regamedll/dlls/weapons.cpp b/regamedll/dlls/weapons.cpp index a472e0030..454b96f04 100644 --- a/regamedll/dlls/weapons.cpp +++ b/regamedll/dlls/weapons.cpp @@ -79,7 +79,7 @@ LINK_HOOK_VOID_CHAIN2(ClearMultiDamage) // Resets the global multi damage accumulator void EXT_FUNC __API_HOOK(ClearMultiDamage)() { - gMultiDamage.pEntity = nullptr; + gMultiDamage.hEntity = nullptr; gMultiDamage.amount = 0; gMultiDamage.type = 0; } @@ -89,11 +89,15 @@ LINK_HOOK_VOID_CHAIN(ApplyMultiDamage, (entvars_t *pevInflictor, entvars_t *pevA // Inflicts contents of global multi damage register on gMultiDamage.pEntity void EXT_FUNC __API_HOOK(ApplyMultiDamage)(entvars_t *pevInflictor, entvars_t *pevAttacker) { - if (!gMultiDamage.pEntity) + EntityHandle hEnt = gMultiDamage.hEntity; + if (!hEnt) return; - gMultiDamage.pEntity->TakeDamage(pevInflictor, pevAttacker, gMultiDamage.amount, gMultiDamage.type); - gMultiDamage.pEntity->ResetDmgPenetrationLevel(); + hEnt->TakeDamage(pevInflictor, pevAttacker, gMultiDamage.amount, gMultiDamage.type); + + // check again, the entity may be removed after taking damage + if (hEnt) + hEnt->ResetDmgPenetrationLevel(); } LINK_HOOK_VOID_CHAIN(AddMultiDamage, (entvars_t *pevInflictor, CBaseEntity *pEntity, float flDamage, int bitsDamageType), pevInflictor, pEntity, flDamage, bitsDamageType) @@ -105,17 +109,17 @@ void EXT_FUNC __API_HOOK(AddMultiDamage)(entvars_t *pevInflictor, CBaseEntity *p gMultiDamage.type |= bitsDamageType; - if (pEntity != gMultiDamage.pEntity) + if (pEntity != gMultiDamage.hEntity) { #ifdef REGAMEDLL_FIXES - if (gMultiDamage.pEntity) // avoid api calls with null default pEntity + if (gMultiDamage.hEntity) // avoid api calls with null default pEntity #endif { // UNDONE: wrong attacker! ApplyMultiDamage(pevInflictor, pevInflictor); } - gMultiDamage.pEntity = pEntity; + gMultiDamage.hEntity = pEntity; gMultiDamage.amount = 0; } diff --git a/regamedll/dlls/weapons.h b/regamedll/dlls/weapons.h index c9cd58c36..9266d8434 100644 --- a/regamedll/dlls/weapons.h +++ b/regamedll/dlls/weapons.h @@ -133,7 +133,7 @@ struct AmmoInfo struct MULTIDAMAGE { - CBaseEntity *pEntity; + EntityHandle hEntity; float amount; int type; }; From bdc96d26d9cf6c745b1362ea11497fea1499d1ef Mon Sep 17 00:00:00 2001 From: s1lentq Date: Sat, 1 Jun 2024 20:24:44 +0700 Subject: [PATCH 23/50] Minor rework bot_quota_mode fill --- regamedll/dlls/bot/cs_bot_manager.cpp | 43 ++++++++++++++++++++------- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/regamedll/dlls/bot/cs_bot_manager.cpp b/regamedll/dlls/bot/cs_bot_manager.cpp index fc1d50c88..7e9238857 100644 --- a/regamedll/dlls/bot/cs_bot_manager.cpp +++ b/regamedll/dlls/bot/cs_bot_manager.cpp @@ -424,6 +424,12 @@ void CCSBotManager::ServerCommand(const char *pcmd) else kickThemAll = false; +#ifdef REGAMEDLL_ADD + bool fillMode = FStrEq(cv_bot_quota_mode.string, "fill"); +#else + bool fillMode = false; +#endif + for (int i = 1; i <= gpGlobals->maxClients; i++) { CBasePlayer *pPlayer = UTIL_PlayerByIndex(i); @@ -443,7 +449,11 @@ void CCSBotManager::ServerCommand(const char *pcmd) // adjust bot quota so kicked bot is not immediately added back in int newQuota = cv_bot_quota.value - 1; SERVER_COMMAND(UTIL_VarArgs("kick \"%s\"\n", name)); - CVAR_SET_FLOAT("bot_quota", clamp(newQuota, 0, int(cv_bot_quota.value))); + + if (kickThemAll || !fillMode) + { + CVAR_SET_FLOAT("bot_quota", clamp(newQuota, 0, int(cv_bot_quota.value))); + } } } } @@ -790,7 +800,8 @@ bool CCSBotManager::BotAddCommand(BotProfileTeamType team, bool isFromConsole) // decrease the bot quota if (!isFromConsole) { - CVAR_SET_FLOAT("bot_quota", cv_bot_quota.value - 1); + int newQuota = cv_bot_quota.value - 1; + CVAR_SET_FLOAT("bot_quota", clamp(newQuota, 0, (int)cv_bot_quota.value)); } #endif @@ -824,7 +835,8 @@ bool CCSBotManager::BotAddCommand(BotProfileTeamType team, bool isFromConsole) if (isFromConsole) { // increase the bot quota to account for manually added bot - CVAR_SET_FLOAT("bot_quota", cv_bot_quota.value + 1); + int newQuota = cv_bot_quota.value + 1; + CVAR_SET_FLOAT("bot_quota", clamp(newQuota, 0, gpGlobals->maxClients)); } } #ifdef REGAMEDLL_FIXES @@ -833,7 +845,8 @@ bool CCSBotManager::BotAddCommand(BotProfileTeamType team, bool isFromConsole) // decrease the bot quota if (!isFromConsole) { - CVAR_SET_FLOAT("bot_quota", cv_bot_quota.value - 1); + int newQuota = cv_bot_quota.value - 1; + CVAR_SET_FLOAT("bot_quota", clamp(newQuota, 0, (int)cv_bot_quota.value)); } } #endif @@ -863,10 +876,17 @@ void CCSBotManager::MaintainBotQuota() int desiredBotCount = int(cv_bot_quota.value); int occupiedBotSlots = UTIL_BotsInGame(); + bool isRoundInDeathmatch = false; + +#ifdef REGAMEDLL_ADD + if (round_infinite.value > 0) + isRoundInDeathmatch = true; // is no round end gameplay +#endif + // isRoundInProgress is true if the round has progressed far enough that new players will join as dead. bool isRoundInProgress = CSGameRules()->IsGameStarted() && !TheCSBots()->IsRoundOver() && - (CSGameRules()->GetRoundElapsedTime() >= CSGameRules()->GetRoundRespawnTime()); + (CSGameRules()->GetRoundRespawnTime() != -1 && CSGameRules()->GetRoundElapsedTime() >= CSGameRules()->GetRoundRespawnTime()) && !isRoundInDeathmatch; #ifdef REGAMEDLL_ADD if (FStrEq(cv_bot_quota_mode.string, "fill")) @@ -875,7 +895,7 @@ void CCSBotManager::MaintainBotQuota() // unless the round is already in progress, in which case we play with what we've been dealt if (!isRoundInProgress) { - desiredBotCount = Q_max(0, desiredBotCount - humanPlayersInGame + spectatorPlayersInGame); + desiredBotCount = Q_max(0, desiredBotCount - humanPlayersInGame); } else { @@ -922,13 +942,15 @@ void CCSBotManager::MaintainBotQuota() if (cv_bot_auto_vacate.value > 0.0) desiredBotCount = Q_min(desiredBotCount, gpGlobals->maxClients - (humanPlayersInGame + 1)); else - desiredBotCount = Q_min(desiredBotCount, gpGlobals->maxClients - humanPlayersInGame + spectatorPlayersInGame); + desiredBotCount = Q_min(desiredBotCount, gpGlobals->maxClients - humanPlayersInGame); #ifdef REGAMEDLL_FIXES // Try to balance teams, if we are in the first specified seconds of a round and bots can join either team. - if (occupiedBotSlots > 0 && desiredBotCount == occupiedBotSlots && CSGameRules()->IsGameStarted()) + if (occupiedBotSlots > 0 && desiredBotCount == occupiedBotSlots && (CSGameRules()->IsGameStarted() || isRoundInDeathmatch)) { - if (CSGameRules()->GetRoundElapsedTime() < CSGameRules()->GetRoundRespawnTime()) // new bots can still spawn during this time + if (isRoundInDeathmatch || + (CSGameRules()->GetRoundRespawnTime() == -1 || // means no time limit + CSGameRules()->GetRoundElapsedTime() < CSGameRules()->GetRoundRespawnTime())) // new bots can still spawn during this time { if (autoteambalance.value > 0.0f) { @@ -1045,7 +1067,8 @@ void CCSBotManager::MaintainBotQuota() UTIL_KickBotFromTeam(TERRORIST); } - CVAR_SET_FLOAT("bot_quota", cv_bot_quota.value - 1.0f); + int newQuota = cv_bot_quota.value - 1; + CVAR_SET_FLOAT("bot_quota", clamp(newQuota, 0, (int)cv_bot_quota.value)); } } From fef9bf3a871c8fec30acffef3ca0f4a380f4f928 Mon Sep 17 00:00:00 2001 From: s1lentq Date: Sat, 1 Jun 2024 20:26:00 +0700 Subject: [PATCH 24/50] mp_defuser_allocation: Send a message with a hint instead of the text in the center --- regamedll/dlls/multiplay_gamerules.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/regamedll/dlls/multiplay_gamerules.cpp b/regamedll/dlls/multiplay_gamerules.cpp index 7651527be..7caa946b8 100644 --- a/regamedll/dlls/multiplay_gamerules.cpp +++ b/regamedll/dlls/multiplay_gamerules.cpp @@ -5464,9 +5464,9 @@ void CHalfLifeMultiplay::GiveDefuserToRandomPlayer() for (int i = 0; i < iDefusersToGive && i < candidates.Count(); ++i) { CBasePlayer *pPlayer = candidates[i]; - assert(pPlayer && pPlayer->m_iTeam == CT && pPlayer->IsAlive()); + DbgAssert(pPlayer && pPlayer->m_iTeam == CT && pPlayer->IsAlive()); pPlayer->GiveDefuser(); - ClientPrint(pPlayer->pev, HUD_PRINTCENTER, "#Got_defuser"); + pPlayer->HintMessage("#Got_defuser", FALSE, TRUE); } } From ad1c58cef5284ba1694dea2c0d5c7f5bf0dfa910 Mon Sep 17 00:00:00 2001 From: s1lentq Date: Mon, 3 Jun 2024 01:28:45 +0700 Subject: [PATCH 25/50] Add bot_mimic --- regamedll/dlls/bot/cs_bot_init.cpp | 4 ++ regamedll/dlls/bot/cs_bot_init.h | 2 + regamedll/dlls/bot/cs_bot_manager.cpp | 17 +++++++ regamedll/dlls/bot/cs_bot_statemachine.cpp | 6 +++ regamedll/dlls/bot/cs_bot_vision.cpp | 6 +++ regamedll/dlls/player.cpp | 9 ++++ regamedll/dlls/player.h | 1 + regamedll/game_shared/bot/bot.cpp | 56 +++++++++++++++++++++- regamedll/game_shared/bot/bot.h | 2 + regamedll/game_shared/bot/bot_util.h | 2 +- regamedll/pm_shared/pm_shared.cpp | 5 ++ regamedll/public/regamedll/API/CSPlayer.h | 11 +++++ 12 files changed, 119 insertions(+), 2 deletions(-) diff --git a/regamedll/dlls/bot/cs_bot_init.cpp b/regamedll/dlls/bot/cs_bot_init.cpp index 4ed27ac55..a6503e901 100644 --- a/regamedll/dlls/bot/cs_bot_init.cpp +++ b/regamedll/dlls/bot/cs_bot_init.cpp @@ -62,6 +62,8 @@ cvar_t cv_bot_deathmatch = { "bot_deathmatch", "0", FCVAR_SERVER, 0. cvar_t cv_bot_quota_mode = { "bot_quota_mode", "normal", FCVAR_SERVER, 0.0f, nullptr }; cvar_t cv_bot_join_delay = { "bot_join_delay", "0", FCVAR_SERVER, 0.0f, nullptr }; cvar_t cv_bot_freeze = { "bot_freeze", "0", 0, 0.0f, nullptr }; +cvar_t cv_bot_mimic = { "bot_mimic", "0", 0, 0.0f, nullptr }; +cvar_t cv_bot_mimic_yaw_offset = { "bot_mimic_yaw_offset", "0", 0, 0.0f, nullptr }; #else // Migrated to bot_quota_mode, use "match" cvar_t cv_bot_quota_match = { "bot_quota_match", "0", FCVAR_SERVER, 0.0f, nullptr }; @@ -131,6 +133,8 @@ void Bot_RegisterCVars() CVAR_REGISTER(&cv_bot_quota_mode); CVAR_REGISTER(&cv_bot_join_delay); CVAR_REGISTER(&cv_bot_freeze); + CVAR_REGISTER(&cv_bot_mimic); + CVAR_REGISTER(&cv_bot_mimic_yaw_offset); #endif } diff --git a/regamedll/dlls/bot/cs_bot_init.h b/regamedll/dlls/bot/cs_bot_init.h index 4f62b511b..8687e3f7f 100644 --- a/regamedll/dlls/bot/cs_bot_init.h +++ b/regamedll/dlls/bot/cs_bot_init.h @@ -62,6 +62,8 @@ extern cvar_t cv_bot_deathmatch; extern cvar_t cv_bot_quota_mode; extern cvar_t cv_bot_join_delay; extern cvar_t cv_bot_freeze; +extern cvar_t cv_bot_mimic; +extern cvar_t cv_bot_mimic_yaw_offset; #else extern cvar_t cv_bot_quota_match; #endif diff --git a/regamedll/dlls/bot/cs_bot_manager.cpp b/regamedll/dlls/bot/cs_bot_manager.cpp index 7e9238857..d87595601 100644 --- a/regamedll/dlls/bot/cs_bot_manager.cpp +++ b/regamedll/dlls/bot/cs_bot_manager.cpp @@ -758,6 +758,23 @@ void CCSBotManager::ServerCommand(const char *pcmd) BOOL CCSBotManager::ClientCommand(CBasePlayer *pPlayer, const char *pcmd) { +#ifdef REGAMEDLL_ADD + if (pPlayer->IsBot()) + return FALSE; + + if (cv_bot_mimic.value == pPlayer->entindex()) + { + // Bots mimic our client commands + ForEachPlayer([pPlayer, pcmd](CBasePlayer *bot) + { + if (pPlayer != bot && bot->IsBot()) + bot->ClientCommand(pcmd, CMD_ARGV_(1)); + + return true; + }); + } +#endif + return FALSE; } diff --git a/regamedll/dlls/bot/cs_bot_statemachine.cpp b/regamedll/dlls/bot/cs_bot_statemachine.cpp index 325252641..7bb31eddc 100644 --- a/regamedll/dlls/bot/cs_bot_statemachine.cpp +++ b/regamedll/dlls/bot/cs_bot_statemachine.cpp @@ -299,6 +299,12 @@ void CCSBot::Attack(CBasePlayer *victim) if (cv_bot_zombie.value != 0.0f) return; +#ifdef REGAMEDLL_ADD + // If mimicing the player, don't attack state + if (cv_bot_mimic.value) + return; +#endif + // cannot attack if we are reloading if (IsActiveWeaponReloading()) return; diff --git a/regamedll/dlls/bot/cs_bot_vision.cpp b/regamedll/dlls/bot/cs_bot_vision.cpp index db9e2143c..f7b16acc5 100644 --- a/regamedll/dlls/bot/cs_bot_vision.cpp +++ b/regamedll/dlls/bot/cs_bot_vision.cpp @@ -61,6 +61,12 @@ void CCSBot::UpdateLookAngles() float stiffness; float damping; +#ifdef REGAMEDLL_ADD + // If mimicing the player, don't modify the view angles + if (cv_bot_mimic.value > 0) + return; +#endif + // springs are stiffer when attacking, so we can track and move between targets better if (IsAttacking()) { diff --git a/regamedll/dlls/player.cpp b/regamedll/dlls/player.cpp index 52eaeaea6..38605ebaa 100644 --- a/regamedll/dlls/player.cpp +++ b/regamedll/dlls/player.cpp @@ -10717,3 +10717,12 @@ bool CBasePlayer::Kill() return true; } + +const usercmd_t *CBasePlayer::GetLastUserCommand() const +{ +#ifdef REGAMEDLL_API + return CSPlayer()->GetLastUserCommand(); +#else + return nullptr; +#endif +} diff --git a/regamedll/dlls/player.h b/regamedll/dlls/player.h index 82e81aa6b..d3d37d3ea 100644 --- a/regamedll/dlls/player.h +++ b/regamedll/dlls/player.h @@ -500,6 +500,7 @@ class CBasePlayer: public CBaseMonster { void SetClientUserInfoModel(char *infobuffer, char *szNewModel); void SetClientUserInfoModel_api(char *infobuffer, char *szNewModel); void SetNewPlayerModel(const char *modelName); + const usercmd_t *GetLastUserCommand() const; BOOL SwitchWeapon(CBasePlayerItem *pWeapon); void CheckPowerups(); bool CanAffordPrimary(); diff --git a/regamedll/game_shared/bot/bot.cpp b/regamedll/game_shared/bot/bot.cpp index f53266bdb..0a9ae70a2 100644 --- a/regamedll/game_shared/bot/bot.cpp +++ b/regamedll/game_shared/bot/bot.cpp @@ -261,6 +261,18 @@ void CBot::ExecuteCommand() // Adjust msec to command time interval adjustedMSec = ThrottledMsec(); + // Run mimic command + usercmd_t botCmd; + if (!RunMimicCommand(botCmd)) + { + botCmd.forwardmove = m_forwardSpeed; + botCmd.sidemove = m_strafeSpeed; + botCmd.upmove = m_verticalSpeed; + botCmd.buttons = m_buttonFlags; + botCmd.impulse = 0; + botCmd.viewangles = pev->v_angle; + } + // player model is "munged" pev->angles = pev->v_angle; pev->angles.x /= -3.0f; @@ -283,7 +295,49 @@ void CBot::ExecuteCommand() #endif // Run the command - PLAYER_RUN_MOVE(edict(), pev->v_angle, m_forwardSpeed, m_strafeSpeed, m_verticalSpeed, m_buttonFlags, 0, adjustedMSec); + PLAYER_RUN_MOVE(edict(), botCmd.viewangles, botCmd.forwardmove, botCmd.sidemove, botCmd.upmove, botCmd.buttons, 0, adjustedMSec); +} + +bool CBot::RunMimicCommand(usercmd_t &botCmd) +{ +#ifdef REGAMEDLL_ADD + if (cv_bot_mimic.value <= 0) + return false; + + if (cv_bot_mimic.value > gpGlobals->maxClients) + return false; + + CBasePlayer *pPlayer = UTIL_PlayerByIndex(cv_bot_mimic.value); + if (!pPlayer) + return false; + + if (!UTIL_IsValidPlayer(pPlayer)) + return false; + + if (!pPlayer->IsAlive()) + return false; + + if (pPlayer->IsBot()) + return false; + + const usercmd_t *ucmd = pPlayer->GetLastUserCommand(); + if (!ucmd) + return false; + + botCmd = *ucmd; + botCmd.viewangles[YAW] += cv_bot_mimic_yaw_offset.value; + + float mult = 8.0f; + botCmd.forwardmove *= mult; + botCmd.sidemove *= mult; + botCmd.upmove *= mult; + + pev->fixangle = 0; + + return true; +#else + return false; +#endif } void CBot::ResetCommand() diff --git a/regamedll/game_shared/bot/bot.h b/regamedll/game_shared/bot/bot.h index 6ad3cda11..17cec5a98 100644 --- a/regamedll/game_shared/bot/bot.h +++ b/regamedll/game_shared/bot/bot.h @@ -259,6 +259,8 @@ class CBot: public CBasePlayer void ResetCommand(); byte ThrottledMsec() const; + bool RunMimicCommand(usercmd_t &botCmd); + // returns current movement speed (for walk/run) float GetMoveSpeed(); diff --git a/regamedll/game_shared/bot/bot_util.h b/regamedll/game_shared/bot/bot_util.h index e7fbd71b0..8dfa352de 100644 --- a/regamedll/game_shared/bot/bot_util.h +++ b/regamedll/game_shared/bot/bot_util.h @@ -141,7 +141,7 @@ inline bool IsIntersecting2D(const Vector &startA, const Vector &endA, const Vec // Iterate over all active players in the game, invoking functor on each. // If functor returns false, stop iteration and return false. template -bool ForEachPlayer(Functor &func) +bool ForEachPlayer(Functor func) { for (int i = 1; i <= gpGlobals->maxClients; i++) { diff --git a/regamedll/pm_shared/pm_shared.cpp b/regamedll/pm_shared/pm_shared.cpp index ed6819769..b40495569 100644 --- a/regamedll/pm_shared/pm_shared.cpp +++ b/regamedll/pm_shared/pm_shared.cpp @@ -3300,6 +3300,11 @@ void EXT_FUNC __API_HOOK(PM_Move)(struct playermove_s *ppmove, int server) { pmove->friction = 1.0f; } + +#ifdef REGAMEDLL_API + // save the last usercmd + pmoveplayer->SetLastUserCommand(pmove->cmd); +#endif } NOXREF int PM_GetVisEntInfo(int ent) diff --git a/regamedll/public/regamedll/API/CSPlayer.h b/regamedll/public/regamedll/API/CSPlayer.h index 0b8672332..f2bfe028b 100644 --- a/regamedll/public/regamedll/API/CSPlayer.h +++ b/regamedll/public/regamedll/API/CSPlayer.h @@ -141,6 +141,16 @@ class CCSPlayer: public CCSMonster { EProtectionState GetProtectionState() const; bool CheckActivityInGame(); + const usercmd_t *GetLastUserCommand() const + { + return &m_LastCmd; + } + + void SetLastUserCommand(const usercmd_t &ucmd) + { + m_LastCmd = ucmd; + } + public: char m_szModel[32]; bool m_bForceShowMenu; @@ -175,6 +185,7 @@ class CCSPlayer: public CCSMonster { bool m_bPlayerDominated[MAX_CLIENTS]; // [0-31] array of state per other player whether player is dominating other players int m_iGibDamageThreshold; // negative health to reach to gib player + usercmd_t m_LastCmd; }; // Inlines From a94c7bd728a2b2b80b016ee86f3310459e872e0d Mon Sep 17 00:00:00 2001 From: s1lentq Date: Sun, 9 Jun 2024 15:46:50 +0700 Subject: [PATCH 26/50] Fix crouch bot --- regamedll/game_shared/bot/bot.cpp | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/regamedll/game_shared/bot/bot.cpp b/regamedll/game_shared/bot/bot.cpp index 0a9ae70a2..0f19e81f4 100644 --- a/regamedll/game_shared/bot/bot.cpp +++ b/regamedll/game_shared/bot/bot.cpp @@ -261,6 +261,20 @@ void CBot::ExecuteCommand() // Adjust msec to command time interval adjustedMSec = ThrottledMsec(); + if (IsCrouching()) + { + m_buttonFlags |= IN_DUCK; + } + +#ifdef REGAMEDLL_FIXES + // don't move if frozen state present + if (pev->flags & FL_FROZEN) + { + adjustedMSec = 0; + ResetCommand(); + } +#endif + // Run mimic command usercmd_t botCmd; if (!RunMimicCommand(botCmd)) @@ -280,20 +294,6 @@ void CBot::ExecuteCommand() // save the command time m_flPreviousCommandTime = gpGlobals->time; - if (IsCrouching()) - { - m_buttonFlags |= IN_DUCK; - } - -#ifdef REGAMEDLL_FIXES - // don't move if frozen state present - if (pev->flags & FL_FROZEN) - { - adjustedMSec = 0; - ResetCommand(); - } -#endif - // Run the command PLAYER_RUN_MOVE(edict(), botCmd.viewangles, botCmd.forwardmove, botCmd.sidemove, botCmd.upmove, botCmd.buttons, 0, adjustedMSec); } From 8005dd9ca3bc1724b9cc7bcc8db2360d84479e0b Mon Sep 17 00:00:00 2001 From: Vaqtincha <51029683+Vaqtincha@users.noreply.github.com> Date: Mon, 8 Jul 2024 22:53:21 +0500 Subject: [PATCH 27/50] Fix bot_kill command (killed already dead bots) (#974) --- regamedll/dlls/bot/cs_bot_manager.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/regamedll/dlls/bot/cs_bot_manager.cpp b/regamedll/dlls/bot/cs_bot_manager.cpp index d87595601..b4391be7f 100644 --- a/regamedll/dlls/bot/cs_bot_manager.cpp +++ b/regamedll/dlls/bot/cs_bot_manager.cpp @@ -408,6 +408,11 @@ void CCSBotManager::ServerCommand(const char *pcmd) if (FStrEq(name, "")) continue; +#ifdef REGAMEDLL_FIXES + if (pPlayer->pev->deadflag != DEAD_NO) + continue; +#endif + if (pPlayer->IsBot()) { CCSBot *pBot = static_cast(pPlayer); From 30572ef0b6522805e1ec00891083c1b6022c9698 Mon Sep 17 00:00:00 2001 From: s1lentq Date: Sun, 14 Jul 2024 23:27:44 +0700 Subject: [PATCH 28/50] Change compiler to Clang, instead of ICC 2019 --- .github/workflows/build.yml | 36 +++++++++++-------------------- regamedll/public/tier0/platform.h | 2 +- 2 files changed, 13 insertions(+), 25 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f8547b3b6..faf884c35 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -29,14 +29,6 @@ jobs: with: fetch-depth: 0 - - name: Setup Nuget - uses: nuget/setup-nuget@v1 - with: - nuget-api-key: ${{ secrets.NuGetAPIKey }} - nuget-version: '5.x' - - - run: nuget restore '${{ env.solution }}' - - name: Setup MSBuild uses: microsoft/setup-msbuild@v1.1.3 with: @@ -140,8 +132,7 @@ jobs: linux: name: 'Linux' - runs-on: ubuntu-20.04 - container: s1lentq/linux86buildtools:latest + runs-on: ubuntu-latest steps: - name: Checkout @@ -150,9 +141,16 @@ jobs: fetch-depth: 0 submodules: true + - name: Check dependencies + run: | + sudo dpkg --add-architecture i386 + sudo apt-get update + sudo apt-get install -y clang + sudo apt-get install -y gcc-multilib g++-multilib + - name: Build and Run unittests run: | - rm -rf build && CC=icc CXX=icpc cmake -DCMAKE_BUILD_TYPE=Unittests -B build && cmake --build build -j8 + rm -rf build && CC=clang CXX=clang++ cmake -DCMAKE_BUILD_TYPE=Unittests -B build && cmake --build build -j8 retVal=0 ./build/regamedll/cs 2> /dev/null > result.log || retVal=$? while read line; do @@ -173,17 +171,9 @@ jobs: fi shell: bash - - name: Build using Intel C++ Compiler 19.0 (only for release) - if: | - github.event_name == 'release' && - github.event.action == 'published' && - startsWith(github.ref, 'refs/tags/') - run: | - rm -rf build-icc && CC=icc CXX=icpc cmake -B build-icc && cmake --build build-icc -j8 - - - name: Build using GCC Compiler 9.3 + - name: Build using Clang C++ Compiler run: | - rm -rf build-gcc && CC=gcc CXX=g++ cmake -B build-gcc && cmake --build build-gcc -j8 + rm -rf build && CC=clang CXX=clang++ cmake -B build && cmake --build build -j8 - name: Prepare CSSDK run: | @@ -193,8 +183,7 @@ jobs: - name: Move files run: | mkdir -p publish/bin/linux32/cstrike/dlls - mv build-icc/regamedll/cs.so publish/bin/linux32/cstrike/dlls/cs.so 2>/dev/null || true - mv build-gcc/regamedll/cs.so publish/cs-gcc.so + mv build/regamedll/cs.so publish/bin/linux32/cstrike/dlls/cs.so 2>/dev/null || true mv regamedll/version/appversion.h publish/appversion.h mv dist/ publish/ @@ -202,7 +191,6 @@ jobs: run: | binaries=( "publish/bin/linux32/cstrike/dlls/cs.so" - "publish/cs-gcc.so" ) bash ./regamedll/version/glibc_test.sh ${binaries[@]} if [[ $? -ne 0 ]]; then diff --git a/regamedll/public/tier0/platform.h b/regamedll/public/tier0/platform.h index 5703cf53b..96a208dc3 100644 --- a/regamedll/public/tier0/platform.h +++ b/regamedll/public/tier0/platform.h @@ -41,7 +41,7 @@ #endif // Used to step into the debugger -#if defined(__GNUC__) && !defined(__clang__) +#if defined(__GNUC__) || defined(__clang__) #define DebuggerBreak() __asm__ __volatile__("int3;") #else #define DebuggerBreak() __asm { int 3 } From 79cfd7103a2b87a47e1c2727fa0f46e781f58648 Mon Sep 17 00:00:00 2001 From: s1lentq Date: Sun, 14 Jul 2024 23:41:13 +0700 Subject: [PATCH 29/50] Linking against legacy libc <= 2.15 --- .github/workflows/build.yml | 25 +++++++++++++------------ regamedll/CMakeLists.txt | 13 ++++++++----- regamedll/lib/linux32/libc-2.15.so | Bin 0 -> 1713640 bytes 3 files changed, 21 insertions(+), 17 deletions(-) create mode 100644 regamedll/lib/linux32/libc-2.15.so diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index faf884c35..a21453ad8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,12 +25,12 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup MSBuild - uses: microsoft/setup-msbuild@v1.1.3 + uses: microsoft/setup-msbuild@v2 with: vs-version: '16.8' @@ -58,13 +58,14 @@ jobs: move msvc\${{ env.buildRelease }}\mp.pdb publish\debug\mp.pdb - name: Deploy artifacts - uses: actions/upload-artifact@v3.1.1 + uses: actions/upload-artifact@v4 with: name: win32 path: publish/* testdemos: - name: 'Test demos' + if: false # TODO: FIXME!! + name: 'Test demos (FIXME)' runs-on: ubuntu-20.04 container: s1lentq/testdemos:latest needs: [windows] @@ -80,7 +81,7 @@ jobs: steps: - name: Deploying windows artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v2 with: name: win32 @@ -136,7 +137,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 submodules: true @@ -173,7 +174,7 @@ jobs: - name: Build using Clang C++ Compiler run: | - rm -rf build && CC=clang CXX=clang++ cmake -B build && cmake --build build -j8 + rm -rf build && CC=clang CXX=clang++ cmake -DUSE_LEGACY_LIBC=ON -B build && cmake --build build -j8 - name: Prepare CSSDK run: | @@ -199,7 +200,7 @@ jobs: shell: bash - name: Deploy artifacts - uses: actions/upload-artifact@v3.1.1 + uses: actions/upload-artifact@v4 id: upload-job with: name: linux32 @@ -213,17 +214,17 @@ jobs: publish: name: 'Publish' - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest needs: [windows, testdemos, linux] steps: - name: Deploying linux artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v6 with: name: linux32 - name: Deploying windows artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v6 with: name: win32 @@ -253,7 +254,7 @@ jobs: 7z a -tzip regamedll-bin-${{ env.APP_VERSION }}.zip bin/ cssdk/ - name: Publish artifacts - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 id: publish-job if: | startsWith(github.ref, 'refs/tags/') && diff --git a/regamedll/CMakeLists.txt b/regamedll/CMakeLists.txt index 69f710b31..ffae23827 100644 --- a/regamedll/CMakeLists.txt +++ b/regamedll/CMakeLists.txt @@ -22,6 +22,7 @@ project(regamedll CXX) option(DEBUG "Build with debug information." OFF) option(USE_STATIC_LIBSTDC "Enables static linking libstdc++." OFF) +option(USE_LEGACY_LIBC "Enables linking against legacy libc (<= 2.15) for compat with older distros (Debian 8/Ubuntu 16.04/Centos 7)." OFF) set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) @@ -32,6 +33,7 @@ set(CMAKE_SHARED_LIBRARY_LINK_CXX_FLAGS "") set(COMPILE_FLAGS "-m32 -U_FORTIFY_SOURCE") set(LINK_FLAGS "-m32 -s") +set(LINK_LIBS dl aelf32) set(COMPILE_FLAGS "${COMPILE_FLAGS} -Wall -fno-exceptions -fno-builtin -Wno-unknown-pragmas") @@ -362,12 +364,13 @@ target_sources(regamedll PRIVATE ${UNITTESTS_SRCS}> ) -target_link_libraries(regamedll PRIVATE - dl - aelf32 +if (CMAKE_BUILD_TYPE MATCHES Unittests) + list(APPEND LINK_LIBS cppunitlite) +elseif (USE_LEGACY_LIBC) + list(APPEND LINK_LIBS libc-2.15.so) +endif() - $<$:cppunitlite> -) +target_link_libraries(regamedll PRIVATE ${LINK_LIBS}) if (USE_STATIC_LIBSTDC) target_compile_definitions(regamedll PRIVATE BUILD_STATIC_LIBSTDC) diff --git a/regamedll/lib/linux32/libc-2.15.so b/regamedll/lib/linux32/libc-2.15.so new file mode 100644 index 0000000000000000000000000000000000000000..8c11121ebd9cc9a840ebb094d2e3e6ca8a6c20e8 GIT binary patch literal 1713640 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�&lObq0(DaRnf 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