/*
 * Teamplay-related code for Action (formerly Axshun).  
 * Some of this is borrowed from Zoid's CTF (thanks Zoid)
 * -Fireblade
 */

#include "g_local.h"

qboolean team_game_going = 0;   // is a team game going right now?
qboolean team_round_going = 0;  // is an actual round of a team game going right now?
int team_round_countdown = 0;   // countdown variable for start of a round
int rulecheckfrequency = 0;     // accumulator variable for checking rules every 1.5 secs
int lights_camera_action = 0;   // countdown variable for "lights...camera...action!" 
int holding_on_tie_check = 0;   // when a team "wins", countdown for a bit and wait...
int current_round_length = 0;   // frames that the current team round has lasted

int team1_score = 0;
int team2_score = 0;
int team1_total = 0;
int team2_total = 0;

#define MAX_SPAWNS 1000 // max DM spawn points supported

edict_t *potential_spawns[MAX_SPAWNS];  
int num_potential_spawns;
edict_t *teamplay_spawns[MAX_TEAMS];
trace_t trace_t_temp;  // used by our trace replace macro in ax_team.h

int num_teams = 2;              // teams in current game, fixed at 2 for now...

transparent_list_t *transparent_list = NULL;


void CreditsMenu(edict_t *ent, pmenu_t *p);


void InitTransparentList()
{
	if (transparent_list != NULL)
	{
		transparent_list_t *p, *q;

		p = transparent_list;
		while (p != NULL)
		{
			q = p->next;
			gi.TagFree(p);
			p = q;
		}

		transparent_list = NULL;
	}
}

void AddToTransparentList(edict_t *ent)
{
	transparent_list_t *p, *n;

	n = (transparent_list_t *)gi.TagMalloc(sizeof(transparent_list_t), TAG_GAME);
	if (n == NULL)
	{
		gi.dprintf("Out of memory\n");
		exit(1);
	}
	n->ent = ent;
	n->next = NULL;

	if (transparent_list == NULL)
	{
		transparent_list = n;
	}
		else
	{
		p = transparent_list;
		while (p->next != NULL)
		{
			p = p->next;
		}
		p->next = n;
	}
}

void RemoveFromTransparentList(edict_t *ent)
{
	transparent_list_t *p, *q, *r;

	if (transparent_list != NULL)
	{
		if (transparent_list->ent == ent)
		{
			q = transparent_list->next;
			gi.TagFree(transparent_list);
			transparent_list = q;
			return;
		}
			else
		{
			p = transparent_list;
			q = p->next;
			while (q != NULL)
			{
				if (q->ent == ent)
				{
					r = q->next;
					gi.TagFree(q);
					p->next = r;
					return;
				}
				p = p->next;
				q = p->next;
			}
		}
	}

	gi.dprintf("Warning: attempt to RemoveFromTransparentList when not in it\n");
}		

void TransparentListSet(solid_t solid_type)
{
	transparent_list_t *p = transparent_list;

	while (p != NULL)
	{
		p->ent->solid = solid_type;
		gi.linkentity(p->ent);
		p = p->next;
	}
}

void ReprintMOTD(edict_t *ent, pmenu_t *p)
{
        PMenu_Close(ent);
        PrintMOTD(ent);
}

void JoinTeam1(edict_t *ent, pmenu_t *p)
{
        JoinTeam(ent, TEAM1, 0);
}

void JoinTeam2(edict_t *ent, pmenu_t *p)
{
        JoinTeam(ent, TEAM2, 0);
}

void SelectWeapon2(edict_t *ent, pmenu_t *p)
{
        ent->client->resp.weapon = FindItem(MP5_NAME);
        PMenu_Close(ent);
        OpenItemMenu(ent);
}

void SelectWeapon3(edict_t *ent, pmenu_t *p)
{
        ent->client->resp.weapon = FindItem(M3_NAME);
        PMenu_Close(ent);
        OpenItemMenu(ent);
}

void SelectWeapon4(edict_t *ent, pmenu_t *p)
{
        ent->client->resp.weapon = FindItem(HC_NAME);
        PMenu_Close(ent);
        OpenItemMenu(ent);
}

void SelectWeapon5(edict_t *ent, pmenu_t *p)
{
        ent->client->resp.weapon = FindItem(SNIPER_NAME);
        PMenu_Close(ent);
        OpenItemMenu(ent);
}

void SelectWeapon6(edict_t *ent, pmenu_t *p)
{
        ent->client->resp.weapon = FindItem(M4_NAME);
        PMenu_Close(ent);
        OpenItemMenu(ent);
}

void SelectWeapon0(edict_t *ent, pmenu_t *p)
{
        ent->client->resp.weapon = FindItem(KNIFE_NAME);
        PMenu_Close(ent);
        OpenItemMenu(ent);
}

void SelectWeapon9(edict_t *ent, pmenu_t *p)
{
        ent->client->resp.weapon = FindItem(DUAL_NAME);
        PMenu_Close(ent);
        OpenItemMenu(ent);
}

void SelectItem1(edict_t *ent, pmenu_t *p)
{
        ent->client->resp.item = FindItem(KEV_NAME);
        PMenu_Close(ent);
}

void SelectItem2(edict_t *ent, pmenu_t *p)
{
        ent->client->resp.item = FindItem(LASER_NAME);
        PMenu_Close(ent);
}

void SelectItem3(edict_t *ent, pmenu_t *p)
{
        ent->client->resp.item = FindItem(SLIP_NAME);
        PMenu_Close(ent);
}

void SelectItem4(edict_t *ent, pmenu_t *p)
{
        ent->client->resp.item = FindItem(SIL_NAME);
        PMenu_Close(ent);
}

void SelectItem5(edict_t *ent, pmenu_t *p)
{
        ent->client->resp.item = FindItem(BAND_NAME);
        PMenu_Close(ent);
}

void CreditsReturnToMain(edict_t *ent, pmenu_t *p)
{
        PMenu_Close(ent);
        OpenJoinMenu(ent);
}

