From d5b01d654a591245d1e2463e9610a81e76914307 Mon Sep 17 00:00:00 2001 From: LegendaryGuard Date: Sat, 23 Oct 2021 01:56:56 +0200 Subject: [PATCH] Add Manhunt gamemode: Hunters go in search of the runners --- gamemodes-client.cfg | 1 + gamemodes-server.cfg | 23 ++++++ qcsrc/common/gamemodes/gamemode/_mod.inc | 1 + qcsrc/common/gamemodes/gamemode/_mod.qh | 1 + qcsrc/common/gamemodes/gamemode/mh/_mod.inc | 8 ++ qcsrc/common/gamemodes/gamemode/mh/_mod.qh | 8 ++ qcsrc/common/gamemodes/gamemode/mh/cl_mh.qc | 83 +++++++++++++++++++++ qcsrc/common/gamemodes/gamemode/mh/cl_mh.qh | 7 ++ qcsrc/common/gamemodes/gamemode/mh/mh.qc | 1 + qcsrc/common/gamemodes/gamemode/mh/mh.qh | 56 ++++++++++++++ qcsrc/common/gamemodes/gamemode/mh/sv_mh.qc | 64 ++++++++++++++++ qcsrc/common/gamemodes/gamemode/mh/sv_mh.qh | 19 +++++ qcsrc/menu/xonotic/util.qc | 1 + qcsrc/server/world.qc | 2 + 14 files changed, 275 insertions(+) create mode 100644 qcsrc/common/gamemodes/gamemode/mh/_mod.inc create mode 100644 qcsrc/common/gamemodes/gamemode/mh/_mod.qh create mode 100644 qcsrc/common/gamemodes/gamemode/mh/cl_mh.qc create mode 100644 qcsrc/common/gamemodes/gamemode/mh/cl_mh.qh create mode 100644 qcsrc/common/gamemodes/gamemode/mh/mh.qc create mode 100644 qcsrc/common/gamemodes/gamemode/mh/mh.qh create mode 100644 qcsrc/common/gamemodes/gamemode/mh/sv_mh.qc create mode 100644 qcsrc/common/gamemodes/gamemode/mh/sv_mh.qh diff --git a/gamemodes-client.cfg b/gamemodes-client.cfg index c43b9d1d3..4a632e7d6 100644 --- a/gamemodes-client.cfg +++ b/gamemodes-client.cfg @@ -32,6 +32,7 @@ alias cl_hook_gamestart_ka alias cl_hook_gamestart_ft alias cl_hook_gamestart_inv alias cl_hook_gamestart_duel +alias cl_hook_gamestart_mh alias cl_hook_gameend "rpn /cl_matchcount dup load 1 + =" // increase match count every time a game ends alias cl_hook_shutdown alias cl_hook_activeweapon diff --git a/gamemodes-server.cfg b/gamemodes-server.cfg index 17b90c624..8dcc0a875 100644 --- a/gamemodes-server.cfg +++ b/gamemodes-server.cfg @@ -29,6 +29,7 @@ alias sv_hook_gamestart_ka alias sv_hook_gamestart_ft alias sv_hook_gamestart_inv alias sv_hook_gamestart_duel +alias sv_hook_gamestart_mh // there is currently no hook for when the match is restarted // see sv_hook_readyrestart for previous uses of this hook //alias sv_hook_gamerestart @@ -58,6 +59,7 @@ alias sv_vote_gametype_hook_ons alias sv_vote_gametype_hook_rc alias sv_vote_gametype_hook_tdm alias sv_vote_gametype_hook_duel +alias sv_vote_gametype_hook_mh // Example preset to allow 1v1ctf to be used for the gametype voting screen. // Aliases can have max 31 chars so the gametype can have max 9 chars. @@ -208,6 +210,13 @@ set g_duel_respawn_delay_large_count 0 set g_duel_respawn_delay_max 0 set g_duel_respawn_waves 0 set g_duel_weapon_stay 0 +set g_mh_respawn_delay_small 0 +set g_mh_respawn_delay_small_count 0 +set g_mh_respawn_delay_large 0 +set g_mh_respawn_delay_large_count 0 +set g_mh_respawn_delay_max 0 +set g_mh_respawn_waves 0 +set g_mh_weapon_stay 0 // ========= @@ -559,3 +568,17 @@ set g_duel 0 "Duel: frag the opponent more in a one versus one arena battle" //set g_duel_warmup 180 "Have a short warmup period before beginning the actual duel" set g_duel_with_powerups 0 "Enable powerups to spawn in the duel gamemode" set g_duel_not_dm_maps 0 "when this is set, DM maps will NOT be listed in duel" + +// ========= +// manhunt +// ========= +set g_mh 0 "Manhunt: Hunters go in search of the runners" +set g_mh_not_dm_maps 0 "when this is set, DM maps will NOT be listed in MH" +set g_mh_not_lms_maps 0 "when this is set, LMS maps will NOT be listed in MH" + +// TODO: change this? +set g_mh_teams 2 "how many teams are in team deathmatch (set by mapinfo)" +set g_mh_team_spawns 0 "when 1, players spawn from the team spawnpoints of the map, if any" +set g_mh_teams_override 0 "how many teams are in manhunt" +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_point_leadlimit -1 "MH point lead limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)" diff --git a/qcsrc/common/gamemodes/gamemode/_mod.inc b/qcsrc/common/gamemodes/gamemode/_mod.inc index a33ec87a0..4f539ba30 100644 --- a/qcsrc/common/gamemodes/gamemode/_mod.inc +++ b/qcsrc/common/gamemodes/gamemode/_mod.inc @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include diff --git a/qcsrc/common/gamemodes/gamemode/_mod.qh b/qcsrc/common/gamemodes/gamemode/_mod.qh index ffd71d59d..43f7c4a95 100644 --- a/qcsrc/common/gamemodes/gamemode/_mod.qh +++ b/qcsrc/common/gamemodes/gamemode/_mod.qh @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include diff --git a/qcsrc/common/gamemodes/gamemode/mh/_mod.inc b/qcsrc/common/gamemodes/gamemode/mh/_mod.inc new file mode 100644 index 000000000..c7114153d --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/mh/_mod.inc @@ -0,0 +1,8 @@ +// generated file; do not modify +#include +#ifdef CSQC + #include +#endif +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/gamemodes/gamemode/mh/_mod.qh b/qcsrc/common/gamemodes/gamemode/mh/_mod.qh new file mode 100644 index 000000000..019427cbb --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/mh/_mod.qh @@ -0,0 +1,8 @@ +// generated file; do not modify +#include +#ifdef CSQC + #include +#endif +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/gamemodes/gamemode/mh/cl_mh.qc b/qcsrc/common/gamemodes/gamemode/mh/cl_mh.qc new file mode 100644 index 000000000..db54ce2d0 --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/mh/cl_mh.qc @@ -0,0 +1,83 @@ +#include "cl_mh.qh" + +#include + +// TODO: change this? + +void HUD_Mod_MH_Export(int fh) +{ + HUD_Write_Cvar("hud_panel_modicons_mh_layout"); +} + +void DrawMHItem(vector myPos, vector mySize, float aspect_ratio, int layout, int i) +{ + TC(int, layout); TC(int, i); + int stat = -1; + string pic = ""; + vector color = '0 0 0'; + switch(i) + { + case 0: stat = STAT(REDALIVE); pic = "player_red"; color = '1 0 0'; break; + case 1: stat = STAT(BLUEALIVE); pic = "player_blue"; color = '0 0 1'; break; + case 2: stat = STAT(YELLOWALIVE); pic = "player_yellow"; color = '1 1 0'; break; + default: + case 3: stat = STAT(PINKALIVE); pic = "player_pink"; color = '1 0 1'; break; + } + + if(mySize.x/mySize.y > aspect_ratio) + { + i = aspect_ratio * mySize.y; + myPos.x = myPos.x + (mySize.x - i) / 2; + mySize.x = i; + } + else + { + i = 1/aspect_ratio * mySize.x; + myPos.y = myPos.y + (mySize.y - i) / 2; + mySize.y = i; + } + + if(layout) + { + drawpic_aspect_skin(myPos, pic, vec2(0.5 * mySize.x, mySize.y), '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL); + drawstring_aspect(myPos + eX * 0.5 * mySize.x, ftos(stat), vec2(0.5 * mySize.x, mySize.y), color, panel_fg_alpha, DRAWFLAG_NORMAL); + } + else + drawstring_aspect(myPos, ftos(stat), mySize, color, panel_fg_alpha, DRAWFLAG_NORMAL); +} + +void HUD_Mod_MH_Draw(vector myPos, vector mySize, int layout) +{ + int rows, columns; + float aspect_ratio; + aspect_ratio = (layout) ? 2 : 1; + rows = HUD_GetRowCount(team_count, mySize, aspect_ratio); + columns = ceil(team_count/rows); + + int i; + float row = 0, column = 0; + vector pos = '0 0 0', itemSize; + itemSize = vec2(mySize.x / columns, mySize.y / rows); + for(i=0; i= rows) + { + row = 0; + ++column; + } + } +} + +// Clan Arena and Freeze Tag HUD modicons +void HUD_Mod_MH(vector myPos, vector mySize) +{ + mod_active = 1; // required in each mod function that always shows something + + HUD_Mod_MH_Draw(myPos, mySize, autocvar_hud_panel_modicons_mh_layout); +} diff --git a/qcsrc/common/gamemodes/gamemode/mh/cl_mh.qh b/qcsrc/common/gamemodes/gamemode/mh/cl_mh.qh new file mode 100644 index 000000000..b198d4f0e --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/mh/cl_mh.qh @@ -0,0 +1,7 @@ +#pragma once + +int autocvar_hud_panel_modicons_mh_layout; + +void HUD_Mod_MH(vector myPos, vector mySize); +void HUD_Mod_MH_Draw(vector myPos, vector mySize, int layout); +void HUD_Mod_MH_Export(int fh); diff --git a/qcsrc/common/gamemodes/gamemode/mh/mh.qc b/qcsrc/common/gamemodes/gamemode/mh/mh.qc new file mode 100644 index 000000000..601967f61 --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/mh/mh.qc @@ -0,0 +1 @@ +#include "mh.qh" diff --git a/qcsrc/common/gamemodes/gamemode/mh/mh.qh b/qcsrc/common/gamemodes/gamemode/mh/mh.qh new file mode 100644 index 000000000..abbb8c243 --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/mh/mh.qh @@ -0,0 +1,56 @@ +#pragma once + +#include + +#ifdef CSQC +void HUD_Mod_MH(vector pos, vector mySize); +void HUD_Mod_MH_Export(int fh); +#endif +CLASS(Manhunt, Gametype) + INIT(Manhunt) + { + this.gametype_init(this, _("Manhunt"),"mh","g_mh",GAMETYPE_FLAG_TEAMPLAY | GAMETYPE_FLAG_USEPOINTS | GAMETYPE_FLAG_PRIORITY,"","timelimit=15 pointlimit=10 teams=2 leadlimit=0",_("Help your team score the most frags against the enemy team")); + } + METHOD(Manhunt, m_parse_mapinfo, bool(string k, string v)) + { + if (!k) { + cvar_set("g_mh_teams", cvar_defstring("g_mh_teams")); + return true; + } + switch (k) { + case "teams": + cvar_set("g_mh_teams", v); + return true; + } + return false; + } + METHOD(Manhunt, m_isAlwaysSupported, bool(Gametype this, int spawnpoints, float diameter)) + { + if(spawnpoints >= 8 && diameter > 4096) + return true; + return false; + } + METHOD(Manhunt, m_isForcedSupported, bool(Gametype this)) + { + if(!cvar("g_mh_not_dm_maps")) + { + // if this is unset, all DM maps support MMM too + if(!(MapInfo_Map_supportedGametypes & this.m_flags) && (MapInfo_Map_supportedGametypes & MAPINFO_TYPE_DEATHMATCH.m_flags)) + return true; // TODO: references another gametype (alternatively, we could check which gamemodes are always enabled and append this if any are supported) + } + + return false; + } + METHOD(Manhunt, m_setTeams, void(string sa)) + { + cvar_set("g_mh_teams", sa); + } + METHOD(Manhunt, m_configuremenu, void(Gametype this, entity menu, void(entity me, string pLabel, float pMin, float pMax, float pStep, string pCvar, string tCvar, string pTooltip) returns)) + { + TC(Gametype, this); + returns(menu, _("Point limit:"), 5, 100, 5, "g_mh_point_limit", "g_mh_teams_override", _("The amount of points needed before the match will end")); + } + ATTRIB(Manhunt, m_legacydefaults, string, "50 20 2 0"); +ENDCLASS(Manhunt) +REGISTER_GAMETYPE(MANHUNT, NEW(Manhunt)); +#define g_mh IS_GAMETYPE(MANHUNT) diff --git a/qcsrc/common/gamemodes/gamemode/mh/sv_mh.qc b/qcsrc/common/gamemodes/gamemode/mh/sv_mh.qc new file mode 100644 index 000000000..65771d8cc --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/mh/sv_mh.qc @@ -0,0 +1,64 @@ +#include "sv_mh.qh" + +// TODO: change this? +int autocvar_g_mh_teams; +int autocvar_g_mh_teams_override; + +spawnfunc(mh_team) +{ + if(!g_mh || !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 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 = autocvar_g_mh_teams_override; + if(numteams < 2) { numteams = autocvar_g_mh_teams; } + + int teams = BITS(bound(2, numteams, 2)); + if(teams & BIT(0)) + mh_SpawnTeam("Red", NUM_TEAM_1); + if(teams & BIT(1)) + mh_SpawnTeam("Blue", NUM_TEAM_2); + } +} + +void mh_Initialize() +{ + GameRules_teams(true); + GameRules_spawning_teams(autocvar_g_mh_team_spawns); + GameRules_limit_score(autocvar_g_mh_point_limit); + GameRules_limit_lead(autocvar_g_mh_point_leadlimit); + + InitializeEntity(NULL, mh_DelayedInit, INITPRIO_GAMETYPE); +} + +MUTATOR_HOOKFUNCTION(mh, TeamBalance_CheckAllowedTeams, CBC_ORDER_EXCLUSIVE) +{ + M_ARGV(1, string) = "mh_team"; +} + +MUTATOR_HOOKFUNCTION(mh, Scores_CountFragsRemaining) +{ + // announce remaining frags + return true; +} diff --git a/qcsrc/common/gamemodes/gamemode/mh/sv_mh.qh b/qcsrc/common/gamemodes/gamemode/mh/sv_mh.qh new file mode 100644 index 000000000..4dcdafcb7 --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/mh/sv_mh.qh @@ -0,0 +1,19 @@ +#pragma once + +#include + +// TODO: change this? +int autocvar_g_mh_point_limit; +int autocvar_g_mh_point_leadlimit; +bool autocvar_g_mh_team_spawns; +void mh_Initialize(); + +REGISTER_MUTATOR(mh, false) +{ + MUTATOR_STATIC(); + MUTATOR_ONADD + { + mh_Initialize(); + } + return 0; +} diff --git a/qcsrc/menu/xonotic/util.qc b/qcsrc/menu/xonotic/util.qc index e77049d20..d3203fb65 100644 --- a/qcsrc/menu/xonotic/util.qc +++ b/qcsrc/menu/xonotic/util.qc @@ -681,6 +681,7 @@ float updateCompression() GAMETYPE(MAPINFO_TYPE_NEXBALL) \ GAMETYPE(MAPINFO_TYPE_ONSLAUGHT) \ GAMETYPE(MAPINFO_TYPE_ASSAULT) \ + GAMETYPE(MAPINFO_TYPE_MANHUNT) \ /* GAMETYPE(MAPINFO_TYPE_DUEL) */ \ /* GAMETYPE(MAPINFO_TYPE_INVASION) */ \ /**/ diff --git a/qcsrc/server/world.qc b/qcsrc/server/world.qc index 02c059ea8..f85591a9d 100644 --- a/qcsrc/server/world.qc +++ b/qcsrc/server/world.qc @@ -287,6 +287,8 @@ void cvar_changes_init() BADCVAR("g_keyhunt"); BADCVAR("g_keyhunt_teams"); BADCVAR("g_lms"); + BADCVAR("g_mh"); + BADCVAR("g_mh_not_dm_maps"); BADCVAR("g_nexball"); BADCVAR("g_onslaught"); BADCVAR("g_race"); -- 2.39.2