mirror of
https://github.com/s1lentq/ReGameDLL_CS.git
synced 2024-12-27 07:05:38 +03:00
5485 lines
132 KiB
C++
5485 lines
132 KiB
C++
#include "precompiled.h"
|
|
|
|
CCStrikeGameMgrHelper g_GameMgrHelper;
|
|
CHalfLifeMultiplay *g_pMPGameRules = nullptr;
|
|
RewardAccount CHalfLifeMultiplay::m_rgRewardAccountRules[RR_END];
|
|
RewardAccount CHalfLifeMultiplay::m_rgRewardAccountRules_default[] = {
|
|
REWARD_CTS_WIN, // RR_CTS_WIN
|
|
REWARD_TERRORISTS_WIN, // RR_TERRORISTS_WIN
|
|
REWARD_TARGET_BOMB, // RR_TARGET_BOMB
|
|
REWARD_VIP_ESCAPED, // RR_VIP_ESCAPED
|
|
REWARD_VIP_ASSASSINATED, // RR_VIP_ASSASSINATED
|
|
REWARD_TERRORISTS_ESCAPED, // RR_TERRORISTS_ESCAPED
|
|
REWARD_CTS_PREVENT_ESCAPE, // RR_CTS_PREVENT_ESCAPE
|
|
REWARD_ESCAPING_TERRORISTS_NEUTRALIZED, // RR_ESCAPING_TERRORISTS_NEUTRALIZED
|
|
REWARD_BOMB_DEFUSED, // RR_BOMB_DEFUSED
|
|
REWARD_BOMB_PLANTED, // RR_BOMB_PLANTED
|
|
REWARD_BOMB_EXPLODED, // RR_BOMB_EXPLODED
|
|
REWARD_ALL_HOSTAGES_RESCUED, // RR_ALL_HOSTAGES_RESCUED
|
|
REWARD_TARGET_BOMB_SAVED, // RR_TARGET_BOMB_SAVED
|
|
REWARD_HOSTAGE_NOT_RESCUED, // RR_HOSTAGE_NOT_RESCUED
|
|
REWARD_VIP_NOT_ESCAPED, // RR_VIP_NOT_ESCAPED
|
|
REWARD_LOSER_BONUS_DEFAULT, // RR_LOSER_BONUS_DEFAULT
|
|
REWARD_LOSER_BONUS_MIN, // RR_LOSER_BONUS_MIN
|
|
REWARD_LOSER_BONUS_MAX, // RR_LOSER_BONUS_MAX
|
|
REWARD_LOSER_BONUS_ADD, // RR_LOSER_BONUS_ADD
|
|
REWARD_RESCUED_HOSTAGE, // RR_RESCUED_HOSTAGE
|
|
REWARD_TOOK_HOSTAGE_ACC, // RR_TOOK_HOSTAGE_ACC
|
|
REWARD_TOOK_HOSTAGE, // RR_TOOK_HOSTAGE
|
|
};
|
|
|
|
bool IsBotSpeaking()
|
|
{
|
|
for (int i = 1; i <= gpGlobals->maxClients; i++)
|
|
{
|
|
CBasePlayer *pPlayer = UTIL_PlayerByIndex(i);
|
|
|
|
if (!UTIL_IsValidPlayer(pPlayer))
|
|
continue;
|
|
|
|
if (!pPlayer->IsBot())
|
|
continue;
|
|
|
|
CCSBot *pBot = static_cast<CCSBot *>(pPlayer);
|
|
|
|
if (pBot->IsUsingVoice())
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
bool CHalfLifeMultiplay::IsInCareerRound()
|
|
{
|
|
return IsMatchStarted() ? false : true;
|
|
}
|
|
|
|
void CHalfLifeMultiplay::SetCareerMatchLimit(int minWins, int winDifference)
|
|
{
|
|
if (!IsCareer())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!m_iCareerMatchWins)
|
|
{
|
|
m_iCareerMatchWins = minWins;
|
|
m_iRoundWinDifference = winDifference;
|
|
}
|
|
}
|
|
|
|
BOOL CHalfLifeMultiplay::IsCareer()
|
|
{
|
|
return IS_CAREER_MATCH();
|
|
}
|
|
|
|
LINK_HOOK_CLASS_VOID_CUSTOM_CHAIN2(CHalfLifeMultiplay, CSGameRules, ServerDeactivate)
|
|
|
|
void EXT_FUNC CHalfLifeMultiplay::__API_HOOK(ServerDeactivate)()
|
|
{
|
|
if (!IsCareer())
|
|
{
|
|
return;
|
|
}
|
|
|
|
CVAR_SET_FLOAT("pausable", 0);
|
|
CVAR_SET_FLOAT("mp_windifference", 1);
|
|
UTIL_LogPrintf("Career End\n");
|
|
}
|
|
|
|
LINK_HOOK_CLASS_CUSTOM_CHAIN(bool, CCStrikeGameMgrHelper, CSGameRules, CanPlayerHearPlayer, (CBasePlayer *pListener, CBasePlayer *pSender), pListener, pSender)
|
|
|
|
bool CCStrikeGameMgrHelper::__API_HOOK(CanPlayerHearPlayer)(CBasePlayer *pListener, CBasePlayer *pSender)
|
|
{
|
|
#ifdef REGAMEDLL_ADD
|
|
if (!GetCanHearPlayer(pListener, pSender))
|
|
{
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
switch ((int)sv_alltalk.value)
|
|
{
|
|
case 1: // allows everyone to talk
|
|
return true;
|
|
#ifdef REGAMEDLL_ADD
|
|
case 2:
|
|
return (pListener->m_iTeam == pSender->m_iTeam);
|
|
case 3:
|
|
return (pListener->m_iTeam == pSender->m_iTeam || pListener->m_iTeam == SPECTATOR || pListener->m_iTeam == UNASSIGNED);
|
|
case 4:
|
|
return (pListener->IsAlive() == pSender->IsAlive() || pSender->IsAlive());
|
|
case 5:
|
|
return ((pListener->IsAlive() == pSender->IsAlive() && pListener->m_iTeam == pSender->m_iTeam) || !pListener->IsAlive());
|
|
#endif
|
|
default:
|
|
{
|
|
if (
|
|
#ifndef REGAMEDLL_FIXES
|
|
!pSender->IsPlayer() ||
|
|
#endif
|
|
pListener->m_iTeam != pSender->m_iTeam) // Different teams can't hear each other
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (pListener->GetObserverMode() != OBS_NONE) // 2 spectators don't need isAlive() checks.
|
|
{
|
|
return true;
|
|
}
|
|
|
|
BOOL bListenerAlive = pListener->IsAlive();
|
|
BOOL bSenderAlive = pSender->IsAlive();
|
|
|
|
return (bListenerAlive == bSenderAlive || bSenderAlive); // Dead/alive voice chats are separated, but dead can hear alive.
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef REGAMEDLL_ADD
|
|
void CCStrikeGameMgrHelper::ResetCanHearPlayer(edict_t* pEdict)
|
|
{
|
|
int index = ENTINDEX(pEdict) - 1;
|
|
|
|
m_iCanHearMasks[index].Init(TRUE);
|
|
for (int iOtherClient = 0; iOtherClient < VOICE_MAX_PLAYERS; iOtherClient++)
|
|
{
|
|
if (index != iOtherClient) {
|
|
m_iCanHearMasks[iOtherClient][index] = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CCStrikeGameMgrHelper::SetCanHearPlayer(CBasePlayer* pListener, CBasePlayer* pSender, bool bCanHear)
|
|
{
|
|
if (!pListener->IsPlayer() || !pSender->IsPlayer())
|
|
{
|
|
return;
|
|
}
|
|
|
|
int listener = pListener->entindex() - 1;
|
|
int sender = pSender->entindex() - 1;
|
|
m_iCanHearMasks[listener][sender] = bCanHear ? TRUE : FALSE;
|
|
}
|
|
|
|
bool CCStrikeGameMgrHelper::GetCanHearPlayer(CBasePlayer* pListener, CBasePlayer* pSender)
|
|
{
|
|
if (!pListener->IsPlayer() || !pSender->IsPlayer())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
int listener = pListener->entindex() - 1;
|
|
int sender = pSender->entindex() - 1;
|
|
return m_iCanHearMasks[listener][sender] != FALSE;
|
|
}
|
|
#endif
|
|
|
|
void Broadcast(const char *sentence)
|
|
{
|
|
char text[32];
|
|
|
|
if (!sentence)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Q_strcpy(text, "%!MRAD_");
|
|
Q_strcat(text, UTIL_VarArgs("%s", sentence));
|
|
|
|
MESSAGE_BEGIN(MSG_BROADCAST, gmsgSendAudio);
|
|
WRITE_BYTE(0);
|
|
WRITE_STRING(text);
|
|
WRITE_SHORT(PITCH_NORM);
|
|
MESSAGE_END();
|
|
}
|
|
|
|
char *GetTeam(int team)
|
|
{
|
|
switch (team)
|
|
{
|
|
case CT: return "CT";
|
|
case TERRORIST: return "TERRORIST";
|
|
case SPECTATOR: return "SPECTATOR";
|
|
default: return "";
|
|
}
|
|
}
|
|
|
|
void CHalfLifeMultiplay::EndRoundMessage(const char *sentence, ScenarioEventEndRound event)
|
|
{
|
|
char *team = nullptr;
|
|
const char *message = sentence;
|
|
bool bTeamTriggered = true;
|
|
|
|
if (sentence[0] == '#')
|
|
message = sentence + 1;
|
|
|
|
if (sentence[0])
|
|
{
|
|
UTIL_ClientPrintAll(HUD_PRINTCENTER, sentence);
|
|
|
|
switch (event)
|
|
{
|
|
case ROUND_TARGET_BOMB:
|
|
case ROUND_VIP_ASSASSINATED:
|
|
case ROUND_TERRORISTS_ESCAPED:
|
|
case ROUND_TERRORISTS_WIN:
|
|
case ROUND_HOSTAGE_NOT_RESCUED:
|
|
case ROUND_VIP_NOT_ESCAPED:
|
|
team = GetTeam(TERRORIST);
|
|
// tell bots the terrorists won the round
|
|
if (TheBots)
|
|
{
|
|
TheBots->OnEvent(EVENT_TERRORISTS_WIN);
|
|
}
|
|
break;
|
|
case ROUND_VIP_ESCAPED:
|
|
case ROUND_CTS_PREVENT_ESCAPE:
|
|
case ROUND_ESCAPING_TERRORISTS_NEUTRALIZED:
|
|
case ROUND_BOMB_DEFUSED:
|
|
case ROUND_CTS_WIN:
|
|
case ROUND_ALL_HOSTAGES_RESCUED:
|
|
case ROUND_TARGET_SAVED:
|
|
case ROUND_TERRORISTS_NOT_ESCAPED:
|
|
team = GetTeam(CT);
|
|
// tell bots the CTs won the round
|
|
if (TheBots)
|
|
{
|
|
TheBots->OnEvent(EVENT_CTS_WIN);
|
|
}
|
|
break;
|
|
default:
|
|
bTeamTriggered = false;
|
|
// tell bots the round was a draw
|
|
if (TheBots)
|
|
{
|
|
TheBots->OnEvent(EVENT_ROUND_DRAW);
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (bTeamTriggered)
|
|
{
|
|
UTIL_LogPrintf("Team \"%s\" triggered \"%s\" (CT \"%i\") (T \"%i\")\n", team, message, m_iNumCTWins, m_iNumTerroristWins);
|
|
}
|
|
else
|
|
{
|
|
UTIL_LogPrintf("World triggered \"%s\" (CT \"%i\") (T \"%i\")\n", message, m_iNumCTWins, m_iNumTerroristWins);
|
|
}
|
|
}
|
|
|
|
UTIL_LogPrintf("World triggered \"Round_End\"\n");
|
|
|
|
#ifdef REGAMEDLL_ADD
|
|
FireTargets("game_round_end", nullptr, nullptr, USE_TOGGLE, 0.0);
|
|
#endif
|
|
}
|
|
|
|
void CHalfLifeMultiplay::ReadMultiplayCvars()
|
|
{
|
|
m_iRoundTime = int(CVAR_GET_FLOAT("mp_roundtime") * 60);
|
|
m_iC4Timer = int(CVAR_GET_FLOAT("mp_c4timer"));
|
|
m_iIntroRoundTime = int(CVAR_GET_FLOAT("mp_freezetime"));
|
|
m_iLimitTeams = int(CVAR_GET_FLOAT("mp_limitteams"));
|
|
|
|
#ifndef REGAMEDLL_ADD
|
|
if (m_iRoundTime > 540)
|
|
{
|
|
CVAR_SET_FLOAT("mp_roundtime", 9);
|
|
m_iRoundTime = 540;
|
|
}
|
|
else if (m_iRoundTime < 60)
|
|
{
|
|
CVAR_SET_FLOAT("mp_roundtime", 1);
|
|
m_iRoundTime = 60;
|
|
}
|
|
|
|
if (m_iIntroRoundTime > 60)
|
|
{
|
|
CVAR_SET_FLOAT("mp_freezetime", 60);
|
|
m_iIntroRoundTime = 60;
|
|
}
|
|
else if (m_iIntroRoundTime < 0)
|
|
{
|
|
CVAR_SET_FLOAT("mp_freezetime", 0);
|
|
m_iIntroRoundTime = 0;
|
|
}
|
|
|
|
if (m_iC4Timer > 90)
|
|
{
|
|
CVAR_SET_FLOAT("mp_c4timer", 90);
|
|
m_iC4Timer = 90;
|
|
}
|
|
else if (m_iC4Timer < 10)
|
|
{
|
|
CVAR_SET_FLOAT("mp_c4timer", 10);
|
|
m_iC4Timer = 10;
|
|
}
|
|
|
|
if (m_iLimitTeams > 20)
|
|
{
|
|
CVAR_SET_FLOAT("mp_limitteams", 20);
|
|
m_iLimitTeams = 20;
|
|
}
|
|
else if (m_iLimitTeams < 0)
|
|
{
|
|
CVAR_SET_FLOAT("mp_limitteams", 0);
|
|
m_iLimitTeams = 0;
|
|
}
|
|
#else
|
|
// a limit of 500 minutes because
|
|
// if you do more minutes would be a bug in the HUD RoundTime in the form 00:00
|
|
if (m_iRoundTime > 30000)
|
|
{
|
|
CVAR_SET_FLOAT("mp_roundtime", 500);
|
|
m_iRoundTime = 30000;
|
|
}
|
|
else if (m_iRoundTime < 0)
|
|
{
|
|
CVAR_SET_FLOAT("mp_roundtime", 0);
|
|
m_iRoundTime = 0;
|
|
}
|
|
if (m_iIntroRoundTime < 0)
|
|
{
|
|
CVAR_SET_FLOAT("mp_freezetime", 0);
|
|
m_iIntroRoundTime = 0;
|
|
}
|
|
if (m_iC4Timer < 0)
|
|
{
|
|
CVAR_SET_FLOAT("mp_c4timer", 0);
|
|
m_iC4Timer = 0;
|
|
}
|
|
if (m_iLimitTeams < 0)
|
|
{
|
|
CVAR_SET_FLOAT("mp_limitteams", 0);
|
|
m_iLimitTeams = 0;
|
|
}
|
|
|
|
// auto-disable ff
|
|
if (freeforall.value)
|
|
{
|
|
CVAR_SET_FLOAT("mp_friendlyfire", 0);
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
CHalfLifeMultiplay::CHalfLifeMultiplay()
|
|
{
|
|
m_bFreezePeriod = TRUE;
|
|
|
|
m_VoiceGameMgr.Init(&g_GameMgrHelper, gpGlobals->maxClients);
|
|
RefreshSkillData();
|
|
Q_memcpy(m_rgRewardAccountRules, m_rgRewardAccountRules_default, sizeof(m_rgRewardAccountRules));
|
|
|
|
m_flIntermissionEndTime = 0;
|
|
m_flIntermissionStartTime = 0;
|
|
m_flRestartRoundTime = 0;
|
|
|
|
m_iAccountCT = 0;
|
|
m_iAccountTerrorist = 0;
|
|
m_iHostagesRescued = 0;
|
|
m_iRoundWinStatus = WINSTATUS_NONE;
|
|
m_iNumCTWins = 0;
|
|
m_iNumTerroristWins = 0;
|
|
m_pVIP = nullptr;
|
|
m_iNumCT = 0;
|
|
m_iNumTerrorist = 0;
|
|
m_iNumSpawnableCT = 0;
|
|
m_iNumSpawnableTerrorist = 0;
|
|
m_bMapHasCameras = -1;
|
|
|
|
m_iLoserBonus = m_rgRewardAccountRules[RR_LOSER_BONUS_DEFAULT];
|
|
m_iNumConsecutiveCTLoses = 0;
|
|
m_iNumConsecutiveTerroristLoses = 0;
|
|
m_iC4Guy = 0;
|
|
m_bBombDefused = false;
|
|
m_bTargetBombed = false;
|
|
m_bLevelInitialized = false;
|
|
m_tmNextPeriodicThink = 0;
|
|
m_bGameStarted = false;
|
|
m_bCompleteReset = false;
|
|
m_flRequiredEscapeRatio = 0.5;
|
|
m_iNumEscapers = 0;
|
|
|
|
// by default everyone can buy
|
|
m_bCTCantBuy = false;
|
|
m_bTCantBuy = false;
|
|
|
|
m_flBombRadius = 500.0;
|
|
m_iTotalGunCount = 0;
|
|
m_iTotalGrenadeCount = 0;
|
|
m_iTotalArmourCount = 0;
|
|
m_iConsecutiveVIP = 0;
|
|
m_iUnBalancedRounds = 0;
|
|
m_iNumEscapeRounds = 0;
|
|
m_bRoundTerminating = false;
|
|
|
|
g_iHostageNumber = 0;
|
|
|
|
m_iMaxRounds = int(CVAR_GET_FLOAT("mp_maxrounds"));
|
|
|
|
if (m_iMaxRounds < 0)
|
|
{
|
|
m_iMaxRounds = 0;
|
|
CVAR_SET_FLOAT("mp_maxrounds", 0);
|
|
}
|
|
|
|
m_iTotalRoundsPlayed = 0;
|
|
m_iMaxRoundsWon = int(CVAR_GET_FLOAT("mp_winlimit"));
|
|
|
|
if (m_iMaxRoundsWon < 0)
|
|
{
|
|
m_iMaxRoundsWon = 0;
|
|
CVAR_SET_FLOAT("mp_winlimit", 0);
|
|
}
|
|
|
|
Q_memset(m_iMapVotes, 0, sizeof(m_iMapVotes));
|
|
|
|
m_iLastPick = 1;
|
|
m_bMapHasEscapeZone = false;
|
|
m_bMapHasVIPSafetyZone = FALSE;
|
|
m_bMapHasBombZone = false;
|
|
m_bMapHasRescueZone = false;
|
|
m_iStoredSpectValue = int(allow_spectators.value);
|
|
|
|
for (int j = 0; j < MAX_VIP_QUEUES; j++)
|
|
{
|
|
m_pVIPQueue[j] = nullptr;
|
|
}
|
|
|
|
#ifdef REGAMEDLL_FIXES
|
|
if (!IS_DEDICATED_SERVER())
|
|
#endif
|
|
{
|
|
// NOTE: cvar cl_himodels refers for the client side
|
|
CVAR_SET_FLOAT("cl_himodels", 0);
|
|
}
|
|
|
|
ReadMultiplayCvars();
|
|
|
|
m_iIntroRoundTime += 2;
|
|
|
|
#ifdef REGAMEDLL_FIXES
|
|
m_fMaxIdlePeriod = (((m_iRoundTime < 60) ? 60 : m_iRoundTime) * 2);
|
|
#else
|
|
m_fMaxIdlePeriod = (m_iRoundTime * 2);
|
|
#endif
|
|
|
|
float flAutoKickIdle = autokick_timeout.value;
|
|
if (flAutoKickIdle > 0.0)
|
|
{
|
|
m_fMaxIdlePeriod = flAutoKickIdle;
|
|
}
|
|
|
|
m_bInCareerGame = false;
|
|
m_iRoundTimeSecs = m_iIntroRoundTime;
|
|
|
|
if (IS_DEDICATED_SERVER())
|
|
{
|
|
CVAR_SET_FLOAT("pausable", 0);
|
|
}
|
|
else if (IsCareer())
|
|
{
|
|
CVAR_SET_FLOAT("pausable", 1);
|
|
CVAR_SET_FLOAT("sv_aim", 0);
|
|
CVAR_SET_FLOAT("sv_maxspeed", 322);
|
|
CVAR_SET_FLOAT("sv_cheats", 0);
|
|
CVAR_SET_FLOAT("mp_windifference", 2);
|
|
|
|
m_bInCareerGame = true;
|
|
UTIL_LogPrintf("Career Start\n");
|
|
}
|
|
else
|
|
{
|
|
// 3/31/99
|
|
// Added lservercfg file cvar, since listen and dedicated servers should not
|
|
// share a single config file. (sjb)
|
|
|
|
// listen server
|
|
CVAR_SET_FLOAT("pausable", 0);
|
|
|
|
const char *lservercfgfile = CVAR_GET_STRING("lservercfgfile");
|
|
if (lservercfgfile && lservercfgfile[0] != '\0')
|
|
{
|
|
char szCommand[256];
|
|
|
|
ALERT(at_console, "Executing listen server config file\n");
|
|
Q_sprintf(szCommand, "exec %s\n", lservercfgfile);
|
|
SERVER_COMMAND(szCommand);
|
|
}
|
|
}
|
|
|
|
m_fRoundStartTime = 0;
|
|
m_fRoundStartTimeReal = 0;
|
|
|
|
#ifndef CSTRIKE
|
|
InstallBotControl();
|
|
#endif
|
|
|
|
InstallHostageManager();
|
|
InstallCommands();
|
|
|
|
m_bSkipSpawn = m_bInCareerGame;
|
|
|
|
m_fCareerRoundMenuTime = 0;
|
|
m_fCareerMatchMenuTime = 0;
|
|
m_iCareerMatchWins = 0;
|
|
|
|
m_iRoundWinDifference = int(CVAR_GET_FLOAT("mp_windifference"));
|
|
|
|
if (IsCareer())
|
|
{
|
|
CCareerTaskManager::Create();
|
|
}
|
|
|
|
if (m_iRoundWinDifference < 1)
|
|
{
|
|
m_iRoundWinDifference = 1;
|
|
CVAR_SET_FLOAT("mp_windifference", 1);
|
|
}
|
|
|
|
InstallTutor(CVAR_GET_FLOAT("tutor_enable") != 0.0f);
|
|
|
|
m_bSkipShowMenu = false;
|
|
m_bNeededPlayers = false;
|
|
m_flEscapeRatio = 0.0f;
|
|
m_flTimeLimit = 0.0f;
|
|
m_flGameStartTime = 0.0f;
|
|
m_bTeamBalanced = false;
|
|
|
|
#ifndef REGAMEDLL_FIXES
|
|
g_pMPGameRules = this;
|
|
#endif
|
|
}
|
|
|
|
void CHalfLifeMultiplay::RefreshSkillData()
|
|
{
|
|
// load all default values
|
|
CGameRules::RefreshSkillData();
|
|
|
|
// override some values for multiplay.
|
|
|
|
gSkillData.plrDmg9MM = 12; // Glock Round
|
|
gSkillData.plrDmgMP5 = 12; // MP5 Round
|
|
gSkillData.plrDmg357 = 40; // 357 Round
|
|
gSkillData.plrDmgRPG = 120; // RPG
|
|
|
|
gSkillData.plrDmgM203Grenade = 100; // M203 grenade
|
|
gSkillData.plrDmgCrossbowClient = 20; // Crossbow
|
|
|
|
// Shotgun buckshot
|
|
// fewer pellets in deathmatch
|
|
gSkillData.plrDmgBuckshot = 20;
|
|
|
|
// suitcharger
|
|
gSkillData.suitchargerCapacity = 30;
|
|
}
|
|
|
|
LINK_HOOK_CLASS_VOID_CUSTOM_CHAIN2(CHalfLifeMultiplay, CSGameRules, RemoveGuns)
|
|
|
|
void EXT_FUNC CHalfLifeMultiplay::__API_HOOK(RemoveGuns)()
|
|
{
|
|
CBaseEntity *toremove = nullptr;
|
|
|
|
while ((toremove = UTIL_FindEntityByClassname(toremove, "weaponbox")))
|
|
((CWeaponBox *)toremove)->Kill();
|
|
|
|
toremove = nullptr;
|
|
|
|
while ((toremove = UTIL_FindEntityByClassname(toremove, "weapon_shield")))
|
|
{
|
|
toremove->SetThink(&CBaseEntity::SUB_Remove);
|
|
toremove->pev->nextthink = gpGlobals->time + 0.1;
|
|
}
|
|
}
|
|
|
|
void CHalfLifeMultiplay::UpdateTeamScores()
|
|
{
|
|
MESSAGE_BEGIN(MSG_ALL, gmsgTeamScore);
|
|
WRITE_STRING("CT");
|
|
WRITE_SHORT(m_iNumCTWins);
|
|
MESSAGE_END();
|
|
|
|
MESSAGE_BEGIN(MSG_ALL, gmsgTeamScore);
|
|
WRITE_STRING("TERRORIST");
|
|
WRITE_SHORT(m_iNumTerroristWins);
|
|
MESSAGE_END();
|
|
}
|
|
|
|
LINK_HOOK_CLASS_VOID_CUSTOM_CHAIN2(CHalfLifeMultiplay, CSGameRules, CleanUpMap)
|
|
|
|
void EXT_FUNC CHalfLifeMultiplay::__API_HOOK(CleanUpMap)()
|
|
{
|
|
#ifdef REGAMEDLL_FIXES
|
|
UTIL_RestartOther("multi_manager");
|
|
|
|
// Release or reset everything entities in depending of flags ObjectCaps
|
|
// (FCAP_MUST_RESET / FCAP_MUST_RELEASE)
|
|
UTIL_ResetEntities();
|
|
#endif
|
|
|
|
// Recreate all the map entities from the map data (preserving their indices),
|
|
// then remove everything else except the players.
|
|
UTIL_RestartOther("cycler_sprite");
|
|
UTIL_RestartOther("light");
|
|
UTIL_RestartOther("func_breakable");
|
|
UTIL_RestartOther("func_door");
|
|
|
|
#ifdef REGAMEDLL_FIXES
|
|
UTIL_RestartOther("func_button");
|
|
UTIL_RestartOther("func_rot_button");
|
|
UTIL_RestartOther("env_render");
|
|
UTIL_RestartOther("env_spark");
|
|
UTIL_RestartOther("trigger_push");
|
|
#endif
|
|
|
|
UTIL_RestartOther("func_water");
|
|
UTIL_RestartOther("func_door_rotating");
|
|
UTIL_RestartOther("func_tracktrain");
|
|
UTIL_RestartOther("func_vehicle");
|
|
UTIL_RestartOther("func_train");
|
|
UTIL_RestartOther("armoury_entity");
|
|
UTIL_RestartOther("ambient_generic");
|
|
UTIL_RestartOther("env_sprite");
|
|
|
|
#ifdef REGAMEDLL_FIXES
|
|
UTIL_RestartOther("trigger_once");
|
|
UTIL_RestartOther("func_wall_toggle");
|
|
UTIL_RestartOther("func_healthcharger");
|
|
UTIL_RestartOther("func_recharge");
|
|
UTIL_RestartOther("trigger_hurt");
|
|
UTIL_RestartOther("multisource");
|
|
UTIL_RestartOther("env_beam");
|
|
UTIL_RestartOther("env_laser");
|
|
UTIL_RestartOther("trigger_auto");
|
|
UTIL_RestartOther("trigger_multiple");
|
|
#endif
|
|
|
|
// Remove grenades and C4
|
|
const int grenadesRemoveCount = 20;
|
|
UTIL_RemoveOther("grenade", grenadesRemoveCount);
|
|
|
|
#ifndef REGAMEDLL_FIXES
|
|
// Remove defuse kit
|
|
// Old code only removed 4 kits and stopped.
|
|
UTIL_RemoveOther("item_thighpack");
|
|
#else
|
|
// Don't remove level items
|
|
CItemThighPack *pDefuser = nullptr;
|
|
|
|
while ((pDefuser = UTIL_FindEntityByClassname(pDefuser, "item_thighpack")))
|
|
{
|
|
if (pDefuser->pev->spawnflags & SF_NORESPAWN)
|
|
{
|
|
pDefuser->SetThink(&CBaseEntity::SUB_Remove);
|
|
pDefuser->pev->nextthink = gpGlobals->time + 0.1;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef REGAMEDLL_FIXES
|
|
UTIL_RemoveOther("gib");
|
|
UTIL_RemoveOther("DelayedUse");
|
|
#endif
|
|
|
|
RemoveGuns();
|
|
PLAYBACK_EVENT((FEV_GLOBAL | FEV_RELIABLE), 0, m_usResetDecals);
|
|
}
|
|
|
|
LINK_HOOK_CLASS_CUSTOM_CHAIN2(CBasePlayer *, CHalfLifeMultiplay, CSGameRules, GiveC4)
|
|
|
|
CBasePlayer *EXT_FUNC CHalfLifeMultiplay::__API_HOOK(GiveC4)()
|
|
{
|
|
int iTeamCount;
|
|
int iTemp = 0;
|
|
int humansPresent = 0;
|
|
|
|
iTeamCount = m_iNumTerrorist;
|
|
m_iC4Guy++;
|
|
|
|
bool giveToHumans = (cv_bot_defer_to_human.value > 0.0);
|
|
if (giveToHumans)
|
|
{
|
|
for (int i = 1; i <= gpGlobals->maxClients; i++)
|
|
{
|
|
CBasePlayer *pPlayer = UTIL_PlayerByIndex(i);
|
|
|
|
if (!UTIL_IsValidPlayer(pPlayer))
|
|
continue;
|
|
|
|
if (pPlayer->pev->deadflag != DEAD_NO || pPlayer->m_iTeam != TERRORIST)
|
|
continue;
|
|
|
|
if (!pPlayer->IsBot())
|
|
humansPresent++;
|
|
}
|
|
|
|
if (humansPresent)
|
|
iTeamCount = humansPresent;
|
|
else
|
|
giveToHumans = false;
|
|
}
|
|
|
|
// if we've looped past the last Terrorist.. then give the C4 to the first one.
|
|
if (m_iC4Guy > iTeamCount)
|
|
{
|
|
m_iC4Guy = 1;
|
|
}
|
|
|
|
// Give the C4 to the specified T player..
|
|
CBaseEntity *pEntity = nullptr;
|
|
while ((pEntity = UTIL_FindEntityByClassname(pEntity, "player")))
|
|
{
|
|
if (FNullEnt(pEntity->edict()))
|
|
break;
|
|
|
|
if (!pEntity->IsPlayer())
|
|
continue;
|
|
|
|
if (pEntity->IsDormant())
|
|
continue;
|
|
|
|
CBasePlayer *pPlayer = GetClassPtr<CCSPlayer>((CBasePlayer *)pEntity->pev);
|
|
if (pPlayer->pev->deadflag != DEAD_NO || pPlayer->m_iTeam != TERRORIST || (giveToHumans && pPlayer->IsBot()))
|
|
continue;
|
|
|
|
if (++iTemp == m_iC4Guy)
|
|
{
|
|
if (pPlayer->MakeBomber())
|
|
{
|
|
#ifdef REGAMEDLL_FIXES
|
|
// we already have bomber
|
|
return pPlayer;
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
// if there are no players with a bomb
|
|
if (!IsThereABomber())
|
|
{
|
|
m_iC4Guy = 0;
|
|
pEntity = nullptr;
|
|
|
|
while ((pEntity = UTIL_FindEntityByClassname(pEntity, "player")))
|
|
{
|
|
if (FNullEnt(pEntity->edict()))
|
|
break;
|
|
|
|
if (!pEntity->IsPlayer())
|
|
continue;
|
|
|
|
if (pEntity->IsDormant())
|
|
continue;
|
|
|
|
CBasePlayer *pPlayer = GetClassPtr<CCSPlayer>((CBasePlayer *)pEntity->pev);
|
|
|
|
if (pPlayer->pev->deadflag != DEAD_NO || pPlayer->m_iTeam != TERRORIST)
|
|
continue;
|
|
|
|
if (pPlayer->MakeBomber())
|
|
return pPlayer;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void CHalfLifeMultiplay::QueueCareerRoundEndMenu(float tmDelay, int iWinStatus)
|
|
{
|
|
if (!TheCareerTasks)
|
|
return;
|
|
|
|
if (m_fCareerMatchMenuTime != 0.0f)
|
|
return;
|
|
|
|
m_fCareerRoundMenuTime = tmDelay + gpGlobals->time;
|
|
|
|
bool humansAreCTs = (Q_strcmp(humans_join_team.string, "CT") == 0);
|
|
if (humansAreCTs)
|
|
{
|
|
CBaseEntity *hostage = nullptr;
|
|
|
|
int numHostagesInMap = 0;
|
|
int numHostagesFollowingHumans = 0;
|
|
int numHostagesAlive = 0;
|
|
|
|
while ((hostage = UTIL_FindEntityByClassname(hostage, "hostage_entity")))
|
|
{
|
|
numHostagesInMap++;
|
|
|
|
CHostage *pHostage = static_cast<CHostage *>(hostage);
|
|
if (!pHostage->IsAlive())
|
|
continue;
|
|
|
|
CBasePlayer *pLeader = nullptr;
|
|
if (pHostage->IsFollowingSomeone())
|
|
pLeader = static_cast<CBasePlayer *>(pHostage->GetLeader());
|
|
|
|
if (pLeader == nullptr)
|
|
{
|
|
numHostagesAlive++;
|
|
}
|
|
else
|
|
{
|
|
if (!pLeader->IsBot())
|
|
{
|
|
numHostagesFollowingHumans++;
|
|
TheCareerTasks->HandleEvent(EVENT_HOSTAGE_RESCUED, pLeader, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!numHostagesAlive)
|
|
{
|
|
if ((numHostagesInMap * 0.5) <= (numHostagesFollowingHumans + m_iHostagesRescued))
|
|
{
|
|
TheCareerTasks->HandleEvent(EVENT_ALL_HOSTAGES_RESCUED);
|
|
}
|
|
}
|
|
}
|
|
|
|
switch (iWinStatus)
|
|
{
|
|
case WINSTATUS_CTS:
|
|
TheCareerTasks->HandleEvent(humansAreCTs ? EVENT_ROUND_WIN : EVENT_ROUND_LOSS);
|
|
break;
|
|
case WINSTATUS_TERRORISTS:
|
|
TheCareerTasks->HandleEvent(humansAreCTs ? EVENT_ROUND_LOSS : EVENT_ROUND_WIN);
|
|
break;
|
|
default:
|
|
TheCareerTasks->HandleEvent(EVENT_ROUND_DRAW);
|
|
break;
|
|
}
|
|
|
|
if (m_fCareerMatchMenuTime == 0.0f && m_iCareerMatchWins)
|
|
{
|
|
bool canTsWin = true;
|
|
bool canCTsWin = true;
|
|
|
|
if (m_iNumCTWins < m_iCareerMatchWins || (m_iNumCTWins - m_iNumTerroristWins < m_iRoundWinDifference))
|
|
canCTsWin = false;
|
|
|
|
if (m_iNumTerroristWins < m_iCareerMatchWins || (m_iNumTerroristWins - m_iNumCTWins < m_iRoundWinDifference))
|
|
canTsWin = false;
|
|
|
|
if (!TheCareerTasks->AreAllTasksComplete())
|
|
{
|
|
if (humansAreCTs)
|
|
return;
|
|
|
|
canTsWin = false;
|
|
}
|
|
|
|
if (canCTsWin || canTsWin)
|
|
{
|
|
m_fCareerRoundMenuTime = 0;
|
|
m_fCareerMatchMenuTime = gpGlobals->time + 3.0f;
|
|
}
|
|
}
|
|
}
|
|
|
|
LINK_HOOK_CLASS_VOID_CUSTOM_CHAIN2(CHalfLifeMultiplay, CSGameRules, CheckWinConditions)
|
|
|
|
// Check if the scenario has been won/lost.
|
|
void EXT_FUNC CHalfLifeMultiplay::__API_HOOK(CheckWinConditions)()
|
|
{
|
|
#ifdef REGAMEDLL_FIXES
|
|
// If a winner has already been determined.. then get the heck out of here
|
|
if (m_iRoundWinStatus != WINSTATUS_NONE)
|
|
{
|
|
// still check if we lost players to where we need to do a full reset next round...
|
|
int NumDeadCT, NumDeadTerrorist, NumAliveTerrorist, NumAliveCT;
|
|
InitializePlayerCounts(NumAliveTerrorist, NumAliveCT, NumDeadTerrorist, NumDeadCT);
|
|
|
|
return;
|
|
}
|
|
#else
|
|
// If a winner has already been determined and game of started.. then get the heck out of here
|
|
if (m_bGameStarted && m_iRoundWinStatus != WINSTATUS_NONE)
|
|
return;
|
|
#endif
|
|
|
|
#ifdef REGAMEDLL_ADD
|
|
int scenarioFlags = UTIL_ReadFlags(round_infinite.string);
|
|
#else
|
|
// the icc compiler will cut out all of the code which refers to it
|
|
int scenarioFlags = 0;
|
|
#endif
|
|
|
|
// Initialize the player counts..
|
|
int NumDeadCT, NumDeadTerrorist, NumAliveTerrorist, NumAliveCT;
|
|
InitializePlayerCounts(NumAliveTerrorist, NumAliveCT, NumDeadTerrorist, NumDeadCT);
|
|
|
|
if (HasRoundInfinite())
|
|
return;
|
|
|
|
// other player's check
|
|
m_bNeededPlayers = false;
|
|
if (!(scenarioFlags & SCENARIO_BLOCK_NEED_PLAYERS) && NeededPlayersCheck())
|
|
return;
|
|
|
|
// Assasination/VIP scenarion check
|
|
if (!(scenarioFlags & SCENARIO_BLOCK_VIP_ESCAPE) && VIPRoundEndCheck())
|
|
return;
|
|
|
|
// Prison escape check
|
|
if (!(scenarioFlags & SCENARIO_BLOCK_PRISON_ESCAPE) && PrisonRoundEndCheck(NumAliveTerrorist, NumAliveCT, NumDeadTerrorist, NumDeadCT))
|
|
return;
|
|
|
|
// Bomb check
|
|
if (!(scenarioFlags & SCENARIO_BLOCK_BOMB) && BombRoundEndCheck())
|
|
return;
|
|
|
|
// Team Extermination check
|
|
// CounterTerrorists won by virture of elimination
|
|
if (!(scenarioFlags & SCENARIO_BLOCK_TEAM_EXTERMINATION) && TeamExterminationCheck(NumAliveTerrorist, NumAliveCT, NumDeadTerrorist, NumDeadCT))
|
|
return;
|
|
|
|
// Hostage rescue check
|
|
if (!(scenarioFlags & SCENARIO_BLOCK_HOSTAGE_RESCUE) && HostageRescueRoundEndCheck())
|
|
return;
|
|
|
|
// scenario not won - still in progress
|
|
}
|
|
|
|
void CHalfLifeMultiplay::InitializePlayerCounts(int &NumAliveTerrorist, int &NumAliveCT, int &NumDeadTerrorist, int &NumDeadCT)
|
|
{
|
|
NumAliveTerrorist = NumAliveCT = NumDeadCT = NumDeadTerrorist = 0;
|
|
m_iNumTerrorist = m_iNumCT = m_iNumSpawnableTerrorist = m_iNumSpawnableCT = 0;
|
|
m_iHaveEscaped = 0;
|
|
|
|
// initialize count dead/alive players
|
|
|
|
// Count how many dead players there are on each team.
|
|
CBaseEntity *pEntity = nullptr;
|
|
while ((pEntity = UTIL_FindEntityByClassname(pEntity, "player")))
|
|
{
|
|
if (FNullEnt(pEntity->edict()))
|
|
break;
|
|
|
|
if (pEntity->IsDormant())
|
|
continue;
|
|
|
|
CBasePlayer *pPlayer = GetClassPtr<CCSPlayer>((CBasePlayer *)pEntity->pev);
|
|
switch (pPlayer->m_iTeam)
|
|
{
|
|
case CT:
|
|
{
|
|
m_iNumCT++;
|
|
|
|
if (pPlayer->m_iMenu != Menu_ChooseAppearance)
|
|
{
|
|
m_iNumSpawnableCT++;
|
|
}
|
|
|
|
if (pPlayer->pev->deadflag != DEAD_NO)
|
|
NumDeadCT++;
|
|
else
|
|
NumAliveCT++;
|
|
|
|
break;
|
|
}
|
|
case TERRORIST:
|
|
{
|
|
m_iNumTerrorist++;
|
|
|
|
if (pPlayer->m_iMenu != Menu_ChooseAppearance)
|
|
{
|
|
m_iNumSpawnableTerrorist++;
|
|
}
|
|
|
|
if (pPlayer->pev->deadflag != DEAD_NO)
|
|
NumDeadTerrorist++;
|
|
else
|
|
NumAliveTerrorist++;
|
|
|
|
// Check to see if this guy escaped.
|
|
if (pPlayer->m_bEscaped)
|
|
m_iHaveEscaped++;
|
|
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool CHalfLifeMultiplay::OnRoundEnd_Intercept(int winStatus, ScenarioEventEndRound event, float tmDelay)
|
|
{
|
|
return g_ReGameHookchains.m_RoundEnd.callChain(&CHalfLifeMultiplay::OnRoundEnd, this, winStatus, event, tmDelay);
|
|
}
|
|
|
|
bool EXT_FUNC CHalfLifeMultiplay::OnRoundEnd(int winStatus, ScenarioEventEndRound event, float tmDelay)
|
|
{
|
|
switch (event)
|
|
{
|
|
case ROUND_TARGET_BOMB: return Target_Bombed(tmDelay);
|
|
case ROUND_VIP_ESCAPED: return VIP_Escaped(tmDelay);
|
|
case ROUND_VIP_ASSASSINATED: return VIP_Died(tmDelay);
|
|
case ROUND_TERRORISTS_ESCAPED: return Prison_Escaped(tmDelay);
|
|
case ROUND_CTS_PREVENT_ESCAPE: return Prison_PreventEscape(tmDelay);
|
|
case ROUND_ESCAPING_TERRORISTS_NEUTRALIZED: return Prison_Neutralized(tmDelay);
|
|
case ROUND_BOMB_DEFUSED: return Target_Defused(tmDelay);
|
|
case ROUND_CTS_WIN: return Round_Cts(tmDelay);
|
|
case ROUND_TERRORISTS_WIN: return Round_Ts(tmDelay);
|
|
case ROUND_END_DRAW: return Round_Draw(tmDelay);
|
|
case ROUND_ALL_HOSTAGES_RESCUED: return Hostage_Rescue(tmDelay);
|
|
case ROUND_TARGET_SAVED: return Target_Saved(tmDelay);
|
|
case ROUND_HOSTAGE_NOT_RESCUED: return Hostage_NotRescued(tmDelay);
|
|
case ROUND_TERRORISTS_NOT_ESCAPED: return Prison_NotEscaped(tmDelay);
|
|
case ROUND_VIP_NOT_ESCAPED: return VIP_NotEscaped(tmDelay);
|
|
case ROUND_GAME_COMMENCE: return NeededPlayersCheck(tmDelay);
|
|
case ROUND_GAME_RESTART: return RestartRoundCheck(tmDelay);
|
|
case ROUND_GAME_OVER: return RoundOver(tmDelay);
|
|
case ROUND_NONE:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool EXT_FUNC CHalfLifeMultiplay::NeededPlayersCheck(float tmDelay)
|
|
{
|
|
// Start the round immediately when the first person joins
|
|
UTIL_LogPrintf("World triggered \"Game_Commencing\"\n");
|
|
|
|
// Make sure we are not on the FreezePeriod.
|
|
m_bFreezePeriod = FALSE;
|
|
m_bCompleteReset = true;
|
|
|
|
EndRoundMessage("#Game_Commencing", ROUND_GAME_COMMENCE);
|
|
TerminateRound(tmDelay, WINSTATUS_DRAW);
|
|
|
|
m_bGameStarted = true;
|
|
if (TheBots)
|
|
{
|
|
TheBots->OnEvent(EVENT_GAME_COMMENCE);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool EXT_FUNC CHalfLifeMultiplay::NeededPlayersCheck()
|
|
{
|
|
// We needed players to start scoring
|
|
// Do we have them now?
|
|
// start the game, after the players entered in game
|
|
if (!m_iNumSpawnableTerrorist || !m_iNumSpawnableCT)
|
|
{
|
|
UTIL_ClientPrintAll(HUD_PRINTCONSOLE, "#Game_scoring");
|
|
m_bNeededPlayers = true;
|
|
m_bGameStarted = false;
|
|
}
|
|
|
|
if (!m_bGameStarted && m_iNumSpawnableTerrorist != 0 && m_iNumSpawnableCT != 0)
|
|
{
|
|
if (IsCareer())
|
|
{
|
|
CBasePlayer *pPlayer = UTIL_PlayerByIndex(gpGlobals->maxClients);
|
|
if (!UTIL_IsValidPlayer(pPlayer) || !pPlayer->IsBot())
|
|
return true;
|
|
}
|
|
|
|
return OnRoundEnd_Intercept(WINSTATUS_DRAW, ROUND_GAME_COMMENCE, IsCareer() ? 0 : 3);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool EXT_FUNC CHalfLifeMultiplay::VIP_Escaped(float tmDelay)
|
|
{
|
|
Broadcast("ctwin");
|
|
m_iAccountCT += m_rgRewardAccountRules[RR_VIP_ESCAPED];
|
|
|
|
if (!m_bNeededPlayers)
|
|
{
|
|
m_iNumCTWins++;
|
|
// Update the clients team score
|
|
UpdateTeamScores();
|
|
}
|
|
|
|
MESSAGE_BEGIN(MSG_SPEC, SVC_DIRECTOR);
|
|
WRITE_BYTE(9); // command length in bytes
|
|
WRITE_BYTE(DRC_CMD_EVENT); // VIP rescued
|
|
WRITE_SHORT(ENTINDEX(m_pVIP->edict())); // index number of primary entity
|
|
WRITE_SHORT(0); // index number of secondary entity
|
|
WRITE_LONG(15 | DRC_FLAG_FINAL); // eventflags (priority and flags)
|
|
MESSAGE_END();
|
|
|
|
EndRoundMessage("#VIP_Escaped", ROUND_VIP_ESCAPED);
|
|
TerminateRound(tmDelay, WINSTATUS_CTS);
|
|
|
|
// tell the bots the VIP got out
|
|
if (TheBots)
|
|
{
|
|
TheBots->OnEvent(EVENT_VIP_ESCAPED);
|
|
}
|
|
|
|
if (IsCareer())
|
|
{
|
|
QueueCareerRoundEndMenu(tmDelay, WINSTATUS_CTS);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool EXT_FUNC CHalfLifeMultiplay::VIP_Died(float tmDelay)
|
|
{
|
|
Broadcast("terwin");
|
|
m_iAccountTerrorist += m_rgRewardAccountRules[RR_VIP_ASSASSINATED];
|
|
|
|
if (!m_bNeededPlayers)
|
|
{
|
|
m_iNumTerroristWins++;
|
|
// Update the clients team score
|
|
UpdateTeamScores();
|
|
}
|
|
|
|
EndRoundMessage("#VIP_Assassinated", ROUND_VIP_ASSASSINATED);
|
|
TerminateRound(tmDelay, WINSTATUS_TERRORISTS);
|
|
|
|
// tell the bots the VIP was killed
|
|
if (TheBots)
|
|
{
|
|
TheBots->OnEvent(EVENT_VIP_ASSASSINATED);
|
|
}
|
|
|
|
if (IsCareer())
|
|
{
|
|
QueueCareerRoundEndMenu(tmDelay, WINSTATUS_TERRORISTS);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool EXT_FUNC CHalfLifeMultiplay::VIPRoundEndCheck()
|
|
{
|
|
// checks to scenario Escaped VIP on map with vip safety zones
|
|
if (m_bMapHasVIPSafetyZone && m_pVIP)
|
|
{
|
|
if (m_pVIP->m_bEscaped)
|
|
{
|
|
return OnRoundEnd_Intercept(WINSTATUS_CTS, ROUND_VIP_ESCAPED, GetRoundRestartDelay());
|
|
}
|
|
// The VIP is dead
|
|
else if (m_pVIP->pev->deadflag != DEAD_NO)
|
|
{
|
|
return OnRoundEnd_Intercept(WINSTATUS_TERRORISTS, ROUND_VIP_ASSASSINATED, GetRoundRestartDelay());
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool EXT_FUNC CHalfLifeMultiplay::Prison_Escaped(float tmDelay)
|
|
{
|
|
Broadcast("terwin");
|
|
m_iAccountTerrorist += m_rgRewardAccountRules[RR_TERRORISTS_ESCAPED];
|
|
|
|
if (!m_bNeededPlayers)
|
|
{
|
|
m_iNumTerroristWins++;
|
|
// Update the clients team score
|
|
UpdateTeamScores();
|
|
}
|
|
|
|
EndRoundMessage("#Terrorists_Escaped", ROUND_TERRORISTS_ESCAPED);
|
|
TerminateRound(tmDelay, WINSTATUS_TERRORISTS);
|
|
|
|
if (IsCareer())
|
|
{
|
|
QueueCareerRoundEndMenu(tmDelay, WINSTATUS_TERRORISTS);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool EXT_FUNC CHalfLifeMultiplay::Prison_PreventEscape(float tmDelay)
|
|
{
|
|
Broadcast("ctwin");
|
|
// CTs are rewarded based on how many terrorists have escaped...
|
|
m_iAccountCT += (1 - m_flEscapeRatio) * m_rgRewardAccountRules[RR_CTS_PREVENT_ESCAPE];
|
|
|
|
if (!m_bNeededPlayers)
|
|
{
|
|
m_iNumCTWins++;
|
|
// Update the clients team score
|
|
UpdateTeamScores();
|
|
}
|
|
|
|
EndRoundMessage("#CTs_PreventEscape", ROUND_CTS_PREVENT_ESCAPE);
|
|
TerminateRound(tmDelay, WINSTATUS_CTS);
|
|
|
|
if (IsCareer())
|
|
{
|
|
QueueCareerRoundEndMenu(tmDelay, WINSTATUS_CTS);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool EXT_FUNC CHalfLifeMultiplay::Prison_Neutralized(float tmDelay)
|
|
{
|
|
Broadcast("ctwin");
|
|
// CTs are rewarded based on how many terrorists have escaped...
|
|
m_iAccountCT += (1 - m_flEscapeRatio) * m_rgRewardAccountRules[RR_ESCAPING_TERRORISTS_NEUTRALIZED];
|
|
|
|
if (!m_bNeededPlayers)
|
|
{
|
|
m_iNumCTWins++;
|
|
// Update the clients team score
|
|
UpdateTeamScores();
|
|
}
|
|
|
|
EndRoundMessage("#Escaping_Terrorists_Neutralized", ROUND_ESCAPING_TERRORISTS_NEUTRALIZED);
|
|
TerminateRound(tmDelay, WINSTATUS_CTS);
|
|
|
|
if (IsCareer())
|
|
{
|
|
QueueCareerRoundEndMenu(tmDelay, WINSTATUS_CTS);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool EXT_FUNC CHalfLifeMultiplay::PrisonRoundEndCheck(int NumAliveTerrorist, int NumAliveCT, int NumDeadTerrorist, int NumDeadCT)
|
|
{
|
|
// checks to scenario Escaped Terrorist's
|
|
if (m_bMapHasEscapeZone)
|
|
{
|
|
m_flEscapeRatio = real_t(m_iHaveEscaped) / real_t(m_iNumEscapers);
|
|
|
|
if (m_flEscapeRatio >= m_flRequiredEscapeRatio)
|
|
{
|
|
return OnRoundEnd_Intercept(WINSTATUS_TERRORISTS, ROUND_TERRORISTS_ESCAPED, GetRoundRestartDelay());
|
|
}
|
|
else if (NumAliveTerrorist == 0 && m_flEscapeRatio < m_flRequiredEscapeRatio)
|
|
{
|
|
return OnRoundEnd_Intercept(WINSTATUS_CTS, ROUND_CTS_PREVENT_ESCAPE, GetRoundRestartDelay());
|
|
}
|
|
else if (NumAliveTerrorist == 0 && NumDeadTerrorist != 0 && m_iNumSpawnableCT > 0)
|
|
{
|
|
return OnRoundEnd_Intercept(WINSTATUS_CTS, ROUND_ESCAPING_TERRORISTS_NEUTRALIZED, GetRoundRestartDelay());
|
|
}
|
|
// else return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CHalfLifeMultiplay::Target_Bombed(float tmDelay)
|
|
{
|
|
Broadcast("terwin");
|
|
m_iAccountTerrorist += m_rgRewardAccountRules[RR_TARGET_BOMB];
|
|
|
|
if (!m_bNeededPlayers)
|
|
{
|
|
m_iNumTerroristWins++;
|
|
// Update the clients team score
|
|
UpdateTeamScores();
|
|
}
|
|
|
|
EndRoundMessage("#Target_Bombed", ROUND_TARGET_BOMB);
|
|
TerminateRound(tmDelay, WINSTATUS_TERRORISTS);
|
|
|
|
if (IsCareer())
|
|
{
|
|
QueueCareerRoundEndMenu(tmDelay, WINSTATUS_TERRORISTS);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CHalfLifeMultiplay::Target_Defused(float tmDelay)
|
|
{
|
|
Broadcast("ctwin");
|
|
|
|
#ifdef REGAMEDLL_ADD
|
|
if (old_bomb_defused_sound.value)
|
|
{
|
|
Broadcast("BOMBDEF");
|
|
}
|
|
#endif
|
|
|
|
m_iAccountCT += m_rgRewardAccountRules[RR_BOMB_DEFUSED];
|
|
m_iAccountTerrorist += m_rgRewardAccountRules[RR_BOMB_PLANTED];
|
|
|
|
if (!m_bNeededPlayers)
|
|
{
|
|
m_iNumCTWins++;
|
|
// Update the clients team score
|
|
UpdateTeamScores();
|
|
}
|
|
|
|
EndRoundMessage("#Bomb_Defused", ROUND_BOMB_DEFUSED);
|
|
TerminateRound(tmDelay, WINSTATUS_CTS);
|
|
|
|
if (IsCareer())
|
|
{
|
|
QueueCareerRoundEndMenu(tmDelay, WINSTATUS_CTS);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CHalfLifeMultiplay::BombRoundEndCheck()
|
|
{
|
|
// Check to see if the bomb target was hit or the bomb defused.. if so, then let's end the round!
|
|
if (m_bTargetBombed && m_bMapHasBombTarget)
|
|
{
|
|
return OnRoundEnd_Intercept(WINSTATUS_TERRORISTS, ROUND_TARGET_BOMB, GetRoundRestartDelay());
|
|
}
|
|
else if (m_bBombDefused && m_bMapHasBombTarget)
|
|
{
|
|
return OnRoundEnd_Intercept(WINSTATUS_CTS, ROUND_BOMB_DEFUSED, GetRoundRestartDelay());
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CHalfLifeMultiplay::Round_Cts(float tmDelay)
|
|
{
|
|
Broadcast("ctwin");
|
|
m_iAccountCT += m_rgRewardAccountRules[m_bMapHasBombTarget ? RR_BOMB_DEFUSED : RR_CTS_WIN];
|
|
|
|
if (!m_bNeededPlayers)
|
|
{
|
|
m_iNumCTWins++;
|
|
// Update the clients team score
|
|
UpdateTeamScores();
|
|
}
|
|
|
|
EndRoundMessage("#CTs_Win", ROUND_CTS_WIN);
|
|
TerminateRound(tmDelay, WINSTATUS_CTS);
|
|
|
|
if (IsCareer())
|
|
{
|
|
QueueCareerRoundEndMenu(tmDelay, WINSTATUS_CTS);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CHalfLifeMultiplay::Round_Ts(float tmDelay)
|
|
{
|
|
Broadcast("terwin");
|
|
m_iAccountTerrorist += m_rgRewardAccountRules[m_bMapHasBombTarget ? RR_BOMB_EXPLODED : RR_TERRORISTS_WIN];
|
|
|
|
if (!m_bNeededPlayers)
|
|
{
|
|
m_iNumTerroristWins++;
|
|
// Update the clients team score
|
|
UpdateTeamScores();
|
|
}
|
|
|
|
EndRoundMessage("#Terrorists_Win", ROUND_TERRORISTS_WIN);
|
|
TerminateRound(tmDelay, WINSTATUS_TERRORISTS);
|
|
|
|
if (IsCareer())
|
|
{
|
|
QueueCareerRoundEndMenu(tmDelay, WINSTATUS_TERRORISTS);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CHalfLifeMultiplay::Round_Draw(float tmDelay)
|
|
{
|
|
EndRoundMessage("#Round_Draw", ROUND_END_DRAW);
|
|
Broadcast("rounddraw");
|
|
TerminateRound(tmDelay, WINSTATUS_DRAW);
|
|
return true;
|
|
}
|
|
|
|
bool CHalfLifeMultiplay::TeamExterminationCheck(int NumAliveTerrorist, int NumAliveCT, int NumDeadTerrorist, int NumDeadCT)
|
|
{
|
|
if ((m_iNumCT > 0 && m_iNumSpawnableCT > 0) && (m_iNumTerrorist > 0 && m_iNumSpawnableTerrorist > 0))
|
|
{
|
|
if (NumAliveTerrorist == 0 && NumDeadTerrorist != 0 && NumAliveCT > 0)
|
|
{
|
|
CGrenade *pBomb = nullptr;
|
|
bool nowin = false;
|
|
|
|
while ((pBomb = UTIL_FindEntityByClassname(pBomb, "grenade")))
|
|
{
|
|
if (pBomb->m_bIsC4 && !pBomb->m_bJustBlew)
|
|
{
|
|
nowin = true;
|
|
#ifdef REGAMEDLL_FIXES
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
if (!nowin)
|
|
{
|
|
return OnRoundEnd_Intercept(WINSTATUS_CTS, ROUND_CTS_WIN, GetRoundRestartDelay());
|
|
}
|
|
}
|
|
|
|
// Terrorists WON
|
|
else if (NumAliveCT == 0 && NumDeadCT != 0)
|
|
{
|
|
return OnRoundEnd_Intercept(WINSTATUS_TERRORISTS, ROUND_TERRORISTS_WIN, GetRoundRestartDelay());
|
|
}
|
|
}
|
|
else if (NumAliveCT == 0 && NumAliveTerrorist == 0)
|
|
{
|
|
return OnRoundEnd_Intercept(WINSTATUS_DRAW, ROUND_END_DRAW, GetRoundRestartDelay());
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CHalfLifeMultiplay::Hostage_Rescue(float tmDelay)
|
|
{
|
|
Broadcast("ctwin");
|
|
m_iAccountCT += m_rgRewardAccountRules[RR_ALL_HOSTAGES_RESCUED];
|
|
|
|
if (!m_bNeededPlayers)
|
|
{
|
|
m_iNumCTWins++;
|
|
// Update the clients team score
|
|
UpdateTeamScores();
|
|
}
|
|
|
|
EndRoundMessage("#All_Hostages_Rescued", ROUND_ALL_HOSTAGES_RESCUED);
|
|
TerminateRound(tmDelay, WINSTATUS_CTS);
|
|
|
|
// tell the bots all the hostages have been rescued
|
|
if (TheBots)
|
|
{
|
|
TheBots->OnEvent(EVENT_ALL_HOSTAGES_RESCUED);
|
|
}
|
|
|
|
if (IsCareer())
|
|
{
|
|
if (TheCareerTasks)
|
|
{
|
|
TheCareerTasks->HandleEvent(EVENT_ALL_HOSTAGES_RESCUED);
|
|
}
|
|
}
|
|
|
|
if (IsCareer())
|
|
{
|
|
QueueCareerRoundEndMenu(tmDelay, WINSTATUS_CTS);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CHalfLifeMultiplay::HostageRescueRoundEndCheck()
|
|
{
|
|
// Check to see if 50% of the hostages have been rescued.
|
|
CBaseEntity *pHostage = nullptr;
|
|
int hostagesCount = 0;
|
|
|
|
// Assume that all hostages are either rescued or dead..
|
|
bool bHostageAlive = false;
|
|
|
|
while ((pHostage = UTIL_FindEntityByClassname(pHostage, "hostage_entity")))
|
|
{
|
|
hostagesCount++;
|
|
|
|
// We've found a live hostage. don't end the round
|
|
if (pHostage->IsAlive())
|
|
{
|
|
bHostageAlive = true;
|
|
}
|
|
}
|
|
|
|
#ifdef REGAMEDLL_ADD
|
|
if (hostagesCount > 0 && m_iHostagesRescued >= (hostagesCount * Q_min(hostages_rescued_ratio.value, 1.0f)))
|
|
{
|
|
return OnRoundEnd_Intercept(WINSTATUS_CTS, ROUND_ALL_HOSTAGES_RESCUED, GetRoundRestartDelay());
|
|
}
|
|
#else
|
|
// There are no hostages alive.. check to see if the CTs have rescued atleast 50% of them.
|
|
if (!bHostageAlive && hostagesCount > 0)
|
|
{
|
|
if (m_iHostagesRescued >= (hostagesCount * 0.5f))
|
|
{
|
|
return OnRoundEnd_Intercept(WINSTATUS_CTS, ROUND_ALL_HOSTAGES_RESCUED, GetRoundRestartDelay());
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return false;
|
|
}
|
|
|
|
void CHalfLifeMultiplay::SwapAllPlayers()
|
|
{
|
|
CBaseEntity *pEntity = nullptr;
|
|
|
|
while ((pEntity = UTIL_FindEntityByClassname(pEntity, "player")))
|
|
{
|
|
if (FNullEnt(pEntity->edict()))
|
|
break;
|
|
|
|
if (pEntity->IsDormant())
|
|
continue;
|
|
|
|
#ifdef REGAMEDLL_FIXES
|
|
// ignore HLTV
|
|
if (pEntity->IsProxy())
|
|
continue;
|
|
#endif
|
|
|
|
CBasePlayer *pPlayer = GetClassPtr<CCSPlayer>((CBasePlayer *)pEntity->pev);
|
|
pPlayer->SwitchTeam();
|
|
}
|
|
|
|
// Swap Team victories
|
|
SWAP(m_iNumTerroristWins, m_iNumCTWins);
|
|
|
|
// Update the clients team score
|
|
UpdateTeamScores();
|
|
}
|
|
|
|
LINK_HOOK_CLASS_VOID_CUSTOM_CHAIN2(CHalfLifeMultiplay, CSGameRules, BalanceTeams)
|
|
|
|
void EXT_FUNC CHalfLifeMultiplay::__API_HOOK(BalanceTeams)()
|
|
{
|
|
int iNumToSwap;
|
|
TeamName iTeamToSwap = UNASSIGNED;
|
|
|
|
m_bTeamBalanced = false;
|
|
|
|
// The ratio for teams is different for Assasination maps
|
|
if (m_bMapHasVIPSafetyZone)
|
|
{
|
|
int iDesiredNumCT, iDesiredNumTerrorist;
|
|
|
|
// uneven number of players
|
|
if ((m_iNumCT + m_iNumTerrorist) % 2 != 0)
|
|
iDesiredNumCT = int((m_iNumCT + m_iNumTerrorist) * 0.55f) + 1;
|
|
else
|
|
iDesiredNumCT = int((m_iNumCT + m_iNumTerrorist) / 2);
|
|
|
|
iDesiredNumTerrorist = (m_iNumCT + m_iNumTerrorist) - iDesiredNumCT;
|
|
|
|
if (m_iNumCT < iDesiredNumCT)
|
|
{
|
|
iTeamToSwap = TERRORIST;
|
|
iNumToSwap = iDesiredNumCT - m_iNumCT;
|
|
}
|
|
else if (m_iNumTerrorist < iDesiredNumTerrorist)
|
|
{
|
|
iTeamToSwap = CT;
|
|
iNumToSwap = iDesiredNumTerrorist - m_iNumTerrorist;
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (m_iNumCT > m_iNumTerrorist)
|
|
{
|
|
iTeamToSwap = CT;
|
|
iNumToSwap = (m_iNumCT - m_iNumTerrorist) / 2;
|
|
}
|
|
else if (m_iNumTerrorist > m_iNumCT)
|
|
{
|
|
iTeamToSwap = TERRORIST;
|
|
iNumToSwap = (m_iNumTerrorist - m_iNumCT) / 2;
|
|
}
|
|
else
|
|
{
|
|
// Teams are even.. Get out of here.
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Don't swap more than 4 players at a time.. This is a naive method of avoiding infinite loops.
|
|
if (iNumToSwap > 4)
|
|
iNumToSwap = 4;
|
|
|
|
// last person to join the server
|
|
int iHighestUserID = 0;
|
|
CBasePlayer *toSwap = nullptr;
|
|
|
|
for (int i = 1; i <= iNumToSwap; i++)
|
|
{
|
|
iHighestUserID = 0;
|
|
toSwap = nullptr;
|
|
|
|
CBaseEntity *pEntity = nullptr;
|
|
|
|
// search for player with highest UserID = most recently joined to switch over
|
|
while ((pEntity = UTIL_FindEntityByClassname(pEntity, "player")))
|
|
{
|
|
if (FNullEnt(pEntity->edict()))
|
|
break;
|
|
|
|
if (pEntity->IsDormant())
|
|
continue;
|
|
|
|
CBasePlayer *pPlayer = GetClassPtr<CCSPlayer>((CBasePlayer *)pEntity->pev);
|
|
|
|
if (pPlayer->CanSwitchTeam(iTeamToSwap) && GETPLAYERUSERID(pPlayer->edict()) > iHighestUserID)
|
|
{
|
|
iHighestUserID = GETPLAYERUSERID(pPlayer->edict());
|
|
toSwap = pPlayer;
|
|
}
|
|
}
|
|
|
|
if (toSwap) {
|
|
m_bTeamBalanced = true;
|
|
toSwap->SwitchTeam();
|
|
m_bTeamBalanced = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
LINK_HOOK_CLASS_VOID_CUSTOM_CHAIN2(CHalfLifeMultiplay, CSGameRules, CheckMapConditions)
|
|
|
|
void EXT_FUNC CHalfLifeMultiplay::__API_HOOK(CheckMapConditions)()
|
|
{
|
|
// Check to see if this map has a bomb target in it
|
|
if (UTIL_FindEntityByClassname(nullptr, "func_bomb_target"))
|
|
{
|
|
m_bMapHasBombTarget = true;
|
|
m_bMapHasBombZone = true;
|
|
}
|
|
else if (UTIL_FindEntityByClassname(nullptr, "info_bomb_target"))
|
|
{
|
|
m_bMapHasBombTarget = true;
|
|
m_bMapHasBombZone = false;
|
|
}
|
|
else
|
|
{
|
|
m_bMapHasBombTarget = false;
|
|
m_bMapHasBombZone = false;
|
|
}
|
|
|
|
// Check to see if this map has hostage rescue zones
|
|
m_bMapHasRescueZone = (UTIL_FindEntityByClassname(nullptr, "func_hostage_rescue") != nullptr);
|
|
|
|
// See if the map has func_buyzone entities
|
|
// Used by CBasePlayer::HandleSignals() to support maps without these entities
|
|
m_bMapHasBuyZone = (UTIL_FindEntityByClassname(nullptr, "func_buyzone") != nullptr);
|
|
|
|
// GOOSEMAN : See if this map has func_escapezone entities
|
|
m_bMapHasEscapeZone = (UTIL_FindEntityByClassname(nullptr, "func_escapezone") != nullptr);
|
|
|
|
// Check to see if this map has VIP safety zones
|
|
m_bMapHasVIPSafetyZone = (UTIL_FindEntityByClassname(nullptr, "func_vip_safetyzone") != nullptr);
|
|
}
|
|
|
|
LINK_HOOK_CLASS_VOID_CUSTOM_CHAIN2(CHalfLifeMultiplay, CSGameRules, RestartRound)
|
|
|
|
void EXT_FUNC CHalfLifeMultiplay::__API_HOOK(RestartRound)()
|
|
{
|
|
// tell bots that the round is restarting
|
|
if (TheBots)
|
|
{
|
|
TheBots->RestartRound();
|
|
}
|
|
|
|
if (g_pHostages)
|
|
{
|
|
g_pHostages->RestartRound();
|
|
}
|
|
|
|
#ifdef REGAMEDLL_FIXES
|
|
if (!m_bCompleteReset)
|
|
#endif
|
|
{
|
|
m_iTotalRoundsPlayed++;
|
|
}
|
|
|
|
ClearBodyQue();
|
|
|
|
// Hardlock the player accelaration to 5.0
|
|
CVAR_SET_FLOAT("sv_accelerate", 5.0);
|
|
CVAR_SET_FLOAT("sv_friction", 4.0);
|
|
CVAR_SET_FLOAT("sv_stopspeed", 75);
|
|
|
|
// Tabulate the number of players on each team.
|
|
m_iNumCT = CountTeamPlayers(CT);
|
|
m_iNumTerrorist = CountTeamPlayers(TERRORIST);
|
|
|
|
// reset the dropped bomb on everyone's radar
|
|
if (m_bMapHasBombTarget)
|
|
{
|
|
MESSAGE_BEGIN(MSG_ALL, gmsgBombPickup);
|
|
MESSAGE_END();
|
|
|
|
#ifdef REGAMEDLL_FIXES
|
|
if (m_iRoundTime > 0)
|
|
#endif
|
|
{
|
|
MESSAGE_BEGIN(MSG_ALL, gmsgShowTimer);
|
|
MESSAGE_END();
|
|
}
|
|
}
|
|
|
|
m_bBombDropped = FALSE;
|
|
|
|
// reset all players health for HLTV
|
|
MESSAGE_BEGIN(MSG_SPEC, gmsgHLTV);
|
|
WRITE_BYTE(0); // 0 = all players
|
|
WRITE_BYTE(100 | DRC_FLAG_FACEPLAYER); // 100 health + msg flag
|
|
MESSAGE_END();
|
|
|
|
// reset all players FOV for HLTV
|
|
MESSAGE_BEGIN(MSG_SPEC, gmsgHLTV);
|
|
WRITE_BYTE(0); // all players
|
|
WRITE_BYTE(0); // to default FOV value
|
|
MESSAGE_END();
|
|
|
|
auto shouldBalancedOnNextRound = []() -> bool
|
|
{
|
|
#ifdef REGAMEDLL_ADD
|
|
return autoteambalance.value == 1;
|
|
#else
|
|
return autoteambalance.value > 0;
|
|
#endif
|
|
};
|
|
|
|
if (shouldBalancedOnNextRound() && m_iUnBalancedRounds >= 1)
|
|
{
|
|
BalanceTeams();
|
|
}
|
|
|
|
if ((m_iNumCT - m_iNumTerrorist) >= 2 || (m_iNumTerrorist - m_iNumCT) >= 2)
|
|
{
|
|
m_iUnBalancedRounds++;
|
|
}
|
|
else
|
|
{
|
|
m_iUnBalancedRounds = 0;
|
|
}
|
|
|
|
// Warn the players of an impending auto-balance next round...
|
|
if (shouldBalancedOnNextRound() && m_iUnBalancedRounds == 1)
|
|
{
|
|
UTIL_ClientPrintAll(HUD_PRINTCENTER, "#Auto_Team_Balance_Next_Round");
|
|
}
|
|
#ifdef REGAMEDLL_ADD
|
|
else if (autoteambalance.value >= 2 && m_iUnBalancedRounds >= 1)
|
|
{
|
|
BalanceTeams();
|
|
}
|
|
#endif
|
|
|
|
if (m_bCompleteReset)
|
|
{
|
|
// bounds check
|
|
if (timelimit.value < 0)
|
|
{
|
|
CVAR_SET_FLOAT("mp_timelimit", 0);
|
|
}
|
|
|
|
m_flGameStartTime = gpGlobals->time;
|
|
|
|
// Reset timelimit
|
|
if (timelimit.value)
|
|
m_flTimeLimit = gpGlobals->time + (timelimit.value * 60);
|
|
|
|
// Reset total # of rounds played
|
|
m_iTotalRoundsPlayed = 0;
|
|
m_iMaxRounds = int(CVAR_GET_FLOAT("mp_maxrounds"));
|
|
|
|
if (m_iMaxRounds < 0)
|
|
{
|
|
m_iMaxRounds = 0;
|
|
CVAR_SET_FLOAT("mp_maxrounds", 0);
|
|
}
|
|
|
|
m_iMaxRoundsWon = int(CVAR_GET_FLOAT("mp_winlimit"));
|
|
|
|
if (m_iMaxRoundsWon < 0)
|
|
{
|
|
m_iMaxRoundsWon = 0;
|
|
CVAR_SET_FLOAT("mp_winlimit", 0);
|
|
}
|
|
|
|
// Reset score info
|
|
m_iNumTerroristWins = 0;
|
|
m_iNumCTWins = 0;
|
|
m_iNumConsecutiveTerroristLoses = 0;
|
|
m_iNumConsecutiveCTLoses = 0;
|
|
|
|
// Reset team scores
|
|
UpdateTeamScores();
|
|
|
|
// Reset the player stats
|
|
for (int i = 1; i <= gpGlobals->maxClients; i++)
|
|
{
|
|
CBasePlayer *pPlayer = UTIL_PlayerByIndex(i);
|
|
|
|
if (!UTIL_IsValidPlayer(pPlayer))
|
|
continue;
|
|
|
|
pPlayer->Reset();
|
|
}
|
|
|
|
if (TheBots)
|
|
{
|
|
TheBots->OnEvent(EVENT_NEW_MATCH);
|
|
}
|
|
}
|
|
|
|
m_bFreezePeriod = TRUE;
|
|
m_bRoundTerminating = false;
|
|
|
|
ReadMultiplayCvars();
|
|
|
|
float flAutoKickIdle = autokick_timeout.value;
|
|
|
|
// set the idlekick max time (in seconds)
|
|
if (flAutoKickIdle > 0)
|
|
m_fMaxIdlePeriod = flAutoKickIdle;
|
|
else
|
|
#ifdef REGAMEDLL_FIXES
|
|
m_fMaxIdlePeriod = (((m_iRoundTime < 60) ? 60 : m_iRoundTime) * 2);
|
|
#else
|
|
m_fMaxIdlePeriod = (m_iRoundTime * 2);
|
|
#endif
|
|
|
|
// This makes the round timer function as the intro timer on the client side
|
|
m_iRoundTimeSecs = m_iIntroRoundTime;
|
|
|
|
// Check to see if there's a mapping info paramater entity
|
|
if (g_pMapInfo)
|
|
g_pMapInfo->CheckMapInfo();
|
|
|
|
CheckMapConditions();
|
|
|
|
if (m_bMapHasEscapeZone)
|
|
{
|
|
// Will increase this later when we count how many Ts are starting
|
|
m_iNumEscapers = m_iHaveEscaped = 0;
|
|
|
|
if (m_iNumEscapeRounds >= 3)
|
|
{
|
|
SwapAllPlayers();
|
|
m_iNumEscapeRounds = 0;
|
|
}
|
|
|
|
// Increment the number of rounds played... After 8 rounds, the players will do a whole sale switch..
|
|
m_iNumEscapeRounds++;
|
|
}
|
|
|
|
if (m_bMapHasVIPSafetyZone)
|
|
{
|
|
PickNextVIP();
|
|
m_iConsecutiveVIP++;
|
|
}
|
|
|
|
int acct_tmp = 0;
|
|
CBaseEntity *hostage = nullptr;
|
|
|
|
while ((hostage = UTIL_FindEntityByClassname(hostage, "hostage_entity")))
|
|
{
|
|
if (acct_tmp >= 2000)
|
|
break;
|
|
|
|
CHostage *temp = static_cast<CHostage *>(hostage);
|
|
|
|
if (hostage->pev->solid != SOLID_NOT)
|
|
{
|
|
acct_tmp += m_rgRewardAccountRules[RR_TOOK_HOSTAGE];
|
|
|
|
if (hostage->pev->deadflag == DEAD_DEAD)
|
|
{
|
|
hostage->pev->deadflag = DEAD_RESPAWNABLE;
|
|
}
|
|
}
|
|
|
|
temp->RePosition();
|
|
}
|
|
|
|
// Scale up the loser bonus when teams fall into losing streaks
|
|
if (m_iRoundWinStatus == WINSTATUS_TERRORISTS) // terrorists won
|
|
{
|
|
// check to see if they just broke a losing streak
|
|
if (m_iNumConsecutiveTerroristLoses > 1)
|
|
{
|
|
// this is the default losing bonus
|
|
m_iLoserBonus = m_rgRewardAccountRules[RR_LOSER_BONUS_MIN];
|
|
}
|
|
|
|
m_iNumConsecutiveTerroristLoses = 0; // starting fresh
|
|
m_iNumConsecutiveCTLoses++; // increment the number of wins the CTs have had
|
|
}
|
|
else if (m_iRoundWinStatus == WINSTATUS_CTS)
|
|
{
|
|
// check to see if they just broke a losing streak
|
|
if (m_iNumConsecutiveCTLoses > 1)
|
|
{
|
|
// this is the default losing bonus
|
|
m_iLoserBonus = m_rgRewardAccountRules[RR_LOSER_BONUS_MIN];
|
|
}
|
|
|
|
m_iNumConsecutiveCTLoses = 0; // starting fresh
|
|
m_iNumConsecutiveTerroristLoses++; // increment the number of wins the Terrorists have had
|
|
}
|
|
|
|
// check if the losing team is in a losing streak & that the loser bonus hasen't maxed out.
|
|
if (m_iNumConsecutiveTerroristLoses > 1 && m_iLoserBonus < m_rgRewardAccountRules[RR_LOSER_BONUS_MAX])
|
|
{
|
|
// help out the team in the losing streak
|
|
m_iLoserBonus += m_rgRewardAccountRules[RR_LOSER_BONUS_ADD];
|
|
}
|
|
else if (m_iNumConsecutiveCTLoses > 1 && m_iLoserBonus < m_rgRewardAccountRules[RR_LOSER_BONUS_MAX])
|
|
{
|
|
// help out the team in the losing streak
|
|
m_iLoserBonus += m_rgRewardAccountRules[RR_LOSER_BONUS_ADD];
|
|
}
|
|
|
|
// assign the wining and losing bonuses
|
|
if (m_iRoundWinStatus == WINSTATUS_TERRORISTS) // terrorists won
|
|
{
|
|
m_iAccountTerrorist += acct_tmp;
|
|
m_iAccountCT += m_iLoserBonus;
|
|
}
|
|
else if (m_iRoundWinStatus == WINSTATUS_CTS) // CT Won
|
|
{
|
|
m_iAccountCT += acct_tmp;
|
|
|
|
if (!m_bMapHasEscapeZone)
|
|
{
|
|
// only give them the bonus if this isn't an escape map
|
|
m_iAccountTerrorist += m_iLoserBonus;
|
|
}
|
|
}
|
|
|
|
// Update CT account based on number of hostages rescued
|
|
m_iAccountCT += m_iHostagesRescued * m_rgRewardAccountRules[RR_RESCUED_HOSTAGE];
|
|
|
|
// Update individual players accounts and respawn players
|
|
|
|
// the round time stamp must be set before players are spawned
|
|
m_fRoundStartTime = m_fRoundStartTimeReal = gpGlobals->time;
|
|
|
|
// Adrian - No cash for anyone at first rounds! ( well, only the default. )
|
|
if (m_bCompleteReset)
|
|
{
|
|
// No extra cash!
|
|
m_iAccountTerrorist = m_iAccountCT = 0;
|
|
|
|
// We are starting fresh. So it's like no one has ever won or lost.
|
|
m_iNumTerroristWins = 0;
|
|
m_iNumCTWins = 0;
|
|
m_iNumConsecutiveTerroristLoses = 0;
|
|
m_iNumConsecutiveCTLoses = 0;
|
|
m_iLoserBonus = m_rgRewardAccountRules[RR_LOSER_BONUS_DEFAULT];
|
|
}
|
|
|
|
#ifdef REGAMEDLL_FIXES
|
|
// Respawn entities (glass, doors, etc..)
|
|
CleanUpMap();
|
|
#endif
|
|
|
|
// tell bots that the round is restarting
|
|
CBaseEntity *pEntity = nullptr;
|
|
while ((pEntity = UTIL_FindEntityByClassname(pEntity, "player")))
|
|
{
|
|
if (FNullEnt(pEntity->edict()))
|
|
break;
|
|
|
|
if (pEntity->IsDormant())
|
|
continue;
|
|
|
|
CBasePlayer *pPlayer = GetClassPtr<CCSPlayer>((CBasePlayer *)pEntity->pev);
|
|
|
|
pPlayer->m_iNumSpawns = 0;
|
|
pPlayer->m_bTeamChanged = false;
|
|
|
|
#ifndef REGAMEDLL_FIXES
|
|
// NOTE: unreachable code
|
|
if (!pPlayer->IsPlayer())
|
|
{
|
|
pPlayer->SyncRoundTimer();
|
|
}
|
|
#endif
|
|
|
|
if (pPlayer->m_iTeam == CT)
|
|
{
|
|
if (!pPlayer->m_bReceivesNoMoneyNextRound)
|
|
{
|
|
pPlayer->AddAccount(m_iAccountCT, RT_ROUND_BONUS);
|
|
}
|
|
}
|
|
else if (pPlayer->m_iTeam == TERRORIST)
|
|
{
|
|
// Add another potential escaper to the mix!
|
|
m_iNumEscapers++;
|
|
|
|
if (!pPlayer->m_bReceivesNoMoneyNextRound)
|
|
{
|
|
pPlayer->AddAccount(m_iAccountTerrorist, RT_ROUND_BONUS);
|
|
}
|
|
|
|
// If it's a prison scenario then remove the Ts guns
|
|
if (m_bMapHasEscapeZone)
|
|
{
|
|
// this will cause them to be reset with default weapons, armor, and items
|
|
pPlayer->m_bNotKilled = false;
|
|
}
|
|
}
|
|
|
|
if (pPlayer->m_iTeam != UNASSIGNED && pPlayer->m_iTeam != SPECTATOR)
|
|
{
|
|
#ifdef REGAMEDLL_FIXES
|
|
// remove the c4 if the player is carrying it
|
|
if (pPlayer->m_bHasC4) {
|
|
pPlayer->RemoveBomb();
|
|
}
|
|
#else
|
|
// drop the c4 if the player is carrying it
|
|
if (pPlayer->m_bHasC4) {
|
|
pPlayer->DropPlayerItem("weapon_c4");
|
|
}
|
|
#endif
|
|
|
|
pPlayer->RoundRespawn();
|
|
|
|
#ifdef REGAMEDLL_ADD
|
|
FireTargets("game_entity_restart", pPlayer, nullptr, USE_TOGGLE, 0.0);
|
|
#endif
|
|
}
|
|
|
|
// Gooseman : The following code fixes the HUD icon bug
|
|
// by removing the C4 and DEFUSER icons from the HUD regardless
|
|
// for EVERY player (regardless of what team they're on)
|
|
}
|
|
|
|
// Moved above the loop spawning the players
|
|
#ifndef REGAMEDLL_FIXES
|
|
CleanUpMap();
|
|
#endif
|
|
|
|
// Give C4 to the terrorists
|
|
if (m_bMapHasBombTarget
|
|
#ifdef REGAMEDLL_ADD
|
|
&& give_player_c4.value
|
|
#endif
|
|
)
|
|
{
|
|
GiveC4();
|
|
}
|
|
|
|
#ifdef REGAMEDLL_ADD
|
|
if (m_bMapHasBombTarget && (int)defuser_allocation.value == DEFUSERALLOCATION_RANDOM)
|
|
GiveDefuserToRandomPlayer();
|
|
#endif
|
|
|
|
if (TheBots)
|
|
{
|
|
TheBots->OnEvent(EVENT_BUY_TIME_START);
|
|
}
|
|
|
|
// Reset game variables
|
|
m_flIntermissionEndTime = 0;
|
|
m_flIntermissionStartTime = 0;
|
|
m_flRestartRoundTime = 0;
|
|
m_iAccountTerrorist = m_iAccountCT = 0;
|
|
m_iHostagesRescued = 0;
|
|
m_iHostagesTouched = 0;
|
|
m_iRoundWinStatus = WINSTATUS_NONE;
|
|
m_bTargetBombed = m_bBombDefused = false;
|
|
m_bLevelInitialized = false;
|
|
m_bCompleteReset = false;
|
|
|
|
#ifdef REGAMEDLL_ADD
|
|
FireTargets("game_round_start", nullptr, nullptr, USE_TOGGLE, 0.0);
|
|
#endif
|
|
}
|
|
|
|
BOOL CHalfLifeMultiplay::IsThereABomber()
|
|
{
|
|
CBaseEntity *pEntity = nullptr;
|
|
while ((pEntity = UTIL_FindEntityByClassname(pEntity, "player")))
|
|
{
|
|
if (FNullEnt(pEntity->edict()))
|
|
break;
|
|
|
|
if (pEntity->IsDormant())
|
|
continue;
|
|
|
|
CBasePlayer *pPlayer = GetClassPtr<CCSPlayer>((CBasePlayer *)pEntity->pev);
|
|
|
|
if (pPlayer->m_iTeam != CT && pPlayer->IsBombGuy())
|
|
{
|
|
// There you are.
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
// Didn't find a bomber.
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL CHalfLifeMultiplay::IsThereABomb()
|
|
{
|
|
CGrenade *pBomb = nullptr;
|
|
bool bFoundBomb = false;
|
|
|
|
while ((pBomb = UTIL_FindEntityByClassname(pBomb, "grenade")))
|
|
{
|
|
if (pBomb->m_bIsC4)
|
|
{
|
|
bFoundBomb = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (bFoundBomb || (UTIL_FindEntityByClassname(nullptr, "weapon_c4")))
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
LINK_HOOK_CLASS_CUSTOM_CHAIN(BOOL, CHalfLifeMultiplay, CSGameRules, TeamFull, (int team_id), team_id)
|
|
|
|
BOOL EXT_FUNC CHalfLifeMultiplay::__API_HOOK(TeamFull)(int team_id)
|
|
{
|
|
switch (team_id)
|
|
{
|
|
case TERRORIST:
|
|
return (m_iNumTerrorist >= m_iSpawnPointCount_Terrorist);
|
|
|
|
case CT:
|
|
return (m_iNumCT >= m_iSpawnPointCount_CT);
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
LINK_HOOK_CLASS_CUSTOM_CHAIN(BOOL, CHalfLifeMultiplay, CSGameRules, TeamStacked, (int newTeam_id, int curTeam_id), newTeam_id, curTeam_id)
|
|
|
|
// checks to see if the desired team is stacked, returns true if it is
|
|
BOOL EXT_FUNC CHalfLifeMultiplay::__API_HOOK(TeamStacked)(int newTeam_id, int curTeam_id)
|
|
{
|
|
// players are allowed to change to their own team
|
|
if (newTeam_id == curTeam_id)
|
|
return FALSE;
|
|
|
|
if (!m_iLimitTeams)
|
|
return FALSE;
|
|
|
|
switch (newTeam_id)
|
|
{
|
|
case TERRORIST:
|
|
if (curTeam_id != UNASSIGNED && curTeam_id != SPECTATOR)
|
|
return ((m_iNumTerrorist + 1) > (m_iNumCT + m_iLimitTeams - 1));
|
|
else
|
|
return ((m_iNumTerrorist + 1) > (m_iNumCT + m_iLimitTeams));
|
|
case CT:
|
|
if (curTeam_id != UNASSIGNED && curTeam_id != SPECTATOR)
|
|
return ((m_iNumCT + 1) > (m_iNumTerrorist + m_iLimitTeams - 1));
|
|
else
|
|
return ((m_iNumCT + 1) > (m_iNumTerrorist + m_iLimitTeams));
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
void CHalfLifeMultiplay::StackVIPQueue()
|
|
{
|
|
for (int i = MAX_VIP_QUEUES - 2; i > 0; i--)
|
|
{
|
|
if (m_pVIPQueue[i - 1])
|
|
{
|
|
if (!m_pVIPQueue[i])
|
|
{
|
|
m_pVIPQueue[i] = m_pVIPQueue[i + 1];
|
|
m_pVIPQueue[i + 1] = nullptr;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_pVIPQueue[i - 1] = m_pVIPQueue[i];
|
|
m_pVIPQueue[i] = m_pVIPQueue[i + 1];
|
|
m_pVIPQueue[i + 1] = nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool CHalfLifeMultiplay::IsVIPQueueEmpty()
|
|
{
|
|
for (int i = 0; i < MAX_VIP_QUEUES; i++)
|
|
{
|
|
CBasePlayer *toCheck = m_pVIPQueue[i];
|
|
if (toCheck && toCheck->m_iTeam != CT)
|
|
{
|
|
m_pVIPQueue[i] = nullptr;
|
|
}
|
|
}
|
|
|
|
StackVIPQueue();
|
|
return (!m_pVIPQueue[0] && !m_pVIPQueue[1] && !m_pVIPQueue[2] && !m_pVIPQueue[3] && !m_pVIPQueue[4]);
|
|
}
|
|
|
|
bool CHalfLifeMultiplay::AddToVIPQueue(CBasePlayer *toAdd)
|
|
{
|
|
for (int i = 0; i < MAX_VIP_QUEUES; i++)
|
|
{
|
|
CBasePlayer *toCheck = m_pVIPQueue[i];
|
|
if (toCheck && toCheck->m_iTeam != CT)
|
|
{
|
|
m_pVIPQueue[i] = nullptr;
|
|
}
|
|
}
|
|
|
|
StackVIPQueue();
|
|
|
|
if (toAdd->m_iTeam == CT)
|
|
{
|
|
int j;
|
|
for (j = 0; j < MAX_VIP_QUEUES; j++)
|
|
{
|
|
if (m_pVIPQueue[j] == toAdd)
|
|
{
|
|
ClientPrint(toAdd->pev, HUD_PRINTCENTER, "#Game_in_position", UTIL_dtos1(j + 1));
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
for (j = 0; j < MAX_VIP_QUEUES; j++)
|
|
{
|
|
if (!m_pVIPQueue[j])
|
|
{
|
|
m_pVIPQueue[j] = toAdd;
|
|
|
|
StackVIPQueue();
|
|
ClientPrint(toAdd->pev, HUD_PRINTCENTER, "#Game_added_position", UTIL_dtos1(j + 1));
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
ClientPrint(toAdd->pev, HUD_PRINTCENTER, "#All_VIP_Slots_Full");
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
void CHalfLifeMultiplay::ResetCurrentVIP()
|
|
{
|
|
char *infobuffer = GET_INFO_BUFFER(m_pVIP->edict());
|
|
int numSkins = AreRunningCZero() ? CZ_NUM_SKIN : CS_NUM_SKIN;
|
|
|
|
switch (RANDOM_LONG(0, numSkins))
|
|
{
|
|
case 1:
|
|
m_pVIP->m_iModelName = MODEL_GSG9;
|
|
m_pVIP->SetClientUserInfoModel(infobuffer, "gsg9");
|
|
break;
|
|
case 2:
|
|
m_pVIP->m_iModelName = MODEL_SAS;
|
|
m_pVIP->SetClientUserInfoModel(infobuffer, "sas");
|
|
break;
|
|
case 3:
|
|
m_pVIP->m_iModelName = MODEL_GIGN;
|
|
m_pVIP->SetClientUserInfoModel(infobuffer, "gign");
|
|
break;
|
|
case 4:
|
|
if (AreRunningCZero())
|
|
{
|
|
m_pVIP->m_iModelName = MODEL_SPETSNAZ;
|
|
m_pVIP->SetClientUserInfoModel(infobuffer, "spetsnaz");
|
|
break;
|
|
}
|
|
default:
|
|
m_pVIP->m_iModelName = MODEL_URBAN;
|
|
m_pVIP->SetClientUserInfoModel(infobuffer, "urban");
|
|
break;
|
|
}
|
|
|
|
m_pVIP->m_bIsVIP = false;
|
|
m_pVIP->m_bNotKilled = false;
|
|
}
|
|
|
|
void CHalfLifeMultiplay::PickNextVIP()
|
|
{
|
|
if (!IsVIPQueueEmpty())
|
|
{
|
|
// Remove the current VIP from his VIP status and make him a regular CT.
|
|
if (m_pVIP)
|
|
{
|
|
ResetCurrentVIP();
|
|
}
|
|
|
|
for (int i = 0; i < MAX_VIP_QUEUES; i++)
|
|
{
|
|
if (m_pVIPQueue[i])
|
|
{
|
|
m_pVIP = m_pVIPQueue[i];
|
|
m_pVIP->MakeVIP();
|
|
|
|
m_pVIPQueue[i] = nullptr; // remove this player from the VIP queue
|
|
StackVIPQueue(); // and re-organize the queue
|
|
m_iConsecutiveVIP = 0;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
// If it's been the same VIP for 3 rounds already.. then randomly pick a new one
|
|
else if (m_iConsecutiveVIP >= 3)
|
|
{
|
|
if (++m_iLastPick > m_iNumCT)
|
|
m_iLastPick = 1;
|
|
|
|
int iCount = 1;
|
|
|
|
CBaseEntity *pEntity = nullptr;
|
|
CBasePlayer *pLastPlayer = nullptr;
|
|
|
|
while ((pEntity = UTIL_FindEntityByClassname(pEntity, "player")))
|
|
{
|
|
if (FNullEnt(pEntity->edict()))
|
|
break;
|
|
|
|
if (pEntity->IsDormant())
|
|
continue;
|
|
|
|
CBasePlayer *pPlayer = GetClassPtr<CCSPlayer>((CBasePlayer *)pEntity->pev);
|
|
if (pPlayer->m_iTeam == CT && iCount == m_iLastPick)
|
|
{
|
|
if (pPlayer == m_pVIP && pLastPlayer)
|
|
pPlayer = pLastPlayer;
|
|
|
|
// Remove the current VIP from his VIP status and make him a regular CT.
|
|
if (m_pVIP)
|
|
{
|
|
ResetCurrentVIP();
|
|
}
|
|
|
|
pPlayer->MakeVIP();
|
|
m_iConsecutiveVIP = 0;
|
|
|
|
break;
|
|
}
|
|
|
|
if (pPlayer->m_iTeam == CT)
|
|
iCount++;
|
|
|
|
if (pPlayer->m_iTeam != SPECTATOR)
|
|
pLastPlayer = pPlayer;
|
|
}
|
|
}
|
|
// There is no VIP and there is no one waiting to be the VIP.. therefore just pick the first CT player we can find.
|
|
else if (m_pVIP == nullptr)
|
|
{
|
|
CBaseEntity *pEntity = nullptr;
|
|
|
|
while ((pEntity = UTIL_FindEntityByClassname(pEntity, "player")))
|
|
{
|
|
if (FNullEnt(pEntity->edict()))
|
|
break;
|
|
|
|
if (pEntity->IsDormant())
|
|
continue;
|
|
|
|
CBasePlayer *pPlayer = GetClassPtr<CCSPlayer>((CBasePlayer *)pEntity->pev);
|
|
if (pPlayer->m_iTeam == CT)
|
|
{
|
|
pPlayer->MakeVIP();
|
|
m_iConsecutiveVIP = 0;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
LINK_HOOK_CLASS_VOID_CUSTOM_CHAIN2(CHalfLifeMultiplay, CSGameRules, Think)
|
|
|
|
void EXT_FUNC CHalfLifeMultiplay::__API_HOOK(Think)()
|
|
{
|
|
MonitorTutorStatus();
|
|
m_VoiceGameMgr.Update(gpGlobals->frametime);
|
|
|
|
if (g_psv_clienttrace->value != 1.0f)
|
|
{
|
|
CVAR_SET_FLOAT("sv_clienttrace", 1);
|
|
}
|
|
|
|
if (!m_fRoundStartTime)
|
|
{
|
|
// initialize the timer time stamps, this happens once only
|
|
m_fRoundStartTime = m_fRoundStartTimeReal = gpGlobals->time;
|
|
}
|
|
|
|
if (m_flForceCameraValue != forcecamera.value
|
|
|| m_flForceChaseCamValue != forcechasecam.value
|
|
|| m_flFadeToBlackValue != fadetoblack.value)
|
|
{
|
|
MESSAGE_BEGIN(MSG_ALL, gmsgForceCam);
|
|
WRITE_BYTE(forcecamera.value != 0);
|
|
WRITE_BYTE(forcechasecam.value != 0);
|
|
WRITE_BYTE(fadetoblack.value == FADETOBLACK_STAY);
|
|
MESSAGE_END();
|
|
|
|
m_flForceCameraValue = forcecamera.value;
|
|
m_flForceChaseCamValue = forcechasecam.value;
|
|
m_flFadeToBlackValue = fadetoblack.value;
|
|
}
|
|
|
|
// Check game rules
|
|
if (CheckGameOver())
|
|
return;
|
|
|
|
// have we hit the timelimit?
|
|
if (CheckTimeLimit())
|
|
return;
|
|
|
|
// did somebody hit the fraglimit ?
|
|
if (CheckFragLimit())
|
|
return;
|
|
|
|
if (!IsCareer())
|
|
{
|
|
// have we hit the max rounds?
|
|
if (CheckMaxRounds())
|
|
return;
|
|
|
|
if (CheckWinLimit())
|
|
return;
|
|
}
|
|
|
|
if (!IsCareer() || (m_fCareerMatchMenuTime <= 0.0 || m_fCareerMatchMenuTime >= gpGlobals->time))
|
|
{
|
|
if (m_iStoredSpectValue != allow_spectators.value)
|
|
{
|
|
m_iStoredSpectValue = allow_spectators.value;
|
|
|
|
MESSAGE_BEGIN(MSG_ALL, gmsgAllowSpec);
|
|
WRITE_BYTE(int(allow_spectators.value));
|
|
MESSAGE_END();
|
|
}
|
|
|
|
// Check for the end of the round.
|
|
if (IsFreezePeriod())
|
|
{
|
|
CheckFreezePeriodExpired();
|
|
}
|
|
else
|
|
{
|
|
CheckRoundTimeExpired();
|
|
}
|
|
|
|
if (m_flRestartRoundTime > 0.0f && m_flRestartRoundTime <= gpGlobals->time)
|
|
{
|
|
if (!IsCareer() || !m_fCareerRoundMenuTime)
|
|
{
|
|
RestartRound();
|
|
}
|
|
else if (TheCareerTasks)
|
|
{
|
|
bool isBotSpeaking = false;
|
|
|
|
if (m_flRestartRoundTime + 10.0f > gpGlobals->time)
|
|
{
|
|
isBotSpeaking = IsBotSpeaking();
|
|
}
|
|
|
|
if (!isBotSpeaking)
|
|
{
|
|
if (m_fCareerMatchMenuTime == 0.0f && m_iCareerMatchWins)
|
|
{
|
|
bool canCTsWin = true;
|
|
bool canTsWin = true;
|
|
|
|
if (m_iNumCTWins < m_iCareerMatchWins || (m_iNumCTWins - m_iNumTerroristWins < m_iRoundWinDifference))
|
|
canCTsWin = false;
|
|
|
|
if (m_iNumTerroristWins < m_iCareerMatchWins || (m_iNumTerroristWins - m_iNumCTWins < m_iRoundWinDifference))
|
|
canTsWin = false;
|
|
|
|
if (!Q_strcmp(humans_join_team.string, "CT"))
|
|
{
|
|
if (!TheCareerTasks->AreAllTasksComplete())
|
|
{
|
|
canCTsWin = false;
|
|
}
|
|
}
|
|
else if (!TheCareerTasks->AreAllTasksComplete())
|
|
{
|
|
canTsWin = false;
|
|
}
|
|
|
|
if (canCTsWin || canTsWin)
|
|
{
|
|
m_fCareerRoundMenuTime = 0;
|
|
m_fCareerMatchMenuTime = gpGlobals->time + 3.0f;
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
m_bFreezePeriod = TRUE;
|
|
|
|
for (int i = 1; i <= gpGlobals->maxClients; i++)
|
|
{
|
|
CBasePlayer *pPlayer = UTIL_PlayerByIndex(i);
|
|
|
|
if (!UTIL_IsValidPlayer(pPlayer))
|
|
continue;
|
|
|
|
if (!pPlayer->IsBot())
|
|
{
|
|
MESSAGE_BEGIN(MSG_ONE, gmsgCZCareerHUD, nullptr, pPlayer->pev);
|
|
WRITE_STRING("ROUND");
|
|
WRITE_LONG(m_iNumCTWins);
|
|
WRITE_LONG(m_iNumTerroristWins);
|
|
WRITE_BYTE(m_iCareerMatchWins);
|
|
WRITE_BYTE(m_iRoundWinDifference);
|
|
WRITE_BYTE(m_iRoundWinStatus);
|
|
MESSAGE_END();
|
|
|
|
pPlayer->m_iHideHUD |= HIDEHUD_ALL;
|
|
m_flRestartRoundTime = gpGlobals->time + 100000.0;
|
|
|
|
UTIL_LogPrintf("Career Round %d %d %d %d\n", m_iRoundWinStatus, m_iNumCTWins, m_iNumTerroristWins, TheCareerTasks->AreAllTasksComplete());
|
|
break;
|
|
}
|
|
}
|
|
|
|
m_fCareerRoundMenuTime = 0;
|
|
}
|
|
}
|
|
|
|
if (TheTutor)
|
|
{
|
|
TheTutor->PurgeMessages();
|
|
}
|
|
}
|
|
|
|
CheckLevelInitialized();
|
|
|
|
if (gpGlobals->time > m_tmNextPeriodicThink)
|
|
{
|
|
CheckRestartRound();
|
|
m_tmNextPeriodicThink = gpGlobals->time + 1.0f;
|
|
|
|
if (g_psv_accelerate->value != 5.0f)
|
|
{
|
|
CVAR_SET_FLOAT("sv_accelerate", 5.0f);
|
|
}
|
|
|
|
if (g_psv_friction->value != 4.0f)
|
|
{
|
|
CVAR_SET_FLOAT("sv_friction", 4.0f);
|
|
}
|
|
|
|
if (g_psv_stopspeed->value != 75.0f)
|
|
{
|
|
CVAR_SET_FLOAT("sv_stopspeed", 75.0f);
|
|
}
|
|
|
|
m_iMaxRounds = int(maxrounds.value);
|
|
|
|
if (m_iMaxRounds < 0)
|
|
{
|
|
m_iMaxRounds = 0;
|
|
CVAR_SET_FLOAT("mp_maxrounds", 0);
|
|
}
|
|
|
|
m_iMaxRoundsWon = int(winlimit.value);
|
|
|
|
if (m_iMaxRoundsWon < 0)
|
|
{
|
|
m_iMaxRoundsWon = 0;
|
|
CVAR_SET_FLOAT("mp_winlimit", 0);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (m_fCareerMatchMenuTime + 10 <= gpGlobals->time || !IsBotSpeaking())
|
|
{
|
|
UTIL_CareerDPrintf("Ending career match...one team has won the specified number of rounds\n");
|
|
|
|
MESSAGE_BEGIN(MSG_ALL, gmsgCZCareer);
|
|
WRITE_STRING("MATCH");
|
|
WRITE_LONG(m_iNumCTWins);
|
|
WRITE_LONG(m_iNumTerroristWins);
|
|
MESSAGE_END();
|
|
|
|
MESSAGE_BEGIN(MSG_ALL, gmsgCZCareerHUD);
|
|
WRITE_STRING("MATCH");
|
|
WRITE_LONG(m_iNumCTWins);
|
|
WRITE_LONG(m_iNumTerroristWins);
|
|
WRITE_BYTE(m_iCareerMatchWins);
|
|
WRITE_BYTE(m_iRoundWinDifference);
|
|
WRITE_BYTE(m_iRoundWinStatus);
|
|
MESSAGE_END();
|
|
|
|
UTIL_LogPrintf("Career Match %d %d %d %d\n", m_iRoundWinStatus, m_iNumCTWins, m_iNumTerroristWins, TheCareerTasks->AreAllTasksComplete());
|
|
SERVER_COMMAND("setpause\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
bool CHalfLifeMultiplay::CheckGameOver()
|
|
{
|
|
// someone else quit the game already
|
|
if (m_bGameOver)
|
|
{
|
|
// bounds check
|
|
int time = int(CVAR_GET_FLOAT("mp_chattime"));
|
|
|
|
if (time < 1)
|
|
CVAR_SET_STRING("mp_chattime", "1");
|
|
|
|
else if (time > MAX_INTERMISSION_TIME)
|
|
CVAR_SET_STRING("mp_chattime", UTIL_dtos1(MAX_INTERMISSION_TIME));
|
|
|
|
m_flIntermissionEndTime = m_flIntermissionStartTime + mp_chattime.value;
|
|
|
|
// check to see if we should change levels now
|
|
if (m_flIntermissionEndTime < gpGlobals->time && !IsCareer())
|
|
{
|
|
if (!UTIL_HumansInGame() // if only bots, just change immediately
|
|
#ifdef REGAMEDLL_FIXES
|
|
|| IsMultiplayer()
|
|
#endif
|
|
|| m_iEndIntermissionButtonHit // check that someone has pressed a key, or the max intermission time is over
|
|
|| ((m_flIntermissionStartTime + MAX_INTERMISSION_TIME) < gpGlobals->time))
|
|
{
|
|
// intermission is over
|
|
ChangeLevel();
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CHalfLifeMultiplay::CheckTimeLimit()
|
|
{
|
|
if (timelimit.value < 0)
|
|
{
|
|
CVAR_SET_FLOAT("mp_timelimit", 0);
|
|
return false;
|
|
}
|
|
|
|
if (!IsCareer())
|
|
{
|
|
if (timelimit.value)
|
|
{
|
|
m_flTimeLimit = m_flGameStartTime + timelimit.value * 60.0f;
|
|
|
|
if (gpGlobals->time >= m_flTimeLimit)
|
|
{
|
|
ALERT(at_console, "Changing maps because time limit has been met\n");
|
|
GoToIntermission();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
#ifdef REGAMEDLL_ADD
|
|
static int lastTime = 0;
|
|
int timeRemaining = (int)(timelimit.value ? (m_flTimeLimit - gpGlobals->time) : 0);
|
|
|
|
// Updates once per second
|
|
if (timeRemaining != lastTime)
|
|
{
|
|
lastTime = timeRemaining;
|
|
g_engfuncs.pfnCvar_DirectSet(&timeleft, UTIL_VarArgs("%02d:%02d", timeRemaining / 60, timeRemaining % 60));
|
|
}
|
|
#endif
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CHalfLifeMultiplay::CheckMaxRounds()
|
|
{
|
|
if (m_iMaxRounds != 0 && m_iTotalRoundsPlayed >= m_iMaxRounds)
|
|
{
|
|
ALERT(at_console, "Changing maps due to maximum rounds have been met\n");
|
|
GoToIntermission();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CHalfLifeMultiplay::CheckWinLimit()
|
|
{
|
|
// has one team won the specified number of rounds?
|
|
if (m_iMaxRoundsWon != 0 && (m_iNumCTWins >= m_iMaxRoundsWon || m_iNumTerroristWins >= m_iMaxRoundsWon))
|
|
{
|
|
if ((m_iNumCTWins - m_iNumTerroristWins >= m_iRoundWinDifference) || (m_iNumTerroristWins - m_iNumCTWins >= m_iRoundWinDifference))
|
|
{
|
|
ALERT(at_console, "Changing maps...one team has won the specified number of rounds\n");
|
|
GoToIntermission();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CHalfLifeMultiplay::CheckFragLimit()
|
|
{
|
|
#ifdef REGAMEDLL_ADD
|
|
int fragsRemaining = 0;
|
|
|
|
if (fraglimit.value >= 1)
|
|
{
|
|
int bestFrags = fraglimit.value;
|
|
|
|
// check if any player is over the frag limit
|
|
for (int i = 1; i <= gpGlobals->maxClients; i++)
|
|
{
|
|
CBasePlayer *pPlayer = UTIL_PlayerByIndex(i);
|
|
|
|
if (!UTIL_IsValidPlayer(pPlayer) || pPlayer->has_disconnected)
|
|
continue;
|
|
|
|
if (pPlayer->pev->frags >= fraglimit.value)
|
|
{
|
|
ALERT(at_console, "Changing maps because frag limit has been met\n");
|
|
GoToIntermission();
|
|
return true;
|
|
}
|
|
|
|
int remain = (int)(fraglimit.value - pPlayer->pev->frags);
|
|
if (remain < bestFrags)
|
|
{
|
|
bestFrags = remain;
|
|
}
|
|
}
|
|
|
|
fragsRemaining = bestFrags;
|
|
}
|
|
|
|
static int lastFrags = 0;
|
|
|
|
// Updates when frags change
|
|
if (fragsRemaining != lastFrags)
|
|
{
|
|
lastFrags = fragsRemaining;
|
|
g_engfuncs.pfnCvar_DirectSet(&fragsleft, UTIL_VarArgs("%i", fragsRemaining));
|
|
}
|
|
#endif
|
|
|
|
return false;
|
|
}
|
|
|
|
void EXT_FUNC CHalfLifeMultiplay::OnRoundFreezeEnd()
|
|
{
|
|
// Log this information
|
|
UTIL_LogPrintf("World triggered \"Round_Start\"\n");
|
|
|
|
// Freeze period expired: kill the flag
|
|
m_bFreezePeriod = FALSE;
|
|
|
|
char CT_sentence[40];
|
|
char T_sentence[40];
|
|
|
|
switch (RANDOM_LONG(0, 3))
|
|
{
|
|
case 0:
|
|
Q_strncpy(CT_sentence, "%!MRAD_MOVEOUT", sizeof(CT_sentence));
|
|
Q_strncpy(T_sentence, "%!MRAD_MOVEOUT", sizeof(T_sentence));
|
|
break;
|
|
case 1:
|
|
Q_strncpy(CT_sentence, "%!MRAD_LETSGO", sizeof(CT_sentence));
|
|
Q_strncpy(T_sentence, "%!MRAD_LETSGO", sizeof(T_sentence));
|
|
break;
|
|
case 2:
|
|
Q_strncpy(CT_sentence, "%!MRAD_LOCKNLOAD", sizeof(CT_sentence));
|
|
Q_strncpy(T_sentence, "%!MRAD_LOCKNLOAD", sizeof(T_sentence));
|
|
break;
|
|
default:
|
|
Q_strncpy(CT_sentence, "%!MRAD_GO", sizeof(CT_sentence));
|
|
Q_strncpy(T_sentence, "%!MRAD_GO", sizeof(T_sentence));
|
|
break;
|
|
}
|
|
|
|
// More specific radio commands for the new scenarios : Prison & Assasination
|
|
if (m_bMapHasEscapeZone)
|
|
{
|
|
Q_strncpy(CT_sentence, "%!MRAD_ELIM", sizeof(CT_sentence));
|
|
Q_strncpy(T_sentence, "%!MRAD_GETOUT", sizeof(T_sentence));
|
|
}
|
|
else if (m_bMapHasVIPSafetyZone)
|
|
{
|
|
Q_strncpy(CT_sentence, "%!MRAD_VIP", sizeof(CT_sentence));
|
|
Q_strncpy(T_sentence, "%!MRAD_LOCKNLOAD", sizeof(T_sentence));
|
|
}
|
|
|
|
// Reset the round time
|
|
m_fRoundStartTimeReal = m_fRoundStartTime = gpGlobals->time;
|
|
|
|
// in seconds
|
|
m_iRoundTimeSecs = m_iRoundTime;
|
|
|
|
bool bCTPlayed = false;
|
|
bool bTPlayed = false;
|
|
|
|
if (TheCareerTasks)
|
|
{
|
|
TheCareerTasks->HandleEvent(EVENT_ROUND_START);
|
|
}
|
|
|
|
for (int i = 1; i <= gpGlobals->maxClients; i++)
|
|
{
|
|
CBasePlayer *plr = UTIL_PlayerByIndex(i);
|
|
|
|
if (!UTIL_IsValidPlayer(plr))
|
|
continue;
|
|
|
|
#ifndef REGAMEDLL_FIXES
|
|
if (plr->pev->flags == FL_DORMANT)
|
|
continue;
|
|
#endif
|
|
|
|
if (plr->m_iJoiningState == JOINED)
|
|
{
|
|
if (plr->m_iTeam == CT && !bCTPlayed)
|
|
{
|
|
plr->Radio(CT_sentence);
|
|
bCTPlayed = true;
|
|
}
|
|
else if (plr->m_iTeam == TERRORIST && !bTPlayed)
|
|
{
|
|
plr->Radio(T_sentence);
|
|
bTPlayed = true;
|
|
}
|
|
|
|
if (plr->m_iTeam != SPECTATOR)
|
|
{
|
|
plr->ResetMaxSpeed();
|
|
plr->m_bCanShoot = true;
|
|
}
|
|
}
|
|
|
|
plr->SyncRoundTimer();
|
|
}
|
|
|
|
if (TheBots)
|
|
{
|
|
TheBots->OnEvent(EVENT_ROUND_START);
|
|
}
|
|
|
|
if (TheCareerTasks)
|
|
{
|
|
TheCareerTasks->HandleEvent(EVENT_ROUND_START);
|
|
}
|
|
|
|
#ifdef REGAMEDLL_ADD
|
|
FireTargets("game_round_freeze_end", nullptr, nullptr, USE_TOGGLE, 0.0);
|
|
#endif
|
|
}
|
|
|
|
void CHalfLifeMultiplay::CheckFreezePeriodExpired()
|
|
{
|
|
if (GetRoundRemainingTime() > 0)
|
|
return;
|
|
|
|
g_ReGameHookchains.m_CSGameRules_OnRoundFreezeEnd.callChain(&CHalfLifeMultiplay::OnRoundFreezeEnd, this);
|
|
}
|
|
|
|
bool CHalfLifeMultiplay::Target_Saved(float tmDelay)
|
|
{
|
|
Broadcast("ctwin");
|
|
m_iAccountCT += m_rgRewardAccountRules[RR_TARGET_BOMB_SAVED];
|
|
|
|
#ifdef REGAMEDLL_FIXES
|
|
if (!m_bNeededPlayers)
|
|
{
|
|
m_iNumCTWins++;
|
|
|
|
// Update the clients team score
|
|
UpdateTeamScores();
|
|
}
|
|
#else
|
|
m_iNumCTWins++;
|
|
#endif
|
|
|
|
EndRoundMessage("#Target_Saved", ROUND_TARGET_SAVED);
|
|
TerminateRound(tmDelay, WINSTATUS_CTS);
|
|
|
|
if (IsCareer())
|
|
{
|
|
QueueCareerRoundEndMenu(tmDelay, WINSTATUS_CTS);
|
|
}
|
|
|
|
#ifndef REGAMEDLL_FIXES
|
|
UpdateTeamScores();
|
|
#endif
|
|
|
|
MarkLivingPlayersOnTeamAsNotReceivingMoneyNextRound(TERRORIST);
|
|
return true;
|
|
}
|
|
|
|
bool CHalfLifeMultiplay::Hostage_NotRescued(float tmDelay)
|
|
{
|
|
Broadcast("terwin");
|
|
m_iAccountTerrorist += m_rgRewardAccountRules[RR_HOSTAGE_NOT_RESCUED];
|
|
|
|
#ifdef REGAMEDLL_FIXES
|
|
if (!m_bNeededPlayers)
|
|
{
|
|
m_iNumTerroristWins++;
|
|
|
|
// Update the clients team score
|
|
UpdateTeamScores();
|
|
}
|
|
#else
|
|
m_iNumTerroristWins++;
|
|
#endif
|
|
|
|
EndRoundMessage("#Hostages_Not_Rescued", ROUND_HOSTAGE_NOT_RESCUED);
|
|
TerminateRound(tmDelay, WINSTATUS_TERRORISTS);
|
|
|
|
if (IsCareer())
|
|
{
|
|
QueueCareerRoundEndMenu(tmDelay, WINSTATUS_TERRORISTS);
|
|
}
|
|
|
|
#ifndef REGAMEDLL_FIXES
|
|
UpdateTeamScores();
|
|
#endif
|
|
|
|
MarkLivingPlayersOnTeamAsNotReceivingMoneyNextRound(CT);
|
|
return true;
|
|
}
|
|
|
|
bool CHalfLifeMultiplay::Prison_NotEscaped(float tmDelay)
|
|
{
|
|
Broadcast("ctwin");
|
|
|
|
#ifdef REGAMEDLL_FIXES
|
|
if (!m_bNeededPlayers)
|
|
{
|
|
m_iNumCTWins++;
|
|
|
|
// Update the clients team score
|
|
UpdateTeamScores();
|
|
}
|
|
#else
|
|
m_iNumCTWins++;
|
|
#endif
|
|
|
|
EndRoundMessage("#Terrorists_Not_Escaped", ROUND_TERRORISTS_NOT_ESCAPED);
|
|
TerminateRound(tmDelay, WINSTATUS_CTS);
|
|
|
|
if (IsCareer())
|
|
{
|
|
QueueCareerRoundEndMenu(tmDelay, WINSTATUS_CTS);
|
|
}
|
|
|
|
#ifndef REGAMEDLL_FIXES
|
|
UpdateTeamScores();
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
bool CHalfLifeMultiplay::VIP_NotEscaped(float tmDelay)
|
|
{
|
|
Broadcast("terwin");
|
|
m_iAccountTerrorist += m_rgRewardAccountRules[RR_VIP_NOT_ESCAPED];
|
|
|
|
#ifdef REGAMEDLL_FIXES
|
|
if (!m_bNeededPlayers)
|
|
{
|
|
m_iNumTerroristWins++;
|
|
|
|
// Update the clients team score
|
|
UpdateTeamScores();
|
|
}
|
|
#else
|
|
m_iNumTerroristWins++;
|
|
#endif
|
|
|
|
EndRoundMessage("#VIP_Not_Escaped", ROUND_VIP_NOT_ESCAPED);
|
|
TerminateRound(tmDelay, WINSTATUS_TERRORISTS);
|
|
|
|
if (IsCareer())
|
|
{
|
|
QueueCareerRoundEndMenu(tmDelay, WINSTATUS_TERRORISTS);
|
|
}
|
|
|
|
#ifndef REGAMEDLL_FIXES
|
|
UpdateTeamScores();
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CHalfLifeMultiplay::RoundOver(float tmDelay)
|
|
{
|
|
EndRoundMessage("#Cstrike_Tutor_Round_Over", ROUND_GAME_OVER);
|
|
Broadcast("rounddraw");
|
|
TerminateRound(tmDelay, WINSTATUS_DRAW);
|
|
return true;
|
|
}
|
|
|
|
void CHalfLifeMultiplay::CheckRoundTimeExpired()
|
|
{
|
|
if (HasRoundInfinite(SCENARIO_BLOCK_TIME_EXPRIRED))
|
|
return;
|
|
|
|
if (!HasRoundTimeExpired())
|
|
return;
|
|
|
|
#if 0
|
|
// Round time expired
|
|
float flEndRoundTime;
|
|
|
|
// Check to see if there's still a live C4 hanging around.. if so, wait until this C4 blows before ending the round
|
|
CGrenade *pBomb = (CGrenade *)UTIL_FindEntityByClassname(nullptr, "grenade");
|
|
|
|
if (pBomb)
|
|
{
|
|
if (!pBomb->m_bJustBlew)
|
|
flEndRoundTime = pBomb->m_flC4Blow;
|
|
else
|
|
flEndRoundTime = gpGlobals->time + 5.0f;
|
|
}
|
|
#endif
|
|
|
|
#ifdef REGAMEDLL_ADD
|
|
int scenarioFlags = UTIL_ReadFlags(round_infinite.string);
|
|
#else
|
|
// the icc compiler will cut out all of the code which refers to it
|
|
int scenarioFlags = 0;
|
|
#endif
|
|
|
|
// New code to get rid of round draws!!
|
|
if (!(scenarioFlags & SCENARIO_BLOCK_BOMB_TIME) && m_bMapHasBombTarget)
|
|
{
|
|
if (!OnRoundEnd_Intercept(WINSTATUS_CTS, ROUND_TARGET_SAVED, GetRoundRestartDelay()))
|
|
return;
|
|
}
|
|
else if (!(scenarioFlags & SCENARIO_BLOCK_HOSTAGE_RESCUE_TIME) && UTIL_FindEntityByClassname(nullptr, "hostage_entity"))
|
|
{
|
|
if (!OnRoundEnd_Intercept(WINSTATUS_TERRORISTS, ROUND_HOSTAGE_NOT_RESCUED, GetRoundRestartDelay()))
|
|
return;
|
|
}
|
|
else if (!(scenarioFlags & SCENARIO_BLOCK_PRISON_ESCAPE_TIME) && m_bMapHasEscapeZone)
|
|
{
|
|
if (!OnRoundEnd_Intercept(WINSTATUS_CTS, ROUND_TERRORISTS_NOT_ESCAPED, GetRoundRestartDelay()))
|
|
return;
|
|
}
|
|
else if (!(scenarioFlags & SCENARIO_BLOCK_VIP_ESCAPE_TIME) && m_bMapHasVIPSafetyZone)
|
|
{
|
|
if (!OnRoundEnd_Intercept(WINSTATUS_TERRORISTS, ROUND_VIP_NOT_ESCAPED, GetRoundRestartDelay()))
|
|
return;
|
|
}
|
|
#ifdef REGAMEDLL_ADD
|
|
else if (roundover.value)
|
|
{
|
|
switch ((int)roundover.value)
|
|
{
|
|
case 1:
|
|
default:
|
|
{
|
|
if (!OnRoundEnd_Intercept(WINSTATUS_DRAW, ROUND_GAME_OVER, GetRoundRestartDelay()))
|
|
return;
|
|
|
|
break;
|
|
}
|
|
case 2:
|
|
{
|
|
if (!OnRoundEnd_Intercept(WINSTATUS_TERRORISTS, ROUND_TERRORISTS_WIN, GetRoundRestartDelay()))
|
|
return;
|
|
|
|
break;
|
|
}
|
|
case 3:
|
|
{
|
|
if (!OnRoundEnd_Intercept(WINSTATUS_CTS, ROUND_CTS_WIN, GetRoundRestartDelay()))
|
|
return;
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// This is done so that the portion of code has enough time to do it's thing.
|
|
m_fRoundStartTime = gpGlobals->time + 60.0f;
|
|
}
|
|
|
|
void CHalfLifeMultiplay::CheckLevelInitialized()
|
|
{
|
|
if (!m_bLevelInitialized)
|
|
{
|
|
// Count the number of spawn points for each team
|
|
// This determines the maximum number of players allowed on each
|
|
m_iSpawnPointCount_Terrorist = UTIL_CountEntities("info_player_deathmatch");
|
|
m_iSpawnPointCount_CT = UTIL_CountEntities("info_player_start");
|
|
#ifdef REGAMEDLL_FIXES
|
|
m_bMapHasCameras = UTIL_CountEntities("trigger_camera");
|
|
#endif
|
|
m_bLevelInitialized = true;
|
|
}
|
|
}
|
|
|
|
bool CHalfLifeMultiplay::RestartRoundCheck(float tmDelay)
|
|
{
|
|
// log the restart
|
|
UTIL_LogPrintf("World triggered \"Restart_Round_(%i_%s)\"\n", (int)tmDelay, (tmDelay == 1) ? "second" : "seconds");
|
|
UTIL_LogPrintf("Team \"CT\" scored \"%i\" with \"%i\" players\n", m_iNumCTWins, m_iNumCT);
|
|
UTIL_LogPrintf("Team \"TERRORIST\" scored \"%i\" with \"%i\" players\n", m_iNumTerroristWins, m_iNumTerrorist);
|
|
|
|
// let the players know
|
|
UTIL_ClientPrintAll(HUD_PRINTCENTER, "#Game_will_restart_in", UTIL_dtos1(tmDelay), (tmDelay == 1) ? "SECOND" : "SECONDS");
|
|
UTIL_ClientPrintAll(HUD_PRINTCONSOLE, "#Game_will_restart_in_console", UTIL_dtos1(tmDelay), (tmDelay == 1) ? "SECOND" : "SECONDS");
|
|
|
|
m_flRestartRoundTime = gpGlobals->time + tmDelay;
|
|
m_bCompleteReset = true;
|
|
|
|
CVAR_SET_FLOAT("sv_restartround", 0);
|
|
CVAR_SET_FLOAT("sv_restart", 0);
|
|
|
|
CareerRestart();
|
|
return true;
|
|
}
|
|
|
|
void CHalfLifeMultiplay::CheckRestartRound()
|
|
{
|
|
// Restart the round if specified by the server
|
|
int iRestartDelay = int(restartround.value);
|
|
if (!iRestartDelay)
|
|
{
|
|
iRestartDelay = sv_restart.value;
|
|
}
|
|
|
|
if (iRestartDelay > 0)
|
|
{
|
|
#ifndef REGAMEDLL_ADD
|
|
if (iRestartDelay > 60)
|
|
iRestartDelay = 60;
|
|
#endif
|
|
|
|
OnRoundEnd_Intercept(WINSTATUS_NONE, ROUND_GAME_RESTART, iRestartDelay);
|
|
}
|
|
}
|
|
|
|
bool CHalfLifeMultiplay::HasRoundTimeExpired()
|
|
{
|
|
#ifdef REGAMEDLL_ADD
|
|
if (!m_iRoundTime)
|
|
return false;
|
|
#endif
|
|
|
|
// We haven't completed other objectives, so go for this!.
|
|
if (GetRoundRemainingTime() > 0 || m_iRoundWinStatus != WINSTATUS_NONE)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// If the bomb is planted, don't let the round timer end the round.
|
|
// keep going until the bomb explodes or is defused
|
|
if (!IsBombPlanted())
|
|
{
|
|
if (cv_bot_nav_edit.value == 0.0f || IS_DEDICATED_SERVER() || UTIL_HumansInGame() != 1)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CHalfLifeMultiplay::IsBombPlanted()
|
|
{
|
|
if (m_bMapHasBombTarget)
|
|
{
|
|
CGrenade *bomb = nullptr;
|
|
while ((bomb = UTIL_FindEntityByClassname(bomb, "grenade")))
|
|
{
|
|
if (bomb->m_bIsC4)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Living players on the given team need to be marked as not receiving any money next round.
|
|
void CHalfLifeMultiplay::MarkLivingPlayersOnTeamAsNotReceivingMoneyNextRound(int iTeam)
|
|
{
|
|
for (int i = 1; i <= gpGlobals->maxClients; i++)
|
|
{
|
|
CBasePlayer *pPlayer = UTIL_PlayerByIndex(i);
|
|
|
|
if (!UTIL_IsValidPlayer(pPlayer))
|
|
continue;
|
|
|
|
if (pPlayer->m_iTeam == iTeam)
|
|
{
|
|
if (pPlayer->pev->health > 0 && pPlayer->pev->deadflag == DEAD_NO)
|
|
{
|
|
pPlayer->m_bReceivesNoMoneyNextRound = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CHalfLifeMultiplay::CareerRestart()
|
|
{
|
|
m_bGameOver = false;
|
|
|
|
if (m_flRestartRoundTime == 0.0f)
|
|
{
|
|
m_flRestartRoundTime = gpGlobals->time + 1.0f;
|
|
}
|
|
|
|
// for reset everything
|
|
m_bCompleteReset = true;
|
|
m_fCareerRoundMenuTime = 0;
|
|
m_fCareerMatchMenuTime = 0;
|
|
|
|
if (TheCareerTasks)
|
|
{
|
|
TheCareerTasks->Reset(false);
|
|
}
|
|
|
|
m_bSkipSpawn = false;
|
|
|
|
for (int i = 1; i <= gpGlobals->maxClients; i++)
|
|
{
|
|
CBasePlayer *pPlayer = UTIL_PlayerByIndex(i);
|
|
|
|
if (!UTIL_IsValidPlayer(pPlayer))
|
|
continue;
|
|
|
|
if (!pPlayer->IsBot())
|
|
{
|
|
pPlayer->ForceClientDllUpdate();
|
|
}
|
|
}
|
|
}
|
|
|
|
BOOL CHalfLifeMultiplay::IsMultiplayer()
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL CHalfLifeMultiplay::IsDeathmatch()
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL CHalfLifeMultiplay::IsCoOp()
|
|
{
|
|
return gpGlobals->coop;
|
|
}
|
|
|
|
LINK_HOOK_CLASS_CUSTOM_CHAIN(BOOL, CHalfLifeMultiplay, CSGameRules, FShouldSwitchWeapon, (CBasePlayer *pPlayer, CBasePlayerItem *pWeapon), pPlayer, pWeapon)
|
|
|
|
BOOL EXT_FUNC CHalfLifeMultiplay::__API_HOOK(FShouldSwitchWeapon)(CBasePlayer *pPlayer, CBasePlayerItem *pWeapon)
|
|
{
|
|
if (!pWeapon->CanDeploy())
|
|
{
|
|
// that weapon can't deploy anyway.
|
|
return FALSE;
|
|
}
|
|
|
|
if (!pPlayer->m_pActiveItem)
|
|
{
|
|
// player doesn't have an active item!
|
|
return TRUE;
|
|
}
|
|
|
|
if (pPlayer->m_iAutoWepSwitch == 0)
|
|
return FALSE;
|
|
|
|
#ifdef REGAMEDLL_ADD
|
|
if (pPlayer->m_iAutoWepSwitch == 2 && (pPlayer->m_afButtonLast & (IN_ATTACK | IN_ATTACK2)))
|
|
return FALSE;
|
|
#endif
|
|
|
|
if (!pPlayer->m_pActiveItem->CanHolster())
|
|
{
|
|
// can't put away the active item.
|
|
return FALSE;
|
|
}
|
|
|
|
#ifdef REGAMEDLL_FIXES
|
|
if (pPlayer->pev->waterlevel == 3)
|
|
{
|
|
if (pWeapon->iFlags() & ITEM_FLAG_NOFIREUNDERWATER)
|
|
return FALSE;
|
|
|
|
if (pPlayer->m_pActiveItem->iFlags() & ITEM_FLAG_NOFIREUNDERWATER)
|
|
return TRUE;
|
|
}
|
|
#endif
|
|
|
|
if (pWeapon->iWeight() > pPlayer->m_pActiveItem->iWeight())
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
LINK_HOOK_CLASS_CUSTOM_CHAIN(BOOL, CHalfLifeMultiplay, CSGameRules, GetNextBestWeapon, (CBasePlayer *pPlayer, CBasePlayerItem *pCurrentWeapon), pPlayer, pCurrentWeapon)
|
|
|
|
BOOL EXT_FUNC CHalfLifeMultiplay::__API_HOOK(GetNextBestWeapon)(CBasePlayer *pPlayer, CBasePlayerItem *pCurrentWeapon)
|
|
{
|
|
CBasePlayerItem *pCheck;
|
|
CBasePlayerItem *pBest; // this will be used in the event that we don't find a weapon in the same category.
|
|
int iBestWeight;
|
|
int i;
|
|
bool inWater = pPlayer->pev->waterlevel == 3;
|
|
|
|
if (!pCurrentWeapon->CanHolster())
|
|
{
|
|
// can't put this gun away right now, so can't switch.
|
|
return FALSE;
|
|
}
|
|
|
|
iBestWeight = -1; // no weapon lower than -1 can be autoswitched to
|
|
pBest = nullptr;
|
|
|
|
for (i = 0; i < MAX_ITEM_TYPES; i++)
|
|
{
|
|
pCheck = pPlayer->m_rgpPlayerItems[i];
|
|
|
|
while (pCheck)
|
|
{
|
|
// don't reselect the weapon we're trying to get rid of
|
|
if (pCheck->iWeight() > iBestWeight && pCheck != pCurrentWeapon
|
|
#ifdef REGAMEDLL_FIXES
|
|
&& !(inWater && (pCheck->iFlags() & ITEM_FLAG_NOFIREUNDERWATER))
|
|
#endif
|
|
)
|
|
{
|
|
//ALERT (at_console, "Considering %s\n", STRING(pCheck->pev->classname));
|
|
// we keep updating the 'best' weapon just in case we can't find a weapon of the same weight
|
|
// that the player was using. This will end up leaving the player with his heaviest-weighted weapon.
|
|
|
|
if (pCheck->CanDeploy())
|
|
{
|
|
// if this weapon is useable, flag it as the best
|
|
iBestWeight = pCheck->iWeight();
|
|
pBest = pCheck;
|
|
}
|
|
}
|
|
|
|
pCheck = pCheck->m_pNext;
|
|
}
|
|
}
|
|
|
|
// if we make it here, we've checked all the weapons and found no useable
|
|
// weapon in the same catagory as the current weapon.
|
|
|
|
// if pBest is null, we didn't find ANYTHING. Shouldn't be possible- should always
|
|
// at least get the crowbar, but ya never know.
|
|
if (!pBest)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
pPlayer->SwitchWeapon(pBest);
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL CHalfLifeMultiplay::ClientCommand_DeadOrAlive(CBasePlayer *pPlayer, const char *pcmd)
|
|
{
|
|
return m_VoiceGameMgr.ClientCommand(pPlayer, pcmd);
|
|
}
|
|
|
|
BOOL CHalfLifeMultiplay::ClientCommand(CBasePlayer *pPlayer, const char *pcmd)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
BOOL CHalfLifeMultiplay::ClientConnected(edict_t *pEntity, const char *pszName, const char *pszAddress, char *szRejectReason)
|
|
{
|
|
m_VoiceGameMgr.ClientConnected(pEntity);
|
|
return TRUE;
|
|
}
|
|
|
|
void CHalfLifeMultiplay::UpdateGameMode(CBasePlayer *pPlayer)
|
|
{
|
|
MESSAGE_BEGIN(MSG_ONE, gmsgGameMode, nullptr, pPlayer->edict());
|
|
WRITE_BYTE(1);
|
|
MESSAGE_END();
|
|
}
|
|
|
|
void CHalfLifeMultiplay::InitHUD(CBasePlayer *pl)
|
|
{
|
|
int i;
|
|
|
|
// notify other clients of player joining the game
|
|
UTIL_LogPrintf("\"%s<%i><%s><>\" entered the game\n", STRING(pl->pev->netname), GETPLAYERUSERID(pl->edict()), GETPLAYERAUTHID(pl->edict()));
|
|
|
|
UpdateGameMode(pl);
|
|
|
|
if (!CVAR_GET_FLOAT("sv_cheats"))
|
|
{
|
|
MESSAGE_BEGIN(MSG_ONE, gmsgViewMode, nullptr, pl->edict());
|
|
MESSAGE_END();
|
|
}
|
|
|
|
// sending just one score makes the hud scoreboard active; otherwise
|
|
// it is just disabled for single play
|
|
MESSAGE_BEGIN(MSG_ONE, gmsgScoreInfo, nullptr, pl->edict());
|
|
WRITE_BYTE(ENTINDEX(pl->edict()));
|
|
WRITE_SHORT(0);
|
|
WRITE_SHORT(0);
|
|
WRITE_SHORT(0);
|
|
WRITE_SHORT(pl->m_iTeam);
|
|
MESSAGE_END();
|
|
|
|
MESSAGE_BEGIN(MSG_ONE, gmsgShadowIdx, nullptr, pl->edict());
|
|
WRITE_LONG(g_iShadowSprite);
|
|
MESSAGE_END();
|
|
|
|
if (IsCareer())
|
|
{
|
|
MESSAGE_BEGIN(MSG_ONE, gmsgCZCareer, nullptr, pl->edict());
|
|
WRITE_STRING("START");
|
|
WRITE_SHORT(m_iRoundTime);
|
|
MESSAGE_END();
|
|
}
|
|
else
|
|
SendMOTDToClient(pl->edict());
|
|
|
|
// loop through all active players and send their score info to the new client
|
|
for (i = 1; i <= gpGlobals->maxClients; i++)
|
|
{
|
|
// FIXME: Probably don't need to cast this just to read m_iDeaths
|
|
CBasePlayer *plr = UTIL_PlayerByIndex(i);
|
|
if (!UTIL_IsValidPlayer(plr))
|
|
continue;
|
|
|
|
MESSAGE_BEGIN(MSG_ONE, gmsgScoreInfo, nullptr, pl->edict());
|
|
WRITE_BYTE(i); // client number
|
|
WRITE_SHORT(int(plr->pev->frags));
|
|
WRITE_SHORT(plr->m_iDeaths);
|
|
WRITE_SHORT(0);
|
|
WRITE_SHORT(plr->m_iTeam);
|
|
MESSAGE_END();
|
|
}
|
|
|
|
MESSAGE_BEGIN(MSG_ONE, gmsgTeamScore, nullptr, pl->edict());
|
|
WRITE_STRING("TERRORIST");
|
|
WRITE_SHORT(m_iNumTerroristWins);
|
|
MESSAGE_END();
|
|
|
|
MESSAGE_BEGIN(MSG_ONE, gmsgTeamScore, nullptr, pl->edict());
|
|
WRITE_STRING("CT");
|
|
WRITE_SHORT(m_iNumCTWins);
|
|
MESSAGE_END();
|
|
|
|
MESSAGE_BEGIN(MSG_ONE, gmsgAllowSpec, nullptr, pl->edict());
|
|
WRITE_BYTE(int(allow_spectators.value));
|
|
MESSAGE_END();
|
|
|
|
MESSAGE_BEGIN(MSG_ONE, gmsgForceCam, nullptr, pl->edict());
|
|
WRITE_BYTE(forcecamera.value != 0);
|
|
WRITE_BYTE(forcechasecam.value != 0);
|
|
WRITE_BYTE(fadetoblack.value == FADETOBLACK_STAY);
|
|
MESSAGE_END();
|
|
|
|
if (m_bGameOver)
|
|
{
|
|
MESSAGE_BEGIN(MSG_ONE, SVC_INTERMISSION, nullptr, pl->edict());
|
|
MESSAGE_END();
|
|
}
|
|
|
|
for (i = 1; i <= gpGlobals->maxClients; i++)
|
|
{
|
|
CBasePlayer *plr = UTIL_PlayerByIndex(i);
|
|
|
|
if (!UTIL_IsValidPlayer(plr))
|
|
continue;
|
|
|
|
MESSAGE_BEGIN(MSG_ONE, gmsgTeamInfo, nullptr, pl->edict());
|
|
WRITE_BYTE(plr->entindex());
|
|
WRITE_STRING(GetTeamName(plr->m_iTeam));
|
|
MESSAGE_END();
|
|
|
|
plr->SetScoreboardAttributes(pl);
|
|
|
|
if (pl->entindex() != i)
|
|
{
|
|
#ifndef REGAMEDLL_FIXES
|
|
if (plr->IsDormant())
|
|
continue;
|
|
#endif
|
|
if (plr->pev->deadflag == DEAD_NO
|
|
#ifdef BUILD_LATEST_FIXES
|
|
&& plr->m_iTeam == pl->m_iTeam
|
|
#endif
|
|
)
|
|
{
|
|
MESSAGE_BEGIN(MSG_ONE, gmsgRadar, nullptr, pl->edict());
|
|
WRITE_BYTE(plr->entindex());
|
|
WRITE_COORD(plr->pev->origin.x);
|
|
WRITE_COORD(plr->pev->origin.y);
|
|
WRITE_COORD(plr->pev->origin.z);
|
|
MESSAGE_END();
|
|
}
|
|
}
|
|
|
|
#ifdef BUILD_LATEST
|
|
MESSAGE_BEGIN(MSG_ONE, gmsgHealthInfo, nullptr, pl->edict());
|
|
WRITE_BYTE(plr->entindex());
|
|
WRITE_LONG(plr->ShouldToShowHealthInfo(pl) ? plr->m_iClientHealth : -1 /* means that 'HP' field will be hidden */);
|
|
MESSAGE_END();
|
|
|
|
MESSAGE_BEGIN(MSG_ONE, gmsgAccount, nullptr, pl->edict());
|
|
WRITE_BYTE(plr->entindex());
|
|
WRITE_LONG(plr->ShouldToShowAccount(pl) ? plr->m_iAccount : -1 /* means that this 'Money' will be hidden */);
|
|
MESSAGE_END();
|
|
#endif // BUILD_LATEST
|
|
}
|
|
|
|
auto SendMsgBombDrop = [&pl](const int flag, const Vector& pos)
|
|
{
|
|
MESSAGE_BEGIN(MSG_ONE, gmsgBombDrop, nullptr, pl->edict());
|
|
WRITE_COORD(pos.x);
|
|
WRITE_COORD(pos.y);
|
|
WRITE_COORD(pos.z);
|
|
WRITE_BYTE(flag);
|
|
MESSAGE_END();
|
|
};
|
|
|
|
if (m_bBombDropped)
|
|
{
|
|
CBaseEntity *pWeaponC4 = UTIL_FindEntityByClassname(nullptr, "weapon_c4");
|
|
if (pWeaponC4)
|
|
{
|
|
SendMsgBombDrop(BOMB_FLAG_DROPPED, pWeaponC4->pev->origin);
|
|
}
|
|
}
|
|
#ifdef REGAMEDLL_FIXES
|
|
else
|
|
{
|
|
CGrenade *bomb = nullptr;
|
|
while ((bomb = UTIL_FindEntityByClassname(bomb, "grenade")))
|
|
{
|
|
if (bomb->m_bIsC4)
|
|
{
|
|
// if the bomb was planted, which will trigger the round timer to hide.
|
|
SendMsgBombDrop(BOMB_FLAG_PLANTED, bomb->pev->origin);
|
|
|
|
if (m_iRoundTime > 0 || GetRoundRemainingTime() >= 1.0f)
|
|
{
|
|
MESSAGE_BEGIN(MSG_ONE, gmsgShowTimer, nullptr, pl->pev);
|
|
MESSAGE_END();
|
|
}
|
|
else
|
|
{
|
|
// HACK HACK, we need to hide only the timer.
|
|
SendMsgBombDrop(BOMB_FLAG_PLANTED, g_vecZero);
|
|
MESSAGE_BEGIN(MSG_ONE, gmsgBombPickup, nullptr, pl->pev);
|
|
MESSAGE_END();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void CHalfLifeMultiplay::ClientDisconnected(edict_t *pClient)
|
|
{
|
|
if (pClient)
|
|
{
|
|
CBasePlayer *pPlayer = CBasePlayer::Instance(pClient);
|
|
if (pPlayer)
|
|
{
|
|
pPlayer->has_disconnected = true;
|
|
pPlayer->pev->deadflag = DEAD_DEAD;
|
|
pPlayer->SetScoreboardAttributes();
|
|
|
|
if (pPlayer->m_bHasC4)
|
|
{
|
|
pPlayer->DropPlayerItem("weapon_c4");
|
|
}
|
|
|
|
if (pPlayer->m_bHasDefuser)
|
|
{
|
|
#ifdef REGAMEDLL_FIXES
|
|
SpawnDefuser(pPlayer->pev->origin, nullptr);
|
|
#else
|
|
pPlayer->DropPlayerItem("item_thighpack"); // DropPlayerItem didn't handle item_thighpack
|
|
#endif
|
|
}
|
|
|
|
if (pPlayer->m_bIsVIP)
|
|
{
|
|
m_pVIP = nullptr;
|
|
}
|
|
|
|
pPlayer->m_iCurrentKickVote = 0;
|
|
|
|
if (pPlayer->m_iMapVote)
|
|
{
|
|
m_iMapVotes[pPlayer->m_iMapVote]--;
|
|
|
|
if (m_iMapVotes[pPlayer->m_iMapVote] < 0)
|
|
{
|
|
m_iMapVotes[pPlayer->m_iMapVote] = 0;
|
|
}
|
|
}
|
|
|
|
MESSAGE_BEGIN(MSG_ALL, gmsgScoreInfo);
|
|
WRITE_BYTE(ENTINDEX(pClient));
|
|
WRITE_SHORT(0);
|
|
WRITE_SHORT(0);
|
|
WRITE_SHORT(0);
|
|
WRITE_SHORT(0);
|
|
MESSAGE_END();
|
|
|
|
MESSAGE_BEGIN(MSG_ALL, gmsgTeamInfo);
|
|
WRITE_BYTE(ENTINDEX(pClient));
|
|
WRITE_STRING("UNASSIGNED");
|
|
MESSAGE_END();
|
|
|
|
MESSAGE_BEGIN(MSG_ALL, gmsgLocation);
|
|
WRITE_BYTE(ENTINDEX(pClient));
|
|
WRITE_STRING("");
|
|
MESSAGE_END();
|
|
|
|
char *team = GetTeam(pPlayer->m_iTeam);
|
|
|
|
FireTargets("game_playerleave", pPlayer, pPlayer, USE_TOGGLE, 0);
|
|
UTIL_LogPrintf("\"%s<%i><%s><%s>\" disconnected\n", STRING(pPlayer->pev->netname), GETPLAYERUSERID(pPlayer->edict()), GETPLAYERAUTHID(pPlayer->edict()), team);
|
|
|
|
// destroy all of the players weapons and items
|
|
pPlayer->RemoveAllItems(TRUE);
|
|
|
|
if (pPlayer->m_pObserver)
|
|
{
|
|
pPlayer->m_pObserver->SUB_Remove();
|
|
}
|
|
|
|
CBasePlayer *pObserver = nullptr;
|
|
while ((pObserver = UTIL_FindEntityByClassname(pObserver, "player")))
|
|
{
|
|
if (FNullEnt(pObserver->edict()))
|
|
break;
|
|
|
|
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)
|
|
{
|
|
int iMode = pObserver->pev->iuser1;
|
|
|
|
pObserver->pev->iuser1 = OBS_NONE;
|
|
#ifdef REGAMEDLL_FIXES
|
|
pObserver->m_flNextFollowTime = 0.0;
|
|
#endif
|
|
pObserver->Observer_SetMode(iMode);
|
|
}
|
|
}
|
|
|
|
#ifdef REGAMEDLL_FIXES
|
|
// Client is gone, make sure that his body disappeared and became not solid
|
|
pPlayer->MakeDormant();
|
|
#endif
|
|
|
|
}
|
|
}
|
|
|
|
CheckWinConditions();
|
|
}
|
|
|
|
LINK_HOOK_CLASS_CUSTOM_CHAIN(float, CHalfLifeMultiplay, CSGameRules, FlPlayerFallDamage, (CBasePlayer *pPlayer), pPlayer)
|
|
|
|
float EXT_FUNC CHalfLifeMultiplay::__API_HOOK(FlPlayerFallDamage)(CBasePlayer *pPlayer)
|
|
{
|
|
|
|
#ifdef REGAMEDLL_ADD
|
|
if (!falldamage.value)
|
|
{
|
|
return 0.0f;
|
|
}
|
|
#endif
|
|
|
|
pPlayer->m_flFallVelocity -= MAX_PLAYER_SAFE_FALL_SPEED;
|
|
return pPlayer->m_flFallVelocity * DAMAGE_FOR_FALL_SPEED * 1.25;
|
|
}
|
|
|
|
LINK_HOOK_CLASS_CUSTOM_CHAIN(BOOL, CHalfLifeMultiplay, CSGameRules, FPlayerCanTakeDamage, (CBasePlayer *pPlayer, CBaseEntity *pAttacker), pPlayer, pAttacker)
|
|
|
|
BOOL EXT_FUNC CHalfLifeMultiplay::__API_HOOK(FPlayerCanTakeDamage)(CBasePlayer *pPlayer, CBaseEntity *pAttacker)
|
|
{
|
|
if (!pAttacker || PlayerRelationship(pPlayer, pAttacker) != GR_TEAMMATE)
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
if (friendlyfire.value != 0.0f || pAttacker == pPlayer)
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
void CHalfLifeMultiplay::PlayerThink(CBasePlayer *pPlayer)
|
|
{
|
|
if (m_bGameOver)
|
|
{
|
|
// check for button presses
|
|
if (!IsCareer() && (pPlayer->m_afButtonPressed & (IN_DUCK | IN_ATTACK | IN_ATTACK2 | IN_USE | IN_JUMP)))
|
|
{
|
|
m_iEndIntermissionButtonHit = TRUE;
|
|
}
|
|
|
|
// clear attack/use commands from player
|
|
pPlayer->m_afButtonPressed = 0;
|
|
pPlayer->pev->button = 0;
|
|
pPlayer->m_afButtonReleased = 0;
|
|
}
|
|
|
|
if (!pPlayer->m_bCanShoot && !IsFreezePeriod())
|
|
{
|
|
pPlayer->m_bCanShoot = true;
|
|
}
|
|
|
|
if (pPlayer->m_pActiveItem && pPlayer->m_pActiveItem->IsWeapon())
|
|
{
|
|
CBasePlayerWeapon *pWeapon = static_cast<CBasePlayerWeapon *>(pPlayer->m_pActiveItem->GetWeaponPtr());
|
|
if (pWeapon->m_iWeaponState & WPNSTATE_SHIELD_DRAWN
|
|
#ifdef REGAMEDLL_ADD
|
|
|| ((pWeapon->iFlags() & ITEM_FLAG_NOFIREUNDERWATER) && pPlayer->pev->waterlevel == 3)
|
|
#endif
|
|
)
|
|
{
|
|
pPlayer->m_bCanShoot = false;
|
|
}
|
|
}
|
|
|
|
if (pPlayer->m_iMenu != Menu_ChooseTeam && pPlayer->m_iJoiningState == SHOWTEAMSELECT)
|
|
{
|
|
int slot = MENU_SLOT_TEAM_UNDEFINED;
|
|
if (!Q_stricmp(humans_join_team.string, "T"))
|
|
{
|
|
slot = MENU_SLOT_TEAM_TERRORIST;
|
|
}
|
|
else if (!Q_stricmp(humans_join_team.string, "CT"))
|
|
{
|
|
slot = MENU_SLOT_TEAM_CT;
|
|
}
|
|
#ifdef REGAMEDLL_ADD
|
|
else if (!Q_stricmp(humans_join_team.string, "any") && auto_join_team.value != 0.0f)
|
|
{
|
|
slot = MENU_SLOT_TEAM_RANDOM;
|
|
}
|
|
else if (!Q_stricmp(humans_join_team.string, "SPEC") && auto_join_team.value != 0.0f)
|
|
{
|
|
slot = MENU_SLOT_TEAM_SPECT;
|
|
}
|
|
#endif
|
|
else
|
|
{
|
|
if (allow_spectators.value == 0.0f)
|
|
ShowVGUIMenu(pPlayer, VGUI_Menu_Team, (MENU_KEY_1 | MENU_KEY_2 | MENU_KEY_5), "#Team_Select");
|
|
else
|
|
ShowVGUIMenu(pPlayer, VGUI_Menu_Team, (MENU_KEY_1 | MENU_KEY_2 | MENU_KEY_5 | MENU_KEY_6), "#Team_Select_Spect");
|
|
}
|
|
|
|
pPlayer->m_iMenu = Menu_ChooseTeam;
|
|
pPlayer->m_iJoiningState = PICKINGTEAM;
|
|
|
|
if (slot != MENU_SLOT_TEAM_UNDEFINED && !pPlayer->IsBot()
|
|
#ifdef REGAMEDLL_ADD
|
|
&& !(pPlayer->pev->flags & FL_FAKECLIENT)
|
|
#endif
|
|
)
|
|
{
|
|
#ifdef REGAMEDLL_ADD
|
|
m_bSkipShowMenu = (auto_join_team.value != 0.0f) && !(pPlayer->pev->flags & FL_FAKECLIENT);
|
|
|
|
if (HandleMenu_ChooseTeam(pPlayer, slot))
|
|
{
|
|
if (slot != MENU_SLOT_TEAM_SPECT && (IsCareer() || m_bSkipShowMenu))
|
|
{
|
|
// slot 6 - chooses randomize the appearance to model player
|
|
HandleMenu_ChooseAppearance(pPlayer, 6);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_bSkipShowMenu = false;
|
|
if (allow_spectators.value == 0.0f)
|
|
ShowVGUIMenu(pPlayer, VGUI_Menu_Team, (MENU_KEY_1 | MENU_KEY_2 | MENU_KEY_5), "#Team_Select");
|
|
else
|
|
ShowVGUIMenu(pPlayer, VGUI_Menu_Team, (MENU_KEY_1 | MENU_KEY_2 | MENU_KEY_5 | MENU_KEY_6), "#Team_Select_Spect");
|
|
}
|
|
|
|
m_bSkipShowMenu = false;
|
|
#else
|
|
HandleMenu_ChooseTeam(pPlayer, slot);
|
|
|
|
if (slot != MENU_SLOT_TEAM_SPECT && IsCareer())
|
|
{
|
|
// slot 6 - chooses randomize the appearance to model player
|
|
HandleMenu_ChooseAppearance(pPlayer, 6);
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
LINK_HOOK_CLASS_VOID_CUSTOM_CHAIN(CHalfLifeMultiplay, CSGameRules, PlayerSpawn, (CBasePlayer *pPlayer), pPlayer)
|
|
|
|
// Purpose: Player has just spawned. Equip them.
|
|
void EXT_FUNC CHalfLifeMultiplay::__API_HOOK(PlayerSpawn)(CBasePlayer *pPlayer)
|
|
{
|
|
// This is tied to the joining state (m_iJoiningState).. add it when the joining state is there.
|
|
if (pPlayer->m_bJustConnected)
|
|
return;
|
|
|
|
#ifdef REGAMEDLL_ADD
|
|
int iAutoWepSwitch = pPlayer->m_iAutoWepSwitch;
|
|
pPlayer->m_iAutoWepSwitch = 1;
|
|
#endif
|
|
|
|
pPlayer->pev->weapons |= (1 << WEAPON_SUIT);
|
|
pPlayer->OnSpawnEquip();
|
|
pPlayer->SetPlayerModel(false);
|
|
|
|
#ifdef REGAMEDLL_ADD
|
|
pPlayer->m_iAutoWepSwitch = iAutoWepSwitch;
|
|
#endif
|
|
|
|
#ifdef REGAMEDLL_ADD
|
|
if (respawn_immunitytime.value > 0)
|
|
pPlayer->SetSpawnProtection(respawn_immunitytime.value);
|
|
|
|
// remove any defusers left over from previous random if there is just one random one
|
|
if (m_bMapHasBombTarget && (int)defuser_allocation.value == DEFUSERALLOCATION_RANDOM)
|
|
pPlayer->RemoveDefuser();
|
|
#endif
|
|
}
|
|
|
|
LINK_HOOK_CLASS_CUSTOM_CHAIN(BOOL, CHalfLifeMultiplay, CSGameRules, FPlayerCanRespawn, (CBasePlayer *pPlayer), pPlayer)
|
|
|
|
BOOL EXT_FUNC CHalfLifeMultiplay::__API_HOOK(FPlayerCanRespawn)(CBasePlayer *pPlayer)
|
|
{
|
|
#ifdef REGAMEDLL_ADD
|
|
if (forcerespawn.value <= 0)
|
|
#endif
|
|
{
|
|
// Player cannot respawn twice in a round
|
|
if (pPlayer->m_iNumSpawns > 0)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
// Player cannot respawn until next round if more than 20 seconds in
|
|
|
|
// Tabulate the number of players on each team.
|
|
m_iNumCT = CountTeamPlayers(CT);
|
|
m_iNumTerrorist = CountTeamPlayers(TERRORIST);
|
|
|
|
if (m_iNumTerrorist > 0 && m_iNumCT > 0)
|
|
{
|
|
#ifdef REGAMEDLL_ADD
|
|
// means no time limit
|
|
if (GetRoundRespawnTime() != -1)
|
|
#endif
|
|
{
|
|
// TODO: to be correct, need use time the real one starts of round, m_fRoundStartTimeReal instead of it.
|
|
// m_fRoundStartTime able to extend the time to 60 seconds when there is a remaining time of round.
|
|
#ifdef REGAMEDLL_FIXES
|
|
if (gpGlobals->time > m_fRoundStartTimeReal + GetRoundRespawnTime())
|
|
#else
|
|
if (gpGlobals->time > m_fRoundStartTime + GetRoundRespawnTime())
|
|
#endif
|
|
{
|
|
// If this player just connected and fadetoblack is on, then maybe
|
|
// the server admin doesn't want him peeking around.
|
|
if (fadetoblack.value == FADETOBLACK_STAY)
|
|
{
|
|
UTIL_ScreenFade(pPlayer, Vector(0, 0, 0), 3, 3, 255, (FFADE_OUT | FFADE_STAYOUT));
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Player cannot respawn while in the Choose Appearance menu
|
|
if (pPlayer->m_iMenu == Menu_ChooseAppearance)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
float CHalfLifeMultiplay::FlPlayerSpawnTime(CBasePlayer *pPlayer)
|
|
{
|
|
return gpGlobals->time;
|
|
}
|
|
|
|
BOOL CHalfLifeMultiplay::AllowAutoTargetCrosshair()
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
// How many points awarded to anyone that kills this player?
|
|
int CHalfLifeMultiplay::IPointsForKill(CBasePlayer *pAttacker, CBasePlayer *pKilled)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
LINK_HOOK_CLASS_VOID_CUSTOM_CHAIN(CHalfLifeMultiplay, CSGameRules, PlayerKilled, (CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor), pVictim, pKiller, pInflictor)
|
|
|
|
// Someone/something killed this player
|
|
void EXT_FUNC CHalfLifeMultiplay::__API_HOOK(PlayerKilled)(CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor)
|
|
{
|
|
DeathNotice(pVictim, pKiller, pInflictor);
|
|
#ifdef REGAMEDLL_FIXES
|
|
pVictim->pev->flags &= ~FL_FROZEN;
|
|
#endif
|
|
pVictim->m_afPhysicsFlags &= ~PFLAG_ONTRAIN;
|
|
pVictim->m_iDeaths++;
|
|
pVictim->m_bNotKilled = false;
|
|
pVictim->m_bEscaped = false;
|
|
pVictim->m_iTrain = (TRAIN_NEW | TRAIN_OFF);
|
|
SET_VIEW(ENT(pVictim->pev), ENT(pVictim->pev));
|
|
|
|
CBasePlayer *peKiller = nullptr;
|
|
CBaseEntity *ktmp = CBaseEntity::Instance(pKiller);
|
|
|
|
if (ktmp && ktmp->Classify() == CLASS_PLAYER)
|
|
{
|
|
peKiller = static_cast<CBasePlayer *>(ktmp);
|
|
}
|
|
else if (ktmp && ktmp->Classify() == CLASS_VEHICLE)
|
|
{
|
|
CBasePlayer *pDriver = static_cast<CBasePlayer *>(((CFuncVehicle *)ktmp)->m_pDriver);
|
|
#ifdef REGAMEDLL_FIXES
|
|
if (pDriver && !pDriver->has_disconnected)
|
|
#else
|
|
if (pDriver)
|
|
#endif
|
|
{
|
|
pKiller = pDriver->pev;
|
|
peKiller = static_cast<CBasePlayer *>(pDriver);
|
|
}
|
|
}
|
|
|
|
FireTargets("game_playerdie", pVictim, pVictim, USE_TOGGLE, 0);
|
|
|
|
#ifdef REGAMEDLL_FIXES
|
|
// Did the player die from a fall?
|
|
if (pVictim->m_bitsDamageType & DMG_FALL)
|
|
{
|
|
// do nothing
|
|
}
|
|
else
|
|
#endif
|
|
// Did the player kill himself?
|
|
if (pVictim->pev == pKiller)
|
|
{
|
|
// Players lose a frag for killing themselves
|
|
pVictim->pev->frags -= 1;
|
|
}
|
|
else if (peKiller && peKiller->IsPlayer())
|
|
{
|
|
// if a player dies in a deathmatch game and the killer is a client, award the killer some points
|
|
CBasePlayer *killer = GetClassPtr<CCSPlayer>((CBasePlayer *)pKiller);
|
|
|
|
if (g_pGameRules->PlayerRelationship(pVictim, killer) == GR_TEAMMATE)
|
|
{
|
|
// if a player dies by from teammate
|
|
pKiller->frags -= IPointsForKill(peKiller, pVictim);
|
|
|
|
killer->AddAccount(PAYBACK_FOR_KILLED_TEAMMATES, RT_TEAMMATES_KILLED);
|
|
killer->m_iTeamKills++;
|
|
killer->m_bJustKilledTeammate = true;
|
|
|
|
ClientPrint(killer->pev, HUD_PRINTCENTER, "#Killed_Teammate");
|
|
ClientPrint(killer->pev, HUD_PRINTCONSOLE, "#Game_teammate_kills", UTIL_dtos1(killer->m_iTeamKills));
|
|
|
|
#ifdef REGAMEDLL_ADD
|
|
if (autokick.value && max_teamkills.value && killer->m_iTeamKills >= (int)max_teamkills.value)
|
|
#else
|
|
if (autokick.value && killer->m_iTeamKills == 3)
|
|
#endif
|
|
{
|
|
#ifdef REGAMEDLL_FIXES
|
|
ClientPrint(killer->pev, HUD_PRINTCONSOLE, "#Banned_For_Killing_Teammates");
|
|
#else
|
|
ClientPrint(killer->pev, HUD_PRINTCONSOLE, "#Banned_For_Killing_Teamates");
|
|
#endif
|
|
int iUserID = GETPLAYERUSERID(killer->edict());
|
|
if (iUserID != -1)
|
|
{
|
|
#ifdef REGAMEDLL_FIXES
|
|
SERVER_COMMAND(UTIL_VarArgs("kick #%d \"For killing too many teammates\"\n", iUserID));
|
|
#else
|
|
SERVER_COMMAND(UTIL_VarArgs("kick # %d\n", iUserID));
|
|
#endif
|
|
}
|
|
}
|
|
|
|
if (!(killer->m_flDisplayHistory & DHF_FRIEND_KILLED))
|
|
{
|
|
killer->m_flDisplayHistory |= DHF_FRIEND_KILLED;
|
|
killer->HintMessage("#Hint_careful_around_teammates");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// if a player dies in a deathmatch game and the killer is a client, award the killer some points
|
|
pKiller->frags += IPointsForKill(peKiller, pVictim);
|
|
|
|
if (pVictim->m_bIsVIP)
|
|
{
|
|
killer->HintMessage("#Hint_reward_for_killing_vip", TRUE);
|
|
killer->AddAccount(REWARD_KILLED_VIP, RT_VIP_KILLED);
|
|
|
|
MESSAGE_BEGIN(MSG_SPEC, SVC_DIRECTOR);
|
|
WRITE_BYTE(9);
|
|
WRITE_BYTE(DRC_CMD_EVENT);
|
|
WRITE_SHORT(ENTINDEX(pVictim->edict()));
|
|
WRITE_SHORT(ENTINDEX(ENT(pInflictor)));
|
|
WRITE_LONG(DRC_FLAG_PRIO_MASK | DRC_FLAG_DRAMATIC | DRC_FLAG_FINAL);
|
|
MESSAGE_END();
|
|
|
|
UTIL_LogPrintf("\"%s<%i><%s><TERRORIST>\" triggered \"Assassinated_The_VIP\"\n", STRING(killer->pev->netname), GETPLAYERUSERID(killer->edict()), GETPLAYERAUTHID(killer->edict()));
|
|
}
|
|
else
|
|
killer->AddAccount(REWARD_KILLED_ENEMY, RT_ENEMY_KILLED);
|
|
|
|
if (!(killer->m_flDisplayHistory & DHF_ENEMY_KILLED))
|
|
{
|
|
killer->m_flDisplayHistory |= DHF_ENEMY_KILLED;
|
|
killer->HintMessage("#Hint_win_round_by_killing_enemy");
|
|
}
|
|
}
|
|
|
|
FireTargets("game_playerkill", peKiller, peKiller, USE_TOGGLE, 0);
|
|
}
|
|
else
|
|
{
|
|
// killed by the world
|
|
pKiller->frags -= 1;
|
|
}
|
|
|
|
// update the scores
|
|
// killed scores
|
|
#ifndef REGAMEDLL_FIXES
|
|
MESSAGE_BEGIN(MSG_BROADCAST, gmsgScoreInfo);
|
|
#else
|
|
MESSAGE_BEGIN(MSG_ALL, gmsgScoreInfo);
|
|
#endif
|
|
WRITE_BYTE(ENTINDEX(pVictim->edict()));
|
|
WRITE_SHORT(int(pVictim->pev->frags));
|
|
WRITE_SHORT(pVictim->m_iDeaths);
|
|
WRITE_SHORT(0);
|
|
WRITE_SHORT(pVictim->m_iTeam);
|
|
MESSAGE_END();
|
|
|
|
// killers score, if it's a player
|
|
CBaseEntity *ep = CBaseEntity::Instance(pKiller);
|
|
|
|
if (ep && ep->Classify() == CLASS_PLAYER)
|
|
{
|
|
CBasePlayer *PK = static_cast<CBasePlayer *>(ep);
|
|
|
|
MESSAGE_BEGIN(MSG_ALL, gmsgScoreInfo);
|
|
WRITE_BYTE(ENTINDEX(PK->edict()));
|
|
WRITE_SHORT(int(PK->pev->frags));
|
|
WRITE_SHORT(PK->m_iDeaths);
|
|
WRITE_SHORT(0);
|
|
WRITE_SHORT(PK->m_iTeam);
|
|
MESSAGE_END();
|
|
|
|
// let the killer paint another decal as soon as he'd like.
|
|
PK->m_flNextDecalTime = gpGlobals->time;
|
|
}
|
|
}
|
|
|
|
LINK_HOOK_CLASS_VOID_CUSTOM_CHAIN(CHalfLifeMultiplay, CSGameRules, DeathNotice, (CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pevInflictor), pVictim, pKiller, pevInflictor)
|
|
|
|
void EXT_FUNC CHalfLifeMultiplay::__API_HOOK(DeathNotice)(CBasePlayer *pVictim, entvars_t *pevKiller, entvars_t *pevInflictor)
|
|
{
|
|
// by default, the player is killed by the world
|
|
CBasePlayer *pKiller = (pevKiller->flags & FL_CLIENT) ? CBasePlayer::Instance(pevKiller) : nullptr;
|
|
const char *killer_weapon_name = pVictim->GetKillerWeaponName(pevInflictor, pevKiller);
|
|
|
|
if (!TheTutor)
|
|
{
|
|
int iRarityOfKill = 0;
|
|
int iDeathMessageFlags = PLAYERDEATH_POSITION; // set default bit
|
|
|
|
CBasePlayer *pAssister = nullptr;
|
|
|
|
bool bFlashAssist = false;
|
|
if ((pAssister = CheckAssistsToKill(pKiller, pVictim, bFlashAssist)))
|
|
{
|
|
// Add a flag indicating the presence of an assistant who assisted in the kill
|
|
iDeathMessageFlags |= PLAYERDEATH_ASSISTANT;
|
|
}
|
|
|
|
iRarityOfKill = GetRarityOfKill(pKiller, pVictim, pAssister, killer_weapon_name, bFlashAssist);
|
|
if (iRarityOfKill != 0)
|
|
{
|
|
// Add a flag indicating that the attacker killed the victim in a rare way
|
|
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
|
|
if (pKiller && pKiller->IsPlayer() && PlayerRelationship(pVictim, pKiller) != GR_TEAMMATE)
|
|
{
|
|
int iPlayerIndexKiller = pKiller->entindex();
|
|
int iPlayerIndexVictim = pVictim->entindex();
|
|
|
|
pKiller->CSPlayer()->m_iNumKilledByUnanswered[iPlayerIndexVictim - 1] = 0;
|
|
pVictim->CSPlayer()->m_iNumKilledByUnanswered[iPlayerIndexKiller - 1]++;
|
|
}
|
|
}
|
|
|
|
// Did he kill himself?
|
|
if (pVictim->pev == pevKiller)
|
|
{
|
|
// killed self
|
|
char *team = GetTeam(pVictim->m_iTeam);
|
|
UTIL_LogPrintf("\"%s<%i><%s><%s>\" committed suicide with \"%s\"\n", STRING(pVictim->pev->netname), GETPLAYERUSERID(pVictim->edict()),
|
|
GETPLAYERAUTHID(pVictim->edict()), team, killer_weapon_name);
|
|
}
|
|
else if (pevKiller->flags & FL_CLIENT)
|
|
{
|
|
const char *VictimTeam = GetTeam(pVictim->m_iTeam);
|
|
const char *KillerTeam = (pKiller && pKiller->IsPlayer()) ? GetTeam(pKiller->m_iTeam) : "";
|
|
|
|
UTIL_LogPrintf("\"%s<%i><%s><%s>\" killed \"%s<%i><%s><%s>\" with \"%s\"\n", STRING(pevKiller->netname), GETPLAYERUSERID(ENT(pevKiller)), GETPLAYERAUTHID(ENT(pevKiller)),
|
|
KillerTeam, STRING(pVictim->pev->netname), GETPLAYERUSERID(pVictim->edict()), GETPLAYERAUTHID(pVictim->edict()), VictimTeam, killer_weapon_name);
|
|
}
|
|
else
|
|
{
|
|
// killed by the world
|
|
char *team = GetTeam(pVictim->m_iTeam);
|
|
UTIL_LogPrintf("\"%s<%i><%s><%s>\" committed suicide with \"%s\" (world)\n", STRING(pVictim->pev->netname), GETPLAYERUSERID(pVictim->edict()),
|
|
GETPLAYERAUTHID(pVictim->edict()), team, killer_weapon_name);
|
|
}
|
|
|
|
// TODO: It is called in CBasePlayer::Killed too, most likely,
|
|
// an unnecessary call. (Need investigate)
|
|
CheckWinConditions();
|
|
|
|
MESSAGE_BEGIN(MSG_SPEC, SVC_DIRECTOR);
|
|
WRITE_BYTE(9); // command length in bytes
|
|
WRITE_BYTE(DRC_CMD_EVENT); // player killed
|
|
WRITE_SHORT(ENTINDEX(pVictim->edict())); // index number of primary entity
|
|
|
|
if (pevInflictor)
|
|
WRITE_SHORT(ENTINDEX(ENT(pevInflictor))); // index number of secondary entity
|
|
else
|
|
WRITE_SHORT(ENTINDEX(ENT(pevKiller))); // index number of secondary entity
|
|
|
|
if (pVictim->m_bHeadshotKilled)
|
|
WRITE_LONG(9 | DRC_FLAG_DRAMATIC | DRC_FLAG_SLOWMOTION);
|
|
else
|
|
WRITE_LONG(7 | DRC_FLAG_DRAMATIC); // eventflags (priority and flags)
|
|
|
|
MESSAGE_END();
|
|
}
|
|
|
|
LINK_HOOK_CLASS_VOID_CUSTOM_CHAIN(CHalfLifeMultiplay, CSGameRules, PlayerGotWeapon, (CBasePlayer *pPlayer, CBasePlayerItem *pWeapon), pPlayer, pWeapon)
|
|
|
|
// Player has grabbed a weapon that was sitting in the world
|
|
void EXT_FUNC CHalfLifeMultiplay::__API_HOOK(PlayerGotWeapon)(CBasePlayer *pPlayer, CBasePlayerItem *pWeapon)
|
|
{
|
|
;
|
|
}
|
|
|
|
// What is the time in the future at which this weapon may spawn?
|
|
float CHalfLifeMultiplay::FlWeaponRespawnTime(CBasePlayerItem *pWeapon)
|
|
{
|
|
#ifdef REGAMEDLL_ADD
|
|
return gpGlobals->time + weapon_respawn_time.value;
|
|
#else
|
|
return gpGlobals->time + WEAPON_RESPAWN_TIME;
|
|
#endif
|
|
}
|
|
|
|
// Returns 0 if the weapon can respawn now,
|
|
// otherwise it returns the time at which it can try to spawn again.
|
|
float CHalfLifeMultiplay::FlWeaponTryRespawn(CBasePlayerItem *pWeapon)
|
|
{
|
|
if (pWeapon && pWeapon->m_iId && (pWeapon->iFlags() & ITEM_FLAG_LIMITINWORLD))
|
|
{
|
|
if (NUMBER_OF_ENTITIES() < (gpGlobals->maxEntities - ENTITY_INTOLERANCE))
|
|
return 0;
|
|
|
|
// we're past the entity tolerance level, so delay the respawn
|
|
return FlWeaponRespawnTime(pWeapon);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// Where should this weapon spawn?
|
|
// Some game variations may choose to randomize spawn locations
|
|
Vector CHalfLifeMultiplay::VecWeaponRespawnSpot(CBasePlayerItem *pWeapon)
|
|
{
|
|
return pWeapon->pev->origin;
|
|
}
|
|
|
|
// Any conditions inhibiting the respawning of this weapon?
|
|
int CHalfLifeMultiplay::WeaponShouldRespawn(CBasePlayerItem *pWeapon)
|
|
{
|
|
if (pWeapon->pev->spawnflags & SF_NORESPAWN)
|
|
{
|
|
return GR_WEAPON_RESPAWN_NO;
|
|
}
|
|
|
|
return GR_WEAPON_RESPAWN_YES;
|
|
}
|
|
|
|
LINK_HOOK_CLASS_CUSTOM_CHAIN(BOOL, CHalfLifeMultiplay, CSGameRules, CanHavePlayerItem, (CBasePlayer *pPlayer, CBasePlayerItem *pItem), pPlayer, pItem)
|
|
|
|
// Returns FALSE if the player is not allowed to pick up this weapon
|
|
BOOL EXT_FUNC CHalfLifeMultiplay::__API_HOOK(CanHavePlayerItem)(CBasePlayer *pPlayer, CBasePlayerItem *pItem)
|
|
{
|
|
return CGameRules::CanHavePlayerItem(pPlayer, pItem);
|
|
}
|
|
|
|
BOOL CHalfLifeMultiplay::CanHaveItem(CBasePlayer *pPlayer, CItem *pItem)
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
void CHalfLifeMultiplay::PlayerGotItem(CBasePlayer *pPlayer, CItem *pItem)
|
|
{
|
|
;
|
|
}
|
|
|
|
int CHalfLifeMultiplay::ItemShouldRespawn(CItem *pItem)
|
|
{
|
|
if (pItem->pev->spawnflags & SF_NORESPAWN)
|
|
{
|
|
return GR_ITEM_RESPAWN_NO;
|
|
}
|
|
|
|
return GR_ITEM_RESPAWN_YES;
|
|
}
|
|
|
|
// At what time in the future may this Item respawn?
|
|
float CHalfLifeMultiplay::FlItemRespawnTime(CItem *pItem)
|
|
{
|
|
#ifdef REGAMEDLL_ADD
|
|
return gpGlobals->time + item_respawn_time.value;
|
|
#else
|
|
return gpGlobals->time + ITEM_RESPAWN_TIME;
|
|
#endif
|
|
}
|
|
|
|
// Where should this item respawn?
|
|
// Some game variations may choose to randomize spawn locations
|
|
Vector CHalfLifeMultiplay::VecItemRespawnSpot(CItem *pItem)
|
|
{
|
|
return pItem->pev->origin;
|
|
}
|
|
|
|
void CHalfLifeMultiplay::PlayerGotAmmo(CBasePlayer *pPlayer, char *szName, int iCount)
|
|
{
|
|
;
|
|
}
|
|
|
|
BOOL CHalfLifeMultiplay::IsAllowedToSpawn(CBaseEntity *pEntity)
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
int CHalfLifeMultiplay::AmmoShouldRespawn(CBasePlayerAmmo *pAmmo)
|
|
{
|
|
if (pAmmo->pev->spawnflags & SF_NORESPAWN)
|
|
{
|
|
return GR_AMMO_RESPAWN_NO;
|
|
}
|
|
|
|
return GR_AMMO_RESPAWN_YES;
|
|
}
|
|
|
|
float CHalfLifeMultiplay::FlAmmoRespawnTime(CBasePlayerAmmo *pAmmo)
|
|
{
|
|
#ifdef REGAMEDLL_ADD
|
|
return gpGlobals->time + ammo_respawn_time.value;
|
|
#else
|
|
return gpGlobals->time + AMMO_RESPAWN_TIME;
|
|
#endif
|
|
}
|
|
|
|
Vector CHalfLifeMultiplay::VecAmmoRespawnSpot(CBasePlayerAmmo *pAmmo)
|
|
{
|
|
return pAmmo->pev->origin;
|
|
}
|
|
|
|
float CHalfLifeMultiplay::FlHealthChargerRechargeTime()
|
|
{
|
|
return 60;
|
|
}
|
|
|
|
float CHalfLifeMultiplay::FlHEVChargerRechargeTime()
|
|
{
|
|
return 30;
|
|
}
|
|
|
|
LINK_HOOK_CLASS_CUSTOM_CHAIN(int, CHalfLifeMultiplay, CSGameRules, DeadPlayerWeapons, (CBasePlayer *pPlayer), pPlayer)
|
|
|
|
int EXT_FUNC CHalfLifeMultiplay::__API_HOOK(DeadPlayerWeapons)(CBasePlayer *pPlayer)
|
|
{
|
|
#ifdef REGAMEDLL_ADD
|
|
switch ((int)weapondrop.value)
|
|
{
|
|
case 3:
|
|
return GR_PLR_DROP_GUN_ALL;
|
|
case 2:
|
|
break;
|
|
case 1:
|
|
return GR_PLR_DROP_GUN_BEST;
|
|
default:
|
|
return GR_PLR_DROP_GUN_NO;
|
|
}
|
|
#endif
|
|
return GR_PLR_DROP_GUN_ACTIVE; // keep original value in return
|
|
}
|
|
|
|
int CHalfLifeMultiplay::DeadPlayerAmmo(CBasePlayer *pPlayer)
|
|
{
|
|
#ifdef REGAMEDLL_ADD
|
|
if (ammodrop.value == 0.0f)
|
|
return GR_PLR_DROP_AMMO_NO;
|
|
#endif
|
|
|
|
return GR_PLR_DROP_AMMO_ACTIVE;
|
|
}
|
|
|
|
LINK_HOOK_CLASS_CUSTOM_CHAIN(edict_t *, CHalfLifeMultiplay, CSGameRules, GetPlayerSpawnSpot, (CBasePlayer *pPlayer), pPlayer)
|
|
|
|
edict_t *EXT_FUNC CHalfLifeMultiplay::__API_HOOK(GetPlayerSpawnSpot)(CBasePlayer *pPlayer)
|
|
{
|
|
// gat valid spawn point
|
|
edict_t *pentSpawnSpot = CGameRules::GetPlayerSpawnSpot(pPlayer);
|
|
|
|
if (IsMultiplayer())
|
|
{
|
|
if (pentSpawnSpot->v.target)
|
|
{
|
|
FireTargets(STRING(pentSpawnSpot->v.target), pPlayer, pPlayer, USE_TOGGLE, 0);
|
|
}
|
|
}
|
|
|
|
return pentSpawnSpot;
|
|
}
|
|
|
|
int CHalfLifeMultiplay::PlayerRelationship(CBasePlayer *pPlayer, CBaseEntity *pTarget)
|
|
{
|
|
if (pPlayer == pTarget)
|
|
return GR_TEAMMATE;
|
|
|
|
#ifdef REGAMEDLL_ADD
|
|
if (IsFreeForAll())
|
|
{
|
|
return GR_NOTTEAMMATE;
|
|
}
|
|
#endif
|
|
|
|
if (!pPlayer || !pTarget)
|
|
{
|
|
return GR_NOTTEAMMATE;
|
|
}
|
|
|
|
if (!pTarget->IsPlayer())
|
|
{
|
|
return GR_NOTTEAMMATE;
|
|
}
|
|
|
|
if (pPlayer->m_iTeam != static_cast<CBasePlayer *>(pTarget)->m_iTeam)
|
|
{
|
|
return GR_NOTTEAMMATE;
|
|
}
|
|
|
|
return GR_TEAMMATE;
|
|
}
|
|
|
|
BOOL CHalfLifeMultiplay::FAllowFlashlight()
|
|
{
|
|
return flashlight.value ? TRUE : FALSE;
|
|
}
|
|
|
|
BOOL CHalfLifeMultiplay::FAllowMonsters()
|
|
{
|
|
#ifdef REGAMEDLL_FIXES
|
|
return FALSE;
|
|
#else
|
|
return allowmonsters.value != 0.0f;
|
|
#endif
|
|
}
|
|
|
|
LINK_HOOK_CLASS_VOID_CUSTOM_CHAIN2(CHalfLifeMultiplay, CSGameRules, GoToIntermission)
|
|
|
|
void EXT_FUNC CHalfLifeMultiplay::__API_HOOK(GoToIntermission)()
|
|
{
|
|
if (m_bGameOver)
|
|
{
|
|
// intermission has already been triggered, so ignore.
|
|
return;
|
|
}
|
|
|
|
UTIL_LogPrintf("Team \"CT\" scored \"%i\" with \"%i\" players\n", m_iNumCTWins, m_iNumCT);
|
|
UTIL_LogPrintf("Team \"TERRORIST\" scored \"%i\" with \"%i\" players\n", m_iNumTerroristWins, m_iNumTerrorist);
|
|
|
|
if (IsCareer())
|
|
{
|
|
MESSAGE_BEGIN(MSG_ALL, gmsgCZCareer);
|
|
WRITE_STRING("MATCH");
|
|
WRITE_LONG(m_iNumCTWins);
|
|
WRITE_LONG(m_iNumTerroristWins);
|
|
MESSAGE_END();
|
|
|
|
MESSAGE_BEGIN(MSG_ALL, gmsgCZCareerHUD);
|
|
WRITE_STRING("MATCH");
|
|
WRITE_LONG(m_iNumCTWins);
|
|
WRITE_LONG(m_iNumTerroristWins);
|
|
WRITE_BYTE(m_iCareerMatchWins);
|
|
WRITE_BYTE(m_iRoundWinDifference);
|
|
WRITE_BYTE(m_iRoundWinStatus);
|
|
MESSAGE_END();
|
|
|
|
if (TheCareerTasks)
|
|
{
|
|
UTIL_LogPrintf("Career Match %d %d %d %d\n", m_iRoundWinStatus, m_iNumCTWins, m_iNumTerroristWins, TheCareerTasks->AreAllTasksComplete());
|
|
}
|
|
}
|
|
|
|
MESSAGE_BEGIN(MSG_ALL, SVC_INTERMISSION);
|
|
MESSAGE_END();
|
|
|
|
if (IsCareer())
|
|
{
|
|
SERVER_COMMAND("setpause\n");
|
|
}
|
|
|
|
// bounds check
|
|
int time = int(CVAR_GET_FLOAT("mp_chattime"));
|
|
if (time < 1)
|
|
CVAR_SET_STRING("mp_chattime", "1");
|
|
else if (time > MAX_INTERMISSION_TIME)
|
|
CVAR_SET_STRING("mp_chattime", UTIL_dtos1(MAX_INTERMISSION_TIME));
|
|
|
|
m_flIntermissionEndTime = gpGlobals->time + int(mp_chattime.value);
|
|
m_flIntermissionStartTime = gpGlobals->time;
|
|
|
|
m_bGameOver = true;
|
|
m_iEndIntermissionButtonHit = FALSE;
|
|
m_iSpawnPointCount_Terrorist = 0;
|
|
m_iSpawnPointCount_CT = 0;
|
|
m_bLevelInitialized = false;
|
|
}
|
|
|
|
// Clean up memory used by mapcycle when switching it
|
|
void DestroyMapCycle(mapcycle_t *cycle)
|
|
{
|
|
mapcycle_item_t *p, *n, *start;
|
|
p = cycle->items;
|
|
|
|
if (p)
|
|
{
|
|
start = p;
|
|
p = p->next;
|
|
while (p != start)
|
|
{
|
|
n = p->next;
|
|
delete p;
|
|
p = n;
|
|
}
|
|
|
|
delete cycle->items;
|
|
}
|
|
|
|
cycle->items = nullptr;
|
|
cycle->next_item = nullptr;
|
|
}
|
|
|
|
// Parses mapcycle.txt file into mapcycle_t structure
|
|
int ReloadMapCycleFile(char *filename, mapcycle_t *cycle)
|
|
{
|
|
char szBuffer[MAX_RULE_BUFFER];
|
|
char szMap[MAX_MAPNAME_LENGHT];
|
|
int length;
|
|
char *pToken;
|
|
char *pFileList;
|
|
char *aFileList = pFileList = (char *)LOAD_FILE_FOR_ME(filename, &length);
|
|
bool hasBuffer;
|
|
mapcycle_item_s *item, *newlist = nullptr, *next;
|
|
|
|
if (pFileList && length)
|
|
{
|
|
// the first map name in the file becomes the default
|
|
while (true)
|
|
{
|
|
hasBuffer = false;
|
|
Q_memset(szBuffer, 0, sizeof(szBuffer));
|
|
|
|
pFileList = SharedParse(pFileList);
|
|
pToken = SharedGetToken();
|
|
|
|
if (Q_strlen(pToken) <= 0)
|
|
break;
|
|
|
|
#ifdef REGAMEDLL_FIXES
|
|
Q_strncpy(szMap, pToken, sizeof(szMap) - 1);
|
|
szMap[sizeof(szMap) - 1] = '\0';
|
|
#else
|
|
Q_strcpy(szMap, pToken);
|
|
#endif
|
|
|
|
// Any more tokens on this line?
|
|
if (SharedTokenWaiting(pFileList))
|
|
{
|
|
pFileList = SharedParse(pFileList);
|
|
if (Q_strlen(pToken) > 0)
|
|
{
|
|
hasBuffer = true;
|
|
Q_strcpy(szBuffer, pToken);
|
|
}
|
|
}
|
|
|
|
// Check map
|
|
if (IS_MAP_VALID(szMap))
|
|
{
|
|
// Create entry
|
|
char *s;
|
|
|
|
item = new mapcycle_item_s;
|
|
|
|
Q_strcpy(item->mapname, szMap);
|
|
|
|
item->minplayers = 0;
|
|
item->maxplayers = 0;
|
|
|
|
Q_memset(item->rulebuffer, 0, sizeof(item->rulebuffer));
|
|
|
|
if (hasBuffer)
|
|
{
|
|
s = GET_KEY_VALUE(szBuffer, "minplayers");
|
|
|
|
if (s && s[0] != '\0')
|
|
{
|
|
item->minplayers = Q_atoi(s);
|
|
item->minplayers = Q_max(item->minplayers, 0);
|
|
item->minplayers = Q_min(item->minplayers, gpGlobals->maxClients);
|
|
}
|
|
|
|
s = GET_KEY_VALUE(szBuffer, "maxplayers");
|
|
if (s && s[0] != '\0')
|
|
{
|
|
item->maxplayers = Q_atoi(s);
|
|
item->maxplayers = Q_max(item->maxplayers, 0);
|
|
item->maxplayers = Q_min(item->maxplayers, gpGlobals->maxClients);
|
|
}
|
|
|
|
// Remove keys
|
|
REMOVE_KEY_VALUE(szBuffer, "minplayers");
|
|
REMOVE_KEY_VALUE(szBuffer, "maxplayers");
|
|
|
|
Q_strcpy(item->rulebuffer, szBuffer);
|
|
}
|
|
|
|
item->next = cycle->items;
|
|
cycle->items = item;
|
|
}
|
|
else
|
|
{
|
|
ALERT(at_console, "Skipping %s from mapcycle, not a valid map\n", szMap);
|
|
}
|
|
}
|
|
|
|
FREE_FILE(aFileList);
|
|
}
|
|
|
|
// Fixup circular list pointer
|
|
item = cycle->items;
|
|
|
|
// Reverse it to get original order
|
|
while (item)
|
|
{
|
|
next = item->next;
|
|
item->next = newlist;
|
|
newlist = item;
|
|
item = next;
|
|
}
|
|
|
|
cycle->items = newlist;
|
|
item = cycle->items;
|
|
|
|
// Didn't parse anything
|
|
if (!item)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
while (item->next)
|
|
{
|
|
item = item->next;
|
|
}
|
|
|
|
item->next = cycle->items;
|
|
cycle->next_item = item->next;
|
|
|
|
return 1;
|
|
}
|
|
|
|
// Determine the current # of active players on the server for map cycling logic
|
|
int CountPlayers()
|
|
{
|
|
int nCount = 0;
|
|
for (int i = 1; i <= gpGlobals->maxClients; i++)
|
|
{
|
|
CBasePlayer *pPlayer = UTIL_PlayerByIndex(i);
|
|
|
|
if (!UTIL_IsValidPlayer(pPlayer) || pPlayer->IsBot())
|
|
continue;
|
|
|
|
nCount++;
|
|
}
|
|
|
|
return nCount;
|
|
}
|
|
|
|
// Parse commands/key value pairs to issue right after map xxx command is issued on server level transition
|
|
void ExtractCommandString(char *s, char *szCommand)
|
|
{
|
|
// Now make rules happen
|
|
char pkey[512];
|
|
char value[512]; // use two buffers so compares
|
|
|
|
// work without stomping on each other
|
|
char *c;
|
|
int nCount;
|
|
|
|
while (*s)
|
|
{
|
|
if (*s == '\\')
|
|
{
|
|
// skip the slash
|
|
s++;
|
|
}
|
|
|
|
// Copy a key
|
|
c = pkey;
|
|
nCount = 0;
|
|
|
|
while (*s != '\\')
|
|
{
|
|
if (!*s)
|
|
{
|
|
// allow value to be ended with NULL
|
|
break;
|
|
}
|
|
|
|
if (nCount >= sizeof(pkey))
|
|
{
|
|
s++;
|
|
// skip oversized key chars till the slash or EOL
|
|
continue;
|
|
}
|
|
|
|
*c++ = *s++;
|
|
nCount++;
|
|
}
|
|
|
|
*c = '\0';
|
|
s++; // skip the slash
|
|
|
|
// Copy a value
|
|
c = value;
|
|
nCount = 0;
|
|
|
|
while (*s != '\\')
|
|
{
|
|
if (!*s)
|
|
{
|
|
// allow value to be ended with NULL
|
|
break;
|
|
}
|
|
|
|
if (nCount >= sizeof(value))
|
|
{
|
|
s++;
|
|
// skip oversized value chars till the slash or EOL
|
|
continue;
|
|
}
|
|
|
|
*c++ = *s++;
|
|
nCount++;
|
|
}
|
|
|
|
*c = '\0';
|
|
|
|
Q_strcat(szCommand, pkey);
|
|
if (Q_strlen(value) > 0)
|
|
{
|
|
Q_strcat(szCommand, " ");
|
|
Q_strcat(szCommand, value);
|
|
}
|
|
Q_strcat(szCommand, "\n");
|
|
|
|
/*if (!*s)
|
|
{
|
|
return;
|
|
}
|
|
|
|
s++;*/
|
|
}
|
|
}
|
|
|
|
void CHalfLifeMultiplay::ResetAllMapVotes()
|
|
{
|
|
CBaseEntity *pEntity = nullptr;
|
|
|
|
while ((pEntity = UTIL_FindEntityByClassname(pEntity, "player")))
|
|
{
|
|
if (FNullEnt(pEntity->edict()))
|
|
break;
|
|
|
|
if (pEntity->IsDormant())
|
|
continue;
|
|
|
|
CBasePlayer *pPlayer = GetClassPtr<CCSPlayer>((CBasePlayer *)pEntity->pev);
|
|
if (pPlayer->m_iTeam != UNASSIGNED)
|
|
{
|
|
pPlayer->m_iMapVote = 0;
|
|
}
|
|
}
|
|
|
|
for (int j = 0; j < MAX_VOTE_MAPS; j++)
|
|
m_iMapVotes[j] = 0;
|
|
}
|
|
|
|
int GetMapCount()
|
|
{
|
|
static mapcycle_t mapcycle;
|
|
char *mapcfile = (char *)CVAR_GET_STRING("mapcyclefile");
|
|
|
|
DestroyMapCycle(&mapcycle);
|
|
ReloadMapCycleFile(mapcfile, &mapcycle);
|
|
|
|
int nCount = 0;
|
|
auto item = mapcycle.next_item;
|
|
|
|
do
|
|
{
|
|
if (!item)
|
|
break;
|
|
|
|
nCount++;
|
|
item = item->next;
|
|
} while (item != mapcycle.next_item);
|
|
|
|
return nCount;
|
|
}
|
|
|
|
void CHalfLifeMultiplay::DisplayMaps(CBasePlayer *pPlayer, int iVote)
|
|
{
|
|
static mapcycle_t mapcycle2;
|
|
char *mapcfile = (char *)CVAR_GET_STRING("mapcyclefile");
|
|
char *pszNewMap = nullptr;
|
|
|
|
int iCount = 0, done = 0;
|
|
|
|
DestroyMapCycle(&mapcycle2);
|
|
ReloadMapCycleFile(mapcfile, &mapcycle2);
|
|
|
|
mapcycle_item_s *item = mapcycle2.next_item;
|
|
while (!done && item)
|
|
{
|
|
if (item->next == mapcycle2.next_item)
|
|
done = 1;
|
|
|
|
iCount++;
|
|
|
|
if (pPlayer)
|
|
{
|
|
if (m_iMapVotes[iCount] == 1)
|
|
{
|
|
ClientPrint(pPlayer->pev, HUD_PRINTCONSOLE, "#Vote", UTIL_dtos1(iCount), item->mapname, UTIL_dtos2(1));
|
|
}
|
|
else
|
|
{
|
|
ClientPrint(pPlayer->pev, HUD_PRINTCONSOLE, "#Votes", UTIL_dtos1(iCount), item->mapname, UTIL_dtos2(m_iMapVotes[iCount]));
|
|
}
|
|
}
|
|
|
|
if (iCount == iVote)
|
|
{
|
|
pszNewMap = item->mapname;
|
|
}
|
|
|
|
item = item->next;
|
|
}
|
|
|
|
if (!pszNewMap || !iVote)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (Q_strcmp(pszNewMap, STRING(gpGlobals->mapname)) != 0)
|
|
{
|
|
CHANGE_LEVEL(pszNewMap, nullptr);
|
|
return;
|
|
}
|
|
|
|
if (timelimit.value)
|
|
{
|
|
timelimit.value += 30;
|
|
UTIL_ClientPrintAll(HUD_PRINTCENTER, "#Map_Vote_Extend");
|
|
}
|
|
|
|
ResetAllMapVotes();
|
|
}
|
|
|
|
void CHalfLifeMultiplay::ProcessMapVote(CBasePlayer *pPlayer, int iVote)
|
|
{
|
|
CBaseEntity *pEntity = nullptr;
|
|
int iValidVotes = 0, iNumPlayers = 0;
|
|
|
|
while ((pEntity = UTIL_FindEntityByClassname(pEntity, "player")))
|
|
{
|
|
if (FNullEnt(pEntity->edict()))
|
|
break;
|
|
|
|
if (pEntity->IsDormant())
|
|
continue;
|
|
|
|
CBasePlayer *pPlayer = GetClassPtr<CCSPlayer>((CBasePlayer *)pEntity->pev);
|
|
|
|
if (pPlayer->m_iTeam != UNASSIGNED)
|
|
{
|
|
iNumPlayers++;
|
|
|
|
if (pPlayer->m_iMapVote == iVote)
|
|
iValidVotes++;
|
|
}
|
|
}
|
|
|
|
m_iMapVotes[iVote] = iValidVotes;
|
|
|
|
float ratio = mapvoteratio.value;
|
|
if (mapvoteratio.value > 1)
|
|
{
|
|
ratio = 1;
|
|
CVAR_SET_STRING("mp_mapvoteratio", "1.0");
|
|
}
|
|
else if (mapvoteratio.value < 0.35f)
|
|
{
|
|
ratio = 0.35f;
|
|
CVAR_SET_STRING("mp_mapvoteratio", "0.35");
|
|
}
|
|
|
|
int iRequiredVotes = 2;
|
|
if (iNumPlayers > 2)
|
|
{
|
|
iRequiredVotes = int(iNumPlayers * ratio + 0.5f);
|
|
}
|
|
|
|
if (iValidVotes < iRequiredVotes)
|
|
{
|
|
DisplayMaps(pPlayer, 0);
|
|
ClientPrint(pPlayer->pev, HUD_PRINTCONSOLE, "#Game_required_votes", UTIL_dtos1(iRequiredVotes));
|
|
}
|
|
else
|
|
{
|
|
DisplayMaps(nullptr, iVote);
|
|
}
|
|
}
|
|
|
|
LINK_HOOK_CLASS_VOID_CUSTOM_CHAIN2(CHalfLifeMultiplay, CSGameRules, ChangeLevel)
|
|
|
|
// Server is changing to a new level, check mapcycle.txt for map name and setup info
|
|
void EXT_FUNC CHalfLifeMultiplay::__API_HOOK(ChangeLevel)()
|
|
{
|
|
static char szPreviousMapCycleFile[256];
|
|
static mapcycle_t mapcycle;
|
|
|
|
char szNextMap[MAX_MAPNAME_LENGHT];
|
|
char szFirstMapInList[MAX_MAPNAME_LENGHT];
|
|
char szCommands[1500];
|
|
char szRules[1500];
|
|
int minplayers = 0, maxplayers = 0;
|
|
|
|
#ifdef REGAMEDLL_FIXES
|
|
// the absolute default level is de_dust
|
|
Q_strcpy(szFirstMapInList, "de_dust");
|
|
#else
|
|
// the absolute default level is hldm1
|
|
Q_strcpy(szFirstMapInList, "hldm1");
|
|
#endif
|
|
|
|
int curplayers;
|
|
bool do_cycle = true;
|
|
|
|
// find the map to change to
|
|
char *mapcfile = (char *)CVAR_GET_STRING("mapcyclefile");
|
|
Assert(mapcfile != nullptr);
|
|
|
|
szCommands[0] = '\0';
|
|
szRules[0] = '\0';
|
|
|
|
curplayers = CountPlayers();
|
|
|
|
// Has the map cycle filename changed?
|
|
if (Q_stricmp(mapcfile, szPreviousMapCycleFile) != 0)
|
|
{
|
|
Q_strcpy(szPreviousMapCycleFile, mapcfile);
|
|
|
|
DestroyMapCycle(&mapcycle);
|
|
|
|
if (!ReloadMapCycleFile(mapcfile, &mapcycle) || !mapcycle.items)
|
|
{
|
|
ALERT(at_console, "Unable to load map cycle file %s\n", mapcfile);
|
|
do_cycle = false;
|
|
}
|
|
}
|
|
|
|
if (do_cycle && mapcycle.items)
|
|
{
|
|
bool keeplooking = false;
|
|
bool found = false;
|
|
mapcycle_item_s *item;
|
|
|
|
// Assume current map
|
|
Q_strcpy(szNextMap, STRING(gpGlobals->mapname));
|
|
Q_strcpy(szFirstMapInList, STRING(gpGlobals->mapname));
|
|
|
|
// Traverse list
|
|
for (item = mapcycle.next_item; item->next != mapcycle.next_item; item = item->next)
|
|
{
|
|
keeplooking = false;
|
|
|
|
Assert(item != nullptr);
|
|
|
|
if (item->minplayers != 0)
|
|
{
|
|
if (curplayers >= item->minplayers)
|
|
{
|
|
found = true;
|
|
minplayers = item->minplayers;
|
|
}
|
|
else
|
|
{
|
|
keeplooking = true;
|
|
}
|
|
}
|
|
|
|
if (item->maxplayers != 0)
|
|
{
|
|
if (curplayers <= item->maxplayers)
|
|
{
|
|
found = true;
|
|
maxplayers = item->maxplayers;
|
|
}
|
|
else
|
|
{
|
|
keeplooking = true;
|
|
}
|
|
}
|
|
|
|
if (keeplooking)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
found = true;
|
|
break;
|
|
}
|
|
|
|
if (!found)
|
|
{
|
|
item = mapcycle.next_item;
|
|
}
|
|
|
|
// Increment next item pointer
|
|
mapcycle.next_item = item->next;
|
|
|
|
// Perform logic on current item
|
|
Q_strcpy(szNextMap, item->mapname);
|
|
ExtractCommandString(item->rulebuffer, szCommands);
|
|
Q_strcpy(szRules, item->rulebuffer);
|
|
}
|
|
|
|
if (!IS_MAP_VALID(szNextMap))
|
|
{
|
|
Q_strcpy(szNextMap, szFirstMapInList);
|
|
}
|
|
|
|
m_bGameOver = true;
|
|
|
|
ALERT(at_console, "CHANGE LEVEL: %s\n", szNextMap);
|
|
if (minplayers || maxplayers)
|
|
{
|
|
ALERT(at_console, "PLAYER COUNT: min %i max %i current %i\n", minplayers, maxplayers, curplayers);
|
|
}
|
|
|
|
if (Q_strlen(szRules) > 0)
|
|
{
|
|
ALERT(at_console, "RULES: %s\n", szRules);
|
|
}
|
|
|
|
CHANGE_LEVEL(szNextMap, nullptr);
|
|
if (Q_strlen(szCommands) > 0)
|
|
{
|
|
SERVER_COMMAND(szCommands);
|
|
}
|
|
}
|
|
|
|
void CHalfLifeMultiplay::SendMOTDToClient(edict_t *client)
|
|
{
|
|
// read from the MOTD.txt file
|
|
int length, char_count = 0;
|
|
char *pFileList;
|
|
char *aFileList = pFileList = (char *)LOAD_FILE_FOR_ME((char *)CVAR_GET_STRING("motdfile"), &length);
|
|
|
|
// send the server name
|
|
MESSAGE_BEGIN(MSG_ONE, gmsgServerName, nullptr, client);
|
|
WRITE_STRING(CVAR_GET_STRING("hostname"));
|
|
MESSAGE_END();
|
|
|
|
// Send the message of the day
|
|
// read it chunk-by-chunk, and send it in parts
|
|
while (pFileList && *pFileList && char_count < MAX_MOTD_LENGTH)
|
|
{
|
|
char chunk[MAX_MOTD_CHUNK + 1];
|
|
|
|
if (Q_strlen(pFileList) < sizeof(chunk))
|
|
{
|
|
Q_strcpy(chunk, pFileList);
|
|
}
|
|
else
|
|
{
|
|
Q_strncpy(chunk, pFileList, sizeof(chunk) - 1);
|
|
// Q_strncpy doesn't always append the null terminator
|
|
chunk[sizeof(chunk) - 1] = '\0';
|
|
}
|
|
|
|
char_count += Q_strlen(chunk);
|
|
|
|
if (char_count < MAX_MOTD_LENGTH)
|
|
pFileList = aFileList + char_count;
|
|
else
|
|
*pFileList = '\0';
|
|
|
|
MESSAGE_BEGIN(MSG_ONE, gmsgMOTD, nullptr, client);
|
|
WRITE_BYTE((*pFileList != '\0') ? FALSE : TRUE); // FALSE means there is still more message to come
|
|
WRITE_STRING(chunk);
|
|
MESSAGE_END();
|
|
}
|
|
|
|
FREE_FILE(aFileList);
|
|
}
|
|
|
|
LINK_HOOK_CLASS_VOID_CUSTOM_CHAIN(CHalfLifeMultiplay, CSGameRules, ClientUserInfoChanged, (CBasePlayer *pPlayer, char *infobuffer), pPlayer, infobuffer);
|
|
|
|
void EXT_FUNC CHalfLifeMultiplay::__API_HOOK(ClientUserInfoChanged)(CBasePlayer *pPlayer, char *infobuffer)
|
|
{
|
|
pPlayer->SetPlayerModel(pPlayer->m_bHasC4);
|
|
pPlayer->SetPrefsFromUserinfo(infobuffer);
|
|
}
|
|
|
|
void CHalfLifeMultiplay::ServerActivate()
|
|
{
|
|
// Check to see if there's a mapping info paramater entity
|
|
if (g_pMapInfo)
|
|
g_pMapInfo->CheckMapInfo();
|
|
|
|
ReadMultiplayCvars();
|
|
CheckMapConditions();
|
|
}
|
|
|
|
TeamName CHalfLifeMultiplay::SelectDefaultTeam()
|
|
{
|
|
TeamName team = UNASSIGNED;
|
|
if (m_iNumTerrorist < m_iNumCT)
|
|
{
|
|
team = TERRORIST;
|
|
}
|
|
else if (m_iNumTerrorist > m_iNumCT)
|
|
{
|
|
team = CT;
|
|
}
|
|
// Choose the team that's losing
|
|
else if (m_iNumTerroristWins < m_iNumCTWins)
|
|
{
|
|
team = TERRORIST;
|
|
}
|
|
else if (m_iNumCTWins < m_iNumTerroristWins)
|
|
{
|
|
team = CT;
|
|
}
|
|
else
|
|
{
|
|
// Teams and scores are equal, pick a random team
|
|
team = (RANDOM_LONG(0, 1) == 0) ? CT : TERRORIST;
|
|
}
|
|
|
|
if (TeamFull(team))
|
|
{
|
|
// Pick the opposite team
|
|
team = (team == TERRORIST) ? CT : TERRORIST;
|
|
|
|
// No choices left
|
|
if (TeamFull(team))
|
|
{
|
|
return UNASSIGNED;
|
|
}
|
|
}
|
|
|
|
return team;
|
|
}
|
|
|
|
void CHalfLifeMultiplay::ChangePlayerTeam(CBasePlayer *pPlayer, const char *pTeamName, BOOL bKill, BOOL bGib)
|
|
{
|
|
if (!pTeamName || !pTeamName[0])
|
|
return;
|
|
|
|
if (!pPlayer->IsAlive() || pPlayer->m_iJoiningState != JOINED)
|
|
return;
|
|
|
|
TeamName newTeam;
|
|
if (!Q_stricmp(pTeamName, "CT"))
|
|
{
|
|
newTeam = CT;
|
|
}
|
|
else if (!Q_stricmp(pTeamName, "TERRORIST"))
|
|
{
|
|
newTeam = TERRORIST;
|
|
}
|
|
else if (!Q_stricmp(pTeamName, "SPECTATOR"))
|
|
{
|
|
newTeam = SPECTATOR;
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (pPlayer->m_iTeam != UNASSIGNED && pPlayer->m_iTeam != newTeam)
|
|
{
|
|
if (bKill)
|
|
{
|
|
pPlayer->m_LastHitGroup = HITGROUP_GENERIC;
|
|
|
|
// have the player kill themself
|
|
pPlayer->pev->health = 0;
|
|
pPlayer->Killed(pPlayer->pev, bGib ? GIB_ALWAYS : GIB_NEVER);
|
|
|
|
// add 1 to frags to balance out the 1 subtracted for killing yourself
|
|
pPlayer->pev->frags++;
|
|
}
|
|
|
|
pPlayer->m_iTeam = newTeam;
|
|
pPlayer->SetPlayerModel(pPlayer->m_bHasC4);
|
|
pPlayer->TeamChangeUpdate();
|
|
|
|
CSGameRules()->CheckWinConditions();
|
|
}
|
|
}
|
|
|
|
bool CHalfLifeMultiplay::CanPlayerBuy(CBasePlayer *pPlayer) const
|
|
{
|
|
if (pPlayer->m_iTeam == CT && m_bCTCantBuy)
|
|
{
|
|
return false;
|
|
}
|
|
else if (pPlayer->m_iTeam == TERRORIST && m_bTCantBuy)
|
|
{
|
|
return false;
|
|
}
|
|
else if (m_bCTCantBuy && m_bTCantBuy)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//
|
|
// Checks for assists in a kill situation
|
|
//
|
|
// This function analyzes damage records and player actions to determine the player who contributed the most to a kill,
|
|
// considering factors such as damage dealt and the use of flashbang grenades
|
|
//
|
|
// pKiller - The killer entity (Note: The killer may be a non-player)
|
|
// pVictim - The victim player
|
|
// bFlashAssist - A flag indicating whether a flashbang was used in the assist
|
|
// Returns - A pointer to the player who gave the most assistance, or NULL if appropriate assistant is not found
|
|
//
|
|
CBasePlayer *CHalfLifeMultiplay::CheckAssistsToKill(CBaseEntity *pKiller, CBasePlayer *pVictim, bool &bFlashAssist)
|
|
{
|
|
#ifdef REGAMEDLL_ADD
|
|
CCSPlayer::DamageList_t &victimDamageTakenList = pVictim->CSPlayer()->GetDamageList();
|
|
|
|
float maxDamage = 0.0f;
|
|
int maxDamageIndex = -1;
|
|
CBasePlayer *maxDamagePlayer = nullptr;
|
|
|
|
// Find the best assistant
|
|
for (int i = 1; i <= gpGlobals->maxClients; i++)
|
|
{
|
|
const CCSPlayer::CDamageRecord_t &record = victimDamageTakenList[i - 1];
|
|
if (record.flDamage == 0)
|
|
continue; // dealt no damage
|
|
|
|
CBasePlayer *pAttackerPlayer = UTIL_PlayerByIndex(i);
|
|
if (!UTIL_IsValidPlayer(pAttackerPlayer))
|
|
continue; // ignore idle clients
|
|
|
|
CCSPlayer *pCSAttackerPlayer = pAttackerPlayer->CSPlayer();
|
|
if (record.userId != pCSAttackerPlayer->m_iUserID)
|
|
continue; // another client?
|
|
|
|
if (pAttackerPlayer == pKiller || pAttackerPlayer == pVictim)
|
|
continue; // ignore involved as killer or victim
|
|
|
|
if (record.flDamage > maxDamage)
|
|
{
|
|
// If the assistant used a flash grenade to aid in the kill,
|
|
// make sure that the victim was blinded, and that the duration of the flash effect is still preserved
|
|
if (record.flFlashDurationTime > 0 && (!pVictim->IsBlind() || record.flFlashDurationTime <= gpGlobals->time))
|
|
continue;
|
|
|
|
maxDamage = record.flDamage;
|
|
maxDamagePlayer = pAttackerPlayer;
|
|
maxDamageIndex = i;
|
|
}
|
|
}
|
|
|
|
// Note: Only the highest damaging player can be an assistant
|
|
// The condition checks if the damage dealt by the player exceeds a certain percentage of the victim's max health
|
|
// Default threshold is 40%, meaning the assistant must deal at least 40% of the victim's max health as damage
|
|
if (maxDamagePlayer && maxDamage > (assist_damage_threshold.value / 100.0f) * pVictim->pev->max_health)
|
|
{
|
|
bFlashAssist = victimDamageTakenList[maxDamageIndex - 1].flFlashDurationTime > 0; // if performed the flash assist
|
|
return maxDamagePlayer;
|
|
}
|
|
#endif
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
//
|
|
// Check the rarity estimation for a kill
|
|
//
|
|
// Estimation to represent the rarity of a kill based on various factors, including assists with flashbang grenades,
|
|
// headshot kills, kills through walls, the killer's blindness, no-scope sniper rifle kills, and kills through smoke
|
|
//
|
|
// pKiller - The entity who committed the kill (Note: The killer may be a non-player)
|
|
// pVictim - The player who was killed
|
|
// pAssister - The assisting player (if any)
|
|
// killerWeaponName - The name of the weapon used by the killer
|
|
// bFlashAssist - A flag indicating whether an assist was made with a flashbang
|
|
// Returns an integer estimation representing the rarity of the kill
|
|
// Use with PLAYERDEATH_KILLRARITY flag to indicate a rare kill in death messages
|
|
//
|
|
int CHalfLifeMultiplay::GetRarityOfKill(CBaseEntity *pKiller, CBasePlayer *pVictim, CBasePlayer *pAssister, const char *killerWeaponName, bool bFlashAssist)
|
|
{
|
|
int iRarity = 0;
|
|
|
|
// The killer player kills the victim with an assistant flashbang grenade
|
|
if (pAssister && bFlashAssist)
|
|
iRarity |= KILLRARITY_ASSISTEDFLASH;
|
|
|
|
// The killer player kills the victim with a headshot
|
|
if (pVictim->m_bHeadshotKilled)
|
|
iRarity |= KILLRARITY_HEADSHOT;
|
|
|
|
CBasePlayer *pKillerPlayer = static_cast<CBasePlayer *>(pKiller);
|
|
if (pKillerPlayer && pKillerPlayer->IsPlayer() && pKillerPlayer != pVictim)
|
|
{
|
|
WeaponClassType weaponClass = AliasToWeaponClass(killerWeaponName);
|
|
if (weaponClass != WEAPONCLASS_NONE &&
|
|
weaponClass != WEAPONCLASS_KNIFE &&
|
|
weaponClass != WEAPONCLASS_GRENADE)
|
|
{
|
|
// The killer player kills the victim through the walls
|
|
if (pVictim->GetDmgPenetrationLevel() > 0)
|
|
iRarity |= KILLRARITY_PENETRATED;
|
|
|
|
// The killer player was blind
|
|
if (pKillerPlayer->IsFullyBlind())
|
|
iRarity |= KILLRARITY_KILLER_BLIND;
|
|
|
|
// The killer player kills the victim with a sniper rifle with no scope
|
|
if (weaponClass == WEAPONCLASS_SNIPERRIFLE && pKillerPlayer->m_iClientFOV == DEFAULT_FOV)
|
|
iRarity |= KILLRARITY_NOSCOPE;
|
|
|
|
// The killer player kills the victim through smoke
|
|
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
|
|
// This is plus 1 as this function gets called before the stat is updated
|
|
// That is done so that the domination and revenge will be calculated prior
|
|
// to the death message being sent to the clients
|
|
int iAttackerEntityIndex = pKillerPlayer->entindex();
|
|
Assert(iAttackerEntityIndex > 0 && iAttackerEntityIndex <= MAX_CLIENTS);
|
|
|
|
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;
|
|
|
|
// set victim to be dominated by killer
|
|
pKillerPlayer->CSPlayer()->SetPlayerDominated(pVictim, true);
|
|
}
|
|
else if (pVictim->CSPlayer()->IsPlayerDominated(pKillerPlayer->entindex() - 1))
|
|
{
|
|
// the killer killed someone who was dominating him, gains revenge
|
|
iRarity |= KILLRARITY_REVENGE;
|
|
|
|
// set victim to no longer be dominating the killer
|
|
pVictim->CSPlayer()->SetPlayerDominated(pKillerPlayer, false);
|
|
}
|
|
}
|
|
|
|
return iRarity;
|
|
}
|
|
|
|
LINK_HOOK_CLASS_VOID_CUSTOM_CHAIN(CHalfLifeMultiplay, CSGameRules, SendDeathMessage, (CBaseEntity *pKiller, CBasePlayer *pVictim, CBasePlayer *pAssister, entvars_t *pevInflictor, const char *killerWeaponName, int iDeathMessageFlags, int iRarityOfKill), pKiller, pVictim, pAssister, pevInflictor, killerWeaponName, iDeathMessageFlags, iRarityOfKill)
|
|
|
|
//
|
|
// Sends death messages to all players, including info about the killer, victim, weapon used,
|
|
// extra death flags, death position, assistant, and kill rarity
|
|
//
|
|
//
|
|
// pKiller - The entity who performed the kill (Note: The killer may be a non-player)
|
|
// pVictim - The player who was killed
|
|
// pAssister - The assisting player (if any)
|
|
// killerWeaponName - The name of the weapon used by the killer
|
|
// iDeathMessageFlags - Flags indicating extra death message info
|
|
// iRarityOfKill - An bitsums representing the rarity classification of the kill
|
|
//
|
|
void EXT_FUNC CHalfLifeMultiplay::__API_HOOK(SendDeathMessage)(CBaseEntity *pKiller, CBasePlayer *pVictim, CBasePlayer *pAssister, entvars_t *pevInflictor, const char *killerWeaponName, int iDeathMessageFlags, int iRarityOfKill)
|
|
{
|
|
MESSAGE_BEGIN(MSG_ALL, gmsgDeathMsg);
|
|
WRITE_BYTE((pKiller && pKiller->IsPlayer()) ? pKiller->entindex() : 0); // the killer
|
|
WRITE_BYTE(pVictim->entindex()); // the victim
|
|
WRITE_BYTE((iRarityOfKill & KILLRARITY_HEADSHOT)); // is killed headshot
|
|
WRITE_STRING(killerWeaponName); // what they were killed by (should this be a string?)
|
|
|
|
#ifdef REGAMEDLL_ADD
|
|
if (iDeathMessageFlags > 0)
|
|
{
|
|
WRITE_LONG(iDeathMessageFlags);
|
|
|
|
// Writes the coordinates of the place where the victim died
|
|
// The victim has just been killed, so this usefully display 'X' dead icon on the HUD radar
|
|
if (iDeathMessageFlags & PLAYERDEATH_POSITION)
|
|
{
|
|
WRITE_COORD(pVictim->pev->origin.x);
|
|
WRITE_COORD(pVictim->pev->origin.y);
|
|
WRITE_COORD(pVictim->pev->origin.z);
|
|
}
|
|
|
|
// Writes the index of the teammate who assisted in the kill
|
|
if (iDeathMessageFlags & PLAYERDEATH_ASSISTANT)
|
|
WRITE_BYTE((pAssister && pAssister->IsPlayer()) ? pAssister->entindex() : 0);
|
|
|
|
// Writes the rarity classification of the kill
|
|
if (iDeathMessageFlags & PLAYERDEATH_KILLRARITY)
|
|
WRITE_LONG(iRarityOfKill);
|
|
}
|
|
#endif
|
|
|
|
MESSAGE_END();
|
|
}
|
|
|
|
void CHalfLifeMultiplay::GiveDefuserToRandomPlayer()
|
|
{
|
|
int iDefusersToGive = 2;
|
|
CUtlVector<CBasePlayer *> candidates;
|
|
candidates.EnsureCapacity(MAX_CLIENTS);
|
|
|
|
// add all CT candidates to a list
|
|
for (int i = 1; i <= gpGlobals->maxClients; i++)
|
|
{
|
|
CBasePlayer *pPlayer = UTIL_PlayerByIndex(i);
|
|
if (!UTIL_IsValidPlayer(pPlayer))
|
|
continue;
|
|
|
|
if (!pPlayer->IsAlive() || pPlayer->m_iTeam != CT)
|
|
continue;
|
|
|
|
candidates.AddToTail(pPlayer);
|
|
}
|
|
|
|
// randomly shuffle the list; this will keep the selection random in case of ties
|
|
for (int i = 0; i < candidates.Count(); i++) {
|
|
SWAP(candidates[i], candidates[RANDOM_LONG(0, candidates.Count() - 1)]);
|
|
}
|
|
|
|
// now sort the shuffled list into subgroups
|
|
candidates.Sort([](CBasePlayer *const *left, CBasePlayer *const *right) -> int {
|
|
// should we prioritize humans over bots?
|
|
if (cv_bot_defer_to_human.value != 0.0f)
|
|
{
|
|
if ((*left)->IsBot() && !(*right)->IsBot())
|
|
return 1;
|
|
|
|
if (!(*left)->IsBot() && (*right)->IsBot())
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
);
|
|
|
|
// give defusers to the first N candidates
|
|
for (int i = 0; i < iDefusersToGive && i < candidates.Count(); ++i)
|
|
{
|
|
CBasePlayer *pPlayer = candidates[i];
|
|
DbgAssert(pPlayer && pPlayer->m_iTeam == CT && pPlayer->IsAlive());
|
|
|
|
pPlayer->GiveDefuser();
|
|
pPlayer->HintMessage("#Got_defuser", FALSE, TRUE);
|
|
}
|
|
}
|