pmenu_t creditsmenu[] = {
  { "*Action Quake 2",                      PMENU_ALIGN_CENTER, NULL, NULL },
  { "--------------",                       PMENU_ALIGN_CENTER, NULL, NULL },
  { "*Head Design, Original Programming",   PMENU_ALIGN_CENTER, NULL, NULL },
  { "Sam 'Cail' Thompson",                PMENU_ALIGN_CENTER, NULL, NULL },
  { "*Sounds, Second-In-Command",           PMENU_ALIGN_CENTER, NULL, NULL },
  { "Patrick 'Bartender' Mills",          PMENU_ALIGN_CENTER, NULL, NULL },
  { "*Original Programming",                PMENU_ALIGN_CENTER, NULL, NULL },
  { "Nathan 'Pietro' Kovner",             PMENU_ALIGN_CENTER, NULL, NULL },
  { "Michael 'Siris' Taylor",             PMENU_ALIGN_CENTER, NULL, NULL },
  { "*Skins, Etc",                          PMENU_ALIGN_CENTER, NULL, NULL },
  { "Jon 'Vain' Delee",                   PMENU_ALIGN_CENTER, NULL, NULL },
  { "*Levels",                              PMENU_ALIGN_CENTER, NULL, NULL },
  { "Evan 'Ace12GA' Prentice",            PMENU_ALIGN_CENTER, NULL, NULL },
  { "*Models, Skins, Etc",                  PMENU_ALIGN_CENTER, NULL, NULL },
  { "Minh 'Gooseman' Le",                 PMENU_ALIGN_CENTER, NULL, NULL },
  { "Dallas 'Suislide' Frank",            PMENU_ALIGN_CENTER, NULL, NULL },
  { "*Action 1.5 ('Axshun') Programming", PMENU_ALIGN_CENTER, NULL, NULL },
  { "Carl 'Zucchini' Schedvin",                PMENU_ALIGN_CENTER, NULL, NULL },
  { "Bob 'Fireblade' Farmer",             PMENU_ALIGN_CENTER, NULL, NULL },


  { NULL,                               PMENU_ALIGN_LEFT, NULL, NULL },
  { "Return to main menu",              PMENU_ALIGN_LEFT, NULL, CreditsReturnToMain },
  { NULL,                               PMENU_ALIGN_LEFT, NULL, NULL },
  { "TAB to exit menu",    PMENU_ALIGN_LEFT, NULL, NULL },
  { NULL,                                 PMENU_ALIGN_LEFT, NULL, NULL },
  { "v" ACTION_VERSION,       PMENU_ALIGN_RIGHT, NULL, NULL },
};

pmenu_t weapmenu[] = {
  { "*Action Quake 2",                 PMENU_ALIGN_CENTER, NULL, NULL },
  { "--------------",                   PMENU_ALIGN_CENTER, NULL, NULL },
  { "Select your weapon",               PMENU_ALIGN_CENTER, NULL, NULL },
  { NULL,                               PMENU_ALIGN_LEFT, NULL, NULL },
  { "MP5/10 Submachinegun",             PMENU_ALIGN_LEFT, NULL, SelectWeapon2 },
  { "M3 Super90 Assault Shotgun",       PMENU_ALIGN_LEFT, NULL, SelectWeapon3 },
  { "Handcannon",                       PMENU_ALIGN_LEFT, NULL, SelectWeapon4 },
  { "SSG 3000 Sniper Rifle",              PMENU_ALIGN_LEFT, NULL, SelectWeapon5 },
  { "M4 Assault Rifle",                   PMENU_ALIGN_LEFT, NULL, SelectWeapon6 },
  { "Combat Knives",                      PMENU_ALIGN_LEFT, NULL, SelectWeapon0 },
  { "Akimbo Pistols",                     PMENU_ALIGN_LEFT, NULL, SelectWeapon9 },
  { NULL,                               PMENU_ALIGN_LEFT, NULL, NULL },
  { "Use [ and ] to move cursor", PMENU_ALIGN_LEFT, NULL, NULL },
  { "ENTER to select",    PMENU_ALIGN_LEFT, NULL, NULL },
  { "TAB to exit menu",    PMENU_ALIGN_LEFT, NULL, NULL },
  { NULL,                                 PMENU_ALIGN_LEFT, NULL, NULL },
  { "v" ACTION_VERSION,       PMENU_ALIGN_RIGHT, NULL, NULL },
};

pmenu_t itemmenu[] = {
  { "*Action Quake 2",                 PMENU_ALIGN_CENTER, NULL, NULL },
  { "--------------",                   PMENU_ALIGN_CENTER, NULL, NULL },
  { "Select your item",               PMENU_ALIGN_CENTER, NULL, NULL },
  { NULL,                               PMENU_ALIGN_LEFT, NULL, NULL },
  { "Kevlar Vest",             PMENU_ALIGN_LEFT, NULL, SelectItem1 },
  { "Laser Sight",       PMENU_ALIGN_LEFT, NULL, SelectItem2 },
  { "Stealth Slippers",                       PMENU_ALIGN_LEFT, NULL, SelectItem3 },
  { "Silencer",              PMENU_ALIGN_LEFT, NULL, SelectItem4 },
  { "Bandolier",                   PMENU_ALIGN_LEFT, NULL, SelectItem5 },
  { NULL,                               PMENU_ALIGN_LEFT, NULL, NULL },
  { "Use [ and ] to move cursor", PMENU_ALIGN_LEFT, NULL, NULL },
  { "ENTER to select",    PMENU_ALIGN_LEFT, NULL, NULL },
  { "TAB to exit menu",    PMENU_ALIGN_LEFT, NULL, NULL },
  { NULL,                                 PMENU_ALIGN_LEFT, NULL, NULL },
  { "v" ACTION_VERSION,       PMENU_ALIGN_RIGHT, NULL, NULL },
};

