From 40b7b8b8f776769118ccdf728e717352d76028e6 Mon Sep 17 00:00:00 2001
From: Mario <mario.mario@y7mail.com>
Date: Thu, 8 May 2014 06:27:09 +1000
Subject: [PATCH] Some improvements to invasion (allow team based invasion if
 map specifies, allow more monsters to spawn at the same time, precache
 monsters only if they're used)

---
 gamemodes.cfg                              |   5 +-
 qcsrc/common/mapinfo.qc                    |   1 +
 qcsrc/common/mapinfo.qh                    |   2 +-
 qcsrc/server/autocvars.qh                  |   3 +
 qcsrc/server/mutators/gamemode_invasion.qc | 188 ++++++++++++++++++---
 qcsrc/server/mutators/gamemode_invasion.qh |   5 +
 qcsrc/server/teamplay.qc                   |   6 +
 7 files changed, 184 insertions(+), 26 deletions(-)

diff --git a/gamemodes.cfg b/gamemodes.cfg
index c67f29d811..dfc240536e 100644
--- a/gamemodes.cfg
+++ b/gamemodes.cfg
@@ -475,4 +475,7 @@ set g_invasion_round_timelimit 120 "maximum time to kill all monsters"
 set g_invasion_warmup 10 "time between waves to prepare for battle"
 set g_invasion_monster_count 10 "number of monsters on first wave (increments)"
 set g_invasion_zombies_only 0 "only spawn zombies"
-set g_invasion_spawn_delay 2 "spawn more monsters after this delay"
+set g_invasion_spawn_delay 0.25
+set g_invasion_spawnpoint_spawn_delay 0.5
+set g_invasion_teams 0 "number of teams in invasion (note: use mapinfo to set this)"
+set g_invasion_team_spawns 1 "use team spawns in teamplay invasion mode"
diff --git a/qcsrc/common/mapinfo.qc b/qcsrc/common/mapinfo.qc
index 01cd2927d6..b8fe58b344 100644
--- a/qcsrc/common/mapinfo.qc
+++ b/qcsrc/common/mapinfo.qc
@@ -633,6 +633,7 @@ void _MapInfo_Map_ApplyGametypeEx(string s, float pWantedType, float pThisType)
 			cvar_set("g_freezetag_teams", v);
 			cvar_set("g_keyhunt_teams", v);
 			cvar_set("g_domination_default_teams", v);
+			cvar_set("g_invasion_teams", v);
 		}
 		else if(k == "qualifying_timelimit")
 		{
diff --git a/qcsrc/common/mapinfo.qh b/qcsrc/common/mapinfo.qh
index 97b3349f8b..3c0afec982 100644
--- a/qcsrc/common/mapinfo.qh
+++ b/qcsrc/common/mapinfo.qh
@@ -75,7 +75,7 @@ REGISTER_GAMETYPE(_("Freeze Tag"),ft,g_freezetag,FREEZETAG,"timelimit=20 pointli
 REGISTER_GAMETYPE(_("Keepaway"),ka,g_keepaway,KEEPAWAY,"timelimit=20 pointlimit=30");
 #define g_keepaway IS_GAMETYPE(KEEPAWAY)
 
-REGISTER_GAMETYPE(_("Invasion"),inv,g_invasion,INVASION,"pointlimit=50");
+REGISTER_GAMETYPE(_("Invasion"),inv,g_invasion,INVASION,"pointlimit=50 teams=0");
 #define g_invasion IS_GAMETYPE(INVASION)
 
 const float MAPINFO_FEATURE_WEAPONS       = 1; // not defined for minstagib-only maps
diff --git a/qcsrc/server/autocvars.qh b/qcsrc/server/autocvars.qh
index 53fc5f5300..ee7d24d8b9 100644
--- a/qcsrc/server/autocvars.qh
+++ b/qcsrc/server/autocvars.qh
@@ -1245,6 +1245,9 @@ float autocvar_g_touchexplode_damage;
 float autocvar_g_touchexplode_edgedamage;
 float autocvar_g_touchexplode_force;
 float autocvar_g_invasion_round_timelimit;
+float autocvar_g_invasion_teams;
+float autocvar_g_invasion_team_spawns;
+float autocvar_g_invasion_spawnpoint_spawn_delay;
 #define autocvar_g_invasion_point_limit cvar("g_invasion_point_limit")
 float autocvar_g_invasion_warmup;
 float autocvar_g_invasion_monster_count;
diff --git a/qcsrc/server/mutators/gamemode_invasion.qc b/qcsrc/server/mutators/gamemode_invasion.qc
index 7a3f0c0584..8448a215e9 100644
--- a/qcsrc/server/mutators/gamemode_invasion.qc
+++ b/qcsrc/server/mutators/gamemode_invasion.qc
@@ -1,8 +1,12 @@
-void invasion_spawnpoint()
+void spawnfunc_invasion_spawnpoint()
 {
 	if(!g_invasion) { remove(self); return; }
 
 	self.classname = "invasion_spawnpoint";
+
+	if(autocvar_g_invasion_zombies_only) // precache only if it hasn't been already
+	if(self.monsterid)
+		MON_ACTION(self.monsterid, MR_PRECACHE);
 }
 
 float invasion_PickMonster(float supermonster_count)
@@ -21,7 +25,7 @@ float invasion_PickMonster(float supermonster_count)
 		if((mon.spawnflags & MONSTER_TYPE_FLY) || (mon.spawnflags & MONSTER_TYPE_SWIM) || ((mon.spawnflags & MON_FLAG_SUPERMONSTER) && supermonster_count >= 1))
 			continue; // flying/swimming monsters not yet supported
 
-		RandomSelection_Add(world, i, "", 1, 1);
+		RandomSelection_Add(world, i, string_null, 1, 1);
 	}
 
 	return RandomSelection_chosen_float;
@@ -35,11 +39,8 @@ entity invasion_PickSpawn()
 
 	for(e = world;(e = find(e, classname, "invasion_spawnpoint")); )
 	{
-		if(time >= e.spawnshieldtime)
-		{
-			e.spawnshieldtime = time + 2;
-			RandomSelection_Add(e, 0, string_null, 1, 1);
-		}
+		RandomSelection_Add(e, 0, string_null, 1, ((time >= e.spawnshieldtime) ? 0.2 : 1)); // give recently used spawnpoints a very low rating
+		e.spawnshieldtime = time + autocvar_g_invasion_spawnpoint_spawn_delay;
 	}
 
 	return RandomSelection_chosen_ent;
@@ -53,14 +54,52 @@ void invasion_SpawnChosenMonster(float mon)
 
 	if(spawn_point == world)
 	{
-		dprint("Warning: couldn't find any invasion_spawnpoint spawnpoints, no monsters will spawn!\n");
-		return;
-	}
+		dprint("Warning: couldn't find any invasion_spawnpoint spawnpoints, attempting to spawn monsters in random locations\n");
+		entity e = spawn();
+		setsize(e, (get_monsterinfo(mon)).mins, (get_monsterinfo(mon)).maxs);
 
-	monster = spawnmonster("", ((spawn_point.monsterid) ? spawn_point.monsterid : mon), spawn_point, spawn_point, spawn_point.origin, FALSE, FALSE, 2);
+		if(MoveToRandomMapLocation(e, DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_PLAYERCLIP, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_BODY | DPCONTENTS_DONOTENTER, Q3SURFACEFLAG_SKY, 10, 1024, 256))
+			monster = spawnmonster("", mon, world, world, e.origin, FALSE, FALSE, 2);
+		else return;
+
+		e.think = SUB_Remove;
+		e.nextthink = time + 0.1;
+	}
+	else
+		monster = spawnmonster("", ((spawn_point.monsterid) ? spawn_point.monsterid : mon), spawn_point, spawn_point, spawn_point.origin, FALSE, FALSE, 2);
 	
