alias cl_hook_gamestart_cts
alias cl_hook_gamestart_ka
alias cl_hook_gamestart_ft
-alias cl_hook_gamestart_td
+alias cl_hook_gamestart_invasion
alias cl_hook_gameend
alias cl_hook_activeweapon
alias sv_hook_gamestart_cts
alias sv_hook_gamestart_ka
alias sv_hook_gamestart_ft
-alias sv_hook_gamestart_td
+alias sv_hook_gamestart_invasion
alias sv_hook_gamerestart
alias sv_hook_gameend
seta g_race_laps_limit -1 "Race laps limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
seta g_nexball_goallimit -1 "Nexball goal limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
seta g_nexball_goalleadlimit -1 "Nexball goal lead limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
+seta g_invasion_round_limit -1 "Invasion round limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
// =================================
set g_ft_respawn_waves 0
set g_ft_respawn_delay 0
set g_ft_weapon_stay 0
-set g_td_respawn_waves 0
-set g_td_respawn_delay 0
-set g_td_weapon_stay 0
+set g_invasion_respawn_waves 0
+set g_invasion_respawn_delay 0
+set g_invasion_weapon_stay 0
// =======
set g_race_qualifying_timelimit_override -1
set g_race_teams 0 "when 2, 3, or 4, the race is played as a team game (the team members can add up their laps)"
-// ===============
-// tower defense
-// ===============
-set g_td 0 "Tower Defense: protect the generator/s from waves of monsters"
-set g_td_majority_factor 0.8
-set g_td_force_settings 0 "if enabled, don't use map settings (monster count, start wave etc.)"
-set g_td_start_wave 1
-set g_td_generator_health 700
-set g_td_generator_damaged_points 20 "player loses this many points if the generator was damaged during the wave"
-set g_td_current_monsters 10 "maximum monsters that can be spawned simultaneously"
-set g_td_monster_count 10
-set g_td_monster_count_increment 5
-set g_td_buildphase_time 20
-set g_td_generator_dontend 0 "don't change maps when a generator is destroyed (only if there is more than 1 generator)"
-set g_td_pvp 0
-set g_td_monsters_skill_start 1 "set to 0 to use g_monsters_skill instead"
-set g_td_monsters_skill_increment 0.1
-set g_td_monsters_spawnshield_time 2
-set g_td_monsters_ignore_turrets 0
-set g_td_max_waves 8
-set g_td_kill_points 5
-set g_td_turretkill_points 3
-set g_td_turret_max 4
-set g_td_turret_plasma_cost 50
-set g_td_turret_mlrs_cost 80
-set g_td_turret_walker_cost 100
-set g_td_turret_towerbuff_cost 70
-set g_td_turret_barricade_cost 20
-set g_td_turret_flac_cost 40
-set g_td_turret_upgrade_cost 100
-set g_td_turret_repair_cost 20
-set g_td_barricade_damage 10
-set g_td_monsters_speed_walk 150
-set g_td_monsters_speed_run 170
-set g_td_monsters_spawn_delay 1.5
+// ==========
+// invasion
+// ==========
+set g_invasion 0 "Invasion: survive against waves of monsters"
+set g_invasion_round_timelimit 120 "maximum time to kill all monsters"
+set g_invasion_warmup 20 "time between waves to prepare for battle"
+set g_invasion_monster_count 20 "number of monsters on first wave (increments)"
+set g_invasion_zombies_only 0 "only spawn zombies"
--- /dev/null
+void invasion_spawnpoint()
+{
+ if not(g_invasion) { remove(self); return; }
+
+ self.classname = "invasion_spawnpoint";
+}
+
+float invasion_PickMonster()
+{
+ if(autocvar_g_invasion_zombies_only)
+ return MONSTER_ZOMBIE;
+
+ float i;
+
+ RandomSelection_Init();
+
+ for(i = MONSTER_FIRST + 1; i < MONSTER_LAST; ++i)
+ {
+ if(i == MONSTER_STINGRAY || i == MONSTER_WYVERN)
+ continue; // flying/swimming monsters not yet supported
+
+ RandomSelection_Add(world, i, "", 1, 1);
+ }
+
+ return RandomSelection_chosen_float;
+}
+
+entity invasion_PickSpawn()
+{
+ entity e;
+
+ RandomSelection_Init();
+
+ for(e = world;(e = find(e, classname, "invasion_spawnpoint")); )
+ RandomSelection_Add(e, 0, string_null, 1, 1);
+
+ return RandomSelection_chosen_ent;
+}
+
+void invasion_SpawnChosenMonster(float mon)
+{
+ entity spawn_point, monster;
+
+ spawn_point = invasion_PickSpawn();
+
+ if(spawn_point == world)
+ {
+ dprint("Warning: couldn't find any invasion_spawnpoint spawnpoints, no monsters will spawn!\n");
+ return;
+ }
+
+ monster = spawnmonster("", mon, spawn_point, spawn_point, spawn_point.origin, FALSE, 2);
+}
+
+void invasion_SpawnMonsters()
+{
+ float chosen_monster = invasion_PickMonster();
+
+ invasion_SpawnChosenMonster(chosen_monster);
+}
+
+float Invasion_CheckWinner()
+{
+ if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
+ {
+ entity head;
+ FOR_EACH_MONSTER(head)
+ {
+ WaypointSprite_Kill(head.sprite);
+ if(head.weaponentity) remove(head.weaponentity);
+ remove(head);
+ }
+
+ Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_OVER);
+ Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_OVER);
+ round_handler_Init(5, autocvar_g_invasion_warmup, autocvar_g_invasion_round_timelimit);
+ return 1;
+ }
+
+ if((numspawned - numkilled) < maxspawned)
+ {
+ if(time >= last_check)
+ {
+ invasion_SpawnMonsters();
+ last_check = time + 0.5;
+ }
+
+ return 0;
+ }
+
+ if(numspawned > 1)
+ return 0;
+
+ if(roundcnt >= maxrounds)
+ {
+ NextLevel();
+ return 1;
+ }
+
+ Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_ROUND_OVER);
+ Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_ROUND_OVER);
+
+ round_handler_Init(5, autocvar_g_invasion_warmup, autocvar_g_invasion_round_timelimit);
+
+ return 1;
+}
+
+float Invasion_CheckPlayers()
+{
+ return TRUE;
+}
+
+void Invasion_RoundStart()
+{
+ entity e;
+ float numplayers = 0;
+ FOR_EACH_PLAYER(e)
+ {
+ ++numplayers;
+ e.player_blocked = 0;
+ }
+
+ roundcnt += 1;
+
+ numspawned = 0;
+ numkilled = 0;
+
+ if(roundcnt > 1)
+ maxspawned = rint(autocvar_g_invasion_monster_count * roundcnt / 0.7);
+ else
+ maxspawned = autocvar_g_invasion_monster_count;
+
+ monster_skill += 0.01 * numplayers;
+}
+
+MUTATOR_HOOKFUNCTION(invasion_MonsterDies)
+{
+ numkilled += 1;
+
+ if(IS_PLAYER(frag_attacker))
+ PlayerScore_Add(frag_attacker, SP_INVASION_KILLS, +1);
+
+ return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(invasion_MonsterSpawn)
+{
+ if(self.realowner == world)
+ {
+ WaypointSprite_Kill(self.sprite);
+ if(self.weaponentity) remove(self.weaponentity);
+ remove(self);
+ return FALSE;
+ }
+
+ numspawned += 1;
+
+ self.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_BOTCLIP | DPCONTENTS_MONSTERCLIP;
+
+ return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(invasion_PlayerThink)
+{
+ monsters_total = maxspawned; // TODO: make sure numspawned never exceeds maxspawned
+ monsters_killed = numkilled;
+
+ return FALSE;
+}
+
+MUTATOR_HOOKFUNCTION(invasion_PlayerDamage)
+{
+ if(IS_PLAYER(frag_attacker) && IS_PLAYER(frag_target))
+ {
+ frag_damage = 0;
+ frag_force = '0 0 0';
+ }
+
+ if(frag_attacker.flags & FL_MONSTER && frag_target.flags & FL_MONSTER)
+ frag_damage = 0;
+
+ return FALSE;
+}
+
+void invasion_ScoreRules()
+{
+ ScoreRules_basics(0, SFL_SORT_PRIO_SECONDARY, 0, FALSE);
+ ScoreInfo_SetLabel_PlayerScore(SP_INVASION_KILLS, "kills", SFL_SORT_PRIO_PRIMARY);
+ ScoreRules_basics_end();
+}
+
+void invasion_Initialize()
+{
+ invasion_ScoreRules();
+
+ round_handler_Spawn(Invasion_CheckPlayers, Invasion_CheckWinner, Invasion_RoundStart);
+ round_handler_Init(5, autocvar_g_invasion_warmup, autocvar_g_invasion_round_timelimit);
+
+ allowed_to_spawn = TRUE;
+
+ monster_skill = 0.01;
+
+ roundcnt = 0;
+}
+
+MUTATOR_DEFINITION(gamemode_invasion)
+{
+ MUTATOR_HOOK(MonsterDies, invasion_MonsterDies, CBC_ORDER_ANY);
+ MUTATOR_HOOK(MonsterSpawn, invasion_MonsterSpawn, CBC_ORDER_ANY);
+ MUTATOR_HOOK(PlayerPreThink, invasion_PlayerThink, CBC_ORDER_ANY);
+ MUTATOR_HOOK(PlayerDamage_Calculate, invasion_PlayerDamage, CBC_ORDER_ANY);
+
+ MUTATOR_ONADD
+ {
+ if(time > 1) // game loads at time 1
+ error("This is a game type and it cannot be added at runtime.");
+ invasion_Initialize();
+
+ cvar_settemp("g_monsters", "1");
+ }
+
+ MUTATOR_ONROLLBACK_OR_REMOVE
+ {
+ // we actually cannot roll back invasion_Initialize here
+ // BUT: we don't need to! If this gets called, adding always
+ // succeeds.
+ }
+
+ MUTATOR_ONREMOVE
+ {
+ print("This is a game type and it cannot be removed at runtime.");
+ return -1;
+ }
+
+ return 0;
+}