pmenu_t joinmenu[] = {
  { "*Action Quake 2",                 PMENU_ALIGN_CENTER, NULL, NULL },
  { NULL /* lvl name */,                PMENU_ALIGN_CENTER, NULL, NULL },
  { NULL,                               PMENU_ALIGN_CENTER, NULL, NULL },
  { NULL /* team 1 */,                  PMENU_ALIGN_LEFT, NULL, JoinTeam1 },
  { NULL,                               PMENU_ALIGN_LEFT, NULL, NULL },
  { NULL /* team 2 */,                  PMENU_ALIGN_LEFT, NULL, JoinTeam2 },
  { NULL,                               PMENU_ALIGN_LEFT, NULL, NULL },
  { NULL,                                 PMENU_ALIGN_LEFT, NULL, NULL },
  { "MOTD",                               PMENU_ALIGN_LEFT, NULL, ReprintMOTD },
  { "Credits",                            PMENU_ALIGN_LEFT, NULL, CreditsMenu },
  { NULL,                                 PMENU_ALIGN_LEFT, NULL, NULL },
  { "Use [ and ] to move cursor", PMENU_ALIGN_LEFT, NULL, NULL },
  { "ENTER to select",    PMENU_ALIGN_LEFT, NULL, NULL },
  { "TAB to exit menu",   PMENU_ALIGN_LEFT, NULL, NULL },
  { NULL,                                 PMENU_ALIGN_LEFT, NULL, NULL },
  { "v" ACTION_VERSION,       PMENU_ALIGN_RIGHT, NULL, NULL },
};

void CreditsMenu(edict_t *ent, pmenu_t *p)
{
        PMenu_Close(ent);
        PMenu_Open(ent, creditsmenu, 4, sizeof(creditsmenu) / sizeof(pmenu_t));
}

char *TeamName(int team)
{
        if (team == TEAM1)
                return team1_name;
                else if (team == TEAM2)
                return team2_name;
                else
                return "None";
}

void AssignSkin(edict_t *ent, char *s)
{
        int playernum = ent-g_edicts-1;

        switch (ent->client->resp.team)
        {
                case TEAM1:
                        gi.configstring (CS_PLAYERSKINS+playernum, va("%s\\%s", 
                                ent->client->pers.netname, team1_skin) );
                        break;
                case TEAM2:
                        gi.configstring (CS_PLAYERSKINS+playernum,
                                va("%s\\%s", ent->client->pers.netname, team2_skin) );
                        break;
                default:
                        gi.configstring (CS_PLAYERSKINS+playernum, 
                                va("%s\\%s", ent->client->pers.netname, s) );
                        break;
        }
}

void Team_f(edict_t *ent)
{
        char *t;
        int desired_team;

        t = gi.args();
        if (!*t)
        {
                gi.cprintf(ent, PRINT_HIGH, "You are on %s.\n",
                                TeamName(ent->client->resp.team));
                return;
        }

        if (level.framenum < (ent->client->resp.joined_team + 50))
        {
                gi.cprintf(ent, PRINT_HIGH, "You must wait 5 seconds before changing teams again.\n");
                return;
        }

        if (Q_stricmp(t, "none") == 0)
        {
                if (ent->client->resp.team == NOTEAM)
                {
                        gi.cprintf(ent, PRINT_HIGH, "You're not on a team.\n");
                }
                        else
                {
                        LeaveTeam(ent);
                }
                return;
        }

        if (Q_stricmp(t, "1") == 0)
                desired_team = TEAM1;
                else if (Q_stricmp(t, "2") == 0)
                desired_team = TEAM2;
                else if (Q_stricmp(t, team1_name) == 0)
                desired_team = TEAM1;
                else if (Q_stricmp(t, team2_name) == 0)
                desired_team = TEAM2;
                else
        {
                gi.cprintf(ent, PRINT_HIGH, "Unknown team %s.\n", t);
                return;
        }

        if (ent->client->resp.team == desired_team)
        {
                gi.cprintf(ent, PRINT_HIGH, "You are already on %s.\n",
                              TeamName(ent->client->resp.team));
                return;
        }

        JoinTeam(ent, desired_team, 1);
}

void UnevenTeamsMsg(int whichteam, int uneven_amount, char *opponent)
{
        int i;
        edict_t *e;

        for (i = 1; i <= maxclients->value; i++)
        {
                e = g_edicts + i;
                if (e->inuse)
                {
                        if (e->client->resp.team == whichteam)
                        {
                                gi.cprintf(e, PRINT_HIGH, "Your team now has %d more player%s than %s.\n", 
                                              uneven_amount, uneven_amount == 1 ? "" : "s", opponent);
                                stuffcmd(e, "play misc/comp_up.wav");
                        }
                }  
        }
}

void CheckForUnevenTeams()
{
        int i, onteam1 = 0, onteam2 = 0;
        edict_t *e;

        // only use these messages during 2-team games...
        if (num_teams > 2)
                return;

        for (i = 1; i <= maxclients->value; i++)
        {
                e = g_edicts + i;
                if (e->inuse)
                {
                        if (e->client->resp.team == TEAM1)
                                onteam1++;
                                else if (e->client->resp.team == TEAM2)
                                onteam2++;
                }  
        }
        if (onteam1 > onteam2)
                UnevenTeamsMsg(TEAM1, onteam1 - onteam2, team2_name);
                else if (onteam2 > onteam1)
                UnevenTeamsMsg(TEAM2, onteam2 - onteam1, team1_name);
}

void JoinTeam(edict_t *ent, int desired_team, int skip_menuclose)
{
        char *s, *a;
  
        if (!skip_menuclose)
        {
                PMenu_Close(ent);
        }

        if (ent->client->resp.team == desired_team)
                return;

        a = (ent->client->resp.team == NOTEAM) ? "joined" : "changed to";

        ent->client->resp.team = desired_team;
        s = Info_ValueForKey (ent->client->pers.userinfo, "skin");
        AssignSkin(ent, s);

        if (ent->solid != SOLID_NOT)  // alive, in game
        {
                ent->health = 0;
                player_die (ent, ent, ent, 100000, vec3_origin);
                ent->deadflag = DEAD_DEAD;
        }

        gi.bprintf(PRINT_HIGH, "%s %s %s.\n",
                        ent->client->pers.netname, a, TeamName(desired_team));

        ent->client->resp.joined_team = level.framenum;

        CheckForUnevenTeams();

        if (!skip_menuclose)
                OpenWeaponMenu(ent);
}

void LeaveTeam(edict_t *ent)
{
        char *g;
  
        if (ent->client->resp.team == NOTEAM)
                return;

        if (ent->solid != SOLID_NOT)  // alive, in game
        {
                ent->health = 0;
                player_die (ent, ent, ent, 100000, vec3_origin);
                ent->deadflag = DEAD_DEAD;
        }

        if (IsNeutral(ent))
                g = "its";
                else if (IsFemale(ent))
                g = "her";
                else
                g = "his";
        gi.bprintf(PRINT_HIGH, "%s left %s team.\n", ent->client->pers.netname, g);

        ent->client->resp.joined_team = 0;
        ent->client->resp.team = NOTEAM;

        CheckForUnevenTeams();
}