-	monster.target2 = spawn_point.target2;
+	if(spawn_point) monster.target2 = spawn_point.target2;
 	monster.spawnshieldtime = time;
+	if(spawn_point && spawn_point.target_range) monster.target_range = spawn_point.target_range;
+	
+	if(teamplay)
+	if(spawn_point && spawn_point.team && inv_monsters_perteam[spawn_point.team] > 0)
+		monster.team = spawn_point.team;
+	else
+	{
+		RandomSelection_Init();
+		if(inv_monsters_perteam[NUM_TEAM_1] > 0) RandomSelection_Add(world, NUM_TEAM_1, string_null, 1, 1);
+		if(inv_monsters_perteam[NUM_TEAM_2] > 0) RandomSelection_Add(world, NUM_TEAM_2, string_null, 1, 1);
+		if(invasion_teams >= 3) if(inv_monsters_perteam[NUM_TEAM_3] > 0) { RandomSelection_Add(world, NUM_TEAM_3, string_null, 1, 1); }
+		if(invasion_teams >= 4) if(inv_monsters_perteam[NUM_TEAM_4] > 0) { RandomSelection_Add(world, NUM_TEAM_4, string_null, 1, 1); }
+		
+		monster.team = RandomSelection_chosen_float;
+	}
+	
+	if(teamplay)
+	{
+		monster_setupcolors(monster);
+	
+		if(monster.sprite)
+		{
+			WaypointSprite_UpdateTeamRadar(monster.sprite, RADARICON_DANGER, ((monster.team) ? Team_ColorRGB(monster.team) : '1 0 0'));
+
+			monster.sprite.team = 0;
+			monster.sprite.SendFlags |= 1;
+		}
+	}
+	
+	monster.monster_attack = FALSE; // it's the player's job to kill all the monsters
 
 	if(inv_roundcnt >= inv_maxrounds)
 		monster.spawnflags |= MONSTERFLAG_MINIBOSS; // last round spawns minibosses
