From 9b5d62a6b88a1260423e925a247e205511f542ee Mon Sep 17 00:00:00 2001 From: "Dr. Jaska" Date: Sun, 5 Mar 2023 18:35:32 +0000 Subject: [PATCH] Mayhem and Team Mayhem gamemodes Each player spawns with 200/200 and full ammo like in LMS&CA and pickup items are removed by default, powerups are kept for extra spice but can be removed with whitelisted cvars. --- balance-mario.cfg | 16 + balance-nexuiz25.cfg | 16 + balance-overkill.cfg | 16 + balance-samual.cfg | 16 + balance-xdf.cfg | 16 + balance-xonotic.cfg | 16 + balance-xpm.cfg | 16 + gamemodes-client.cfg | 2 + gamemodes-server.cfg | 60 +++ qcsrc/common/gamemodes/gamemode/_mod.inc | 2 + qcsrc/common/gamemodes/gamemode/_mod.qh | 2 + .../common/gamemodes/gamemode/mayhem/_mod.inc | 5 + .../common/gamemodes/gamemode/mayhem/_mod.qh | 5 + .../gamemodes/gamemode/mayhem/mayhem.qc | 1 + .../gamemodes/gamemode/mayhem/mayhem.qh | 35 ++ .../gamemodes/gamemode/mayhem/sv_mayhem.qc | 369 ++++++++++++++++++ .../gamemodes/gamemode/mayhem/sv_mayhem.qh | 17 + .../gamemodes/gamemode/tmayhem/_mod.inc | 5 + .../common/gamemodes/gamemode/tmayhem/_mod.qh | 5 + .../gamemodes/gamemode/tmayhem/sv_tmayhem.qc | 259 ++++++++++++ .../gamemodes/gamemode/tmayhem/sv_tmayhem.qh | 21 + .../gamemodes/gamemode/tmayhem/tmayhem.qc | 1 + .../gamemodes/gamemode/tmayhem/tmayhem.qh | 51 +++ qcsrc/menu/xonotic/util.qc | 2 + qcsrc/server/world.qc | 10 + 25 files changed, 964 insertions(+) create mode 100644 qcsrc/common/gamemodes/gamemode/mayhem/_mod.inc create mode 100644 qcsrc/common/gamemodes/gamemode/mayhem/_mod.qh create mode 100644 qcsrc/common/gamemodes/gamemode/mayhem/mayhem.qc create mode 100644 qcsrc/common/gamemodes/gamemode/mayhem/mayhem.qh create mode 100644 qcsrc/common/gamemodes/gamemode/mayhem/sv_mayhem.qc create mode 100644 qcsrc/common/gamemodes/gamemode/mayhem/sv_mayhem.qh create mode 100644 qcsrc/common/gamemodes/gamemode/tmayhem/_mod.inc create mode 100644 qcsrc/common/gamemodes/gamemode/tmayhem/_mod.qh create mode 100644 qcsrc/common/gamemodes/gamemode/tmayhem/sv_tmayhem.qc create mode 100644 qcsrc/common/gamemodes/gamemode/tmayhem/sv_tmayhem.qh create mode 100644 qcsrc/common/gamemodes/gamemode/tmayhem/tmayhem.qc create mode 100644 qcsrc/common/gamemodes/gamemode/tmayhem/tmayhem.qh diff --git a/balance-mario.cfg b/balance-mario.cfg index 42e3a6696..8b9f37ac1 100644 --- a/balance-mario.cfg +++ b/balance-mario.cfg @@ -49,6 +49,22 @@ set g_lms_start_ammo_rockets 160 set g_lms_start_ammo_cells 180 set g_lms_start_ammo_plasma 180 set g_lms_start_ammo_fuel 0 +set g_mayhem_start_health 200 +set g_mayhem_start_armor 200 +set g_mayhem_start_ammo_shells 60 +set g_mayhem_start_ammo_nails 320 +set g_mayhem_start_ammo_rockets 160 +set g_mayhem_start_ammo_cells 180 +set g_mayhem_start_ammo_plasma 180 +set g_mayhem_start_ammo_fuel 0 +set g_tmayhem_start_health 200 +set g_tmayhem_start_armor 200 +set g_tmayhem_start_ammo_shells 60 +set g_tmayhem_start_ammo_nails 320 +set g_tmayhem_start_ammo_rockets 160 +set g_tmayhem_start_ammo_cells 180 +set g_tmayhem_start_ammo_plasma 180 +set g_tmayhem_start_ammo_fuel 0 set g_balance_nix_roundtime 25 set g_balance_nix_incrtime 1.6 set g_balance_nix_ammo_shells 60 diff --git a/balance-nexuiz25.cfg b/balance-nexuiz25.cfg index 6cdc29dcc..0b39de222 100644 --- a/balance-nexuiz25.cfg +++ b/balance-nexuiz25.cfg @@ -49,6 +49,22 @@ set g_lms_start_ammo_rockets 50 set g_lms_start_ammo_cells 50 set g_lms_start_ammo_plasma 50 set g_lms_start_ammo_fuel 0 +set g_mayhem_start_health 250 +set g_mayhem_start_armor 100 +set g_mayhem_start_ammo_shells 50 +set g_mayhem_start_ammo_nails 150 +set g_mayhem_start_ammo_rockets 50 +set g_mayhem_start_ammo_cells 50 +set g_mayhem_start_ammo_plasma 50 +set g_mayhem_start_ammo_fuel 0 +set g_tmayhem_start_health 250 +set g_tmayhem_start_armor 100 +set g_tmayhem_start_ammo_shells 50 +set g_tmayhem_start_ammo_nails 150 +set g_tmayhem_start_ammo_rockets 50 +set g_tmayhem_start_ammo_cells 50 +set g_tmayhem_start_ammo_plasma 50 +set g_tmayhem_start_ammo_fuel 0 set g_balance_nix_roundtime 25 set g_balance_nix_incrtime 1.6 set g_balance_nix_ammo_shells 15 diff --git a/balance-overkill.cfg b/balance-overkill.cfg index e33ee827f..3944d7d3b 100644 --- a/balance-overkill.cfg +++ b/balance-overkill.cfg @@ -49,6 +49,22 @@ set g_lms_start_ammo_rockets 160 set g_lms_start_ammo_cells 180 set g_lms_start_ammo_plasma 180 set g_lms_start_ammo_fuel 0 +set g_mayhem_start_health 200 +set g_mayhem_start_armor 100 +set g_mayhem_start_ammo_shells 60 +set g_mayhem_start_ammo_nails 320 +set g_mayhem_start_ammo_rockets 160 +set g_mayhem_start_ammo_cells 180 +set g_mayhem_start_ammo_plasma 180 +set g_mayhem_start_ammo_fuel 0 +set g_tmayhem_start_health 200 +set g_tmayhem_start_armor 100 +set g_tmayhem_start_ammo_shells 60 +set g_tmayhem_start_ammo_nails 320 +set g_tmayhem_start_ammo_rockets 160 +set g_tmayhem_start_ammo_cells 180 +set g_tmayhem_start_ammo_plasma 180 +set g_tmayhem_start_ammo_fuel 0 set g_balance_nix_roundtime 25 set g_balance_nix_incrtime 1.6 set g_balance_nix_ammo_shells 60 diff --git a/balance-samual.cfg b/balance-samual.cfg index 7850aaba8..5686c58d6 100644 --- a/balance-samual.cfg +++ b/balance-samual.cfg @@ -49,6 +49,22 @@ set g_lms_start_ammo_rockets 160 set g_lms_start_ammo_cells 180 set g_lms_start_ammo_plasma 180 set g_lms_start_ammo_fuel 0 +set g_mayhem_start_health 200 +set g_mayhem_start_armor 200 +set g_mayhem_start_ammo_shells 60 +set g_mayhem_start_ammo_nails 320 +set g_mayhem_start_ammo_rockets 160 +set g_mayhem_start_ammo_cells 180 +set g_mayhem_start_ammo_plasma 180 +set g_mayhem_start_ammo_fuel 0 +set g_tmayhem_start_health 200 +set g_tmayhem_start_armor 200 +set g_tmayhem_start_ammo_shells 60 +set g_tmayhem_start_ammo_nails 320 +set g_tmayhem_start_ammo_rockets 160 +set g_tmayhem_start_ammo_cells 180 +set g_tmayhem_start_ammo_plasma 180 +set g_tmayhem_start_ammo_fuel 0 set g_balance_nix_roundtime 25 set g_balance_nix_incrtime 1.6 set g_balance_nix_ammo_shells 60 diff --git a/balance-xdf.cfg b/balance-xdf.cfg index 67e8810b5..d8d4df60d 100644 --- a/balance-xdf.cfg +++ b/balance-xdf.cfg @@ -49,6 +49,22 @@ set g_lms_start_ammo_rockets 160 set g_lms_start_ammo_cells 180 set g_lms_start_ammo_plasma 180 set g_lms_start_ammo_fuel 0 +set g_mayhem_start_health 200 +set g_mayhem_start_armor 200 +set g_mayhem_start_ammo_shells 60 +set g_mayhem_start_ammo_nails 320 +set g_mayhem_start_ammo_rockets 160 +set g_mayhem_start_ammo_cells 180 +set g_mayhem_start_ammo_plasma 180 +set g_mayhem_start_ammo_fuel 0 +set g_tmayhem_start_health 200 +set g_tmayhem_start_armor 200 +set g_tmayhem_start_ammo_shells 60 +set g_tmayhem_start_ammo_nails 320 +set g_tmayhem_start_ammo_rockets 160 +set g_tmayhem_start_ammo_cells 180 +set g_tmayhem_start_ammo_plasma 180 +set g_tmayhem_start_ammo_fuel 0 set g_balance_nix_roundtime 25 set g_balance_nix_incrtime 1.6 set g_balance_nix_ammo_shells 60 diff --git a/balance-xonotic.cfg b/balance-xonotic.cfg index 784e447f4..2cf8c9eb4 100644 --- a/balance-xonotic.cfg +++ b/balance-xonotic.cfg @@ -49,6 +49,22 @@ set g_lms_start_ammo_rockets 160 set g_lms_start_ammo_cells 180 set g_lms_start_ammo_plasma 180 set g_lms_start_ammo_fuel 0 +set g_mayhem_start_health 200 +set g_mayhem_start_armor 200 +set g_mayhem_start_ammo_shells 60 +set g_mayhem_start_ammo_nails 320 +set g_mayhem_start_ammo_rockets 160 +set g_mayhem_start_ammo_cells 180 +set g_mayhem_start_ammo_plasma 180 +set g_mayhem_start_ammo_fuel 0 +set g_tmayhem_start_health 200 +set g_tmayhem_start_armor 200 +set g_tmayhem_start_ammo_shells 60 +set g_tmayhem_start_ammo_nails 320 +set g_tmayhem_start_ammo_rockets 160 +set g_tmayhem_start_ammo_cells 180 +set g_tmayhem_start_ammo_plasma 180 +set g_tmayhem_start_ammo_fuel 0 set g_balance_nix_roundtime 25 set g_balance_nix_incrtime 1.6 set g_balance_nix_ammo_shells 60 diff --git a/balance-xpm.cfg b/balance-xpm.cfg index 189be9c71..87b781a55 100644 --- a/balance-xpm.cfg +++ b/balance-xpm.cfg @@ -49,6 +49,22 @@ set g_lms_start_ammo_rockets 160 set g_lms_start_ammo_cells 180 set g_lms_start_ammo_plasma 180 set g_lms_start_ammo_fuel 0 +set g_mayhem_start_health 200 +set g_mayhem_start_armor 200 +set g_mayhem_start_ammo_shells 60 +set g_mayhem_start_ammo_nails 320 +set g_mayhem_start_ammo_rockets 160 +set g_mayhem_start_ammo_cells 180 +set g_mayhem_start_ammo_plasma 180 +set g_mayhem_start_ammo_fuel 0 +set g_tmayhem_start_health 200 +set g_tmayhem_start_armor 200 +set g_tmayhem_start_ammo_shells 60 +set g_tmayhem_start_ammo_nails 320 +set g_tmayhem_start_ammo_rockets 160 +set g_tmayhem_start_ammo_cells 180 +set g_tmayhem_start_ammo_plasma 180 +set g_tmayhem_start_ammo_fuel 0 set g_balance_nix_roundtime 25 set g_balance_nix_incrtime 1.6 set g_balance_nix_ammo_shells 60 diff --git a/gamemodes-client.cfg b/gamemodes-client.cfg index 71d272a17..259c2c31d 100644 --- a/gamemodes-client.cfg +++ b/gamemodes-client.cfg @@ -32,6 +32,8 @@ alias cl_hook_gamestart_ka alias cl_hook_gamestart_ft alias cl_hook_gamestart_inv alias cl_hook_gamestart_duel +alias cl_hook_gamestart_mayhem +alias cl_hook_gamestart_tmayhem alias cl_hook_gameend alias cl_hook_shutdown alias cl_hook_activeweapon diff --git a/gamemodes-server.cfg b/gamemodes-server.cfg index bbe0c37bc..eb0dcdcdc 100644 --- a/gamemodes-server.cfg +++ b/gamemodes-server.cfg @@ -29,6 +29,8 @@ alias sv_hook_gamestart_ka alias sv_hook_gamestart_ft alias sv_hook_gamestart_inv alias sv_hook_gamestart_duel +alias sv_hook_gamestart_mayhem +alias sv_hook_gamestart_tmayhem // 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 +60,8 @@ 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_mayhem +alias sv_vote_gametype_hook_tmayhem // 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 +212,20 @@ 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_mayhem_respawn_delay_small 0 +set g_mayhem_respawn_delay_small_count 0 +set g_mayhem_respawn_delay_large 0 +set g_mayhem_respawn_delay_large_count 0 +set g_mayhem_respawn_delay_max 0 +set g_mayhem_respawn_waves 0 +set g_mayhem_weapon_stay 0 +set g_tmayhem_respawn_delay_small 0 +set g_tmayhem_respawn_delay_small_count 0 +set g_tmayhem_respawn_delay_large 0 +set g_tmayhem_respawn_delay_large_count 0 +set g_tmayhem_respawn_delay_max 0 +set g_tmayhem_respawn_waves 0 +set g_tmayhem_weapon_stay 0 // ========= @@ -576,3 +594,45 @@ 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" + +// ============================== +// free for all and team mayhem +// ============================== +set g_mayhem 0 "Mayhem: Compete for the most damage dealt and kills in this chaotic mayhem!" +set g_tmayhem 0 "Team Mayhem: Compete with your team for the most damage dealt and kills in this chaotic mayhem!" + +set g_mayhem_scoring_upscaler 20 "upscale one frag's worth to be this amount of in score" +set g_tmayhem_scoring_upscaler 20 "upscale one frag's worth to be this amount of in score" +set g_mayhem_scoring_kill_weight 0.25 "how much is a kill worth in frags" +set g_tmayhem_scoring_kill_weight 0.25 "how much is a kill worth in frags" +set g_mayhem_scoring_damage_weight 0.75 "how much is damage equal to player's spawning health+armor worth in frags" +set g_tmayhem_scoring_damage_weight 0.75 "how much is damage equal to player's spawning health+armor worth in frags" +set g_mayhem_scoring_disable_selfdamage2score 0 "disable reducing score with self damage at the cost of full penalty for suicides regardless of how much health was lost suiciding" +set g_tmayhem_scoring_disable_selfdamage2score 0 "disable reducing score with self damage at the cost of full penalty for suicides regardless of how much health was lost suiciding" + +set g_mayhem_point_limit -1 "Mayhem score limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)" +set g_mayhem_point_leadlimit -1 "Mayhem score lead limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)" +set g_tmayhem_point_limit -1 "Team Mayhem score limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)" +set g_tmayhem_point_leadlimit -1 "Team Mayhem score lead limit overriding the mapinfo specified one (use 0 to play without limit, and -1 to use the mapinfo's limit)" + +set g_mayhem_weaponarena "most_available" "starting weapons - takes the same options as g_weaponarena" +set g_tmayhem_weaponarena "most_available" "starting weapons - takes the same options as g_weaponarena" + +set g_mayhem_powerups 1 "Allow powerups in mayhem. Only checked if g_powerups is -1 therefore this will be overridden by g_powerups 1 or 0" +set g_tmayhem_powerups 1 "Allow powerups in team mayhem. Only checked if g_powerups is -1 therefore this will be overridden by g_powerups 1 or 0" +set g_mayhem_pickup_items 0 "spawn pickup items in mayhem" +set g_tmayhem_pickup_items 0 "spawn pickup items in team mayhem" +set g_mayhem_pickup_items_remove_weapons_and_ammo 1 "when pickup items are enabled in mayhem still remove weapons and ammo pickups" +set g_tmayhem_pickup_items_remove_weapons_and_ammo 1 "when pickup items are enabled in team mayhem still remove weapons and ammo pickups" + +set g_mayhem_selfdamage 0 "0 = disable selfdamage in mayhem, 1 = enable selfdamage in mayhem" +set g_tmayhem_selfdamage 0 "0 = disable selfdamage in tmayhem, 1 = enable selfdamage in tmayhem" + +set g_mayhem_regenerate 0 "health and/or armor regeneration, according to g_balance_health_regen and g_balance_armor_regen" +set g_tmayhem_regenerate 0 "health and/or armor regeneration, according to g_balance_health_regen and g_balance_armor_regen" +set g_mayhem_rot 0 "health and/or armor rotting, according to g_balance_health_rot and g_balance_armor_rot" +set g_tmayhem_rot 0 "health and/or armor rotting, according to g_balance_health_rot and g_balance_armor_rot" + +set g_tmayhem_teams 2 "how many teams are in team mayhem (set by mapinfo)" +set g_tmayhem_team_spawns 0 "when 1, players spawn from the team spawnpoints of the map, if any" +set g_tmayhem_teams_override 0 "how many teams are in team mayhem" diff --git a/qcsrc/common/gamemodes/gamemode/_mod.inc b/qcsrc/common/gamemodes/gamemode/_mod.inc index a33ec87a0..b106ec9ac 100644 --- a/qcsrc/common/gamemodes/gamemode/_mod.inc +++ b/qcsrc/common/gamemodes/gamemode/_mod.inc @@ -12,7 +12,9 @@ #include #include #include +#include #include #include #include #include +#include \ No newline at end of file diff --git a/qcsrc/common/gamemodes/gamemode/_mod.qh b/qcsrc/common/gamemodes/gamemode/_mod.qh index ffd71d59d..15b6ecaac 100644 --- a/qcsrc/common/gamemodes/gamemode/_mod.qh +++ b/qcsrc/common/gamemodes/gamemode/_mod.qh @@ -12,7 +12,9 @@ #include #include #include +#include #include #include #include #include +#include \ No newline at end of file diff --git a/qcsrc/common/gamemodes/gamemode/mayhem/_mod.inc b/qcsrc/common/gamemodes/gamemode/mayhem/_mod.inc new file mode 100644 index 000000000..37daedbee --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/mayhem/_mod.inc @@ -0,0 +1,5 @@ +// generated file; do not modify +#include +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/gamemodes/gamemode/mayhem/_mod.qh b/qcsrc/common/gamemodes/gamemode/mayhem/_mod.qh new file mode 100644 index 000000000..abe19802e --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/mayhem/_mod.qh @@ -0,0 +1,5 @@ +// generated file; do not modify +#include +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/gamemodes/gamemode/mayhem/mayhem.qc b/qcsrc/common/gamemodes/gamemode/mayhem/mayhem.qc new file mode 100644 index 000000000..6af742888 --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/mayhem/mayhem.qc @@ -0,0 +1 @@ +#include "mayhem.qh" diff --git a/qcsrc/common/gamemodes/gamemode/mayhem/mayhem.qh b/qcsrc/common/gamemodes/gamemode/mayhem/mayhem.qh new file mode 100644 index 000000000..930e3067b --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/mayhem/mayhem.qh @@ -0,0 +1,35 @@ +#pragma once + +#include +#include +#include + +CLASS(mayhem, Gametype) + INIT(mayhem) + { + this.gametype_init(this, _("Mayhem"),"mayhem","g_mayhem",GAMETYPE_FLAG_USEPOINTS,"","timelimit=15 pointlimit=1000 leadlimit=0",_("Compete for the most damage dealt and frags in this chaotic mayhem!")); + } + METHOD(mayhem, m_isAlwaysSupported, bool(Gametype this, int spawnpoints, float diameter)) + { + return true; + } + METHOD(mayhem, m_isForcedSupported, bool(Gametype this)) + { + if(!(MapInfo_Map_supportedGametypes & this.m_flags) && (MapInfo_Map_supportedGametypes & MAPINFO_TYPE_DEATHMATCH.m_flags)) + { + return true; + } + if(!(MapInfo_Map_supportedGametypes & this.m_flags) && (MapInfo_Map_supportedGametypes & MAPINFO_TYPE_TEAM_DEATHMATCH.m_flags)) + { + return true; + } + return false; + } + METHOD(mayhem, 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:"), 200, 2000, 100, "g_mayhem_point_limit", string_null, _("How much score is needed before the match will end")); + } + ATTRIB(mayhem, m_legacydefaults, string, "1000 20 0"); +ENDCLASS(mayhem) +REGISTER_GAMETYPE(MAYHEM, NEW(mayhem)); diff --git a/qcsrc/common/gamemodes/gamemode/mayhem/sv_mayhem.qc b/qcsrc/common/gamemodes/gamemode/mayhem/sv_mayhem.qc new file mode 100644 index 000000000..7c4d09d73 --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/mayhem/sv_mayhem.qc @@ -0,0 +1,369 @@ +#include "sv_mayhem.qh" +#include + +// import autocvars for MayhemCalculatePlayerScore's teamplay cvars +#include "common/gamemodes/gamemode/tmayhem/sv_tmayhem.qh" + +float autocvar_g_mayhem_point_limit; +float autocvar_g_mayhem_point_leadlimit; + +bool autocvar_g_mayhem_regenerate; +bool autocvar_g_mayhem_rot; +string autocvar_g_mayhem_weaponarena; +bool autocvar_g_mayhem_powerups; +bool autocvar_g_mayhem_selfdamage; +float autocvar_g_mayhem_scoring_upscaler; +float autocvar_g_mayhem_scoring_damage_weight; +float autocvar_g_mayhem_scoring_kill_weight; +bool autocvar_g_mayhem_scoring_disable_selfdamage2score; +bool autocvar_g_mayhem_pickup_items; +bool autocvar_g_mayhem_pickup_items_remove_weapons_and_ammo; +bool autocvar_g_mayhem_unlimited_ammo; + +float autocvar_g_mayhem_start_health = 200; +float autocvar_g_mayhem_start_armor = 200; +float autocvar_g_mayhem_start_ammo_shells = 60; +float autocvar_g_mayhem_start_ammo_nails = 320; +float autocvar_g_mayhem_start_ammo_rockets = 160; +float autocvar_g_mayhem_start_ammo_cells = 180; +float autocvar_g_mayhem_start_ammo_plasma = 180; +float autocvar_g_mayhem_start_ammo_fuel = 0; + +.float total_damage_dealt; + +/* +// unused for now +void mayhem_DelayedInit(entity this) +{ + return; +} +*/ + +void mayhem_Initialize() +{ + GameRules_limit_score(autocvar_g_mayhem_point_limit); + GameRules_limit_lead(autocvar_g_mayhem_point_leadlimit); + + // unused for now + //InitializeEntity(NULL, mayhem_DelayedInit, INITPRIO_GAMETYPE); +} + +/* +MUTATOR_HOOKFUNCTION(mayhem, Scores_CountFragsRemaining) +{ + // do not announce remaining frags, upscaled score count doesn't match well with this + // when scorelimit is set to 1000 it would announce 997, 998 and 999 score counts + // usually a single shot which deals ~40-80 dmg gives 2 or 3 score + // this usually would cause a "2 fra..." announcement to be played as the match ends + // without leaving anyone time to even process the announcement + return false; +} +*/ + +MUTATOR_HOOKFUNCTION(mayhem, SetStartItems) +{ + start_items &= ~(IT_UNLIMITED_AMMO | IT_UNLIMITED_SUPERWEAPONS); + if (!cvar("g_use_ammunition") || autocvar_g_mayhem_unlimited_ammo) + start_items |= IT_UNLIMITED_AMMO; + + start_health = warmup_start_health = autocvar_g_mayhem_start_health; + start_armorvalue = warmup_start_armorvalue = autocvar_g_mayhem_start_armor; + start_ammo_shells = warmup_start_ammo_shells = autocvar_g_mayhem_start_ammo_shells; + start_ammo_nails = warmup_start_ammo_nails = autocvar_g_mayhem_start_ammo_nails; + start_ammo_rockets = warmup_start_ammo_rockets = autocvar_g_mayhem_start_ammo_rockets; + start_ammo_cells = warmup_start_ammo_cells = autocvar_g_mayhem_start_ammo_cells; + start_ammo_plasma = warmup_start_ammo_plasma = autocvar_g_mayhem_start_ammo_plasma; + start_ammo_fuel = warmup_start_ammo_fuel = autocvar_g_mayhem_start_ammo_fuel; +} + +MUTATOR_HOOKFUNCTION(mayhem, PlayerRegen) +{ + if(!autocvar_g_mayhem_regenerate) + M_ARGV(2, float) = 0; + if(!autocvar_g_mayhem_rot) + M_ARGV(3, float) = 0; + return (!autocvar_g_mayhem_regenerate && !autocvar_g_mayhem_rot); +} + +MUTATOR_HOOKFUNCTION(mayhem, ForbidThrowCurrentWeapon) +{ + return true; +} + +MUTATOR_HOOKFUNCTION(mayhem, SetWeaponArena) +{ + if (M_ARGV(0, string) == "0" || M_ARGV(0, string) == "") + M_ARGV(0, string) = autocvar_g_mayhem_weaponarena; +} + +MUTATOR_HOOKFUNCTION(mayhem, FilterItem) +{ + entity item = M_ARGV(0, entity); + + // enable powerups if forced globally or global accepts gamemodes to have powerups according to their own settings + if (autocvar_g_powerups == 1 || (autocvar_g_powerups == -1 && autocvar_g_mayhem_powerups == 1)){ + if (item.itemdef.instanceOfPowerup){ + return false; + } + } + // disabled powerups if forced off globally or in this gamemode + if (autocvar_g_powerups == 0 || autocvar_g_mayhem_powerups == 0){ + if (item.itemdef.instanceOfPowerup){ + return true; + } + } + // remove all items if items are forced off globally + if (autocvar_g_pickup_items == 0){ + return true; + } + // if items are switched on in this gamemode allow the removal of weapons and ammo still + if ((autocvar_g_mayhem_pickup_items == 1 && autocvar_g_mayhem_pickup_items_remove_weapons_and_ammo == 1) && autocvar_g_pickup_items <= 0){ + if (item.itemdef.instanceOfAmmo || item.itemdef.instanceOfWeaponPickup){ + return true; + } + } + // remove items if not globally set to follow mode's settings and locally set off + if (autocvar_g_pickup_items == -1 && autocvar_g_mayhem_pickup_items == 0){ + return true; + } + return false; +} + +MUTATOR_HOOKFUNCTION(mayhem, Damage_Calculate) +{ + entity frag_attacker = M_ARGV(1, entity); + entity frag_target = M_ARGV(2, entity); + float frag_deathtype = M_ARGV(3, float); + float frag_damage = M_ARGV(4, float); + + if (IS_PLAYER(frag_target)) // nullify self-damage if self-damage is disabled and always nullify splat + if (!IS_DEAD(frag_target)) // but enable anyone to gib corpses, even their own corpses with delayed damage + if ((autocvar_g_mayhem_selfdamage == 0 && frag_target == frag_attacker) || frag_deathtype == DEATH_FALL.m_id) + frag_damage = 0; + + M_ARGV(4, float) = frag_damage; +} + +void MayhemCalculatePlayerScore(entity scorer) +{ + int scoringmethod = 1; + float upscaler; // how much score does 1 frag give + float frag_weight; // how many frags should a kill be worth + float damage_weight; // how many frags is damage worth of player's spawn health+armor + + // if frag is 0.25 , damage is 0.75 and upscaler is 20 + // then killing a full hp opponent is 1 frag which is worth of 20 score + // or damage worth of 2 starting health+armor is 1.5 frags or 30 score even if no kills were gotten + + bool disable_selfdamage2score; + + if (teamplay) { + // use Team Mayhem values + upscaler = autocvar_g_tmayhem_scoring_upscaler; + frag_weight = autocvar_g_tmayhem_scoring_kill_weight; + damage_weight = autocvar_g_tmayhem_scoring_damage_weight; + disable_selfdamage2score = autocvar_g_tmayhem_scoring_disable_selfdamage2score; + } else { + // use FFA Mayhem values + upscaler = autocvar_g_mayhem_scoring_upscaler; + frag_weight = autocvar_g_mayhem_scoring_kill_weight; + damage_weight = autocvar_g_mayhem_scoring_damage_weight; + disable_selfdamage2score = autocvar_g_mayhem_scoring_disable_selfdamage2score; + } + + // decide scoringmethod and avoid potential divide by 0 errors + + if (frag_weight && damage_weight) // both frag and damage weights have non-zero values + scoringmethod = 1; + else if (frag_weight) // frag weight has a value + scoringmethod = 2; + else if (damage_weight) // damage weight has a value + scoringmethod = 3; + else + return; // neither frags nor damage are set to give score + + switch (scoringmethod) + { + default: + case 1: + { + // calculate how much score the player should have based on their damage dealt and frags gotten and then add the missing score + + // give a different weight for suicides if scoring method 1 doesn't have selfdamage2score enabled to harshly punish for suicides to avoid exploiting + float suicide_weight = 1 + (disable_selfdamage2score / frag_weight); + + // total damage divided by player start health&armor to get how many lives worth of damage they've dealt, + // then calculate new value affected by weight + float playerdamagescore = ((scorer.total_damage_dealt / (start_health + start_armorvalue)) * 100) * upscaler * damage_weight; + // * 100 to avoid float inaccuracy at that decimal level + + // playerdamagescore rounded to one decimal + float roundedplayerdamagescore = rint(playerdamagescore * 10) / 10; + + // amount of kills + float killcount = PlayerScore_Get(scorer, SP_KILLS) - PlayerScore_Get(scorer, SP_TEAMKILLS) - (PlayerScore_Get(scorer, SP_SUICIDES) * suicide_weight); + + // kills minus suicides, calculate weight + float playerkillscore = (killcount * 100) * upscaler * frag_weight; + // * 100 to avoid float inaccuracy at that decimal level + + float playerscore = roundedplayerdamagescore + playerkillscore; + + // calculated how much score the player has and now calculate total of how much they are supposed to have + float scoretoadd = playerscore - (PlayerScore_Get(scorer, SP_SCORE) * 100); + // * 100 to avoid float inaccuracy at that decimal level + + // adjust total score to be what the player is supposed to have + GameRules_scoring_add_team(scorer, SCORE, floor(scoretoadd / 100)); + // / 100 to move back to the original decimal level + + #if 0 + // debug printing + if(!IS_BOT_CLIENT(scorer)){ + print(sprintf("%f", scorer.total_damage_dealt), " scorer.total_damage_dealt \n"); + print(sprintf("%f", scorer.hitsound_damage_dealt), " scorer.hitsound_damage_dealt \n"); + print(sprintf("%f", playerdamagescore/100), " playerdamagescore \n"); + print(sprintf("%f", roundedplayerdamagescore/100), " rounded playerdamagescore \n"); + print(sprintf("%f", playerkillscore/100), " playerkillscore \n"); + print(sprintf("%f", PlayerScore_Get(scorer, SP_KILLS)), " PlayerScore_Get(scorer, SP_KILLS) \n"); + print(sprintf("%f", playerscore/100), " playerscore \n"); + print(sprintf("%f", scoretoadd/100), " scoretoadd \n"); + print(sprintf("%f", PlayerScore_Get(scorer, SP_SCORE)), " PlayerScore_Get(scorer, SP_SCORE) \n \n"); + } + #endif + return; + } + + case 2: + { + // calculate how much score the player should have based on their frags gotten and then add the missing score + float playerkillscore = PlayerScore_Get(scorer, SP_KILLS) - PlayerScore_Get(scorer, SP_TEAMKILLS) - PlayerScore_Get(scorer, SP_SUICIDES); + float upscaledplayerscore = playerkillscore * upscaler; + float scoretoadd = upscaledplayerscore - PlayerScore_Get(scorer, SP_SCORE); + GameRules_scoring_add_team(scorer, SCORE, floor(scoretoadd)); + + #if 0 + // debug printing + if(!IS_BOT_CLIENT(scorer)){ + print(sprintf("%f", playerkillscore), " playerkillscore \n"); + print(sprintf("%f", PlayerScore_Get(scorer, SP_KILLS)), " PlayerScore_Get(scorer, SP_KILLS) \n"); + print(sprintf("%f", upscaledplayerscore), " upscaled playerscore \n"); + print(sprintf("%f", scoretoadd), " scoretoadd \n"); + print(sprintf("%f", PlayerScore_Get(scorer, SP_SCORE)), " PlayerScore_Get(scorer, SP_SCORE) \n \n"); + } + #endif + return; + } + + case 3: + { + // calculate how much score the player should have based on their damage dealt and then add the missing score + float playerdamagescore = ((scorer.total_damage_dealt / (start_health + start_armorvalue)) * 100); + float roundedplayerdamagescore = rint(playerdamagescore * 10) / 10; + float upscaledplayerscore = roundedplayerdamagescore * upscaler; + float scoretoadd = upscaledplayerscore - (PlayerScore_Get(scorer, SP_SCORE) * 100); + GameRules_scoring_add_team(scorer, SCORE, floor(scoretoadd / 100)); + + #if 0 + // debug printing + if(!IS_BOT_CLIENT(scorer)){ + print(sprintf("%f", scorer.total_damage_dealt), " scorer.total_damage_dealt \n"); + print(sprintf("%f", playerdamagescore), " playerdamagescore \n"); + print(sprintf("%f", roundedplayerdamagescore), " rounded playerdamagescore \n"); + print(sprintf("%f", upscaledplayerscore), " upscaled playerscore \n"); + print(sprintf("%f", scoretoadd), " scoretoadd \n"); + print(sprintf("%f", PlayerScore_Get(scorer, SP_SCORE)), " PlayerScore_Get(scorer, SP_SCORE) \n \n"); + } + #endif + return; + } + } +} + +MUTATOR_HOOKFUNCTION(mayhem, PlayerDamage_SplitHealthArmor) +{ + if (!autocvar_g_mayhem_scoring_damage_weight) return; + + entity frag_target = M_ARGV(2, entity); + + if (StatusEffects_active(STATUSEFFECT_SpawnShield, frag_target) && autocvar_g_spawnshield_blockdamage >= 1) return; + + entity frag_attacker = M_ARGV(1, entity); + float frag_deathtype = M_ARGV(6, float); + float frag_damage = M_ARGV(7, float); + float damage_take = bound(0, M_ARGV(4, float), GetResource(frag_target, RES_HEALTH)); + float damage_save = bound(0, M_ARGV(5, float), GetResource(frag_target, RES_ARMOR)); + float excess = max(0, frag_damage - damage_take - damage_save); + float total = frag_damage - excess; + + if (total == 0) return; + + if (StatusEffects_active(STATUSEFFECT_SpawnShield, frag_target) && autocvar_g_spawnshield_blockdamage) + total *= 1 - autocvar_g_spawnshield_blockdamage; + + entity scorer = NULL; // entity which needs their score to be updated + + if (IS_PLAYER(frag_attacker)) + { + // non-friendly fire + if (frag_target != frag_attacker) + frag_attacker.total_damage_dealt += total; + + // friendly fire aka self damage + if (frag_target == frag_attacker && !autocvar_g_mayhem_scoring_disable_selfdamage2score) + frag_attacker.total_damage_dealt -= total; + + scorer = frag_attacker; + } + else + { + // handle (environmental hazard) suiciding, check first if the player + // has a registered attacker who most likely pushed them there to + // avoid punishing pushed players as pushers are already rewarded + // deathtypes: + // kill = suicide, drown = drown in water/liquid, hurttrigger = out of the map void or hurt triggers inside maps like electric sparks + // camp = campcheck, lava = lava, slime = slime + // team change / rebalance suicides are currently not included + if (!autocvar_g_mayhem_scoring_disable_selfdamage2score && ( + frag_deathtype == DEATH_KILL.m_id || + frag_deathtype == DEATH_DROWN.m_id || + frag_deathtype == DEATH_HURTTRIGGER.m_id || + frag_deathtype == DEATH_CAMP.m_id || + frag_deathtype == DEATH_LAVA.m_id || + frag_deathtype == DEATH_SLIME.m_id || + frag_deathtype == DEATH_SWAMP.m_id)) + frag_target.total_damage_dealt -= total; + + scorer = frag_target; + } + + #if 0 + // debug printing + if(!IS_BOT_CLIENT(scorer)){ + print(sprintf("%f", total), " total dmg from PlayerDamage_SplitHealthArmor \n"); + print(sprintf("%f", scorer.total_damage_dealt), "scorer.total_damage_dealt\n"); + } + #endif + + MayhemCalculatePlayerScore(scorer); +} + +MUTATOR_HOOKFUNCTION(mayhem, GiveFragsForKill, CBC_ORDER_FIRST) +{ + entity frag_attacker = M_ARGV(0, entity); + M_ARGV(2, float) = 0; //score to give for the frag directly + + if (IS_PLAYER(frag_attacker)) MayhemCalculatePlayerScore(frag_attacker); + + return true; +} + +MUTATOR_HOOKFUNCTION(mayhem, reset_map_players) +{ + // reset damage dealt on reset + FOREACH_CLIENT(true, { + it.total_damage_dealt = 0; + }); + return false; +} + diff --git a/qcsrc/common/gamemodes/gamemode/mayhem/sv_mayhem.qh b/qcsrc/common/gamemodes/gamemode/mayhem/sv_mayhem.qh new file mode 100644 index 000000000..ab4ae5c0b --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/mayhem/sv_mayhem.qh @@ -0,0 +1,17 @@ +#pragma once + +#include + +void mayhem_Initialize(); + +REGISTER_MUTATOR(mayhem, false) +{ + MUTATOR_STATIC(); + MUTATOR_ONADD + { + mayhem_Initialize(); + } + return 0; +} + +void MayhemCalculatePlayerScore(entity scorer); diff --git a/qcsrc/common/gamemodes/gamemode/tmayhem/_mod.inc b/qcsrc/common/gamemodes/gamemode/tmayhem/_mod.inc new file mode 100644 index 000000000..e78eb59b1 --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/tmayhem/_mod.inc @@ -0,0 +1,5 @@ +// generated file; do not modify +#include +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/gamemodes/gamemode/tmayhem/_mod.qh b/qcsrc/common/gamemodes/gamemode/tmayhem/_mod.qh new file mode 100644 index 000000000..261392346 --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/tmayhem/_mod.qh @@ -0,0 +1,5 @@ +// generated file; do not modify +#include +#ifdef SVQC + #include +#endif diff --git a/qcsrc/common/gamemodes/gamemode/tmayhem/sv_tmayhem.qc b/qcsrc/common/gamemodes/gamemode/tmayhem/sv_tmayhem.qc new file mode 100644 index 000000000..761c6e10a --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/tmayhem/sv_tmayhem.qc @@ -0,0 +1,259 @@ +#include "sv_tmayhem.qh" +#include "common/gamemodes/gamemode/mayhem/sv_mayhem.qh" + +float autocvar_g_tmayhem_point_limit; +float autocvar_g_tmayhem_point_leadlimit; + +int autocvar_g_tmayhem_teams; +int autocvar_g_tmayhem_teams_override; +bool autocvar_g_tmayhem_team_spawns; + +bool autocvar_g_tmayhem_regenerate; +bool autocvar_g_tmayhem_rot; +string autocvar_g_tmayhem_weaponarena; +bool autocvar_g_tmayhem_powerups; +bool autocvar_g_tmayhem_selfdamage; +bool autocvar_g_tmayhem_pickup_items; +bool autocvar_g_tmayhem_pickup_items_remove_weapons_and_ammo; +bool autocvar_g_tmayhem_unlimited_ammo; + +float autocvar_g_tmayhem_start_health = 200; +float autocvar_g_tmayhem_start_armor = 200; +float autocvar_g_tmayhem_start_ammo_shells = 60; +float autocvar_g_tmayhem_start_ammo_nails = 320; +float autocvar_g_tmayhem_start_ammo_rockets = 160; +float autocvar_g_tmayhem_start_ammo_cells = 180; +float autocvar_g_tmayhem_start_ammo_plasma = 180; +float autocvar_g_tmayhem_start_ammo_fuel = 0; + +.float total_damage_dealt; + +// code from here on is just to support maps that don't have team entities +void tmayhem_SpawnTeam (string teamname, int teamcolor) +{ + entity this = new_pure(tmayhem_team); + this.netname = teamname; + this.cnt = teamcolor - 1; + this.team = teamcolor; + this.spawnfunc_checked = true; + //spawnfunc_tmayhem_team(this); +} + +void tmayhem_DelayedInit(entity this) +{ + // if no teams are found, spawn defaults + if (find(NULL, classname, "tmayhem_team") == NULL) + { + LOG_TRACE("No \"tmayhem_team\" entities found on this map, creating them anyway."); + + int numteams = autocvar_g_tmayhem_teams_override; + if(numteams < 2) { numteams = autocvar_g_tmayhem_teams; } + + int teams = BITS(bound(2, numteams, 4)); + if(teams & BIT(0)) + tmayhem_SpawnTeam("Red", NUM_TEAM_1); + if(teams & BIT(1)) + tmayhem_SpawnTeam("Blue", NUM_TEAM_2); + if(teams & BIT(2)) + tmayhem_SpawnTeam("Yellow", NUM_TEAM_3); + if(teams & BIT(3)) + tmayhem_SpawnTeam("Pink", NUM_TEAM_4); + } +} + +void tmayhem_Initialize() +{ + GameRules_teams(true); + GameRules_spawning_teams(autocvar_g_tmayhem_team_spawns); + + GameRules_limit_score(autocvar_g_tmayhem_point_limit); + GameRules_limit_lead(autocvar_g_tmayhem_point_leadlimit); + + InitializeEntity(NULL, tmayhem_DelayedInit, INITPRIO_GAMETYPE); +} +// code up to here is just to support maps that don't have team entities + +MUTATOR_HOOKFUNCTION(tmayhem, TeamBalance_CheckAllowedTeams, CBC_ORDER_EXCLUSIVE) +{ + M_ARGV(1, string) = "tmayhem_team"; +} + +/* +MUTATOR_HOOKFUNCTION(tmayhem, Scores_CountFragsRemaining) +{ + // do not announce remaining frags, upscaled score count doesn't match well with this + // when scorelimit is set to 1000 it would announce 997, 998 and 999 score counts + // usually a single shot which deals ~40-80 dmg gives 2 or 3 score + // this usually would cause a "2 fra..." announcement to be played as the match ends + // without leaving anyone time to even process the announcement + return false; +} +*/ + +MUTATOR_HOOKFUNCTION(tmayhem, SetStartItems) +{ + start_items &= ~(IT_UNLIMITED_AMMO | IT_UNLIMITED_SUPERWEAPONS); + if (!cvar("g_use_ammunition") || autocvar_g_tmayhem_unlimited_ammo) + start_items |= IT_UNLIMITED_AMMO; + + start_health = warmup_start_health = autocvar_g_tmayhem_start_health; + start_armorvalue = warmup_start_armorvalue = autocvar_g_tmayhem_start_armor; + start_ammo_shells = warmup_start_ammo_shells = autocvar_g_tmayhem_start_ammo_shells; + start_ammo_nails = warmup_start_ammo_nails = autocvar_g_tmayhem_start_ammo_nails; + start_ammo_rockets = warmup_start_ammo_rockets = autocvar_g_tmayhem_start_ammo_rockets; + start_ammo_cells = warmup_start_ammo_cells = autocvar_g_tmayhem_start_ammo_cells; + start_ammo_plasma = warmup_start_ammo_plasma = autocvar_g_tmayhem_start_ammo_plasma; + start_ammo_fuel = warmup_start_ammo_fuel = autocvar_g_tmayhem_start_ammo_fuel; +} + +MUTATOR_HOOKFUNCTION(tmayhem, PlayerRegen) +{ + if(!autocvar_g_tmayhem_regenerate) + M_ARGV(2, float) = 0; + if(!autocvar_g_tmayhem_rot) + M_ARGV(3, float) = 0; + return (!autocvar_g_tmayhem_regenerate && !autocvar_g_tmayhem_rot); +} + +MUTATOR_HOOKFUNCTION(tmayhem, ForbidThrowCurrentWeapon) +{ + return true; +} + +MUTATOR_HOOKFUNCTION(tmayhem, SetWeaponArena) +{ + if (M_ARGV(0, string) == "0" || M_ARGV(0, string) == "") + M_ARGV(0, string) = autocvar_g_tmayhem_weaponarena; +} + +MUTATOR_HOOKFUNCTION(tmayhem, FilterItem) +{ + entity item = M_ARGV(0, entity); + + // enable powerups if forced globally or global accepts gamemodes to have powerups according to their own settings + if (autocvar_g_powerups == 1 || (autocvar_g_powerups == -1 && autocvar_g_tmayhem_powerups == 1)){ + if (item.itemdef.instanceOfPowerup){ + return false; + } + } + // disabled powerups if forced off globally or in this gamemode + if (autocvar_g_powerups == 0 || autocvar_g_tmayhem_powerups == 0){ + if (item.itemdef.instanceOfPowerup){ + return true; + } + } + // remove all items if items are forced off globally + if (autocvar_g_pickup_items == 0){ + return true; + } + // if items are switched on in this gamemode allow the removal of weapons and ammo still + if ((autocvar_g_tmayhem_pickup_items == 1 && autocvar_g_tmayhem_pickup_items_remove_weapons_and_ammo == 1) && autocvar_g_pickup_items <= 0){ + if (item.itemdef.instanceOfAmmo || item.itemdef.instanceOfWeaponPickup){ + return true; + } + } + // remove items if not globally set to follow mode's settings and locally set off + if (autocvar_g_pickup_items == -1 && autocvar_g_tmayhem_pickup_items == 0){ + return true; + } + return false; +} + +MUTATOR_HOOKFUNCTION(tmayhem, Damage_Calculate) +{ + entity frag_attacker = M_ARGV(1, entity); + entity frag_target = M_ARGV(2, entity); + float frag_deathtype = M_ARGV(3, float); + float frag_damage = M_ARGV(4, float); + float frag_mirrordamage = M_ARGV(5, float); + + if (IS_PLAYER(frag_target)) // nullify self-damage if self-damage is disabled and always nullify splat + if (!IS_DEAD(frag_target)) // but enable anyone to gib corpses, even their own corpses with delayed damage + if ((autocvar_g_tmayhem_selfdamage == 0 && frag_target == frag_attacker) || frag_deathtype == DEATH_FALL.m_id) + frag_damage = 0; + + frag_mirrordamage = 0; // no mirror damaging + + M_ARGV(4, float) = frag_damage; + M_ARGV(5, float) = frag_mirrordamage; +} + +MUTATOR_HOOKFUNCTION(tmayhem, PlayerDamage_SplitHealthArmor) +{ + if (!autocvar_g_tmayhem_scoring_damage_weight) return; + + entity frag_target = M_ARGV(2, entity); + + if (StatusEffects_active(STATUSEFFECT_SpawnShield, frag_target) && autocvar_g_spawnshield_blockdamage >= 1) return; + + entity frag_attacker = M_ARGV(1, entity); + float frag_deathtype = M_ARGV(6, float); + float frag_damage = M_ARGV(7, float); + float damage_take = bound(0, M_ARGV(4, float), GetResource(frag_target, RES_HEALTH)); + float damage_save = bound(0, M_ARGV(5, float), GetResource(frag_target, RES_ARMOR)); + float excess = max(0, frag_damage - damage_take - damage_save); + float total = frag_damage - excess; + + if (total == 0) return; + + if (StatusEffects_active(STATUSEFFECT_SpawnShield, frag_target) && autocvar_g_spawnshield_blockdamage) + total *= 1 - autocvar_g_spawnshield_blockdamage; + + entity scorer = NULL; // entity which needs their score to be updated + + if (IS_PLAYER(frag_attacker)) + { + // non-friendly fire + if (!SAME_TEAM(frag_target, frag_attacker)) + frag_attacker.total_damage_dealt += total; + + // friendly fire aka self damage + if (SAME_TEAM(frag_target, frag_attacker) || (frag_target == frag_attacker && !autocvar_g_tmayhem_scoring_disable_selfdamage2score)) + frag_attacker.total_damage_dealt -= total; + + scorer = frag_attacker; + } + else + { + // handle (environmental hazard) suiciding, check first if the player + // has a registered attacker who most likely pushed them there to + // avoid punishing pushed players as pushers are already rewarded + // deathtypes: + // kill = suicide, drown = drown in water/liquid, hurttrigger = out of the map void or hurt triggers inside maps like electric sparks + // camp = campcheck, lava = lava, slime = slime + // team change / rebalance suicides are currently not included + if (!autocvar_g_tmayhem_scoring_disable_selfdamage2score && ( + frag_deathtype == DEATH_KILL.m_id || + frag_deathtype == DEATH_DROWN.m_id || + frag_deathtype == DEATH_HURTTRIGGER.m_id || + frag_deathtype == DEATH_CAMP.m_id || + frag_deathtype == DEATH_LAVA.m_id || + frag_deathtype == DEATH_SLIME.m_id || + frag_deathtype == DEATH_SWAMP.m_id)) + frag_target.total_damage_dealt -= total; + + scorer = frag_target; + } + + MayhemCalculatePlayerScore(scorer); +} + +MUTATOR_HOOKFUNCTION(tmayhem, GiveFragsForKill, CBC_ORDER_FIRST) +{ + entity frag_attacker = M_ARGV(0, entity); + M_ARGV(2, float) = 0; // score to give for the frag directly + + if (IS_PLAYER(frag_attacker)) MayhemCalculatePlayerScore(frag_attacker); + + return true; +} + +MUTATOR_HOOKFUNCTION(tmayhem, reset_map_players) +{ + // reset damage dealt on reset + FOREACH_CLIENT(true, { + it.total_damage_dealt = 0; + }); + return false; +} + diff --git a/qcsrc/common/gamemodes/gamemode/tmayhem/sv_tmayhem.qh b/qcsrc/common/gamemodes/gamemode/tmayhem/sv_tmayhem.qh new file mode 100644 index 000000000..d05727e8d --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/tmayhem/sv_tmayhem.qh @@ -0,0 +1,21 @@ +#pragma once + +#include + +// autocvars defined here so they can be imported in FFA sv_mayhem.qc +float autocvar_g_tmayhem_scoring_upscaler; +float autocvar_g_tmayhem_scoring_kill_weight; +float autocvar_g_tmayhem_scoring_damage_weight; +bool autocvar_g_tmayhem_scoring_disable_selfdamage2score; + +void tmayhem_Initialize(); + +REGISTER_MUTATOR(tmayhem, false) +{ + MUTATOR_STATIC(); + MUTATOR_ONADD + { + tmayhem_Initialize(); + } + return 0; +} diff --git a/qcsrc/common/gamemodes/gamemode/tmayhem/tmayhem.qc b/qcsrc/common/gamemodes/gamemode/tmayhem/tmayhem.qc new file mode 100644 index 000000000..b05860a8c --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/tmayhem/tmayhem.qc @@ -0,0 +1 @@ +#include "tmayhem.qh" diff --git a/qcsrc/common/gamemodes/gamemode/tmayhem/tmayhem.qh b/qcsrc/common/gamemodes/gamemode/tmayhem/tmayhem.qh new file mode 100644 index 000000000..6e37f666f --- /dev/null +++ b/qcsrc/common/gamemodes/gamemode/tmayhem/tmayhem.qh @@ -0,0 +1,51 @@ +#pragma once + +#include +#include +#include + +CLASS(tmayhem, Gametype) + INIT(tmayhem) + { + this.gametype_init(this, _("Team Mayhem"),"tmayhem","g_tmayhem",GAMETYPE_FLAG_TEAMPLAY | GAMETYPE_FLAG_USEPOINTS,"","timelimit=20 pointlimit=1500 teams=2 leadlimit=0",_("Compete with your team for the most damage dealt and frags in this chaotic mayhem!")); + } + METHOD(tmayhem, m_parse_mapinfo, bool(string k, string v)) + { + if (!k) { + cvar_set("g_tmayhem_teams", cvar_defstring("g_tmayhem_teams")); + return true; + } + switch (k) { + case "teams": + cvar_set("g_tmayhem_teams", v); + return true; + } + return false; + } + METHOD(tmayhem, m_isAlwaysSupported, bool(Gametype this, int spawnpoints, float diameter)) + { + return true; + } + METHOD(tmayhem, m_isForcedSupported, bool(Gametype this)) + { + if(!(MapInfo_Map_supportedGametypes & this.m_flags) && (MapInfo_Map_supportedGametypes & MAPINFO_TYPE_DEATHMATCH.m_flags)){ + return true; + } + if(!(MapInfo_Map_supportedGametypes & this.m_flags) && (MapInfo_Map_supportedGametypes & MAPINFO_TYPE_TEAM_DEATHMATCH.m_flags)){ + return true; + } + return false; + } + METHOD(tmayhem, m_setTeams, void(string sa)) + { + cvar_set("g_tmayhem_teams", sa); + } + METHOD(tmayhem, 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:"), 200, 3000, 100, "g_tmayhem_point_limit", "g_tmayhem_teams_override", _("How much score is needed before the match will end")); + } + ATTRIB(tmayhem, m_legacydefaults, string, "1500 20 2 0"); +ENDCLASS(tmayhem) +REGISTER_GAMETYPE(TEAM_MAYHEM, NEW(tmayhem)); +#define g_tmayhem IS_GAMETYPE(TEAM_MAYHEM) diff --git a/qcsrc/menu/xonotic/util.qc b/qcsrc/menu/xonotic/util.qc index d8640a501..86b0f5e87 100644 --- a/qcsrc/menu/xonotic/util.qc +++ b/qcsrc/menu/xonotic/util.qc @@ -653,6 +653,8 @@ float updateCompression() GAMETYPE(MAPINFO_TYPE_CTF) \ GAMETYPE(MAPINFO_TYPE_CA) \ GAMETYPE(MAPINFO_TYPE_FREEZETAG) \ + GAMETYPE(MAPINFO_TYPE_TEAM_MAYHEM) \ + GAMETYPE(MAPINFO_TYPE_MAYHEM) \ GAMETYPE(MAPINFO_TYPE_KEEPAWAY) \ GAMETYPE(MAPINFO_TYPE_KEYHUNT) \ GAMETYPE(MAPINFO_TYPE_LMS) \ diff --git a/qcsrc/server/world.qc b/qcsrc/server/world.qc index f893c337b..c04b188fa 100644 --- a/qcsrc/server/world.qc +++ b/qcsrc/server/world.qc @@ -285,6 +285,7 @@ void cvar_changes_init() BADCVAR("g_keyhunt"); BADCVAR("g_keyhunt_teams"); BADCVAR("g_lms"); + BADCVAR("g_mayhem"); BADCVAR("g_nexball"); BADCVAR("g_onslaught"); BADCVAR("g_race"); @@ -299,6 +300,8 @@ void cvar_changes_init() BADCVAR("g_tdm"); BADCVAR("g_tdm_on_dm_maps"); BADCVAR("g_tdm_teams"); + BADCVAR("g_tmayhem"); + BADCVAR("g_tmayhem_teams"); BADCVAR("g_vip"); BADCVAR("leadlimit"); BADCVAR("nextmap"); @@ -376,6 +379,10 @@ void cvar_changes_init() BADCVAR("g_spawn_alloweffects"); BADCVAR("g_tdm_point_leadlimit"); BADCVAR("g_tdm_point_limit"); + BADCVAR("g_mayhem_point_limit"); + BADCVAR("g_mayhem_point_leadlimit"); + BADCVAR("g_tmayhem_point_limit"); + BADCVAR("g_tmayhem_point_leadlimit"); BADCVAR("leadlimit_and_fraglimit"); BADCVAR("leadlimit_override"); BADCVAR("pausable"); @@ -453,6 +460,7 @@ void cvar_changes_init() BADCVAR("g_keyhunt_point_limit"); BADCVAR("g_keyhunt_teams_override"); BADCVAR("g_lms_lives_override"); + BADCVAR("g_mayhem_powerups"); BADCVAR("g_maplist"); BADCVAR("g_maxplayers"); BADCVAR("g_mirrordamage"); @@ -469,6 +477,8 @@ void cvar_changes_init() BADCVAR("g_start_delay"); BADCVAR("g_superspectate"); BADCVAR("g_tdm_teams_override"); + BADCVAR("g_tmayhem_teams_override"); + BADCVAR("g_tmayhem_powerups"); BADCVAR("g_weapon_stay"); BADPRESUFFIX("g_", "_weapon_stay"); BADCVAR("hostname"); BADCVAR("log_file"); -- 2.39.2