]> git.rm.cloudns.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Survival gamemode: Identify and eliminate all the hunters before all your allies...
authorMario <mario.mario@y7mail.com>
Sun, 12 Jul 2020 14:48:21 +0000 (00:48 +1000)
committerMario <mario.mario@y7mail.com>
Sun, 12 Jul 2020 14:48:21 +0000 (00:48 +1000)
28 files changed:
gamemodes-client.cfg
gamemodes-server.cfg
gfx/menu/luma/gametype_surv.tga [new file with mode: 0644]
gfx/menu/luminos/gametype_surv.tga [new file with mode: 0644]
gfx/menu/wickedx/gametype_surv.tga [new file with mode: 0644]
gfx/menu/xaw/gametype_surv.tga [new file with mode: 0644]
notifications.cfg
qcsrc/client/csqcmodel_hooks.qc
qcsrc/client/hud/panel/scoreboard.qc
qcsrc/client/mutators/events.qh
qcsrc/common/ent_cs.qc
qcsrc/common/gamemodes/gamemode/_mod.inc
qcsrc/common/gamemodes/gamemode/_mod.qh
qcsrc/common/gamemodes/gamemode/survival/_mod.inc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/survival/_mod.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/survival/cl_survival.qc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/survival/cl_survival.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/survival/survival.qc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/survival/survival.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/survival/sv_survival.qc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/survival/sv_survival.qh [new file with mode: 0644]
qcsrc/common/mapinfo.qh
qcsrc/common/notifications/all.inc
qcsrc/common/notifications/all.qh
qcsrc/menu/xonotic/util.qc
qcsrc/server/g_damage.qc
qcsrc/server/g_world.qc
qcsrc/server/mutators/events.qh

index c43b9d1d3f2e6fe73fddbb33e8489670350edc4d..a95ac6d6638161d669df830f3d3c07e858a89a6c 100644 (file)
@@ -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_surv
 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
index b1631b2333f56e6ba6ad1d9290fd89f0f77cddb8..94ace93e2ae61092660d3a9ec540c13e45ea4047 100644 (file)
@@ -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_surv
 // 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_surv
 
 // 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_surv_respawn_delay_small 0
+set g_surv_respawn_delay_small_count 0
+set g_surv_respawn_delay_large 0
+set g_surv_respawn_delay_large_count 0
+set g_surv_respawn_delay_max 0
+set g_surv_respawn_waves 0
+set g_surv_weapon_stay 0
 
 
 // =========
@@ -551,3 +560,14 @@ 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"
+
+// ==========
+//  survival
+// ==========
+set g_survival 0 "Survival: identify and eliminate all the hunters before all your allies are gone"
+set g_survival_not_dm_maps 0 "when this is set, DM maps will NOT be listed in survival"
+set g_survival_hunter_count 0.25 "number of players who will become hunters, set between 0 and 0.9 to use a multiplier of the current players, or 1 and above to specify an exact number of players"
+set g_survival_punish_teamkill 1 "kill the player when they kill an ally"
+set g_survival_reward_survival 1 "give a point to all surviving players if the round timelimit is reached, in addition to the points given for kills"
+set g_survival_warmup 10 "how long the players will have time to run around the map before the round starts"
+set g_survival_round_timelimit 180 "round time limit in seconds"
diff --git a/gfx/menu/luma/gametype_surv.tga b/gfx/menu/luma/gametype_surv.tga
new file mode 100644 (file)
index 0000000..e89e60b
Binary files /dev/null and b/gfx/menu/luma/gametype_surv.tga differ
diff --git a/gfx/menu/luminos/gametype_surv.tga b/gfx/menu/luminos/gametype_surv.tga
new file mode 100644 (file)
index 0000000..67a545d
Binary files /dev/null and b/gfx/menu/luminos/gametype_surv.tga differ
diff --git a/gfx/menu/wickedx/gametype_surv.tga b/gfx/menu/wickedx/gametype_surv.tga
new file mode 100644 (file)
index 0000000..67a545d
Binary files /dev/null and b/gfx/menu/wickedx/gametype_surv.tga differ
diff --git a/gfx/menu/xaw/gametype_surv.tga b/gfx/menu/xaw/gametype_surv.tga
new file mode 100644 (file)
index 0000000..bc72cd1
Binary files /dev/null and b/gfx/menu/xaw/gametype_surv.tga differ
index 41f0706ea3d72f95c1d2b4180c3a27d5dd6232ad..814dccc9fdd09fae423f64fdebbeda98be36a506 100644 (file)
@@ -282,6 +282,8 @@ seta notification_INFO_SCORES "1" "0 = off, 1 = print to console, 2 = print to c
 seta notification_INFO_SPECTATE_WARNING "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
 seta notification_INFO_SUPERSPEC_MISSING_UID "2" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
 seta notification_INFO_SUPERWEAPON_PICKUP "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
+seta notification_INFO_SURVIVAL_HUNTER_WIN "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
+seta notification_INFO_SURVIVAL_SUVIVOR_WIN "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
 seta notification_INFO_TEAMCHANGE_LARGERTEAM "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
 seta notification_INFO_TEAMCHANGE_NOTALLOWED "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
 seta notification_INFO_VERSION_BETA "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
@@ -532,6 +534,10 @@ seta notification_CENTER_SEQUENCE_COUNTER_FEWMORE "1" "0 = off, 1 = centerprint"
 seta notification_CENTER_SUPERWEAPON_BROKEN "1" "0 = off, 1 = centerprint"
 seta notification_CENTER_SUPERWEAPON_LOST "1" "0 = off, 1 = centerprint"
 seta notification_CENTER_SUPERWEAPON_PICKUP "1" "0 = off, 1 = centerprint"
