
Also backport a define being moved at the top in "multiplay_gamerules.cpp". And two changes related to weapons in general in "weapons.cpp".
1916 lines
46 KiB
C++
1916 lines
46 KiB
C++
/***
|
|
*
|
|
* Copyright (c) 1996-2001, Valve LLC. All rights reserved.
|
|
*
|
|
* This product contains software technology licensed from Id
|
|
* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc.
|
|
* All Rights Reserved.
|
|
*
|
|
* Use, distribution, and modification of this source code and/or resulting
|
|
* object code is restricted to non-commercial enhancements to products from
|
|
* Valve LLC. All other use, distribution, or modification is prohibited
|
|
* without written permission from Valve LLC.
|
|
*
|
|
****/
|
|
//
|
|
// teamplay_gamerules.cpp
|
|
//
|
|
#include "extdll.h"
|
|
#include "util.h"
|
|
#include "cbase.h"
|
|
#include "player.h"
|
|
#include "weapons.h"
|
|
#include "gamerules.h"
|
|
|
|
#include "skill.h"
|
|
#include "game.h"
|
|
#include "items.h"
|
|
#include "voice_gamemgr.h"
|
|
#include "hltv.h"
|
|
#include "trains.h"
|
|
#include "UserMessages.h"
|
|
|
|
#define ITEM_RESPAWN_TIME 30
|
|
#define WEAPON_RESPAWN_TIME 20
|
|
#define AMMO_RESPAWN_TIME 20
|
|
|
|
// longest the intermission can last, in seconds
|
|
#define MAX_INTERMISSION_TIME 120
|
|
|
|
CVoiceGameMgr g_VoiceGameMgr;
|
|
|
|
class CMultiplayGameMgrHelper : public IVoiceGameMgrHelper
|
|
{
|
|
public:
|
|
bool CanPlayerHearPlayer(CBasePlayer* pListener, CBasePlayer* pTalker) override
|
|
{
|
|
if (g_teamplay)
|
|
{
|
|
if (g_pGameRules->PlayerRelationship(pListener, pTalker) != GR_TEAMMATE)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
};
|
|
static CMultiplayGameMgrHelper g_GameMgrHelper;
|
|
|
|
//*********************************************************
|
|
// Rules for the half-life multiplayer game.
|
|
//*********************************************************
|
|
|
|
CHalfLifeMultiplay::CHalfLifeMultiplay()
|
|
{
|
|
g_VoiceGameMgr.Init(&g_GameMgrHelper, gpGlobals->maxClients);
|
|
|
|
RefreshSkillData();
|
|
|
|
// 11/8/98
|
|
// Modified by YWB: Server .cfg file is now a cvar, so that
|
|
// server ops can run multiple game servers, with different server .cfg files,
|
|
// from a single installed directory.
|
|
// Mapcyclefile is already a cvar.
|
|
|
|
// 3/31/99
|
|
// Added lservercfg file cvar, since listen and dedicated servers should not
|
|
// share a single config file. (sjb)
|
|
if (IS_DEDICATED_SERVER())
|
|
{
|
|
// this code has been moved into engine, to only run server.cfg once
|
|
}
|
|
else
|
|
{
|
|
// listen server
|
|
char* lservercfgfile = (char*)CVAR_GET_STRING("lservercfgfile");
|
|
|
|
if (lservercfgfile && '\0' != lservercfgfile[0])
|
|
{
|
|
char szCommand[256];
|
|
|
|
ALERT(at_console, "Executing listen server config file\n");
|
|
sprintf(szCommand, "exec %s\n", lservercfgfile);
|
|
SERVER_COMMAND(szCommand);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool CHalfLifeMultiplay::ClientCommand(CBasePlayer* pPlayer, const char* pcmd)
|
|
{
|
|
if (g_VoiceGameMgr.ClientCommand(pPlayer, pcmd))
|
|
return true;
|
|
|
|
return CGameRules::ClientCommand(pPlayer, pcmd);
|
|
}
|
|
|
|
void CHalfLifeMultiplay::ClientUserInfoChanged(CBasePlayer* pPlayer, char* infobuffer)
|
|
{
|
|
pPlayer->SetPrefsFromUserinfo(infobuffer);
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
void CHalfLifeMultiplay::RefreshSkillData()
|
|
{
|
|
// load all default values
|
|
CGameRules::RefreshSkillData();
|
|
|
|
// override some values for multiplay.
|
|
|
|
// suitcharger
|
|
gSkillData.suitchargerCapacity = 30;
|
|
|
|
// Crowbar whack
|
|
gSkillData.plrDmgCrowbar = 25;
|
|
|
|
// Glock Round
|
|
gSkillData.plrDmg9MM = 12;
|
|
|
|
// 357 Round
|
|
gSkillData.plrDmg357 = 50;
|
|
|
|
// MP5 Round
|
|
gSkillData.plrDmgMP5 = 12;
|
|
|
|
// M203 grenade
|
|
gSkillData.plrDmgM203Grenade = 100;
|
|
|
|
// Shotgun buckshot
|
|
gSkillData.plrDmgBuckshot = 20; // fewer pellets in deathmatch
|
|
|
|
// Crossbow
|
|
gSkillData.plrDmgCrossbowClient = 20;
|
|
|
|
// RPG
|
|
gSkillData.plrDmgRPG = 120;
|
|
|
|
// Egon
|
|
gSkillData.plrDmgEgonWide = 20;
|
|
gSkillData.plrDmgEgonNarrow = 10;
|
|
|
|
// Hand Grendade
|
|
gSkillData.plrDmgHandGrenade = 100;
|
|
|
|
// Satchel Charge
|
|
gSkillData.plrDmgSatchel = 120;
|
|
|
|
// Tripmine
|
|
gSkillData.plrDmgTripmine = 150;
|
|
|
|
// hornet
|
|
gSkillData.plrDmgHornet = 10;
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
void CHalfLifeMultiplay::Think()
|
|
{
|
|
g_VoiceGameMgr.Update(gpGlobals->frametime);
|
|
|
|
///// Check game rules /////
|
|
static int last_frags;
|
|
static int last_time;
|
|
|
|
int frags_remaining = 0;
|
|
int time_remaining = 0;
|
|
|
|
if (g_fGameOver) // someone else quit the game already
|
|
{
|
|
// 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)
|
|
{
|
|
if (m_iEndIntermissionButtonHit // check that someone has pressed a key, or the max intermission time is over
|
|
|| ((m_flIntermissionStartTime + MAX_INTERMISSION_TIME) < gpGlobals->time))
|
|
ChangeLevel(); // intermission is over
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
float flTimeLimit = timelimit.value * 60;
|
|
float flFragLimit = fraglimit.value;
|
|
|
|
time_remaining = (int)(0 != flTimeLimit ? (flTimeLimit - gpGlobals->time) : 0);
|
|
|
|
if (flTimeLimit != 0 && gpGlobals->time >= flTimeLimit)
|
|
{
|
|
GoToIntermission();
|
|
return;
|
|
}
|
|
|
|
if (0 != flFragLimit)
|
|
{
|
|
int bestfrags = 9999;
|
|
int remain;
|
|
|
|
// check if any player is over the frag limit
|
|
for (int i = 1; i <= gpGlobals->maxClients; i++)
|
|
{
|
|
CBaseEntity* pPlayer = UTIL_PlayerByIndex(i);
|
|
|
|
if (pPlayer && pPlayer->pev->frags >= flFragLimit)
|
|
{
|
|
GoToIntermission();
|
|
return;
|
|
}
|
|
|
|
|
|
if (pPlayer)
|
|
{
|
|
remain = flFragLimit - pPlayer->pev->frags;
|
|
if (remain < bestfrags)
|
|
{
|
|
bestfrags = remain;
|
|
}
|
|
}
|
|
}
|
|
frags_remaining = bestfrags;
|
|
}
|
|
|
|
// Updates when frags change
|
|
if (frags_remaining != last_frags)
|
|
{
|
|
g_engfuncs.pfnCvar_DirectSet(&fragsleft, UTIL_VarArgs("%i", frags_remaining));
|
|
}
|
|
|
|
// Updates once per second
|
|
if (timeleft.value != last_time)
|
|
{
|
|
g_engfuncs.pfnCvar_DirectSet(&timeleft, UTIL_VarArgs("%i", time_remaining));
|
|
}
|
|
|
|
last_frags = frags_remaining;
|
|
last_time = time_remaining;
|
|
}
|
|
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
bool CHalfLifeMultiplay::IsMultiplayer()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
bool CHalfLifeMultiplay::IsDeathmatch()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
bool CHalfLifeMultiplay::IsCoOp()
|
|
{
|
|
return 0 != gpGlobals->coop;
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
bool CHalfLifeMultiplay::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_pActiveItem->CanHolster())
|
|
{
|
|
// can't put away the active item.
|
|
return false;
|
|
}
|
|
|
|
//Never switch
|
|
if (pPlayer->m_iAutoWepSwitch == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//Only switch if not attacking
|
|
if (pPlayer->m_iAutoWepSwitch == 2 && (pPlayer->m_afButtonLast & (IN_ATTACK | IN_ATTACK2)) != 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (pWeapon->iWeight() > pPlayer->m_pActiveItem->iWeight())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
bool CHalfLifeMultiplay::ClientConnected(edict_t* pEntity, const char* pszName, const char* pszAddress, char szRejectReason[128])
|
|
{
|
|
g_VoiceGameMgr.ClientConnected(pEntity);
|
|
return true;
|
|
}
|
|
|
|
void CHalfLifeMultiplay::UpdateGameMode(CBasePlayer* pPlayer)
|
|
{
|
|
MESSAGE_BEGIN(MSG_ONE, gmsgGameMode, NULL, pPlayer->edict());
|
|
WRITE_BYTE(0); // game mode none
|
|
MESSAGE_END();
|
|
}
|
|
|
|
void CHalfLifeMultiplay::InitHUD(CBasePlayer* pl)
|
|
{
|
|
// notify other clients of player joining the game
|
|
UTIL_ClientPrintAll(HUD_PRINTNOTIFY, UTIL_VarArgs("%s has joined the game\n",
|
|
(!FStringNull(pl->pev->netname) && STRING(pl->pev->netname)[0] != 0) ? STRING(pl->pev->netname) : "unconnected"));
|
|
|
|
// team match?
|
|
if (g_teamplay)
|
|
{
|
|
UTIL_LogPrintf("\"%s<%i><%s><%s>\" entered the game\n",
|
|
STRING(pl->pev->netname),
|
|
GETPLAYERUSERID(pl->edict()),
|
|
GETPLAYERAUTHID(pl->edict()),
|
|
g_engfuncs.pfnInfoKeyValue(g_engfuncs.pfnGetInfoKeyBuffer(pl->edict()), "model"));
|
|
}
|
|
else
|
|
{
|
|
UTIL_LogPrintf("\"%s<%i><%s><%i>\" entered the game\n",
|
|
STRING(pl->pev->netname),
|
|
GETPLAYERUSERID(pl->edict()),
|
|
GETPLAYERAUTHID(pl->edict()),
|
|
GETPLAYERUSERID(pl->edict()));
|
|
}
|
|
|
|
UpdateGameMode(pl);
|
|
|
|
// sending just one score makes the hud scoreboard active; otherwise
|
|
// it is just disabled for single play
|
|
MESSAGE_BEGIN(MSG_ONE, gmsgScoreInfo, NULL, pl->edict());
|
|
WRITE_BYTE(ENTINDEX(pl->edict()));
|
|
WRITE_SHORT(0);
|
|
WRITE_SHORT(0);
|
|
WRITE_SHORT(0);
|
|
WRITE_SHORT(0);
|
|
MESSAGE_END();
|
|
|
|
SendMOTDToClient(pl->edict());
|
|
|
|
// loop through all active players and send their score info to the new client
|
|
for (int i = 1; i <= gpGlobals->maxClients; i++)
|
|
{
|
|
// FIXME: Probably don't need to cast this just to read m_iDeaths
|
|
CBasePlayer* plr = (CBasePlayer*)UTIL_PlayerByIndex(i);
|
|
|
|
if (plr)
|
|
{
|
|
MESSAGE_BEGIN(MSG_ONE, gmsgScoreInfo, NULL, pl->edict());
|
|
WRITE_BYTE(i); // client number
|
|
WRITE_SHORT(plr->pev->frags);
|
|
WRITE_SHORT(plr->m_iDeaths);
|
|
WRITE_SHORT(0);
|
|
WRITE_SHORT(GetTeamIndex(plr->m_szTeamName) + 1);
|
|
MESSAGE_END();
|
|
}
|
|
}
|
|
|
|
if (g_fGameOver)
|
|
{
|
|
MESSAGE_BEGIN(MSG_ONE, SVC_INTERMISSION, NULL, pl->edict());
|
|
MESSAGE_END();
|
|
}
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
void CHalfLifeMultiplay::ClientDisconnected(edict_t* pClient)
|
|
{
|
|
if (pClient)
|
|
{
|
|
CBasePlayer* pPlayer = (CBasePlayer*)CBaseEntity::Instance(pClient);
|
|
|
|
if (pPlayer)
|
|
{
|
|
FireTargets("game_playerleave", pPlayer, pPlayer, USE_TOGGLE, 0);
|
|
|
|
// team match?
|
|
if (g_teamplay)
|
|
{
|
|
UTIL_LogPrintf("\"%s<%i><%s><%s>\" disconnected\n",
|
|
STRING(pPlayer->pev->netname),
|
|
GETPLAYERUSERID(pPlayer->edict()),
|
|
GETPLAYERAUTHID(pPlayer->edict()),
|
|
g_engfuncs.pfnInfoKeyValue(g_engfuncs.pfnGetInfoKeyBuffer(pPlayer->edict()), "model"));
|
|
}
|
|
else
|
|
{
|
|
UTIL_LogPrintf("\"%s<%i><%s><%i>\" disconnected\n",
|
|
STRING(pPlayer->pev->netname),
|
|
GETPLAYERUSERID(pPlayer->edict()),
|
|
GETPLAYERAUTHID(pPlayer->edict()),
|
|
GETPLAYERUSERID(pPlayer->edict()));
|
|
}
|
|
|
|
pPlayer->RemoveAllItems(true); // destroy all of the players weapons and items
|
|
}
|
|
}
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
float CHalfLifeMultiplay::FlPlayerFallDamage(CBasePlayer* pPlayer)
|
|
{
|
|
int iFallDamage = (int)falldamage.value;
|
|
|
|
switch (iFallDamage)
|
|
{
|
|
case 1: //progressive
|
|
pPlayer->m_flFallVelocity -= PLAYER_MAX_SAFE_FALL_SPEED;
|
|
return pPlayer->m_flFallVelocity * DAMAGE_FOR_FALL_SPEED;
|
|
break;
|
|
default:
|
|
case 0: // fixed
|
|
return 10;
|
|
break;
|
|
}
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
bool CHalfLifeMultiplay::FPlayerCanTakeDamage(CBasePlayer* pPlayer, CBaseEntity* pAttacker)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
void CHalfLifeMultiplay::PlayerThink(CBasePlayer* pPlayer)
|
|
{
|
|
if (g_fGameOver)
|
|
{
|
|
// check for button presses
|
|
if ((pPlayer->m_afButtonPressed & (IN_DUCK | IN_ATTACK | IN_ATTACK2 | IN_USE | IN_JUMP)) != 0)
|
|
m_iEndIntermissionButtonHit = true;
|
|
|
|
// clear attack/use commands from player
|
|
pPlayer->m_afButtonPressed = 0;
|
|
pPlayer->pev->button = 0;
|
|
pPlayer->m_afButtonReleased = 0;
|
|
}
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
void CHalfLifeMultiplay::PlayerSpawn(CBasePlayer* pPlayer)
|
|
{
|
|
bool addDefault;
|
|
CBaseEntity* pWeaponEntity = NULL;
|
|
|
|
//Ensure the player switches to the Glock on spawn regardless of setting
|
|
const int originalAutoWepSwitch = pPlayer->m_iAutoWepSwitch;
|
|
pPlayer->m_iAutoWepSwitch = 1;
|
|
|
|
pPlayer->SetHasSuit(true);
|
|
|
|
addDefault = true;
|
|
|
|
while (pWeaponEntity = UTIL_FindEntityByClassname(pWeaponEntity, "game_player_equip"))
|
|
{
|
|
pWeaponEntity->Touch(pPlayer);
|
|
addDefault = false;
|
|
}
|
|
|
|
if (addDefault)
|
|
{
|
|
pPlayer->GiveNamedItem("weapon_crowbar");
|
|
pPlayer->GiveNamedItem("weapon_9mmhandgun");
|
|
pPlayer->GiveAmmo(68, "9mm", _9MM_MAX_CARRY); // 4 full reloads
|
|
}
|
|
|
|
pPlayer->m_iAutoWepSwitch = originalAutoWepSwitch;
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
bool CHalfLifeMultiplay::FPlayerCanRespawn(CBasePlayer* pPlayer)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
float CHalfLifeMultiplay::FlPlayerSpawnTime(CBasePlayer* pPlayer)
|
|
{
|
|
return gpGlobals->time; //now!
|
|
}
|
|
|
|
bool CHalfLifeMultiplay::AllowAutoTargetCrosshair()
|
|
{
|
|
return (aimcrosshair.value != 0);
|
|
}
|
|
|
|
//=========================================================
|
|
// IPointsForKill - how many points awarded to anyone
|
|
// that kills this player?
|
|
//=========================================================
|
|
int CHalfLifeMultiplay::IPointsForKill(CBasePlayer* pAttacker, CBasePlayer* pKilled)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
|
|
//=========================================================
|
|
// PlayerKilled - someone/something killed this player
|
|
//=========================================================
|
|
void CHalfLifeMultiplay::PlayerKilled(CBasePlayer* pVictim, entvars_t* pKiller, entvars_t* pInflictor)
|
|
{
|
|
CBasePlayer* peKiller = NULL;
|
|
CBaseEntity* ktmp = CBaseEntity::Instance(pKiller);
|
|
if (ktmp && (ktmp->Classify() == CLASS_PLAYER))
|
|
peKiller = (CBasePlayer*)ktmp;
|
|
else if (ktmp && (ktmp->Classify() == CLASS_VEHICLE))
|
|
{
|
|
CBasePlayer* pDriver = ((CFuncVehicle*)ktmp)->m_pDriver;
|
|
if (pDriver != NULL)
|
|
{
|
|
peKiller = pDriver;
|
|
ktmp = pDriver;
|
|
pKiller = pDriver->pev;
|
|
}
|
|
}
|
|
|
|
DeathNotice(pVictim, pKiller, pInflictor);
|
|
|
|
pVictim->m_iDeaths += 1;
|
|
|
|
FireTargets("game_playerdie", pVictim, pVictim, USE_TOGGLE, 0);
|
|
|
|
if (pVictim->pev == pKiller)
|
|
{ // killed self
|
|
pKiller->frags -= 1;
|
|
}
|
|
else if (ktmp && ktmp->IsPlayer())
|
|
{
|
|
// if a player dies in a deathmatch game and the killer is a client, award the killer some points
|
|
pKiller->frags += IPointsForKill(peKiller, pVictim);
|
|
|
|
FireTargets("game_playerkill", ktmp, ktmp, USE_TOGGLE, 0);
|
|
}
|
|
else
|
|
{ // killed by the world
|
|
pKiller->frags -= 1;
|
|
}
|
|
|
|
// update the scores
|
|
// killed scores
|
|
MESSAGE_BEGIN(MSG_ALL, gmsgScoreInfo);
|
|
WRITE_BYTE(ENTINDEX(pVictim->edict()));
|
|
WRITE_SHORT(pVictim->pev->frags);
|
|
WRITE_SHORT(pVictim->m_iDeaths);
|
|
WRITE_SHORT(0);
|
|
WRITE_SHORT(GetTeamIndex(pVictim->m_szTeamName) + 1);
|
|
MESSAGE_END();
|
|
|
|
// killers score, if it's a player
|
|
CBaseEntity* ep = CBaseEntity::Instance(pKiller);
|
|
if (ep && ep->Classify() == CLASS_PLAYER)
|
|
{
|
|
CBasePlayer* PK = (CBasePlayer*)ep;
|
|
|
|
MESSAGE_BEGIN(MSG_ALL, gmsgScoreInfo);
|
|
WRITE_BYTE(ENTINDEX(PK->edict()));
|
|
WRITE_SHORT(PK->pev->frags);
|
|
WRITE_SHORT(PK->m_iDeaths);
|
|
WRITE_SHORT(0);
|
|
WRITE_SHORT(GetTeamIndex(PK->m_szTeamName) + 1);
|
|
MESSAGE_END();
|
|
|
|
// let the killer paint another decal as soon as he'd like.
|
|
PK->m_flNextDecalTime = gpGlobals->time;
|
|
}
|
|
|
|
if (pVictim->HasNamedPlayerItem("weapon_satchel"))
|
|
{
|
|
DeactivateSatchels(pVictim);
|
|
}
|
|
}
|
|
|
|
//=========================================================
|
|
// Deathnotice.
|
|
//=========================================================
|
|
void CHalfLifeMultiplay::DeathNotice(CBasePlayer* pVictim, entvars_t* pKiller, entvars_t* pevInflictor)
|
|
{
|
|
// Work out what killed the player, and send a message to all clients about it
|
|
CBaseEntity* Killer = CBaseEntity::Instance(pKiller);
|
|
|
|
const char* killer_weapon_name = "world"; // by default, the player is killed by the world
|
|
int killer_index = 0;
|
|
|
|
// Hack to fix name change
|
|
const char* tau = "tau_cannon";
|
|
const char* gluon = "gluon gun";
|
|
|
|
if ((pKiller->flags & FL_CLIENT) != 0)
|
|
{
|
|
killer_index = ENTINDEX(ENT(pKiller));
|
|
|
|
if (pevInflictor)
|
|
{
|
|
if (pevInflictor == pKiller)
|
|
{
|
|
// If the inflictor is the killer, then it must be their current weapon doing the damage
|
|
CBasePlayer* pPlayer = (CBasePlayer*)CBaseEntity::Instance(pKiller);
|
|
|
|
if (pPlayer->m_pActiveItem)
|
|
{
|
|
killer_weapon_name = pPlayer->m_pActiveItem->pszName();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
killer_weapon_name = STRING(pevInflictor->classname); // it's just that easy
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
killer_weapon_name = STRING(pevInflictor->classname);
|
|
}
|
|
|
|
// strip the monster_* or weapon_* from the inflictor's classname
|
|
if (strncmp(killer_weapon_name, "weapon_", 7) == 0)
|
|
killer_weapon_name += 7;
|
|
else if (strncmp(killer_weapon_name, "monster_", 8) == 0)
|
|
killer_weapon_name += 8;
|
|
else if (strncmp(killer_weapon_name, "func_", 5) == 0)
|
|
killer_weapon_name += 5;
|
|
|
|
MESSAGE_BEGIN(MSG_ALL, gmsgDeathMsg);
|
|
WRITE_BYTE(killer_index); // the killer
|
|
WRITE_BYTE(ENTINDEX(pVictim->edict())); // the victim
|
|
WRITE_STRING(killer_weapon_name); // what they were killed by (should this be a string?)
|
|
MESSAGE_END();
|
|
|
|
// replace the code names with the 'real' names
|
|
if (0 == strcmp(killer_weapon_name, "egon"))
|
|
killer_weapon_name = gluon;
|
|
else if (0 == strcmp(killer_weapon_name, "gauss"))
|
|
killer_weapon_name = tau;
|
|
|
|
if (pVictim->pev == pKiller)
|
|
{
|
|
// killed self
|
|
|
|
// team match?
|
|
if (g_teamplay)
|
|
{
|
|
UTIL_LogPrintf("\"%s<%i><%s><%s>\" committed suicide with \"%s\"\n",
|
|
STRING(pVictim->pev->netname),
|
|
GETPLAYERUSERID(pVictim->edict()),
|
|
GETPLAYERAUTHID(pVictim->edict()),
|
|
g_engfuncs.pfnInfoKeyValue(g_engfuncs.pfnGetInfoKeyBuffer(pVictim->edict()), "model"),
|
|
killer_weapon_name);
|
|
}
|
|
else
|
|
{
|
|
UTIL_LogPrintf("\"%s<%i><%s><%i>\" committed suicide with \"%s\"\n",
|
|
STRING(pVictim->pev->netname),
|
|
GETPLAYERUSERID(pVictim->edict()),
|
|
GETPLAYERAUTHID(pVictim->edict()),
|
|
GETPLAYERUSERID(pVictim->edict()),
|
|
killer_weapon_name);
|
|
}
|
|
}
|
|
else if ((pKiller->flags & FL_CLIENT) != 0)
|
|
{
|
|
// team match?
|
|
if (g_teamplay)
|
|
{
|
|
UTIL_LogPrintf("\"%s<%i><%s><%s>\" killed \"%s<%i><%s><%s>\" with \"%s\"\n",
|
|
STRING(pKiller->netname),
|
|
GETPLAYERUSERID(ENT(pKiller)),
|
|
GETPLAYERAUTHID(ENT(pKiller)),
|
|
g_engfuncs.pfnInfoKeyValue(g_engfuncs.pfnGetInfoKeyBuffer(ENT(pKiller)), "model"),
|
|
STRING(pVictim->pev->netname),
|
|
GETPLAYERUSERID(pVictim->edict()),
|
|
GETPLAYERAUTHID(pVictim->edict()),
|
|
g_engfuncs.pfnInfoKeyValue(g_engfuncs.pfnGetInfoKeyBuffer(pVictim->edict()), "model"),
|
|
killer_weapon_name);
|
|
}
|
|
else
|
|
{
|
|
UTIL_LogPrintf("\"%s<%i><%s><%i>\" killed \"%s<%i><%s><%i>\" with \"%s\"\n",
|
|
STRING(pKiller->netname),
|
|
GETPLAYERUSERID(ENT(pKiller)),
|
|
GETPLAYERAUTHID(ENT(pKiller)),
|
|
GETPLAYERUSERID(ENT(pKiller)),
|
|
STRING(pVictim->pev->netname),
|
|
GETPLAYERUSERID(pVictim->edict()),
|
|
GETPLAYERAUTHID(pVictim->edict()),
|
|
GETPLAYERUSERID(pVictim->edict()),
|
|
killer_weapon_name);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// killed by the world
|
|
|
|
// team match?
|
|
if (g_teamplay)
|
|
{
|
|
UTIL_LogPrintf("\"%s<%i><%s><%s>\" committed suicide with \"%s\" (world)\n",
|
|
STRING(pVictim->pev->netname),
|
|
GETPLAYERUSERID(pVictim->edict()),
|
|
GETPLAYERAUTHID(pVictim->edict()),
|
|
g_engfuncs.pfnInfoKeyValue(g_engfuncs.pfnGetInfoKeyBuffer(pVictim->edict()), "model"),
|
|
killer_weapon_name);
|
|
}
|
|
else
|
|
{
|
|
UTIL_LogPrintf("\"%s<%i><%s><%i>\" committed suicide with \"%s\" (world)\n",
|
|
STRING(pVictim->pev->netname),
|
|
GETPLAYERUSERID(pVictim->edict()),
|
|
GETPLAYERAUTHID(pVictim->edict()),
|
|
GETPLAYERUSERID(pVictim->edict()),
|
|
killer_weapon_name);
|
|
}
|
|
}
|
|
|
|
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(pKiller))); // index number of secondary entity
|
|
WRITE_LONG(7 | DRC_FLAG_DRAMATIC); // eventflags (priority and flags)
|
|
MESSAGE_END();
|
|
|
|
// Print a standard message
|
|
// TODO: make this go direct to console
|
|
return; // just remove for now
|
|
/*
|
|
char szText[ 128 ];
|
|
|
|
if ( pKiller->flags & FL_MONSTER )
|
|
{
|
|
// killed by a monster
|
|
strcpy ( szText, STRING( pVictim->pev->netname ) );
|
|
strcat ( szText, " was killed by a monster.\n" );
|
|
return;
|
|
}
|
|
|
|
if ( pKiller == pVictim->pev )
|
|
{
|
|
strcpy ( szText, STRING( pVictim->pev->netname ) );
|
|
strcat ( szText, " commited suicide.\n" );
|
|
}
|
|
else if ( pKiller->flags & FL_CLIENT )
|
|
{
|
|
strcpy ( szText, STRING( pKiller->netname ) );
|
|
|
|
strcat( szText, " : " );
|
|
strcat( szText, killer_weapon_name );
|
|
strcat( szText, " : " );
|
|
|
|
strcat ( szText, STRING( pVictim->pev->netname ) );
|
|
strcat ( szText, "\n" );
|
|
}
|
|
else if ( FClassnameIs ( pKiller, "worldspawn" ) )
|
|
{
|
|
strcpy ( szText, STRING( pVictim->pev->netname ) );
|
|
strcat ( szText, " fell or drowned or something.\n" );
|
|
}
|
|
else if ( pKiller->solid == SOLID_BSP )
|
|
{
|
|
strcpy ( szText, STRING( pVictim->pev->netname ) );
|
|
strcat ( szText, " was mooshed.\n" );
|
|
}
|
|
else
|
|
{
|
|
strcpy ( szText, STRING( pVictim->pev->netname ) );
|
|
strcat ( szText, " died mysteriously.\n" );
|
|
}
|
|
|
|
UTIL_ClientPrintAll( szText );
|
|
*/
|
|
}
|
|
|
|
//=========================================================
|
|
// PlayerGotWeapon - player has grabbed a weapon that was
|
|
// sitting in the world
|
|
//=========================================================
|
|
void CHalfLifeMultiplay::PlayerGotWeapon(CBasePlayer* pPlayer, CBasePlayerItem* pWeapon)
|
|
{
|
|
}
|
|
|
|
//=========================================================
|
|
// FlWeaponRespawnTime - what is the time in the future
|
|
// at which this weapon may spawn?
|
|
//=========================================================
|
|
float CHalfLifeMultiplay::FlWeaponRespawnTime(CBasePlayerItem* pWeapon)
|
|
{
|
|
if (weaponstay.value > 0)
|
|
{
|
|
// make sure it's only certain weapons
|
|
if ((pWeapon->iFlags() & ITEM_FLAG_LIMITINWORLD) == 0)
|
|
{
|
|
return gpGlobals->time + 0; // weapon respawns almost instantly
|
|
}
|
|
}
|
|
|
|
return gpGlobals->time + WEAPON_RESPAWN_TIME;
|
|
}
|
|
|
|
// when we are within this close to running out of entities, items
|
|
// marked with the ITEM_FLAG_LIMITINWORLD will delay their respawn
|
|
#define ENTITY_INTOLERANCE 100
|
|
|
|
//=========================================================
|
|
// FlWeaponRespawnTime - 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 && WEAPON_NONE != pWeapon->m_iId && (pWeapon->iFlags() & ITEM_FLAG_LIMITINWORLD) != 0)
|
|
{
|
|
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;
|
|
}
|
|
|
|
//=========================================================
|
|
// VecWeaponRespawnSpot - where should this weapon spawn?
|
|
// Some game variations may choose to randomize spawn locations
|
|
//=========================================================
|
|
Vector CHalfLifeMultiplay::VecWeaponRespawnSpot(CBasePlayerItem* pWeapon)
|
|
{
|
|
return pWeapon->pev->origin;
|
|
}
|
|
|
|
//=========================================================
|
|
// WeaponShouldRespawn - any conditions inhibiting the
|
|
// respawning of this weapon?
|
|
//=========================================================
|
|
int CHalfLifeMultiplay::WeaponShouldRespawn(CBasePlayerItem* pWeapon)
|
|
{
|
|
if ((pWeapon->pev->spawnflags & SF_NORESPAWN) != 0)
|
|
{
|
|
return GR_WEAPON_RESPAWN_NO;
|
|
}
|
|
|
|
return GR_WEAPON_RESPAWN_YES;
|
|
}
|
|
|
|
//=========================================================
|
|
// CanHaveWeapon - returns false if the player is not allowed
|
|
// to pick up this weapon
|
|
//=========================================================
|
|
bool CHalfLifeMultiplay::CanHavePlayerItem(CBasePlayer* pPlayer, CBasePlayerItem* pItem)
|
|
{
|
|
if (weaponstay.value > 0)
|
|
{
|
|
if ((pItem->iFlags() & ITEM_FLAG_LIMITINWORLD) != 0)
|
|
return CGameRules::CanHavePlayerItem(pPlayer, pItem);
|
|
|
|
// check if the player already has this weapon
|
|
for (int i = 0; i < MAX_ITEM_TYPES; i++)
|
|
{
|
|
CBasePlayerItem* it = pPlayer->m_rgpPlayerItems[i];
|
|
|
|
while (it != NULL)
|
|
{
|
|
if (it->m_iId == pItem->m_iId)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
it = it->m_pNext;
|
|
}
|
|
}
|
|
}
|
|
|
|
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) != 0)
|
|
{
|
|
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)
|
|
{
|
|
return gpGlobals->time + ITEM_RESPAWN_TIME;
|
|
}
|
|
|
|
//=========================================================
|
|
// 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)
|
|
{
|
|
// if ( pEntity->pev->flags & FL_MONSTER )
|
|
// return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
int CHalfLifeMultiplay::AmmoShouldRespawn(CBasePlayerAmmo* pAmmo)
|
|
{
|
|
if ((pAmmo->pev->spawnflags & SF_NORESPAWN) != 0)
|
|
{
|
|
return GR_AMMO_RESPAWN_NO;
|
|
}
|
|
|
|
return GR_AMMO_RESPAWN_YES;
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
float CHalfLifeMultiplay::FlAmmoRespawnTime(CBasePlayerAmmo* pAmmo)
|
|
{
|
|
return gpGlobals->time + AMMO_RESPAWN_TIME;
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
Vector CHalfLifeMultiplay::VecAmmoRespawnSpot(CBasePlayerAmmo* pAmmo)
|
|
{
|
|
return pAmmo->pev->origin;
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
float CHalfLifeMultiplay::FlHealthChargerRechargeTime()
|
|
{
|
|
return 60;
|
|
}
|
|
|
|
|
|
float CHalfLifeMultiplay::FlHEVChargerRechargeTime()
|
|
{
|
|
return 30;
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
int CHalfLifeMultiplay::DeadPlayerWeapons(CBasePlayer* pPlayer)
|
|
{
|
|
return GR_PLR_DROP_GUN_ACTIVE;
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
int CHalfLifeMultiplay::DeadPlayerAmmo(CBasePlayer* pPlayer)
|
|
{
|
|
return GR_PLR_DROP_AMMO_ACTIVE;
|
|
}
|
|
|
|
edict_t* CHalfLifeMultiplay::GetPlayerSpawnSpot(CBasePlayer* pPlayer)
|
|
{
|
|
edict_t* pentSpawnSpot = CGameRules::GetPlayerSpawnSpot(pPlayer);
|
|
if (IsMultiplayer() && !FStringNull(pentSpawnSpot->v.target))
|
|
{
|
|
FireTargets(STRING(pentSpawnSpot->v.target), pPlayer, pPlayer, USE_TOGGLE, 0);
|
|
}
|
|
|
|
return pentSpawnSpot;
|
|
}
|
|
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
int CHalfLifeMultiplay::PlayerRelationship(CBaseEntity* pPlayer, CBaseEntity* pTarget)
|
|
{
|
|
// half life deathmatch has only enemies
|
|
return GR_NOTTEAMMATE;
|
|
}
|
|
|
|
bool CHalfLifeMultiplay::PlayFootstepSounds(CBasePlayer* pl, float fvol)
|
|
{
|
|
if (g_footsteps && g_footsteps->value == 0)
|
|
return false;
|
|
|
|
if (pl->IsOnLadder() || pl->pev->velocity.Length2D() > 220)
|
|
return true; // only make step sounds in multiplayer if the player is moving fast enough
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CHalfLifeMultiplay::FAllowFlashlight()
|
|
{
|
|
return flashlight.value != 0;
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
bool CHalfLifeMultiplay::FAllowMonsters()
|
|
{
|
|
return (allowmonsters.value != 0);
|
|
}
|
|
|
|
//=========================================================
|
|
//======== CHalfLifeMultiplay private functions ===========
|
|
#define INTERMISSION_TIME 6
|
|
|
|
void CHalfLifeMultiplay::GoToIntermission()
|
|
{
|
|
if (g_fGameOver)
|
|
return; // intermission has already been triggered, so ignore.
|
|
|
|
MESSAGE_BEGIN(MSG_ALL, SVC_INTERMISSION);
|
|
MESSAGE_END();
|
|
|
|
// 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;
|
|
|
|
g_fGameOver = true;
|
|
m_iEndIntermissionButtonHit = false;
|
|
}
|
|
|
|
#define MAX_RULE_BUFFER 1024
|
|
|
|
typedef struct mapcycle_item_s
|
|
{
|
|
struct mapcycle_item_s* next;
|
|
|
|
char mapname[32];
|
|
int minplayers, maxplayers;
|
|
char rulebuffer[MAX_RULE_BUFFER];
|
|
} mapcycle_item_t;
|
|
|
|
typedef struct mapcycle_s
|
|
{
|
|
struct mapcycle_item_s* items;
|
|
struct mapcycle_item_s* next_item;
|
|
} mapcycle_t;
|
|
|
|
/*
|
|
==============
|
|
DestroyMapCycle
|
|
|
|
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 = NULL;
|
|
cycle->next_item = NULL;
|
|
}
|
|
|
|
static char com_token[1500];
|
|
|
|
/*
|
|
==============
|
|
COM_Parse
|
|
|
|
Parse a token out of a string
|
|
==============
|
|
*/
|
|
char* COM_Parse(char* data)
|
|
{
|
|
int c;
|
|
int len;
|
|
|
|
len = 0;
|
|
com_token[0] = 0;
|
|
|
|
if (!data)
|
|
return NULL;
|
|
|
|
// skip whitespace
|
|
skipwhite:
|
|
while ((c = *data) <= ' ')
|
|
{
|
|
if (c == 0)
|
|
return NULL; // end of file;
|
|
data++;
|
|
}
|
|
|
|
// skip // comments
|
|
if (c == '/' && data[1] == '/')
|
|
{
|
|
while ('\0' != *data && *data != '\n')
|
|
data++;
|
|
goto skipwhite;
|
|
}
|
|
|
|
|
|
// handle quoted strings specially
|
|
if (c == '\"')
|
|
{
|
|
data++;
|
|
while (true)
|
|
{
|
|
c = *data++;
|
|
if (c == '\"' || '\0' == c)
|
|
{
|
|
com_token[len] = 0;
|
|
return data;
|
|
}
|
|
com_token[len] = c;
|
|
len++;
|
|
}
|
|
}
|
|
|
|
// parse single characters
|
|
if (c == '{' || c == '}' || c == ')' || c == '(' || c == '\'' || c == ',')
|
|
{
|
|
com_token[len] = c;
|
|
len++;
|
|
com_token[len] = 0;
|
|
return data + 1;
|
|
}
|
|
|
|
// parse a regular word
|
|
do
|
|
{
|
|
com_token[len] = c;
|
|
data++;
|
|
len++;
|
|
c = *data;
|
|
if (c == '{' || c == '}' || c == ')' || c == '(' || c == '\'' || c == ',')
|
|
break;
|
|
} while (c > 32);
|
|
|
|
com_token[len] = 0;
|
|
return data;
|
|
}
|
|
|
|
/*
|
|
==============
|
|
COM_TokenWaiting
|
|
|
|
Returns 1 if additional data is waiting to be processed on this line
|
|
==============
|
|
*/
|
|
bool COM_TokenWaiting(char* buffer)
|
|
{
|
|
char* p;
|
|
|
|
p = buffer;
|
|
while ('\0' != *p && *p != '\n')
|
|
{
|
|
if (0 == isspace(*p) || 0 != isalnum(*p))
|
|
return true;
|
|
|
|
p++;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
==============
|
|
ReloadMapCycleFile
|
|
|
|
|
|
Parses mapcycle.txt file into mapcycle_t structure
|
|
==============
|
|
*/
|
|
bool ReloadMapCycleFile(char* filename, mapcycle_t* cycle)
|
|
{
|
|
char szBuffer[MAX_RULE_BUFFER];
|
|
char szMap[32];
|
|
int length;
|
|
char* pFileList;
|
|
char* aFileList = pFileList = (char*)LOAD_FILE_FOR_ME(filename, &length);
|
|
bool hasbuffer;
|
|
mapcycle_item_s *item, *newlist = NULL, *next;
|
|
|
|
if (pFileList && 0 != length)
|
|
{
|
|
// the first map name in the file becomes the default
|
|
while (true)
|
|
{
|
|
hasbuffer = false;
|
|
memset(szBuffer, 0, MAX_RULE_BUFFER);
|
|
|
|
pFileList = COM_Parse(pFileList);
|
|
if (strlen(com_token) <= 0)
|
|
break;
|
|
|
|
strncpy(szMap, com_token, sizeof(szMap));
|
|
szMap[sizeof(szMap) - 1] = '\0';
|
|
|
|
// Any more tokens on this line?
|
|
if (COM_TokenWaiting(pFileList))
|
|
{
|
|
pFileList = COM_Parse(pFileList);
|
|
if (strlen(com_token) > 0)
|
|
{
|
|
hasbuffer = true;
|
|
strncpy(szBuffer, com_token, sizeof(szBuffer));
|
|
szBuffer[sizeof(szBuffer) - 1] = '\0';
|
|
}
|
|
}
|
|
|
|
// Check map
|
|
if (IS_MAP_VALID(szMap))
|
|
{
|
|
// Create entry
|
|
char* s;
|
|
|
|
item = new mapcycle_item_s;
|
|
|
|
strcpy(item->mapname, szMap);
|
|
|
|
item->minplayers = 0;
|
|
item->maxplayers = 0;
|
|
|
|
memset(item->rulebuffer, 0, MAX_RULE_BUFFER);
|
|
|
|
if (hasbuffer)
|
|
{
|
|
s = g_engfuncs.pfnInfoKeyValue(szBuffer, "minplayers");
|
|
if (s && '\0' != s[0])
|
|
{
|
|
item->minplayers = atoi(s);
|
|
item->minplayers = V_max(item->minplayers, 0);
|
|
item->minplayers = V_min(item->minplayers, gpGlobals->maxClients);
|
|
}
|
|
s = g_engfuncs.pfnInfoKeyValue(szBuffer, "maxplayers");
|
|
if (s && '\0' != s[0])
|
|
{
|
|
item->maxplayers = atoi(s);
|
|
item->maxplayers = V_max(item->maxplayers, 0);
|
|
item->maxplayers = V_min(item->maxplayers, gpGlobals->maxClients);
|
|
}
|
|
|
|
// Remove keys
|
|
//
|
|
g_engfuncs.pfnInfo_RemoveKey(szBuffer, "minplayers");
|
|
g_engfuncs.pfnInfo_RemoveKey(szBuffer, "maxplayers");
|
|
|
|
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 false;
|
|
}
|
|
|
|
while (item->next)
|
|
{
|
|
item = item->next;
|
|
}
|
|
item->next = cycle->items;
|
|
|
|
cycle->next_item = item->next;
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
==============
|
|
CountPlayers
|
|
|
|
Determine the current # of active players on the server for map cycling logic
|
|
==============
|
|
*/
|
|
int CountPlayers()
|
|
{
|
|
int num = 0;
|
|
|
|
for (int i = 1; i <= gpGlobals->maxClients; i++)
|
|
{
|
|
CBaseEntity* pEnt = UTIL_PlayerByIndex(i);
|
|
|
|
if (pEnt)
|
|
{
|
|
num = num + 1;
|
|
}
|
|
}
|
|
|
|
return num;
|
|
}
|
|
|
|
/*
|
|
==============
|
|
ExtractCommandString
|
|
|
|
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* o;
|
|
|
|
if (*s == '\\')
|
|
s++;
|
|
|
|
while (true)
|
|
{
|
|
o = pkey;
|
|
while (*s != '\\')
|
|
{
|
|
if ('\0' == *s)
|
|
return;
|
|
*o++ = *s++;
|
|
}
|
|
*o = 0;
|
|
s++;
|
|
|
|
o = value;
|
|
|
|
while (*s != '\\' && '\0' != *s)
|
|
{
|
|
if ('\0' == *s)
|
|
return;
|
|
*o++ = *s++;
|
|
}
|
|
*o = 0;
|
|
|
|
strcat(szCommand, pkey);
|
|
if (strlen(value) > 0)
|
|
{
|
|
strcat(szCommand, " ");
|
|
strcat(szCommand, value);
|
|
}
|
|
strcat(szCommand, "\n");
|
|
|
|
if ('\0' == *s)
|
|
return;
|
|
s++;
|
|
}
|
|
}
|
|
|
|
/*
|
|
==============
|
|
ChangeLevel
|
|
|
|
Server is changing to a new level, check mapcycle.txt for map name and setup info
|
|
==============
|
|
*/
|
|
void CHalfLifeMultiplay::ChangeLevel()
|
|
{
|
|
static char szPreviousMapCycleFile[256];
|
|
static mapcycle_t mapcycle;
|
|
|
|
char szNextMap[32];
|
|
char szFirstMapInList[32];
|
|
char szCommands[1500];
|
|
char szRules[1500];
|
|
int minplayers = 0, maxplayers = 0;
|
|
strcpy(szFirstMapInList, "hldm1"); // the absolute default level is hldm1
|
|
|
|
int curplayers;
|
|
bool do_cycle = true;
|
|
|
|
// find the map to change to
|
|
char* mapcfile = (char*)CVAR_GET_STRING("mapcyclefile");
|
|
ASSERT(mapcfile != NULL);
|
|
|
|
szCommands[0] = '\0';
|
|
szRules[0] = '\0';
|
|
|
|
curplayers = CountPlayers();
|
|
|
|
// Has the map cycle filename changed?
|
|
if (stricmp(mapcfile, szPreviousMapCycleFile))
|
|
{
|
|
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
|
|
strcpy(szNextMap, STRING(gpGlobals->mapname));
|
|
strcpy(szFirstMapInList, STRING(gpGlobals->mapname));
|
|
|
|
// Traverse list
|
|
for (item = mapcycle.next_item; item->next != mapcycle.next_item; item = item->next)
|
|
{
|
|
keeplooking = false;
|
|
|
|
ASSERT(item != NULL);
|
|
|
|
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
|
|
strcpy(szNextMap, item->mapname);
|
|
|
|
ExtractCommandString(item->rulebuffer, szCommands);
|
|
strcpy(szRules, item->rulebuffer);
|
|
}
|
|
|
|
if (!IS_MAP_VALID(szNextMap))
|
|
{
|
|
strcpy(szNextMap, szFirstMapInList);
|
|
}
|
|
|
|
g_fGameOver = true;
|
|
|
|
ALERT(at_console, "CHANGE LEVEL: %s\n", szNextMap);
|
|
if (0 != minplayers || 0 != maxplayers)
|
|
{
|
|
ALERT(at_console, "PLAYER COUNT: min %i max %i current %i\n", minplayers, maxplayers, curplayers);
|
|
}
|
|
if (strlen(szRules) > 0)
|
|
{
|
|
ALERT(at_console, "RULES: %s\n", szRules);
|
|
}
|
|
|
|
CHANGE_LEVEL(szNextMap, NULL);
|
|
if (strlen(szCommands) > 0)
|
|
{
|
|
SERVER_COMMAND(szCommands);
|
|
}
|
|
}
|
|
|
|
#define MAX_MOTD_CHUNK 60
|
|
#define MAX_MOTD_LENGTH 1536 // (MAX_MOTD_CHUNK * 4)
|
|
|
|
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, NULL, 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 && '\0' != *pFileList && char_count < MAX_MOTD_LENGTH)
|
|
{
|
|
char chunk[MAX_MOTD_CHUNK + 1];
|
|
|
|
if (strlen(pFileList) < MAX_MOTD_CHUNK)
|
|
{
|
|
strcpy(chunk, pFileList);
|
|
}
|
|
else
|
|
{
|
|
strncpy(chunk, pFileList, MAX_MOTD_CHUNK);
|
|
chunk[MAX_MOTD_CHUNK] = 0; // strncpy doesn't always append the null terminator
|
|
}
|
|
|
|
char_count += strlen(chunk);
|
|
if (char_count < MAX_MOTD_LENGTH)
|
|
pFileList = aFileList + char_count;
|
|
else
|
|
*pFileList = 0;
|
|
|
|
MESSAGE_BEGIN(MSG_ONE, gmsgMOTD, NULL, client);
|
|
WRITE_BYTE(static_cast<int>('\0' == *pFileList)); // false means there is still more message to come
|
|
WRITE_STRING(chunk);
|
|
MESSAGE_END();
|
|
}
|
|
|
|
FREE_FILE(aFileList);
|
|
}
|
|
|
|
//=========================================================
|
|
//=========================================================
|
|
// Busters Gamerules
|
|
//=========================================================
|
|
//=========================================================
|
|
|
|
#define EGON_BUSTING_TIME 10
|
|
|
|
bool IsBustingGame()
|
|
{
|
|
return sv_busters.value == 1;
|
|
}
|
|
|
|
bool IsPlayerBusting(CBaseEntity* pPlayer)
|
|
{
|
|
if (!pPlayer || !pPlayer->IsPlayer() || !IsBustingGame())
|
|
return false;
|
|
|
|
return ((CBasePlayer*)pPlayer)->HasPlayerItemFromID(WEAPON_EGON);
|
|
}
|
|
|
|
bool BustingCanHaveItem(CBasePlayer* pPlayer, CBaseEntity* pItem)
|
|
{
|
|
bool bIsWeaponOrAmmo = false;
|
|
|
|
if (strstr(STRING(pItem->pev->classname), "weapon_") || strstr(STRING(pItem->pev->classname), "ammo_"))
|
|
{
|
|
bIsWeaponOrAmmo = true;
|
|
}
|
|
|
|
// Busting players can't have ammo nor weapons
|
|
if (IsPlayerBusting(pPlayer) && bIsWeaponOrAmmo)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
//=========================================================
|
|
CMultiplayBusters::CMultiplayBusters()
|
|
{
|
|
m_flEgonBustingCheckTime = -1;
|
|
}
|
|
|
|
//=========================================================
|
|
void CMultiplayBusters::Think()
|
|
{
|
|
CheckForEgons();
|
|
|
|
CHalfLifeMultiplay::Think();
|
|
}
|
|
|
|
//=========================================================
|
|
int CMultiplayBusters::IPointsForKill(CBasePlayer* pAttacker, CBasePlayer* pKilled)
|
|
{
|
|
// If the attacker is busting, they get a point per kill
|
|
if (IsPlayerBusting(pAttacker))
|
|
return 1;
|
|
|
|
// If the victim is busting, then the attacker gets a point
|
|
if (IsPlayerBusting(pKilled))
|
|
return 2;
|
|
|
|
return 0;
|
|
}
|
|
|
|
//=========================================================
|
|
void CMultiplayBusters::PlayerKilled(CBasePlayer* pVictim, entvars_t* pKiller, entvars_t* pInflictor)
|
|
{
|
|
if (IsPlayerBusting(pVictim))
|
|
{
|
|
UTIL_ClientPrintAll(HUD_PRINTCENTER, "The Buster is dead!!");
|
|
|
|
// Reset egon check time
|
|
m_flEgonBustingCheckTime = -1;
|
|
|
|
CBasePlayer* peKiller = NULL;
|
|
CBaseEntity* ktmp = CBaseEntity::Instance(pKiller);
|
|
|
|
if (ktmp && (ktmp->Classify() == CLASS_PLAYER))
|
|
{
|
|
peKiller = (CBasePlayer*)ktmp;
|
|
}
|
|
else if (ktmp && (ktmp->Classify() == CLASS_VEHICLE))
|
|
{
|
|
CBasePlayer* pDriver = ((CFuncVehicle*)ktmp)->m_pDriver;
|
|
|
|
if (pDriver != NULL)
|
|
{
|
|
peKiller = pDriver;
|
|
ktmp = pDriver;
|
|
pKiller = pDriver->pev;
|
|
}
|
|
}
|
|
|
|
if (peKiller)
|
|
{
|
|
UTIL_ClientPrintAll(HUD_PRINTTALK, UTIL_VarArgs("%s has has killed the Buster!\n", STRING((CBasePlayer*)peKiller->pev->netname)));
|
|
}
|
|
|
|
pVictim->pev->renderfx = kRenderFxNone;
|
|
pVictim->pev->rendercolor = g_vecZero;
|
|
// pVictim->pev->effects &= ~EF_BRIGHTFIELD;
|
|
}
|
|
|
|
CHalfLifeMultiplay::PlayerKilled(pVictim, pKiller, pInflictor);
|
|
}
|
|
|
|
//=========================================================
|
|
void CMultiplayBusters::DeathNotice(CBasePlayer* pVictim, entvars_t* pKiller, entvars_t* pevInflictor)
|
|
{
|
|
// Only death notices that the Buster was involved in in Busting game mode
|
|
if (!IsPlayerBusting(pVictim) && !IsPlayerBusting(CBaseEntity::Instance(pKiller)))
|
|
return;
|
|
|
|
CHalfLifeMultiplay::DeathNotice(pVictim, pKiller, pevInflictor);
|
|
}
|
|
|
|
//=========================================================
|
|
int CMultiplayBusters::WeaponShouldRespawn(CBasePlayerItem* pWeapon)
|
|
{
|
|
if (pWeapon->m_iId == WEAPON_EGON)
|
|
return GR_WEAPON_RESPAWN_NO;
|
|
|
|
return CHalfLifeMultiplay::WeaponShouldRespawn(pWeapon);
|
|
}
|
|
|
|
|
|
//=========================================================
|
|
// CheckForEgons:
|
|
// Check to see if any player has an egon
|
|
// If they don't then get the lowest player on the scoreboard and give them one
|
|
// Then check to see if any weapon boxes out there has an egon, and delete it
|
|
//=========================================================
|
|
void CMultiplayBusters::CheckForEgons()
|
|
{
|
|
if (m_flEgonBustingCheckTime <= 0.0f)
|
|
{
|
|
m_flEgonBustingCheckTime = gpGlobals->time + EGON_BUSTING_TIME;
|
|
return;
|
|
}
|
|
|
|
if (m_flEgonBustingCheckTime <= gpGlobals->time)
|
|
{
|
|
m_flEgonBustingCheckTime = -1.0f;
|
|
|
|
for (int i = 1; i <= gpGlobals->maxClients; i++)
|
|
{
|
|
CBasePlayer* pPlayer = (CBasePlayer*)UTIL_PlayerByIndex(i);
|
|
|
|
// Someone is busting, no need to continue
|
|
if (IsPlayerBusting(pPlayer))
|
|
return;
|
|
}
|
|
|
|
int bBestFrags = 9999;
|
|
CBasePlayer* pBestPlayer = NULL;
|
|
|
|
for (int i = 1; i <= gpGlobals->maxClients; i++)
|
|
{
|
|
CBasePlayer* pPlayer = (CBasePlayer*)UTIL_PlayerByIndex(i);
|
|
|
|
if (pPlayer && pPlayer->pev->frags <= bBestFrags)
|
|
{
|
|
bBestFrags = pPlayer->pev->frags;
|
|
pBestPlayer = pPlayer;
|
|
}
|
|
}
|
|
|
|
if (pBestPlayer)
|
|
{
|
|
pBestPlayer->GiveNamedItem("weapon_egon");
|
|
|
|
CBaseEntity* pEntity = NULL;
|
|
|
|
// Find a weaponbox that includes an Egon, then destroy it
|
|
while ((pEntity = UTIL_FindEntityByClassname(pEntity, "weaponbox")) != NULL)
|
|
{
|
|
CWeaponBox* pWeaponBox = (CWeaponBox*)pEntity;
|
|
|
|
if (pWeaponBox)
|
|
{
|
|
CBasePlayerItem* pWeapon;
|
|
|
|
for (int i = 0; i < MAX_ITEM_TYPES; i++)
|
|
{
|
|
pWeapon = pWeaponBox->m_rgpPlayerItems[i];
|
|
|
|
while (pWeapon)
|
|
{
|
|
// There you are, bye box
|
|
if (pWeapon->m_iId == WEAPON_EGON)
|
|
{
|
|
pWeaponBox->Kill();
|
|
break;
|
|
}
|
|
|
|
pWeapon = pWeapon->m_pNext;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//=========================================================
|
|
bool CMultiplayBusters::CanHavePlayerItem(CBasePlayer* pPlayer, CBasePlayerItem* pItem)
|
|
{
|
|
// Buster cannot have more weapons nor ammo
|
|
if (BustingCanHaveItem(pPlayer, pItem) == false)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return CHalfLifeMultiplay::CanHavePlayerItem(pPlayer, pItem);
|
|
}
|
|
|
|
//=========================================================
|
|
bool CMultiplayBusters::CanHaveItem(CBasePlayer* pPlayer, CItem* pItem)
|
|
{
|
|
// Buster cannot have more weapons nor ammo
|
|
if (BustingCanHaveItem(pPlayer, pItem) == false)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return CHalfLifeMultiplay::CanHaveItem(pPlayer, pItem);
|
|
}
|
|
|
|
//=========================================================
|
|
void CMultiplayBusters::PlayerGotWeapon(CBasePlayer* pPlayer, CBasePlayerItem* pWeapon)
|
|
{
|
|
if (pWeapon->m_iId == WEAPON_EGON)
|
|
{
|
|
pPlayer->RemoveAllItems(false);
|
|
|
|
UTIL_ClientPrintAll(HUD_PRINTCENTER, "Long live the new Buster!");
|
|
UTIL_ClientPrintAll(HUD_PRINTTALK, UTIL_VarArgs("%s is busting!\n", STRING((CBasePlayer*)pPlayer->pev->netname)));
|
|
|
|
SetPlayerModel(pPlayer);
|
|
|
|
pPlayer->pev->health = pPlayer->pev->max_health;
|
|
pPlayer->pev->armorvalue = 100;
|
|
|
|
pPlayer->pev->renderfx = kRenderFxGlowShell;
|
|
pPlayer->pev->renderamt = 25;
|
|
pPlayer->pev->rendercolor = Vector(0, 75, 250);
|
|
|
|
CBasePlayerWeapon* pEgon = (CBasePlayerWeapon*)pWeapon;
|
|
|
|
pEgon->m_iDefaultAmmo = 100;
|
|
pPlayer->m_rgAmmo[pEgon->m_iPrimaryAmmoType] = pEgon->m_iDefaultAmmo;
|
|
|
|
g_engfuncs.pfnSetClientKeyValue(pPlayer->entindex(), g_engfuncs.pfnGetInfoKeyBuffer(pPlayer->edict()), "model", "ivan");
|
|
}
|
|
}
|
|
|
|
void CMultiplayBusters::ClientUserInfoChanged(CBasePlayer* pPlayer, char* infobuffer)
|
|
{
|
|
SetPlayerModel(pPlayer);
|
|
|
|
// Set preferences
|
|
pPlayer->SetPrefsFromUserinfo(infobuffer);
|
|
}
|
|
|
|
void CMultiplayBusters::PlayerSpawn(CBasePlayer* pPlayer)
|
|
{
|
|
CHalfLifeMultiplay::PlayerSpawn(pPlayer);
|
|
SetPlayerModel(pPlayer);
|
|
}
|
|
|
|
void CMultiplayBusters::SetPlayerModel(CBasePlayer* pPlayer)
|
|
{
|
|
if (IsPlayerBusting(pPlayer))
|
|
{
|
|
g_engfuncs.pfnSetClientKeyValue(pPlayer->entindex(), g_engfuncs.pfnGetInfoKeyBuffer(pPlayer->edict()), "model", "ivan");
|
|
}
|
|
else
|
|
{
|
|
g_engfuncs.pfnSetClientKeyValue(pPlayer->entindex(), g_engfuncs.pfnGetInfoKeyBuffer(pPlayer->edict()), "model", "skeleton");
|
|
}
|
|
}
|