3 //set g_mmm_sleuth_count 0.125 "number of players who will become sleuths, 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_mmm_sleuth_count = 0.125; //I don't think that it won't be used...
5 float autocvar_g_mmm_civilian_count = 0.625;
6 //float autocvar_g_mmm_murderer_count = 0.25;
7 float autocvar_g_mmm_round_timelimit = 180;
8 float autocvar_g_mmm_warmup = 10;
9 bool autocvar_g_mmm_punish_teamkill = false;
10 bool autocvar_g_mmm_reward_civilian = true;
11 bool autocvar_g_mmm_reward_sleuth = true; //sleuth reward if investigated corpses
12 float autocvar_g_mmm_max_karma_points = 1000; //LegendGuard sets Karma points 21-02-2021
13 float autocvar_g_mmm_min_karma_points = 400;
14 int autocvar_g_mmm_karma_bankick_tool = 0; //LegendGuard sets a ban tool for server admins 11-03-2021
15 float autocvar_g_mmm_karma_bantime = 1800; //karma ban seconds
16 bool autocvar_g_mmm_karma_damageactive = true; //LegendGuard sets Karma damage setting if active 20-03-2021
18 // Sleuth is a created team, this team is added inside Civilians team
20 void mmm_FakeTimeLimit(entity e, float t)
22 if(!IS_REAL_CLIENT(e))
26 WriteByte(MSG_ONE, 3); // svc_updatestat
27 WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
29 WriteCoord(MSG_ONE, autocvar_timelimit);
31 WriteCoord(MSG_ONE, (t + 1) / 60);
33 STAT(MMM_ROUNDTIMER, e) = t;
37 void nades_Clear(entity player);
39 void karma_Control(entity it)
41 float masksize = autocvar_g_ban_default_masksize;
42 float bantime = autocvar_g_mmm_karma_bantime;
43 if(it.karmapoints >= autocvar_g_mmm_max_karma_points)
45 //Resets karmapoints to maintain the maximum
46 //PrintToChatAll("^3REWARD ^1MAXIMUM RESET");
47 GameRules_scoring_add(it, MMM_KARMA, autocvar_g_mmm_max_karma_points - it.karmapoints);
48 it.karmapoints = autocvar_g_mmm_max_karma_points;
50 else if(it.karmapoints <= autocvar_g_mmm_min_karma_points)
52 switch (autocvar_g_mmm_karma_bankick_tool)
55 case 0: PutObserverInServer(it); return;
57 case 1: dropclient(it); return;
59 case 2: Ban_KickBanClient(it, bantime, masksize, "Too low karma"); return;
61 default: PutObserverInServer(it); return;
66 void karmaLoseDifference(entity attacker, entity target)
68 //BASIC MATH THEORY: example: 1000 * 0.3 * (0.1 + 0.4) * 0.25 // karma points reduce when player attacked to other player
69 if (target.karmapoints < attacker.karmapoints)
71 float decreasekarma = - ( target.karmapoints * random() * ( 0.1 + random() ) * 0.25 );
72 GameRules_scoring_add(attacker, MMM_KARMA, decreasekarma);
73 attacker.karmapoints = attacker.karmapoints + decreasekarma;
75 else if (target.karmapoints > attacker.karmapoints)
77 float decreasekarma = - ( target.karmapoints * random() * ( 0.1 + random() ) * 0.25 );
78 GameRules_scoring_add(attacker, MMM_KARMA, decreasekarma);
79 attacker.karmapoints = attacker.karmapoints + decreasekarma;
83 float decreasekarma = - ( target.karmapoints * random() * ( 0.1 + random() ) * 0.25 );
84 GameRules_scoring_add(attacker, MMM_KARMA, decreasekarma);
85 attacker.karmapoints = attacker.karmapoints + decreasekarma;
89 void karmaWinDifference(entity it)
91 GameRules_scoring_add(it, SCORE, 1); // reward civilians who make it to the end of the round time limit
92 float increasekarma = ( autocvar_g_mmm_min_karma_points * random() * ( 0.1 + random() ) * 0.12 );
93 GameRules_scoring_add(it, MMM_KARMA, increasekarma);
94 it.karmapoints = it.karmapoints + increasekarma;
97 void mmm_UpdateScores(bool timed_out)
99 // give players their hard-earned kills now that the round is over
102 it.totalfrags += it.mmm_validkills;
103 if(it.mmm_validkills)
105 GameRules_scoring_add(it, SCORE, it.mmm_validkills);
107 it.mmm_validkills = 0;
108 // player survived the round
109 if(IS_PLAYER(it) && !IS_DEAD(it)) // LegendGuard adds something for Karma 21-02-2021
111 if((autocvar_g_mmm_reward_civilian && timed_out && it.mmm_status == MMM_STATUS_CIVILIAN)
112 || (autocvar_g_mmm_reward_civilian && !timed_out && it.mmm_status == MMM_STATUS_CIVILIAN))
114 karmaWinDifference(it);
115 //PrintToChatAll(sprintf("^2REWARD ^7it.karmapoints: ^1%f", it.karmapoints));
119 //Sleuth reward after investigated a corpse
120 if((autocvar_g_mmm_reward_sleuth && timed_out && it.mmm_status == MMM_STATUS_SLEUTH)
121 || (autocvar_g_mmm_reward_sleuth && !timed_out && it.mmm_status == MMM_STATUS_SLEUTH))
123 if (it.investigated == true)
125 karmaWinDifference(it);
126 it.investigated = false;
131 if(it.mmm_status == MMM_STATUS_CIVILIAN)
133 karmaWinDifference(it);
134 //PrintToChatAll(sprintf("^2CIVILIAN ^7it.karmapoints: ^1%f", it.karmapoints));
137 else if(it.mmm_status == MMM_STATUS_MURDERER)
139 karmaWinDifference(it);
140 //PrintToChatAll(sprintf("^1MURDERER ^7it.karmapoints: ^1%f", it.karmapoints));
147 float mmm_CheckWinner()
149 if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
151 // if the match times out, civilians win too!
152 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_MMM_CIVILIAN_WIN);
153 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_MMM_CIVILIAN_WIN);
158 mmm_FakeTimeLimit(it, -1);
161 mmm_UpdateScores(true);
163 allowed_to_spawn = false;
165 round_handler_Init(5, autocvar_g_mmm_warmup, autocvar_g_mmm_round_timelimit);
169 int civilian_count = 0, murderer_count = 0, sleuth_count = 0;
170 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
172 if(it.mmm_status == MMM_STATUS_CIVILIAN)
174 else if(it.mmm_status == MMM_STATUS_MURDERER)
176 else if(it.mmm_status == MMM_STATUS_SLEUTH) //LegendGuard adds sleuth_count 20-02-2021
179 if(civilian_count > 0 && murderer_count > 0)
184 if(murderer_count > 0) // murderers win
186 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_MMM_MURDERER_WIN);
187 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_MMM_MURDERER_WIN);
189 else if(civilian_count > 0) // civilians win
191 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_MMM_CIVILIAN_WIN);
192 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_MMM_CIVILIAN_WIN);
194 else if (sleuth_count > 0 && civilian_count > 0) // sleuths are same as civilians win
196 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_MMM_CIVILIAN_WIN);
197 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_MMM_CIVILIAN_WIN);
201 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
202 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
205 mmm_UpdateScores(false);
207 allowed_to_spawn = false;
209 round_handler_Init(5, autocvar_g_mmm_warmup, autocvar_g_mmm_round_timelimit);
215 it.respawn_flags = RESPAWN_SILENT; //CSQC print output respawn lib.qh error fix
218 mmm_FakeTimeLimit(it, -1);
224 void mmm_RoundStart()
226 allowed_to_spawn = boolean(warmup_stage);
231 if(IS_PLAYER(it) && !IS_DEAD(it))
234 it.mmm_status = MMM_STATUS_CIVILIAN;
237 it.mmm_status = 0; // this is mostly a safety check; if a client manages to somehow maintain a mmm status, clear it before the round starts!
238 it.mmm_validkills = 0;
241 int civilian_count = bound(1, ((autocvar_g_mmm_civilian_count >= 1) ? autocvar_g_mmm_civilian_count : floor(playercount * autocvar_g_mmm_civilian_count)), playercount - 1); // 20%, but ensure at least 1 and less than total
242 int total_civilians = 0;
243 //int murderer_count = bound(1, ((autocvar_g_mmm_murderer_count >= 1) ? autocvar_g_mmm_murderer_count : floor(playercount * autocvar_g_mmm_murderer_count)), playercount - 1); // 20%, but ensure at least 1 and less than total
244 int total_murderers = 0;
245 //int sleuth_count = bound(1, ((autocvar_g_mmm_sleuth_count >= 1) ? autocvar_g_mmm_sleuth_count : floor(playercount * autocvar_g_mmm_sleuth_count)), playercount - 1); // 20%, but ensure at least 1 and less than total
246 int total_sleuths = 0;
249 FOREACH_CLIENT_RANDOM(IS_PLAYER(it) && !IS_DEAD(it),
251 if(total_civilians >= civilian_count)
253 //LegendGuard fixes the round start again 22-03-2021
255 if (total_civilians <= 1)
257 if (total_murderers <= 1)
260 it.mmm_status = MMM_STATUS_MURDERER;
263 else if (total_civilians == 2)
265 if (total_sleuths >= 1)
270 it.mmm_status = MMM_STATUS_SLEUTH;
273 else if (total_civilians == 5)
275 if (total_murderers <= 2)
280 it.mmm_status = MMM_STATUS_MURDERER;
283 else if (total_civilians == 6)
285 if (total_sleuths >= 2)
290 it.mmm_status = MMM_STATUS_SLEUTH;
293 else if (total_civilians == 7)
295 if (total_sleuths >= 3)
297 else if (total_murderers == 3)
300 it.mmm_status = MMM_STATUS_MURDERER;
305 it.mmm_status = MMM_STATUS_SLEUTH;
308 else if (total_civilians >= 8)
310 if (total_sleuths >= 4)
312 else if (total_murderers == 4)
315 it.mmm_status = MMM_STATUS_MURDERER;
320 it.mmm_status = MMM_STATUS_SLEUTH;
326 it.mmm_status = MMM_STATUS_MURDERER;
330 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
332 float totalmeankarma = ((autocvar_g_mmm_max_karma_points + autocvar_g_mmm_min_karma_points + it.karmapoints) / 3);
334 it.activekillerrole = false;
336 if(it.mmm_status == MMM_STATUS_CIVILIAN)
338 SetResource(it, RES_SHELLS, 10);
339 SetResource(it, RES_BULLETS, 20);
340 SetResource(it, RES_ROCKETS, 10);
341 SetResource(it, RES_CELLS, 10);
342 if (it.karmapoints <= totalmeankarma)
344 centerprint(it, strcat(BOLD_OPERATOR, "^1KARMA WARNING!\n^3Here, have the Rifle!"));
345 GiveWeapon(it, WEP_RIFLE.m_id, OP_PLUS, 1);
347 //Gives Mine Layer weapon to the player
348 GiveWeapon(it, WEP_MINE_LAYER.m_id, OP_PLUS, 1);
349 Send_Notification(NOTIF_ONE_ONLY, it, MSG_CENTER, CENTER_MMM_CIVILIAN);
350 Send_Notification(NOTIF_ONE_ONLY, it, MSG_INFO, INFO_MMM_CIVILIAN);
351 //PrintToChatAll(sprintf("^1DEBUG^7: %s is ^2Civilian^7!", it.netname));
353 else if(it.mmm_status == MMM_STATUS_MURDERER)
355 SetResource(it, RES_SHELLS, 10);
356 SetResource(it, RES_BULLETS, 20);
357 SetResource(it, RES_ROCKETS, 10);
358 SetResource(it, RES_CELLS, 10);
359 if (it.karmapoints <= totalmeankarma)
361 centerprint(it, strcat(BOLD_OPERATOR, "^1KARMA WARNING!\n^3Here, have the Rifle!"));
362 GiveWeapon(it, WEP_RIFLE.m_id, OP_PLUS, 1);
364 //Gives Mine Layer weapon to the player
365 GiveWeapon(it, WEP_MINE_LAYER.m_id, OP_PLUS, 1);
366 Send_Notification(NOTIF_ONE_ONLY, it, MSG_CENTER, CENTER_MMM_MURDERER);
367 Send_Notification(NOTIF_ONE_ONLY, it, MSG_INFO, INFO_MMM_MURDERER);
368 //PrintToChatAll(sprintf("^1DEBUG^7: %s is ^1Murderer^7!", it.netname));
370 else if(it.mmm_status == MMM_STATUS_SLEUTH)
372 SetResource(it, RES_ROCKETS, 10);
373 if (it.karmapoints <= totalmeankarma)
375 centerprint(it, strcat(BOLD_OPERATOR, "^1KARMA WARNING!\n^3Here, have the Rifle!"));
376 GiveWeapon(it, WEP_RIFLE.m_id, OP_PLUS, 1);
378 //Gives Shockwave and Mine Layer weapon to the player
379 GiveWeapon(it, WEP_SHOCKWAVE.m_id, OP_PLUS, 1);
380 GiveWeapon(it, WEP_MINE_LAYER.m_id, OP_PLUS, 1);
381 Send_Notification(NOTIF_ONE_ONLY, it, MSG_CENTER, CENTER_MMM_SLEUTH);
382 Send_Notification(NOTIF_ONE_ONLY, it, MSG_INFO, INFO_MMM_SLEUTH);
383 PrintToChatAll(sprintf("%s is ^4Sleuth^7!", it.netname));
385 mmm_FakeTimeLimit(it, round_handler_GetEndTime());
389 bool mmm_CheckPlayers()
391 static int prev_missing_players;
392 allowed_to_spawn = true;
395 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
397 //PrintToChatAll(sprintf("it.karmapoints ^5begin: ^3%f",it.karmapoints));
399 if (it.karmastarted != true)
401 GameRules_scoring_add(it, MMM_KARMA, autocvar_g_mmm_max_karma_points - it.karmapoints);
402 it.karmapoints = autocvar_g_mmm_max_karma_points;
403 it.karmastarted = true;
407 //PrintToChatAll(sprintf("it.karmapoints ^6end: ^3%f",it.karmapoints));
410 if (playercount >= 2)
412 if(prev_missing_players > 0)
413 Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_PLAYERS);
414 prev_missing_players = -1;
420 if(prev_missing_players > 0)
421 Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_PLAYERS);
422 prev_missing_players = -1;
426 // if we get here, only 1 player is missing
427 if(prev_missing_players != 1)
429 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_MISSING_PLAYERS, 1);
430 prev_missing_players = 1;
435 bool mmm_isEliminated(entity e)
437 if(e.caplayer == 1 && (IS_DEAD(e) || e.frags == FRAGS_PLAYER_OUT_OF_GAME))
439 if(e.caplayer == 0.5)
444 void mmm_Initialize() // run at the start of a match, initiates game mode
446 GameRules_scoring(0, SFL_SORT_PRIO_PRIMARY, 0, {
447 field(SP_MMM_KARMA, "karma", SFL_SORT_PRIO_SECONDARY); //LegendGuard adds Karma points in the scoreboard 22-02-2021
450 allowed_to_spawn = true;
451 round_handler_Spawn(mmm_CheckPlayers, mmm_CheckWinner, mmm_RoundStart);
452 round_handler_Init(5, autocvar_g_mmm_warmup, autocvar_g_mmm_round_timelimit);
453 EliminatedPlayers_Init(mmm_isEliminated);
456 void checkWeaponDeathtype(entity target, float deathtype)
460 case WEP_ARC.m_id: case 276: case 788: target.killedwithweapon = "Impacted by the Arc's electric shock"; return;
461 case WEP_BLASTER.m_id: case 513: target.killedwithweapon = "Blasted by the Blaster"; return;
462 case WEP_CRYLINK.m_id: case 263: case 519: target.killedwithweapon = "Shot by the Crylink"; return;
463 case WEP_DEVASTATOR.m_id: case 522: case 1546: target.killedwithweapon = "Bombarded by the Devastator"; return;
464 case WEP_ELECTRO.m_id: case 262: case 518: case 1542: target.killedwithweapon = "Electrocuted by the Electro"; return;
465 case WEP_FIREBALL.m_id: case 273: case 529: case 1297: target.killedwithweapon = "Burned by the Fireball"; return;
466 case WEP_HAGAR.m_id: case 265: target.killedwithweapon = "Gunned by the Hagar"; return;
467 case WEP_HLAC.m_id: case 270: case 526: target.killedwithweapon = "Cut down with the HLAC"; return;
468 case WEP_HOOK.m_id: case 1805: target.killedwithweapon = "Caught in Hook gravity bomb"; return;
469 case WEP_MACHINEGUN.m_id: target.activekillerrole = true; target.killedwithweapon = "Riddled full of holes by the Machine Gun"; return;
470 case WEP_MINE_LAYER.m_id: case 517: case 1541: target.killedwithweapon = "Exploited by the Mine Layer"; return;
471 case WEP_MORTAR.m_id: case 516: case 1284: target.killedwithweapon = "Blew up with the Mortar"; return;
472 case WEP_OVERKILL_NEX.m_id: target.killedwithweapon = "Sniped by the Overkill Nex"; return;
473 case WEP_RIFLE.m_id: case 272: target.activekillerrole = true; target.killedwithweapon = "Sniped by the Rifle"; return;
474 case WEP_SEEKER.m_id: case 274: case 786: target.killedwithweapon = "Blasted by the Seeker"; return;
475 case WEP_SHOCKWAVE.m_id: target.killedwithweapon = "Gunned down by the Shockwave"; return;
476 case 275: target.killedwithweapon = "Knocked by the Shockwave"; return;
477 case WEP_SHOTGUN.m_id: target.activekillerrole = true; target.killedwithweapon = "Shot by Shotgun"; return;
478 case 258: target.killedwithweapon = "Knocked by the Shotgun"; return;
479 case WEP_TUBA.m_id: target.killedwithweapon = "Ear pain by the @!#%'n Tuba"; return;
480 case WEP_VAPORIZER.m_id: case 257: case 769: target.killedwithweapon = "Sniped by the Vaporizer"; return;
481 case WEP_VORTEX.m_id: target.killedwithweapon = "Sniped by the Vortex"; return;
482 case DEATH_FALL.m_id: target.killedwithweapon = "Fall"; return;
483 case DEATH_FIRE.m_id: target.killedwithweapon = "Burned with the fire"; return;
484 case DEATH_LAVA.m_id: target.killedwithweapon = "Burned in lava"; return;
485 case DEATH_MIRRORDAMAGE.m_id: target.killedwithweapon = "Suicide"; return;
486 case DEATH_SLIME.m_id: target.killedwithweapon = "Melted in slime"; return;
487 case DEATH_TELEFRAG.m_id: target.killedwithweapon = "Telefragged"; return;
488 case DEATH_NADE.m_id: target.killedwithweapon = "Blown up by the nade"; return;
489 case DEATH_NADE_NAPALM.m_id: target.killedwithweapon = "Burned by the Napalm nade"; return;
490 case DEATH_NADE_ICE.m_id: target.killedwithweapon = "Frozen by the Ice nade"; return;
491 case DEATH_NADE_HEAL.m_id: target.killedwithweapon = "Sucked by the Heal nade"; return;
492 default: target.killedwithweapon = "Unknown"; return;
496 void ReduceKarmaPointsandFrags(entity frag_attacker, entity frag_target, float frag_deathtype, entity wep_ent)
498 karmaLoseDifference(frag_attacker, frag_target);
499 GiveFrags(frag_attacker, frag_target, ((autocvar_g_mmm_punish_teamkill) ? -1 : -2), frag_deathtype, wep_ent.weaponentity_fld);
500 karma_Control(frag_attacker);
501 frag_target.whokilled = frag_attacker.netname;
508 MUTATOR_HOOKFUNCTION(mmm, ClientObituary)
510 // LegendGuard's IDEA: To adjust the grade of severity of karma,
511 // we could add if sentence per weapons and adjust each weapon attack
512 // its own grade. Instead doing random decrease grade 22-02-2021
514 // in mmm, announcing a frag would tell everyone who the murderer is
515 entity frag_attacker = M_ARGV(1, entity);
516 entity frag_target = M_ARGV(2, entity);
518 if(IS_PLAYER(frag_attacker) && frag_attacker != frag_target)
520 float frag_deathtype = M_ARGV(3, float);
521 entity wep_ent = M_ARGV(4, entity);
523 //PrintToChatAll(strcat("deathtype var: ", ftos(frag_deathtype)));
524 checkWeaponDeathtype(frag_target, frag_deathtype);
525 // "team" kill, a point is awarded to the player by default so we must take it away plus an extra one
526 // unless the player is going to be punished for suicide, in which case just remove one
527 if(frag_attacker.mmm_status == frag_target.mmm_status)
529 //PrintToChatAll("^1DEBUG^7: A ^2PLAYER^7 has fragged a ^2PLAYER OF HIS OWN TEAM^7, TOO BAD!");
530 ReduceKarmaPointsandFrags(frag_attacker, frag_target, frag_deathtype, wep_ent);
531 switch (frag_attacker.mmm_status)
533 case MMM_STATUS_CIVILIAN: frag_target.killerrole = "\n^3Killer role: ^2Civilian"; return;
534 case MMM_STATUS_MURDERER: frag_target.killerrole = "\n^3Killer role: ^1Murderer"; return;
535 case MMM_STATUS_SLEUTH: frag_target.killerrole = "\n^3Killer role: ^4Sleuth"; return;
538 //PrintToChatAll(sprintf("frag_attacker.karmapoints: ^1%f", frag_attacker.karmapoints));
541 if(frag_attacker.mmm_status == MMM_STATUS_SLEUTH)
543 if (frag_target.mmm_status == MMM_STATUS_CIVILIAN || frag_target.mmm_status == MMM_STATUS_SLEUTH)
545 //PrintToChatAll("^1DEBUG^7: A ^4Sleuth^7 fragged an ^2Civilian^7/^4Sleuth^7, TOO BAD!");
546 ReduceKarmaPointsandFrags(frag_attacker, frag_target, frag_deathtype, wep_ent);
547 frag_target.killerrole = "\n^3Killer role: ^4Sleuth";
548 //PrintToChatAll(sprintf("frag_attacker.karmapoints: ^1%f", frag_attacker.karmapoints));
552 frag_target.whokilled = frag_attacker.netname;
553 frag_target.killerrole = "\n^3Killer role: ^4Sleuth";
557 if (frag_attacker.mmm_status == MMM_STATUS_CIVILIAN)
559 if (frag_target.mmm_status == MMM_STATUS_SLEUTH)
561 //PrintToChatAll("^1DEBUG^7: An ^2Civilian^7 fragged a ^4Sleuth^7, TOO BAD!");
562 ReduceKarmaPointsandFrags(frag_attacker, frag_target, frag_deathtype, wep_ent);
563 frag_target.killerrole = "\n^3Killer role: ^2Civilian";
567 frag_target.whokilled = frag_attacker.netname;
568 frag_target.killerrole = "\n^3Killer role: ^2Civilian";
572 if (frag_attacker.mmm_status == MMM_STATUS_MURDERER)
574 if (frag_target.mmm_status == MMM_STATUS_CIVILIAN)
576 frag_target.whokilled = frag_attacker.netname;
577 frag_target.killerrole = "\n^3Killer role: ^1Murderer";
581 frag_target.whokilled = frag_attacker.netname;
582 frag_target.killerrole = "\n^3Killer role: ^1Murderer";
585 //if mmm_status is 1, means civilian, 2 means murderer, 3 means sleuth
586 //PrintToChatAll(sprintf("^1DEBUG^7: frag_attacker.mmm_status is ^3%s^7",ftos(frag_attacker.mmm_status)));
587 //PrintToChatAll(sprintf("^1DEBUG^7: frag_target.mmm_status is ^3%s^7",ftos(frag_target.mmm_status)));
591 float frag_deathtype = M_ARGV(3, float);
592 checkWeaponDeathtype(frag_target, frag_deathtype);
595 //TODO: try to do a "find out" if a sleuth can see who fragged to who if possible 21-02-2021
596 M_ARGV(5, bool) = true; // anonymous attacker
599 //karma weapon damage, halve the damage attack when player has low karma 20-03-2021
600 MUTATOR_HOOKFUNCTION(mmm, Damage_Calculate)
602 entity attacker = M_ARGV(1, entity);
603 entity target = M_ARGV(2, entity);
604 float deathtype = M_ARGV(3, float);
605 float damage = M_ARGV(4, float);
606 vector force = M_ARGV(6, vector);
607 string corpsemessagestrcat = "";
609 if (autocvar_g_mmm_karma_damageactive != false)
611 if (IS_PLAYER(attacker))
613 if(target == attacker) // damage done to yourself
615 damage /= autocvar_g_weapondamagefactor / (attacker.karmapoints / autocvar_g_mmm_max_karma_points);
616 force /= autocvar_g_weaponforcefactor / (attacker.karmapoints / autocvar_g_mmm_max_karma_points);
618 else if (target != attacker)
620 damage /= autocvar_g_weapondamagefactor / (attacker.karmapoints / autocvar_g_mmm_max_karma_points);
621 force /= autocvar_g_weaponforcefactor / (attacker.karmapoints / autocvar_g_mmm_max_karma_points);
625 damage *= autocvar_g_weapondamagefactor;
626 force *= autocvar_g_weaponforcefactor;
631 //SLEUTH CORPSE DETECTION SKILL 21-03-2021
632 if (attacker.mmm_status == MMM_STATUS_SLEUTH)
636 //Shockwave weapon as radar gun to check the corpses 22-03-2021
637 if(DEATH_ISWEAPON(deathtype, WEP_SHOCKWAVE))
639 if (target.killedwithweapon == "")
640 target.killedwithweapon = "UNKNOWN CAUSE";
642 if (target.activekillerrole != true)
644 target.killerrole = "";
645 target.activekillerrole = false;
648 string killedbyphrase = strcat("\n^3Killed by:^7 ", target.whokilled, target.killerrole);
649 string wepkilledphrase = strcat("\n^3Cause:^7 ", target.killedwithweapon);
650 if (target.whokilled == "")
653 if (target.killedwithweapon == "")
654 wepkilledphrase = "\n^3Cause:^7 UNKNOWN CAUSE";
659 if (target.mmm_status == MMM_STATUS_CIVILIAN)
661 //try to add centerprint message for chat privately if possible
662 corpsemessagestrcat = strcat("\n^3Name:^7 ", target.netname, "\n^3Role: ^2Civilian", killedbyphrase, wepkilledphrase);
663 centerprint(attacker, strcat(BOLD_OPERATOR, corpsemessagestrcat));//("\n^6Name^3:^7 ", target.netname, "\n^5Role^3: ^2Civilian\n", "^1Killed by^3:^7 ", target.whokilled)));
665 else if (target.mmm_status == MMM_STATUS_MURDERER)
667 corpsemessagestrcat = strcat("\n^3Name:^7 ", target.netname, "\n^3Role: ^1Murderer", killedbyphrase, wepkilledphrase);
668 centerprint(attacker, strcat(BOLD_OPERATOR, corpsemessagestrcat));//("\n^6Name^3:^7 ", target.netname, "\n^5Role^3: ^1Murderer\n", "^1Killed by^3:^7 ", target.whokilled)));
670 else if (target.mmm_status == MMM_STATUS_SLEUTH)
672 corpsemessagestrcat = strcat("\n^3Name:^7 ", target.netname, "\n^3Role: ^4Sleuth", killedbyphrase, wepkilledphrase);
673 centerprint(attacker, strcat(BOLD_OPERATOR, corpsemessagestrcat));//("\n^6Name^3:^7 ", target.netname, "\n^5Role^3: ^4Sleuth\n", "^1Killed by^3:^7 ", target.whokilled)));
675 attacker.investigated = true;
680 M_ARGV(4, float) = damage;
681 M_ARGV(6, vector) = force;
684 MUTATOR_HOOKFUNCTION(mmm, PlayerPreThink)
686 entity player = M_ARGV(0, entity);
688 if(IS_PLAYER(player) || player.caplayer)
690 // update the scoreboard colour display to out the real killer at the end of the round
691 // running this every frame to avoid cheats
692 int plcolor = MMM_COLOR_CIVILIAN;
693 if(player.mmm_status == MMM_STATUS_CIVILIAN && game_stopped) //Civilian status by default
694 plcolor = MMM_COLOR_CIVILIAN;
695 if(player.mmm_status == MMM_STATUS_MURDERER && game_stopped)
696 plcolor = MMM_COLOR_MURDERER;
697 //LegendGuard adds for Sleuth 21-02-2021
698 if(player.mmm_status == MMM_STATUS_SLEUTH)// && game_stopped)
699 plcolor = MMM_COLOR_SLEUTH;
700 setcolor(player, plcolor);
703 //CORPSE FEATURE 10-03-2021
706 player.event_damage = func_null;
708 player.solid = SOLID_CORPSE;
709 set_movetype(player, MOVETYPE_STEP); //test with MOVETYPE_TOSS or MOVETYPE_WALK (it's like sliding object) or MOVETYPE_BOUNCE (maybe not good)
713 MUTATOR_HOOKFUNCTION(mmm, PlayerSpawn)
715 entity player = M_ARGV(0, entity);
717 player.mmm_status = 0;
718 player.mmm_validkills = 0;
721 eliminatedPlayers.SendFlags |= 1;
724 MUTATOR_HOOKFUNCTION(mmm, ForbidSpawn)
726 entity player = M_ARGV(0, entity);
728 // spectators / observers that weren't playing can join; they are
729 // immediately forced to observe in the PutClientInServer hook
730 // this way they are put in a team and can play in the next round
731 if (!allowed_to_spawn && player.caplayer)
736 MUTATOR_HOOKFUNCTION(mmm, PutClientInServer)
738 entity player = M_ARGV(0, entity);
740 if (!allowed_to_spawn && IS_PLAYER(player)) // this is true even when player is trying to join
742 TRANSMUTE(Observer, player);
743 if (CS(player).jointime != time && !player.caplayer) // not when connecting
745 player.caplayer = 0.5;
746 Send_Notification(NOTIF_ONE_ONLY, player, MSG_INFO, INFO_CA_JOIN_LATE);
751 MUTATOR_HOOKFUNCTION(mmm, reset_map_players)
753 FOREACH_CLIENT(true, {
754 CS(it).killcount = 0;
756 mmm_FakeTimeLimit(it, -1); // restore original timelimit
757 if (!it.caplayer && IS_BOT_CLIENT(it))
761 TRANSMUTE(Player, it);
763 it.respawn_flags = RESPAWN_SILENT; //CSQC print output respawn lib.qh error fix
764 PutClientInServer(it);
767 bot_relinkplayerlist();
771 MUTATOR_HOOKFUNCTION(mmm, reset_map_global)
773 allowed_to_spawn = true;
777 entity mmm_LastPlayerForTeam(entity this)
779 entity last_pl = NULL;
780 FOREACH_CLIENT(IS_PLAYER(it) && it != this, {
781 if (!IS_DEAD(it) && this.mmm_status == it.mmm_status)
794 void mmm_LastPlayerForTeam_Notify(entity this)
796 if (!warmup_stage && round_handler_IsActive() && round_handler_IsRoundStarted())
798 entity pl = mmm_LastPlayerForTeam(this);
800 Send_Notification(NOTIF_ONE_ONLY, pl, MSG_CENTER, CENTER_ALONE);
804 MUTATOR_HOOKFUNCTION(mmm, PlayerDies)
806 entity frag_attacker = M_ARGV(1, entity);
807 entity frag_target = M_ARGV(2, entity);
808 //float frag_deathtype = M_ARGV(3, float);
810 mmm_LastPlayerForTeam_Notify(frag_target);
811 if (!allowed_to_spawn)
813 frag_target.respawn_flags = RESPAWN_DENY;
814 // prevent unwanted sudden rejoin as spectator and movement of spectator camera
815 frag_target.respawn_time = time + 2;
817 frag_target.respawn_flags |= RESPAWN_DENY;
818 frag_target.event_damage = func_null;
819 frag_target.health = 0;
823 eliminatedPlayers.SendFlags |= 1;
824 if (IS_BOT_CLIENT(frag_target))
825 bot_clear(frag_target);
828 //if(frag_attacker.mmm_status == frag_target.mmm_status)
829 // killed an ally! punishment is sentenced
830 if(frag_attacker.mmm_status == MMM_STATUS_SLEUTH)
832 if (frag_target.mmm_status == MMM_STATUS_CIVILIAN)
834 //PrintToChatAll("^1DEBUG^7: ^4SLEUTH ^1DAMAGE/DEAD^7 HAS TAKEN!");
835 //30 damage points deal
836 Damage(frag_attacker, frag_attacker, frag_attacker, 30, DEATH_MIRRORDAMAGE.m_id, DMG_NOWEP, frag_attacker.origin, '0 0 0');
839 if (frag_attacker.mmm_status == MMM_STATUS_CIVILIAN)
841 if (frag_target.mmm_status == MMM_STATUS_SLEUTH)
843 //PrintToChatAll("^1DEBUG^7: ^2CIVILIAN ^1DAMAGE/DEAD^7 HAS TAKEN!");
844 //30 damage points deal
845 Damage(frag_attacker, frag_attacker, frag_attacker, 30, DEATH_MIRRORDAMAGE.m_id, DMG_NOWEP, frag_attacker.origin, '0 0 0');
851 MUTATOR_HOOKFUNCTION(mmm, ClientDisconnect)
853 entity player = M_ARGV(0, entity);
855 if (IS_PLAYER(player) && !IS_DEAD(player))
856 mmm_LastPlayerForTeam_Notify(player);
860 MUTATOR_HOOKFUNCTION(mmm, MakePlayerObserver)
862 // LegendGuard, here is where spectators shouldn't talk to any players to say the hints or who is who 21-10-2021
863 entity player = M_ARGV(0, entity);
865 if (IS_PLAYER(player) && !IS_DEAD(player))
866 mmm_LastPlayerForTeam_Notify(player);
867 if (player.killindicator_teamchange == -2) // player wants to spectate
870 player.frags = FRAGS_PLAYER_OUT_OF_GAME;
872 eliminatedPlayers.SendFlags |= 1;
873 if (!player.caplayer)
875 player.mmm_validkills = 0;
876 player.mmm_status = 0;
877 mmm_FakeTimeLimit(player, -1); // restore original timelimit
878 return false; // allow team reset
880 return true; // prevent team reset
883 MUTATOR_HOOKFUNCTION(mmm, Scores_CountFragsRemaining)
885 // announce remaining frags?
889 MUTATOR_HOOKFUNCTION(mmm, GiveFragsForKill, CBC_ORDER_FIRST)
891 entity frag_attacker = M_ARGV(0, entity);
892 if(!warmup_stage && round_handler_IsActive() && round_handler_IsRoundStarted())
893 frag_attacker.mmm_validkills += M_ARGV(2, float);
894 M_ARGV(2, float) = 0; // score will be given to the winner when the round ends
898 MUTATOR_HOOKFUNCTION(mmm, AddPlayerScore)
900 // add scorefield for scoreboard here
901 entity scorefield = M_ARGV(0, entity);
902 if(scorefield == SP_KILLS || scorefield == SP_DEATHS || scorefield == SP_SUICIDES || scorefield == SP_DMG || scorefield == SP_DMGTAKEN)
903 M_ARGV(1, float) = 0; // don't report that the player has killed or been killed, that would out them as a murderer!
906 MUTATOR_HOOKFUNCTION(mmm, CalculateRespawnTime)
908 // no respawn calculations needed, player is forced to spectate anyway
912 MUTATOR_HOOKFUNCTION(mmm, Bot_FixCount, CBC_ORDER_EXCLUSIVE)
914 FOREACH_CLIENT(IS_REAL_CLIENT(it), {
915 if (IS_PLAYER(it) || it.caplayer == 1)
922 MUTATOR_HOOKFUNCTION(mmm, ClientCommand_Spectate)
924 entity player = M_ARGV(0, entity);
928 // they're going to spec, we can do other checks
929 if (autocvar_sv_spectate && (IS_SPEC(player) || IS_OBSERVER(player)))
930 Send_Notification(NOTIF_ONE_ONLY, player, MSG_INFO, INFO_CA_LEAVE);
931 return MUT_SPECCMD_FORCE;
934 return MUT_SPECCMD_CONTINUE;
937 MUTATOR_HOOKFUNCTION(mmm, GetPlayerStatus)
939 entity player = M_ARGV(0, entity);
941 return player.caplayer == 1;
944 MUTATOR_HOOKFUNCTION(mmm, BotShouldAttack)
946 entity bot = M_ARGV(0, entity);
947 entity targ = M_ARGV(1, entity);
949 if(targ.mmm_status == bot.mmm_status)
954 // LegendGuard fixed the problem of Sleuths and Civilians attacking each other 26-03-2021
955 if(bot.mmm_status == MMM_STATUS_SLEUTH)
957 if(targ.mmm_status == MMM_STATUS_CIVILIAN)