1 #include "gamemode_invasion.qh"
6 #include "../../common/monsters/spawn.qh"
7 #include "../../common/monsters/sv_monsters.qh"
9 void spawnfunc_invasion_spawnpoint()
11 if(!g_invasion) { remove(self); return; }
13 self.classname = "invasion_spawnpoint";
15 if(autocvar_g_invasion_zombies_only) // precache only if it hasn't been already
17 MON_ACTION(self.monsterid, MR_PRECACHE);
20 float invasion_PickMonster(float supermonster_count)
22 if(autocvar_g_invasion_zombies_only)
28 RandomSelection_Init();
30 for(i = MON_FIRST; i <= MON_LAST; ++i)
32 mon = get_monsterinfo(i);
33 if((mon.spawnflags & MONSTER_TYPE_FLY) || (mon.spawnflags & MONSTER_TYPE_SWIM) || ((mon.spawnflags & MON_FLAG_SUPERMONSTER) && supermonster_count >= 1))
34 continue; // flying/swimming monsters not yet supported
36 RandomSelection_Add(world, i, string_null, 1, 1);
39 return RandomSelection_chosen_float;
42 entity invasion_PickSpawn()
46 RandomSelection_Init();
48 for(e = world;(e = find(e, classname, "invasion_spawnpoint")); )
50 RandomSelection_Add(e, 0, string_null, 1, ((time >= e.spawnshieldtime) ? 0.2 : 1)); // give recently used spawnpoints a very low rating
51 e.spawnshieldtime = time + autocvar_g_invasion_spawnpoint_spawn_delay;
54 return RandomSelection_chosen_ent;
57 void invasion_SpawnChosenMonster(float mon)
59 entity spawn_point, monster;
61 spawn_point = invasion_PickSpawn();
63 if(spawn_point == world)
65 dprint("Warning: couldn't find any invasion_spawnpoint spawnpoints, attempting to spawn monsters in random locations\n");
67 setsize(e, (get_monsterinfo(mon)).mins, (get_monsterinfo(mon)).maxs);
69 if(MoveToRandomMapLocation(e, DPCONTENTS_SOLID | DPCONTENTS_CORPSE | DPCONTENTS_PLAYERCLIP, DPCONTENTS_SLIME | DPCONTENTS_LAVA | DPCONTENTS_SKY | DPCONTENTS_BODY | DPCONTENTS_DONOTENTER, Q3SURFACEFLAG_SKY, 10, 1024, 256))
70 monster = spawnmonster("", mon, world, world, e.origin, false, false, 2);
74 e.nextthink = time + 0.1;
77 monster = spawnmonster("", ((spawn_point.monsterid) ? spawn_point.monsterid : mon), spawn_point, spawn_point, spawn_point.origin, false, false, 2);
79 if(spawn_point) monster.target2 = spawn_point.target2;
80 monster.spawnshieldtime = time;
81 if(spawn_point && spawn_point.target_range) monster.target_range = spawn_point.target_range;
84 if(spawn_point && spawn_point.team && inv_monsters_perteam[spawn_point.team] > 0)
85 monster.team = spawn_point.team;
88 RandomSelection_Init();
89 if(inv_monsters_perteam[NUM_TEAM_1] > 0) RandomSelection_Add(world, NUM_TEAM_1, string_null, 1, 1);
90 if(inv_monsters_perteam[NUM_TEAM_2] > 0) RandomSelection_Add(world, NUM_TEAM_2, string_null, 1, 1);
91 if(invasion_teams >= 3) if(inv_monsters_perteam[NUM_TEAM_3] > 0) { RandomSelection_Add(world, NUM_TEAM_3, string_null, 1, 1); }
92 if(invasion_teams >= 4) if(inv_monsters_perteam[NUM_TEAM_4] > 0) { RandomSelection_Add(world, NUM_TEAM_4, string_null, 1, 1); }
94 monster.team = RandomSelection_chosen_float;
99 monster_setupcolors(monster);
103 WaypointSprite_UpdateTeamRadar(monster.sprite, RADARICON_DANGER, ((monster.team) ? Team_ColorRGB(monster.team) : '1 0 0'));
105 monster.sprite.team = 0;
106 monster.sprite.SendFlags |= 1;
110 monster.monster_attack = false; // it's the player's job to kill all the monsters
112 if(inv_roundcnt >= inv_maxrounds)
113 monster.spawnflags |= MONSTERFLAG_MINIBOSS; // last round spawns minibosses
116 void invasion_SpawnMonsters(float supermonster_count)
118 float chosen_monster = invasion_PickMonster(supermonster_count);
120 invasion_SpawnChosenMonster(chosen_monster);
123 float Invasion_CheckWinner()
126 if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
128 FOR_EACH_MONSTER(head)
129 monster_remove(head);
131 Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_OVER);
132 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_OVER);
133 round_handler_Init(5, autocvar_g_invasion_warmup, autocvar_g_invasion_round_timelimit);
137 float total_alive_monsters = 0, supermonster_count = 0, red_alive = 0, blue_alive = 0, yellow_alive = 0, pink_alive = 0;
139 FOR_EACH_MONSTER(head) if(head.health > 0)
141 if((get_monsterinfo(head.monsterid)).spawnflags & MON_FLAG_SUPERMONSTER)
142 ++supermonster_count;
143 ++total_alive_monsters;
148 case NUM_TEAM_1: ++red_alive; break;
149 case NUM_TEAM_2: ++blue_alive; break;
150 case NUM_TEAM_3: ++yellow_alive; break;
151 case NUM_TEAM_4: ++pink_alive; break;
155 if((total_alive_monsters + inv_numkilled) < inv_maxspawned && inv_maxcurrent < inv_maxspawned)
157 if(time >= inv_lastcheck)
159 invasion_SpawnMonsters(supermonster_count);
160 inv_lastcheck = time + autocvar_g_invasion_spawn_delay;
166 if(inv_numspawned < 1)
167 return 0; // nothing has spawned yet
171 if(((red_alive > 0) + (blue_alive > 0) + (yellow_alive > 0) + (pink_alive > 0)) > 1)
174 else if(inv_numkilled < inv_maxspawned)
177 entity winner = world;
178 float winning_score = 0, winner_team = 0;
183 if(red_alive > 0) { winner_team = NUM_TEAM_1; }
185 if(winner_team) { winner_team = 0; }
186 else { winner_team = NUM_TEAM_2; }
188 if(winner_team) { winner_team = 0; }
189 else { winner_team = NUM_TEAM_3; }
191 if(winner_team) { winner_team = 0; }
192 else { winner_team = NUM_TEAM_4; }
195 FOR_EACH_PLAYER(head)
197 float cs = PlayerScore_Add(head, SP_KILLS, 0);
198 if(cs > winning_score)
205 FOR_EACH_MONSTER(head)
206 monster_remove(head);
212 Send_Notification(NOTIF_ALL, world, MSG_CENTER, APP_TEAM_NUM_4(winner_team, CENTER_ROUND_TEAM_WIN_));
213 Send_Notification(NOTIF_ALL, world, MSG_INFO, APP_TEAM_NUM_4(winner_team, INFO_ROUND_TEAM_WIN_));
218 Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_PLAYER_WIN, winner.netname);
219 Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_PLAYER_WIN, winner.netname);
222 round_handler_Init(5, autocvar_g_invasion_warmup, autocvar_g_invasion_round_timelimit);
227 float Invasion_CheckPlayers()
232 void Invasion_RoundStart()
235 float numplayers = 0;
238 e.player_blocked = 0;
242 if(inv_roundcnt < inv_maxrounds)
243 inv_roundcnt += 1; // a limiter to stop crazy counts
245 inv_monsterskill = inv_roundcnt + max(1, numplayers * 0.3);
251 inv_maxspawned = rint(max(autocvar_g_invasion_monster_count, autocvar_g_invasion_monster_count * (inv_roundcnt * 0.5)));
255 DistributeEvenly_Init(inv_maxspawned, invasion_teams);
256 inv_monsters_perteam[NUM_TEAM_1] = DistributeEvenly_Get(1);
257 inv_monsters_perteam[NUM_TEAM_2] = DistributeEvenly_Get(1);
258 if(invasion_teams >= 3) inv_monsters_perteam[NUM_TEAM_3] = DistributeEvenly_Get(1);
259 if(invasion_teams >= 4) inv_monsters_perteam[NUM_TEAM_4] = DistributeEvenly_Get(1);
263 MUTATOR_HOOKFUNCTION(invasion_MonsterDies)
265 if(!(self.spawnflags & MONSTERFLAG_RESPAWNED))
269 if(teamplay) { inv_monsters_perteam[self.team] -= 1; }
271 if(IS_PLAYER(frag_attacker))
272 if(SAME_TEAM(frag_attacker, self)) // in non-teamplay modes, same team = same player, so this works
273 PlayerScore_Add(frag_attacker, SP_KILLS, -1);
276 PlayerScore_Add(frag_attacker, SP_KILLS, +1);
278 TeamScore_AddToTeam(frag_attacker.team, ST_INV_KILLS, +1);
285 MUTATOR_HOOKFUNCTION(invasion_MonsterSpawn)
287 if(!(self.spawnflags & MONSTERFLAG_SPAWNED))
290 if(!(self.spawnflags & MONSTERFLAG_RESPAWNED))
296 self.monster_skill = inv_monsterskill;
298 if((get_monsterinfo(self.monsterid)).spawnflags & MON_FLAG_SUPERMONSTER)
299 Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_INVASION_SUPERMONSTER, M_NAME(self.monsterid));
301 self.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_BOTCLIP | DPCONTENTS_MONSTERCLIP;
306 MUTATOR_HOOKFUNCTION(invasion_OnEntityPreSpawn)
308 if(startsWith(self.classname, "monster_"))
309 if(!(self.spawnflags & MONSTERFLAG_SPAWNED))
315 MUTATOR_HOOKFUNCTION(invasion_StartFrame)
317 monsters_total = inv_maxspawned; // TODO: make sure numspawned never exceeds maxspawned
318 monsters_killed = inv_numkilled;
323 MUTATOR_HOOKFUNCTION(invasion_PlayerRegen)
325 // no regeneration in invasion
329 MUTATOR_HOOKFUNCTION(invasion_PlayerSpawn)
331 self.bot_attack = false;
335 MUTATOR_HOOKFUNCTION(invasion_PlayerDamage)
337 if(IS_PLAYER(frag_attacker) && IS_PLAYER(frag_target) && frag_attacker != frag_target)
340 frag_force = '0 0 0';
346 MUTATOR_HOOKFUNCTION(invasion_PlayerCommand)
348 if(MUTATOR_RETURNVALUE) // command was already handled?
351 if(cmd_name == "debuginvasion")
353 sprint(self, strcat("inv_maxspawned = ", ftos(inv_maxspawned), "\n"));
354 sprint(self, strcat("inv_numspawned = ", ftos(inv_numspawned), "\n"));
355 sprint(self, strcat("inv_numkilled = ", ftos(inv_numkilled), "\n"));
356 sprint(self, strcat("inv_roundcnt = ", ftos(inv_roundcnt), "\n"));
357 sprint(self, strcat("monsters_total = ", ftos(monsters_total), "\n"));
358 sprint(self, strcat("monsters_killed = ", ftos(monsters_killed), "\n"));
359 sprint(self, strcat("inv_monsterskill = ", ftos(inv_monsterskill), "\n"));
367 MUTATOR_HOOKFUNCTION(invasion_BotShouldAttack)
369 if(!(checkentity.flags & FL_MONSTER))
375 MUTATOR_HOOKFUNCTION(invasion_SetStartItems)
378 start_armorvalue = 200;
383 MUTATOR_HOOKFUNCTION(invasion_AccuracyTargetValid)
385 if(frag_target.flags & FL_MONSTER)
386 return MUT_ACCADD_INVALID;
387 return MUT_ACCADD_INDIFFERENT;
390 MUTATOR_HOOKFUNCTION(invasion_AllowMobSpawning)
392 // monster spawning disabled during an invasion
396 MUTATOR_HOOKFUNCTION(invasion_GetTeamCount)
398 ret_float = invasion_teams;
402 void invasion_ScoreRules(float inv_teams)
404 if(inv_teams) { CheckAllowedTeams(world); }
405 ScoreRules_basics(inv_teams, 0, 0, false);
406 if(inv_teams) ScoreInfo_SetLabel_TeamScore(ST_INV_KILLS, "frags", SFL_SORT_PRIO_PRIMARY);
407 ScoreInfo_SetLabel_PlayerScore(SP_KILLS, "frags", ((inv_teams) ? SFL_SORT_PRIO_SECONDARY : SFL_SORT_PRIO_PRIMARY));
408 ScoreRules_basics_end();
411 void invasion_DelayedInit() // Do this check with a delay so we can wait for teams to be set up.
413 if(autocvar_g_invasion_teams)
414 invasion_teams = bound(2, autocvar_g_invasion_teams, 4);
418 independent_players = 1; // to disable extra useless scores
420 invasion_ScoreRules(invasion_teams);
422 independent_players = 0;
424 round_handler_Spawn(Invasion_CheckPlayers, Invasion_CheckWinner, Invasion_RoundStart);
425 round_handler_Init(5, autocvar_g_invasion_warmup, autocvar_g_invasion_round_timelimit);
428 inv_maxrounds = 15; // 15?
431 void invasion_Initialize()
433 if(autocvar_g_invasion_zombies_only)
434 MON_ACTION(MON_ZOMBIE, MR_PRECACHE);
439 for(i = MON_FIRST; i <= MON_LAST; ++i)
441 mon = get_monsterinfo(i);
442 if((mon.spawnflags & MONSTER_TYPE_FLY) || (mon.spawnflags & MONSTER_TYPE_SWIM))
443 continue; // flying/swimming monsters not yet supported
445 MON_ACTION(i, MR_PRECACHE);
449 InitializeEntity(world, invasion_DelayedInit, INITPRIO_GAMETYPE);
452 MUTATOR_DEFINITION(gamemode_invasion)
454 MUTATOR_HOOK(MonsterDies, invasion_MonsterDies, CBC_ORDER_ANY);
455 MUTATOR_HOOK(MonsterSpawn, invasion_MonsterSpawn, CBC_ORDER_ANY);
456 MUTATOR_HOOK(OnEntityPreSpawn, invasion_OnEntityPreSpawn, CBC_ORDER_ANY);
457 MUTATOR_HOOK(SV_StartFrame, invasion_StartFrame, CBC_ORDER_ANY);
458 MUTATOR_HOOK(PlayerRegen, invasion_PlayerRegen, CBC_ORDER_ANY);
459 MUTATOR_HOOK(PlayerSpawn, invasion_PlayerSpawn, CBC_ORDER_ANY);
460 MUTATOR_HOOK(PlayerDamage_Calculate, invasion_PlayerDamage, CBC_ORDER_ANY);
461 MUTATOR_HOOK(SV_ParseClientCommand, invasion_PlayerCommand, CBC_ORDER_ANY);
462 MUTATOR_HOOK(BotShouldAttack, invasion_BotShouldAttack, CBC_ORDER_ANY);
463 MUTATOR_HOOK(SetStartItems, invasion_SetStartItems, CBC_ORDER_ANY);
464 MUTATOR_HOOK(AccuracyTargetValid, invasion_AccuracyTargetValid, CBC_ORDER_ANY);
465 MUTATOR_HOOK(AllowMobSpawning, invasion_AllowMobSpawning, CBC_ORDER_ANY);
466 MUTATOR_HOOK(GetTeamCount, invasion_GetTeamCount, CBC_ORDER_ANY);
470 if(time > 1) // game loads at time 1
471 error("This is a game type and it cannot be added at runtime.");
472 invasion_Initialize();
474 cvar_settemp("g_monsters", "1");
477 MUTATOR_ONROLLBACK_OR_REMOVE
479 // we actually cannot roll back invasion_Initialize here
480 // BUT: we don't need to! If this gets called, adding always
486 print("This is a game type and it cannot be removed at runtime.");