From 0e696e081d6f58739d8b4ce013c6a839b2dbb7d9 Mon Sep 17 00:00:00 2001 From: drjaska Date: Tue, 26 Oct 2021 05:39:26 +0300 Subject: [PATCH] copypasted hooks and functions from CA to get round-based gameplay. 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 | 1 + qcsrc/common/gamemodes/gamemode/mh/sv_mh.qc | 550 ++++++++++++++++++-- qcsrc/common/gamemodes/gamemode/mh/sv_mh.qh | 50 +- 3 files changed, 558 insertions(+), 43 deletions(-) diff --git a/gamemodes-server.cfg b/gamemodes-server.cfg index d571a2227..77f152146 100644 --- a/gamemodes-server.cfg +++ b/gamemodes-server.cfg @@ -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" diff --git a/qcsrc/common/gamemodes/gamemode/mh/sv_mh.qc b/qcsrc/common/gamemodes/gamemode/mh/sv_mh.qc index 3086ee7bf..df4782621 100644 --- a/qcsrc/common/gamemodes/gamemode/mh/sv_mh.qc +++ b/qcsrc/common/gamemodes/gamemode/mh/sv_mh.qc @@ -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 diff --git a/qcsrc/common/gamemodes/gamemode/mh/sv_mh.qh b/qcsrc/common/gamemodes/gamemode/mh/sv_mh.qh index c4eb58b32..0c6f4506a 100644 --- a/qcsrc/common/gamemodes/gamemode/mh/sv_mh.qh +++ b/qcsrc/common/gamemodes/gamemode/mh/sv_mh.qh @@ -1,20 +1,62 @@ #pragma once #include +#include +#include +#include // 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 -- 2.39.2