+seta notification_CENTER_SURVIVAL_HUNTER "1" "0 = off, 1 = centerprint"
+seta notification_CENTER_SURVIVAL_HUNTER_WIN "1" "0 = off, 1 = centerprint"
+seta notification_CENTER_SURVIVAL_SURVIVOR "1" "0 = off, 1 = centerprint"
+seta notification_CENTER_SURVIVAL_SURVIVOR_WIN "1" "0 = off, 1 = centerprint"
 seta notification_CENTER_TEAMCHANGE_AUTO "1" "0 = off, 1 = centerprint"
 seta notification_CENTER_TEAMCHANGE "1" "0 = off, 1 = centerprint"
 seta notification_CENTER_TEAMCHANGE_SPECTATE "1" "0 = off, 1 = centerprint"
index 99456bd3ed03d129d2fb2ffc741e380e612f66ec..f08378b6a86b27546eed52fe92434375c9615b5b 100644 (file)
@@ -133,6 +133,9 @@ void CSQCPlayer_ModelAppearance_PostUpdate(entity this)
 }
 void CSQCPlayer_ModelAppearance_Apply(entity this, bool islocalplayer)
 {
+       int cm = this.forceplayermodels_savecolormap;
+       cm = (cm >= 1024) ? cm : (entcs_GetClientColors(cm - 1) + 1024);
+
        if(MUTATOR_CALLHOOK(ForcePlayermodels_Skip, this, islocalplayer))
                goto skipforcemodels;
 
@@ -193,9 +196,6 @@ void CSQCPlayer_ModelAppearance_Apply(entity this, bool islocalplayer)
 
        // apply it
        bool isfriend;
-       int cm;
-       cm = this.forceplayermodels_savecolormap;
-       cm = (cm >= 1024) ? cm : (entcs_GetClientColors(cm - 1) + 1024);
 
        if(teamplay)
                isfriend = (cm == 1024 + 17 * myteam);
@@ -227,6 +227,11 @@ void CSQCPlayer_ModelAppearance_Apply(entity this, bool islocalplayer)
                this.skin = this.forceplayermodels_saveskin;
        }
 
+       LABEL(skipforcemodels)
+
+       if(MUTATOR_CALLHOOK(ForcePlayercolors_Skip, this, islocalplayer))
+               goto skipforcecolors;
+
        // forceplayercolors too
        if(teamplay)
        {
@@ -280,7 +285,7 @@ void CSQCPlayer_ModelAppearance_Apply(entity this, bool islocalplayer)
                        this.colormap = player_localnum + 1;
        }
 
