]> git.rm.cloudns.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
add Infection gamemode: survivors must avoid getting infected squeaktoy/infection-gamemode
authorsqueaktoy <latex@disroot.org>
Wed, 10 Jul 2024 18:16:53 +0000 (20:16 +0200)
committersqueaktoy <latex@disroot.org>
Thu, 11 Jul 2024 19:27:14 +0000 (21:27 +0200)
The game begins with only survivors. After a few seconds, a random
player will be selected to become infected. If a survivor is killed by
an infected player, the survivor will become infected. If all survivors
are dead, the infected win. If the infected fail to kill all survivors
within the timeframe, survivors win.

This commit is incomplete and will not work. Current issues:
- Fix game crash when a survivor is killed
- Mutators for setting what weapons infected have at their disposal

14 files changed:
gamemodes-client.cfg
gamemodes-server.cfg
notifications.cfg
qcsrc/common/gamemodes/gamemode/_mod.inc
qcsrc/common/gamemodes/gamemode/_mod.qh
qcsrc/common/gamemodes/gamemode/infection/_mod.inc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/infection/_mod.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/infection/infection.qc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/infection/infection.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/infection/sv_infection.qc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/infection/sv_infection.qh [new file with mode: 0644]
qcsrc/common/notifications/all.inc
qcsrc/menu/xonotic/util.qc
qcsrc/server/world.qc

index c63bba5a76efb86a6ca15ae8ba4405dd4598828d..4dc3aa5704992dc8968d718852f6d054cae0489d 100644 (file)
@@ -30,6 +30,7 @@ alias cl_hook_gamestart_nb
 alias cl_hook_gamestart_cts
 alias cl_hook_gamestart_ka
 alias cl_hook_gamestart_ft
+alias cl_hook_gamestart_infect
 alias cl_hook_gamestart_inv
 alias cl_hook_gamestart_duel
 alias cl_hook_gamestart_mayhem
index f3b6a95bbd2faeee87602893468abeb56f075305..6bbe3adbee9edaffd1b0c5a81b90cc93200cfc41 100644 (file)
@@ -27,6 +27,7 @@ alias sv_hook_gamestart_nb
 alias sv_hook_gamestart_cts
 alias sv_hook_gamestart_ka
 alias sv_hook_gamestart_ft
+alias sv_hook_gamestart_infect
 alias sv_hook_gamestart_inv
 alias sv_hook_gamestart_duel
 alias sv_hook_gamestart_mayhem
@@ -53,6 +54,7 @@ alias sv_vote_gametype_hook_cts
 alias sv_vote_gametype_hook_dm
 alias sv_vote_gametype_hook_dom
 alias sv_vote_gametype_hook_ft
+alias sv_vote_gametype_hook_infection
 alias sv_vote_gametype_hook_inv
 alias sv_vote_gametype_hook_ka
 alias sv_vote_gametype_hook_kh
@@ -596,6 +598,16 @@ set g_race_qualifying_timelimit_override -1 "qualifying session time limit overr
 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)"
 set g_race_cptimes_onlyself 0 "only show player's own checkpoint times"
 
+// ===========
+//  infection
+// ===========
+
+set g_infection 0 "Infection: survivors must avoid getting infected"
+set g_infection_round_timelimit 900 "maximum time for the infected to infect all survivors"
+set g_infection_warmup "how long the players will have time to run around the map before the round starts"
+set g_infection_infected_count "the minimum amount of players to infect"
+set g_infection_round_enddelay "seconds of delay for score evaluation after round could end"
+
 // ==========
 //  invasion
 // ==========
index 92f41697162f0b48e6b74e8f00e634ba033280c9..c5ea8eb2a3acbfa6dc3389e8d374aefa3731a453 100644 (file)
@@ -287,6 +287,8 @@ seta notification_INFO_SUPERSPEC_MISSING_UID "2" "0 = off, 1 = print to console,
 seta notification_INFO_SUPERWEAPON_PICKUP "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
 seta notification_INFO_SURVIVAL_HUNTER_WIN "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
 seta notification_INFO_SURVIVAL_SURVIVOR_WIN "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
