]> git.rm.cloudns.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Merge 'LegendaryGuard/ttt' to 'Legendary/bai_mod'
authorLegendaryGuard <rootuser999@gmail.com>
Fri, 16 Apr 2021 20:21:10 +0000 (22:21 +0200)
committerLegendaryGuard <rootuser999@gmail.com>
Fri, 16 Apr 2021 20:21:10 +0000 (22:21 +0200)
1  2 
gamemodes-server.cfg
notifications.cfg
qcsrc/common/ent_cs.qc
qcsrc/common/gamemodes/gamemode/mmm/sv_mmm.qc
qcsrc/common/notifications/all.inc
qcsrc/common/notifications/all.qh
qcsrc/common/scores.qh
qcsrc/server/world.qc

index 11d49b9e9e5af0dd07efa44b4058a0ed9b9f20b7,f19b435f06baf1afc1361b14e6b6dc3c43c54c3d..7577822c64f1aea368bb25158f7dd111ff2cf6d4
@@@ -555,4 -564,25 +564,25 @@@ set g_invasion_type 0 "type of invasio
  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"
+ set g_duel_not_dm_maps 0 "when this is set, DM maps will NOT be listed in duel"
+ //LegendGuard adds mmm cvars for MMM 20-02-2021
+ // ==========
+ //  murder in megaerebus manor
+ // ==========
+ set g_mmm 0 "Murder in Megaerebus Manor: A group of space civilians have murderers among them. Murderers must kill civilians, while the civilians have to try to find and kill the murderers"
+ set g_mmm_not_lms_maps 0 "when this is set, LMS maps will NOT be listed in mmm"
+ set g_mmm_civilian_count 0.625 "number of players who will become civilians, 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_mmm_murderer_count 0.25 "number of players who will become murderers, 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_mmm_punish_teamkill 0 "enable option to kill the player when they kill an ally"
+ set g_mmm_reward_civilian 1 "give a point to all civilian players if the round timelimit is reached, in addition to the points given for kills"
+ set g_mmm_warmup 10 "how long the players will have time to run around the map before the round starts"
+ set g_mmm_round_timelimit 180 "round time limit in seconds"
+ set g_mmm_max_karma_points 1000 "limit of maximum number of karma points will have in the server"
+ set g_mmm_min_karma_points 400 "limit where number of karma points can be reached when are being decreased"
+ set g_mmm_karma_bankick_tool 0 "tool for strict rules when karma is low: '0' forces player to spec, '1' kicks player, '2' bans player"
+ set g_mmm_karma_bantime 1800 "number of seconds to ban someone with very low karma"
+ set g_mmm_karma_damageactive 1 "enable karma damage rule. If a player's karma is low, they will not do as much damage as a player who has high or full karma"
+ set g_mmm_karma_damagepunishmentdeal 20 "punishment damage points when player kills an ally"
+ set g_mmm_karma_severity 0.25 "how severe karma is to decrease karma points to the players [0.1 - 1.0]"
 -set g_mmm_reward_sleuth 1 "give a point to all sleuth players if investigated corpses"
++set g_mmm_reward_sleuth 1 "give a point to all sleuth players if investigated corpses"
index 8c5b28bd5e4c23e211a960f5a17a26a87c3ac271,3028b11b970330dd76b7a6451c9667b5d5232ce3..0ef257f54d1fa190575a72c7e05d86a5399a07ae
@@@ -539,7 -543,13 +543,14 @@@ seta notification_CENTER_TEAMCHANGE_SPE
  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 mmm notification for MMM 20-02-2021
+ seta notification_CENTER_MMM_MURDERER "1" "0 = off, 1 = centerprint"
+ seta notification_CENTER_MMM_MURDERER_WIN "1" "0 = off, 1 = centerprint"
+ seta notification_CENTER_MMM_CIVILIAN "1" "0 = off, 1 = centerprint"
+ seta notification_CENTER_MMM_CIVILIAN_WIN "1" "0 = off, 1 = centerprint"
+ seta notification_CENTER_MMM_SLEUTH "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 fbce8a70e5806bf102eb0a43e5e6fbd2983bdc30,21172448527ccf0cd4a49bce3fb1ec850f94556e..ac4aa8bf34b6a1ec2c62383bae511bff554010ab
@@@ -161,30 -157,20 +161,36 @@@ ENTCS_PROP(SOLID, true, sv_solid, solid
        { WriteByte(chan, ent.sv_solid); },
        { ent.sv_solid = ReadByte(); })
  
 +// z411 weapon
 +ENTCS_PROP(ACTIVEWEPID, false, activewepid, activewepid, ENTCS_SET_NORMAL,
 +      { WriteByte(chan, ent.activewepid); },
 +      { ent.activewepid = ReadByte(); })
 +