void ReturnToMain(edict_t *ent, pmenu_t *p)
{
        PMenu_Close(ent);
        OpenJoinMenu(ent);
}

void OpenItemMenu(edict_t *ent)
{
        PMenu_Open(ent, itemmenu, 4, sizeof(itemmenu) / sizeof(pmenu_t));
}

void OpenWeaponMenu(edict_t *ent)
{
        PMenu_Open(ent, weapmenu, 4, sizeof(weapmenu) / sizeof(pmenu_t));
}

int UpdateJoinMenu(edict_t *ent)
{
        static char levelname[32];
        static char team1players[32];
        static char team2players[32];
        int num1, num2, i;

        joinmenu[3].text = team1_name;
        joinmenu[3].SelectFunc = JoinTeam1;
        joinmenu[5].text = team2_name;
        joinmenu[5].SelectFunc = JoinTeam2;

        levelname[0] = '*';
        if (g_edicts[0].message)
                strncpy(levelname+1, g_edicts[0].message, sizeof(levelname) - 2);
                else
                strncpy(levelname+1, level.mapname, sizeof(levelname) - 2);
        levelname[sizeof(levelname) - 1] = 0;

        num1 = num2 = 0;
        for (i = 0; i < maxclients->value; i++)
        {
                if (!g_edicts[i+1].inuse)
                        continue;
                if (game.clients[i].resp.team == TEAM1)
                        num1++;
                        else if (game.clients[i].resp.team == TEAM2)
                        num2++;
        }

        sprintf(team1players, "  (%d players)", num1);
        sprintf(team2players, "  (%d players)", num2);

        joinmenu[1].text = levelname;
        if (joinmenu[3].text)
                joinmenu[4].text = team1players;
                else
                joinmenu[4].text = NULL;
        if (joinmenu[5].text)
                joinmenu[6].text = team2players;
                else
                joinmenu[6].text = NULL;

        if (num1 > num2)
                return TEAM2;
                else if (num2 > num1)
                return TEAM1;
                else 
                return TEAM1;
}

void OpenJoinMenu(edict_t *ent)
{
        int team;

        team = UpdateJoinMenu(ent);
        if (team == TEAM1)
                team = 3;
                else
                team = 5;
        PMenu_Open(ent, joinmenu, team, sizeof(joinmenu) / sizeof(pmenu_t));
}

int member_array(char *str, char *arr[], int num_elems)
{
        int l;
        for (l = 0; l < num_elems; l++)
        {
                if (!strcmp(str, arr[l]))
                        return l;
        }
        return -1;
}

void CleanLevel()
{
        char *remove_classnames[] = 
        {
                "weapon_Mk23",
                "weapon_MP5",
                "weapon_M4",
                "weapon_M3",
                "weapon_HC",
                "weapon_Sniper",
                "weapon_Dual",
                "weapon_Knife",
                "weapon_Grenade",
                "ammo_sniper",
                "ammo_clip",
                "ammo_mag",
                "ammo_m4",
                "ammo_m3",
                "item_quiet",
                "item_slippers",
                "item_band",
                "item_lasersight",
                "item_vest",
				"thrown_knife",
				"hgrenade",

        };
        int i;
        int base;
        edict_t *ent;
 
        base = 1 + maxclients->value + BODY_QUEUE_SIZE;
        ent = g_edicts + base;
        for (i = 1 + maxclients->value + BODY_QUEUE_SIZE; 
                        i < globals.num_edicts; 
                        i++, ent++)
        {
                if (!ent->classname)
                        continue;
                if (member_array(ent->classname, remove_classnames, 
                                        sizeof(remove_classnames) / sizeof(char *)) > -1)
                {
                        G_FreeEdict(ent);
                }
        }

        CleanBodies();
}

qboolean StartClient(edict_t *ent)
{
        if (ent->client->resp.team != NOTEAM)
                return false;

        // start as 'observer'
        ent->movetype = MOVETYPE_NOCLIP;
        ent->solid = SOLID_NOT;
        ent->svflags |= SVF_NOCLIENT;
        ent->client->resp.team = NOTEAM;
        ent->client->ps.gunindex = 0; 
        gi.linkentity (ent);

        return true;
}

void CenterPrintAll(char *msg)
{
        int i;
        edict_t *ent;

        gi.cprintf(NULL, PRINT_HIGH, msg); // so it goes to the server console...

        for (i = 0; i < game.maxclients; i++)
        {
                ent = &g_edicts[1+i];
                if (!ent->inuse)
                        continue;
                gi.centerprintf(ent, "%s", msg);
        }               
}

qboolean BothTeamsHavePlayers()
{
        int onteam1 = 0, onteam2 = 0, i;
        edict_t *ent;

        for (i = 0; i < game.maxclients; i++)
        {
                ent = &g_edicts[1+i];
                if (!ent->inuse)
                        continue;
                if (game.clients[i].resp.team == TEAM1)
                        onteam1++;
                        else if (game.clients[i].resp.team == TEAM2)
                        onteam2++;
        }

        return (onteam1 > 0 && onteam2 > 0);
}

// CheckForWinner: Checks for a winner (or not).
int CheckForWinner()
{
        int onteam1 = 0, onteam2 = 0, i;
        edict_t *ent;

	for (i = 0; i < game.maxclients; i++)
        {
                ent = &g_edicts[1+i];
                if (!ent->inuse)
                        continue;
                if (game.clients[i].resp.team == TEAM1 && 
                                ent->solid != SOLID_NOT)
                        onteam1++;
                        else if (game.clients[i].resp.team == TEAM2 && 
                                        ent->solid != SOLID_NOT)
                        onteam2++;
        }
  
        if (onteam1 > 0 && onteam2 > 0)
                return WINNER_NONE;
                else if (onteam1 == 0 && onteam2 == 0)
                return WINNER_TIE;
                else if (onteam1 > 0 && onteam2 == 0)
                return WINNER_TEAM1;
                else
                return WINNER_TEAM2;
}

