]> git.rm.cloudns.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
copypasted hooks and functions from CA to get round-based gameplay.
authordrjaska <drjaska83@gmail.com>
Tue, 26 Oct 2021 02:39:26 +0000 (05:39 +0300)
committerdrjaska <drjaska83@gmail.com>
Tue, 26 Oct 2021 02:39:26 +0000 (05:39 +0300)
manages to compile atm so I'm pushing this WIP to let LG see what I've done thusfar
might crash on launch though

gamemodes-server.cfg
qcsrc/common/gamemodes/gamemode/mh/sv_mh.qc
qcsrc/common/gamemodes/gamemode/mh/sv_mh.qh

index d571a2227bd992b3d79202420eb4f19bdfce70c0..77f152146e008568a97437cb7892df7c0cc41ee8 100644 (file)
@@ -577,4 +577,5 @@ set g_mh_not_dm_maps 0 "when this is set, DM maps will NOT be listed in MH"
 set g_mh_team_spawns 0 "when 1, players spawn from the team spawnpoints of the map, if any"
 set g_mh_point_limit -1 "MH point limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)"
 set g_mh_warmup 10 "time players get to run around before the round starts"
+set g_mh_round_timelimit 180 "round time limit in seconds"
 set g_mh_weaponarena "" "starting weapons - takes the same options as g_weaponarena"
index 3086ee7bf21856c36947adc167e7c9ab7005412e..df4782621167565b522f14310e6eee1c7861e620 100644 (file)
@@ -7,45 +7,6 @@ spawnfunc(mh_team)
        this.team = this.cnt + 1;
 }
 
-// code from here on is just to support maps that don't have team entities
-void mh_SpawnTeam (string teamname, int teamcolor)
-{
-       entity this = new_pure(mh_team);
-       this.netname = teamname;
-       this.cnt = teamcolor - 1;
-       this.team = teamcolor;
-       this.spawnfunc_checked = true;
-       //spawnfunc_mh_team(this);
-}
-
-void mh_DelayedInit(entity this)
-{
-       // TODO: change this?
-
-       // if no teams are found, spawn defaults
-       if(find(NULL, classname, "mh_team") == NULL)
-       {
-               LOG_TRACE("No \"mh_team\" entities found on this map, creating them anyway.");
-
-               int numteams = 2;
-
-               int teams = BITS(bound(2, numteams, 2));
-               if(teams & BIT(0))
-                       mh_SpawnTeam("Red", NUM_TEAM_1); //is this the place to change the displayed team name?
-               if(teams & BIT(1))
-                       mh_SpawnTeam("Blue", NUM_TEAM_2); //is this the place to change the displayed team name?
-       }
-}
-
-void mh_Initialize()
-{
-       GameRules_teams(true);
-       GameRules_spawning_teams(autocvar_g_mh_team_spawns);
-       GameRules_limit_score(autocvar_g_mh_point_limit);
-
-       InitializeEntity(NULL, mh_DelayedInit, INITPRIO_GAMETYPE);
-}
-
 MUTATOR_HOOKFUNCTION(mh, TeamBalance_CheckAllowedTeams, CBC_ORDER_EXCLUSIVE)
 {
        M_ARGV(1, string) = "mh_team";
@@ -159,3 +120,514 @@ MUTATOR_HOOKFUNCTION(mh, GiveFragsForKill, CBC_ORDER_FIRST)
        M_ARGV(2, float) = 0; // score will be given to the players differently
        return true;
 }
