]> git.rm.cloudns.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Added Trouble in Terrorist Town gamemode, using another way to recover the lost gamem...
authorLegendaryGuard <rootuser999@gmail.com>
Sat, 20 Feb 2021 15:27:29 +0000 (16:27 +0100)
committerLegendaryGuard <rootuser999@gmail.com>
Sat, 20 Feb 2021 15:27:29 +0000 (16:27 +0100)
24 files changed:
gamemodes-client.cfg
gamemodes-server.cfg
gfx/menu/luma/gametype_ttt.tga [new file with mode: 0644]
gfx/menu/luminos/gametype_ttt.tga [new file with mode: 0644]
gfx/menu/wickedx/gametype_ttt.tga [new file with mode: 0644]
gfx/menu/xaw/gametype_ttt.tga [new file with mode: 0644]
notifications.cfg
qcsrc/common/ent_cs.qc
qcsrc/common/gamemodes/gamemode/_mod.inc
qcsrc/common/gamemodes/gamemode/_mod.qh
qcsrc/common/gamemodes/gamemode/ttt/_mod.inc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/ttt/_mod.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/ttt/cl_ttt.qc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/ttt/cl_ttt.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/ttt/sv_ttt.qc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/ttt/sv_ttt.qh [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/ttt/ttt.qc [new file with mode: 0644]
qcsrc/common/gamemodes/gamemode/ttt/ttt.qh [new file with mode: 0644]
qcsrc/common/notifications/all.inc
qcsrc/common/notifications/all.qh
qcsrc/common/scores.qh
qcsrc/common/stats.qh
qcsrc/menu/xonotic/util.qc
qcsrc/server/world.qc

index c43b9d1d3f2e6fe73fddbb33e8489670350edc4d..633b73fe280b6c0fb731e83226cfb248197a9d1f 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_ttt //LegendGuard adds ttt client hook for TTT 20-02-2021
 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 c15baaf9d101150e0fba283fe39422860894168e..05531496e20d0b0a69d485f68adbc71299ff3e3e 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_ttt //LegendGuard adds ttt hook for TTT 20-02-2021
 // 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_ttt //LegendGuard adds ttt hook for TTT 20-02-2021
 
 // 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_ttt_respawn_delay_small 0 //LegendGuard adds ttt cvars for TTT 20-02-2021
+set g_ttt_respawn_delay_small_count 0
+set g_ttt_respawn_delay_large 0
+set g_ttt_respawn_delay_large_count 0
+set g_ttt_respawn_delay_max 0
+set g_ttt_respawn_waves 0
+set g_ttt_weapon_stay 0
 
 
 // =========
@@ -556,3 +565,15 @@ 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"
+
+//LegendGuard adds ttt cvars for TTT 20-02-2021
+// ==========
+//  trouble in terrorist town
+// ==========
+set g_ttt 0 "Trouble in Terrorist Town: A group of space terrorists have traitors among them. Traitors must kill terrorists, while the terrorists have to try to find and kill the traitors"
+set g_ttt_not_lms_maps 0 "when this is set, LMS maps will NOT be listed in ttt"
+set g_ttt_traitor_count 0.25 "number of players who will become traitors, 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_ttt_punish_teamkill 1 "kill the player when they kill an ally"
+set g_ttt_reward_innocent 1 "give a point to all innocent players if the round timelimit is reached, in addition to the points given for kills"
+set g_ttt_warmup 10 "how long the players will have time to run around the map before the round starts"
+set g_ttt_round_timelimit 180 "round time limit in seconds"
\ No newline at end of file
diff --git a/gfx/menu/luma/gametype_ttt.tga b/gfx/menu/luma/gametype_ttt.tga
new file mode 100644 (file)
index 0000000..d36db5d
Binary files /dev/null and b/gfx/menu/luma/gametype_ttt.tga differ
diff --git a/gfx/menu/luminos/gametype_ttt.tga b/gfx/menu/luminos/gametype_ttt.tga
new file mode 100644 (file)
index 0000000..7fd5a53
Binary files /dev/null and b/gfx/menu/luminos/gametype_ttt.tga differ
diff --git a/gfx/menu/wickedx/gametype_ttt.tga b/gfx/menu/wickedx/gametype_ttt.tga
new file mode 100644 (file)
index 0000000..7fd5a53
Binary files /dev/null and b/gfx/menu/wickedx/gametype_ttt.tga differ
diff --git a/gfx/menu/xaw/gametype_ttt.tga b/gfx/menu/xaw/gametype_ttt.tga
new file mode 100644 (file)
index 0000000..f0be4ad
Binary files /dev/null and b/gfx/menu/xaw/gametype_ttt.tga differ
index 61fafdecbb81da8cc99453a2a8cd6e17261c3375..ff9fbfad7755df1444d5de2489e33cfe849d14b3 100644 (file)
@@ -285,6 +285,11 @@ seta notification_INFO_SUPERSPEC_MISSING_UID "2" "0 = off, 1 = print to console,
 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_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)"
