#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'll 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 = 1; //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) { //do nothing case 0: return; //force to spec case 1: PutObserverInServer(it); return; //kick case 2: dropclient(it); return; //ban and kick case 3: Ban_KickBanClient(it, bantime, masksize, "Too low karma"); return; 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 Karma_WarningCheck(entity it) { float totalmeankarma = ((autocvar_g_mmm_max_karma_points + autocvar_g_mmm_min_karma_points + it.karmapoints) / 3); if (it.karmapoints <= totalmeankarma) { Send_Notification(NOTIF_ONE_ONLY, it, MSG_INFO, INFO_MMM_KARMAWARNING); //centerprint(it, strcat(BOLD_OPERATOR, "^1KARMA WARNING!\n^3Here, have the Rifle!")); GiveWeapon(it, WEP_RIFLE.m_id, OP_PLUS, 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), { karma_Control(it); it.activekillerrole = false; if(it.mmm_status == MMM_STATUS_CIVILIAN) { Karma_WarningCheck(it); //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) { Karma_WarningCheck(it); //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) { Karma_WarningCheck(it); //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_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 = ""; string corpsemsginfo = ""; 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("^3Killed by:^7 ", target.whokilled, target.killerrole); string wepkilledphrase = strcat("^3Cause:^7 ", target.killedwithweapon); if (target.whokilled == "") { killedbyphrase = ""; if (target.killedwithweapon == "") wepkilledphrase = "^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", "\n", killedbyphrase, "\n", wepkilledphrase); corpsemsginfo = strcat("^5DEAD PLAYER DETAILS^7: ^3Name:^7 ", target.netname, " ^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, corpsemsginfo); } else if (target.mmm_status == MMM_STATUS_MURDERER) { corpsemessagestrcat = strcat("^5DEAD PLAYER DETAILS^7: \n^3Name:^7 ", target.netname, "\n^3Role: ^1Murderer", "\n", killedbyphrase, "\n", wepkilledphrase); corpsemsginfo = strcat("^5DEAD PLAYER DETAILS^7: ^3Name:^7 ", target.netname, " ^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, corpsemsginfo); } else if (target.mmm_status == MMM_STATUS_SLEUTH) { corpsemessagestrcat = strcat("^5DEAD PLAYER DETAILS^7: \n^3Name:^7 ", target.netname, "\n^3Role: ^4Sleuth", "\n", killedbyphrase, "\n", wepkilledphrase); corpsemsginfo = strcat("^5DEAD PLAYER DETAILS^7: ^3Name:^7 ", target.netname, " ^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, corpsemsginfo); } attacker.investigated = true; } } M_ARGV(4, float) = damage; M_ARGV(6, vector) = force; } MUTATOR_HOOKFUNCTION(mmm, PlayerPreThink) { entity player = M_ARGV(0, entity); int playercount = 0; bool playercheck = false; if (playercheck != true) { FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it), { ++playercount; }); playercheck = true; } //if the murderer is still here around, then avoid illogical winning if (playercheck == true) { FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it), { if (playercount == 3) { if (it.mmm_status == MMM_STATUS_SLEUTH) it.mmm_status = MMM_STATUS_CIVILIAN; } }); } if(IS_PLAYER(player) || player.caplayer) { if (player.karmaspectated != true) { if (CS(player).scorekeeper.(scores(SP_MMM_KARMA)) <= 0) //wtf? Visualization works 100% correct? GameRules_scoring_add(player, MMM_KARMA, player.karmapoints); player.karmaspectated = true; } // 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); } if(warmup_stage) player.karmastarted = false; //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); //Karma points start if (player.karmastarted != true) { CS(player).scorekeeper.(scores(SP_MMM_KARMA)) = 0; //full karma reset lol player.karmapoints = autocvar_g_mmm_max_karma_points; GameRules_scoring_add(player, MMM_KARMA, player.karmapoints); player.karmastarted = true; } 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) 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.karmaspectated == true) player.karmaspectated = false; 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; } //if server admin sets "sv_ready_restart_after_countdown 1", will avoid possible visual failure for karma in the scoreboard MUTATOR_HOOKFUNCTION(mmm, ReadLevelCvars) { sv_ready_restart_after_countdown = 0; } 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; } }