+
+// ============================
+//  round-based gameplay hooks
+//    including teamchanging
+// ============================
+
+
+
+// "wtf do these functions do" chart
+
+// all functions:                                      read and                        modified
+//                                  understood:                        to fit mh:
+
+// mh_LastPlayerForTeam                                y                                       y
+// mh_LastPlayerForTeam_Notify         y                                       y
+// PlayerDies                                          y                                       
+// ClientDisconnect                                    y                                       y
+// HideTeamNagger                                      y                                       y
+// PlayerSpawn                                                                                 
+// ForbidSpawn                                                                                 
+// PutClientInServer                                                                   
+// reset_map_players                                                                   
+// reset_map_global                                    y                                       y until something needs to be added there
+// MH_count_alive_players                      y                                       y
+// MH_GetWinnerTeam                                    y                                       y
+// nades_Clear                                         ??                                      ??
+// MH_CheckWinner                                      y                                       y
+// MH_RoundStart                                       y                                       y
+// MH_CheckTeams                                       y                                       y
+// mh_isEliminated                                     y                                       y
+
+// general order which they are called in:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+// Function: 
+// mh_LastPlayerForTeam
+// Purpose in CA:
+// when there are more than 1 player alive for that team return null meaning there are many players alive for that team
+// Needed in MH? Purpose?:
+// yes, same
+// Needed modifications for MH:
+// Removed check for if player is not dead as players don't "die" die
+// Called by:
+// mh_LastPlayerForTeam_Notify
+// Calls:
+// none
+entity mh_LastPlayerForTeam(entity this)
+{
+       entity last_pl = NULL;
+       FOREACH_CLIENT(IS_PLAYER(it) && it != this, {
+               if (SAME_TEAM(this, it))
+               {
+                       if (!last_pl)
+                               last_pl = it;
+                       else
+                               return NULL;
+               }
+       });
+       return last_pl;
+}
+
+// Function: 
+// mh_LastPlayerForTeam_Notify
+// Purpose in CA:
+// is called when a player dies, calls mh_LastPlayerForTeam to see if there are more than 1 players left for that team, if only one is left then send them a notification
+// Needed in MH? Purpose?:
+// yes, same
+// Needed modifications for MH:
+// none I think
+// Called by:
+// PlayerDies , mh_LastPlayerForTeam_Notify
+// Calls:
+// mh_LastPlayerForTeam
+void mh_LastPlayerForTeam_Notify(entity this)
+{
+       if (!warmup_stage && round_handler_IsActive() && round_handler_IsRoundStarted())
+       {
+               entity pl = mh_LastPlayerForTeam(this);
+               if (pl)
+                       Send_Notification(NOTIF_ONE, pl, MSG_CENTER, CENTER_ALONE);
+       }
+}
+
+// Function: 
+// PlayerDies
+// Purpose in CA:
+// handle players dying
+// Needed in MH? Purpose?:
+// yes, handle players getting tagged
+// Needed modifications for MH:
+// change respawning
+// Called by:
+// a player dying
+// Calls:
+// mh_LastPlayerForTeam_Notify
+MUTATOR_HOOKFUNCTION(mh, PlayerDies)
+{
+       entity frag_target = M_ARGV(2, entity);
+
+       mh_LastPlayerForTeam_Notify(frag_target);
+       if (!allowed_to_spawn_untagged)
+       {
+               frag_target.respawn_flags = RESPAWN_SILENT;
+               // prevent unwanted sudden rejoin as spectator and movement of spectator camera
+               frag_target.respawn_time = time + 2;
+       }
+       frag_target.respawn_flags |= RESPAWN_FORCE;
+       if (!warmup_stage)
+               eliminatedPlayers.SendFlags |= 1;
+       return true;
+}
+
+// Function: 
+// ClientDisconnect
+// Purpose in CA:
+// if 2nd last player of a team dc's notify the last player that they are the last one
+// Needed in MH? Purpose?:
+// yes, same
+// Needed modifications for MH:
+// none
+// Called by:
+// a player disconnecting
+// Calls:
+// mh_LastPlayerForTeam_Notify
+MUTATOR_HOOKFUNCTION(mh, ClientDisconnect)
+{
+       entity player = M_ARGV(0, entity);
+
+       if (IS_PLAYER(player))
+               mh_LastPlayerForTeam_Notify(player);
+       return true;
+}
+
+// Function: 
+// HideTeamNagger
+// Purpose:
+// hides TeamNagger which nags about players stacking in one team
+MUTATOR_HOOKFUNCTION(mh, HideTeamNagger)
+{
+       return true; // doesn't work well with the whole stack teams until no non-tagged players exist thing
+}
+
+// Function: 
+// PlayerSpawn
+// Purpose in CA:
+// 
+// Needed in MH? Purpose?:
+// 
+// Needed modifications for MH:
+// 
+// Called by:
+// a player spawning
+// Calls:
+// none
+MUTATOR_HOOKFUNCTION(mh, PlayerSpawn)
+{
+       entity player = M_ARGV(0, entity);
+
+       player.caplayer = 1;
+       if (!warmup_stage)
+               eliminatedPlayers.SendFlags |= 1;
+}
+
+// Function: 
+// ForbidSpawn
+// Purpose in CA:
+// 
+// Needed in MH? Purpose?:
+// 
+// Needed modifications for MH:
+// 
+// Called by:
+// 
+// Calls:
+// 
+MUTATOR_HOOKFUNCTION(mh, ForbidSpawn)
+{
+       entity player = M_ARGV(0, entity);
+
+       // spectators / observers that weren't playing can join; they are
+       // immediately forced to observe in the PutClientInServer hook
+       // this way they are put in a team and can play in the next round
+       if (!allowed_to_spawn_untagged && player.caplayer)
+               return true;
+       return false;
+}
+
+// Function: 
+// PutClientInServer
+// Purpose in CA:
+// 
+// Needed in MH? Purpose?:
+// 
+// Needed modifications for MH:
+// 
+// Called by:
+// 
+// Calls:
+// 
+MUTATOR_HOOKFUNCTION(mh, PutClientInServer)
+{
+       entity player = M_ARGV(0, entity);
+
+       if (!allowed_to_spawn_untagged && IS_PLAYER(player)) // this is true even when player is trying to join
+       {
+               TRANSMUTE(Observer, player);
+               if (CS(player).jointime != time && !player.caplayer) // not when connecting
+               {
+                       player.caplayer = 0.5;
+                       Send_Notification(NOTIF_ONE_ONLY, player, MSG_INFO, INFO_CA_JOIN_LATE);
+               }
+       }
+}
+
+// Function: 
+// reset_map_players
+// Purpose in CA:
+// reset's each player's killstreak
+//
+// Needed in MH? Purpose?:
+// yes, 
+// Needed modifications for MH:
+// 
+// Called by:
+// map being reset after round end
+// Calls:
+// PutClientInServer
+MUTATOR_HOOKFUNCTION(mh, reset_map_players)
+{
+       FOREACH_CLIENT(true, {
+               CS(it).killcount = 0;
+               if (!it.caplayer && IS_BOT_CLIENT(it))
+               {
+                       it.team = -1;
+                       it.caplayer = 1;
+               }
+               if (it.caplayer)
+               {
+                       TRANSMUTE(Player, it);
+                       it.caplayer = 1;
+                       PutClientInServer(it);
+               }
+       });
+       return true;
+}
+
+// Function: 
+// reset_map_global
+// Purpose in CA:
+// allow players to spawn after a new round is started
+// Needed in MH? Purpose?:
+// yes, same but as untagged
+// Needed modifications for MH:
+// renamed allowed_to_spawn to allowed_to_spawn_untagged
+// Called by:
+// map being reset after round end
+// Calls:
+// none
+MUTATOR_HOOKFUNCTION(mh, reset_map_global)
+{
+       allowed_to_spawn_untagged = true;
+       return true;
+}
+
+// Function: 
+// MH_count_alive_players
+// Purpose in CA:
+// refresh count of how many players are alive in each team to Team_ functions
+// Needed in MH? Purpose?:
+// yes, same
+// Needed modifications for MH:
+// Removed support for 3rd and 4th teams
+// Called by:
+// MH_CheckWinner
+// Calls:
+// multiple Team_ functions which are imported
+void MH_count_alive_players()
+{
+       total_players = 0;
+       for (int i = 1; i <= 2; ++i)
+       {
+               Team_SetNumberOfAlivePlayers(Team_GetTeamFromIndex(i), 0);
+       }
+       FOREACH_CLIENT(IS_PLAYER(it) && Entity_HasValidTeam(it),
+       {
+               ++total_players;
+               if (IS_DEAD(it))
+               {
+                       continue;
+               }
+               entity team_ = Entity_GetTeam(it);
+               int num_alive = Team_GetNumberOfAlivePlayers(team_);
+               ++num_alive;
+               Team_SetNumberOfAlivePlayers(team_, num_alive);
+       });
+       FOREACH_CLIENT(IS_REAL_CLIENT(it),
+       {
+               STAT(REDALIVE, it) = Team_GetNumberOfAlivePlayers(
+                       Team_GetTeamFromIndex(1));
+               STAT(BLUEALIVE, it) = Team_GetNumberOfAlivePlayers(
+                       Team_GetTeamFromIndex(2));
+       });
+}
+
+// Function: 
+// MH_GetWinnerTeam
+// Purpose in CA:
+// checks the number of players alive on teams and returns a number appropriate to the situation
+// team 1 has players alive = return 1
+// team 2 has players alive = return 2
+// multiple have players alive = return 0
+// no one has players alive = return -1
+// Needed in MH? Purpose?:
+// yes, same
+// Needed modifications for MH:
+// cleanup
+// Called by:
+// MH_CheckWinner
+// Calls:
+// none
+int MH_GetWinnerTeam()
+{
+       int winner_team = 0;
+       if (Team_GetNumberOfAlivePlayers(Team_GetTeamFromIndex(1)) >= 1){
+               winner_team = 1;
+       }
+       for (int i = 2; i <= 2; ++i){
+               if (Team_GetNumberOfAlivePlayers(Team_GetTeamFromIndex(i)) >= 1){
+                       if (winner_team != 0){
+                               return 0;
+                       }
+                       winner_team = Team_IndexToTeam(i);
+               }
+       }
+       if (winner_team){
+               return winner_team;
+       }
+       return -1; // no player left
+}
+
+// Function: 
+// nades_Clear
+// Purpose in CA:
+// ??? maybe clear nades of the entity given as argument? how?
+// Needed in MH? Purpose?:
+// if it's needed in CA, yes
+// Needed modifications for MH:
+// none?
+// Called by:
+// MH_CheckWinner
+// Calls:
+// ??? maybe qcsrc/common/mutators/mutator/nades/nades.qc nades_Clear, idfk this magic / bubblegum fix and only given documentation is "// Remove nades that are being thrown"
+void nades_Clear(entity player);
+
+// Function: 
+// MH_CheckWinner
+// Purpose in CA:
+// 
+// Needed in MH? Purpose?:
+// 
+// Needed modifications for MH:
+// renamed allowed_to_spawn to allowed_to_spawn_untagged
+// Called by:
+// 
+// Calls:
+// round_handler_Init , MH_count_alive_players
+float MH_CheckWinner(){
+       if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0){
+               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_OVER);
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_OVER);
+               FOREACH_CLIENT(IS_PLAYER(it), { nades_Clear(it); });
+
+               allowed_to_spawn_untagged = false;
+               game_stopped = true;
+               round_handler_Init(5, autocvar_g_mh_warmup, autocvar_g_mh_round_timelimit);
+               return 1;
+       }
+
+       MH_count_alive_players();
+       if (Team_GetNumberOfAliveTeams() > 1){
+               return 0;
+       }
+
+       int winner_team = MH_GetWinnerTeam();
+       if(winner_team > 0){
+               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, APP_TEAM_NUM(winner_team, CENTER_ROUND_TEAM_WIN));
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(winner_team, INFO_ROUND_TEAM_WIN));
+               TeamScore_AddToTeam(winner_team, ST_MH_ROUNDS, +1);
+       }
+       else if(winner_team == -1){
+               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
+       }
+
+       allowed_to_spawn_untagged = false;
+       game_stopped = true;
+       round_handler_Init(5, autocvar_g_mh_warmup, autocvar_g_mh_round_timelimit);
+
+       FOREACH_CLIENT(IS_PLAYER(it), { nades_Clear(it); });
+
+       return 1;
+}
+
+// Function: 
+// MH_RoundStart
+// Purpose in CA:
+// if the game is in warmup players can just respawn as players instead of be forced to spectate
+// Needed in MH? Purpose?:
+// yes, allow players to respawn in runners while in warmup. maybe other stuff which depend on the round starting?
+// Needed modifications for MH:
+// renamed allowed_to_spawn to allowed_to_spawn_untagged
+// Called by:
+// round_handler_Spawn
+// Calls:
+// none
+void MH_RoundStart()
+{
+       allowed_to_spawn_untagged = boolean(warmup_stage);
+}
+
+// Function: 
+// MH_CheckTeams
+// Purpose in CA:
+// check that there are no empty teams
+// Needed in MH? Purpose?:
+// yes, same
+// Needed modifications for MH:
+// 
+// Called by:
+// round_handler_Spawn
+// Calls:
+// MH_count_alive_players , Team_ functions which are imported
+bool MH_CheckTeams()
+{
+       static int prev_missing_teams_mask;
+       allowed_to_spawn_untagged = true;
+       MH_count_alive_players();
+       if (Team_GetNumberOfAliveTeams() == NumTeams(mh_teams))
+       {
+               if(prev_missing_teams_mask > 0)
+                       Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS);
+               prev_missing_teams_mask = -1;
+               return true;
+       }
+       if(total_players == 0)
+       {
+               if(prev_missing_teams_mask > 0)
+                       Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_TEAMS);
+               prev_missing_teams_mask = -1;
+               return false;
+       }
+       int missing_teams_mask = 0;
+       for (int i = 1; i <= 2; ++i)
+       {
+               if ((mh_teams & Team_IndexToBit(i)) &&
+                       (Team_GetNumberOfAlivePlayers(Team_GetTeamFromIndex(i)) == 0))
+               {
+                       missing_teams_mask |= Team_IndexToBit(i);
+               }
+       }
+       if(prev_missing_teams_mask != missing_teams_mask)
+       {
+               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_MISSING_TEAMS, missing_teams_mask);
+               prev_missing_teams_mask = missing_teams_mask;
+       }
+       return false;
+}
+
+// Function: 
+// mh_isEliminated
+// Purpose in CA:
+// find if player has been eliminated and is not alive anymore
+// Needed in MH? Purpose?:
+// no?
+// Needed modifications for MH:
+// remove?
+// Called by:
+// EliminatedPlayers_Init
+// Calls:
+// none
+bool mh_isEliminated(entity e)
+{
+       if(e.caplayer == 1 && (IS_DEAD(e) || e.frags == FRAGS_PLAYER_OUT_OF_GAME))
+               return true;
+       if(e.caplayer == 0.5)
+               return true;
+       return false;
+}
\ No newline at end of file
index c4eb58b3236ee8d07367b8475d765505584949f3..0c6f4506a9f9c5dc4b831892198d9049f01533b3 100644 (file)
@@ -1,20 +1,62 @@
 #pragma once
 
 #include <common/mutators/base.qh>