+
+//LegendGuard adds ttt notifications for TTT 20-02-2021
+seta notification_INFO_TTT_TRAITOR_WIN "1" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
+seta notification_INFO_TTT_INNOCENT_WIN "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)"
 seta notification_INFO_VERSION_OLD "2" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
 seta notification_INFO_VERSION_OUTDATED "2" "0 = off, 1 = print to console, 2 = print to console and chatbox (if notification_allow_chatboxprint is enabled)"
@@ -538,6 +543,12 @@ seta notification_CENTER_TEAMCHANGE_SPECTATE "1" "0 = off, 1 = centerprint"
 seta notification_CENTER_TEAMCHANGE_SUICIDE "1" "0 = off, 1 = centerprint"
 seta notification_CENTER_TIMEOUT_BEGINNING "1" "0 = off, 1 = centerprint"
 seta notification_CENTER_TIMEOUT_ENDING "1" "0 = off, 1 = centerprint"
+//LegendGuard adds ttt notification for TTT 20-02-2021
+seta notification_CENTER_TTT_TRAITOR "1" "0 = off, 1 = centerprint"
+seta notification_CENTER_TTT_TRAITOR_WIN "1" "0 = off, 1 = centerprint"
+seta notification_CENTER_TTT_INNOCENT "1" "0 = off, 1 = centerprint"
+seta notification_CENTER_TTT_INNOCENT_WIN "1" "0 = off, 1 = centerprint"
+
 seta notification_CENTER_VEHICLE_ENTER "1" "0 = off, 1 = centerprint"
 seta notification_CENTER_VEHICLE_ENTER_GUNNER "1" "0 = off, 1 = centerprint"
 seta notification_CENTER_VEHICLE_ENTER_STEAL "1" "0 = off, 1 = centerprint"
index 777bd417a6d2607a0e252dc3bbc3933b4b9b0462..5f0d788970fbcb4bbddc866baa31c6a46c1e572e 100644 (file)
@@ -157,6 +157,12 @@ ENTCS_PROP(SOLID, true, sv_solid, solid, ENTCS_SET_NORMAL,
        { WriteByte(chan, ent.sv_solid); },
        { ent.sv_solid = ReadByte(); })
 
+//LegendGuard adds ENTCS_PROP from TTT 20-02-2021
+// gamemode specific player ttt status (independent of score and frags)
+ENTCS_PROP(TTT_STATUS, true, ttt_status, ttt_status, ENTCS_SET_NORMAL,
+       { WriteShort(chan, ent.ttt_status); },
+       { ent.ttt_status = ReadShort(); })
+
 #ifdef SVQC
 
        int ENTCS_PUBLICMASK = 0;