@@ -87,13 +126,22 @@ float Invasion_CheckWinner()
 		return 1;
 	}
 
-	float total_alive_monsters = 0, supermonster_count = 0;
+	float total_alive_monsters = 0, supermonster_count = 0, red_alive = 0, blue_alive = 0, yellow_alive = 0, pink_alive = 0;;
 
 	FOR_EACH_MONSTER(head) if(head.health > 0)
 	{
 		if((get_monsterinfo(head.monsterid)).spawnflags & MON_FLAG_SUPERMONSTER)
 			++supermonster_count;
 		++total_alive_monsters;
+
+		if(teamplay)
+		switch(head.team)
+		{
+			case NUM_TEAM_1: ++red_alive; break;
+			case NUM_TEAM_2: ++blue_alive; break;
+			case NUM_TEAM_3: ++yellow_alive; break;
+			case NUM_TEAM_4: ++pink_alive; break;
+		}
 	}
 
 	if((total_alive_monsters + inv_numkilled) < inv_maxspawned && inv_maxcurrent < inv_maxspawned)
@@ -107,12 +155,35 @@ float Invasion_CheckWinner()
 		return 0;
 	}
 
-	if(inv_numspawned < 1 || inv_numkilled < inv_maxspawned)
-		return 0; // nothing has spawned yet, or there are still alive monsters
+	if(inv_numspawned < 1)
+		return 0; // nothing has spawned yet
+		
+	if(teamplay)
+	{
+		if(((red_alive > 0) + (blue_alive > 0) + (yellow_alive > 0) + (pink_alive > 0)) > 1)
+			return 0;
+	}
+	else if(inv_numkilled < inv_maxspawned)
+		return 0;
 
 	entity winner = world;
-	float winning_score = 0;
+	float winning_score = 0, winner_team = 0;
+
 
+	if(teamplay)
+	{
+		if(red_alive > 0) { winner_team = NUM_TEAM_1; }
+		if(blue_alive > 0)
+		if(winner_team) { winner_team = 0; }
+		else { winner_team = NUM_TEAM_2; }
+		if(yellow_alive > 0)
+		if(winner_team) { winner_team = 0; }
+		else { winner_team = NUM_TEAM_3; }
+		if(pink_alive > 0)
+		if(winner_team) { winner_team = 0; }
+		else { winner_team = NUM_TEAM_4; }
+	}	
+	else
 	FOR_EACH_PLAYER(head)
 	{
 		float cs = PlayerScore_Add(head, SP_KILLS, 0);
@@ -126,7 +197,15 @@ float Invasion_CheckWinner()
 	FOR_EACH_MONSTER(head)
 		monster_remove(head);
 
-	if(winner)
+	if(teamplay)
+	{
+		if(winner_team)
+		{
+			Send_Notification(NOTIF_ALL, world, MSG_CENTER, APP_TEAM_NUM_4(winner_team, CENTER_ROUND_TEAM_WIN_));
+			Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(winner_team, INFO_ROUND_TEAM_WIN_));
+		}
+	}
+	else if(winner)
 	{
 		Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_PLAYER_WIN, winner.netname);
 		Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_PLAYER_WIN, winner.netname);
