3 float autocvar_g_mmm_detective_count = 0.125;
4 float autocvar_g_mmm_murderer_count = 0.25;
5 float autocvar_g_mmm_round_timelimit = 180;
6 float autocvar_g_mmm_warmup = 10;
7 bool autocvar_g_mmm_punish_teamkill = false;
8 bool autocvar_g_mmm_reward_civilian = true;
9 bool autocvar_g_mmm_reward_detective = true; //detective reward if investigated corpses
10 float autocvar_g_mmm_max_karma_points = 1000; //LegendGuard sets Karma points 21-02-2021
11 float autocvar_g_mmm_min_karma_points = 550;
12 int autocvar_g_mmm_karma_bankick_tool = 1; //LegendGuard sets a ban tool for server admins 11-03-2021
13 float autocvar_g_mmm_karma_bantime = 1800; //karma ban seconds
14 bool autocvar_g_mmm_karma_damageactive = true; //LegendGuard sets Karma damage setting if active 20-03-2021
15 float autocvar_g_mmm_karma_severity = 0.25;
16 float autocvar_g_mmm_karma_damagepunishmentdeal = 20; //LegendGuard sets Karma punishment damage setting if player kills an ally 28-03-2021
17 // Detective is a created team, this team is added inside Civilians team
19 void mmm_FakeTimeLimit(entity e, float t)
21 if(!IS_REAL_CLIENT(e))
25 WriteByte(MSG_ONE, 3); // svc_updatestat
26 WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
28 WriteCoord(MSG_ONE, autocvar_timelimit);
30 WriteCoord(MSG_ONE, (t + 1) / 60);
32 STAT(MMM_ROUNDTIMER, e) = t;
36 void nades_Clear(entity player);
38 void karma_Control(entity it)
40 float masksize = autocvar_g_ban_default_masksize;
41 float bantime = autocvar_g_mmm_karma_bantime;
42 if(it.karmapoints >= autocvar_g_mmm_max_karma_points)
44 //Resets karmapoints to maintain the maximum
45 //PrintToChatAll("^3REWARD ^1MAXIMUM RESET");
46 GameRules_scoring_add(it, MMM_KARMA, autocvar_g_mmm_max_karma_points - it.karmapoints);
47 it.karmapoints = autocvar_g_mmm_max_karma_points;
49 else if(it.karmapoints <= autocvar_g_mmm_min_karma_points)
51 switch (autocvar_g_mmm_karma_bankick_tool)
56 case 1: PutObserverInServer(it, true); return;
58 case 2: dropclient(it); return;
60 case 3: Ban_KickBanClient(it, bantime, masksize, "Too low karma"); return;
61 default: PutObserverInServer(it, true); return;
66 void karmaLoseDifference(entity attacker, entity target)
68 if (autocvar_g_mmm_karma_severity <= 0.09)
69 autocvar_g_mmm_karma_severity = 0.1;
70 else if (autocvar_g_mmm_karma_severity > 1)
71 autocvar_g_mmm_karma_severity = 1;
73 //BASIC MATH THEORY: example: 1000 * 0.3 * (0.1 + 0.4) * 0.25 // karma points reduce when player attacked to other player
74 if (target.karmapoints < attacker.karmapoints)
76 float decreasekarma = - ( target.karmapoints * random() * ( 0.1 + random() ) * autocvar_g_mmm_karma_severity );
77 GameRules_scoring_add(attacker, MMM_KARMA, decreasekarma);
78 attacker.karmapoints = attacker.karmapoints + decreasekarma;
80 else if (target.karmapoints > attacker.karmapoints)
82 float decreasekarma = - ( target.karmapoints * random() * ( 0.1 + random() ) * autocvar_g_mmm_karma_severity );
83 GameRules_scoring_add(attacker, MMM_KARMA, decreasekarma);
84 attacker.karmapoints = attacker.karmapoints + decreasekarma;
88 float decreasekarma = - ( target.karmapoints * random() * ( 0.1 + random() ) * autocvar_g_mmm_karma_severity );
89 GameRules_scoring_add(attacker, MMM_KARMA, decreasekarma);
90 attacker.karmapoints = attacker.karmapoints + decreasekarma;
94 void karmaWinDifference(entity it)
96 GameRules_scoring_add(it, SCORE, 1); // reward civilians who make it to the end of the round time limit
97 float increasekarma = ( autocvar_g_mmm_min_karma_points * random() * ( 0.1 + random() ) * 0.12 );
98 GameRules_scoring_add(it, MMM_KARMA, increasekarma);
99 it.karmapoints = it.karmapoints + increasekarma;
103 void mmm_UpdateScores(bool timed_out)
105 // give players their hard-earned kills now that the round is over
108 it.totalfrags += it.mmm_validkills;
109 if(it.mmm_validkills)
111 GameRules_scoring_add(it, SCORE, it.mmm_validkills);
113 it.mmm_validkills = 0;
114 // player survived the round
115 if(IS_PLAYER(it) && !IS_DEAD(it)) // LegendGuard adds something for Karma 21-02-2021
117 if((autocvar_g_mmm_reward_civilian && timed_out && it.mmm_status == MMM_STATUS_CIVILIAN)
118 || (autocvar_g_mmm_reward_civilian && !timed_out && it.mmm_status == MMM_STATUS_CIVILIAN))
120 karmaWinDifference(it);
121 //PrintToChatAll(sprintf("^2REWARD ^7it.karmapoints: ^1%f", it.karmapoints));
124 //Detective reward after investigated a corpse
125 if((autocvar_g_mmm_reward_detective && timed_out && it.mmm_status == MMM_STATUS_DETECTIVE)
126 || (autocvar_g_mmm_reward_detective && !timed_out && it.mmm_status == MMM_STATUS_DETECTIVE))
128 if (it.investigated == true)
130 karmaWinDifference(it);
131 it.investigated = false;
135 if(it.mmm_status == MMM_STATUS_MURDERER)
137 karmaWinDifference(it);
138 //PrintToChatAll(sprintf("^1MURDERER ^7it.karmapoints: ^1%f", it.karmapoints));
144 float mmm_CheckWinner()
146 if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
148 // if the match times out, civilians win too!
149 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_MMM_CIVILIAN_WIN);
150 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_MMM_CIVILIAN_WIN);
155 mmm_FakeTimeLimit(it, -1);
159 mmm_UpdateScores(true);
161 allowed_to_spawn = false;
163 round_handler_Init(5, autocvar_g_mmm_warmup, autocvar_g_mmm_round_timelimit);
167 int civilian_count = 0, murderer_count = 0, detective_count = 0;
168 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
170 if(it.mmm_status == MMM_STATUS_CIVILIAN)
172 else if(it.mmm_status == MMM_STATUS_MURDERER)
174 else if(it.mmm_status == MMM_STATUS_DETECTIVE) //LegendGuard adds detective_count 20-02-2021
177 if(civilian_count > 0 && murderer_count > 0)
182 if(murderer_count > 0) // murderers win
184 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_MMM_MURDERER_WIN);
185 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_MMM_MURDERER_WIN);
187 else if(civilian_count > 0) // civilians win
189 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_MMM_CIVILIAN_WIN);
190 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_MMM_CIVILIAN_WIN);
192 else if (detective_count > 0 && civilian_count > 0) // detectives are same as civilians win
194 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_MMM_CIVILIAN_WIN);
195 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_MMM_CIVILIAN_WIN);
199 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
200 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
203 mmm_UpdateScores(false);
205 allowed_to_spawn = false;
207 round_handler_Init(5, autocvar_g_mmm_warmup, autocvar_g_mmm_round_timelimit);
213 it.respawn_flags = RESPAWN_SILENT; //CSQC print output respawn lib.qh error fix
216 mmm_FakeTimeLimit(it, -1);
223 void Karma_WarningCheck(entity it)
225 float totalmeankarma = ((autocvar_g_mmm_max_karma_points + autocvar_g_mmm_min_karma_points + it.karmapoints) / 3);
226 if (it.karmapoints <= totalmeankarma)
228 Send_Notification(NOTIF_ONE_ONLY, it, MSG_INFO, INFO_MMM_KARMAWARNING);
229 //centerprint(it, strcat(BOLD_OPERATOR, "^1KARMA WARNING!\n^3Here, have the Rifle!"));
230 GiveWeapon(it, WEP_RIFLE.m_id, OP_PLUS, 1);
234 void mmm_RoundStart()
236 allowed_to_spawn = boolean(warmup_stage);
241 if(IS_PLAYER(it) && !IS_DEAD(it))
246 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!
247 it.mmm_validkills = 0;
250 int murderer_count = ((autocvar_g_mmm_murderer_count >= 1) ? autocvar_g_mmm_murderer_count : max(floor(playercount * autocvar_g_mmm_murderer_count), 1)); // There must be at least 1 murderer
251 int detective_count = ((autocvar_g_mmm_detective_count >= 1 || autocvar_g_mmm_detective_count == 0) ? autocvar_g_mmm_detective_count : floor(playercount * autocvar_g_mmm_detective_count)); // It's fine if there are no detectives
253 int total_murderers = 0;
254 int total_detectives = 0;
255 int total_civilians = 0;
257 FOREACH_CLIENT_RANDOM(IS_PLAYER(it) && !IS_DEAD(it),
259 if (total_murderers < murderer_count) {
260 it.mmm_status = MMM_STATUS_MURDERER;
262 } else if (total_detectives < detective_count) {
263 it.mmm_status = MMM_STATUS_DETECTIVE;
266 it.mmm_status = MMM_STATUS_CIVILIAN;
271 //LOG_INFOF("Total players: %d || Murderers: %d, Detectives: %d, Civilians: %d", playercount, total_murderers, total_detectives, total_civilians);
273 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
276 it.activekillerrole = false;
278 if(it.mmm_status == MMM_STATUS_CIVILIAN)
280 Karma_WarningCheck(it);
281 //Gives Mine Layer weapon to the player
282 GiveWeapon(it, WEP_MINE_LAYER.m_id, OP_PLUS, 1);
283 Send_Notification(NOTIF_ONE_ONLY, it, MSG_CENTER, CENTER_MMM_CIVILIAN);
284 Send_Notification(NOTIF_ONE_ONLY, it, MSG_INFO, INFO_MMM_CIVILIAN);
285 //PrintToChatAll(sprintf("^1DEBUG^7: %s is ^2Civilian^7!", it.netname));
287 else if(it.mmm_status == MMM_STATUS_MURDERER)
289 Karma_WarningCheck(it);
290 //Gives Mine Layer weapon to the player
291 GiveWeapon(it, WEP_MINE_LAYER.m_id, OP_PLUS, 1);
292 Send_Notification(NOTIF_ONE_ONLY, it, MSG_CENTER, CENTER_MMM_MURDERER);
293 Send_Notification(NOTIF_ONE_ONLY, it, MSG_INFO, INFO_MMM_MURDERER);
294 //PrintToChatAll(sprintf("^1DEBUG^7: %s is ^1Murderer^7!", it.netname));
296 else if(it.mmm_status == MMM_STATUS_DETECTIVE)
298 Karma_WarningCheck(it);
299 //Gives Shockwave and Mine Layer weapon to the player
300 GiveWeapon(it, WEP_SHOCKWAVE.m_id, OP_PLUS, 1);
301 GiveWeapon(it, WEP_MINE_LAYER.m_id, OP_PLUS, 1);
302 Send_Notification(NOTIF_ONE_ONLY, it, MSG_CENTER, CENTER_MMM_DETECTIVE);
303 Send_Notification(NOTIF_ONE_ONLY, it, MSG_INFO, INFO_MMM_DETECTIVE);
304 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_MMM_WHOISDETECTIVE, it.netname);
306 mmm_FakeTimeLimit(it, round_handler_GetEndTime());
310 bool mmm_CheckPlayers()
312 static int prev_missing_players;
313 allowed_to_spawn = true;
316 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
318 //PrintToChatAll(sprintf("it.karmapoints ^5begin: ^3%f",it.karmapoints));
321 //PrintToChatAll(sprintf("it.karmapoints ^6end: ^3%f",it.karmapoints));
324 if (playercount >= 2)
326 if(prev_missing_players > 0)
327 Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_PLAYERS);
328 prev_missing_players = -1;
334 if(prev_missing_players > 0)
335 Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_PLAYERS);
336 prev_missing_players = -1;
340 // if we get here, only 1 player is missing
341 if(prev_missing_players != 1)
343 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_MISSING_PLAYERS, 1);
344 prev_missing_players = 1;
349 bool mmm_isEliminated(entity e)
351 if(INGAME_JOINED(e) && (IS_DEAD(e) || e.frags == FRAGS_PLAYER_OUT_OF_GAME))
353 if(INGAME_JOINING(e))
358 void mmm_Initialize() // run at the start of a match, initiates game mode
360 GameRules_scoring(0, SFL_SORT_PRIO_PRIMARY, 0, {
361 field(SP_MMM_KARMA, "karma", SFL_SORT_PRIO_SECONDARY); //LegendGuard adds Karma points in the scoreboard 22-02-2021
364 allowed_to_spawn = true;
365 round_handler_Spawn(mmm_CheckPlayers, mmm_CheckWinner, mmm_RoundStart);
366 round_handler_Init(5, autocvar_g_mmm_warmup, autocvar_g_mmm_round_timelimit);
367 EliminatedPlayers_Init(mmm_isEliminated);
370 void checkWeaponDeathtype(entity target, float deathtype)
374 case WEP_ARC.m_id: case 276: case 788: target.killedwithweapon = "Impacted by the Arc's electric shock"; return;
375 case WEP_BLASTER.m_id: case 513: target.killedwithweapon = "Blasted by the Blaster"; return;
376 case WEP_CRYLINK.m_id: case 263: case 519: target.killedwithweapon = "Shot by the Crylink"; return;
377 case WEP_DEVASTATOR.m_id: case 522: case 1546: target.killedwithweapon = "Bombarded by the Devastator"; return;
378 case WEP_ELECTRO.m_id: case 262: case 518: case 1542: target.killedwithweapon = "Electrocuted by the Electro"; return;
379 case WEP_FIREBALL.m_id: case 273: case 529: case 1297: target.killedwithweapon = "Burned by the Fireball"; return;
380 case WEP_HAGAR.m_id: case 265: target.killedwithweapon = "Gunned by the Hagar"; return;
381 case WEP_HLAC.m_id: case 270: case 526: target.killedwithweapon = "Cut down with the HLAC"; return;
382 case WEP_HOOK.m_id: case 1805: target.killedwithweapon = "Caught in Hook gravity bomb"; return;
383 case WEP_MACHINEGUN.m_id: target.activekillerrole = true; target.killedwithweapon = "Riddled full of holes by the Machine Gun"; return;
384 case WEP_MINE_LAYER.m_id: case 517: case 1541: target.killedwithweapon = "Exploited by the Mine Layer"; return;
385 case WEP_MORTAR.m_id: case 516: case 1284: target.killedwithweapon = "Blew up with the Mortar"; return;
386 case WEP_OVERKILL_NEX.m_id: target.killedwithweapon = "Sniped by the Overkill Nex"; return;
387 case WEP_RIFLE.m_id: case 272: target.activekillerrole = true; target.killedwithweapon = "Sniped by the Rifle"; return;
388 case WEP_SEEKER.m_id: case 274: case 786: target.killedwithweapon = "Blasted by the Seeker"; return;
389 case WEP_SHOCKWAVE.m_id: target.killedwithweapon = "Gunned down by the Shockwave"; return;
390 case 275: target.killedwithweapon = "Knocked by the Shockwave"; return;
391 case WEP_SHOTGUN.m_id: target.activekillerrole = true; target.killedwithweapon = "Shot by Shotgun"; return;
392 case 258: target.killedwithweapon = "Knocked by the Shotgun"; return;
393 case WEP_TUBA.m_id: target.killedwithweapon = "Ear pain by the @!#%'n Tuba"; return;
394 case WEP_VAPORIZER.m_id: case 257: case 769: target.killedwithweapon = "Sniped by the Vaporizer"; return;
395 case WEP_VORTEX.m_id: target.killedwithweapon = "Sniped by the Vortex"; return;
396 case DEATH_FALL.m_id: target.killedwithweapon = "Fall"; return;
397 case DEATH_FIRE.m_id: target.killedwithweapon = "Burned with the fire"; return;
398 case DEATH_LAVA.m_id: target.killedwithweapon = "Burned in lava"; return;
399 case DEATH_MIRRORDAMAGE.m_id: target.killedwithweapon = "Suicide"; return;
400 case DEATH_SLIME.m_id: target.killedwithweapon = "Melted in slime"; return;
401 case DEATH_TELEFRAG.m_id: target.killedwithweapon = "Telefragged"; return;
402 case DEATH_NADE.m_id: target.killedwithweapon = "Blown up by the nade"; return;
403 case DEATH_NADE_NAPALM.m_id: target.killedwithweapon = "Burned by the Napalm nade"; return;
404 case DEATH_NADE_ICE.m_id: target.killedwithweapon = "Frozen by the Ice nade"; return;
405 case DEATH_NADE_HEAL.m_id: target.killedwithweapon = "Sucked by the Heal nade"; return;
406 default: target.killedwithweapon = "Unknown"; return;
410 void ReduceKarmaPointsandFrags(entity frag_attacker, entity frag_target, float frag_deathtype, entity wep_ent)
412 karmaLoseDifference(frag_attacker, frag_target);
413 GiveFrags(frag_attacker, frag_target, ((autocvar_g_mmm_punish_teamkill) ? -1 : -2), frag_deathtype, wep_ent.weaponentity_fld);
414 frag_target.whokilled = frag_attacker.netname;
421 MUTATOR_HOOKFUNCTION(mmm, FragCenterMessage)
423 entity attacker = M_ARGV(0, entity);
424 entity targ = M_ARGV(1, entity);
426 // Conditions that count as team kill in MMM
427 if((attacker.mmm_status == targ.mmm_status) ||
428 (attacker.mmm_status == MMM_STATUS_DETECTIVE && targ.mmm_status == MMM_STATUS_CIVILIAN) ||
429 (attacker.mmm_status == MMM_STATUS_CIVILIAN && targ.mmm_status == MMM_STATUS_DETECTIVE)
432 Send_Notification(NOTIF_ONE, attacker, MSG_CENTER, CENTER_DEATH_TEAMKILL_FRAG, targ.netname);
433 Send_Notification(NOTIF_ONE, targ, MSG_CENTER, CENTER_DEATH_TEAMKILL_FRAGGED, "???");
438 MUTATOR_HOOKFUNCTION(mmm, ClientObituary)
440 // LegendGuard's IDEA: To adjust the grade of severity of karma,
441 // we could add if sentence per weapons and adjust each weapon attack
442 // its own grade. Instead doing random decrease grade 22-02-2021
444 // in mmm, announcing a frag would tell everyone who the murderer is
445 entity frag_attacker = M_ARGV(1, entity);
446 entity frag_target = M_ARGV(2, entity);
447 M_ARGV(5, bool) = true; // anonymous attacker
449 if(IS_PLAYER(frag_attacker) && frag_attacker != frag_target)
451 float frag_deathtype = M_ARGV(3, float);
452 entity wep_ent = M_ARGV(4, entity);
454 //PrintToChatAll(strcat("deathtype var: ", ftos(frag_deathtype)));
455 checkWeaponDeathtype(frag_target, frag_deathtype);
456 // "team" kill, a point is awarded to the player by default so we must take it away plus an extra one
457 // unless the player is going to be punished for suicide, in which case just remove one
458 if(frag_attacker.mmm_status == frag_target.mmm_status)
460 //PrintToChatAll("^1DEBUG^7: A ^2PLAYER^7 has fragged a ^2PLAYER OF HIS OWN TEAM^7, TOO BAD!");
461 ReduceKarmaPointsandFrags(frag_attacker, frag_target, frag_deathtype, wep_ent);
462 frag_attacker.killsound -= 1; frag_attacker.typehitsound += 1; // Teamkill sound
463 switch (frag_attacker.mmm_status)
465 case MMM_STATUS_CIVILIAN: frag_target.killerrole = "\n^3Killer role: ^2Civilian"; return;
466 case MMM_STATUS_MURDERER: frag_target.killerrole = "\n^3Killer role: ^1Murderer"; return;
467 case MMM_STATUS_DETECTIVE: frag_target.killerrole = "\n^3Killer role: ^4Detective"; return;
470 //PrintToChatAll(sprintf("frag_attacker.karmapoints: ^1%f", frag_attacker.karmapoints));
473 if(frag_attacker.mmm_status == MMM_STATUS_DETECTIVE)
475 if (frag_target.mmm_status == MMM_STATUS_CIVILIAN)
477 //PrintToChatAll("^1DEBUG^7: A ^4Detective^7 fragged an ^2Civilian^7/^4Detective^7, TOO BAD!");
478 ReduceKarmaPointsandFrags(frag_attacker, frag_target, frag_deathtype, wep_ent);
479 frag_attacker.killsound -= 1; frag_attacker.typehitsound += 1; // Teamkill sound
480 frag_target.killerrole = "\n^3Killer role: ^4Detective";
481 //PrintToChatAll(sprintf("frag_attacker.karmapoints: ^1%f", frag_attacker.karmapoints));
485 frag_target.whokilled = frag_attacker.netname;
486 frag_target.killerrole = "\n^3Killer role: ^4Detective";
490 if (frag_attacker.mmm_status == MMM_STATUS_CIVILIAN)
492 if (frag_target.mmm_status == MMM_STATUS_DETECTIVE)
494 //PrintToChatAll("^1DEBUG^7: An ^2Civilian^7 fragged a ^4Detective^7, TOO BAD!");
495 ReduceKarmaPointsandFrags(frag_attacker, frag_target, frag_deathtype, wep_ent);
496 frag_attacker.killsound -= 1; frag_attacker.typehitsound += 1; // Teamkill sound
497 frag_target.killerrole = "\n^3Killer role: ^2Civilian";
501 frag_target.whokilled = frag_attacker.netname;
502 frag_target.killerrole = "\n^3Killer role: ^2Civilian";
506 if (frag_attacker.mmm_status == MMM_STATUS_MURDERER)
508 if (frag_target.mmm_status == MMM_STATUS_CIVILIAN)
510 frag_target.whokilled = frag_attacker.netname;
511 frag_target.killerrole = "\n^3Killer role: ^1Murderer";
515 frag_target.whokilled = frag_attacker.netname;
516 frag_target.killerrole = "\n^3Killer role: ^1Murderer";
519 //if mmm_status is 1, means civilian, 2 means murderer, 3 means detective
520 //PrintToChatAll(sprintf("^1DEBUG^7: frag_attacker.mmm_status is ^3%s^7",ftos(frag_attacker.mmm_status)));
521 //PrintToChatAll(sprintf("^1DEBUG^7: frag_target.mmm_status is ^3%s^7",ftos(frag_target.mmm_status)));
525 float frag_deathtype = M_ARGV(3, float);
526 checkWeaponDeathtype(frag_target, frag_deathtype);
530 //karma weapon damage, halve the damage attack when player has low karma 20-03-2021
531 MUTATOR_HOOKFUNCTION(mmm, Damage_Calculate)
533 entity attacker = M_ARGV(1, entity);
534 entity target = M_ARGV(2, entity);
535 float deathtype = M_ARGV(3, float);
536 float damage = M_ARGV(4, float);
537 vector force = M_ARGV(6, vector);
538 string corpsemessagestrcat = "";
539 string corpsemsginfo = "";
541 if (autocvar_g_mmm_karma_damageactive != false)
543 if (IS_PLAYER(attacker))
545 if(target == attacker) // damage done to yourself
547 damage /= autocvar_g_weapondamagefactor / (attacker.karmapoints / autocvar_g_mmm_max_karma_points);
548 force /= autocvar_g_weaponforcefactor / (attacker.karmapoints / autocvar_g_mmm_max_karma_points);
550 else if (target != attacker)
552 damage /= autocvar_g_weapondamagefactor / (attacker.karmapoints / autocvar_g_mmm_max_karma_points);
553 force /= autocvar_g_weaponforcefactor / (attacker.karmapoints / autocvar_g_mmm_max_karma_points);
557 damage *= autocvar_g_weapondamagefactor;
558 force *= autocvar_g_weaponforcefactor;
563 //CORPSE DETECTION SKILL 21-03-2021
566 //Shockwave weapon as radar gun to check the corpses 22-03-2021
567 if(DEATH_ISWEAPON(deathtype, WEP_SHOCKWAVE))
569 if (target.killedwithweapon == "")
570 target.killedwithweapon = "UNKNOWN CAUSE";
572 if (target.activekillerrole != true)
574 target.killerrole = "";
575 target.activekillerrole = false;
578 string killedbyphrase = strcat("^3Killed by:^7 ", target.whokilled, target.killerrole);
579 string wepkilledphrase = strcat("^3Cause:^7 ", target.killedwithweapon);
580 if (target.whokilled == "")
583 if (target.killedwithweapon == "")
584 wepkilledphrase = "^3Cause:^7 UNKNOWN CAUSE";
589 if (target.mmm_status == MMM_STATUS_CIVILIAN)
591 //try to add centerprint message for chat privately if possible
592 corpsemessagestrcat = strcat("^5DEAD PLAYER DETAILS^7: \n^3Name:^7 ", target.netname, "\n^3Role: ^2Civilian", "\n", killedbyphrase, "\n", wepkilledphrase);
593 corpsemsginfo = strcat("^5DEAD PLAYER DETAILS^7: ^3Name:^7 ", target.netname, " ^3Role: ^2Civilian", " ", killedbyphrase, " ", wepkilledphrase);
594 //centerprint(attacker, strcat(BOLD_OPERATOR, corpsemessagestrcat));
595 Send_Notification(NOTIF_ONE_ONLY, attacker, MSG_CENTER, CENTER_MMM_CORPSEDETECTION, corpsemessagestrcat);
596 Send_Notification(NOTIF_ONE_ONLY, attacker, MSG_INFO, INFO_MMM_CORPSEDETECTION, corpsemsginfo);
598 else if (target.mmm_status == MMM_STATUS_MURDERER)
600 corpsemessagestrcat = strcat("^5DEAD PLAYER DETAILS^7: \n^3Name:^7 ", target.netname, "\n^3Role: ^1Murderer", "\n", killedbyphrase, "\n", wepkilledphrase);
601 corpsemsginfo = strcat("^5DEAD PLAYER DETAILS^7: ^3Name:^7 ", target.netname, " ^3Role: ^1Murderer", " ", killedbyphrase, " ", wepkilledphrase);
602 //centerprint(attacker, strcat(BOLD_OPERATOR, corpsemessagestrcat));
603 Send_Notification(NOTIF_ONE_ONLY, attacker, MSG_CENTER, CENTER_MMM_CORPSEDETECTION, corpsemessagestrcat);
604 Send_Notification(NOTIF_ONE_ONLY, attacker, MSG_INFO, INFO_MMM_CORPSEDETECTION, corpsemsginfo);
606 else if (target.mmm_status == MMM_STATUS_DETECTIVE)
608 corpsemessagestrcat = strcat("^5DEAD PLAYER DETAILS^7: \n^3Name:^7 ", target.netname, "\n^3Role: ^4Detective", "\n", killedbyphrase, "\n", wepkilledphrase);
609 corpsemsginfo = strcat("^5DEAD PLAYER DETAILS^7: ^3Name:^7 ", target.netname, " ^3Role: ^4Detective", " ", killedbyphrase, " ", wepkilledphrase);
610 //centerprint(attacker, strcat(BOLD_OPERATOR, corpsemessagestrcat));
611 Send_Notification(NOTIF_ONE_ONLY, attacker, MSG_CENTER, CENTER_MMM_CORPSEDETECTION, corpsemessagestrcat);
612 Send_Notification(NOTIF_ONE_ONLY, attacker, MSG_INFO, INFO_MMM_CORPSEDETECTION, corpsemsginfo);
614 attacker.investigated = true;
618 M_ARGV(4, float) = damage;
619 M_ARGV(6, vector) = force;
622 MUTATOR_HOOKFUNCTION(mmm, PlayerPreThink)
624 entity player = M_ARGV(0, entity);
626 bool playercheck = false;
628 if (playercheck != true)
630 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
637 //if the murderer is still here around, then avoid illogical winning
638 if (playercheck == true)
640 FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
642 if (playercount == 3)
644 if (it.mmm_status == MMM_STATUS_DETECTIVE)
645 it.mmm_status = MMM_STATUS_CIVILIAN;
650 if(IS_PLAYER(player) || INGAME(player))
652 if (player.karmaspectated != true)
654 if (CS(player).scorekeeper.(scores(SP_MMM_KARMA)) <= 0) //wtf? Visualization works 100% correct?
655 GameRules_scoring_add(player, MMM_KARMA, player.karmapoints);
656 player.karmaspectated = true;
658 // update the scoreboard colour display to out the real killer at the end of the round
659 // running this every frame to avoid cheats
660 int plcolor = MMM_COLOR_CIVILIAN;
661 if(player.mmm_status == MMM_STATUS_CIVILIAN && game_stopped) //Civilian status by default
662 plcolor = MMM_COLOR_CIVILIAN;
663 if(player.mmm_status == MMM_STATUS_MURDERER && game_stopped)
664 plcolor = MMM_COLOR_MURDERER;
665 //LegendGuard adds for Detective 21-02-2021
666 if(player.mmm_status == MMM_STATUS_DETECTIVE)// && game_stopped)
667 plcolor = MMM_COLOR_DETECTIVE;
668 setcolor(player, plcolor);
671 player.karmastarted = false;
673 //CORPSE FEATURE 10-03-2021
676 player.event_damage = func_null;
678 player.solid = SOLID_CORPSE;
679 set_movetype(player, MOVETYPE_STEP); //test with MOVETYPE_TOSS or MOVETYPE_WALK (it's like sliding object) or MOVETYPE_BOUNCE (maybe not good)
683 MUTATOR_HOOKFUNCTION(mmm, PlayerSpawn)
685 entity player = M_ARGV(0, entity);
688 if (player.karmastarted != true)
690 CS(player).scorekeeper.(scores(SP_MMM_KARMA)) = 0; //full karma reset lol
691 player.karmapoints = autocvar_g_mmm_max_karma_points;
692 GameRules_scoring_add(player, MMM_KARMA, player.karmapoints);
693 player.karmastarted = true;
696 player.mmm_status = 0;
697 player.mmm_validkills = 0;
698 INGAME_STATUS_SET(player, INGAME_STATUS_JOINED);
700 eliminatedPlayers.SendFlags |= 1;
703 MUTATOR_HOOKFUNCTION(mmm, ForbidSpawn)
705 entity player = M_ARGV(0, entity);
707 // spectators / observers that weren't playing can join; they are
708 // immediately forced to observe in the PutClientInServer hook
709 // this way they are put in a team and can play in the next round
710 if (!allowed_to_spawn && INGAME(player))
715 MUTATOR_HOOKFUNCTION(mmm, PutClientInServer)
717 entity player = M_ARGV(0, entity);
719 if (!allowed_to_spawn && IS_PLAYER(player)) // this is true even when player is trying to join
721 TRANSMUTE(Observer, player);
722 if (CS(player).jointime != time && !INGAME(player)) // not when connecting
724 INGAME_STATUS_SET(player, INGAME_STATUS_JOINING);
725 Send_Notification(NOTIF_ONE_ONLY, player, MSG_INFO, INFO_CA_JOIN_LATE);
730 MUTATOR_HOOKFUNCTION(mmm, reset_map_players)
732 FOREACH_CLIENT(true, {
733 CS(it).killcount = 0;
735 mmm_FakeTimeLimit(it, -1); // restore original timelimit
736 if (!INGAME(it) && IS_BOT_CLIENT(it))
737 INGAME_STATUS_SET(it, INGAME_STATUS_JOINED);
740 TRANSMUTE(Player, it);
741 INGAME_STATUS_SET(it, INGAME_STATUS_JOINED);
742 it.respawn_flags = RESPAWN_SILENT; //CSQC print output respawn lib.qh error fix
743 PutClientInServer(it);
746 bot_relinkplayerlist();
750 MUTATOR_HOOKFUNCTION(mmm, reset_map_global)
752 allowed_to_spawn = true;
756 entity mmm_LastPlayerForTeam(entity this)
758 entity last_pl = NULL;
759 FOREACH_CLIENT(IS_PLAYER(it) && it != this, {
760 if (!IS_DEAD(it) && this.mmm_status == it.mmm_status)
773 void mmm_LastPlayerForTeam_Notify(entity this)
775 if (!warmup_stage && round_handler_IsActive() && round_handler_IsRoundStarted())
777 entity pl = mmm_LastPlayerForTeam(this);
779 Send_Notification(NOTIF_ONE_ONLY, pl, MSG_CENTER, CENTER_ALONE);
783 MUTATOR_HOOKFUNCTION(mmm, PlayerDies)
785 entity frag_attacker = M_ARGV(1, entity);
786 entity frag_target = M_ARGV(2, entity);
787 //float frag_deathtype = M_ARGV(3, float);
789 mmm_LastPlayerForTeam_Notify(frag_target);
790 if (!allowed_to_spawn)
792 frag_target.respawn_flags = RESPAWN_DENY;
793 // prevent unwanted sudden rejoin as spectator and movement of spectator camera
794 frag_target.respawn_time = time + 2;
796 frag_target.respawn_flags |= RESPAWN_DENY;
797 frag_target.event_damage = func_null;
798 frag_target.health = 0;
801 eliminatedPlayers.SendFlags |= 1;
803 //if(frag_attacker.mmm_status == frag_target.mmm_status)
804 // killed an ally! punishment is sentenced
805 if(frag_attacker.mmm_status == MMM_STATUS_DETECTIVE)
807 if (frag_target.mmm_status == MMM_STATUS_CIVILIAN)
809 //PrintToChatAll("^1DEBUG^7: ^4DETECTIVE ^1DAMAGE/DEAD^7 HAS TAKEN!");
810 Damage(frag_attacker, frag_attacker, frag_attacker, autocvar_g_mmm_karma_damagepunishmentdeal, DEATH_MIRRORDAMAGE.m_id, DMG_NOWEP, frag_attacker.origin, '0 0 0');
813 if (frag_attacker.mmm_status == MMM_STATUS_CIVILIAN)
815 if (frag_target.mmm_status == MMM_STATUS_DETECTIVE)
817 //PrintToChatAll("^1DEBUG^7: ^2CIVILIAN ^1DAMAGE/DEAD^7 HAS TAKEN!");
818 Damage(frag_attacker, frag_attacker, frag_attacker, autocvar_g_mmm_karma_damagepunishmentdeal, DEATH_MIRRORDAMAGE.m_id, DMG_NOWEP, frag_attacker.origin, '0 0 0');
821 if (frag_attacker.mmm_status == MMM_STATUS_MURDERER)
823 if (frag_target.mmm_status == MMM_STATUS_MURDERER)
825 //PrintToChatAll("^1DEBUG^7: ^1MURDERER ^1DAMAGE/DEAD^7 HAS TAKEN!");
826 Damage(frag_attacker, frag_attacker, frag_attacker, autocvar_g_mmm_karma_damagepunishmentdeal, DEATH_MIRRORDAMAGE.m_id, DMG_NOWEP, frag_attacker.origin, '0 0 0');
832 MUTATOR_HOOKFUNCTION(mmm, ClientDisconnect)
834 entity player = M_ARGV(0, entity);
836 if (IS_PLAYER(player) && !IS_DEAD(player))
837 mmm_LastPlayerForTeam_Notify(player);
841 MUTATOR_HOOKFUNCTION(mmm, MakePlayerObserver)
843 entity player = M_ARGV(0, entity);
845 if (IS_PLAYER(player) && !IS_DEAD(player))
846 mmm_LastPlayerForTeam_Notify(player);
847 if (player.karmaspectated == true)
848 player.karmaspectated = false;
849 if (player.killindicator_teamchange == -2) // player wants to spectate
850 INGAME_STATUS_CLEAR(player);
852 player.frags = FRAGS_PLAYER_OUT_OF_GAME;
854 eliminatedPlayers.SendFlags |= 1;
857 player.mmm_validkills = 0;
858 player.mmm_status = 0;
859 mmm_FakeTimeLimit(player, -1); // restore original timelimit
860 return false; // allow team reset
862 return true; // prevent team reset
865 MUTATOR_HOOKFUNCTION(mmm, Scores_CountFragsRemaining)
867 // announce remaining frags?
871 MUTATOR_HOOKFUNCTION(mmm, GiveFragsForKill, CBC_ORDER_FIRST)
873 entity frag_attacker = M_ARGV(0, entity);
874 if(!warmup_stage && round_handler_IsActive() && round_handler_IsRoundStarted())
875 frag_attacker.mmm_validkills += M_ARGV(2, float);
876 M_ARGV(2, float) = 0; // score will be given to the winner when the round ends
880 MUTATOR_HOOKFUNCTION(mmm, AddPlayerScore)
882 // add scorefield for scoreboard here
883 entity scorefield = M_ARGV(0, entity);
884 if(scorefield == SP_KILLS || scorefield == SP_DEATHS || scorefield == SP_SUICIDES || scorefield == SP_DMG || scorefield == SP_DMGTAKEN)
885 M_ARGV(1, float) = 0; // don't report that the player has killed or been killed, that would out them as a murderer!
888 MUTATOR_HOOKFUNCTION(mmm, CalculateRespawnTime)
890 // no respawn calculations needed, player is forced to spectate anyway
894 //if server admin sets "sv_ready_restart_after_countdown 1", will avoid possible visual failure for karma in the scoreboard
895 MUTATOR_HOOKFUNCTION(mmm, ReadLevelCvars)
897 sv_ready_restart_after_countdown = 0;
900 MUTATOR_HOOKFUNCTION(mmm, Bot_FixCount, CBC_ORDER_EXCLUSIVE)
902 FOREACH_CLIENT(IS_REAL_CLIENT(it), {
903 if (IS_PLAYER(it) || INGAME_JOINED(it))
910 MUTATOR_HOOKFUNCTION(mmm, ClientCommand_Spectate)
912 entity player = M_ARGV(0, entity);
916 // they're going to spec, we can do other checks
917 if (autocvar_sv_spectate && (IS_SPEC(player) || IS_OBSERVER(player)))
918 Send_Notification(NOTIF_ONE_ONLY, player, MSG_INFO, INFO_CA_LEAVE);
919 return MUT_SPECCMD_FORCE;
922 return MUT_SPECCMD_CONTINUE;
925 MUTATOR_HOOKFUNCTION(mmm, BotShouldAttack)
927 entity bot = M_ARGV(0, entity);
928 entity targ = M_ARGV(1, entity);
930 if(targ.mmm_status == bot.mmm_status)
933 // LegendGuard fixed the problem of Detectives and Civilians attacking each other 26-03-2021
934 if(bot.mmm_status == MMM_STATUS_DETECTIVE)
935 if(targ.mmm_status == MMM_STATUS_CIVILIAN)