// CheckForForcedWinner: A winner is being forced, find who it is.
int CheckForForcedWinner()
{
        int onteam1 = 0, onteam2 = 0, i;
        int health1 = 0, health2 = 0;
        edict_t *ent;

        for (i = 0; i < game.maxclients; i++)
        {
                ent = &g_edicts[1+i];
                if (!ent->inuse)
                        continue;
                if (game.clients[i].resp.team == TEAM1 && ent->solid != SOLID_NOT)
                {
                        onteam1++;
                        health1 += ent->health;
                }
                        else if (game.clients[i].resp.team == TEAM2 && ent->solid != SOLID_NOT)
                {
                        onteam2++;
                        health2 += ent->health;
                }
        }
  
        if (onteam1 > onteam2)
        {
                return WINNER_TEAM1;
        }
                else if (onteam2 > onteam1)
        {
                return WINNER_TEAM2;
        }
                else
        {
                if (health1 > health2)
                        return WINNER_TEAM1;
                        else if (health2 > health1)
                        return WINNER_TEAM2;
                        else
                        return WINNER_TIE;
        }
}

void SpawnPlayers()
{
        int i;
        edict_t *ent;

        GetSpawnPoints();
        SetupTeamSpawnPoints();
	InitTransparentList();
        for (i = 0; i < game.maxclients; i++)
        {
                ent = &g_edicts[1+i];
                if (ent->inuse && ent->client->resp.team != NOTEAM)
		{
                        PutClientInServer(ent);
			AddToTransparentList(ent);
		}
        }
}

void StartRound()
{
        team_round_going = 1;
        current_round_length = 0;
}

void StartLCA()
{
        CleanLevel();
        CenterPrintAll("LIGHTS...\n");
        gi.sound(&g_edicts[0], CHAN_VOICE | CHAN_NO_PHS_ADD, 
                gi.soundindex("atl/lights.wav"), 1.0, ATTN_NONE, 0.0);
        lights_camera_action = 41;
        SpawnPlayers();
}

// FindOverlap: Find the first (or next) overlapping player for ent.
edict_t *FindOverlap(edict_t *ent, edict_t *last_overlap)
{
        int i;
        edict_t *other;
        vec3_t diff;

        for (i = last_overlap ? last_overlap - g_edicts : 0; i < game.maxclients; i++)
        {
                other = &g_edicts[i+1];

                if (!other->inuse || other->client->resp.team == NOTEAM
                                || other == ent 
                                || other->solid == SOLID_NOT
                                || other->deadflag == DEAD_DEAD)
                        continue;

                VectorSubtract(ent->s.origin, other->s.origin, diff);

                if (diff[0] >= -33 && diff[0] <= 33 &&
                                diff[1] >= -33 && diff[1] <= 33 &&
                                diff[2] >= -65 && diff[2] <= 65)
                        return other;
        }

        return NULL;
}

void ContinueLCA()
{
        if (lights_camera_action == 21)
        {
                CenterPrintAll("CAMERA...\n");
                gi.sound(&g_edicts[0], CHAN_VOICE | CHAN_NO_PHS_ADD, 
                gi.soundindex("atl/camera.wav"), 1.0, ATTN_NONE, 0.0);
        }
                else if (lights_camera_action == 1)
        { 
		CenterPrintAll("ACTION!\n");
		gi.sound(&g_edicts[0], CHAN_VOICE | CHAN_NO_PHS_ADD, 
		gi.soundindex("atl/action.wav"), 1.0, ATTN_NONE, 0.0);
		StartRound();
        }
        lights_camera_action--;
}

void MakeAllLivePlayersObservers()
{
        edict_t *ent;
        int saveteam, i;

        for (i = 0; i < game.maxclients; i++)
        {
                ent = &g_edicts[1+i];
                if (!ent->inuse || ent->solid == SOLID_NOT)
                        continue;
                saveteam = ent->client->resp.team;
                ent->client->resp.team = NOTEAM;
                PutClientInServer(ent);
                ent->client->resp.team = saveteam;
        }
}

// WonGame: returns true if we're exiting the level.
int WonGame(int winner)
{
        gi.bprintf(PRINT_HIGH, "The round is over:\n");

        if (winner == WINNER_TIE)
        {
                gi.bprintf(PRINT_HIGH, "It was a tie, no points awarded!\n");
        }
                else        
        {
                if (winner == WINNER_TEAM1)
                {
                        gi.bprintf(PRINT_HIGH, "%s won!\n", TeamName(TEAM1));
                        team1_score++;
                }
                        else
                {
                        gi.bprintf(PRINT_HIGH, "%s won!\n", TeamName(TEAM2));
                        team2_score++;
                }
        }

        if (timelimit->value)
        {
                if (level.time >= timelimit->value*60)
                {
                        gi.bprintf(PRINT_HIGH, "Timelimit hit.\n");
                        EndDMLevel();
                        team_round_going = team_round_countdown = team_game_going = 0;
                        return 1;
                }
        }

        if (roundlimit->value)
        {
                if (team1_score >= roundlimit->value || team2_score >= roundlimit->value)
                {
                        gi.bprintf(PRINT_HIGH, "Roundlimit hit.\n");
                        EndDMLevel();
                        team_round_going = team_round_countdown = team_game_going = 0;
                        return 1;
                }
        }

        return 0;
}