+ //LegendGuard adds ENTCS_PROP for MMM 20-02-2021
+ // gamemode specific player mmm status (independent of score and frags)
+ ENTCS_PROP(MMM_STATUS, true, mmm_status, mmm_status, ENTCS_SET_NORMAL,
+       { WriteShort(chan, ent.mmm_status); },
+       { ent.mmm_status = ReadShort(); })
  #ifdef SVQC
  
 -      int ENTCS_PUBLICMASK = 0;
 +      int ENTCS_PUBLICMASK = 0, ENTCS_PRIVATEMASK = 0;
        STATIC_INIT(ENTCS_PUBLICMASK)
        {
 -              FOREACH(EntCSProps, it.m_public,
 +              FOREACH(EntCSProps, true,
                {
 -                      ENTCS_PUBLICMASK |= BIT(it.m_id);
 +                      if (it.m_public)
 +                              ENTCS_PUBLICMASK |= BIT(it.m_id);
 +                      else
 +                              ENTCS_PRIVATEMASK |= BIT(it.m_id);
 +              });
 +      }
 +
 +      void entcs_update_players(entity player)
 +      {
 +              FOREACH_CLIENT(it != player && IS_PLAYER(it),
 +              {
 +                      CS(it).entcs.SendFlags |= ENTCS_PRIVATEMASK;
                });
        }
  
index 0000000000000000000000000000000000000000,19a3f94f2d9cd238b5197dea0407c731d30e0249..74c5b9f5de516615fbc130dc5fe97aa180472cb9
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,957 +1,953 @@@
 -      {
+ #include "sv_mmm.qh"
+ //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"
+ //float autocvar_g_mmm_sleuth_count = 0.125; //I don't think that it won't be used...
+ float autocvar_g_mmm_civilian_count = 0.625;
+ //float autocvar_g_mmm_murderer_count = 0.25;
+ float autocvar_g_mmm_round_timelimit = 180;
+ float autocvar_g_mmm_warmup = 10;
+ bool autocvar_g_mmm_punish_teamkill = false;
+ bool autocvar_g_mmm_reward_civilian = true;
+ bool autocvar_g_mmm_reward_sleuth = true; //sleuth reward if investigated corpses
+ float autocvar_g_mmm_max_karma_points = 1000; //LegendGuard sets Karma points 21-02-2021
+ float autocvar_g_mmm_min_karma_points = 400;
+ int autocvar_g_mmm_karma_bankick_tool = 0; //LegendGuard sets a ban tool for server admins 11-03-2021
+ float autocvar_g_mmm_karma_bantime = 1800; //karma ban seconds
+ bool autocvar_g_mmm_karma_damageactive = true; //LegendGuard sets Karma damage setting if active 20-03-2021
+ float autocvar_g_mmm_karma_severity = 0.25;
+ float autocvar_g_mmm_karma_damagepunishmentdeal = 20; //LegendGuard sets Karma punishment damage setting if player kills an ally 28-03-2021
+ // Sleuth is a created team, this team is added inside Civilians team
+ void mmm_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(MMM_ROUNDTIMER, e) = t;
+ #endif
+ }
+ void nades_Clear(entity player);
+ void karma_Control(entity it)
+ {
+       float masksize = autocvar_g_ban_default_masksize;
+       float bantime = autocvar_g_mmm_karma_bantime;
+       if(it.karmapoints >= autocvar_g_mmm_max_karma_points)
+       {
+               //Resets karmapoints to maintain the maximum
+               //PrintToChatAll("^3REWARD ^1MAXIMUM RESET");
+               GameRules_scoring_add(it, MMM_KARMA, autocvar_g_mmm_max_karma_points - it.karmapoints);
+               it.karmapoints = autocvar_g_mmm_max_karma_points;
+       }
+       else if(it.karmapoints <= autocvar_g_mmm_min_karma_points)
+       {
+               switch (autocvar_g_mmm_karma_bankick_tool)
+               {
+                       //force to spec
+                       case 0: PutObserverInServer(it); return;
+                       //kick
+                       case 1: dropclient(it); return;
+                       //ban and kick
+                       case 2: Ban_KickBanClient(it, bantime, masksize, "Too low karma"); return;
+                       //force to spec
+                       default: PutObserverInServer(it); return;
+               }
+       }
+ }
+ void karmaLoseDifference(entity attacker, entity target)
+ {
+       if (autocvar_g_mmm_karma_severity <= 0.09)
+               autocvar_g_mmm_karma_severity = 0.1;
+       else if (autocvar_g_mmm_karma_severity > 1)
+               autocvar_g_mmm_karma_severity = 1;
+       
+       //BASIC MATH THEORY: example: 1000 * 0.3 * (0.1 + 0.4) * 0.25 // karma points reduce when player attacked to other player
+       if (target.karmapoints < attacker.karmapoints)
+       {
+               float decreasekarma = - ( target.karmapoints * random() * ( 0.1 + random() ) * autocvar_g_mmm_karma_severity );
+               GameRules_scoring_add(attacker, MMM_KARMA, decreasekarma);
+               attacker.karmapoints = attacker.karmapoints + decreasekarma;
+       }
+       else if (target.karmapoints > attacker.karmapoints)
+       {
+               float decreasekarma = - ( target.karmapoints * random() * ( 0.1 + random() ) * autocvar_g_mmm_karma_severity );
+               GameRules_scoring_add(attacker, MMM_KARMA, decreasekarma);
+               attacker.karmapoints = attacker.karmapoints + decreasekarma;
+       }
+       else
+       {
+               float decreasekarma = - ( target.karmapoints * random() * ( 0.1 + random() ) * autocvar_g_mmm_karma_severity );
+               GameRules_scoring_add(attacker, MMM_KARMA, decreasekarma);
+               attacker.karmapoints = attacker.karmapoints + decreasekarma;
+       }
+ }
+ void karmaWinDifference(entity it)
+ {
+       GameRules_scoring_add(it, SCORE, 1); // reward civilians who make it to the end of the round time limit
+       float increasekarma = ( autocvar_g_mmm_min_karma_points * random() * ( 0.1 + random() ) * 0.12 );
+       GameRules_scoring_add(it, MMM_KARMA, increasekarma);
+       it.karmapoints = it.karmapoints + increasekarma;
+       karma_Control(it);
+ }
+ void mmm_UpdateScores(bool timed_out)
+ {
+       // give players their hard-earned kills now that the round is over
+       FOREACH_CLIENT(true,
+       {
+               it.totalfrags += it.mmm_validkills;
+               if(it.mmm_validkills)
+               {
+                       GameRules_scoring_add(it, SCORE, it.mmm_validkills);
+               }
+               it.mmm_validkills = 0;
+               // player survived the round
+               if(IS_PLAYER(it) && !IS_DEAD(it)) // LegendGuard adds something for Karma 21-02-2021
+               {
+                       if((autocvar_g_mmm_reward_civilian && timed_out && it.mmm_status == MMM_STATUS_CIVILIAN) 
+                       || (autocvar_g_mmm_reward_civilian && !timed_out && it.mmm_status == MMM_STATUS_CIVILIAN))
+                       {
+                               karmaWinDifference(it);
+                               //PrintToChatAll(sprintf("^2REWARD ^7it.karmapoints: ^1%f", it.karmapoints));
+                       }
+                       //Sleuth reward after investigated a corpse
+                       if((autocvar_g_mmm_reward_sleuth && timed_out && it.mmm_status == MMM_STATUS_SLEUTH) 
+                       || (autocvar_g_mmm_reward_sleuth && !timed_out && it.mmm_status == MMM_STATUS_SLEUTH))
+                       {
+                               if (it.investigated == true)
+                               {
+                                       karmaWinDifference(it);
+                                       it.investigated = false;
+                               }
+                       }
+                       if(it.mmm_status == MMM_STATUS_MURDERER)
+                       {
+                               karmaWinDifference(it);
+                               //PrintToChatAll(sprintf("^1MURDERER ^7it.karmapoints: ^1%f", it.karmapoints));
+                       }
+               }
+       });
+ }
+ float mmm_CheckWinner()
+ {
+       if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
+       {
+               // if the match times out, civilians win too!
+               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_MMM_CIVILIAN_WIN);
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_MMM_CIVILIAN_WIN);
+               FOREACH_CLIENT(true,
+               {
+                       if(IS_PLAYER(it))
+                               nades_Clear(it);
+                       mmm_FakeTimeLimit(it, -1);
+                       karma_Control(it);
+               });
+               mmm_UpdateScores(true);
+               allowed_to_spawn = false;
+               game_stopped = true;
+               round_handler_Init(5, autocvar_g_mmm_warmup, autocvar_g_mmm_round_timelimit);
+               return 1;
+       }
+       int civilian_count = 0, murderer_count = 0, sleuth_count = 0;
+       FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
+       {
+               if(it.mmm_status == MMM_STATUS_CIVILIAN)
+                       civilian_count++;
+               else if(it.mmm_status == MMM_STATUS_MURDERER)
+                       murderer_count++;
+               else if(it.mmm_status == MMM_STATUS_SLEUTH) //LegendGuard adds sleuth_count 20-02-2021 
+                       sleuth_count++;
+       });
+       if(civilian_count > 0 && murderer_count > 0)
+       {
+               return 0;
+       }
+       if(murderer_count > 0) // murderers win
+       {
+               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_MMM_MURDERER_WIN);
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_MMM_MURDERER_WIN);
+       }
+       else if(civilian_count > 0) // civilians win
+       {
+               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_MMM_CIVILIAN_WIN);
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_MMM_CIVILIAN_WIN);
+       }
+       else if (sleuth_count > 0 && civilian_count > 0) // sleuths are same as civilians win
+       {
+               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_MMM_CIVILIAN_WIN);
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_MMM_CIVILIAN_WIN);
+       }
+       else
+       {
+               Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
+               Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
+       }
+       mmm_UpdateScores(false);
+       allowed_to_spawn = false;
+       game_stopped = true;
+       round_handler_Init(5, autocvar_g_mmm_warmup, autocvar_g_mmm_round_timelimit);
+       FOREACH_CLIENT(true,
+       {
+               if(IS_PLAYER(it))
+               {
+                       it.respawn_flags = RESPAWN_SILENT; //CSQC print output respawn lib.qh error fix
+                       nades_Clear(it);
+               }
+               mmm_FakeTimeLimit(it, -1);
+               karma_Control(it);
+       });
+       return 1;
+ }
+ void mmm_RoundStart()
+ {
+       allowed_to_spawn = boolean(warmup_stage);
+       int playercount = 0;
+       FOREACH_CLIENT(true,
+       {
+               if(IS_PLAYER(it) && !IS_DEAD(it))
+               {
+                       ++playercount;
+                       it.mmm_status = MMM_STATUS_CIVILIAN;
+               }
+               else
+                       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!
+               it.mmm_validkills = 0;
+       });
+       
+       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
+       int total_civilians = 0;
+       //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
+       int total_murderers = 0;
+       //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
+       int total_sleuths = 0;
+       //civilians TOTAL
+       FOREACH_CLIENT_RANDOM(IS_PLAYER(it) && !IS_DEAD(it),
+       {
+               if(total_civilians >= civilian_count)
+                       break;
+               //LegendGuard fixes the round start again 22-03-2021
+               total_civilians++;
+               if (total_civilians <= 1)
+               {
+                       if (total_murderers <= 1)
+                       {
+                               total_murderers++;
+                               it.mmm_status = MMM_STATUS_MURDERER;
+                       }
+               }
+               else if (total_civilians == 2)
+               {
+                       if (total_sleuths >= 1)
+                               break;
+                       else
+                       {
+                               total_sleuths++;
+                               it.mmm_status = MMM_STATUS_SLEUTH;
+                       }
+               }
+               else if (total_civilians == 5)
+               {
+                       if (total_murderers <= 2)
+                               break;
+                       else
+                       {
+                               total_murderers++;
+                               it.mmm_status = MMM_STATUS_MURDERER;
+                       }
+               }
+               else if (total_civilians == 6)
+               {
+                       if (total_sleuths >= 2)
+                               break;
+                       else
+                       {
+                               total_sleuths++;
+                               it.mmm_status = MMM_STATUS_SLEUTH;
+                       }
+               }
+               else if (total_civilians == 7)
+               {
+                       if (total_sleuths >= 3)
+                               break;
+                       else if (total_murderers == 3)
+                       {
+                               total_murderers++;
+                               it.mmm_status = MMM_STATUS_MURDERER;
+                       }
+                       else
+                       {
+                               total_sleuths++;
+                               it.mmm_status = MMM_STATUS_SLEUTH;
+                       }
+               }
+               else if (total_civilians >= 8)
+               {
+                       if (total_sleuths >= 4)
+                               break;
+                       else if (total_murderers == 4)
+                       {
+                               total_murderers++;
+                               it.mmm_status = MMM_STATUS_MURDERER;
+                       }
+                       else
+                       {
+                               total_sleuths++;
+                               it.mmm_status = MMM_STATUS_SLEUTH;
+                       }
+               }
+               else
+               {
+                       total_murderers++; 
+                       it.mmm_status = MMM_STATUS_MURDERER;
+               }
+       });
+       FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
+       {
+               float totalmeankarma = ((autocvar_g_mmm_max_karma_points + autocvar_g_mmm_min_karma_points + it.karmapoints) / 3);
+               karma_Control(it);
+               it.activekillerrole = false;
+               if(it.mmm_status == MMM_STATUS_CIVILIAN)
+               {
+                       if (it.karmapoints <= totalmeankarma)
+                       {
+                               centerprint(it, strcat(BOLD_OPERATOR, "^1KARMA WARNING!\n^3Here, have the Rifle!"));
+                               GiveWeapon(it, WEP_RIFLE.m_id, OP_PLUS, 1);
+                       }
+                       //Gives Mine Layer weapon to the player
+                       GiveWeapon(it, WEP_MINE_LAYER.m_id, OP_PLUS, 1);
+                       Send_Notification(NOTIF_ONE_ONLY, it, MSG_CENTER, CENTER_MMM_CIVILIAN);
+                       Send_Notification(NOTIF_ONE_ONLY, it, MSG_INFO, INFO_MMM_CIVILIAN);
+                       //PrintToChatAll(sprintf("^1DEBUG^7: %s is ^2Civilian^7!", it.netname));
+               }
+               else if(it.mmm_status == MMM_STATUS_MURDERER)
+               {
+                       if (it.karmapoints <= totalmeankarma)
+                       {
+                               centerprint(it, strcat(BOLD_OPERATOR, "^1KARMA WARNING!\n^3Here, have the Rifle!"));
+                               GiveWeapon(it, WEP_RIFLE.m_id, OP_PLUS, 1);
+                       }
+                       //Gives Mine Layer weapon to the player
+                       GiveWeapon(it, WEP_MINE_LAYER.m_id, OP_PLUS, 1);
+                       Send_Notification(NOTIF_ONE_ONLY, it, MSG_CENTER, CENTER_MMM_MURDERER);
+                       Send_Notification(NOTIF_ONE_ONLY, it, MSG_INFO, INFO_MMM_MURDERER);
+                       //PrintToChatAll(sprintf("^1DEBUG^7: %s is ^1Murderer^7!", it.netname));
+               }
+               else if(it.mmm_status == MMM_STATUS_SLEUTH)
+               {
+                       if (it.karmapoints <= totalmeankarma)
+                       {
+                               centerprint(it, strcat(BOLD_OPERATOR, "^1KARMA WARNING!\n^3Here, have the Rifle!"));
+                               GiveWeapon(it, WEP_RIFLE.m_id, OP_PLUS, 1);
+                       }
+                       //Gives Shockwave and Mine Layer weapon to the player
+                       GiveWeapon(it, WEP_SHOCKWAVE.m_id, OP_PLUS, 1);
+                       GiveWeapon(it, WEP_MINE_LAYER.m_id, OP_PLUS, 1);
+                       Send_Notification(NOTIF_ONE_ONLY, it, MSG_CENTER, CENTER_MMM_SLEUTH);
+                       Send_Notification(NOTIF_ONE_ONLY, it, MSG_INFO, INFO_MMM_SLEUTH);
+                       Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_MMM_WHOISSLEUTH, it.netname);
+               }
+               mmm_FakeTimeLimit(it, round_handler_GetEndTime());
+       });
+ }
+ bool mmm_CheckPlayers()
+ {
+       static int prev_missing_players;
+       allowed_to_spawn = true;
+       int playercount = 0;
+       FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
+       {       
+               //PrintToChatAll(sprintf("it.karmapoints ^5begin: ^3%f",it.karmapoints));
+               //Karma points start
+               if (it.karmastarted != true)
+               {
+                       GameRules_scoring_add(it, MMM_KARMA, autocvar_g_mmm_max_karma_points - it.karmapoints);
+                       it.karmapoints = autocvar_g_mmm_max_karma_points;
+                       it.karmastarted = true;
+               }
+               karma_Control(it);
+               ++playercount;
+               //PrintToChatAll(sprintf("it.karmapoints ^6end: ^3%f",it.karmapoints));
+       });
+       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 mmm_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 mmm_Initialize() // run at the start of a match, initiates game mode
+ {
+       GameRules_scoring(0, SFL_SORT_PRIO_PRIMARY, 0, {
+               field(SP_MMM_KARMA, "karma", SFL_SORT_PRIO_SECONDARY); //LegendGuard adds Karma points in the scoreboard 22-02-2021
+       });
+       allowed_to_spawn = true;
+       round_handler_Spawn(mmm_CheckPlayers, mmm_CheckWinner, mmm_RoundStart);
+       round_handler_Init(5, autocvar_g_mmm_warmup, autocvar_g_mmm_round_timelimit);
+       EliminatedPlayers_Init(mmm_isEliminated);
+ }
+ void checkWeaponDeathtype(entity target, float deathtype)
+ {
+       switch (deathtype)
+       {
+               case WEP_ARC.m_id: case 276: case 788: target.killedwithweapon = "Impacted by the Arc's electric shock"; return;
+               case WEP_BLASTER.m_id: case 513: target.killedwithweapon = "Blasted by the Blaster"; return;
+               case WEP_CRYLINK.m_id: case 263: case 519: target.killedwithweapon = "Shot by the Crylink"; return;
+               case WEP_DEVASTATOR.m_id: case 522: case 1546: target.killedwithweapon = "Bombarded by the Devastator"; return;
+               case WEP_ELECTRO.m_id: case 262: case 518: case 1542: target.killedwithweapon = "Electrocuted by the Electro"; return;
+               case WEP_FIREBALL.m_id: case 273: case 529: case 1297: target.killedwithweapon = "Burned by the Fireball"; return;
+               case WEP_HAGAR.m_id: case 265: target.killedwithweapon = "Gunned by the Hagar"; return;
+               case WEP_HLAC.m_id: case 270: case 526: target.killedwithweapon = "Cut down with the HLAC"; return;
+               case WEP_HOOK.m_id: case 1805: target.killedwithweapon = "Caught in Hook gravity bomb"; return;
+               case WEP_MACHINEGUN.m_id: target.activekillerrole = true; target.killedwithweapon = "Riddled full of holes by the Machine Gun"; return;
+               case WEP_MINE_LAYER.m_id: case 517: case 1541: target.killedwithweapon = "Exploited by the Mine Layer"; return;
+               case WEP_MORTAR.m_id: case 516: case 1284: target.killedwithweapon = "Blew up with the Mortar"; return;
+               case WEP_OVERKILL_NEX.m_id: target.killedwithweapon = "Sniped by the Overkill Nex"; return;
+               case WEP_RIFLE.m_id: case 272: target.activekillerrole = true; target.killedwithweapon = "Sniped by the Rifle"; return;
+               case WEP_SEEKER.m_id: case 274: case 786: target.killedwithweapon = "Blasted by the Seeker"; return;
+               case WEP_SHOCKWAVE.m_id: target.killedwithweapon = "Gunned down by the Shockwave"; return;
+               case 275: target.killedwithweapon = "Knocked by the Shockwave"; return;
+               case WEP_SHOTGUN.m_id: target.activekillerrole = true; target.killedwithweapon = "Shot by Shotgun"; return;
+               case 258: target.killedwithweapon = "Knocked by the Shotgun"; return;
+               case WEP_TUBA.m_id: target.killedwithweapon = "Ear pain by the @!#%'n Tuba"; return;
+               case WEP_VAPORIZER.m_id: case 257: case 769: target.killedwithweapon = "Sniped by the Vaporizer"; return;
+               case WEP_VORTEX.m_id: target.killedwithweapon = "Sniped by the Vortex"; return;
+               case DEATH_FALL.m_id: target.killedwithweapon = "Fall"; return;
+               case DEATH_FIRE.m_id: target.killedwithweapon = "Burned with the fire"; return;
+               case DEATH_LAVA.m_id: target.killedwithweapon = "Burned in lava"; return;
+               case DEATH_MIRRORDAMAGE.m_id: target.killedwithweapon = "Suicide"; return;
+               case DEATH_SLIME.m_id: target.killedwithweapon = "Melted in slime"; return;
+               case DEATH_TELEFRAG.m_id: target.killedwithweapon = "Telefragged"; return;
+               case DEATH_NADE.m_id: target.killedwithweapon = "Blown up by the nade"; return;
+               case DEATH_NADE_NAPALM.m_id: target.killedwithweapon = "Burned by the Napalm nade"; return;
+               case DEATH_NADE_ICE.m_id: target.killedwithweapon = "Frozen by the Ice nade"; return;
+               case DEATH_NADE_HEAL.m_id: target.killedwithweapon = "Sucked by the Heal nade"; return;
+               default: target.killedwithweapon = "Unknown"; return;
+       }
+ }
+ void ReduceKarmaPointsandFrags(entity frag_attacker, entity frag_target, float frag_deathtype, entity wep_ent)
+ {
+       karmaLoseDifference(frag_attacker, frag_target);
+       GiveFrags(frag_attacker, frag_target, ((autocvar_g_mmm_punish_teamkill) ? -1 : -2), frag_deathtype, wep_ent.weaponentity_fld);
+       frag_target.whokilled = frag_attacker.netname;
+ }
+ // ==============
+ // Hook Functions
+ // ==============
+ MUTATOR_HOOKFUNCTION(mmm, ClientObituary)
+ {
+       // LegendGuard's IDEA: To adjust the grade of severity of karma, 
+       // we could add if sentence per weapons and adjust each weapon attack
+       // its own grade. Instead doing random decrease grade 22-02-2021
+       
+       // in mmm, announcing a frag would tell everyone who the murderer 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);
+               
+               //PrintToChatAll(strcat("deathtype var: ", ftos(frag_deathtype)));
+               checkWeaponDeathtype(frag_target, frag_deathtype);
+               // "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.mmm_status == frag_target.mmm_status)
+               {
+                       //PrintToChatAll("^1DEBUG^7: A ^2PLAYER^7 has fragged a ^2PLAYER OF HIS OWN TEAM^7, TOO BAD!");
+                       ReduceKarmaPointsandFrags(frag_attacker, frag_target, frag_deathtype, wep_ent);
+                       switch (frag_attacker.mmm_status)
+                       {
+                               case MMM_STATUS_CIVILIAN: frag_target.killerrole = "\n^3Killer role: ^2Civilian"; return;
+                               case MMM_STATUS_MURDERER: frag_target.killerrole = "\n^3Killer role: ^1Murderer"; return;
+                               case MMM_STATUS_SLEUTH: frag_target.killerrole = "\n^3Killer role: ^4Sleuth"; return;
+                               default: return;
+                       }
+                       //PrintToChatAll(sprintf("frag_attacker.karmapoints: ^1%f", frag_attacker.karmapoints));
+               }
+               if(frag_attacker.mmm_status == MMM_STATUS_SLEUTH)
+               {
+                       if (frag_target.mmm_status == MMM_STATUS_CIVILIAN || frag_target.mmm_status == MMM_STATUS_SLEUTH)
+                       {       
+                               //PrintToChatAll("^1DEBUG^7: A ^4Sleuth^7 fragged an ^2Civilian^7/^4Sleuth^7, TOO BAD!");
+                               ReduceKarmaPointsandFrags(frag_attacker, frag_target, frag_deathtype, wep_ent);
+                               frag_target.killerrole = "\n^3Killer role: ^4Sleuth";
+                               //PrintToChatAll(sprintf("frag_attacker.karmapoints: ^1%f", frag_attacker.karmapoints));
+                       }
+                       else
+                       {
+                               frag_target.whokilled = frag_attacker.netname;
+                               frag_target.killerrole = "\n^3Killer role: ^4Sleuth";
+                       }
+               }
+               if (frag_attacker.mmm_status == MMM_STATUS_CIVILIAN)
+               {
+                       if (frag_target.mmm_status == MMM_STATUS_SLEUTH)
+                       {
+                               //PrintToChatAll("^1DEBUG^7: An ^2Civilian^7 fragged a ^4Sleuth^7, TOO BAD!");
+                               ReduceKarmaPointsandFrags(frag_attacker, frag_target, frag_deathtype, wep_ent);
+                               frag_target.killerrole = "\n^3Killer role: ^2Civilian";
+                       }
+                       else
+                       {
+                               frag_target.whokilled = frag_attacker.netname;
+                               frag_target.killerrole = "\n^3Killer role: ^2Civilian";
+                       }
+               }
+               
+               if (frag_attacker.mmm_status == MMM_STATUS_MURDERER)
+               {
+                       if (frag_target.mmm_status == MMM_STATUS_CIVILIAN)
+                       {
+                               frag_target.whokilled = frag_attacker.netname;
+                               frag_target.killerrole = "\n^3Killer role: ^1Murderer";
+                       }
+                       else
+                       {
+                               frag_target.whokilled = frag_attacker.netname;
+                               frag_target.killerrole = "\n^3Killer role: ^1Murderer";
+                       }
+               }
+               //if mmm_status is 1, means civilian, 2 means murderer, 3 means sleuth
+               //PrintToChatAll(sprintf("^1DEBUG^7: frag_attacker.mmm_status is ^3%s^7",ftos(frag_attacker.mmm_status)));
+               //PrintToChatAll(sprintf("^1DEBUG^7: frag_target.mmm_status is ^3%s^7",ftos(frag_target.mmm_status)));
+       }
+       else
+       {
+               float frag_deathtype = M_ARGV(3, float);
+               checkWeaponDeathtype(frag_target, frag_deathtype);
+       }
+       M_ARGV(5, bool) = true; // anonymous attacker
+ }
+ //karma weapon damage, halve the damage attack when player has low karma 20-03-2021
+ MUTATOR_HOOKFUNCTION(mmm, Damage_Calculate)
+ {
+       entity attacker = M_ARGV(1, entity);
+       entity target = M_ARGV(2, entity);
+       float deathtype = M_ARGV(3, float);
+       float damage = M_ARGV(4, float);
+       vector force = M_ARGV(6, vector);
+       string corpsemessagestrcat = "";
+       if (autocvar_g_mmm_karma_damageactive != false)
+       {
+               if (IS_PLAYER(attacker))
+               {
+                       if(target == attacker) // damage done to yourself
+                       {
+                               damage /= autocvar_g_weapondamagefactor / (attacker.karmapoints / autocvar_g_mmm_max_karma_points);
+                               force /= autocvar_g_weaponforcefactor / (attacker.karmapoints / autocvar_g_mmm_max_karma_points);
+                       }
+                       else if (target != attacker)
+                       {
+                               damage /= autocvar_g_weapondamagefactor / (attacker.karmapoints / autocvar_g_mmm_max_karma_points);
+                               force /= autocvar_g_weaponforcefactor / (attacker.karmapoints / autocvar_g_mmm_max_karma_points);
+                       }
+                       else
+                       {
+                               damage *= autocvar_g_weapondamagefactor;
+                               force *= autocvar_g_weaponforcefactor;
+                       }
+               }
+       }
+       //CORPSE DETECTION SKILL 21-03-2021
+       if(IS_DEAD(target))
+       {
+               //Shockwave weapon as radar gun to check the corpses 22-03-2021
+               if(DEATH_ISWEAPON(deathtype, WEP_SHOCKWAVE))
+               {
+                       if (target.killedwithweapon == "")
+                               target.killedwithweapon = "UNKNOWN CAUSE";
+                       
+                       if (target.activekillerrole != true)
+                       {
+                               target.killerrole = "";
+                               target.activekillerrole = false;
+                       }
+                       string killedbyphrase = strcat("\n^3Killed by:^7 ", target.whokilled, target.killerrole); 
+                       string wepkilledphrase = strcat("\n^3Cause:^7 ", target.killedwithweapon);
+                       if (target.whokilled == "")
+                       {
+                               killedbyphrase = "";
+                               if (target.killedwithweapon == "")
+                                       wepkilledphrase = "\n^3Cause:^7 UNKNOWN CAUSE";
+                       }
+                       damage = 0;
+                       force = '0 0 0';
+                       if (target.mmm_status == MMM_STATUS_CIVILIAN)
+                       {
+                               //try to add centerprint message for chat privately if possible
+                               corpsemessagestrcat = strcat("^5DEAD PLAYER DETAILS^7: \n^3Name:^7 ", target.netname, "\n^3Role: ^2Civilian", killedbyphrase, wepkilledphrase);
+                               //centerprint(attacker, strcat(BOLD_OPERATOR, corpsemessagestrcat));
+                               Send_Notification(NOTIF_ONE_ONLY, attacker, MSG_CENTER, CENTER_MMM_CORPSEDETECTION, corpsemessagestrcat);
+                               Send_Notification(NOTIF_ONE_ONLY, attacker, MSG_INFO, INFO_MMM_CORPSEDETECTION, corpsemessagestrcat);
+                       }
+                       else if (target.mmm_status == MMM_STATUS_MURDERER)
+                       {
+                               corpsemessagestrcat = strcat("^5DEAD PLAYER DETAILS^7: \n^3Name:^7 ", target.netname, "\n^3Role: ^1Murderer", killedbyphrase, wepkilledphrase);
+                               //centerprint(attacker, strcat(BOLD_OPERATOR, corpsemessagestrcat));
+                               Send_Notification(NOTIF_ONE_ONLY, attacker, MSG_CENTER, CENTER_MMM_CORPSEDETECTION, corpsemessagestrcat);
+                               Send_Notification(NOTIF_ONE_ONLY, attacker, MSG_INFO, INFO_MMM_CORPSEDETECTION, corpsemessagestrcat);
+                       }
+                       else if (target.mmm_status == MMM_STATUS_SLEUTH)
+                       {
+                               corpsemessagestrcat = strcat("^5DEAD PLAYER DETAILS^7: \n^3Name:^7 ", target.netname, "\n^3Role: ^4Sleuth", killedbyphrase, wepkilledphrase);
+                               //centerprint(attacker, strcat(BOLD_OPERATOR, corpsemessagestrcat));
+                               Send_Notification(NOTIF_ONE_ONLY, attacker, MSG_CENTER, CENTER_MMM_CORPSEDETECTION, corpsemessagestrcat);
+                               Send_Notification(NOTIF_ONE_ONLY, attacker, MSG_INFO, INFO_MMM_CORPSEDETECTION, corpsemessagestrcat);
+                       }
+                       attacker.investigated = true;
+               }
+       }
+       M_ARGV(4, float) = damage;
+       M_ARGV(6, vector) = force;
+ }
+ MUTATOR_HOOKFUNCTION(mmm, 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 = MMM_COLOR_CIVILIAN;
+               if(player.mmm_status == MMM_STATUS_CIVILIAN && game_stopped) //Civilian status by default
+                       plcolor = MMM_COLOR_CIVILIAN;
+               if(player.mmm_status == MMM_STATUS_MURDERER && game_stopped)
+                       plcolor = MMM_COLOR_MURDERER;
+               //LegendGuard adds for Sleuth 21-02-2021
+               if(player.mmm_status == MMM_STATUS_SLEUTH)// && game_stopped)
+                       plcolor = MMM_COLOR_SLEUTH;
+               setcolor(player, plcolor);
+       }
+       //CORPSE FEATURE 10-03-2021
+       if (IS_DEAD(player))
+       {
+               player.event_damage = func_null;
+               //player.health = 0;
+               player.solid = SOLID_CORPSE;
+               set_movetype(player, MOVETYPE_STEP); //test with MOVETYPE_TOSS or MOVETYPE_WALK (it's like sliding object) or MOVETYPE_BOUNCE (maybe not good)
+       }
+ }
+ MUTATOR_HOOKFUNCTION(mmm, PlayerSpawn)
+ {
+       entity player = M_ARGV(0, entity);
+       player.mmm_status = 0;
+       player.mmm_validkills = 0;
+       player.caplayer = 1;
+       if (!warmup_stage)
+               eliminatedPlayers.SendFlags |= 1;
+ }
+ MUTATOR_HOOKFUNCTION(mmm, 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(mmm, 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(mmm, reset_map_players)
+ {
+       FOREACH_CLIENT(true, {
+               CS(it).killcount = 0;
+               it.mmm_status = 0;
+               mmm_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;
+                       it.respawn_flags = RESPAWN_SILENT; //CSQC print output respawn lib.qh error fix
+                       PutClientInServer(it);
+               }
+       });
+       bot_relinkplayerlist();
+       return true;
+ }
+ MUTATOR_HOOKFUNCTION(mmm, reset_map_global)
+ {
+       allowed_to_spawn = true;
+       return true;
+ }
+ entity mmm_LastPlayerForTeam(entity this)
+ {
+       entity last_pl = NULL;
+       FOREACH_CLIENT(IS_PLAYER(it) && it != this, {
+               if (!IS_DEAD(it) && this.mmm_status == it.mmm_status)
+               {
+                       if (!last_pl)
+                       {
+                               last_pl = it;
+                       }
+                       else
+                               return NULL;
+               }
+       });
+       return last_pl;
+ }
+ void mmm_LastPlayerForTeam_Notify(entity this)
+ {
+       if (!warmup_stage && round_handler_IsActive() && round_handler_IsRoundStarted())
+       {
+               entity pl = mmm_LastPlayerForTeam(this);
+               if (pl)
+                       Send_Notification(NOTIF_ONE_ONLY, pl, MSG_CENTER, CENTER_ALONE);
+       }
+ }
+ MUTATOR_HOOKFUNCTION(mmm, PlayerDies)
+ {
+       entity frag_attacker = M_ARGV(1, entity);
+       entity frag_target = M_ARGV(2, entity);
+       //float frag_deathtype = M_ARGV(3, float);
+       mmm_LastPlayerForTeam_Notify(frag_target);
+       if (!allowed_to_spawn)
+       {
+               frag_target.respawn_flags = RESPAWN_DENY;
+               // prevent unwanted sudden rejoin as spectator and movement of spectator camera
+               frag_target.respawn_time = time + 2;
+       }
+       frag_target.respawn_flags |= RESPAWN_DENY;
+       frag_target.event_damage = func_null;
+       frag_target.health = 0;
+       
+       if (!warmup_stage)
 -              if (IS_BOT_CLIENT(frag_target))
 -                      bot_clear(frag_target);
 -      }
+               eliminatedPlayers.SendFlags |= 1;
+       
+       //if(frag_attacker.mmm_status == frag_target.mmm_status)
+       // killed an ally! punishment is sentenced
+       if(frag_attacker.mmm_status == MMM_STATUS_SLEUTH)
+       {
+               if (frag_target.mmm_status == MMM_STATUS_CIVILIAN)
+               {
+                       //PrintToChatAll("^1DEBUG^7: ^4SLEUTH ^1DAMAGE/DEAD^7 HAS TAKEN!");
+                       Damage(frag_attacker, frag_attacker, frag_attacker, autocvar_g_mmm_karma_damagepunishmentdeal, DEATH_MIRRORDAMAGE.m_id, DMG_NOWEP, frag_attacker.origin, '0 0 0');
+               }
+       }
+       if (frag_attacker.mmm_status == MMM_STATUS_CIVILIAN)
+       {
+               if (frag_target.mmm_status == MMM_STATUS_SLEUTH)
+               {
+                       //PrintToChatAll("^1DEBUG^7: ^2CIVILIAN ^1DAMAGE/DEAD^7 HAS TAKEN!");
+                       Damage(frag_attacker, frag_attacker, frag_attacker, autocvar_g_mmm_karma_damagepunishmentdeal, DEATH_MIRRORDAMAGE.m_id, DMG_NOWEP, frag_attacker.origin, '0 0 0');
+               }
+       }
+       if (frag_attacker.mmm_status == MMM_STATUS_MURDERER)
+       {
+               if (frag_target.mmm_status == MMM_STATUS_MURDERER)
+               {
+                       //PrintToChatAll("^1DEBUG^7: ^1MURDERER ^1DAMAGE/DEAD^7 HAS TAKEN!");
+                       Damage(frag_attacker, frag_attacker, frag_attacker, autocvar_g_mmm_karma_damagepunishmentdeal, DEATH_MIRRORDAMAGE.m_id, DMG_NOWEP, frag_attacker.origin, '0 0 0');
+               }
+       }
+       return true;
+ }
+ MUTATOR_HOOKFUNCTION(mmm, ClientDisconnect)
+ {
+       entity player = M_ARGV(0, entity);
+       if (IS_PLAYER(player) && !IS_DEAD(player))
+               mmm_LastPlayerForTeam_Notify(player);
+       return true;
+ }
+ MUTATOR_HOOKFUNCTION(mmm, MakePlayerObserver)
+ {
+       entity player = M_ARGV(0, entity);
+       if (IS_PLAYER(player) && !IS_DEAD(player))
+               mmm_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.mmm_validkills = 0;
+               player.mmm_status = 0;
+               mmm_FakeTimeLimit(player, -1); // restore original timelimit
+               return false;  // allow team reset
+       }
+       return true;  // prevent team reset
+ }
+ MUTATOR_HOOKFUNCTION(mmm, Scores_CountFragsRemaining)
+ {
+       // announce remaining frags?
+       return true;
+ }
+ MUTATOR_HOOKFUNCTION(mmm, GiveFragsForKill, CBC_ORDER_FIRST)
+ {
+       entity frag_attacker = M_ARGV(0, entity);
+       if(!warmup_stage && round_handler_IsActive() && round_handler_IsRoundStarted()) 
+               frag_attacker.mmm_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(mmm, AddPlayerScore)
+ {
+       // add scorefield for scoreboard here
+       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 murderer!
+ }
+ MUTATOR_HOOKFUNCTION(mmm, CalculateRespawnTime)
+ {
+       // no respawn calculations needed, player is forced to spectate anyway
+       return true;
+ }
+ MUTATOR_HOOKFUNCTION(mmm, 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(mmm, 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(mmm, GetPlayerStatus)
+ {
+       entity player = M_ARGV(0, entity);
+       return player.caplayer == 1;
+ }
+ MUTATOR_HOOKFUNCTION(mmm, BotShouldAttack)
+ {
+       entity bot = M_ARGV(0, entity);
+       entity targ = M_ARGV(1, entity);
+       if(targ.mmm_status == bot.mmm_status)
+       {
+               return true;
+       }
+       
+       // LegendGuard fixed the problem of Sleuths and Civilians attacking each other 26-03-2021
+       if(bot.mmm_status == MMM_STATUS_SLEUTH)
+       {
+               if(targ.mmm_status == MMM_STATUS_CIVILIAN)
+                       return true;
+       }
+ }
index 94965fc2dfcd8a2b6b955a53dbba2b43f66671fb,32273000d72b5400620570e1791ee610a1971fcd..a9ef5cd4cdbdb067494b503fda6318b14f4969da
@@@ -847,7 -783,15 +860,16 @@@ string multiteam_info_sprintf(string in
  
      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"), "")
 +      MSG_CENTER_NOTIF(TIMEOUT_ONGOING,                   N_ENABLE,    0, 0, "",               CPID_TIMEIN,            "1 f1", _("^F4Match paused"), "")
+     //LegendGuard adds MSG_CENTER_NOTIF for MMM 20-02-2021
+     MSG_CENTER_NOTIF(MMM_MURDERER,                      N_ENABLE,    0, 0, "",               CPID_MMM,               "5 0",  strcat(BOLD_OPERATOR, _("^BGYou are ^K1Murderer^BG! Kill all the civilians without raising suspicion!")), "")
+     MSG_CENTER_NOTIF(MMM_MURDERER_WIN,                  N_ENABLE,    0, 0, "",               CPID_ROUND,             "0 0",  _("^K1Murderers^BG win the round"), "")
+     
+     MSG_CENTER_NOTIF(MMM_CIVILIAN,                      N_ENABLE,    0, 0, "",               CPID_MMM,               "5 0",  strcat(BOLD_OPERATOR, _("^BGYou are ^F1Civilian^BG! Try to find out who are murderers and survive until time is up!")), "")
+     MSG_CENTER_NOTIF(MMM_CIVILIAN_WIN,                  N_ENABLE,    0, 0, "",               CPID_ROUND,             "0 0",  _("^F1Civilians^BG win the round"), "")
+     
+     MSG_CENTER_NOTIF(MMM_SLEUTH,                        N_ENABLE,    0, 0, "",               CPID_MMM,               "5 0",  strcat(BOLD_OPERATOR, _("^BGYou are ^4Sleuth^BG! Find out who are murderers and protect the civilians!")), "")
+     MSG_CENTER_NOTIF(MMM_CORPSEDETECTION,               N_ENABLE,    1, 0, "s1",             CPID_MMM,               "4 0",  strcat(BOLD_OPERATOR, _("^BG%s")), "")
  
      MSG_CENTER_NOTIF(JOIN_PREVENT_MINIGAME,             N_ENABLE,    0, 0, "",               CPID_Null,              "0 0",  _("^K1Cannot join given minigame session!"), "" )
  
Simple merge
index 63e49ae5f8cab6030f34a39d28a2d2050841df0e,8e1d8e61cbb79da1582ee4e68cd7c18215cfa5f4..3b00c4da6d8b4706a3788a58f9a0d47fd882cbad
@@@ -90,27 -86,7 +90,28 @@@ REGISTER_SP(NEXBALL_FAULTS)
  REGISTER_SP(ONS_TAKES);
  REGISTER_SP(ONS_CAPS);
  
 +REGISTER_SP(MEDAL_AIRSHOT);
 +REGISTER_SP(MEDAL_DAMAGE);
 +REGISTER_SP(MEDAL_ELECTROBITCH);
 +REGISTER_SP(MEDAL_EXCELLENT);
 +REGISTER_SP(MEDAL_FIRSTBLOOD);
 +REGISTER_SP(MEDAL_HEADSHOT);
 +REGISTER_SP(MEDAL_HUMILIATION);
 +REGISTER_SP(MEDAL_IMPRESSIVE);
 +REGISTER_SP(MEDAL_YODA);
 +REGISTER_SP(MEDAL_TELEFRAG);
 +
 +REGISTER_SP(MEDAL_ACCURACY);
 +REGISTER_SP(MEDAL_ASSIST);
 +REGISTER_SP(MEDAL_CAPTURE);
 +REGISTER_SP(MEDAL_DEFENSE);
 +REGISTER_SP(MEDAL_PERFECT);
 +
 +REGISTER_SP(MEDAL_KILLSTREAK_03);
 +REGISTER_SP(MEDAL_KILLSTREAK_05);
 +REGISTER_SP(MEDAL_KILLSTREAK_10);
 +REGISTER_SP(MEDAL_KILLSTREAK_15);
+ REGISTER_SP(MMM_KARMA); //LegendGuard adds REGISTER_SP for MMM Karma points 21-02-2021
  #endif
  
  
Simple merge