-       LABEL(skipforcemodels)
+       LABEL(skipforcecolors)
 
        if((this.csqcmodel_effects & CSQCMODEL_EF_RESPAWNGHOST) && !autocvar_cl_respawn_ghosts_keepcolors)
        {
index 120feeafa05cb858f0835c232b4e88f796e7ee88..9999680200584e8374fee0bd66ddd78de3537148 100644 (file)
@@ -364,13 +364,13 @@ void Cmd_Scoreboard_Help()
 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
 #define SCOREBOARD_DEFAULT_COLUMNS \
 "ping pl fps name |" \
-" -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
-" -teams,lms/deaths +ft,tdm/deaths" \
+" -teams,rc,cts,inv,lms,surv/kills +ft,tdm/kills ?+rc,inv/kills" \
+" -teams,lms,surv/deaths +ft,tdm/deaths" \
 " +tdm/sum" \
-" -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
-" -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
+" -teams,lms,rc,cts,inv,ka,surv/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
+" -cts,dm,tdm,ka,ft,surv/frags" /* tdm already has this in "score" */ \
 " +tdm,ft,dom,ons,as/teamkills"\
-" -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
+" -rc,cts,nb,surv/dmg -rc,cts,nb,surv/dmgtaken" \
 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
 " +lms/lives +lms/rank" \
 " +kh/kckills +kh/losses +kh/caps" \
index 0629c2a9f0dd17a7897c73f6ff68dc818748c4ac..2b1792878258416133ec80e54b66b21825925d03 100644 (file)
@@ -179,13 +179,20 @@ MUTATOR_HOOKABLE(DrawViewModel, EV_DrawViewModel);
 /** Called when updating the view's liquid contents, return true to disable the standard checks and apply your own */
 MUTATOR_HOOKABLE(HUD_Contents, EV_NO_ARGS);
 
-/** Return true to disable player model/color forcing */
+/** Return true to disable player model forcing */
 #define EV_ForcePlayermodels_Skip(i, o) \
        /** entity id */                i(entity, MUTATOR_ARGV_0_entity) \
        /** is local */                 i(bool, MUTATOR_ARGV_1_bool) \
        /**/
 MUTATOR_HOOKABLE(ForcePlayermodels_Skip, EV_ForcePlayermodels_Skip);
 
+/** Return true to disable player color forcing */
+#define EV_ForcePlayercolors_Skip(i, o) \
+       /** entity id */                i(entity, MUTATOR_ARGV_0_entity) \
+       /** is local */                 i(bool, MUTATOR_ARGV_1_bool) \
+       /**/
+MUTATOR_HOOKABLE(ForcePlayercolors_Skip, EV_ForcePlayercolors_Skip);
+
 /** Called when damage info is received on the client, useful for playing explosion effects */
 #define EV_DamageInfo(i, o) \
        /** entity id */                i(entity, MUTATOR_ARGV_0_entity) \
index a7aa27961139530a73a9a7feda0d4ae1fe8d54f4..abd7c2c35ec5778fe9d76b5acf61a419bab5b978 100644 (file)
@@ -152,6 +152,11 @@ ENTCS_PROP(SOLID, true, sv_solid, solid, ENTCS_SET_NORMAL,
        { WriteByte(chan, ent.sv_solid); },
        { ent.sv_solid = ReadByte(); })
 
+// gamemode specific player survival status (independent of score and frags)
+ENTCS_PROP(SURVIVAL_STATUS, true, survival_status, survival_status, ENTCS_SET_NORMAL,
+       { WriteShort(chan, ent.survival_status); },
+       { ent.survival_status = ReadShort(); })
+
 #ifdef SVQC
 
        int ENTCS_PUBLICMASK = 0;
index a33ec87a01a34a5a8406f57aa3c3d52829cf3994..e261fa7b9fdc736fc5b7640689c7872d87dd2684 100644 (file)
@@ -15,4 +15,5 @@
 #include <common/gamemodes/gamemode/nexball/_mod.inc>
 #include <common/gamemodes/gamemode/onslaught/_mod.inc>
 #include <common/gamemodes/gamemode/race/_mod.inc>
+#include <common/gamemodes/gamemode/survival/_mod.inc>
 #include <common/gamemodes/gamemode/tdm/_mod.inc>
index ffd71d59d3f1092453b6d83f8048003693dfa531..928bd44cbe1205c7fc87498af845f26f40f36dee 100644 (file)
@@ -15,4 +15,5 @@
 #include <common/gamemodes/gamemode/nexball/_mod.qh>
 #include <common/gamemodes/gamemode/onslaught/_mod.qh>
 #include <common/gamemodes/gamemode/race/_mod.qh>
+#include <common/gamemodes/gamemode/survival/_mod.qh>
 #include <common/gamemodes/gamemode/tdm/_mod.qh>
diff --git a/qcsrc/common/gamemodes/gamemode/survival/_mod.inc b/qcsrc/common/gamemodes/gamemode/survival/_mod.inc
new file mode 100644 (file)
index 0000000..7121d39
--- /dev/null
@@ -0,0 +1,8 @@
+// generated file; do not modify
+#include <common/gamemodes/gamemode/survival/survival.qc>
+#ifdef CSQC
+    #include <common/gamemodes/gamemode/survival/cl_survival.qc>
+#endif
+#ifdef SVQC
+    #include <common/gamemodes/gamemode/survival/sv_survival.qc>
+#endif
diff --git a/qcsrc/common/gamemodes/gamemode/survival/_mod.qh b/qcsrc/common/gamemodes/gamemode/survival/_mod.qh
new file mode 100644 (file)
index 0000000..875e4d2
--- /dev/null
@@ -0,0 +1,8 @@
+// generated file; do not modify
+#include <common/gamemodes/gamemode/survival/survival.qh>
+#ifdef CSQC
+    #include <common/gamemodes/gamemode/survival/cl_survival.qh>
+#endif
+#ifdef SVQC
+    #include <common/gamemodes/gamemode/survival/sv_survival.qh>
+#endif
diff --git a/qcsrc/common/gamemodes/gamemode/survival/cl_survival.qc b/qcsrc/common/gamemodes/gamemode/survival/cl_survival.qc
new file mode 100644 (file)
index 0000000..a7899da
--- /dev/null
@@ -0,0 +1,40 @@
+#include "cl_survival.qh"
+
+#include <client/hud/panel/modicons.qh>
+
+void HUD_Mod_Survival(vector pos, vector mySize)
+{
+       mod_active = 1; // survival should always show the mod HUD
+
+       // since survivor state is the default value, spectators are considered survivors
+       // so we must hide them manually here
+       if(entcs_GetSpecState(player_localnum) == ENTCS_SPEC_PURE)
+               return; // no icon while spectating
+
+       int mystatus = entcs_receiver(player_localnum).survival_status;
+       //string player_icon = ((mystatus == SURV_STATUS_HUNTER) ? "player_red" : "player_neutral");
+       string player_text = ((mystatus == SURV_STATUS_HUNTER) ? _("Hunter") : _("Survivor"));
+       vector player_color = ((mystatus == SURV_STATUS_HUNTER) ? '1 0 0' : '0 1 0');
+       //drawpic_aspect_skin(pos, player_icon, vec2(0.5 * mySize.x, mySize.y), '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
+       drawstring_aspect(pos, player_text, vec2(mySize.x, mySize.y), player_color, panel_fg_alpha, DRAWFLAG_NORMAL);
+}
+
+REGISTER_MUTATOR(cl_surv, true);
+
+MUTATOR_HOOKFUNCTION(cl_surv, ForcePlayercolors_Skip)
+{
+       if(!ISGAMETYPE(SURVIVAL))
+               return false;
+               
+       entity player = M_ARGV(0, entity);
+       entity e = entcs_receiver(player.entnum - 1);
+       int surv_status = ((e) ? e.survival_status : 0);
+       int mystatus = entcs_receiver(player_localnum).survival_status;
+
+       int plcolor = SURV_COLOR_PREY; // green
+       if((mystatus == SURV_STATUS_HUNTER || intermission || STAT(GAME_STOPPED)) && surv_status == SURV_STATUS_HUNTER)
+               plcolor = SURV_COLOR_HUNTER; // red
+
+       player.colormap = 1024 + plcolor;
+       return true;
+}
diff --git a/qcsrc/common/gamemodes/gamemode/survival/cl_survival.qh b/qcsrc/common/gamemodes/gamemode/survival/cl_survival.qh
new file mode 100644 (file)
index 0000000..6f70f09
--- /dev/null
@@ -0,0 +1 @@
+#pragma once
diff --git a/qcsrc/common/gamemodes/gamemode/survival/survival.qc b/qcsrc/common/gamemodes/gamemode/survival/survival.qc
new file mode 100644 (file)
index 0000000..1f2d144
--- /dev/null
@@ -0,0 +1 @@
+#include "survival.qh"
diff --git a/qcsrc/common/gamemodes/gamemode/survival/survival.qh b/qcsrc/common/gamemodes/gamemode/survival/survival.qh
new file mode 100644 (file)
index 0000000..314ec62
--- /dev/null
@@ -0,0 +1,10 @@
+#pragma once
+
+// shared state signalling the player's survival status
+.int survival_status;
+const int SURV_STATUS_PREY = 0;
+const int SURV_STATUS_HUNTER = 1;
+
+// hardcoded player colors for survival
+const int SURV_COLOR_PREY = 51; // green
+const int SURV_COLOR_HUNTER = 68; // red
diff --git a/qcsrc/common/gamemodes/gamemode/survival/sv_survival.qc b/qcsrc/common/gamemodes/gamemode/survival/sv_survival.qc
new file mode 100644 (file)
index 0000000..72a0c49
--- /dev/null
@@ -0,0 +1,420 @@
+#include "sv_survival.qh"
+
+float autocvar_g_survival_hunter_count = 0.25;
+float autocvar_g_survival_round_timelimit = 180;
+float autocvar_g_survival_warmup = 10;
+bool autocvar_g_survival_punish_teamkill = true;
+bool autocvar_g_survival_reward_survival = true;
+
+void surv_FakeTimeLimit(entity e, float t)
+{
+       if(!IS_REAL_CLIENT(e))
+               return;
+       msg_entity = e;
+       WriteByte(MSG_ONE, 3); // svc_updatestat
+       WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
+       if(t < 0)
+               WriteCoord(MSG_ONE, autocvar_timelimit);
+       else
+               WriteCoord(MSG_ONE, (t + 1) / 60);
+}
+
+void Surv_count_alive_players()
+{
+       total_players = 0;
+       FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
+       {
+               ++total_players;
+       });
+}
+
+void nades_Clear(entity player);
+
+void Surv_UpdateScores(bool timed_out)
+{
+       // give players their hard-earned kills now that the round is over
+       FOREACH_CLIENT(true,
+       {
+               it.totalfrags += it.survival_validkills;
+               if(it.survival_validkills)
+                       GameRules_scoring_add(it, SCORE, it.survival_validkills);
+               it.survival_validkills = 0;
+               if(autocvar_g_survival_reward_survival && timed_out && IS_PLAYER(it) && !IS_DEAD(it) && it.survival_status == SURV_STATUS_PREY)
+                       GameRules_scoring_add(it, SCORE, 1); // reward survivors who make it to the end of the round time limit
+       });
+}
+
+float Surv_CheckWinner()
+{
+       if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
+       {
+               // if the match times out, survivors win too!
+               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_SURVIVAL_SURVIVOR_WIN);
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_SURVIVAL_SURVIVOR_WIN);
+               FOREACH_CLIENT(true,
+               {
+                       if(IS_PLAYER(it))
+                               nades_Clear(it);
+                       surv_FakeTimeLimit(it, -1);
+               });
+
+               Surv_UpdateScores(true);
+
+               allowed_to_spawn = false;
+               game_stopped = true;
+               round_handler_Init(5, autocvar_g_survival_warmup, autocvar_g_survival_round_timelimit);
+               return 1;
+       }
+
+       int survivor_count = 0, hunter_count = 0;
+       FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
+       {
+               if(it.survival_status == SURV_STATUS_PREY)
+                       survivor_count++;
+               else if(it.survival_status == SURV_STATUS_HUNTER)
+                       hunter_count++;
+       });
+       if(survivor_count > 0 && hunter_count > 0)
+       {
+               return 0;
+       }
+
+       if(hunter_count > 0) // hunters win
+       {
+               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_SURVIVAL_HUNTER_WIN);
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_SURVIVAL_HUNTER_WIN);
+       }
+       else if(survivor_count > 0) // survivors win
+       {
+               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_SURVIVAL_SURVIVOR_WIN);
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_SURVIVAL_SURVIVOR_WIN);
+       }
+       else
+       {
+               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
+       }
+
+       Surv_UpdateScores(false);
+
+       allowed_to_spawn = false;
+       game_stopped = true;
+       round_handler_Init(5, autocvar_g_survival_warmup, autocvar_g_survival_round_timelimit);
+
+       FOREACH_CLIENT(true,
+       {
+               if(IS_PLAYER(it))
+                       nades_Clear(it);
+               surv_FakeTimeLimit(it, -1);
+       });
+
+       return 1;
+}
+
+void Surv_RoundStart()
+{
+       allowed_to_spawn = boolean(warmup_stage);
+       FOREACH_CLIENT(true, { it.survival_status = SURV_STATUS_PREY; it.survival_validkills = 0; });
+       Surv_count_alive_players();
+       int hunter_count = bound(1, ((autocvar_g_survival_hunter_count >= 1) ? autocvar_g_survival_hunter_count : floor(total_players * autocvar_g_survival_hunter_count)), total_players - 1); // 20%, but ensure at least 1 and less than total
+       int total_hunters = 0;
+       FOREACH_CLIENT_RANDOM(IS_PLAYER(it) && !IS_DEAD(it),
+       {
+               if(total_hunters >= hunter_count)
+                       break;
+               total_hunters++;
+               it.survival_status = SURV_STATUS_HUNTER;
+       });
+
+       FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
+       {
+               if(it.survival_status == SURV_STATUS_PREY)
+                       Send_Notification(NOTIF_ONE_ONLY, it, MSG_CENTER, CENTER_SURVIVAL_SURVIVOR);
+               else if(it.survival_status == SURV_STATUS_HUNTER)
+                       Send_Notification(NOTIF_ONE_ONLY, it, MSG_CENTER, CENTER_SURVIVAL_HUNTER);
+
+               surv_FakeTimeLimit(it, round_handler_GetEndTime());
+       });
+}
+
+bool Surv_CheckPlayers()
+{
+       static int prev_missing_players;
+       allowed_to_spawn = true;
+       Surv_count_alive_players();
+       if (total_players >= 2)
+       {
+               if(prev_missing_players > 0)
+                       Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_PLAYERS);
+               prev_missing_players = -1;
+               return true;
+       }
+       if(total_players == 0)
+       {
+               if(prev_missing_players > 0)
+                       Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_PLAYERS);
+               prev_missing_players = -1;
+               return false;
+       }
+       // if we get here, only 1 player is missing
+       if(prev_missing_players != 1)
+       {
+               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_MISSING_PLAYERS, 1);
+               prev_missing_players = 1;
+       }
+       return false;
+}
+
+bool surv_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;
+}
+
+void surv_Initialize() // run at the start of a match, initiates game mode
+{
+       allowed_to_spawn = true;
+       round_handler_Spawn(Surv_CheckPlayers, Surv_CheckWinner, Surv_RoundStart);
+       round_handler_Init(5, autocvar_g_survival_warmup, autocvar_g_survival_round_timelimit);
+       EliminatedPlayers_Init(surv_isEliminated);
+}
+
+
+// ==============
+// Hook Functions
+// ==============
+
+MUTATOR_HOOKFUNCTION(surv, ClientObituary)
+{
+       // in survival, announcing a frag would tell everyone who the hunter is
+       // for the sake of anonymity, a barebones
+       entity frag_attacker = M_ARGV(1, entity);
+       entity frag_target = M_ARGV(2, entity);
+       if(IS_PLAYER(frag_attacker) && frag_attacker != frag_target)
+       {
+               float frag_deathtype = M_ARGV(3, float);
+               entity wep_ent = M_ARGV(4, entity);
+               // "team" kill, a point is awarded to the player by default so we must take it away plus an extra one
+               // unless the player is going to be punished for suicide, in which case just remove one
+               if(frag_attacker.survival_status == frag_target.survival_status)
+                       GiveFrags(frag_attacker, frag_target, ((autocvar_g_survival_punish_teamkill) ? -1 : -2), frag_deathtype, wep_ent.weaponentity_fld);
+       }
+
+       M_ARGV(5, bool) = true; // anonymous attacker
+}
+
+MUTATOR_HOOKFUNCTION(surv, PlayerSpawn)
+{
+       entity player = M_ARGV(0, entity);
+
+       player.survival_status = SURV_STATUS_PREY;
+       player.caplayer = 1;
+       if (!warmup_stage)
+               eliminatedPlayers.SendFlags |= 1;
+}
+
+MUTATOR_HOOKFUNCTION(surv, 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 && player.caplayer)
+               return true;
+       return false;
+}
+
+MUTATOR_HOOKFUNCTION(surv, PutClientInServer)
+{
+       entity player = M_ARGV(0, entity);
+
+       if (!allowed_to_spawn && 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);
+               }
+       }
+}
+
+MUTATOR_HOOKFUNCTION(surv, reset_map_players)
+{
+       FOREACH_CLIENT(true, {
+               CS(it).killcount = 0;
+               if (!it.caplayer && IS_BOT_CLIENT(it))
+                       it.caplayer = 1;
+               if (it.caplayer)
+               {
+                       TRANSMUTE(Player, it);
+                       it.caplayer = 1;
+                       PutClientInServer(it);
+               }
+       });
+       bot_relinkplayerlist();
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(surv, reset_map_global)
+{
+       allowed_to_spawn = true;
+       return true;
+}
+
+entity surv_LastPlayerForTeam(entity this)
+{
+       entity last_pl = NULL;
+       FOREACH_CLIENT(IS_PLAYER(it) && it != this, {
+               if (!IS_DEAD(it) && this.survival_status == it.survival_status)
+               {
+                       if (!last_pl)
+                               last_pl = it;
+                       else
+                               return NULL;
+               }
+       });
+       return last_pl;
+}
+
+void surv_LastPlayerForTeam_Notify(entity this)
+{
+       if (!warmup_stage && round_handler_IsActive() && round_handler_IsRoundStarted())
+       {
+               entity pl = surv_LastPlayerForTeam(this);
+               if (pl)
+                       Send_Notification(NOTIF_ONE, pl, MSG_CENTER, CENTER_ALONE);
+       }
+}
+
+MUTATOR_HOOKFUNCTION(surv, PlayerDies)
+{
+       entity frag_attacker = M_ARGV(1, entity);
+       entity frag_target = M_ARGV(2, entity);
+       float frag_deathtype = M_ARGV(3, float);
+
+       surv_LastPlayerForTeam_Notify(frag_target);
+       if (!allowed_to_spawn)
+       {
+               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;
+               if (IS_BOT_CLIENT(frag_target))
+                       bot_clear(frag_target);
+       }
+
+       // killed an ally! punishment is death
+       if(autocvar_g_survival_punish_teamkill && frag_attacker != frag_target && IS_PLAYER(frag_attacker) && IS_PLAYER(frag_target) && frag_attacker.survival_status == frag_target.survival_status && !ITEM_DAMAGE_NEEDKILL(frag_deathtype))
+       if(!warmup_stage && round_handler_IsActive() && round_handler_IsRoundStarted()) // don't autokill if the round hasn't
+               ClientKill_Silent(frag_attacker, 0);
+               //Damage(frag_attacker, frag_attacker, frag_attacker, 100000, DEATH_AUTOTEAMCHANGE.m_id, DMG_NOWEP, frag_attacker.origin, '0 0 0');
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(surv, ClientDisconnect)
+{
+       entity player = M_ARGV(0, entity);
+
+       if (IS_PLAYER(player) && !IS_DEAD(player))
+               surv_LastPlayerForTeam_Notify(player);
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(surv, MakePlayerObserver)
+{
+       entity player = M_ARGV(0, entity);
+
+       if (IS_PLAYER(player) && !IS_DEAD(player))
+               surv_LastPlayerForTeam_Notify(player);
+       if (player.killindicator_teamchange == -2) // player wants to spectate
+               player.caplayer = 0;
+       if (player.caplayer)
+               player.frags = FRAGS_PLAYER_OUT_OF_GAME;
+       if (!warmup_stage)
+               eliminatedPlayers.SendFlags |= 1;
+       if (!player.caplayer)
+       {
+               player.survival_validkills = 0;
+               player.survival_status = 0;
+               surv_FakeTimeLimit(player, -1); // restore original timelimit
+               return false;  // allow team reset
+       }
+       return true;  // prevent team reset
+}
+
+MUTATOR_HOOKFUNCTION(surv, Scores_CountFragsRemaining)
+{
+       // announce remaining frags?
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(surv, GiveFragsForKill, CBC_ORDER_FIRST)
+{
+       entity frag_attacker = M_ARGV(0, entity);
+       frag_attacker.survival_validkills += M_ARGV(2, float);
+       M_ARGV(2, float) = 0; // score will be given to the winner when the round ends
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(surv, AddPlayerScore)
+{
+       entity scorefield = M_ARGV(0, entity);
+       if(scorefield == SP_KILLS || scorefield == SP_DEATHS || scorefield == SP_SUICIDES || scorefield == SP_DMG || scorefield == SP_DMGTAKEN)
+               M_ARGV(1, float) = 0; // don't report that the player has killed or been killed, that would out them as a hunter!
+}
+
+MUTATOR_HOOKFUNCTION(surv, CalculateRespawnTime)
+{
+       // no respawn calculations needed, player is forced to spectate anyway
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(surv, Bot_FixCount, CBC_ORDER_EXCLUSIVE)
+{
+       FOREACH_CLIENT(IS_REAL_CLIENT(it), {
+               if (IS_PLAYER(it) || it.caplayer == 1)
+                       ++M_ARGV(0, int);
+               ++M_ARGV(1, int);
+       });
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(surv, ClientCommand_Spectate)
+{
+       entity player = M_ARGV(0, entity);
+
+       if (player.caplayer)
+       {
+               // they're going to spec, we can do other checks
+               if (autocvar_sv_spectate && (IS_SPEC(player) || IS_OBSERVER(player)))
+                       Send_Notification(NOTIF_ONE_ONLY, player, MSG_INFO, INFO_CA_LEAVE);
+               return MUT_SPECCMD_FORCE;
+       }
+
+       return MUT_SPECCMD_CONTINUE;
+}
+
+MUTATOR_HOOKFUNCTION(surv, GetPlayerStatus)
+{
+       entity player = M_ARGV(0, entity);
+
+       return player.caplayer == 1;
+}
+
+MUTATOR_HOOKFUNCTION(surv, BotShouldAttack)
+{
+       entity bot = M_ARGV(0, entity);
+       entity targ = M_ARGV(1, entity);
+
+       if(targ.survival_status == bot.survival_status)
+               return true;
+}
diff --git a/qcsrc/common/gamemodes/gamemode/survival/sv_survival.qh b/qcsrc/common/gamemodes/gamemode/survival/sv_survival.qh
new file mode 100644 (file)
index 0000000..ad68f02
--- /dev/null
@@ -0,0 +1,20 @@
+#pragma once
+
+#include <common/mutators/base.qh>
+#include <common/scores.qh>
+void surv_Initialize();
+
+REGISTER_MUTATOR(surv, false)
+{
+    MUTATOR_STATIC();
+       MUTATOR_ONADD
+       {
+           GameRules_scoring(0, SFL_SORT_PRIO_PRIMARY, 0, {
+        });
+
+               surv_Initialize();
+       }
+       return false;
+}
+
+.int survival_validkills; // store the player's valid kills to be given at the end of the match (avoid exposing their score until then)
index 9b4dfa2575aff6461a2fd56857f27ca6da066a1c..c1fff635067484a74b44100ca425bae57ff48779 100644 (file)
@@ -608,6 +608,34 @@ ENDCLASS(Duel)
 REGISTER_GAMETYPE(DUEL, NEW(Duel));
 #define g_duel IS_GAMETYPE(DUEL)
 
+#ifdef CSQC
+void HUD_Mod_Survival(vector pos, vector mySize);
+#endif
+CLASS(Survival, Gametype)
+    INIT(Survival)
+    {
+        this.gametype_init(this, _("Survival"),"surv","g_survival",false,true,"","timelimit=20 pointlimit=20",_("Identify and eliminate all the hunters before all your allies are gone"));
+    }
+    METHOD(Survival, m_isAlwaysSupported, bool(Gametype this, int spawnpoints, float diameter))
+    {
+        return true;
+    }
+    METHOD(Survival, m_isForcedSupported, bool(Gametype this))
+    {
+        if(!cvar("g_duel_not_dm_maps"))
+        {
+            // if this is set, all DM maps support Survival 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;
+    }
+#ifdef CSQC
+    ATTRIB(Survival, m_modicons, void(vector pos, vector mySize), HUD_Mod_Survival);
+#endif
+ENDCLASS(Survival)
+REGISTER_GAMETYPE(SURVIVAL, NEW(Survival));
+
 const int MAPINFO_FEATURE_WEAPONS       = 1; // not defined for instagib-only maps
 const int MAPINFO_FEATURE_VEHICLES      = 2;
 const int MAPINFO_FEATURE_TURRETS       = 4;
index a9a2ee655139555dfc70bb72ae875d9fc2061a28..296b318354b3fcfd2d0a5a077c345a373653253e 100644 (file)
@@ -435,6 +435,9 @@ string multiteam_info_sprintf(string input, string teamname) { return ((input !=
 
     MSG_INFO_NOTIF(SUPERWEAPON_PICKUP,                      N_CONSOLE,  1, 0, "s1", "s1",       "superweapons",         _("^BG%s^K1 picked up a Superweapon"), "")
 
+    MSG_INFO_NOTIF(SURVIVAL_HUNTER_WIN,                     N_CONSOLE,  0, 0, "", "",           "",                     _("^K1Hunters^BG win the round"), "")
+    MSG_INFO_NOTIF(SURVIVAL_SURVIVOR_WIN,                   N_CONSOLE,  0, 0, "", "",           "",                     _("^F1Survivors^BG win the round"), "")
+
     MSG_INFO_NOTIF(TEAMCHANGE_LARGERTEAM,                   N_CONSOLE,  0, 0, "", "",           "",                     _("^BGYou cannot change to a larger team"), "")
     MSG_INFO_NOTIF(TEAMCHANGE_NOTALLOWED,                   N_CONSOLE,  0, 0, "", "",           "",                     _("^BGYou are not allowed to change teams"), "")
 
@@ -763,6 +766,11 @@ string multiteam_info_sprintf(string input, string teamname) { return ((input !=
     MSG_CENTER_NOTIF(SUPERWEAPON_LOST,                  N_ENABLE,    0, 0, "",               CPID_POWERUP,           "0 0",  _("^F2Superweapons have been lost"), "")
     MSG_CENTER_NOTIF(SUPERWEAPON_PICKUP,                N_ENABLE,    0, 0, "",               CPID_POWERUP,           "0 0",  _("^F2You now have a superweapon"), "")
 
+    MSG_CENTER_NOTIF(SURVIVAL_HUNTER,                   N_ENABLE,    0, 0, "",               CPID_SURVIVAL,          "5 0",  strcat(BOLD_OPERATOR, _("^BGYou are a ^K1hunter^BG! Eliminate the survivor(s) without raising suspicion!")), "")
+    MSG_CENTER_NOTIF(SURVIVAL_HUNTER_WIN,               N_ENABLE,    0, 0, "",               CPID_ROUND,             "0 0",  _("^K1Hunters^BG win the round"), "")
+    MSG_CENTER_NOTIF(SURVIVAL_SURVIVOR,                 N_ENABLE,    0, 0, "",               CPID_SURVIVAL,          "5 0",  strcat(BOLD_OPERATOR, _("^BGYou are a ^F1survivor^BG! Identify and eliminate the hunter(s)!")), "")
+    MSG_CENTER_NOTIF(SURVIVAL_SURVIVOR_WIN,             N_ENABLE,    0, 0, "",               CPID_ROUND,             "0 0",  _("^F1Survivors^BG win the round"), "")
+
     MULTITEAM_CENTER(TEAMCHANGE,                        N_ENABLE,    0, 1, "",               CPID_TEAMCHANGE,        "1 f1", _("^K1Changing to ^TC^TT^K1 in ^COUNT"), "", NAME)
     MSG_CENTER_NOTIF(TEAMCHANGE_AUTO,                   N_ENABLE,    0, 1, "",               CPID_TEAMCHANGE,        "1 f1", _("^K1Changing team in ^COUNT"), "")
     MSG_CENTER_NOTIF(TEAMCHANGE_SPECTATE,               N_ENABLE,    0, 1, "",               CPID_TEAMCHANGE,        "1 f1", _("^K1Spectating in ^COUNT"), "")
index 481154f3e6ad207fdb4e6d05140273a4d7d0c9cc..efe602a4736b30261d7d4ffd90ae6537b16ba164 100644 (file)
@@ -73,6 +73,7 @@ ENUMCLASS(CPID)
        CASE(CPID, OVERTIME)
        CASE(CPID, POWERUP)
        CASE(CPID, RACE_FINISHLAP)
+       CASE(CPID, SURVIVAL)
        CASE(CPID, TEAMCHANGE)
        CASE(CPID, TIMEOUT)
        CASE(CPID, TIMEIN)
index c0a8c6b2b54e416017cf39149ee976f55e769e90..236f5ae07cbdab2b1d1cfe1eccb8f64d2311abb2 100644 (file)
@@ -683,6 +683,7 @@ float updateCompression()
        GAMETYPE(MAPINFO_TYPE_ASSAULT) \
        /* GAMETYPE(MAPINFO_TYPE_DUEL) */ \
        /* GAMETYPE(MAPINFO_TYPE_INVASION) */ \
+       /* GAMETYPE(MAPINFO_TYPE_SURVIVAL) */ \
        /**/
 
 // hidden gametypes come last so indexing always works correctly
index 274378f17e0f9ffe804a7b3780e75af6546d0f62..9a8be98da877ea4bb29b7cde797d9ebec386dc86 100644 (file)
@@ -209,12 +209,12 @@ float Obituary_WeaponDeath(
        return true;
 }
 
-bool frag_centermessage_override(entity attacker, entity targ, int deathtype, int kill_count_to_attacker, int kill_count_to_target)
+bool frag_centermessage_override(entity attacker, entity targ, int deathtype, int kill_count_to_attacker, int kill_count_to_target, string attacker_name)
 {
        if(deathtype == DEATH_FIRE.m_id)
        {
                Send_Notification(NOTIF_ONE, attacker, MSG_CHOICE, CHOICE_FRAG_FIRE, targ.netname, kill_count_to_attacker, (IS_BOT_CLIENT(targ) ? -1 : CS(targ).ping));
-               Send_Notification(NOTIF_ONE, targ, MSG_CHOICE, CHOICE_FRAGGED_FIRE, attacker.netname, kill_count_to_target, GetResource(attacker, RES_HEALTH), GetResource(attacker, RES_ARMOR), (IS_BOT_CLIENT(attacker) ? -1 : CS(attacker).ping));
+               Send_Notification(NOTIF_ONE, targ, MSG_CHOICE, CHOICE_FRAGGED_FIRE, attacker_name, kill_count_to_target, GetResource(attacker, RES_HEALTH), GetResource(attacker, RES_ARMOR), (IS_BOT_CLIENT(attacker) ? -1 : CS(attacker).ping));
                return true;
        }
 
@@ -229,16 +229,25 @@ void Obituary(entity attacker, entity inflictor, entity targ, int deathtype, .en
        // Declarations
        float notif_firstblood = false;
        float kill_count_to_attacker, kill_count_to_target;
+       bool notif_anonymous = false;
+       string attacker_name = attacker.netname;
 
        // Set final information for the death
        targ.death_origin = targ.origin;
        string deathlocation = (autocvar_notification_server_allows_location ? NearestLocation(targ.death_origin) : "");
 
+       // Abort now if a mutator requests it
+       if (MUTATOR_CALLHOOK(ClientObituary, inflictor, attacker, targ, deathtype, attacker.(weaponentity))) { CS(targ).killcount = 0; return; }
+       notif_anonymous = M_ARGV(5, bool);
+
+       if(notif_anonymous)
+               attacker_name = "Anonymous player";
+
        #ifdef NOTIFICATIONS_DEBUG
        Debug_Notification(
                sprintf(
                        "Obituary(%s, %s, %s, %s = %d);\n",
-                       attacker.netname,
+                       attacker_name,
                        inflictor.netname,
                        targ.netname,
                        Deathtype_Name(deathtype),
@@ -299,8 +308,8 @@ void Obituary(entity attacker, entity inflictor, entity targ, int deathtype, .en
                        CS(attacker).killcount = 0;
 
                        Send_Notification(NOTIF_ONE, attacker, MSG_CENTER, CENTER_DEATH_TEAMKILL_FRAG, targ.netname);
-                       Send_Notification(NOTIF_ONE, targ, MSG_CENTER, CENTER_DEATH_TEAMKILL_FRAGGED, attacker.netname);
-                       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(targ.team, INFO_DEATH_TEAMKILL), targ.netname, attacker.netname, deathlocation, CS(targ).killcount);
+                       Send_Notification(NOTIF_ONE, targ, MSG_CENTER, CENTER_DEATH_TEAMKILL_FRAGGED, attacker_name);
+                       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(targ.team, INFO_DEATH_TEAMKILL), targ.netname, attacker_name, deathlocation, CS(targ).killcount);
 
                        // In this case, the death message will ALWAYS be "foo was betrayed by bar"
                        // No need for specific death/weapon messages...
@@ -364,14 +373,14 @@ void Obituary(entity attacker, entity inflictor, entity targ, int deathtype, .en
                                        targ,
                                        MSG_CHOICE,
                                        CHOICE_TYPEFRAGGED,
-                                       attacker.netname,
+                                       attacker_name,
                                        kill_count_to_target,
                                        GetResource(attacker, RES_HEALTH),
                                        GetResource(attacker, RES_ARMOR),
                                        (IS_BOT_CLIENT(attacker) ? -1 : CS(attacker).ping)
                                );
                        }
-                       else if(!frag_centermessage_override(attacker, targ, deathtype, kill_count_to_attacker, kill_count_to_target))
+                       else if(!frag_centermessage_override(attacker, targ, deathtype, kill_count_to_attacker, kill_count_to_target, attacker_name))
                        {
                                Send_Notification(
                                        NOTIF_ONE,
@@ -387,7 +396,7 @@ void Obituary(entity attacker, entity inflictor, entity targ, int deathtype, .en
                                        targ,
                                        MSG_CHOICE,
                                        CHOICE_FRAGGED,
-                                       attacker.netname,
+                                       attacker_name,
                                        kill_count_to_target,
                                        GetResource(attacker, RES_HEALTH),
                                        GetResource(attacker, RES_ARMOR),
@@ -399,8 +408,8 @@ void Obituary(entity attacker, entity inflictor, entity targ, int deathtype, .en
                        if(deathtype == DEATH_BUFF.m_id)
                                f3 = buff_FirstFromFlags(STAT(BUFFS, attacker)).m_id;
 
-                       if (!Obituary_WeaponDeath(targ, true, deathtype, targ.netname, attacker.netname, deathlocation, CS(targ).killcount, kill_count_to_attacker))
-                               Obituary_SpecialDeath(targ, true, deathtype, targ.netname, attacker.netname, deathlocation, CS(targ).killcount, kill_count_to_attacker, f3);
+                       if (!Obituary_WeaponDeath(targ, true, deathtype, targ.netname, attacker_name, deathlocation, CS(targ).killcount, kill_count_to_attacker))
+                               Obituary_SpecialDeath(targ, true, deathtype, targ.netname, attacker_name, deathlocation, CS(targ).killcount, kill_count_to_attacker, f3);
                }
        }
 
index 7231bd8f6fba2d94be4e2604cb2d75149231c6e5..0ea5921410672d0498b1488fbd901853347e5d20 100644 (file)
@@ -287,6 +287,8 @@ void cvar_changes_init()
                BADCVAR("g_runematch");
                BADCVAR("g_shootfromeye");
                BADCVAR("g_snafu");
+               BADCVAR("g_survival");
+               BADCVAR("g_survival_not_dm_maps");
                BADCVAR("g_tdm");
                BADCVAR("g_tdm_on_dm_maps");
                BADCVAR("g_tdm_teams");
index a310c6ccadb294c25aad77a6f3900cbb1a96a19a..35a8f6fd93b7f31900f8d8eb8341815c99f69e65 100644 (file)
@@ -90,6 +90,17 @@ MUTATOR_HOOKABLE(PlayerDies, EV_PlayerDies);
     /**/
 MUTATOR_HOOKABLE(PlayerDied, EV_PlayerDied);
 
+/** called when showing an obituary for the player. return true to show nothing (workarounds may be needed) */
+#define EV_ClientObituary(i, o) \
+    /** inflictor       */ i(entity, MUTATOR_ARGV_0_entity) \
+    /** attacker        */ i(entity, MUTATOR_ARGV_1_entity) \
+    /** target          */ i(entity, MUTATOR_ARGV_2_entity) \
+    /** deathtype       */ i(float,  MUTATOR_ARGV_3_float) \
+    /** wep entity      */ i(entity, MUTATOR_ARGV_4_entity) \
+    /** anonymous killer*/ o(bool,   MUTATOR_ARGV_5_bool) \
+    /**/
+MUTATOR_HOOKABLE(ClientObituary, EV_ClientObituary);
+
 /** allows overriding the frag centerprint messages */
 #define EV_FragCenterMessage(i, o) \
     /** attacker       */ i(entity, MUTATOR_ARGV_0_entity) \