3 //set g_ttt_detective_count 0.125 "number of players who will become detectives, 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"
4 //float autocvar_g_ttt_detective_count = 0.125; //I don't think that it won't be used...
5 float autocvar_g_ttt_innocent_count = 0.625;
6 //float autocvar_g_ttt_traitor_count = 0.25;
7 float autocvar_g_ttt_round_timelimit = 180;
8 float autocvar_g_ttt_warmup = 10;
9 bool autocvar_g_ttt_punish_teamkill = false;
10 bool autocvar_g_ttt_reward_innocent = true;
11 bool autocvar_g_ttt_reward_detective = true; //detective reward if investigated corpses
12 float autocvar_g_ttt_max_karma_points = 1000; //LegendGuard sets Karma points 21-02-2021
13 float autocvar_g_ttt_min_karma_points = 400;
14 int autocvar_g_ttt_karma_bankick_tool = 0; //LegendGuard sets a ban tool for server admins 11-03-2021
15 float autocvar_g_ttt_karma_bantime = 1800; //karma ban seconds
16 bool autocvar_g_ttt_karma_damageactive = true; //LegendGuard sets Karma damage setting if active 20-03-2021
20 // Add for the corpse a role of who killed 22-03-2021
22 // Detective is a created team, this team is added inside Innocents team
25 // detective shouldn't be attacked by innocent bots
27 void ttt_FakeTimeLimit(entity e, float t)
29 if(!IS_REAL_CLIENT(e))
33 WriteByte(MSG_ONE, 3); // svc_updatestat
34 WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
36 WriteCoord(MSG_ONE, autocvar_timelimit);
38 WriteCoord(MSG_ONE, (t + 1) / 60);
40 STAT(TTT_ROUNDTIMER, e) = t;
44 void nades_Clear(entity player);
46 void karma_Control(entity it)
48 float masksize = autocvar_g_ban_default_masksize;
49 float bantime = autocvar_g_ttt_karma_bantime;
50 if(it.karmapoints >= autocvar_g_ttt_max_karma_points)
52 //Resets karmapoints to maintain the maximum
53 //PrintToChatAll("^3REWARD ^1MAXIMUM RESET");
54 GameRules_scoring_add(it, TTT_KARMA, autocvar_g_ttt_max_karma_points - it.karmapoints);
55 it.karmapoints = autocvar_g_ttt_max_karma_points;
57 else if(it.karmapoints <= autocvar_g_ttt_min_karma_points)
59 switch (autocvar_g_ttt_karma_bankick_tool)
64 PutObserverInServer(it);
76 Ban_KickBanClient(it, bantime, masksize, "Too low karma");
82 PutObserverInServer(it);
89 void karmaLoseDifference(entity attacker, entity target)
91 //BASIC MATH THEORY: example: 1000 * 0.3 * (0.1 + 0.4) * 0.25 // karma points reduce when player attacked to other player
92 if (target.karmapoints < attacker.karmapoints)
94 float decreasekarma = - ( target.karmapoints * random() * ( 0.1 + random() ) * 0.25 );
95 GameRules_scoring_add(attacker, TTT_KARMA, decreasekarma);
96 attacker.karmapoints = attacker.karmapoints + decreasekarma;
98 else if (target.karmapoints > attacker.karmapoints)
100 float decreasekarma = - ( target.karmapoints * random() * ( 0.1 + random() ) * 0.25 );
101 GameRules_scoring_add(attacker, TTT_KARMA, decreasekarma);
102 attacker.karmapoints = attacker.karmapoints + decreasekarma;
106 float decreasekarma = - ( target.karmapoints * random() * ( 0.1 + random() ) * 0.25 );
107 GameRules_scoring_add(attacker, TTT_KARMA, decreasekarma);
108 attacker.karmapoints = attacker.karmapoints + decreasekarma;
112 void karmaWinDifference(entity it)
114 GameRules_scoring_add(it, SCORE, 1); // reward innocents who make it to the end of the round time limit
115 float increasekarma = ( autocvar_g_ttt_min_karma_points * random() * ( 0.1 + random() ) * 0.12 );
116 GameRules_scoring_add(it, TTT_KARMA, increasekarma);
117 it.karmapoints = it.karmapoints + increasekarma;
120 void ttt_UpdateScores(bool timed_out)
122 // give players their hard-earned kills now that the round is over
125 it.totalfrags += it.ttt_validkills;
126 if(it.ttt_validkills)
128 GameRules_scoring_add(it, SCORE, it.ttt_validkills);
130 it.ttt_validkills = 0;
131 // player survived the round
132 if(IS_PLAYER(it) && !IS_DEAD(it)) // LegendGuard adds something for Karma 21-02-2021
134 if((autocvar_g_ttt_reward_innocent && timed_out && it.ttt_status == TTT_STATUS_INNOCENT)
135 || (autocvar_g_ttt_reward_innocent && !timed_out && it.ttt_status == TTT_STATUS_INNOCENT))
137 karmaWinDifference(it);
138 //PrintToChatAll(sprintf("^2REWARD ^7it.karmapoints: ^1%f", it.karmapoints));
142 //Detective reward after investigated a corpse
143 if((autocvar_g_ttt_reward_detective && timed_out && it.ttt_status == TTT_STATUS_DETECTIVE)
144 || (autocvar_g_ttt_reward_detective && !timed_out && it.ttt_status == TTT_STATUS_DETECTIVE))
146 if (it.investigated == true)
148 karmaWinDifference(it);
149 it.investigated = false;
154 if(it.ttt_status == TTT_STATUS_INNOCENT)
156 GameRules_scoring_add(it, TTT_RESISTS, 1);
157 karmaWinDifference(it);
158 //PrintToChatAll(sprintf("^2INNOCENT ^7it.karmapoints: ^1%f", it.karmapoints));
161 else if(it.ttt_status == TTT_STATUS_TRAITOR)
163 karmaWinDifference(it);
164 //PrintToChatAll(sprintf("^1TRAITOR ^7it.karmapoints: ^1%f", it.karmapoints));
171 float ttt_CheckWinner()
173 if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
175 // if the match times out, innocents win too!
176 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_TTT_INNOCENT_WIN);
177 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_TTT_INNOCENT_WIN);
182 ttt_FakeTimeLimit(it, -1);
185 ttt_UpdateScores(true);
187 allowed_to_spawn = false;
189 round_handler_Init(5, autocvar_g_ttt_warmup, autocvar_g_ttt_round_timelimit);
193 int innocent_count = 0, traitor_count = 0, detective_count = 0;
194 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
196 if(it.ttt_status == TTT_STATUS_INNOCENT)
198 else if(it.ttt_status == TTT_STATUS_TRAITOR)
200 else if(it.ttt_status == TTT_STATUS_DETECTIVE) //LegendGuard adds detective_count 20-02-2021
203 if(innocent_count > 0 && traitor_count > 0)
208 if(traitor_count > 0) // traitors win
210 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_TTT_TRAITOR_WIN);
211 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_TTT_TRAITOR_WIN);
213 else if(innocent_count > 0) // innocents win
215 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_TTT_INNOCENT_WIN);
216 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_TTT_INNOCENT_WIN);
218 else if (detective_count > 0 && innocent_count > 0) // detectives are same as innocents win
220 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_TTT_INNOCENT_WIN);
221 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_TTT_INNOCENT_WIN);
225 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
226 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
229 ttt_UpdateScores(false);
231 allowed_to_spawn = false;
233 round_handler_Init(5, autocvar_g_ttt_warmup, autocvar_g_ttt_round_timelimit);
239 it.respawn_flags = RESPAWN_SILENT; //CSQC print output respawn lib.qh error fix
242 ttt_FakeTimeLimit(it, -1);
248 void ttt_RoundStart()
250 allowed_to_spawn = boolean(warmup_stage);
255 if(IS_PLAYER(it) && !IS_DEAD(it))
258 it.ttt_status = TTT_STATUS_INNOCENT;
261 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!
262 it.ttt_validkills = 0;
265 int innocent_count = bound(1, ((autocvar_g_ttt_innocent_count >= 1) ? autocvar_g_ttt_innocent_count : floor(playercount * autocvar_g_ttt_innocent_count)), playercount - 1); // 20%, but ensure at least 1 and less than total
266 int total_innocents = 0;
267 //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
268 int total_traitors = 0;
269 //int detective_count = bound(1, ((autocvar_g_ttt_detective_count >= 1) ? autocvar_g_ttt_detective_count : floor(playercount * autocvar_g_ttt_detective_count)), playercount - 1); // 20%, but ensure at least 1 and less than total
270 int total_detectives = 0;
273 FOREACH_CLIENT_RANDOM(IS_PLAYER(it) && !IS_DEAD(it),
275 if(total_innocents >= innocent_count)
277 //LegendGuard fixes the round start again 22-03-2021
279 if (total_innocents <= 1)
281 if (total_traitors <= 1)
284 it.ttt_status = TTT_STATUS_TRAITOR;
287 else if (total_innocents == 2)
289 if (total_detectives >= 1)
294 it.ttt_status = TTT_STATUS_DETECTIVE;
297 else if (total_innocents == 5)
299 if (total_detectives >= 2)
304 it.ttt_status = TTT_STATUS_DETECTIVE;
307 else if (total_innocents >= 7)
309 if (total_detectives >= 3)
311 else if (total_traitors == 3)
314 it.ttt_status = TTT_STATUS_TRAITOR;
319 it.ttt_status = TTT_STATUS_DETECTIVE;
323 it.ttt_status = TTT_STATUS_TRAITOR;
326 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
330 if(it.ttt_status == TTT_STATUS_INNOCENT)
332 //Gets Mine Layer weapon to the player
333 SetResource(it, RES_SHELLS, 50);
334 SetResource(it, RES_BULLETS, 70);
335 SetResource(it, RES_ROCKETS, 30);
336 SetResource(it, RES_CELLS, 60);
337 GiveWeapon(it, WEP_MINE_LAYER.m_id, OP_PLUS, 1);
338 Send_Notification(NOTIF_ONE_ONLY, it, MSG_CENTER, CENTER_TTT_INNOCENT);
339 Send_Notification(NOTIF_ONE_ONLY, it, MSG_INFO, INFO_TTT_INNOCENT);
340 //PrintToChatAll(sprintf("^1DEBUG^7: %s is ^2Innocent^7!", it.netname));
342 else if(it.ttt_status == TTT_STATUS_TRAITOR)
344 //Gets Mine Layer weapon to the player
345 SetResource(it, RES_SHELLS, 20);
346 SetResource(it, RES_BULLETS, 60);
347 SetResource(it, RES_ROCKETS, 20);
348 SetResource(it, RES_CELLS, 40);
349 GiveWeapon(it, WEP_MINE_LAYER.m_id, OP_PLUS, 1);
350 Send_Notification(NOTIF_ONE_ONLY, it, MSG_CENTER, CENTER_TTT_TRAITOR);
351 Send_Notification(NOTIF_ONE_ONLY, it, MSG_INFO, INFO_TTT_TRAITOR);
352 //PrintToChatAll(sprintf("^1DEBUG^7: %s is ^1Traitor^7!", it.netname));
354 else if(it.ttt_status == TTT_STATUS_DETECTIVE)
356 //Gets Shockwave and Mine Layer weapon to the player
357 SetResource(it, RES_ROCKETS, 20);
358 GiveWeapon(it, WEP_SHOCKWAVE.m_id, OP_PLUS, 1);
359 GiveWeapon(it, WEP_MINE_LAYER.m_id, OP_PLUS, 1);
360 Send_Notification(NOTIF_ONE_ONLY, it, MSG_CENTER, CENTER_TTT_DETECTIVE);
361 Send_Notification(NOTIF_ONE_ONLY, it, MSG_INFO, INFO_TTT_DETECTIVE);
362 PrintToChatAll(sprintf("%s is ^4Detective^7!", it.netname));
364 ttt_FakeTimeLimit(it, round_handler_GetEndTime());
368 bool ttt_CheckPlayers()
370 static int prev_missing_players;
371 allowed_to_spawn = true;
374 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
376 //PrintToChatAll(sprintf("it.karmapoints ^5begin: ^3%f",it.karmapoints));
378 if (it.karmastarted != true)
380 GameRules_scoring_add(it, TTT_KARMA, autocvar_g_ttt_max_karma_points - it.karmapoints);
381 it.karmapoints = autocvar_g_ttt_max_karma_points;
382 it.karmastarted = true;
386 //PrintToChatAll(sprintf("it.karmapoints ^6end: ^3%f",it.karmapoints));
389 if (playercount >= 2)
391 if(prev_missing_players > 0)
392 Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_PLAYERS);
393 prev_missing_players = -1;
399 if(prev_missing_players > 0)
400 Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_PLAYERS);
401 prev_missing_players = -1;
405 // if we get here, only 1 player is missing
406 if(prev_missing_players != 1)
408 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_MISSING_PLAYERS, 1);
409 prev_missing_players = 1;
414 bool ttt_isEliminated(entity e)
416 if(e.caplayer == 1 && (IS_DEAD(e) || e.frags == FRAGS_PLAYER_OUT_OF_GAME))
418 if(e.caplayer == 0.5)
423 void ttt_Initialize() // run at the start of a match, initiates game mode
425 GameRules_scoring(0, SFL_SORT_PRIO_PRIMARY, 0, {
426 field(SP_TTT_RESISTS, "resists", 0);
427 field(SP_TTT_KARMA, "karma", SFL_SORT_PRIO_SECONDARY); //LegendGuard adds Karma points in the scoreboard 22-02-2021
430 allowed_to_spawn = true;
431 round_handler_Spawn(ttt_CheckPlayers, ttt_CheckWinner, ttt_RoundStart);
432 round_handler_Init(5, autocvar_g_ttt_warmup, autocvar_g_ttt_round_timelimit);
433 EliminatedPlayers_Init(ttt_isEliminated);
436 void checkWeaponDeathtype(entity target, float deathtype)
440 case WEP_ARC.m_id: case 276: case 788: target.killedwithweapon = "Impacted by the Arc's electric shock"; return;
441 case WEP_BLASTER.m_id: case 513: target.killedwithweapon = "Blasted by the Blaster"; return;
442 case WEP_CRYLINK.m_id: case 263: case 519: target.killedwithweapon = "Shot by the Crylink"; return;
443 case WEP_DEVASTATOR.m_id: case 522: target.killedwithweapon = "Bombarded by the Devastator"; return;
444 case WEP_ELECTRO.m_id: case 262: case 518: case 1542: target.killedwithweapon = "Electrocuted by the Electro"; return;
445 case WEP_FIREBALL.m_id: case 273: case 529: case 1297: target.killedwithweapon = "Burned by the Fireball"; return;
446 case WEP_HAGAR.m_id: target.killedwithweapon = "Gunned by the Hagar"; return;
447 case WEP_HOOK.m_id: case 1805: target.killedwithweapon = "Caught in Hook gravity bomb"; return;
448 case WEP_MACHINEGUN.m_id: target.killedwithweapon = "Riddled full of holes by the Machine Gun"; return;
449 case WEP_MINE_LAYER.m_id: target.killedwithweapon = "Exploited by the Mine Layer"; return;
450 case WEP_MORTAR.m_id: case 516: case 1284: target.killedwithweapon = "Blew up with the Mortar"; return;
451 case WEP_RIFLE.m_id: target.killedwithweapon = "Sniped by the Rifle"; return;
452 case WEP_SEEKER.m_id: target.killedwithweapon = "Blasted by the Seeker"; return;
453 case WEP_SHOCKWAVE.m_id: target.killedwithweapon = "Gunned down by the Shockwave"; return;
454 case 275: target.killedwithweapon = "Knocked by the Shockwave"; return;
455 case WEP_SHOTGUN.m_id: target.killedwithweapon = "Shot by Shotgun"; return;
456 case 258: target.killedwithweapon = "Knocked by the Shotgun"; return;
457 case WEP_TUBA.m_id: target.killedwithweapon = "Ear pain by the @!#%'n Tuba"; return;
458 case WEP_VAPORIZER.m_id: case 257: case 769: target.killedwithweapon = "Sniped by the Vaporizer"; return;
459 case WEP_VORTEX.m_id: target.killedwithweapon = "Sniped by the Vortex"; return;
460 case DEATH_FALL.m_id: target.killedwithweapon = "Fall"; return;
461 case DEATH_FIRE.m_id: target.killedwithweapon = "Burned with the fire"; return;
462 case DEATH_LAVA.m_id: target.killedwithweapon = "Burned in lava"; return;
463 case DEATH_MIRRORDAMAGE.m_id: target.killedwithweapon = "Suicide"; return;
464 case DEATH_SLIME.m_id: target.killedwithweapon = "Melted in slime"; return;
465 case DEATH_TELEFRAG.m_id: target.killedwithweapon = "Telefragged"; return;
466 default: target.killedwithweapon = "Unknown"; return;
470 void ReduceKarmaPointsandFrags(entity frag_attacker, entity frag_target, float frag_deathtype, entity wep_ent)
472 karmaLoseDifference(frag_attacker, frag_target);
473 GiveFrags(frag_attacker, frag_target, ((autocvar_g_ttt_punish_teamkill) ? -1 : -2), frag_deathtype, wep_ent.weaponentity_fld);
474 karma_Control(frag_attacker);
475 frag_target.whokilled = frag_attacker.netname;
482 MUTATOR_HOOKFUNCTION(ttt, ClientObituary)
484 // LegendGuard's IDEA: To adjust the grade of severity of karma,
485 // we could add if sentence per weapons and adjust each weapon attack
486 // its own grade. Instead doing random decrease grade 22-02-2021
488 // in ttt, announcing a frag would tell everyone who the traitor is
489 entity frag_attacker = M_ARGV(1, entity);
490 entity frag_target = M_ARGV(2, entity);
492 if(IS_PLAYER(frag_attacker) && frag_attacker != frag_target)
494 float frag_deathtype = M_ARGV(3, float);
495 entity wep_ent = M_ARGV(4, entity);
497 //PrintToChatAll(strcat("deathtype var: ", ftos(frag_deathtype)));
498 checkWeaponDeathtype(frag_target, frag_deathtype);
499 // "team" kill, a point is awarded to the player by default so we must take it away plus an extra one
500 // unless the player is going to be punished for suicide, in which case just remove one
501 if(frag_attacker.ttt_status == frag_target.ttt_status)
503 //PrintToChatAll("^1DEBUG^7: A ^2PLAYER^7 has fragged a ^2PLAYER OF HIS OWN TEAM^7, TOO BAD!");
504 ReduceKarmaPointsandFrags(frag_attacker, frag_target, frag_deathtype, wep_ent);
505 //PrintToChatAll(sprintf("frag_attacker.karmapoints: ^1%f", frag_attacker.karmapoints));
508 if(frag_attacker.ttt_status == TTT_STATUS_DETECTIVE)
510 if (frag_target.ttt_status == TTT_STATUS_INNOCENT || frag_target.ttt_status == TTT_STATUS_DETECTIVE)
512 //PrintToChatAll("^1DEBUG^7: A ^4Detective^7 fragged an ^2Innocent^7/^4Detective^7, TOO BAD!");
513 ReduceKarmaPointsandFrags(frag_attacker, frag_target, frag_deathtype, wep_ent);
514 //PrintToChatAll(sprintf("frag_attacker.karmapoints: ^1%f", frag_attacker.karmapoints));
518 frag_target.whokilled = frag_attacker.netname;
522 if (frag_attacker.ttt_status == TTT_STATUS_INNOCENT)
524 if (frag_target.ttt_status == TTT_STATUS_DETECTIVE)
526 //PrintToChatAll("^1DEBUG^7: An ^2Innocent^7 fragged a ^4Detective^7, TOO BAD!");
527 ReduceKarmaPointsandFrags(frag_attacker, frag_target, frag_deathtype, wep_ent);
531 frag_target.whokilled = frag_attacker.netname;
535 if (frag_attacker.ttt_status == TTT_STATUS_TRAITOR)
537 if (frag_target.ttt_status == TTT_STATUS_INNOCENT)
539 frag_target.whokilled = frag_attacker.netname;
543 frag_target.whokilled = frag_attacker.netname;
546 //if ttt_status is 1, means innocent, 2 means traitor, 3 means detective, TODO: the bots: frag_attacker(1) shouldn't attack to frag_target(3)
547 //PrintToChatAll(sprintf("^1DEBUG^7: frag_attacker.ttt_status is ^3%s^7",ftos(frag_attacker.ttt_status)));
548 //PrintToChatAll(sprintf("^1DEBUG^7: frag_target.ttt_status is ^3%s^7",ftos(frag_target.ttt_status)));
551 //TODO: try to do a "find out" if a detective can see who fragged to who if possible 21-02-2021
552 M_ARGV(5, bool) = true; // anonymous attacker
555 //karma weapon damage, halve the damage attack when player has low karma 20-03-2021
556 MUTATOR_HOOKFUNCTION(ttt, Damage_Calculate)
558 entity attacker = M_ARGV(1, entity);
559 entity target = M_ARGV(2, entity);
560 float deathtype = M_ARGV(3, float);
561 float damage = M_ARGV(4, float);
562 vector force = M_ARGV(6, vector);
563 string corpsemessagestrcat = "";
565 if (autocvar_g_ttt_karma_damageactive != false)
567 if (IS_PLAYER(attacker))
569 if(target == attacker) // damage done to yourself
571 damage /= autocvar_g_weapondamagefactor / (attacker.karmapoints / autocvar_g_ttt_max_karma_points);
572 force /= autocvar_g_weaponforcefactor / (attacker.karmapoints / autocvar_g_ttt_max_karma_points);
574 else if (target != attacker)
576 damage /= autocvar_g_weapondamagefactor / (attacker.karmapoints / autocvar_g_ttt_max_karma_points);
577 force /= autocvar_g_weaponforcefactor / (attacker.karmapoints / autocvar_g_ttt_max_karma_points);
581 damage *= autocvar_g_weapondamagefactor;
582 force *= autocvar_g_weaponforcefactor;
587 //DETECTIVE CORPSE DETECTION SKILL 21-03-2021
588 if (attacker.ttt_status == TTT_STATUS_DETECTIVE)
592 //Shockwave weapon as radar gun to check the corpses 22-03-2021
593 if(DEATH_ISWEAPON(deathtype, WEP_SHOCKWAVE))
595 if (target.killedwithweapon == "")
596 target.killedwithweapon = "UNKNOWN CAUSE";
598 string killedbyphrase = strcat("^3Killed by:^7 ", target.whokilled);
599 string wepkilledphrase = strcat("^3Cause:^7 ", target.killedwithweapon);
600 if (target.whokilled == "")
603 if (target.killedwithweapon == "")
604 wepkilledphrase = "^3Cause:^7 UNCLEAR";
609 if (target.ttt_status == TTT_STATUS_INNOCENT)
611 //try to add centerprint message for chat privately if possible
612 corpsemessagestrcat = strcat("\n^3Name:^7 ", target.netname, "\n^3Role: ^2Innocent\n", killedbyphrase, "\n", wepkilledphrase);
613 centerprint(attacker, strcat(BOLD_OPERATOR, corpsemessagestrcat));//("\n^6Name^3:^7 ", target.netname, "\n^5Role^3: ^2Innocent\n", "^1Killed by^3:^7 ", target.whokilled)));
615 else if (target.ttt_status == TTT_STATUS_TRAITOR)
617 corpsemessagestrcat = strcat("\n^3Name:^7 ", target.netname, "\n^3Role: ^1Traitor\n", killedbyphrase, "\n", wepkilledphrase);
618 centerprint(attacker, strcat(BOLD_OPERATOR, corpsemessagestrcat));//("\n^6Name^3:^7 ", target.netname, "\n^5Role^3: ^1Traitor\n", "^1Killed by^3:^7 ", target.whokilled)));
620 else if (target.ttt_status == TTT_STATUS_DETECTIVE)
622 corpsemessagestrcat = strcat("\n^3Name:^7 ", target.netname, "\n^3Role: ^4Detective\n", killedbyphrase, "\n", wepkilledphrase);
623 centerprint(attacker, strcat(BOLD_OPERATOR, corpsemessagestrcat));//("\n^6Name^3:^7 ", target.netname, "\n^5Role^3: ^4Detective\n", "^1Killed by^3:^7 ", target.whokilled)));
625 attacker.investigated = true;
630 M_ARGV(4, float) = damage;
631 M_ARGV(6, vector) = force;
634 MUTATOR_HOOKFUNCTION(ttt, PlayerPreThink)
636 entity player = M_ARGV(0, entity);
638 if(IS_PLAYER(player) || player.caplayer)
640 // update the scoreboard colour display to out the real killer at the end of the round
641 // running this every frame to avoid cheats
642 int plcolor = TTT_COLOR_INNOCENT;
643 if(player.ttt_status == TTT_STATUS_INNOCENT && game_stopped) //Innocent status by default
644 plcolor = TTT_COLOR_INNOCENT;
645 if(player.ttt_status == TTT_STATUS_TRAITOR && game_stopped)
646 plcolor = TTT_COLOR_TRAITOR;
647 //LegendGuard adds for Detective 21-02-2021
648 if(player.ttt_status == TTT_STATUS_DETECTIVE)// && game_stopped)
649 plcolor = TTT_COLOR_DETECTIVE;
650 setcolor(player, plcolor);
653 //CORPSE FEATURE 10-03-2021
656 player.event_damage = func_null;
658 player.solid = SOLID_CORPSE;
659 set_movetype(player, MOVETYPE_STEP); //test with MOVETYPE_TOSS or MOVETYPE_WALK (it's like sliding object) or MOVETYPE_BOUNCE (maybe not good)
663 MUTATOR_HOOKFUNCTION(ttt, PlayerSpawn)
665 entity player = M_ARGV(0, entity);
667 player.ttt_status = 0;
668 player.ttt_validkills = 0;
671 eliminatedPlayers.SendFlags |= 1;
674 MUTATOR_HOOKFUNCTION(ttt, ForbidSpawn)
676 entity player = M_ARGV(0, entity);
678 // spectators / observers that weren't playing can join; they are
679 // immediately forced to observe in the PutClientInServer hook
680 // this way they are put in a team and can play in the next round
681 if (!allowed_to_spawn && player.caplayer)
686 MUTATOR_HOOKFUNCTION(ttt, PutClientInServer)
688 entity player = M_ARGV(0, entity);
690 if (!allowed_to_spawn && IS_PLAYER(player)) // this is true even when player is trying to join
692 TRANSMUTE(Observer, player);
693 if (CS(player).jointime != time && !player.caplayer) // not when connecting
695 player.caplayer = 0.5;
696 Send_Notification(NOTIF_ONE_ONLY, player, MSG_INFO, INFO_CA_JOIN_LATE);
701 MUTATOR_HOOKFUNCTION(ttt, reset_map_players)
703 FOREACH_CLIENT(true, {
704 CS(it).killcount = 0;
706 ttt_FakeTimeLimit(it, -1); // restore original timelimit
707 if (!it.caplayer && IS_BOT_CLIENT(it))
711 TRANSMUTE(Player, it);
713 it.respawn_flags = RESPAWN_SILENT; //CSQC print output respawn lib.qh error fix
714 PutClientInServer(it);
717 bot_relinkplayerlist();
721 MUTATOR_HOOKFUNCTION(ttt, reset_map_global)
723 allowed_to_spawn = true;
727 entity ttt_LastPlayerForTeam(entity this)
729 entity last_pl = NULL;
730 FOREACH_CLIENT(IS_PLAYER(it) && it != this, {
731 if (!IS_DEAD(it) && this.ttt_status == it.ttt_status)
744 void ttt_LastPlayerForTeam_Notify(entity this)
746 if (!warmup_stage && round_handler_IsActive() && round_handler_IsRoundStarted())
748 entity pl = ttt_LastPlayerForTeam(this);
750 Send_Notification(NOTIF_ONE_ONLY, pl, MSG_CENTER, CENTER_ALONE);
754 MUTATOR_HOOKFUNCTION(ttt, PlayerDies)
756 entity frag_attacker = M_ARGV(1, entity);
757 entity frag_target = M_ARGV(2, entity);
758 //float frag_deathtype = M_ARGV(3, float);
760 ttt_LastPlayerForTeam_Notify(frag_target);
761 if (!allowed_to_spawn)
763 frag_target.respawn_flags = RESPAWN_DENY;
764 // prevent unwanted sudden rejoin as spectator and movement of spectator camera
765 frag_target.respawn_time = time + 2;
767 frag_target.respawn_flags |= RESPAWN_DENY;
768 frag_target.event_damage = func_null;
769 frag_target.health = 0;
773 eliminatedPlayers.SendFlags |= 1;
774 if (IS_BOT_CLIENT(frag_target))
775 bot_clear(frag_target);
778 //if(frag_attacker.ttt_status == frag_target.ttt_status)
779 // killed an ally! punishment is sentenced
780 if(frag_attacker.ttt_status == TTT_STATUS_DETECTIVE)
782 if (frag_target.ttt_status == TTT_STATUS_INNOCENT)
784 //PrintToChatAll("^1DEBUG^7: ^4DETECTIVE ^1DAMAGE/DEAD^7 HAS TAKEN!");
785 //30 damage points deal
786 Damage(frag_attacker, frag_attacker, frag_attacker, 30, DEATH_MIRRORDAMAGE.m_id, DMG_NOWEP, frag_attacker.origin, '0 0 0');
789 if (frag_attacker.ttt_status == TTT_STATUS_INNOCENT)
791 if (frag_target.ttt_status == TTT_STATUS_DETECTIVE)
793 //PrintToChatAll("^1DEBUG^7: ^2INNOCENT ^1DAMAGE/DEAD^7 HAS TAKEN!");
794 //30 damage points deal
795 Damage(frag_attacker, frag_attacker, frag_attacker, 30, DEATH_MIRRORDAMAGE.m_id, DMG_NOWEP, frag_attacker.origin, '0 0 0');
801 MUTATOR_HOOKFUNCTION(ttt, ClientDisconnect)
803 entity player = M_ARGV(0, entity);
805 if (IS_PLAYER(player) && !IS_DEAD(player))
806 ttt_LastPlayerForTeam_Notify(player);
810 MUTATOR_HOOKFUNCTION(ttt, MakePlayerObserver)
812 // LegendGuard, here is where spectators shouldn't talk to any players to say the hints or who is who 21-10-2021
813 entity player = M_ARGV(0, entity);
815 if (IS_PLAYER(player) && !IS_DEAD(player))
816 ttt_LastPlayerForTeam_Notify(player);
817 if (player.killindicator_teamchange == -2) // player wants to spectate
820 player.frags = FRAGS_PLAYER_OUT_OF_GAME;
822 eliminatedPlayers.SendFlags |= 1;
823 if (!player.caplayer)
825 player.ttt_validkills = 0;
826 player.ttt_status = 0;
827 ttt_FakeTimeLimit(player, -1); // restore original timelimit
828 return false; // allow team reset
830 return true; // prevent team reset
833 MUTATOR_HOOKFUNCTION(ttt, Scores_CountFragsRemaining)
835 // announce remaining frags?
839 MUTATOR_HOOKFUNCTION(ttt, GiveFragsForKill, CBC_ORDER_FIRST)
841 entity frag_attacker = M_ARGV(0, entity);
842 if(!warmup_stage && round_handler_IsActive() && round_handler_IsRoundStarted())
843 frag_attacker.ttt_validkills += M_ARGV(2, float);
844 M_ARGV(2, float) = 0; // score will be given to the winner when the round ends
848 MUTATOR_HOOKFUNCTION(ttt, AddPlayerScore)
850 // add scorefield for scoreboard here
851 entity scorefield = M_ARGV(0, entity);
852 if(scorefield == SP_KILLS || scorefield == SP_DEATHS || scorefield == SP_SUICIDES || scorefield == SP_DMG || scorefield == SP_DMGTAKEN)
853 M_ARGV(1, float) = 0; // don't report that the player has killed or been killed, that would out them as a traitor!
856 MUTATOR_HOOKFUNCTION(ttt, CalculateRespawnTime)
858 // no respawn calculations needed, player is forced to spectate anyway
862 MUTATOR_HOOKFUNCTION(ttt, Bot_FixCount, CBC_ORDER_EXCLUSIVE)
864 FOREACH_CLIENT(IS_REAL_CLIENT(it), {
865 if (IS_PLAYER(it) || it.caplayer == 1)
872 MUTATOR_HOOKFUNCTION(ttt, ClientCommand_Spectate)
874 entity player = M_ARGV(0, entity);
878 // they're going to spec, we can do other checks
879 if (autocvar_sv_spectate && (IS_SPEC(player) || IS_OBSERVER(player)))
880 Send_Notification(NOTIF_ONE_ONLY, player, MSG_INFO, INFO_CA_LEAVE);
881 return MUT_SPECCMD_FORCE;
884 return MUT_SPECCMD_CONTINUE;
887 MUTATOR_HOOKFUNCTION(ttt, GetPlayerStatus)
889 entity player = M_ARGV(0, entity);
891 return player.caplayer == 1;
894 MUTATOR_HOOKFUNCTION(ttt, BotShouldAttack)
896 //TODO: LegendGuard, try bots attack to innocents vs traitors, detectives must be on innocents team 21-02-2021
897 entity bot = M_ARGV(0, entity);
898 entity targ = M_ARGV(1, entity);
900 if(targ.ttt_status == bot.ttt_status)
905 if(targ.ttt_status == TTT_STATUS_DETECTIVE)
907 if(bot.ttt_status == TTT_STATUS_INNOCENT)