+seta notification_INFO_INFECTION_INFECTED_WIN "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
+seta notification_INFO_INFECTION_SURVIVOR_WIN "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
 seta notification_INFO_TEAMCHANGE_LARGERTEAM "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
 seta notification_INFO_TEAMCHANGE_NOTALLOWED "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
 seta notification_INFO_VERSION_BETA "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
index 75b1ea00185c748278c7ad1b5b3045bff4550d6f..7eec50161df1e8901135edcbdf23d0780afe8347 100644 (file)
@@ -8,6 +8,7 @@
 #include <common/gamemodes/gamemode/domination/_mod.inc>
 #include <common/gamemodes/gamemode/duel/_mod.inc>
 #include <common/gamemodes/gamemode/freezetag/_mod.inc>
+#include <common/gamemodes/gamemode/infection/_mod.inc>
 #include <common/gamemodes/gamemode/invasion/_mod.inc>
 #include <common/gamemodes/gamemode/keepaway/_mod.inc>
 #include <common/gamemodes/gamemode/keyhunt/_mod.inc>
index 776a88d259104e5c1d5693cf739f5a940219804e..938ad192a4fdc4d87a6346f73d2ee9c3744ed1e3 100644 (file)
@@ -8,6 +8,7 @@
 #include <common/gamemodes/gamemode/domination/_mod.qh>
 #include <common/gamemodes/gamemode/duel/_mod.qh>
 #include <common/gamemodes/gamemode/freezetag/_mod.qh>
+#include <common/gamemodes/gamemode/infection/_mod.qh>
 #include <common/gamemodes/gamemode/invasion/_mod.qh>
 #include <common/gamemodes/gamemode/keepaway/_mod.qh>
 #include <common/gamemodes/gamemode/keyhunt/_mod.qh>