@@ -162,6 +241,15 @@ void Invasion_RoundStart()
 	inv_numkilled = 0;
 
 	inv_maxspawned = rint(max(autocvar_g_invasion_monster_count, autocvar_g_invasion_monster_count * (inv_roundcnt * 0.5)));
+
+	if(teamplay)
+	{
+		DistributeEvenly_Init(inv_maxspawned, invasion_teams);
+		inv_monsters_perteam[NUM_TEAM_1] = DistributeEvenly_Get(1);
+		inv_monsters_perteam[NUM_TEAM_2] = DistributeEvenly_Get(1);
+		if(invasion_teams >= 3) inv_monsters_perteam[NUM_TEAM_3] = DistributeEvenly_Get(1);
+		if(invasion_teams >= 4) inv_monsters_perteam[NUM_TEAM_4] = DistributeEvenly_Get(1);
+	}
 }
 
 MUTATOR_HOOKFUNCTION(invasion_MonsterDies)
@@ -170,9 +258,17 @@ MUTATOR_HOOKFUNCTION(invasion_MonsterDies)
 	{
 		inv_numkilled += 1;
 		inv_maxcurrent -= 1;
+		if(teamplay) { inv_monsters_perteam[self.team] -= 1; }
 
 		if(IS_PLAYER(frag_attacker))
+		if(SAME_TEAM(frag_attacker, self)) // in non-teamplay modes, same team = same player, so this works
+			PlayerScore_Add(frag_attacker, SP_KILLS, -1);
+		else
+		{
 			PlayerScore_Add(frag_attacker, SP_KILLS, +1);
+			if(teamplay)
+				TeamScore_AddToTeam(frag_attacker.team, ST_INV_KILLS, +1);
+		}
 	}
 
 	return FALSE;
@@ -208,7 +304,7 @@ MUTATOR_HOOKFUNCTION(invasion_OnEntityPreSpawn)
 	return FALSE;
 }
 
-MUTATOR_HOOKFUNCTION(invasion_PlayerThink)
+MUTATOR_HOOKFUNCTION(invasion_StartFrame)
 {
 	monsters_total = inv_maxspawned; // TODO: make sure numspawned never exceeds maxspawned
 	monsters_killed = inv_numkilled;
@@ -260,6 +356,14 @@ MUTATOR_HOOKFUNCTION(invasion_PlayerCommand)
 	return FALSE;
 }
 
+MUTATOR_HOOKFUNCTION(invasion_BotShouldAttack)
+{
+	if(!(checkentity.flags & FL_MONSTER))
+		return TRUE;
+	
+	return FALSE;
+}
+
 MUTATOR_HOOKFUNCTION(invasion_SetStartItems)
 {
 	start_health = 200;
@@ -281,18 +385,31 @@ MUTATOR_HOOKFUNCTION(invasion_AllowMobSpawning)
 	return TRUE;
 }
 
-void invasion_ScoreRules()
+MUTATOR_HOOKFUNCTION(invasion_GetTeamCount)
+{
+	ret_float = invasion_teams;
+	return FALSE;
+}
+
+void invasion_ScoreRules(float inv_teams)
 {
-	ScoreRules_basics(0, 0, 0, FALSE);
-	ScoreInfo_SetLabel_PlayerScore(SP_KILLS, "frags", SFL_SORT_PRIO_PRIMARY);
+	if(inv_teams) { CheckAllowedTeams(world); }
+	ScoreRules_basics(inv_teams, 0, 0, FALSE);
+	if(inv_teams) ScoreInfo_SetLabel_TeamScore(ST_INV_KILLS, "frags", SFL_SORT_PRIO_PRIMARY);
+	ScoreInfo_SetLabel_PlayerScore(SP_KILLS, "frags", ((inv_teams) ? SFL_SORT_PRIO_SECONDARY : SFL_SORT_PRIO_PRIMARY));
 	ScoreRules_basics_end();
 }
 