+#include <server/elimination.qh>
+#include <server/round_handler.qh>
+#include <server/command/sv_cmd.qh>
 
 // TODO: change this?
 float autocvar_g_mh_point_limit;
 bool autocvar_g_mh_team_spawns;
-//float autocvar_g_mh_warmup;
+float autocvar_g_mh_warmup;
+float autocvar_g_mh_round_timelimit;
 string autocvar_g_mh_weaponarena;
-void mh_Initialize();
+
+int mh_teams;
+bool allowed_to_spawn_untagged;
+
+const int ST_MH_ROUNDS = 1;
+
+bool MH_CheckTeams();
+bool MH_CheckWinner();
+void MH_RoundStart();
+bool mh_isEliminated(entity e);
+
+// code from here on is just to support maps that don't have team entities
+void mh_SpawnTeam (string teamname, int teamcolor)
+{
+       entity this = new_pure(mh_team);
+       this.netname = teamname;
+       this.cnt = teamcolor - 1;
+       this.team = teamcolor;
+       this.spawnfunc_checked = true;
+       //spawnfunc_mh_team(this);
+}
 
 REGISTER_MUTATOR(mh, false)
 {
        MUTATOR_STATIC();
        MUTATOR_ONADD
        {
-               mh_Initialize();
+               GameRules_teams(true);
+               GameRules_spawning_teams(autocvar_g_mh_team_spawns);
+               GameRules_limit_score(autocvar_g_mh_point_limit);
+
+
+               mh_teams = BITS(2);
+               if(mh_teams & BIT(0))
+                       mh_SpawnTeam("Runners", NUM_TEAM_1); //is this the place to change the displayed team name?
+               if(mh_teams & BIT(1))
+                       mh_SpawnTeam("Hunters", NUM_TEAM_2); //is this the place to change the displayed team name?
+               
+               GameRules_scoring(mh_teams, SFL_SORT_PRIO_PRIMARY, 0, {
+            field_team(ST_MH_ROUNDS, "rounds", SFL_SORT_PRIO_PRIMARY);
+        });
+
+               allowed_to_spawn_untagged = true;
+               round_handler_Spawn(MH_CheckTeams, MH_CheckWinner, MH_RoundStart);
+               round_handler_Init(5, autocvar_g_mh_warmup, autocvar_g_mh_round_timelimit);
+               EliminatedPlayers_Init(mh_isEliminated);
        }
        return 0;
-}
+}
\ No newline at end of file