#include "sv_clanarena.qh"
-float autocvar_g_ca_damage2score_multiplier;
-bool autocvar_g_ca_spectate_enemies;
+float autocvar_g_ca_damage2score = 100;
+bool autocvar_g_ca_prevent_stalemate;
float autocvar_g_ca_start_health = 200;
float autocvar_g_ca_start_armor = 200;
float autocvar_g_ca_start_ammo_plasma = 180;
float autocvar_g_ca_start_ammo_fuel = 0;
+.float ca_damage_counter;
+
void CA_count_alive_players()
{
total_players = 0;
for (int i = 1; i <= NUM_TEAMS; ++i)
{
Team_SetNumberOfAlivePlayers(Team_GetTeamFromIndex(i), 0);
+ Team_SetNumberOfPlayers(Team_GetTeamFromIndex(i), 0);
}
- FOREACH_CLIENT(IS_PLAYER(it) && Entity_HasValidTeam(it),
+ FOREACH_CLIENT(Entity_HasValidTeam(it),
{
++total_players;
- if (IS_DEAD(it))
+ entity team_ = Entity_GetTeam(it);
+
+ int num_total = Team_GetNumberOfPlayers(team_);
+ ++num_total;
+ Team_SetNumberOfPlayers(team_, num_total);
+
+ if (IS_DEAD(it) || !IS_PLAYER(it))
{
continue;
}
- entity team_ = Entity_GetTeam(it);
+
int num_alive = Team_GetNumberOfAlivePlayers(team_);
++num_alive;
Team_SetNumberOfAlivePlayers(team_, num_alive);
void nades_Clear(entity player);
+entity ca_LastPlayer(float tm)
+{
+ entity last_pl = NULL;
+ FOREACH_CLIENT(IS_PLAYER(it) && it.team == tm, {
+ if (!IS_DEAD(it))
+ {
+ if (!last_pl)
+ last_pl = it;
+ else
+ return NULL;
+ }
+ });
+ return last_pl;
+}
+
+
+int CA_PreventStalemate()
+{
+ //LOG_INFO("PreventStalemate running");
+ int winnerTeam = 0;
+ int secondTeam = 0;
+
+ for(int i = 1; i <= AVAILABLE_TEAMS; i++)
+ {
+ if(!winnerTeam || Team_GetNumberOfAlivePlayers(Team_GetTeamFromIndex(i)) > Team_GetNumberOfAlivePlayers(Team_GetTeam(winnerTeam)))
+ {
+ secondTeam = winnerTeam;
+ winnerTeam = Team_IndexToTeam(i);
+ }
+ else
+ {
+ if(!secondTeam || Team_GetNumberOfAlivePlayers(Team_GetTeamFromIndex(i)) > Team_GetNumberOfAlivePlayers(Team_GetTeam(secondTeam)))
+ secondTeam = Team_IndexToTeam(i);
+ }
+ }
+
+ if(Team_GetNumberOfAlivePlayers(Team_GetTeam(winnerTeam)) != Team_GetNumberOfAlivePlayers(Team_GetTeam(secondTeam)))
+ {
+ LOG_INFOF("Stalemate broken by alive players. Best team: %s%s (%d)^7 - Trailing team: %s%s (%d)",
+ Team_ColorCode(winnerTeam), Team_ColorName(winnerTeam), Team_GetNumberOfAlivePlayers(Team_GetTeam(winnerTeam)),
+ Team_ColorCode(secondTeam), Team_ColorName(secondTeam), Team_GetNumberOfAlivePlayers(Team_GetTeam(secondTeam)));
+ return winnerTeam;
+ }
+
+ // Equality. Let's check which team has more health now
+ //LOG_INFO("Equality. Checking health now.");
+ winnerTeam = 0;
+ secondTeam = 0;
+ int winnerTeamHealth = 0;
+ int secondTeamHealth = 0;
+ int teamIndex, teamHealth;
+
+ for(int i = 1; i <= AVAILABLE_TEAMS; i++)
+ {
+ teamIndex = i;
+ teamHealth = 0;
+
+ // Add up health for the players in this team
+ FOREACH_CLIENT(IS_PLAYER(it) && Entity_HasValidTeam(it) && it.team == Team_IndexToTeam(teamIndex),
+ {
+ if (IS_DEAD(it))
+ continue;
+ teamHealth += GetResource(it, RES_HEALTH) + GetResource(it, RES_ARMOR);
+ });
+
+ // Set the winner teams
+ if(!winnerTeam || teamHealth > winnerTeamHealth)
+ {
+ secondTeam = winnerTeam;
+ secondTeamHealth = winnerTeamHealth;
+ winnerTeam = Team_IndexToTeam(i);
+ winnerTeamHealth = teamHealth;
+ }
+ else
+ {
+ if(!secondTeam || teamHealth > secondTeamHealth)
+ {
+ secondTeam = Team_IndexToTeam(i);
+ secondTeamHealth = teamHealth;
+ }
+ }
+ }
+
+ if(winnerTeamHealth != secondTeamHealth)
+ {
+ LOG_INFOF("Stalemate broken by team health. Best team: %s%s (%d)^7 - Trailing team: %s%s (%d)",
+ Team_ColorCode(winnerTeam), Team_ColorName(winnerTeam), winnerTeamHealth,
+ Team_ColorCode(secondTeam), Team_ColorName(secondTeam), secondTeamHealth);
+ return winnerTeam;
+ }
+ else
+ return -2; // Equality. Can't avoid the stalemate.
+}
+
float CA_CheckWinner()
{
+ int winner_team = 0;
+
if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
{
- Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER);
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER);
- FOREACH_CLIENT(IS_PLAYER(it), { nades_Clear(it); });
-
- allowed_to_spawn = false;
- game_stopped = true;
- round_handler_Init(5, autocvar_g_ca_warmup, autocvar_g_ca_round_timelimit);
- return 1;
+ if(autocvar_g_ca_prevent_stalemate)
+ winner_team = CA_PreventStalemate();
+ else
+ winner_team = -2;
}
CA_count_alive_players();
- int winner_team = Team_GetWinnerAliveTeam();
+ if (!winner_team)
+ winner_team = Team_GetWinnerAliveTeam();
if (!winner_team)
return 0;
+ bool perfect = false;
if(winner_team > 0)
{
+ entity tm = Team_GetTeam(winner_team);
+ entity last_pl = ca_LastPlayer(winner_team);
+
+ if(last_pl && Team_GetNumberOfPlayers(tm) >= 3) {
+ Give_Medal(last_pl, DEFENSE);
+ }
+
Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN));
+ if(fragsleft > 1) Send_Notification(NOTIF_ALL, NULL, MSG_ANNCE, APP_TEAM_NUM(winner_team, ANNCE_ROUND_TEAM_WIN));
TeamScore_AddToTeam(winner_team, ST_CA_ROUNDS, +1);
+
+ if (Team_GetNumberOfPlayers(tm) >= 3 &&
+ Team_GetNumberOfAlivePlayers(tm) == Team_GetNumberOfPlayers(tm))
+ perfect = true;
}
else if(winner_team == -1)
{
Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
+ Send_Notification(NOTIF_ALL, NULL, MSG_ANNCE, ANNCE_ROUND_TIED);
+ }
+ else if(winner_team == -2)
+ {
+ Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER);
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER);
+ Send_Notification(NOTIF_ALL, NULL, MSG_ANNCE, ANNCE_ROUND_OVER);
}
allowed_to_spawn = false;
- game_stopped = true;
+ if(autocvar_g_ca_round_stop)
+ game_stopped = true;
round_handler_Init(5, autocvar_g_ca_warmup, autocvar_g_ca_round_timelimit);
- FOREACH_CLIENT(IS_PLAYER(it), { nades_Clear(it); });
+ FOREACH_CLIENT(IS_PLAYER(it), {
+ nades_Clear(it);
+
+ // Give perfect medal if everyone in the winner team is alive
+ if(perfect && it.team == winner_team) {
+ Give_Medal(it, PERFECT);
+ }
+
+ // Give accuracy medal for each weapon above 50%
+ entity ra = it.roundaccuracy;
+ for (int w = 0; w <= WEP_LAST - WEP_FIRST; ++w) {
+ if(ra.accuracy_fired[w] > 1 && (ra.accuracy_hit[w] / ra.accuracy_fired[w]) > 0.5) {
+ Give_Medal(it, ACCURACY);
+ }
+ }
+ });
return 1;
}
bool ca_isEliminated(entity e)
{
- if(e.caplayer == 1 && (IS_DEAD(e) || e.frags == FRAGS_PLAYER_OUT_OF_GAME))
+ if(INGAME_JOINED(e) && (IS_DEAD(e) || e.frags == FRAGS_PLAYER_OUT_OF_GAME))
return true;
- if(e.caplayer == 0.5)
+ if(INGAME_JOINING(e))
return true;
return false;
}
{
entity player = M_ARGV(0, entity);
- player.caplayer = 1;
+ INGAME_STATUS_SET(player, INGAME_STATUS_JOINED);
+ if (time <= game_starttime) // reset on game restart, not on round start
+ player.ca_damage_counter = 0;
if (!warmup_stage)
eliminatedPlayers.SendFlags |= 1;
}
// spectators / observers that weren't playing can join; they are
// immediately forced to observe in the PutClientInServer hook
// this way they are put in a team and can play in the next round
- if (!allowed_to_spawn && player.caplayer)
+ if (!allowed_to_spawn && INGAME(player))
return true;
return false;
}
if (!allowed_to_spawn && IS_PLAYER(player)) // this is true even when player is trying to join
{
TRANSMUTE(Observer, player);
- if (CS(player).jointime != time && !player.caplayer) // not when connecting
+ if (CS(player).jointime != time && !INGAME(player)) // not when connecting
{
- player.caplayer = 0.5;
+ INGAME_STATUS_SET(player, INGAME_STATUS_JOINING);
Send_Notification(NOTIF_ONE_ONLY, player, MSG_INFO, INFO_CA_JOIN_LATE);
}
}
+
+ if (!warmup_stage)
+ eliminatedPlayers.SendFlags |= 1;
}
MUTATOR_HOOKFUNCTION(ca, reset_map_players)
{
+ g_ca_spectate_enemies = autocvar_g_ca_spectate_enemies;
+ observe_blocked_if_eliminated = (g_ca_spectate_enemies == -1);
+ // we can avoid sending observe_blocked_if_eliminated to all clients here (with ClientData_Touch)
+ // since it will get sent whenever the client spectates someone anyway
+
FOREACH_CLIENT(true, {
CS(it).killcount = 0;
- if (!it.caplayer && IS_BOT_CLIENT(it))
- {
- it.team = -1;
- it.caplayer = 1;
- }
- if (it.caplayer)
+ if (INGAME(it) || IS_BOT_CLIENT(it))
{
TRANSMUTE(Player, it);
- it.caplayer = 1;
+ INGAME_STATUS_SET(it, INGAME_STATUS_JOINED);
PutClientInServer(it);
}
});
return true;
}
+MUTATOR_HOOKFUNCTION(ca, Scores_CountFragsRemaining)
+{
+ // announce remaining frags
+ return true;
+}
+
MUTATOR_HOOKFUNCTION(ca, reset_map_global)
{
allowed_to_spawn = true;
if (!warmup_stage && round_handler_IsActive() && round_handler_IsRoundStarted())
{
entity pl = ca_LastPlayerForTeam(this);
- if (pl)
+ if (pl) {
Send_Notification(NOTIF_ONE, pl, MSG_CENTER, CENTER_ALONE);
+ Send_Notification(NOTIF_ONE, pl, MSG_ANNCE, ANNCE_ALONE);
+ }
}
}
return true;
}
+
MUTATOR_HOOKFUNCTION(ca, ClientDisconnect)
{
entity player = M_ARGV(0, entity);
entity player = M_ARGV(0, entity);
bool is_forced = M_ARGV(1, bool);
- if (is_forced && player.caplayer)
- player.caplayer = 0;
+ if (is_forced && INGAME(player))
+ INGAME_STATUS_CLEAR(player);
if (IS_PLAYER(player) && !IS_DEAD(player))
ca_LastPlayerForTeam_Notify(player);
if (player.killindicator_teamchange == -2) // player wants to spectate
{
entcs_update_players(player);
- player.caplayer = 0;
+ INGAME_STATUS_CLEAR(player);
}
- if (player.caplayer)
+ if (INGAME(player))
+ {
player.frags = FRAGS_PLAYER_OUT_OF_GAME;
+ player.would_spectate = observe_blocked_if_eliminated; // if blocked from observing force to spectate now
+ }
if (!warmup_stage)
eliminatedPlayers.SendFlags |= 1;
- if (!player.caplayer)
+ if (!INGAME(player))
return false; // allow team reset
return true; // prevent team reset
}
float excess = max(0, frag_damage - damage_take - damage_save);
- //non-friendly fire
- if (frag_target != frag_attacker && IS_PLAYER(frag_attacker) && DIFF_TEAM(frag_target, frag_attacker))
- GameRules_scoring_add_team(frag_attacker, SCORE, (frag_damage - excess) * autocvar_g_ca_damage2score_multiplier);
-
- //friendly fire
- if (SAME_TEAM(frag_target, frag_attacker))
- GameRules_scoring_add_team(frag_attacker, SCORE, (-1 * (frag_damage - excess)) * autocvar_g_ca_damage2score_multiplier);
-
- //handle (environmental hazard) suiciding, check first if player has a registered attacker who most likely pushed them there to avoid punishing pushed players as pushers are already rewarded
- //deathtypes:
- //kill = suicide, drown = drown in water/liquid, hurttrigger = out of the map void or hurt triggers inside maps like electric sparks
- //camp = campcheck, lava = lava, slime = slime
- //team change / rebalance suicides are currently not included
- if (!IS_PLAYER(frag_attacker) && (
- frag_deathtype == DEATH_KILL.m_id ||
- frag_deathtype == DEATH_DROWN.m_id ||
- frag_deathtype == DEATH_HURTTRIGGER.m_id ||
- frag_deathtype == DEATH_CAMP.m_id ||
- frag_deathtype == DEATH_LAVA.m_id ||
- frag_deathtype == DEATH_SLIME.m_id ||
- frag_deathtype == DEATH_SWAMP.m_id))
- GameRules_scoring_add_team(frag_target, SCORE, (-1 * (frag_damage - excess)) * autocvar_g_ca_damage2score_multiplier);
+ if (autocvar_g_ca_damage2score <= 0 || frag_damage - excess == 0) return;
+
+ entity scorer = NULL;
+ float scorer_damage = 0;
+
+ if (IS_PLAYER(frag_attacker))
+ {
+ if (DIFF_TEAM(frag_target, frag_attacker))
+ scorer_damage = frag_damage - excess;
+ else // friendly fire
+ scorer_damage = -(frag_damage - excess);
+
+ scorer = frag_attacker;
+ }
+ else
+ {
+ //handle (environmental hazard) suiciding, check first if player has a registered attacker who most likely pushed them there to avoid punishing pushed players as pushers are already rewarded
+ //deathtypes:
+ //kill = suicide, drown = drown in water/liquid, hurttrigger = out of the map void or hurt triggers inside maps like electric sparks
+ //camp = campcheck, lava = lava, slime = slime
+ //team change / rebalance suicides are currently not included
+ if (frag_deathtype == DEATH_KILL.m_id ||
+ frag_deathtype == DEATH_DROWN.m_id ||
+ frag_deathtype == DEATH_HURTTRIGGER.m_id ||
+ frag_deathtype == DEATH_CAMP.m_id ||
+ frag_deathtype == DEATH_LAVA.m_id ||
+ frag_deathtype == DEATH_SLIME.m_id ||
+ frag_deathtype == DEATH_SWAMP.m_id)
+ {
+ scorer_damage = -(frag_damage - excess);
+ scorer = frag_target;
+ }
+ }
+
+ if (scorer)
+ GameRules_scoring_add_float2int(scorer, SCORE, scorer_damage, ca_damage_counter, autocvar_g_ca_damage2score);
}
MUTATOR_HOOKFUNCTION(ca, CalculateRespawnTime)
return true;
}
-MUTATOR_HOOKFUNCTION(ca, Scores_CountFragsRemaining)
-{
- // announce remaining frags
- return true;
-}
-
MUTATOR_HOOKFUNCTION(ca, SpectateSet)
{
entity client = M_ARGV(0, entity);
entity targ = M_ARGV(1, entity);
- if (!autocvar_g_ca_spectate_enemies && client.caplayer)
+ if (g_ca_spectate_enemies != 1 && INGAME(client))
if (DIFF_TEAM(targ, client))
return true;
}
{
entity client = M_ARGV(0, entity);
- if (!autocvar_g_ca_spectate_enemies && client.caplayer
+ if (g_ca_spectate_enemies != 1 && INGAME(client)
&& Team_GetNumberOfAlivePlayers(Entity_GetTeam(client)))
{
entity targ = M_ARGV(1, entity);
entity targ = M_ARGV(1, entity);
entity first = M_ARGV(2, entity);
- if (!autocvar_g_ca_spectate_enemies && client.caplayer
+ if (g_ca_spectate_enemies != 1 && INGAME(client)
&& Team_GetNumberOfAlivePlayers(Entity_GetTeam(client)))
{
do { targ = targ.chain; }
MUTATOR_HOOKFUNCTION(ca, Bot_FixCount, CBC_ORDER_EXCLUSIVE)
{
FOREACH_CLIENT(IS_REAL_CLIENT(it), {
- if (IS_PLAYER(it) || it.caplayer == 1)
+ if (IS_PLAYER(it) || INGAME_JOINED(it))
++M_ARGV(0, int);
++M_ARGV(1, int);
});
{
entity player = M_ARGV(0, entity);
- if (player.caplayer)
+ if (INGAME(player))
{
// they're going to spec, we can do other checks
if (autocvar_sv_spectate && (IS_SPEC(player) || IS_OBSERVER(player)))
return true; // doesn't work well with the whole spectator as player thing
}
-MUTATOR_HOOKFUNCTION(ca, GetPlayerStatus)
-{
- entity player = M_ARGV(0, entity);
-
- return player.caplayer == 1;
-}
-
MUTATOR_HOOKFUNCTION(ca, SetWeaponArena)
{
if (M_ARGV(0, string) == "0" || M_ARGV(0, string) == "")