-void invasion_Initialize()
+void invasion_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
 {
+	if(autocvar_g_invasion_teams)
+		invasion_teams = bound(2, autocvar_g_invasion_teams, 4);
+	else
+		invasion_teams = 0;
+	
 	independent_players = 1; // to disable extra useless scores
 
-	invasion_ScoreRules();
+	invasion_ScoreRules(invasion_teams);
 
 	independent_players = 0;
 
@@ -305,19 +422,42 @@ void invasion_Initialize()
 	inv_maxrounds = 15; // 15?
 }
 
+void invasion_Initialize()
+{
+	if(autocvar_g_invasion_zombies_only)
+		MON_ACTION(MON_ZOMBIE, MR_PRECACHE);
+	else
+	{
+		float i;
+		entity mon;
+		for(i = MON_FIRST; i <= MON_LAST; ++i)
+		{
+			mon = get_monsterinfo(i);
+			if((mon.spawnflags & MONSTER_TYPE_FLY) || (mon.spawnflags & MONSTER_TYPE_SWIM))
+				continue; // flying/swimming monsters not yet supported
+
+			MON_ACTION(i, MR_PRECACHE);
+		}
+	}
+	
+	InitializeEntity(world, invasion_DelayedInit, INITPRIO_GAMETYPE);
+}
+
 MUTATOR_DEFINITION(gamemode_invasion)
 {
 	MUTATOR_HOOK(MonsterDies, invasion_MonsterDies, CBC_ORDER_ANY);
 	MUTATOR_HOOK(MonsterSpawn, invasion_MonsterSpawn, CBC_ORDER_ANY);
 	MUTATOR_HOOK(OnEntityPreSpawn, invasion_OnEntityPreSpawn, CBC_ORDER_ANY);
-	MUTATOR_HOOK(PlayerPreThink, invasion_PlayerThink, CBC_ORDER_ANY);
+	MUTATOR_HOOK(SV_StartFrame, invasion_StartFrame, CBC_ORDER_ANY);
 	MUTATOR_HOOK(PlayerRegen, invasion_PlayerRegen, CBC_ORDER_ANY);
 	MUTATOR_HOOK(PlayerSpawn, invasion_PlayerSpawn, CBC_ORDER_ANY);
 	MUTATOR_HOOK(PlayerDamage_Calculate, invasion_PlayerDamage, CBC_ORDER_ANY);
 	MUTATOR_HOOK(SV_ParseClientCommand, invasion_PlayerCommand, CBC_ORDER_ANY);
+	MUTATOR_HOOK(BotShouldAttack, invasion_BotShouldAttack, CBC_ORDER_ANY);
 	MUTATOR_HOOK(SetStartItems, invasion_SetStartItems, CBC_ORDER_ANY);
 	MUTATOR_HOOK(AccuracyTargetValid, invasion_AccuracyTargetValid, CBC_ORDER_ANY);
 	MUTATOR_HOOK(AllowMobSpawning, invasion_AllowMobSpawning, CBC_ORDER_ANY);
+	MUTATOR_HOOK(GetTeamCount, invasion_GetTeamCount, CBC_ORDER_ANY);
 
 	MUTATOR_ONADD
 	{
diff --git a/qcsrc/server/mutators/gamemode_invasion.qh b/qcsrc/server/mutators/gamemode_invasion.qh
index 3438f47241..04492d4a34 100644
--- a/qcsrc/server/mutators/gamemode_invasion.qh
+++ b/qcsrc/server/mutators/gamemode_invasion.qh
@@ -6,4 +6,9 @@ float inv_numkilled;
 float inv_lastcheck;
 float inv_maxcurrent;
 
+float invasion_teams;
+float inv_monsters_perteam[17];
+
 float inv_monsterskill;
+
+#define ST_INV_KILLS 1
diff --git a/qcsrc/server/teamplay.qc b/qcsrc/server/teamplay.qc
index 932dcc12ac..a0c80dbd21 100644
--- a/qcsrc/server/teamplay.qc
+++ b/qcsrc/server/teamplay.qc
@@ -198,6 +198,12 @@ void InitGameplayMode()
 	if(g_invasion)
 	{
 		fraglimit_override = autocvar_g_invasion_point_limit;
+		if(autocvar_g_invasion_teams >= 2)
+		{
+			ActivateTeamplay();
+			if(autocvar_g_invasion_team_spawns)
+				have_team_spawns = -1; // request team spawns
+		}
 		MUTATOR_ADD(gamemode_invasion);
 	}
 
-- 
2.39.5