diff --git a/qcsrc/common/gamemodes/gamemode/infection/_mod.inc b/qcsrc/common/gamemodes/gamemode/infection/_mod.inc
new file mode 100644 (file)
index 0000000..5393d74
--- /dev/null
@@ -0,0 +1,5 @@
+// generated file; do not modify
+#include <common/gamemodes/gamemode/infection/infection.qc>
+#ifdef SVQC
+    #include <common/gamemodes/gamemode/infection/sv_infection.qc>
+#endif
diff --git a/qcsrc/common/gamemodes/gamemode/infection/_mod.qh b/qcsrc/common/gamemodes/gamemode/infection/_mod.qh
new file mode 100644 (file)
index 0000000..a9ce57b
--- /dev/null
@@ -0,0 +1,5 @@
+// generated file; do not modify
+#include <common/gamemodes/gamemode/infection/infection.qh>
+#ifdef SVQC
+    #include <common/gamemodes/gamemode/infection/sv_infection.qh>
+#endif
diff --git a/qcsrc/common/gamemodes/gamemode/infection/infection.qc b/qcsrc/common/gamemodes/gamemode/infection/infection.qc
new file mode 100644 (file)
index 0000000..0b8a13a
--- /dev/null
@@ -0,0 +1 @@
+#include "infection.qh"
diff --git a/qcsrc/common/gamemodes/gamemode/infection/infection.qh b/qcsrc/common/gamemodes/gamemode/infection/infection.qh
new file mode 100644 (file)
index 0000000..5ae1f60
--- /dev/null
@@ -0,0 +1,30 @@
+#pragma once
+
+#include <common/gamemodes/gamemode/deathmatch/deathmatch.qh>
+#include <common/gamemodes/gamemode/tdm/tdm.qh>
+#include <common/mapinfo.qh>
+
+CLASS(Infection, Gametype)
+       INIT(Infection)
+       {
+               this.gametype_init(this, _("Infection"),"inf","g_infection",GAMETYPE_FLAG_TEAMPLAY,"","timelimit=30",_("Survivors must avoid getting infected"));
+       }
+       METHOD(Infection, m_isAlwaysSupported, bool(Gametype this, int spawnpoints, float diameter))
+       {
+               return true;
+       }
+       METHOD(Infection, m_isForcedSupported, bool(Gametype this))
+       {
+               if(!(MapInfo_Map_supportedGametypes & this.gametype_flags) && (MapInfo_Map_supportedGametypes & MAPINFO_TYPE_DEATHMATCH.gametype_flags))
+               {
+                       return true;
+               }
+               if(!(MapInfo_Map_supportedGametypes & this.gametype_flags) && (MapInfo_Map_supportedGametypes & MAPINFO_TYPE_TEAM_DEATHMATCH.gametype_flags))
+               {
+                       return true;
+               }
+               return false;
+       }
+ENDCLASS(Infection)
+REGISTER_GAMETYPE(INFECTION, NEW(Infection));
+#define g_infection IS_GAMETYPE(TEAM_DEATHMATCH)
diff --git a/qcsrc/common/gamemodes/gamemode/infection/sv_infection.qc b/qcsrc/common/gamemodes/gamemode/infection/sv_infection.qc
new file mode 100644 (file)
index 0000000..30a2ab1
--- /dev/null
@@ -0,0 +1,172 @@
+#include "sv_infection.qh"
+
+const int NUM_TEAM_SURVIVOR = 2;
+const int NUM_TEAM_INFECTED = 1;
+
+float autocvar_g_infection_round_timelimit = 900;
+float autocvar_g_infection_warmup = 10;
+float autocvar_g_infection_infected_count = 1;
+float autocvar_g_infection_round_enddelay = 1;
+
+spawnfunc(inf_team)
+{
+       if(!g_infection || !this.cnt) { delete(this); return; }
+
+       this.team = this.cnt + 1;
+}
+
+// code from here on is just to support maps that don't have team entities
+void Infection_SpawnTeam (string teamname, int teamcolor)
+{
+       entity this = new_pure(inf_team);
+       this.netname = teamname;
+       this.cnt = teamcolor - 1;
+       this.team = teamcolor;
+       this.spawnfunc_checked = true;
+}
+
+void Infection_InitTeams(entity this)
+{
+       // if no teams are found, spawn defaults
+       if(find(NULL, classname, "Infection_team") == NULL)
+       {
+               LOG_TRACE("No \"infection_team\" entities found on this map, creating them anyway.");
+
+               int numteams = 2;
+
+               int teams = BITS(numteams);
+               if(teams & BIT(0))
+                       Infection_SpawnTeam("Infected", NUM_TEAM_2);
+               if(teams & BIT(1))
+                       Infection_SpawnTeam("Survivor", NUM_TEAM_1);
+       }
+}
+
+bool Infection_CheckPlayers()
+{
+       allowed_to_spawn = true;
+       int playercount = 0;
+       FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
+       {
+               ++playercount;
+       });
+       if (playercount < 2) {
+               Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_PLAYERS);
+               return false;
+       }
+       return true;
+}
+
+bool Infection_CheckWinner()
+{
+       if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0
+               && autocvar_g_infection_round_enddelay == -1)
+       {
+               // if the match times out, survivors win
+               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_INFECTION_SURVIVOR_WIN);
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_INFECTION_SURVIVOR_WIN);
+
+               allowed_to_spawn = false;
+               game_stopped = true;
+               round_handler_Init(5, autocvar_g_infection_warmup, autocvar_g_infection_round_timelimit);
+               return 1;
+       }
+
+       int survivor_count = 0, infected_count = 0;
+       FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
+       {
+               if(Entity_GetTeamIndex(it) == NUM_TEAM_SURVIVOR)
+                       ++survivor_count;
+               else if(Entity_GetTeamIndex(it) == NUM_TEAM_INFECTED)
+                       ++infected_count;
+       });
+       // Not enough infected? Let's infect some players.
+       int playercount = survivor_count + infected_count;
+       int must_infect = bound(1, ((autocvar_g_infection_infected_count >= 1) ? autocvar_g_infection_infected_count : floor(playercount * autocvar_g_infection_infected_count)), playercount - 1); // 20%, but ensure at least 1 and less than total
+       FOREACH_CLIENT_RANDOM(IS_PLAYER(it) && !IS_DEAD(it),
+       {
+               if(infected_count >= must_infect)
+                       break;
+               ++infected_count;
+               Player_SetTeamIndex(it, NUM_TEAM_INFECTED);
+       });
+
+       if (survivor_count > 0) {
+               round_handler_ResetEndDelayTime();
+               return 0;
+       }
+
+       // delay round ending a bit
+       if (autocvar_g_infection_round_enddelay
+               && round_handler_GetEndTime() > 0
+               && round_handler_GetEndTime() - time > 0) // don't delay past timelimit
+       {
+               if (round_handler_GetEndDelayTime() == -1)
+               {
+                       round_handler_SetEndDelayTime(min(time + autocvar_g_infection_round_enddelay, round_handler_GetEndTime()));
+                       return 0;
+               }
+               else if (round_handler_GetEndDelayTime() >= time)
+               {
+                       return 0;
+               }
+       }
+
+       if(infected_count > 0) // infected win
+       {
+               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_INFECTION_INFECTED_WIN);
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_INFECTION_INFECTED_WIN);
+       } else {
+               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
+       }
+
+       allowed_to_spawn = false;
+       game_stopped = true;
+       round_handler_Init(5, autocvar_g_infection_warmup, autocvar_g_infection_round_timelimit);
+       return 1;
+}
+
+void Infection_RoundStart()
+{
+       allowed_to_spawn = boolean(warmup_stage);
+       FOREACH_CLIENT(IS_PLAYER(it), {
+               it.player_blocked = false;
+               Player_SetTeamIndex(it, NUM_TEAM_SURVIVOR);
+       });
+}
+
+void Infection_Initialize()
+{
+       GameRules_teams(true);
+       GameRules_spawning_teams(2);
+       InitializeEntity(NULL, Infection_InitTeams, INITPRIO_GAMETYPE);
+
+       allowed_to_spawn = true;
+       round_handler_Spawn(Infection_CheckPlayers, Infection_CheckWinner, Infection_RoundStart);
+       round_handler_Init(5, autocvar_g_infection_warmup, autocvar_g_infection_round_timelimit);
+}
+
+MUTATOR_HOOKFUNCTION(inf, TeamBalance_CheckAllowedTeams)
+{
+       M_ARGV(0, float) = BIT(0);
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(inf, PlayerDies)
+{
+       entity attacker = M_ARGV(1, entity);
+       entity target = M_ARGV(2, entity);
+
+       if (!IS_PLAYER(attacker)) {
+               Player_SetTeamIndex(target, NUM_TEAM_INFECTED);
+               return true;
+       }
+
+       int attacker_team = Entity_GetTeamIndex(attacker);
+
+       if (DIFF_TEAM(attacker, target) && attacker_team != NUM_TEAM_SURVIVOR)
+               Player_SetTeamIndex(target, attacker_team);
+
+       return true;
+}
diff --git a/qcsrc/common/gamemodes/gamemode/infection/sv_infection.qh b/qcsrc/common/gamemodes/gamemode/infection/sv_infection.qh
new file mode 100644 (file)
index 0000000..fe38ee3
--- /dev/null
@@ -0,0 +1,14 @@
+#pragma once
+
+#include <common/mutators/base.qh>
+void Infection_Initialize();
+
+REGISTER_MUTATOR(inf, false)
+{
+    MUTATOR_STATIC();
+       MUTATOR_ONADD
+       {
+               Infection_Initialize();
+       }
+       return false;
+}
index 365f6cb60e7e823afd715a26dcdeb62950b3da00..cfc68231300dfe1a32ba149bc196c27bec963f7e 100644 (file)
@@ -455,6 +455,9 @@ string multiteam_info_sprintf(string input, string teamname) { return ((input !=
     MSG_INFO_NOTIF(SURVIVAL_HUNTER_WIN,                     N_CONSOLE,  0, 0, "", "",           "",                     _("^K1Hunters^BG win the round"), "")
     MSG_INFO_NOTIF(SURVIVAL_SURVIVOR_WIN,                   N_CONSOLE,  0, 0, "", "",           "",                     _("^F1Survivors^BG win the round"), "")
 
+    MSG_INFO_NOTIF(INFECTION_INFECTED_WIN,                  N_CONSOLE,  0, 0, "", "",           "",                     _("^K1Infected^BG wins the round"), "")
+    MSG_INFO_NOTIF(INFECTION_SURVIVOR_WIN,                  N_CONSOLE,  0, 0, "", "",           "",                     _("^F1Survivors^BG win the round"), "")
+
     MSG_INFO_NOTIF(TEAMCHANGE_LARGERTEAM,                   N_CONSOLE,  0, 0, "", "",           "",                     _("^BGYou cannot change to a larger team"), "")
     MSG_INFO_NOTIF(TEAMCHANGE_NOTALLOWED,                   N_CONSOLE,  0, 0, "", "",           "",                     _("^BGYou are not allowed to change teams"), "")
 
@@ -800,6 +803,11 @@ string multiteam_info_sprintf(string input, string teamname) { return ((input !=
     MSG_CENTER_NOTIF(SURVIVAL_SURVIVOR,                 N_ENABLE,    0, 0, "",               CPID_SURVIVAL,          "5 0",  BOLD(_("^BGYou are a ^F1survivor^BG! Identify and eliminate the hunter(s)!")), "")
     MSG_CENTER_NOTIF(SURVIVAL_SURVIVOR_WIN,             N_ENABLE,    0, 0, "",               CPID_ROUND,             "0 0",  _("^F1Survivors^BG win the round"), "")
 
+    MSG_CENTER_NOTIF(INFECTION_INFECTED,                N_ENABLE,    0, 0, "",               CPID_SURVIVAL,          "5 0",  BOLD(_("^BGYou are ^K1infected^BG! Eliminate the survivor(s)!")), "")
+    MSG_CENTER_NOTIF(INFECTION_INFECTED_WIN,            N_ENABLE,    0, 0, "",               CPID_ROUND,             "0 0",  _("^K1Infected^BG wins the round"), "")
+    MSG_CENTER_NOTIF(INFECTION_SURVIVOR,                N_ENABLE,    0, 0, "",               CPID_SURVIVAL,          "5 0",  BOLD(_("^BGYou are a ^F1survivor^BG! Don't get infected!")), "")
+    MSG_CENTER_NOTIF(INFECTION_SURVIVOR_WIN,            N_ENABLE,    0, 0, "",               CPID_ROUND,             "0 0",  _("^F1Survivors^BG win the round"), "")
+
     MULTITEAM_CENTER(TEAMCHANGE,                        N_ENABLE,    0, 1, "",               CPID_TEAMCHANGE,        "1 f1", _("^K1Changing to ^TC^TT^K1 in ^COUNT"), "", NAME)
     MSG_CENTER_NOTIF(TEAMCHANGE_AUTO,                   N_ENABLE,    0, 1, "",               CPID_TEAMCHANGE,        "1 f1", _("^K1Changing team in ^COUNT"), "")
     MSG_CENTER_NOTIF(TEAMCHANGE_SPECTATE,               N_ENABLE,    0, 1, "",               CPID_TEAMCHANGE,        "1 f1", _("^K1Spectating in ^COUNT"), "")
index ac3c798a9f3f424e8fbe338b8459fc9ada444f27..3be8e6837e52f70b489ef8ef2446a4ea11402a6a 100644 (file)
@@ -673,6 +673,7 @@ float updateCompression()
        GAMETYPE(MAPINFO_TYPE_ONSLAUGHT) \
        GAMETYPE(MAPINFO_TYPE_ASSAULT) \
        GAMETYPE(MAPINFO_TYPE_SURVIVAL) \
+       GAMETYPE(MAPINFO_TYPE_INFECTION) \
        /* GAMETYPE(MAPINFO_TYPE_DUEL) */ \
        /**/
 
index f051eea48ba8666375c81a5c802c44d6de2eaacc..e86d172cddddb54068afe2a52eacec91a4636bc5 100644 (file)
@@ -309,6 +309,7 @@ void cvar_changes_init()
                BADCVAR("g_tmayhem");
                BADCVAR("g_tmayhem_teams");
                BADCVAR("g_vip");
+               BADCVAR("g_infection");
                BADCVAR("leadlimit");
                BADCVAR("nextmap");
                BADCVAR("teamplay");