1278 lines
29 KiB
Plaintext
1278 lines
29 KiB
Plaintext
/*
|
|
* Copyright (c) 2016-2021 Marco Cawthorne <marco@icculus.org>
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
|
|
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
|
|
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
|
|
string
|
|
CSMultiplayerRules::Title(void)
|
|
{
|
|
return "Counter-Strike";
|
|
}
|
|
|
|
int
|
|
CSMultiplayerRules::MaxItemPerSlot(int slot)
|
|
{
|
|
/* grenades */
|
|
if (slot == 3) {
|
|
return (3);
|
|
}
|
|
return (1);
|
|
}
|
|
|
|
void
|
|
CSMultiplayerRules::PlayerDisconnect(NSClientPlayer pl)
|
|
{
|
|
if (health > 0)
|
|
PlayerDeath(pl);
|
|
|
|
super::PlayerDisconnect(pl);
|
|
}
|
|
|
|
void
|
|
CSMultiplayerRules::PlayerDeath(NSClientPlayer pl)
|
|
{
|
|
player targ = (player)g_dmg_eTarget;
|
|
player attk = (player)g_dmg_eAttacker;
|
|
NSRenderableEntity newCorpse;
|
|
float deathAnimation = ANIM_DEATH1;
|
|
|
|
if (targ.flags & FL_CROUCHING) {
|
|
deathAnimation = ANIM_CROUCH_DIE;
|
|
} else {
|
|
switch (g_dmg_iHitBody) {
|
|
case BODY_HEAD:
|
|
deathAnimation = ANIM_DIE_HEAD;
|
|
break;
|
|
case BODY_STOMACH:
|
|
deathAnimation = ANIM_DIE_GUT;
|
|
break;
|
|
case BODY_ARMLEFT:
|
|
deathAnimation = ANIM_DIE_LEFT;
|
|
break;
|
|
case BODY_ARMRIGHT:
|
|
deathAnimation = ANIM_DIE_RIGHT;
|
|
break;
|
|
default:
|
|
bool isFacing = targ.IsFacingPosition(g_dmg_vecLocation);
|
|
|
|
/* still want to play ANIM_DEATH1 */
|
|
if (random() < 0.5f) {
|
|
if (isFacing == false) {
|
|
deathAnimation = ANIM_DIE_FORWARD;
|
|
} else {
|
|
deathAnimation = ANIM_DIE_BACK;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
newCorpse = (NSRenderableEntity)FX_Corpse_Spawn(targ, deathAnimation);
|
|
|
|
targ.SpectatorDeathcam(newCorpse, attk, 3.0f);
|
|
|
|
/* obituary networking */
|
|
WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET);
|
|
WriteByte(MSG_MULTICAST, EV_OBITUARY);
|
|
if (g_dmg_eAttacker.netname)
|
|
WriteString(MSG_MULTICAST, strcat(HUD_GetChatColorHEX(g_dmg_eAttacker.team), g_dmg_eAttacker.netname));
|
|
else
|
|
WriteString(MSG_MULTICAST, g_dmg_eAttacker.classname);
|
|
|
|
WriteString(MSG_MULTICAST, strcat(HUD_GetChatColorHEX(targ.team), targ.netname));
|
|
|
|
WriteByte(MSG_MULTICAST, g_dmg_iWeapon);
|
|
WriteByte(MSG_MULTICAST, 0);
|
|
msg_entity = world;
|
|
multicast([0,0,0], MULTICAST_ALL);
|
|
|
|
Plugin_PlayerObituary(g_dmg_eAttacker, g_dmg_eTarget, g_dmg_iWeapon, g_dmg_iHitBody, g_dmg_iDamage);
|
|
|
|
/* death-counter */
|
|
targ.deaths++;
|
|
forceinfokey(targ, "*deaths", ftos(targ.deaths));
|
|
|
|
/* update score-counter */
|
|
if (g_dmg_eTarget.flags & FL_CLIENT || g_dmg_eTarget.flags & FL_MONSTER)
|
|
if (g_dmg_eAttacker.flags & FL_CLIENT) {
|
|
float vip = (g_dmg_eTarget.team == TEAM_VIP && g_dmg_eAttacker.team == TEAM_CT);
|
|
|
|
if (g_dmg_eTarget == g_dmg_eAttacker) {
|
|
g_dmg_eAttacker.frags--;
|
|
} else if (g_dmg_eTarget.team == g_dmg_eAttacker.team || vip) {
|
|
g_dmg_eAttacker.frags--;
|
|
Money_AddMoney((NSClientPlayer)g_dmg_eAttacker, autocvar_fcs_penalty_teamkill);
|
|
} else {
|
|
g_dmg_eAttacker.frags++;
|
|
Money_AddMoney((NSClientPlayer)g_dmg_eAttacker, autocvar_fcs_reward_kill);
|
|
}
|
|
}
|
|
|
|
/* scoreboard death icon */
|
|
if (g_dmg_eTarget.flags & FL_CLIENT) {
|
|
targ.SetInfoKey("*icon1", "d_skull");
|
|
targ.SetInfoKey("*icon1_r", "1");
|
|
targ.SetInfoKey("*icon1_g", "0");
|
|
targ.SetInfoKey("*icon1_b", "0");
|
|
}
|
|
|
|
Weapon_DropCurrentWeapon(targ);
|
|
|
|
/* if we're the bomb carrier, make sure we drop the bomb. */
|
|
if (targ.g_items & ITEM_C4BOMB) {
|
|
targ.activeweapon = WEAPON_C4BOMB;
|
|
Weapon_DropCurrentWeapon(targ);
|
|
} else {
|
|
targ.activeweapon = Cstrike_WeaponToDropUponDeath(targ);
|
|
Weapon_DropCurrentWeapon(targ);
|
|
}
|
|
|
|
/* clear all ammo and inventory... */
|
|
PlayerClearWeaponry(targ);
|
|
targ.Death();
|
|
targ.gflags &= ~GF_FLASHLIGHT;
|
|
|
|
targ.StartSoundDef("Player.Death", CHAN_AUTO, true);
|
|
|
|
/* gamerule stuff */
|
|
targ.MakeTempSpectator();
|
|
forceinfokey(targ, "*dead", "1");
|
|
forceinfokey(targ, "*team", ftos(targ.team));
|
|
CountPlayers();
|
|
|
|
/* In Assassination, all Terrorists receive a $2500
|
|
* reward if they won by killing the VIP. */
|
|
if (targ.team == TEAM_VIP) {
|
|
RoundOver(TEAM_T, 2500, FALSE);
|
|
return;
|
|
}
|
|
DeathCheck(targ);
|
|
}
|
|
|
|
void
|
|
CSMultiplayerRules::PlayerPreFrame(NSClientPlayer pl)
|
|
{
|
|
super::PlayerPreFrame(pl);
|
|
|
|
/* the messages that get printed when you aim at something
|
|
happen here */
|
|
{
|
|
vector sourcePos, destPos;
|
|
player p = (player)pl;
|
|
|
|
sourcePos = pl.GetEyePos();
|
|
destPos = sourcePos + (pl.GetForward() * 512);
|
|
traceline(sourcePos, destPos, MOVE_NORMAL, pl);
|
|
|
|
if (trace_ent) {
|
|
if (trace_ent.classname == "player")
|
|
if (trace_ent.team == p.team && p.m_seenFriend == false) {
|
|
env_message_single(pl, "Hint_spotted_a_friend");
|
|
p.m_seenFriend = true;
|
|
} else if (trace_ent.team != p.team && p.m_seenEnemy == false) {
|
|
env_message_single(pl, "Hint_spotted_an_enemy");
|
|
p.m_seenEnemy = true;
|
|
}
|
|
|
|
if (trace_ent.classname == "hostage_entity" && p.m_seenHostage == false) {
|
|
if (p.team == TEAM_T) {
|
|
env_message_single(pl, "Hint_prevent_hostage_rescue");
|
|
} else {
|
|
env_message_single(pl, "Hint_rescue_the_hostages");
|
|
env_message_single(pl, "Hint_press_use_so_hostage_will_follow");
|
|
}
|
|
p.m_seenHostage = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
CSMultiplayerRules::FrameStart(void)
|
|
{
|
|
if ((g_total_players > 0) && (g_cs_gamestate == GAME_INACTIVE)) {
|
|
TimerBegin(2, GAME_COMMENCING);
|
|
} else if (g_total_players == 0) {
|
|
g_cs_gamestate = GAME_INACTIVE;
|
|
g_cs_gametime = 0;
|
|
g_cs_roundswon_t = 0;
|
|
g_cs_roundswon_ct = 0;
|
|
g_cs_roundsplayed = 0;
|
|
} else {
|
|
TimerUpdate(); // Timer that happens once players have started joining
|
|
}
|
|
}
|
|
|
|
void
|
|
CSMultiplayerRules::CreateRescueZones(void)
|
|
{
|
|
int zones = 0;
|
|
|
|
/* not in hostage rescue mode */
|
|
if (g_cs_hostagestotal <= 0) {
|
|
return;
|
|
}
|
|
|
|
/* count the already existing rescue zones. */
|
|
for (entity e = world; (e = find(e, ::classname, "func_hostage_rescue"));) {
|
|
zones++;
|
|
}
|
|
|
|
/* we don't need to create any additional rescue zones. */
|
|
if (zones > 0)
|
|
return;
|
|
|
|
/* hostage zones need to go somewhere */
|
|
for (entity e = world; (e = find(e, ::classname, "info_player_start"));) {
|
|
info_hostage_rescue newzone = spawn(info_hostage_rescue, origin: e.origin);
|
|
newzone.Respawn();
|
|
}
|
|
}
|
|
|
|
void
|
|
CSMultiplayerRules::CreateCTBuyzones(void)
|
|
{
|
|
int zones = 0;
|
|
|
|
/* count the already existing CT zones. */
|
|
for (entity e = world; (e = find(e, ::classname, "func_buyzone"));) {
|
|
if (e.team == 0 || e.team == TEAM_CT) {
|
|
zones++;
|
|
}
|
|
}
|
|
|
|
/* we don't need to create any additional CT zones. */
|
|
if (zones > 0)
|
|
return;
|
|
|
|
/* since no buyzones are available, let's create one around every CT spawn */
|
|
for (entity e = world; (e = find(e, ::classname, "info_player_start"));) {
|
|
info_buyzone newzone = spawn(info_buyzone, origin: e.origin);
|
|
newzone.Respawn();
|
|
newzone.team = TEAM_CT;
|
|
}
|
|
}
|
|
|
|
void
|
|
CSMultiplayerRules::CreateTBuyzones(void)
|
|
{
|
|
int zones = 0;
|
|
|
|
/* count the already existing T zones. */
|
|
for (entity e = world; (e = find(e, ::classname, "func_buyzone"));) {
|
|
if (e.team == 0 || e.team == TEAM_T) {
|
|
zones++;
|
|
}
|
|
}
|
|
|
|
/* we don't need to create any additional T zones. */
|
|
if (zones > 0)
|
|
return;
|
|
|
|
/* since no buyzones are available, let's create one around every T spawn */
|
|
for (entity e = world; (e = find(e, ::classname, "info_player_deathmatch"));) {
|
|
info_buyzone newzone = spawn(info_buyzone, origin: e.origin);
|
|
newzone.Respawn();
|
|
newzone.team = TEAM_T;
|
|
}
|
|
}
|
|
|
|
void
|
|
CSMultiplayerRules::InitPostEnts(void)
|
|
{
|
|
MOTD_LoadDefault();
|
|
|
|
/* let's check if we need to create buyzones */
|
|
switch (g_cstrike_buying) {
|
|
case BUY_CT:
|
|
CreateCTBuyzones();
|
|
break;
|
|
case BUY_T:
|
|
CreateTBuyzones();
|
|
break;
|
|
case BUY_NEITHER:
|
|
break;
|
|
default:
|
|
CreateCTBuyzones();
|
|
CreateTBuyzones();
|
|
}
|
|
|
|
CreateRescueZones();
|
|
|
|
/* get all of the vip zones */
|
|
g_cs_vipzones = 0;
|
|
for (entity e = world; (e = find(e, ::classname, "func_vip_safetyzone"));) {
|
|
g_cs_vipzones++;
|
|
}
|
|
}
|
|
|
|
void
|
|
CSMultiplayerRules::TimerBegin(float tleft, int mode)
|
|
{
|
|
g_cs_gametime = tleft;
|
|
|
|
if (mode == GAME_FREEZE) {
|
|
g_cs_gamestate = GAME_FREEZE;
|
|
} else if (mode == GAME_ACTIVE) {
|
|
g_cs_gamestate = GAME_ACTIVE;
|
|
CountPlayers();
|
|
|
|
if (g_cs_total_t <= 1 || g_cs_total_ct <= 1)
|
|
return;
|
|
|
|
/* if no players are present in the chosen team, force restart round */
|
|
if ((g_cs_alive_t == 0)) {
|
|
RoundOver(TEAM_CT, 3600, FALSE);
|
|
return;
|
|
} else if (g_cs_alive_ct == 0) {
|
|
RoundOver(TEAM_T, 3600, FALSE);
|
|
return;
|
|
}
|
|
|
|
} else if (mode == GAME_END) {
|
|
g_cs_gamestate = GAME_END;
|
|
} else if (mode == GAME_COMMENCING) {
|
|
g_cs_gamestate = GAME_COMMENCING;
|
|
} else if (mode == GAME_OVER) {
|
|
g_cs_gamestate = GAME_OVER;
|
|
}
|
|
}
|
|
|
|
void
|
|
CSMultiplayerRules::TimerUpdate(void)
|
|
{
|
|
/* if we've got hostages in the map... */
|
|
if (g_cs_hostagestotal > 0) {
|
|
/* and they're all rescued.... */
|
|
if (g_cs_hostagesrescued >= g_cs_hostagestotal) {
|
|
/* CTs win! */
|
|
RoundOver(TEAM_CT, 0, FALSE);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// This map has been played enough we think
|
|
if (g_cs_gamestate != GAME_OVER) {
|
|
if (cvar("mp_timelimit") > 0) {
|
|
if (time >= (cvar("mp_timelimit") * 60)) {
|
|
IntermissionStart();
|
|
g_cs_gamestate = GAME_OVER;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Okay, this means that timelimit is not the only deciding factor
|
|
if (autocvar_mp_winlimit > 0 && g_cs_gamestate != GAME_OVER) {
|
|
// It really doesn't matter who won. Do some logging perhaps?
|
|
if (g_cs_roundswon_ct == autocvar_mp_winlimit ||
|
|
g_cs_roundswon_t == autocvar_mp_winlimit) {
|
|
IntermissionStart();
|
|
}
|
|
}
|
|
|
|
/* INACTIVE means no one is registered as a player */
|
|
if (g_cs_gamestate == GAME_INACTIVE) {
|
|
return;
|
|
}
|
|
|
|
/* our continously running down timer */
|
|
g_cs_gametime = bound(0, g_cs_gametime - frametime, g_cs_gametime);
|
|
|
|
/* if the round is over or the game is done with... */
|
|
if (g_cs_gamestate == GAME_COMMENCING || g_cs_gamestate == GAME_END) {
|
|
if (g_cs_gametime <= 0) {
|
|
if (g_cs_roundswon_t == 0 && g_cs_roundswon_ct == 0) {
|
|
Money_ResetTeamReward();
|
|
Money_ResetRoundReward();
|
|
RestartRound(TRUE);
|
|
} else {
|
|
if (autocvar_mp_halftime == TRUE && (autocvar_mp_winlimit / 2 == g_cs_roundsplayed)) {
|
|
Money_ResetTeamReward();
|
|
SwitchTeams();
|
|
RestartRound(TRUE);
|
|
} else {
|
|
RestartRound(FALSE);
|
|
}
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
if ((g_cs_gamestate == GAME_ACTIVE) || (g_cs_gamestate == GAME_FREEZE)) {
|
|
if (g_cs_gametime <= 0) {
|
|
if (g_cs_gamestate == GAME_ACTIVE) {
|
|
/* 1.5 will make the T's lose if time runs out no matter what */
|
|
if (autocvar_fcs_fix_bombtimer == TRUE) {
|
|
if (g_cs_bombzones > 0 && g_cs_bombplanted == TRUE) {
|
|
return;
|
|
}
|
|
}
|
|
TimeOut();
|
|
TimerBegin(5, GAME_END); // Round is over, 5 seconds til a new round starts
|
|
} else {
|
|
TimerBegin(autocvar_mp_roundtime * 60, GAME_ACTIVE); // Unfreeze
|
|
Radio_StartMessage();
|
|
CSBot_RoundStart();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
BuyingPossible
|
|
|
|
Checks if it is possible for players to buy anything
|
|
=================
|
|
*/
|
|
bool
|
|
CSMultiplayerRules::BuyingPossible(NSClientPlayer pl)
|
|
{
|
|
if (pl.health <= 0) {
|
|
return (false);
|
|
}
|
|
|
|
if (g_cs_gamestate == GAME_ACTIVE) {
|
|
if (((autocvar_mp_roundtime * 60) - g_cs_gametime) > autocvar_mp_buytime) {
|
|
centerprint(pl, sprintf("%d seconds have passed...\nYou can't buy anything now!", autocvar_mp_buytime));
|
|
return (false);
|
|
}
|
|
}
|
|
|
|
if (pl.team == TEAM_VIP) {
|
|
centerprint(pl, "You are the VIP...\nYou can't buy anything!\n");
|
|
return (false);
|
|
}
|
|
|
|
if (g_cstrike_buying == BUY_NEITHER) {
|
|
centerprint(pl, "Sorry, you aren't meant\nto be buying anything.\n");
|
|
return (false);
|
|
}
|
|
|
|
if (g_cstrike_buying != BUY_BOTH) {
|
|
if (g_cstrike_buying == BUY_CT && pl.team == TEAM_T) {
|
|
centerprint(pl, "Terrorists aren't allowed to\nbuy anything on this map!\n");
|
|
return (false);
|
|
} else if (g_cstrike_buying == BUY_T && pl.team == TEAM_CT) {
|
|
centerprint(pl, "CTs aren't allowed to buy\nanything on this map!\n");
|
|
return (false);
|
|
}
|
|
}
|
|
|
|
if (!(pl.gflags & GF_BUYZONE)) {
|
|
centerprint(pl, "Sorry, you aren't in a buyzone.\n");
|
|
return (false);
|
|
}
|
|
|
|
return (true);
|
|
}
|
|
|
|
void
|
|
CSMultiplayerRules::MakeBomber(NSClientPlayer pl)
|
|
{
|
|
Weapons_AddItem(pl, WEAPON_C4BOMB, -1);
|
|
env_message_single(pl, "Hint_you_have_the_bomb");
|
|
}
|
|
|
|
void
|
|
CSMultiplayerRules::MakeVIP(NSClientPlayer pl)
|
|
{
|
|
pl.team = TEAM_VIP;
|
|
PlayerRespawn(pl, pl.team);
|
|
env_message_single(pl, "Hint_you_are_the_vip");
|
|
forceinfokey(pl, "*dead", "2");
|
|
pl.SetModel("models/player/vip/vip.mdl");
|
|
}
|
|
|
|
/*
|
|
=================
|
|
RestartRound
|
|
|
|
Loop through all ents and handle them
|
|
=================
|
|
*/
|
|
void
|
|
CSMultiplayerRules::RestartRound(int iWipe)
|
|
{
|
|
if (autocvar_fcs_swaponround > 0)
|
|
if (m_iSwapTeamRoundCounter >= autocvar_fcs_swaponround) {
|
|
m_iSwapTeamRoundCounter = 0;
|
|
|
|
for (entity eFind = world; (eFind = find(eFind, ::classname, "player"));) {
|
|
player pl = (player)eFind;
|
|
|
|
if (pl.team == TEAM_T) {
|
|
pl.team = TEAM_CT; /* temp for CT */
|
|
pl.charmodel += 4;
|
|
pl.health = 0;
|
|
} else if (pl.team == TEAM_VIP || pl.team == TEAM_CT) {
|
|
pl.team = TEAM_T; /* temp for CT */
|
|
pl.charmodel -= 4;
|
|
pl.health = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Reset CSBot vars
|
|
CSBot_RestartRound();
|
|
|
|
for (entity eFind = world; (eFind = findfloat(eFind, ::team, TEAM_T));) {
|
|
if (!(eFind.flags & FL_CLIENT))
|
|
continue;
|
|
|
|
player pl = (player)eFind;
|
|
|
|
if (pl.health > 0 && iWipe == FALSE) {
|
|
PlayerRespawn(pl, pl.team);
|
|
} else {
|
|
PlayerMakeSpectator(pl);
|
|
PlayerMakePlayable(pl, pl.charmodel);
|
|
}
|
|
|
|
if (iWipe == FALSE) {
|
|
Money_GiveTeamReward(pl);
|
|
} else {
|
|
PlayerReset(pl);
|
|
}
|
|
}
|
|
for (entity eFind = world; (eFind = findfloat(eFind, ::team, TEAM_CT));) {
|
|
if (!(eFind.flags & FL_CLIENT))
|
|
continue;
|
|
|
|
player pl = (player)eFind;
|
|
|
|
if (pl.health > 0 && iWipe == FALSE) {
|
|
PlayerRespawn(pl, pl.team);
|
|
} else {
|
|
PlayerMakeSpectator(pl);
|
|
PlayerMakePlayable(pl, pl.charmodel);
|
|
}
|
|
|
|
if (iWipe == FALSE) {
|
|
Money_GiveTeamReward(pl);
|
|
} else {
|
|
PlayerReset(pl);
|
|
}
|
|
}
|
|
|
|
/* respawn the VIP as well, but turn them back into a CT. */
|
|
for (entity eFind = world; (eFind = findfloat(eFind, ::team, TEAM_VIP));) {
|
|
if (!(eFind.flags & FL_CLIENT))
|
|
continue;
|
|
|
|
player pl = (player)eFind;
|
|
pl.team = TEAM_CT;
|
|
|
|
if (pl.health > 0 && iWipe == FALSE) {
|
|
PlayerRespawn(pl, TEAM_CT);
|
|
} else {
|
|
PlayerMakeSpectator(pl);
|
|
PlayerMakePlayable(pl, pl.charmodel);
|
|
}
|
|
|
|
if (iWipe == FALSE) {
|
|
Money_GiveTeamReward(pl);
|
|
} else {
|
|
PlayerReset(pl);
|
|
}
|
|
}
|
|
|
|
for (entity eFind = world; (eFind = find(eFind, ::classname, "tempdecal"));) {
|
|
decal dec = (decal)eFind;
|
|
dec.m_strTexture = "";
|
|
dec.SendFlags = -1;
|
|
}
|
|
for (entity eFind = world; (eFind = find(eFind, ::classname, "item_c4"));) {
|
|
NSEntity e = (NSEntity)eFind;
|
|
e.Destroy();
|
|
}
|
|
// Remove potential bomb backpack model from the world, else bots will go
|
|
// chase a ghost.
|
|
entity e = find(world, ::model, "models/w_backpack.mdl");
|
|
if (e != world) {
|
|
remove(e);
|
|
}
|
|
|
|
WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET);
|
|
WriteByte(MSG_MULTICAST, EV_CLEARDECALS);
|
|
msg_entity = world;
|
|
multicast([0,0,0], MULTICAST_ALL);
|
|
|
|
// Select a random Terrorist for the bomb, if needed
|
|
if (g_cs_bombzones > 0) {
|
|
int iRandomT = floor(random(1, (float)g_cs_alive_t + 1));
|
|
int iPickT = 0;
|
|
|
|
for (entity eFind = world; (eFind = find(eFind, ::classname, "player"));) {
|
|
if (eFind.team == TEAM_T) {
|
|
iPickT++;
|
|
|
|
if (iPickT == iRandomT) {
|
|
MakeBomber((player)eFind);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// If there is a VIP, select a random CT to be it
|
|
if (g_cs_vipzones > 0) {
|
|
int iRandomCT = floor(random(1, (float)g_cs_alive_ct + 1));
|
|
int iPickCT = 0;
|
|
|
|
for (entity eFind = world; (eFind = find(eFind, ::classname, "player"));) {
|
|
if (eFind.team == TEAM_CT) {
|
|
iPickCT++;
|
|
if (iPickCT == iRandomCT) {
|
|
MakeVIP((player)eFind);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Respawn all the entities
|
|
for (entity a = world; (a = findfloat(a, ::identity, 1));) {
|
|
NSEntity caw = (NSEntity)a;
|
|
if (caw.classname != "player")
|
|
caw.Respawn();
|
|
}
|
|
|
|
/* clear the corpses/items/bombs */
|
|
for (entity eFind = world; (eFind = find(eFind, ::classname, "remove_me"));) {
|
|
if (eFind.identity) {
|
|
NSEntity toRemove = (NSEntity)eFind;
|
|
toRemove.Destroy();
|
|
} else {
|
|
remove(eFind);
|
|
}
|
|
}
|
|
|
|
CSBot_BuyStart();
|
|
TimerBegin(autocvar_mp_freezetime, GAME_FREEZE);
|
|
Money_ResetTeamReward();
|
|
}
|
|
|
|
/*
|
|
=================
|
|
RoundOver
|
|
|
|
This happens whenever an objective is complete or time is up
|
|
=================
|
|
*/
|
|
void
|
|
CSMultiplayerRules::RoundOver(int iTeamWon, int iMoneyReward, int fSilent)
|
|
{
|
|
if (g_cs_gamestate != GAME_ACTIVE && g_cs_gamestate != GAME_FREEZE) {
|
|
return;
|
|
}
|
|
|
|
if (iTeamWon == TEAM_T) {
|
|
if (fSilent == FALSE) {
|
|
Radio_BroadcastMessage(RADIO_TERWIN);
|
|
}
|
|
g_cs_roundswon_t++;
|
|
m_iSwapTeamRoundCounter++;
|
|
} else if (iTeamWon == TEAM_CT) {
|
|
if (fSilent == FALSE) {
|
|
Radio_BroadcastMessage(RADIO_CTWIN);
|
|
}
|
|
g_cs_roundswon_ct++;
|
|
m_iSwapTeamRoundCounter++;
|
|
|
|
/* In Bomb Defusal, if Terrorists were able to plant the bomb
|
|
* but lose the round, all Terrorists receive an $800 bonus. */
|
|
if (g_cs_bombplanted) {
|
|
Money_QueTeamReward(TEAM_T, 800);
|
|
}
|
|
} else {
|
|
if (fSilent == FALSE) {
|
|
Radio_BroadcastMessage(RADIO_ROUNDDRAW);
|
|
}
|
|
}
|
|
|
|
Money_HandleRoundReward(iTeamWon);
|
|
Money_QueTeamReward(iTeamWon, iMoneyReward);
|
|
TimerBegin(5, GAME_END); // Round is over, 5 seconds til a new round starts
|
|
|
|
g_cs_hostagesrescued = 0;
|
|
g_cs_bombbeingdefused = 0;
|
|
g_cs_bombplanted = 0;
|
|
g_cs_roundsplayed++;
|
|
|
|
forceinfokey(world, "teamscore_1", sprintf("%i", g_cs_roundswon_t));
|
|
forceinfokey(world, "teamscore_2", sprintf("%i", g_cs_roundswon_ct));
|
|
}
|
|
|
|
/*
|
|
=================
|
|
TimeOut
|
|
|
|
Whenever mp_roundtime was being counted down to 0
|
|
=================
|
|
*/
|
|
void
|
|
CSMultiplayerRules::TimeOut(void)
|
|
{
|
|
if (g_cs_vipzones > 0) {
|
|
RoundOver(TEAM_T, 3250, FALSE);
|
|
} else if (g_cs_bombzones > 0) {
|
|
/* In Bomb Defusal, all Counter-Terrorists receive $3250
|
|
* if they won running down the time. */
|
|
RoundOver(TEAM_CT, 3250, FALSE);
|
|
} else if (g_cs_hostagestotal > 0) {
|
|
// TODO: Broadcast_Print: Hostages have not been rescued!
|
|
RoundOver(TEAM_T, 3250, FALSE);
|
|
} else {
|
|
RoundOver(0, 0, FALSE);
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
SwitchTeams
|
|
|
|
Happens rarely
|
|
=================
|
|
*/
|
|
void
|
|
CSMultiplayerRules::SwitchTeams(void)
|
|
{
|
|
int iCTW, iTW;
|
|
|
|
for (entity eFind = world; (eFind = find(eFind, ::classname, "player"));) {
|
|
player pl = (player)eFind;
|
|
if (pl.team == TEAM_CT) {
|
|
pl.team = TEAM_T;
|
|
pl.charmodel -= 4;
|
|
} else if (pl.team == TEAM_T) {
|
|
pl.team = TEAM_CT;
|
|
pl.charmodel += 4;
|
|
}
|
|
forceinfokey(pl, "*team", ftos(pl.team));
|
|
}
|
|
|
|
iCTW = g_cs_roundswon_ct;
|
|
iTW = g_cs_roundswon_t;
|
|
|
|
g_cs_roundswon_t = iCTW;
|
|
g_cs_roundswon_ct = iTW;
|
|
|
|
iCTW = g_cs_alive_ct;
|
|
iTW = g_cs_alive_t;
|
|
|
|
g_cs_alive_ct = iTW;
|
|
g_cs_alive_t = iCTW;
|
|
|
|
forceinfokey(world, "teamscore_1", sprintf("%i", g_cs_roundswon_t));
|
|
forceinfokey(world, "teamscore_2", sprintf("%i", g_cs_roundswon_ct));
|
|
}
|
|
|
|
void
|
|
CSMultiplayerRules::CountPlayers(void)
|
|
{
|
|
g_cs_alive_t = 0;
|
|
g_cs_alive_ct = 0;
|
|
g_cs_total_t = 0;
|
|
g_cs_total_ct = 0;
|
|
|
|
for (entity eFind = world; (eFind = find(eFind, ::classname, "player"));) {
|
|
if (eFind.team == TEAM_T) {
|
|
g_cs_total_t++;
|
|
|
|
if (eFind.health > 0)
|
|
g_cs_alive_t++;
|
|
} else if (eFind.team == TEAM_CT || eFind.team == TEAM_VIP) {
|
|
g_cs_total_ct++;
|
|
|
|
if (eFind.health > 0)
|
|
g_cs_alive_ct++;
|
|
}
|
|
}
|
|
|
|
g_total_players = g_cs_total_t + g_cs_total_ct;
|
|
}
|
|
|
|
void
|
|
CSMultiplayerRules::DeathCheck(NSClientPlayer pl)
|
|
{
|
|
if (g_cs_gamestate == GAME_END)
|
|
return;
|
|
|
|
/* hack so that we can kill rounds */
|
|
if ((g_cs_alive_t == 0) && (g_cs_alive_ct == 0)) {
|
|
g_cs_gamestate = GAME_ACTIVE;
|
|
}
|
|
|
|
switch (g_cs_gamestate) {
|
|
case GAME_INACTIVE:
|
|
case GAME_COMMENCING:
|
|
case GAME_END:
|
|
case GAME_OVER:
|
|
return;
|
|
break;
|
|
}
|
|
|
|
if ((g_cs_alive_t == 0) && (g_cs_alive_ct == 0)) {
|
|
if (g_cs_bombplanted == TRUE) {
|
|
RoundOver(TEAM_T, 3600, FALSE);
|
|
} else {
|
|
RoundOver(FALSE, 0, FALSE);
|
|
}
|
|
} else {
|
|
int winner;
|
|
if ((pl.team == TEAM_T) && (g_cs_alive_t == 0)) {
|
|
winner = TEAM_CT;
|
|
} else if ((pl.team == TEAM_CT) && (g_cs_alive_ct == 0)) {
|
|
winner = TEAM_T;
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
if (g_cs_bombzones > 0) {
|
|
/* In Bomb Defusal, the winning team receives $3250
|
|
* if they won by eliminating the enemy team. */
|
|
if (!g_cs_bombplanted || g_cs_alive_ct == 0) {
|
|
RoundOver(winner, 3250, FALSE);
|
|
}
|
|
} else {
|
|
/* In Hostage Rescue, the winning team receives $3600
|
|
* if they won by eliminating the enemy team. */
|
|
RoundOver(winner, 3600, FALSE);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
PlayerFindSpawn
|
|
|
|
Recursive function that gets the next spawnpoint
|
|
=================
|
|
*/
|
|
entity
|
|
CSMultiplayerRules::PlayerFindSpawn(float t)
|
|
{
|
|
entity point = world;
|
|
|
|
if (t == TEAM_T) {
|
|
m_eLastTSpawn = find(m_eLastTSpawn, ::classname, "info_player_deathmatch");
|
|
|
|
if (m_eLastTSpawn == world) {
|
|
m_eLastTSpawn = find(m_eLastTSpawn, ::classname, "info_player_deathmatch");
|
|
}
|
|
point = m_eLastTSpawn;
|
|
} else if (t == TEAM_CT) {
|
|
m_eLastCTSpawn = find(m_eLastCTSpawn, ::classname, "info_player_start");
|
|
|
|
if (m_eLastCTSpawn == world) {
|
|
m_eLastCTSpawn = find(m_eLastCTSpawn, ::classname, "info_player_start");
|
|
}
|
|
point = m_eLastCTSpawn;
|
|
} else if (t == TEAM_VIP) {
|
|
point = find(world, ::classname, "info_vip_start");
|
|
|
|
if (!point)
|
|
point = find(m_eLastTSpawn, ::classname, "info_player_start");
|
|
}
|
|
|
|
if (point == world) {
|
|
error("Error: No valid spawnpoints available.");
|
|
}
|
|
|
|
return point;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
PlayerRespawn
|
|
|
|
Called whenever a player survived a round and needs a basic respawn.
|
|
=================
|
|
*/
|
|
void
|
|
CSMultiplayerRules::PlayerRespawn(NSClientPlayer pp, int fTeam)
|
|
{
|
|
player pl = (player)pp;
|
|
|
|
entity eSpawn;
|
|
forceinfokey(pl, "*spec", "0");
|
|
eSpawn = PlayerFindSpawn(pl.team);
|
|
|
|
pl.classname = "player";
|
|
pl.health = pl.max_health = 100;
|
|
forceinfokey(pl, "*dead", "0");
|
|
CountPlayers();
|
|
|
|
pl.takedamage = DAMAGE_YES;
|
|
pl.solid = SOLID_SLIDEBOX;
|
|
pl.movetype = MOVETYPE_WALK;
|
|
pl.flags = FL_CLIENT;
|
|
pl.iBleeds = TRUE;
|
|
pl.viewzoom = 1.0;
|
|
pl.g_items &= ~ITEM_C4BOMB;
|
|
|
|
pl.SetOrigin(eSpawn.origin);
|
|
pl.angles = eSpawn.angles;
|
|
pl.SendFlags = UPDATE_ALL;
|
|
Client_FixAngle(pl, pl.angles);
|
|
|
|
pl.SetInfoKey("*icon1", "");
|
|
pl.SetInfoKey("*icon1_r", "1");
|
|
pl.SetInfoKey("*icon1_g", "1");
|
|
pl.SetInfoKey("*icon1_b", "1");
|
|
|
|
switch (pl.charmodel) {
|
|
case 1:
|
|
pl.model = "models/player/terror/terror.mdl";
|
|
break;
|
|
case 2:
|
|
pl.model = "models/player/leet/leet.mdl";
|
|
break;
|
|
case 3:
|
|
pl.model = "models/player/arctic/arctic.mdl";
|
|
break;
|
|
case 4:
|
|
pl.model = "models/player/guerilla/guerilla.mdl";
|
|
break;
|
|
case 5:
|
|
pl.model = "models/player/urban/urban.mdl";
|
|
break;
|
|
case 6:
|
|
pl.model = "models/player/gsg9/gsg9.mdl";
|
|
break;
|
|
case 7:
|
|
pl.model = "models/player/sas/sas.mdl";
|
|
break;
|
|
case 8:
|
|
pl.model = "models/player/gign/gign.mdl";
|
|
break;
|
|
default:
|
|
pl.model = "models/player/vip/vip.mdl";
|
|
}
|
|
|
|
pl.SetModel(pl.model);
|
|
pl.SetSize(VEC_HULL_MIN, VEC_HULL_MAX);
|
|
|
|
pl.velocity = [0,0,0];
|
|
pl.progress = 0.0f;
|
|
Weapons_SwitchBest(pl);
|
|
Ammo_AutoFill(pl);
|
|
}
|
|
|
|
void
|
|
CSMultiplayerRules::PlayerClearWeaponry(NSClientPlayer pp)
|
|
{
|
|
player pl = (player)pp;
|
|
|
|
pl.g_items = 0x0;
|
|
pl.activeweapon = 0;
|
|
pl.ammo_50ae = 0;
|
|
pl.ammo_762mm = 0;
|
|
pl.ammo_556mm = 0;
|
|
pl.ammo_556mmbox = 0;
|
|
pl.ammo_338mag = 0;
|
|
pl.ammo_9mm = 0;
|
|
pl.ammo_buckshot = 0;
|
|
pl.ammo_45acp = 0;
|
|
pl.ammo_357sig = 0;
|
|
pl.ammo_57mm = 0;
|
|
pl.ammo_hegrenade = 0;
|
|
pl.ammo_fbgrenade = 0;
|
|
pl.ammo_smokegrenade = 0;
|
|
pl.usp45_mag = 0;
|
|
pl.glock18_mag = 0;
|
|
pl.deagle_mag = 0;
|
|
pl.p228_mag = 0;
|
|
pl.elites_mag = 0;
|
|
pl.fiveseven_mag = 0;
|
|
pl.m3_mag = 0;
|
|
pl.xm1014_mag = 0;
|
|
pl.mp5_mag = 0;
|
|
pl.p90_mag = 0;
|
|
pl.ump45_mag = 0;
|
|
pl.mac10_mag = 0;
|
|
pl.tmp_mag = 0;
|
|
pl.ak47_mag = 0;
|
|
pl.sg552_mag = 0;
|
|
pl.m4a1_mag = 0;
|
|
pl.aug_mag = 0;
|
|
pl.scout_mag = 0;
|
|
pl.awp_mag = 0;
|
|
pl.g3sg1_mag = 0;
|
|
pl.sg550_mag = 0;
|
|
pl.para_mag = 0;
|
|
pl.viewzoom = 1.0f;
|
|
pl.mode_temp = 0;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
PlayerMakePlayable
|
|
|
|
Called whenever need a full-reinit of a player.
|
|
This may be after a player had died or when the game starts for the first time.
|
|
=================
|
|
*/
|
|
|
|
static void
|
|
MakePlayable(entity targ)
|
|
{
|
|
entity oself = self;
|
|
self = targ;
|
|
|
|
if (clienttype(targ) != CLIENTTYPE_REAL)
|
|
spawnfunc_csbot();
|
|
else
|
|
spawnfunc_player();
|
|
|
|
self = oself;
|
|
}
|
|
|
|
void
|
|
CSMultiplayerRules::PlayerMakePlayable(NSClientPlayer pp, int chara)
|
|
{
|
|
player pl = (player)pp;
|
|
/* spectator */
|
|
if (chara == 0) {
|
|
PlayerSpawn(pl);
|
|
return;
|
|
}
|
|
|
|
MakePlayable(pp);
|
|
pl.g_items |= ITEM_SUIT;
|
|
Weapons_AddItem(pl, WEAPON_KNIFE, -1);
|
|
|
|
/* terrorists */
|
|
if (chara < 5) {
|
|
pl.team = TEAM_T;
|
|
if (autocvar_fcs_knifeonly == FALSE) {
|
|
Weapons_AddItem(pl, WEAPON_GLOCK18, -1);
|
|
pl.ammo_9mm = 40;
|
|
}
|
|
} else {
|
|
pl.team = TEAM_CT;
|
|
|
|
if (autocvar_fcs_knifeonly == FALSE) {
|
|
Weapons_AddItem(pl, WEAPON_USP45, -1);
|
|
pl.ammo_45acp = 24;
|
|
}
|
|
}
|
|
|
|
pl.ingame = TRUE;
|
|
forceinfokey(pl, "*team", ftos(pl.team));
|
|
PlayerRespawn(pl, pl.team);
|
|
}
|
|
|
|
/*
|
|
=================
|
|
PlayerMakeSpectator
|
|
|
|
Force the player to become an observer.
|
|
=================
|
|
*/
|
|
void
|
|
CSMultiplayerRules::PlayerMakeSpectator(NSClientPlayer pp)
|
|
{
|
|
player pl = (player)pp;
|
|
pl.MakeTempSpectator();
|
|
PlayerClearWeaponry(pl);
|
|
pl.view_ofs = g_vec_null;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
PlayerReset
|
|
|
|
Called when we give the initial starting money, and also when
|
|
another player joins and causes the game rules/scores to reset fully
|
|
=================
|
|
*/
|
|
void
|
|
CSMultiplayerRules::PlayerReset(NSClientPlayer pl)
|
|
{
|
|
player p = (player)pl;
|
|
|
|
/* give the initial server-joining money */
|
|
p.money = 0;
|
|
Money_AddMoney(pl, autocvar_mp_startmoney);
|
|
p.m_buyMessage = false; /* unset the buy message. */
|
|
p.m_hostMessageT = false;
|
|
p.m_seenFriend = false;
|
|
p.m_seenEnemy = false;
|
|
p.m_seenHostage = false;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
PlayerSpawn
|
|
|
|
Called on the client first joining the server.
|
|
=================
|
|
*/
|
|
void
|
|
CSMultiplayerRules::PlayerSpawn(NSClientPlayer pl)
|
|
{
|
|
/* immediately put us into spectating mode */
|
|
PlayerMakeSpectator(pl);
|
|
Spawn_ObserverCam(pl);
|
|
|
|
PlayerReset(pl);
|
|
|
|
/* we don't belong to any team */
|
|
pl.team = TEAM_SPECTATOR;
|
|
forceinfokey(pl, "*team", "0");
|
|
}
|
|
|
|
bool
|
|
CSMultiplayerRules::ConsoleCommand(NSClientPlayer pp, string cmd)
|
|
{
|
|
tokenize(cmd);
|
|
|
|
switch (argv(0)) {
|
|
default:
|
|
return (false);
|
|
}
|
|
|
|
return (true);
|
|
}
|
|
|
|
bool
|
|
CSMultiplayerRules::IsTeamplay(void)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
void
|
|
CSMultiplayerRules::CSMultiplayerRules(void)
|
|
{
|
|
forceinfokey(world, "teams", "2");
|
|
forceinfokey(world, "team_1", "Terrorist");
|
|
forceinfokey(world, "teamscore_1", "0");
|
|
forceinfokey(world, "teamcolor_1", "1 0 0" );
|
|
forceinfokey(world, "team_2", "Counter-Terrorist");
|
|
forceinfokey(world, "teamscore_2", "0");
|
|
forceinfokey(world, "teamcolor_2", "0.25 0.25 1" );
|
|
m_iEscapedTerrorists = 0;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
CSEv_JoinTeam_f
|
|
|
|
Event Handling, called by the Client codebase via 'sendevent'
|
|
=================
|
|
*/
|
|
void
|
|
CSEv_JoinTeam_f(float flChar)
|
|
{
|
|
CSMultiplayerRules rules;
|
|
player pl;
|
|
|
|
/* matches Game_InitRules() */
|
|
if (cvar("sv_playerslots") == 1 || cvar("coop") == 1) {
|
|
return;
|
|
}
|
|
|
|
rules = (CSMultiplayerRules)g_grMode;
|
|
pl = (player)self;
|
|
|
|
if (pl.team == TEAM_VIP) {
|
|
centerprint(pl, "You are the VIP!\nYou cannot switch roles now.\n");
|
|
return;
|
|
}
|
|
|
|
// alive and are trying to switch teams, so subtract us from the Alive_Team counter.
|
|
if (pl.health > 0) {
|
|
rules.PlayerKill(pl);
|
|
}
|
|
|
|
switch (g_cs_gamestate) {
|
|
/* spawn the players immediately when its in the freeze state */
|
|
case GAME_FREEZE:
|
|
pl.charmodel = (int)flChar;
|
|
rules.PlayerMakePlayable(pl, (int)flChar);
|
|
|
|
if ((pl.team == TEAM_T) && (g_cs_alive_t == 1)) {
|
|
if (g_cs_bombzones > 0) {
|
|
rules.MakeBomber(pl);
|
|
}
|
|
} else if ((pl.team == TEAM_CT) && (g_cs_alive_ct == 1)) {
|
|
if (g_cs_vipzones > 0) {
|
|
rules.MakeVIP(pl);
|
|
}
|
|
}
|
|
|
|
break;
|
|
/* otherwise, just prepare their fields for the next round */
|
|
default:
|
|
if (flChar == 0) {
|
|
rules.PlayerSpawn(pl);
|
|
return;
|
|
}
|
|
|
|
//PlayerMakeSpectator(pl);
|
|
pl.charmodel = (int)flChar;
|
|
forceinfokey(pl, "*dead", "1");
|
|
break;
|
|
}
|
|
|
|
if (flChar < 5)
|
|
pl.team = TEAM_T;
|
|
else
|
|
pl.team = TEAM_CT;
|
|
|
|
forceinfokey(pl, "*team", ftos(pl.team));
|
|
|
|
pl.frags = 0;
|
|
pl.deaths = 0;
|
|
forceinfokey(pl, "*deaths", ftos(pl.deaths));
|
|
|
|
rules.CountPlayers();
|
|
|
|
/* if no players are present in the chosen team, force restart round */
|
|
if ((pl.team == TEAM_T) && (g_cs_alive_t == 0)) {
|
|
rules.RoundOver(FALSE, 0, FALSE);
|
|
} else if ((pl.team == TEAM_CT) && (g_cs_alive_ct == 0)) {
|
|
rules.RoundOver(FALSE, 0, FALSE);
|
|
}
|
|
}
|
|
|
|
void
|
|
CSEv_JoinAuto(void)
|
|
{
|
|
CSMultiplayerRules rules;
|
|
|
|
/* matches Game_InitRules() */
|
|
if (cvar("sv_playerslots") == 1 || cvar("coop") == 1) {
|
|
return;
|
|
}
|
|
|
|
rules = (CSMultiplayerRules)g_grMode;
|
|
rules.CountPlayers();
|
|
|
|
if (g_cs_total_ct >= g_cs_total_t) {
|
|
CSEv_JoinTeam_f(floor(random(1,5)));
|
|
} else {
|
|
CSEv_JoinTeam_f(floor(random(5,9)));
|
|
}
|
|
}
|
|
|
|
void
|
|
CSEv_JoinSpectator(void)
|
|
{
|
|
NSClientPlayer pl = (NSClientPlayer)self;
|
|
ClientKill();
|
|
pl.MakeSpectator();
|
|
}
|