index a33ec87a01a34a5a8406f57aa3c3d52829cf3994..759f00def8f49e6ccefb4f2a4bbcaa61d8e93c07 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/tdm/_mod.inc>
+#include <common/gamemodes/gamemode/ttt/_mod.inc> //LegendGuard adds _mod.inc for Trouble In Terrorist Town 20-02-2021
+#include <common/gamemodes/gamemode/tdm/_mod.inc>
\ No newline at end of file
index ffd71d59d3f1092453b6d83f8048003693dfa531..ef725161bd9a24349a79fff843ec993b3a94ab95 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/tdm/_mod.qh>
+#include <common/gamemodes/gamemode/ttt/_mod.qh> //LegendGuard adds _mod.qh for Trouble In Terrorist Town 20-02-2021
+#include <common/gamemodes/gamemode/tdm/_mod.qh>
\ No newline at end of file
diff --git a/qcsrc/common/gamemodes/gamemode/ttt/_mod.inc b/qcsrc/common/gamemodes/gamemode/ttt/_mod.inc
new file mode 100644 (file)
index 0000000..5574f4a
--- /dev/null
@@ -0,0 +1,8 @@
+// generated file; do not modify
+#include <common/gamemodes/gamemode/ttt/ttt.qc>
+#ifdef CSQC
+    #include <common/gamemodes/gamemode/ttt/cl_ttt.qc>
+#endif
+#ifdef SVQC
+    #include <common/gamemodes/gamemode/ttt/sv_ttt.qc>
+#endif
diff --git a/qcsrc/common/gamemodes/gamemode/ttt/_mod.qh b/qcsrc/common/gamemodes/gamemode/ttt/_mod.qh
new file mode 100644 (file)
index 0000000..09983d7
--- /dev/null
@@ -0,0 +1,8 @@
+// generated file; do not modify
+#include <common/gamemodes/gamemode/ttt/ttt.qh>
+#ifdef CSQC
+    #include <common/gamemodes/gamemode/ttt/cl_ttt.qh>
+#endif
+#ifdef SVQC
+    #include <common/gamemodes/gamemode/ttt/sv_ttt.qh>
+#endif
diff --git a/qcsrc/common/gamemodes/gamemode/ttt/cl_ttt.qc b/qcsrc/common/gamemodes/gamemode/ttt/cl_ttt.qc
new file mode 100644 (file)
index 0000000..a5b4ce4
--- /dev/null
@@ -0,0 +1,84 @@
+#include "cl_ttt.qh"
+
+#include <client/draw.qh>
+#include <client/hud/panel/modicons.qh>
+
+void HUD_Mod_TTT(vector pos, vector mySize)
+{
+       mod_active = 1; // ttt should always show the mod HUD
+
+       int mystatus = entcs_receiver(player_localnum).ttt_status;
+       string player_text = "";
+       vector player_color = '1 1 1';
+       //string player_icon = "";
+       if(mystatus == TTT_STATUS_TRAITOR)
+       {
+               player_text = _("Traitor");
+               player_color = '1 0 0';
+               //player_icon = "player_red";
+       }
+       else if(mystatus == TTT_STATUS_INNOCENT)
+       {
+               player_text = _("Innocent");
+               player_color = '0 1 0';
+               //player_icon = "player_neutral";
+       }
+       else
+       {
+               // if the player has no valid status, don't draw anything
+               return;
+       }
+
+       string time_text = string_null;
+       vector timer_color = '1 1 1';
+       if(!STAT(GAME_STOPPED) && !warmup_stage && STAT(TTT_ROUNDTIMER) > 0)
+       {
+               float timeleft = max(0, STAT(TTT_ROUNDTIMER) - time);
+               timeleft = ceil(timeleft);
+               float minutesLeft = floor(timeleft / 60);
+               time_text = seconds_tostring(timeleft);
+               if(intermission_time || minutesLeft >= 5 || warmup_stage || STAT(TTT_ROUNDTIMER) == 0)
+                       timer_color = '1 1 1'; //white
+               else if(minutesLeft >= 1)
+                       timer_color = '1 1 0'; //yellow
+               else
+                       timer_color = '1 0 0'; //red
+       }
+
+       //drawpic_aspect_skin(pos, player_icon, vec2(0.5 * mySize.x, mySize.y), '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
+       if(!time_text)
+               drawstring_aspect(pos, player_text, vec2(mySize.x, mySize.y), player_color, panel_fg_alpha, DRAWFLAG_NORMAL);
+       else
+       {
+               drawstring_aspect(pos, player_text, vec2(0.5 * mySize.x, mySize.y), player_color, panel_fg_alpha, DRAWFLAG_NORMAL);
+               drawstring_aspect(pos + eX * (0.5 * mySize.x), time_text, vec2(0.5 * mySize.x, mySize.y), timer_color, panel_fg_alpha, DRAWFLAG_NORMAL);
+       }
+}
+
+REGISTER_MUTATOR(cl_ttt, true);
+
+MUTATOR_HOOKFUNCTION(cl_ttt, ForcePlayercolors_Skip, CBC_ORDER_LAST)
+{
+       if(!ISGAMETYPE(TTT))
+               return false;
+               
+       entity player = M_ARGV(0, entity);
+       entity e = entcs_receiver(player.entnum - 1);
+       int innocent_status = ((e) ? e.ttt_status : 0);
+       int mystatus = entcs_receiver(player_localnum).ttt_status;
+
+       int plcolor = TTT_COLOR_INNOCENT; // default to innocent
+       //TODO: add Detective 20-02-2021
+       if((mystatus == TTT_STATUS_TRAITOR || intermission || STAT(GAME_STOPPED)) && innocent_status == TTT_STATUS_TRAITOR)
+               plcolor = TTT_COLOR_TRAITOR;
+               
+
+       player.colormap = 1024 + plcolor;
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(cl_ttt, DrawScoreboard_Force)
+{
+       // show the scoreboard when the round ends, so players can see who the hunter was
+       return STAT(GAME_STOPPED);
+}
diff --git a/qcsrc/common/gamemodes/gamemode/ttt/cl_ttt.qh b/qcsrc/common/gamemodes/gamemode/ttt/cl_ttt.qh
new file mode 100644 (file)
index 0000000..508ddf2
--- /dev/null
@@ -0,0 +1,3 @@
+#pragma once
+
+void HUD_Mod_TTT(vector pos, vector mySize);
diff --git a/qcsrc/common/gamemodes/gamemode/ttt/sv_ttt.qc b/qcsrc/common/gamemodes/gamemode/ttt/sv_ttt.qc
new file mode 100644 (file)
index 0000000..4730ec2
--- /dev/null
@@ -0,0 +1,460 @@
+#include "sv_ttt.qh"
+
+float autocvar_g_ttt_traitor_count = 0.25;
+float autocvar_g_ttt_round_timelimit = 180;
+float autocvar_g_ttt_warmup = 10;
+bool autocvar_g_ttt_punish_teamkill = true;
+bool autocvar_g_ttt_reward_innocent = true;
+
+//TODO: add Detective and corpse function if a player is DEAD
+void ttt_FakeTimeLimit(entity e, float t)
+{
+       if(!IS_REAL_CLIENT(e))
+               return;
+#if 0
+       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);
+#else
+       STAT(TTT_ROUNDTIMER, e) = t;
+#endif
+}
+
+void nades_Clear(entity player);
+
+void Ttt_UpdateScores(bool timed_out)
+{
+       // give players their hard-earned kills now that the round is over
+       FOREACH_CLIENT(true,
+       {
+               it.totalfrags += it.ttt_validkills;
+               if(it.ttt_validkills)
+                       GameRules_scoring_add(it, SCORE, it.ttt_validkills);
+               it.ttt_validkills = 0;
+               // player survived the round
+               if(IS_PLAYER(it) && !IS_DEAD(it))
+               {
+                       if(autocvar_g_ttt_reward_innocent && timed_out && it.ttt_status == TTT_STATUS_INNOCENT)
+                               GameRules_scoring_add(it, SCORE, 1); // reward innocents who make it to the end of the round time limit
+                       if(it.ttt_status == TTT_STATUS_INNOCENT)
+                               GameRules_scoring_add(it, TTT_RESISTS, 1);
+                       else if(it.ttt_status == TTT_STATUS_TRAITOR)
+                               GameRules_scoring_add(it, TTT_HUNTS, 1);
+               }
+       });
+}
+
+float Ttt_CheckWinner()
+{
+       if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
+       {
+               // if the match times out, innocents win too!
+               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_TTT_INNOCENT_WIN);
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_TTT_INNOCENT_WIN);
+               FOREACH_CLIENT(true,
+               {
+                       if(IS_PLAYER(it))
+                               nades_Clear(it);
+                       ttt_FakeTimeLimit(it, -1);
+               });
+
+               Ttt_UpdateScores(true);
+
+               allowed_to_spawn = false;
+               game_stopped = true;
+               round_handler_Init(5, autocvar_g_ttt_warmup, autocvar_g_ttt_round_timelimit);
+               return 1;
+       }
+
+       int innocent_count = 0, traitor_count = 0;
+       FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
+       {
+               if(it.ttt_status == TTT_STATUS_INNOCENT)
+                       innocent_count++;
+               else if(it.ttt_status == TTT_STATUS_TRAITOR)
+                       traitor_count++;
+       });
+       if(innocent_count > 0 && traitor_count > 0)
+       {
+               return 0;
+       }
+
+       if(traitor_count > 0) // traitors win
+       {
+               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_TTT_TRAITOR_WIN);
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_TTT_TRAITOR_WIN);
+       }
+       else if(innocent_count > 0) // innocents win
+       {
+               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_TTT_INNOCENT_WIN);
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_TTT_INNOCENT_WIN);
+       }
+       else
+       {
+               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
+       }
+
+       Ttt_UpdateScores(false);
+
+       allowed_to_spawn = false;
+       game_stopped = true;
+       round_handler_Init(5, autocvar_g_ttt_warmup, autocvar_g_ttt_round_timelimit);
+
+       FOREACH_CLIENT(true,
+       {
+               if(IS_PLAYER(it))
+                       nades_Clear(it);
+               ttt_FakeTimeLimit(it, -1);
+       });
+
+       return 1;
+}
+
+void Ttt_RoundStart()
+{
+       allowed_to_spawn = boolean(warmup_stage);
+       int playercount = 0;
+       FOREACH_CLIENT(true,
+       {
+               if(IS_PLAYER(it) && !IS_DEAD(it))
+               {
+                       ++playercount;
+                       it.ttt_status = TTT_STATUS_INNOCENT;
+               }
+               else
+                       it.ttt_status = 0; // this is mostly a safety check; if a client manages to somehow maintain a ttt status, clear it before the round starts!
+               it.ttt_validkills = 0;
+       });
+       int traitor_count = bound(1, ((autocvar_g_ttt_traitor_count >= 1) ? autocvar_g_ttt_traitor_count : floor(playercount * autocvar_g_ttt_traitor_count)), playercount - 1); // 20%, but ensure at least 1 and less than total
+       int total_traitors = 0;
+       FOREACH_CLIENT_RANDOM(IS_PLAYER(it) && !IS_DEAD(it),
+       {
+               if(total_traitors >= traitor_count)
+                       break;
+               total_traitors++;
+               it.ttt_status = TTT_STATUS_TRAITOR;
+       });
+
+       FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
+       {
+               if(it.ttt_status == TTT_STATUS_INNOCENT)
+                       Send_Notification(NOTIF_ONE_ONLY, it, MSG_CENTER, CENTER_TTT_INNOCENT);
+               else if(it.ttt_status == TTT_STATUS_TRAITOR)
+                       Send_Notification(NOTIF_ONE_ONLY, it, MSG_CENTER, CENTER_TTT_TRAITOR);
+
+               ttt_FakeTimeLimit(it, round_handler_GetEndTime());
+       });
+}
+
+bool Ttt_CheckPlayers()
+{
+       static int prev_missing_players;
+       allowed_to_spawn = true;
+       int playercount = 0;
+       FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
+       {
+               ++playercount;
+       });
+       if (playercount >= 2)
+       {
+               if(prev_missing_players > 0)
+                       Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_PLAYERS);
+               prev_missing_players = -1;
+               return true;
+       }
+       if(playercount == 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 ttt_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 ttt_Initialize() // run at the start of a match, initiates game mode
+{
+       GameRules_scoring(0, SFL_SORT_PRIO_PRIMARY, 0, {
+               field(SP_TTT_RESISTS, "resists", 0);
+               field(SP_TTT_HUNTS, "hunts", SFL_SORT_PRIO_SECONDARY);
+       });
+
+       allowed_to_spawn = true;
+       round_handler_Spawn(Ttt_CheckPlayers, Ttt_CheckWinner, Ttt_RoundStart);
+       round_handler_Init(5, autocvar_g_ttt_warmup, autocvar_g_ttt_round_timelimit);
+       EliminatedPlayers_Init(ttt_isEliminated);
+}
+
+
+// ==============
+// Hook Functions
+// ==============
+
+MUTATOR_HOOKFUNCTION(ttt, ClientObituary)
+{
+       // in ttt, announcing a frag would tell everyone who the traitor is
+       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.ttt_status == frag_target.ttt_status)
+                       GiveFrags(frag_attacker, frag_target, ((autocvar_g_ttt_punish_teamkill) ? -1 : -2), frag_deathtype, wep_ent.weaponentity_fld);
+       }
+
+       M_ARGV(5, bool) = true; // anonymous attacker
+}
+
+MUTATOR_HOOKFUNCTION(ttt, PlayerPreThink)
+{
+       entity player = M_ARGV(0, entity);
+
+       if(IS_PLAYER(player) || player.caplayer)
+       {
+               // update the scoreboard colour display to out the real killer at the end of the round
+               // running this every frame to avoid cheats
+               int plcolor = TTT_COLOR_INNOCENT;
+               if(player.ttt_status == TTT_STATUS_TRAITOR && game_stopped)
+                       plcolor = TTT_COLOR_TRAITOR;
+               setcolor(player, plcolor);
+       }
+}
+
+MUTATOR_HOOKFUNCTION(ttt, PlayerSpawn)
+{
+       entity player = M_ARGV(0, entity);
+
+       player.ttt_status = 0;
+       player.ttt_validkills = 0;
+       player.caplayer = 1;
+       if (!warmup_stage)
+               eliminatedPlayers.SendFlags |= 1;
+}
+
+MUTATOR_HOOKFUNCTION(ttt, 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(ttt, 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(ttt, reset_map_players)
+{
+       FOREACH_CLIENT(true, {
+               CS(it).killcount = 0;
+               it.ttt_status = 0;
+               ttt_FakeTimeLimit(it, -1); // restore original timelimit
+               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(ttt, reset_map_global)
+{
+       allowed_to_spawn = true;
+       return true;
+}
+
+entity ttt_LastPlayerForTeam(entity this)
+{
+       entity last_pl = NULL;
+       FOREACH_CLIENT(IS_PLAYER(it) && it != this, {
+               if (!IS_DEAD(it) && this.ttt_status == it.ttt_status)
+               {
+                       if (!last_pl)
+                               last_pl = it;
+                       else
+                               return NULL;
+               }
+       });
+       return last_pl;
+}
+
+void ttt_LastPlayerForTeam_Notify(entity this)
+{
+       if (!warmup_stage && round_handler_IsActive() && round_handler_IsRoundStarted())
+       {
+               entity pl = ttt_LastPlayerForTeam(this);
+               if (pl)
+                       Send_Notification(NOTIF_ONE_ONLY, pl, MSG_CENTER, CENTER_ALONE);
+       }
+}
+
+MUTATOR_HOOKFUNCTION(ttt, PlayerDies)
+{
+       entity frag_attacker = M_ARGV(1, entity);
+       entity frag_target = M_ARGV(2, entity);
+       float frag_deathtype = M_ARGV(3, float);
+
+       ttt_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_ttt_punish_teamkill && frag_attacker != frag_target && IS_PLAYER(frag_attacker) && IS_PLAYER(frag_target) && frag_attacker.ttt_status == frag_target.ttt_status && !ITEM_DAMAGE_NEEDKILL(frag_deathtype))
+       if(!warmup_stage && round_handler_IsActive() && round_handler_IsRoundStarted()) // don't autokill if the round hasn't
+               Damage(frag_attacker, frag_attacker, frag_attacker, 100000, DEATH_MIRRORDAMAGE.m_id, DMG_NOWEP, frag_attacker.origin, '0 0 0');
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(ttt, ClientDisconnect)
+{
+       entity player = M_ARGV(0, entity);
+
+       if (IS_PLAYER(player) && !IS_DEAD(player))
+               ttt_LastPlayerForTeam_Notify(player);
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(ttt, MakePlayerObserver)
+{
+       entity player = M_ARGV(0, entity);
+
+       if (IS_PLAYER(player) && !IS_DEAD(player))
+               ttt_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.ttt_validkills = 0;
+               player.ttt_status = 0;
+               ttt_FakeTimeLimit(player, -1); // restore original timelimit
+               return false;  // allow team reset
+       }
+       return true;  // prevent team reset
+}
+
+MUTATOR_HOOKFUNCTION(ttt, Scores_CountFragsRemaining)
+{
+       // announce remaining frags?
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(ttt, GiveFragsForKill, CBC_ORDER_FIRST)
+{
+       entity frag_attacker = M_ARGV(0, entity);
+       if(!warmup_stage && round_handler_IsActive() && round_handler_IsRoundStarted())
+               frag_attacker.ttt_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(ttt, 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 traitor!
+}
+
+MUTATOR_HOOKFUNCTION(ttt, CalculateRespawnTime)
+{
+       // no respawn calculations needed, player is forced to spectate anyway
+       return true;
+}
+
+MUTATOR_HOOKFUNCTION(ttt, 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(ttt, 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(ttt, GetPlayerStatus)
+{
+       entity player = M_ARGV(0, entity);
+
+       return player.caplayer == 1;
+}
+
+MUTATOR_HOOKFUNCTION(ttt, BotShouldAttack)
+{
+       entity bot = M_ARGV(0, entity);
+       entity targ = M_ARGV(1, entity);
+
+       if(targ.ttt_status == bot.ttt_status)
+               return true;
+}
diff --git a/qcsrc/common/gamemodes/gamemode/ttt/sv_ttt.qh b/qcsrc/common/gamemodes/gamemode/ttt/sv_ttt.qh
new file mode 100644 (file)
index 0000000..20b300b
--- /dev/null
@@ -0,0 +1,17 @@
+#pragma once
+
+#include <common/mutators/base.qh>
+#include <common/scores.qh>
+void ttt_Initialize();
+
+REGISTER_MUTATOR(ttt, false)
+{
+    MUTATOR_STATIC();
+       MUTATOR_ONADD
+       {
+               ttt_Initialize();
+       }
+       return false;
+}
+
+.int ttt_validkills; // store the player's valid kills to be given at the end of the match (avoid exposing their score until then)
diff --git a/qcsrc/common/gamemodes/gamemode/ttt/ttt.qc b/qcsrc/common/gamemodes/gamemode/ttt/ttt.qc
new file mode 100644 (file)
index 0000000..0e43e09
--- /dev/null
@@ -0,0 +1 @@
+#include "ttt.qh"
diff --git a/qcsrc/common/gamemodes/gamemode/ttt/ttt.qh b/qcsrc/common/gamemodes/gamemode/ttt/ttt.qh
new file mode 100644 (file)
index 0000000..d2bc4ff
--- /dev/null
@@ -0,0 +1,44 @@
+#pragma once
+
+#include <common/gamemodes/gamemode/lms/lms.qh>
+#include <common/mapinfo.qh>
+
+#ifdef CSQC
+void HUD_Mod_TTT(vector pos, vector mySize);
+#endif
+CLASS(TroubleinTerroristTown, Gametype)
+    INIT(TroubleinTerroristTown)
+    {
+        this.gametype_init(this, _("Trouble in Terrorist Town"),"ttt","g_ttt",GAMETYPE_FLAG_USEPOINTS,"","timelimit=30 pointlimit=20",_("A group of space terrorists have traitors among them. Traitors must kill terrorists, while the terrorists have to try to find and kill the traitors"));
+    }
+    METHOD(TroubleinTerroristTown, m_isAlwaysSupported, bool(Gametype this, int spawnpoints, float diameter))
+    {
+        return true;
+    }
+    METHOD(TroubleinTerroristTown, m_isForcedSupported, bool(Gametype this))
+    {
+        if(!cvar("g_ttt_not_lms_maps"))
+        {
+            // if this is unset, all LMS maps support TroubleinTerroristTown too
+            if(!(MapInfo_Map_supportedGametypes & this.m_flags) && (MapInfo_Map_supportedGametypes & MAPINFO_TYPE_LMS.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(TroubleinTerroristTown, m_modicons, void(vector pos, vector mySize), HUD_Mod_TTT);
+#endif
+ENDCLASS(TroubleinTerroristTown)
+REGISTER_GAMETYPE(TTT, NEW(TroubleinTerroristTown));
+
+#ifdef GAMEQC
+// shared state signalling the player's ttt status
+//TODO: add Detective STATUS and COLOR 20-02-2021
+.int ttt_status;
+const int TTT_STATUS_INNOCENT = 1;
+const int TTT_STATUS_TRAITOR = 2;
+
+// hardcoded player colors for ttt
+const int TTT_COLOR_INNOCENT = 51; // green
+const int TTT_COLOR_TRAITOR = 68; // red
+#endif
index d692d73c8df82aa1f5463f31e03e50b20aeb8901..0e4bb7157e63ae3a7e023a5bc59d94f08b00243c 100644 (file)
@@ -439,6 +439,9 @@ string multiteam_info_sprintf(string input, string teamname) { return ((input !=
 
     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"), "")
+    //LegendGuard adds MSG_INFO_NOTIF for TTT 20-02-2021
+    MSG_INFO_NOTIF(TTT_TRAITOR_WIN,                         N_CONSOLE,  0, 0, "", "",           "",                     _("^K1Traitors^BG win the round"), "")
+    MSG_INFO_NOTIF(TTT_INNOCENT_WIN,                        N_CONSOLE,  0, 0, "", "",           "",                     _("^F1Innocents^BG win the round"), "")
 
     MSG_INFO_NOTIF(VERSION_BETA,                            N_CONSOLE,  2, 0, "s1 s2", "",      "",                     _("^F4NOTE: ^BGThe server is running ^F1Xonotic %s (beta)^BG, you have ^F2Xonotic %s"), "")
     MSG_INFO_NOTIF(VERSION_OLD,                             N_CHATCON,  2, 0, "s1 s2", "",      "",                     _("^F4NOTE: ^BGThe server is running ^F1Xonotic %s^BG, you have ^F2Xonotic %s"), "")
@@ -770,6 +773,11 @@ string multiteam_info_sprintf(string input, string teamname) { return ((input !=
 
     MSG_CENTER_NOTIF(TIMEOUT_BEGINNING,                 N_ENABLE,    0, 1, "",               CPID_TIMEOUT,           "1 f1", _("^F4Timeout begins in ^COUNT"), "")
     MSG_CENTER_NOTIF(TIMEOUT_ENDING,                    N_ENABLE,    0, 1, "",               CPID_TIMEIN,            "1 f1", _("^F4Timeout ends in ^COUNT"), "")
+    //LegendGuard adds MSG_CENTER_NOTIF for TTT 20-02-2021
+    MSG_CENTER_NOTIF(TTT_TRAITOR,                       N_ENABLE,    0, 0, "",               CPID_TTT,               "5 0",  strcat(BOLD_OPERATOR, _("^BGYou are ^K1traitor^BG! Kill all the innocents without raising suspicion!")), "")
+    MSG_CENTER_NOTIF(TTT_TRAITOR_WIN,                   N_ENABLE,    0, 0, "",               CPID_ROUND,             "0 0",  _("^K1Traitors^BG win the round"), "")
+    MSG_CENTER_NOTIF(TTT_INNOCENT,                      N_ENABLE,    0, 0, "",               CPID_TTT,               "5 0",  strcat(BOLD_OPERATOR, _("^BGYou are ^F1innocent^BG! Try to find out who is/are traitor(s) and survive until time is up!")), "")
+    MSG_CENTER_NOTIF(TTT_INNOCENT_WIN,                  N_ENABLE,    0, 0, "",               CPID_ROUND,             "0 0",  _("^F1Innocents^BG win the round"), "")
 
     MSG_CENTER_NOTIF(JOIN_PREVENT_MINIGAME,             N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  _("^K1Cannot join given minigame session!"), "" )
 
index 72017e3dc8a3efd83a2cebeb521ddd5a3b99ac50..8c921055af609ce4e7445366f8a1595299e30bb7 100644 (file)
@@ -40,7 +40,7 @@ string Get_Notif_TypeName(MSG net_type)
        LOG_WARNF("Get_Notif_TypeName(%d): Improper net type!", ORDINAL(net_type));
        return "";
 }
-
+//LegendGuard adds CASE(CPID, TTT) after TIMEIN for TTT 20-02-2021
 ENUMCLASS(CPID)
        CASE(CPID, ASSAULT_ROLE)
        CASE(CPID, ROUND)
@@ -72,6 +72,7 @@ ENUMCLASS(CPID)
        CASE(CPID, TEAMCHANGE)
        CASE(CPID, TIMEOUT)
        CASE(CPID, TIMEIN)
+       CASE(CPID, TTT)
        CASE(CPID, VEHICLES)
        CASE(CPID, VEHICLES_OTHER)
        /** always last */
index 3bc6c55636c65fa4e3875d0bf2b328e0f3a0aa02..84d992ac816a79e089b642a43b55e1f043fa2c75 100644 (file)
@@ -85,6 +85,10 @@ REGISTER_SP(NEXBALL_FAULTS);
 
 REGISTER_SP(ONS_TAKES);
 REGISTER_SP(ONS_CAPS);
+
+REGISTER_SP(TTT_RESISTS); //LegendGuard adds REGISTER_SP for TTT 20-02-2021 //Innocents as number of suvivals
+REGISTER_SP(TTT_HUNTS); //Traitors as number of hunts
+
 #endif
 
 
index 67f7fd60c479217269a4572d236e7f8d84cd8abe..880b378c84aefb4b80b652e2ba8ca733a52609c3 100644 (file)
@@ -434,3 +434,5 @@ REGISTER_STAT(GUNALIGN, int)
 #ifdef SVQC
 SPECTATE_COPYFIELD(_STAT(GUNALIGN))
 #endif
+
+REGISTER_STAT(TTT_ROUNDTIMER, float) //LegendGuard adds TTT_ROUNDTIMER for TTT 20-02-2021
index e77049d200153e9c2f15fb0ca097edd1c1649fe3..5a98716bde92199d711a03e743d71b818c919c02 100644 (file)
@@ -683,6 +683,8 @@ float updateCompression()
        GAMETYPE(MAPINFO_TYPE_ASSAULT) \
        /* GAMETYPE(MAPINFO_TYPE_DUEL) */ \
        /* GAMETYPE(MAPINFO_TYPE_INVASION) */ \
+       GAMETYPE(MAPINFO_TYPE_TTT) \
+       //LegendGuard adds GAMETYPE for menu for TTT 20-02-2021
        /**/
 
 // hidden gametypes come last so indexing always works correctly
index 9da86388c1f22936b91de84c88ee9bae265b4b03..91cb13810a1e6e6bf3fac3033f2902bdf2bc4a1b 100644 (file)
@@ -301,6 +301,8 @@ void cvar_changes_init()
                BADCVAR("g_tdm");
                BADCVAR("g_tdm_on_dm_maps");
                BADCVAR("g_tdm_teams");
+               BADCVAR("g_ttt");
+               BADCVAR("g_ttt_not_dm_maps");
                BADCVAR("g_vip");
                BADCVAR("leadlimit");
                BADCVAR("nextmap");
@@ -310,6 +312,7 @@ void cvar_changes_init()
                BADCVAR("g_mapinfo_ignore_warnings");
                BADCVAR("g_maplist_ignore_sizes");
                BADCVAR("g_maplist_sizes_count_bots");
+               //LegendGuard adds BADCVAR(g_*) for TTT 20-02-2021
 
                // long
                BADCVAR("hostname");