void CheckTeamRules()
{
        int winner;
        int checked_tie = 0;

        if (lights_camera_action)
        {
                ContinueLCA();
                return;
        }

        if (team_round_going)
                current_round_length++;

        if (holding_on_tie_check)
        {
                holding_on_tie_check--;
                if (holding_on_tie_check > 0)
                        return;
                holding_on_tie_check = 0;
                checked_tie = 1;
        }

        if (team_round_countdown == 1)
        {
                team_round_countdown = 0;
                if (BothTeamsHavePlayers())
                {
                        team_game_going = 1;
                        StartLCA();
                }
                        else
                {
                        CenterPrintAll("Not enough players to play!\n");
                        MakeAllLivePlayersObservers();
                }
        }
                else
        {
                if (team_round_countdown)
                        team_round_countdown--;
        }

        // check these rules every 1.5 seconds...
        rulecheckfrequency++;
        if (rulecheckfrequency % 15 && !checked_tie)  
                return;

        if (!team_round_going)
        {
                if (timelimit->value)
                {
                        if (level.time >= timelimit->value*60)
                        {
                                gi.bprintf (PRINT_HIGH, "Timelimit hit.\n");
                                EndDMLevel();
                                team_round_going = team_round_countdown = team_game_going = 0;
                                return;
                        }
                }

                if (!team_round_countdown)
                {
                        if (BothTeamsHavePlayers())
                        {
                                CenterPrintAll("The round will begin in 20 seconds!\n");
				team_round_countdown = 201;
                        }
                }
        }
                else /* team_round_going */
        {
                if ((winner = CheckForWinner()) != WINNER_NONE)
                {
                        if (!checked_tie)
                        {
                                holding_on_tie_check = 50;
                                return;
                        }
                        if (WonGame(winner))
                                return;
                        team_round_going = 0;
                        lights_camera_action = 0;
                        holding_on_tie_check = 0;
                        team_round_countdown = 71;
                        return;
                }

                if (roundtimelimit->value &&
                        (current_round_length > roundtimelimit->value * 600))
                {
                        gi.bprintf(PRINT_HIGH, "Round timelimit hit.\n");
                        winner = CheckForForcedWinner();
                        if (WonGame(winner))
                                return;
                        team_round_going = 0;
                        lights_camera_action = 0;
                        holding_on_tie_check = 0;
                        team_round_countdown = 71;
                        return;
                }
        }
}


void A_Scoreboard(edict_t *ent)
{
        if (ent->client->showscores && ent->client->scoreboardnum == 1)
        {
                // blink header of the winning team during intermission
                if (level.intermissiontime && (level.framenum & 8)) { // blink 1/8th second
                        if (team1_score > team2_score)
                                ent->client->ps.stats[STAT_TEAM1_PIC] = 0;
                        else if (team2_score > team1_score)
                                ent->client->ps.stats[STAT_TEAM2_PIC] = 0;
                        else if (team1_total > team2_total) // frag tie breaker
                                ent->client->ps.stats[STAT_TEAM1_PIC] = 0;
                        else if (team2_total > team1_total) 
                                ent->client->ps.stats[STAT_TEAM2_PIC] = 0;
                        else { // tie game!
                                ent->client->ps.stats[STAT_TEAM1_PIC] = 0;
                                ent->client->ps.stats[STAT_TEAM2_PIC] = 0;
                        }
                }
                        else
                {
                        ent->client->ps.stats[STAT_TEAM1_PIC] = gi.imageindex(team1_skin_index);
                        ent->client->ps.stats[STAT_TEAM2_PIC] = gi.imageindex(team2_skin_index);
                }

                ent->client->ps.stats[STAT_TEAM1_SCORE] = team1_score;
                ent->client->ps.stats[STAT_TEAM2_SCORE] = team2_score;
        }
}

// Maximum number of lines of scores to put under each team's header.
#define MAX_SCORES_PER_TEAM 9
        
void A_ScoreboardMessage (edict_t *ent, edict_t *killer)
{
        char        string[1400];
        gclient_t   *cl;
        edict_t     *cl_ent;
        int         maxsize = 1000, i, j, k;


        if (ent->client->scoreboardnum == 1)
        {
                int team, len;
                int sorted[TEAM_TOP][MAX_CLIENTS];
                int sortedscores[TEAM_TOP][MAX_CLIENTS];
                int score, total[TEAM_TOP], totalscore[TEAM_TOP];
                int totalalive[TEAM_TOP], totalaliveprinted[TEAM_TOP];
                int stoppedat[TEAM_TOP];
                int name_pos[TEAM_TOP];

                ent->client->ps.stats[STAT_TEAM_HEADER] = gi.imageindex ("tag3");

                total[TEAM1] = total[TEAM2] = totalalive[TEAM1] = totalalive[TEAM2] = 
                        totalscore[TEAM1] = totalscore[TEAM2] = 0;

                for (i=0 ; i<game.maxclients ; i++)
                {
                        cl_ent = g_edicts + 1 + i;
                        if (!cl_ent->inuse)
                                continue;
        
                        if (game.clients[i].resp.team == NOTEAM)
                                continue;
                                else
                                team = game.clients[i].resp.team;
        
                        score = game.clients[i].resp.score;
			if (noscore->value)
			{
				j = total[team];
			}
				else
			{
	                        for (j = 0; j < total[team]; j++)
                        	{
	                                if (score > sortedscores[team][j])
                                        	break;
                        	}
                        	for (k=total[team] ; k>j ; k--)
                        	{
	                                sorted[team][k] = sorted[team][k-1];
                                	sortedscores[team][k] = sortedscores[team][k-1];
                        	}
			}
                        sorted[team][j] = i;
                        sortedscores[team][j] = score;
                        totalscore[team] += score;
                        total[team]++;
                        if (cl_ent->solid != SOLID_NOT)
                                totalalive[team]++;
                }
        
                // I've shifted the scoreboard position 8 pixels to the left in Axshun so it works
                // correctly in 320x240 (Action's does not)--any problems with this?  -FB
                // Also going to center the team names.

                name_pos[TEAM1] = ((20 - strlen(team1_name)) / 2) * 8;
                if (name_pos[TEAM1] < 0)
                        name_pos[TEAM1] = 0;
                name_pos[TEAM2] = ((20 - strlen(team2_name)) / 2) * 8;
                if (name_pos[TEAM2] < 0)
                        name_pos[TEAM2] = 0;

                sprintf(string, 
                        // TEAM1
                        "if 24 xv 0 yv 8 pic 24 endif "  
                        "if 22 xv 32 yv 8 pic 22 endif "
                        "xv 32 yv 28 string \"%4d/%-3d\" "
                        "xv 90 yv 12 num 2 26 "
                        "xv %d yv 0 string \"%s\" "
                        // TEAM2
                        "if 25 xv 160 yv 8 pic 25 endif "  
                        "if 22 xv 192 yv 8 pic 22 endif "
                        "xv 192 yv 28 string \"%4d/%-3d\" "
                        "xv 248 yv 12 num 2 27 "
                        "xv %d yv 0 string \"%s\" ",
                        totalscore[TEAM1], total[TEAM1], name_pos[TEAM1], team1_name,
                        totalscore[TEAM2], total[TEAM2], name_pos[TEAM2] + 160, team2_name);
        
                len = strlen(string);

                totalaliveprinted[TEAM1] = totalaliveprinted[TEAM2] = 0;
                stoppedat[TEAM1] = stoppedat[TEAM2] = -1;
        
                for (i=0 ; i < (MAX_SCORES_PER_TEAM + 1) ; i++)
                {
                        if (i >= total[TEAM1] && i >= total[TEAM2])
                                break; 
        
                        // ok, if we're approaching the "maxsize", then let's stop printing members of each
                        // teams (if there's more than one member left to print in that team...)
                        if (len > (maxsize - 100))
                        {
                                if (i < (total[TEAM1] - 1))
                                        stoppedat[TEAM1] = i;
                                if (i < (total[TEAM2] - 1))
                                        stoppedat[TEAM2] = i;
                        }
                        if (i == MAX_SCORES_PER_TEAM-1) 
                        {
                                if (total[TEAM1] > MAX_SCORES_PER_TEAM)
                                        stoppedat[TEAM1] = i;
                                if (total[TEAM2] > MAX_SCORES_PER_TEAM)
                                        stoppedat[TEAM2] = i;
                        }
        
                        if (i < total[TEAM1] && stoppedat[TEAM1] == -1)  // print next team 1 member...
                        {
                                cl = &game.clients[sorted[TEAM1][i]];
                                cl_ent = g_edicts + 1 + sorted[TEAM1][i];
                                if (cl_ent->solid != SOLID_NOT)
                                        totalaliveprinted[TEAM1]++;
        
                                // AQ truncates names at 12, not sure why, except maybe to conserve scoreboard 
                                // string space?  skipping that "feature".  -FB
        
                                sprintf(string+strlen(string), 
                                        "xv 0 yv %d string%s \"%s\" ",  
                                        42 + i * 8,
                                        (ent->solid == SOLID_NOT) ? (cl_ent->solid == SOLID_NOT ? "" : "2") : "",
                                        game.clients[sorted[TEAM1][i]].pers.netname);
                        }
        
                        if (i < total[TEAM2] && stoppedat[TEAM2] == -1)  // print next team 2 member...
                        {
                                cl = &game.clients[sorted[TEAM2][i]];
                                cl_ent = g_edicts + 1 + sorted[TEAM2][i];
                                if (cl_ent->solid != SOLID_NOT)
                                        totalaliveprinted[TEAM2]++;
        
                                // AQ truncates names at 12, not sure why, except maybe to conserve scoreboard 
                                // string space?  skipping that "feature".  -FB
        
                                sprintf(string+strlen(string), 
                                        "xv 160 yv %d string%s \"%s\" ",  
                                        42 + i * 8,
                                        (ent->solid == SOLID_NOT) ? (cl_ent->solid == SOLID_NOT ? "" : "2") : "",
                                        game.clients[sorted[TEAM2][i]].pers.netname);
                        }
        
                        len = strlen(string);
                }
        
                // Print remaining players if we ran out of room...
                if (ent->solid != SOLID_NOT) // live player viewing scoreboard...
                {
                        if (stoppedat[TEAM1] > -1)
                        {
                                sprintf(string + strlen(string), "xv 0 yv %d string \"..and %d more\" ",
                                        42 + (stoppedat[TEAM1] * 8), total[TEAM1] - stoppedat[TEAM1]);
                        }
                        if (stoppedat[TEAM2] > -1)
                        {
                                sprintf(string + strlen(string), "xv 160 yv %d string \"..and %d more\" ",
                                        42 + (stoppedat[TEAM2] * 8), total[TEAM2] - stoppedat[TEAM2]);
                        }
                }
                        else // dead player viewing scoreboard...
                {
                        if (stoppedat[TEAM1] > -1)
                        {
                                sprintf(string + strlen(string), "xv 0 yv %d string%s \"..and %d/%d more\" ",
                                        42 + (stoppedat[TEAM1] * 8), 
                                        (totalalive[TEAM1] - totalaliveprinted[TEAM1]) ? "2" : "",
                                        totalalive[TEAM1] - totalaliveprinted[TEAM1],
                                        total[TEAM1] - stoppedat[TEAM1]);
                        }
                        if (stoppedat[TEAM2] > -1)
                        {
                                sprintf(string + strlen(string), "xv 160 yv %d string%s \"..and %d/%d more\" ",
                                        42 + (stoppedat[TEAM1] * 8), 
                                        (totalalive[TEAM2] - totalaliveprinted[TEAM2]) ? "2" : "",
                                        totalalive[TEAM2] - totalaliveprinted[TEAM2],
                                        total[TEAM2] - stoppedat[TEAM2]);
                        }
                }
        }

                else if (ent->client->scoreboardnum == 2)

        {
                int total, score, ping;
                int sortedscores[MAX_CLIENTS], sorted[MAX_CLIENTS];

                total = score = 0;

                for (i = 0; i < game.maxclients; i++)
                {
                        cl_ent = g_edicts + 1 + i;
                        if (!cl_ent->inuse)
                                continue;
        
                        score = game.clients[i].resp.score;
			if (noscore->value)
			{
				j = total;
			}
			else
			{
	                        for (j = 0; j < total; j++)
                        	{
	                                if (score > sortedscores[j])
                                        	break;
                        	}
                        	for (k = total ; k > j ; k--)
                        	{
	                                sorted[k] = sorted[k-1];
                                	sortedscores[k] = sortedscores[k-1];
                        	}
			}
	                sorted[j] = i;
        	        sortedscores[j] = score;
                        total++;
                }

		if (noscore->value)
		{
			strcpy(string, 	"xv 0 yv 32 string2 \"Player          Time Ping\" "
					"xv 0 yv 40 string2 \"--------------- ---- ----\" ");
		}
			else
		{
			strcpy(string, 	"xv 0 yv 32 string2 \"Frags Player          Time Ping\" "
					"xv 0 yv 40 string2 \"----- --------------- ---- ----\" ");
		}

                for (i = 0; i < total; i++)
                {
			ping = game.clients[sorted[i]].ping;
			if (ping > 999)
				ping = 999;
			if (noscore->value)
			{
	                        sprintf(string + strlen(string), 
        	                        "xv 0 yv %d string \"%-15s %4d %4d\" ",
                	                48 + i * 8, 
                                	game.clients[sorted[i]].pers.netname,
                                	(level.framenum - game.clients[sorted[i]].resp.enterframe)/600,
                                	ping);                  
			}	
			else
			{
	                        sprintf(string + strlen(string), 
        	                        "xv 0 yv %d string \"%5d %-15s %4d %4d\" ",
                	                48 + i * 8, 
					sortedscores[i],
                                	game.clients[sorted[i]].pers.netname,
                                	(level.framenum - game.clients[sorted[i]].resp.enterframe)/600,
                                	ping);                  
			}

                        if (strlen(string) > (maxsize - 100) &&
                                i < (total - 2))
                        {
                                sprintf(string + strlen(string),
                                        "xv 0 yv %d string \"..and %d more\" ",
                                        48 + (i + 1) * 8,
                                        (total - i - 1));
                                break;
                        }
                }
        }


        if (strlen(string) > 1300)  // for debugging...
                gi.dprintf("Warning: scoreboard string neared or exceeded max length\nDump:\n%s\n---\n", 
                                string);

        gi.WriteByte (svc_layout);
        gi.WriteString (string);
}

// called when we enter the intermission
void TallyEndOfLevelTeamScores(void)
{
        int i;

        team1_total = team2_total = 0;
        for (i = 0; i < maxclients->value; i++)
        {
                if (!g_edicts[i+1].inuse)
                        continue;
                if (game.clients[i].resp.team == TEAM1)
                        team1_total += game.clients[i].resp.score;
                else if (game.clients[i].resp.team == TEAM2)
                        team2_total += game.clients[i].resp.score;
        }
}


/*
 * Teamplay spawning functions...
 */

edict_t *SelectTeamplaySpawnPoint(edict_t *ent)
{
        return teamplay_spawns[ent->client->resp.team - 1];
}

// SpawnPointDistance: 
// Returns the distance between two spawn points (or any entities, actually...)
float SpawnPointDistance(edict_t *spot1, edict_t *spot2)
{
        vec3_t v;
        VectorSubtract (spot1->s.origin, spot2->s.origin, v);
        return VectorLength(v);
}

// GetSpawnPoints:
// Put the spawn points into our potential_spawns array so we can work with them easily.
void GetSpawnPoints()
{
        edict_t *spot;

        spot = NULL;
        num_potential_spawns = 0;
        while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL)
        {
                potential_spawns[num_potential_spawns] = spot;
                num_potential_spawns++;
		if (num_potential_spawns >= MAX_SPAWNS)
		{
			gi.dprintf("Warning: MAX_SPAWNS exceeded\n");
			return;
		}
        }
}

// newrand returns n, where 0 >= n < top
int newrand(int top)
{
        return (int)(random() * top);
}

// compare_spawn_distances is used by the qsort() call
int compare_spawn_distances(const void *sd1, const void *sd2)
{
        if (((spawn_distances_t *)sd1)->distance < 
                ((spawn_distances_t *)sd2)->distance)
                return -1;
                else if (((spawn_distances_t *)sd1)->distance >
                        ((spawn_distances_t *)sd2)->distance)
                return 1;
                else
                return 0;
}

void SelectRandomTeamplaySpawnPoint(int team, qboolean teams_assigned[])
{
        int spawn_point;
        spawn_point = newrand(num_potential_spawns);
        teamplay_spawns[team] = potential_spawns[spawn_point];
        teams_assigned[team] = true;
}

void SelectFarTeamplaySpawnPoint(int team, qboolean teams_assigned[])
{
        int x, y, spawn_to_use, preferred_spawn_points, num_already_used,
                total_good_spawn_points;
        float closest_spawn_distance, distance;
        spawn_distances_t *spawn_distances;

        spawn_distances = (spawn_distances_t *)gi.TagMalloc(num_potential_spawns * sizeof(spawn_distances_t), TAG_GAME);

        num_already_used = 0;
        for (x = 0; x < num_potential_spawns; x++)
        {
                closest_spawn_distance = 2000000000;

                for (y = 0; y < num_teams; y++)
                {
                        if (teams_assigned[y])
                        {
                                distance = SpawnPointDistance(potential_spawns[x], teamplay_spawns[y]);
                                if (distance < closest_spawn_distance)
                                {
                                        closest_spawn_distance = distance;
                                }
                        }
                }

                if (closest_spawn_distance == 0)
                        num_already_used++;

                spawn_distances[x].s = potential_spawns[x];
                spawn_distances[x].distance = closest_spawn_distance;
        }

        qsort(spawn_distances, num_potential_spawns, 
                sizeof(spawn_distances_t), compare_spawn_distances);

        total_good_spawn_points = num_potential_spawns - num_already_used;

        if (total_good_spawn_points <= 4)
                preferred_spawn_points = 1;
                else
                if (total_good_spawn_points <= 10)
                preferred_spawn_points = 2;
                else
                preferred_spawn_points = 3;

        spawn_to_use = newrand(preferred_spawn_points);

        teams_assigned[team] = true;
        teamplay_spawns[team] = spawn_distances[num_potential_spawns - spawn_to_use - 1].s;
}

// SetupTeamSpawnPoints:
//
// Setup the points at which the teams will spawn.
//
void SetupTeamSpawnPoints()
{
        qboolean teams_assigned[MAX_TEAMS];
        int started_at, l, firstassignment;

        for (l = 0; l < num_teams; l++)
        {
                teamplay_spawns[l] = NULL;
                teams_assigned[l] = false;
        }

        started_at = l = newrand(num_teams);
        firstassignment = 1;
        do
        {
		if (l < 0 || l >= MAX_TEAMS)
		{
			gi.dprintf("Warning: attempt to setup spawns for out-of-range team (%d)\n", l);
		}

                if (firstassignment)
                {
                        SelectRandomTeamplaySpawnPoint(l, teams_assigned);
                        firstassignment = 0;
                }
                        else
                {
                        SelectFarTeamplaySpawnPoint(l, teams_assigned);
                }
                l++;
                if (l == num_teams)
                        l = 0;
        } while (l != started_at);
}
