]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/common/gamemodes/gamemode/mmm/sv_mmm.qc
Added more player stability in terms of quantity
[xonotic/xonotic-data.pk3dir.git] / qcsrc / common / gamemodes / gamemode / mmm / sv_mmm.qc
1 #include "sv_mmm.qh"
2
3 //set g_mmm_sleuth_count 0.125 "number of players who will become sleuths, set between 0 and 0.9 to use a multiplier of the current players, or 1 and above to specify an exact number of players"
4 //float autocvar_g_mmm_sleuth_count = 0.125; //I don't think that it won't be used...
5 float autocvar_g_mmm_civilian_count = 0.625;
6 //float autocvar_g_mmm_murderer_count = 0.25;
7 float autocvar_g_mmm_round_timelimit = 180;
8 float autocvar_g_mmm_warmup = 10;
9 bool autocvar_g_mmm_punish_teamkill = false;
10 bool autocvar_g_mmm_reward_civilian = true;
11 bool autocvar_g_mmm_reward_sleuth = true; //sleuth reward if investigated corpses
12 float autocvar_g_mmm_max_karma_points = 1000; //LegendGuard sets Karma points 21-02-2021
13 float autocvar_g_mmm_min_karma_points = 400;
14 int autocvar_g_mmm_karma_bankick_tool = 0; //LegendGuard sets a ban tool for server admins 11-03-2021
15 float autocvar_g_mmm_karma_bantime = 1800; //karma ban seconds
16 bool autocvar_g_mmm_karma_damageactive = true; //LegendGuard sets Karma damage setting if active 20-03-2021
17
18 // Sleuth is a created team, this team is added inside Civilians team
19
20 void mmm_FakeTimeLimit(entity e, float t)
21 {
22         if(!IS_REAL_CLIENT(e))
23                 return;
24 #if 0
25         msg_entity = e;
26         WriteByte(MSG_ONE, 3); // svc_updatestat
27         WriteByte(MSG_ONE, 236); // STAT_TIMELIMIT
28         if(t < 0)
29                 WriteCoord(MSG_ONE, autocvar_timelimit);
30         else
31                 WriteCoord(MSG_ONE, (t + 1) / 60);
32 #else
33         STAT(MMM_ROUNDTIMER, e) = t;
34 #endif
35 }
36
37 void nades_Clear(entity player);
38
39 void karma_Control(entity it)
40 {
41         float masksize = autocvar_g_ban_default_masksize;
42         float bantime = autocvar_g_mmm_karma_bantime;
43         if(it.karmapoints >= autocvar_g_mmm_max_karma_points)
44         {
45                 //Resets karmapoints to maintain the maximum
46                 //PrintToChatAll("^3REWARD ^1MAXIMUM RESET");
47                 GameRules_scoring_add(it, MMM_KARMA, autocvar_g_mmm_max_karma_points - it.karmapoints);
48                 it.karmapoints = autocvar_g_mmm_max_karma_points;
49         }
50         else if(it.karmapoints <= autocvar_g_mmm_min_karma_points)
51         {
52                 switch (autocvar_g_mmm_karma_bankick_tool)
53                 {
54                         //force to spec
55                         case 0: PutObserverInServer(it); return;
56                         //kick
57                         case 1: dropclient(it); return;
58                         //ban and kick
59                         case 2: Ban_KickBanClient(it, bantime, masksize, "Too low karma"); return;
60                         //force to spec
61                         default: PutObserverInServer(it); return;
62                 }
63         }
64 }
65
66 void karmaLoseDifference(entity attacker, entity target)
67 {
68         //BASIC MATH THEORY: example: 1000 * 0.3 * (0.1 + 0.4) * 0.25 // karma points reduce when player attacked to other player
69         if (target.karmapoints < attacker.karmapoints)
70         {       
71                 float decreasekarma = - ( target.karmapoints * random() * ( 0.1 + random() ) * 0.25 );
72                 GameRules_scoring_add(attacker, MMM_KARMA, decreasekarma);
73                 attacker.karmapoints = attacker.karmapoints + decreasekarma;
74         }
75         else if (target.karmapoints > attacker.karmapoints)
76         {
77                 float decreasekarma = - ( target.karmapoints * random() * ( 0.1 + random() ) * 0.25 );
78                 GameRules_scoring_add(attacker, MMM_KARMA, decreasekarma);
79                 attacker.karmapoints = attacker.karmapoints + decreasekarma;
80         }
81         else
82         {
83                 float decreasekarma = - ( target.karmapoints * random() * ( 0.1 + random() ) * 0.25 );
84                 GameRules_scoring_add(attacker, MMM_KARMA, decreasekarma);
85                 attacker.karmapoints = attacker.karmapoints + decreasekarma;
86         }
87 }
88
89 void karmaWinDifference(entity it)
90 {
91         GameRules_scoring_add(it, SCORE, 1); // reward civilians who make it to the end of the round time limit
92         float increasekarma = ( autocvar_g_mmm_min_karma_points * random() * ( 0.1 + random() ) * 0.12 );
93         GameRules_scoring_add(it, MMM_KARMA, increasekarma);
94         it.karmapoints = it.karmapoints + increasekarma;
95 }
96
97 void mmm_UpdateScores(bool timed_out)
98 {
99         // give players their hard-earned kills now that the round is over
100         FOREACH_CLIENT(true,
101         {
102                 it.totalfrags += it.mmm_validkills;
103                 if(it.mmm_validkills)
104                 {
105                         GameRules_scoring_add(it, SCORE, it.mmm_validkills);
106                 }
107                 it.mmm_validkills = 0;
108                 // player survived the round
109                 if(IS_PLAYER(it) && !IS_DEAD(it)) // LegendGuard adds something for Karma 21-02-2021
110                 {
111                         if((autocvar_g_mmm_reward_civilian && timed_out && it.mmm_status == MMM_STATUS_CIVILIAN) 
112                         || (autocvar_g_mmm_reward_civilian && !timed_out && it.mmm_status == MMM_STATUS_CIVILIAN))
113                         {
114                                 karmaWinDifference(it);
115                                 //PrintToChatAll(sprintf("^2REWARD ^7it.karmapoints: ^1%f", it.karmapoints));
116                                 karma_Control(it);
117                         }
118
119                         //Sleuth reward after investigated a corpse
120                         if((autocvar_g_mmm_reward_sleuth && timed_out && it.mmm_status == MMM_STATUS_SLEUTH) 
121                         || (autocvar_g_mmm_reward_sleuth && !timed_out && it.mmm_status == MMM_STATUS_SLEUTH))
122                         {
123                                 if (it.investigated == true)
124                                 {
125                                         karmaWinDifference(it);
126                                         it.investigated = false;
127                                 }
128                                 karma_Control(it);
129                         }
130
131                         if(it.mmm_status == MMM_STATUS_CIVILIAN)
132                         {
133                                 karmaWinDifference(it);
134                                 //PrintToChatAll(sprintf("^2CIVILIAN ^7it.karmapoints: ^1%f", it.karmapoints));
135                                 karma_Control(it);
136                         }
137                         else if(it.mmm_status == MMM_STATUS_MURDERER)
138                         {
139                                 karmaWinDifference(it);
140                                 //PrintToChatAll(sprintf("^1MURDERER ^7it.karmapoints: ^1%f", it.karmapoints));
141                                 karma_Control(it);
142                         }
143                 }
144         });
145 }
146
147 float mmm_CheckWinner()
148 {
149         if(round_handler_GetEndTime() > 0 && round_handler_GetEndTime() - time <= 0)
150         {
151                 // if the match times out, civilians win too!
152                 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_MMM_CIVILIAN_WIN);
153                 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_MMM_CIVILIAN_WIN);
154                 FOREACH_CLIENT(true,
155                 {
156                         if(IS_PLAYER(it))
157                                 nades_Clear(it);
158                         mmm_FakeTimeLimit(it, -1);
159                 });
160
161                 mmm_UpdateScores(true);
162
163                 allowed_to_spawn = false;
164                 game_stopped = true;
165                 round_handler_Init(5, autocvar_g_mmm_warmup, autocvar_g_mmm_round_timelimit);
166                 return 1;
167         }
168
169         int civilian_count = 0, murderer_count = 0, sleuth_count = 0;
170         FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
171         {
172                 if(it.mmm_status == MMM_STATUS_CIVILIAN)
173                         civilian_count++;
174                 else if(it.mmm_status == MMM_STATUS_MURDERER)
175                         murderer_count++;
176                 else if(it.mmm_status == MMM_STATUS_SLEUTH) //LegendGuard adds sleuth_count 20-02-2021 
177                         sleuth_count++;
178         });
179         if(civilian_count > 0 && murderer_count > 0)
180         {
181                 return 0;
182         }
183
184         if(murderer_count > 0) // murderers win
185         {
186                 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_MMM_MURDERER_WIN);
187                 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_MMM_MURDERER_WIN);
188         }
189         else if(civilian_count > 0) // civilians win
190         {
191                 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_MMM_CIVILIAN_WIN);
192                 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_MMM_CIVILIAN_WIN);
193         }
194         else if (sleuth_count > 0 && civilian_count > 0) // sleuths are same as civilians win
195         {
196                 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_MMM_CIVILIAN_WIN);
197                 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_MMM_CIVILIAN_WIN);
198         }
199         else
200         {
201                 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_ROUND_TIED);
202                 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_ROUND_TIED);
203         }
204
205         mmm_UpdateScores(false);
206
207         allowed_to_spawn = false;
208         game_stopped = true;
209         round_handler_Init(5, autocvar_g_mmm_warmup, autocvar_g_mmm_round_timelimit);
210
211         FOREACH_CLIENT(true,
212         {
213                 if(IS_PLAYER(it))
214                 {
215                         it.respawn_flags = RESPAWN_SILENT; //CSQC print output respawn lib.qh error fix
216                         nades_Clear(it);
217                 }
218                 mmm_FakeTimeLimit(it, -1);
219         });
220
221         return 1;
222 }
223
224 void mmm_RoundStart()
225 {
226         allowed_to_spawn = boolean(warmup_stage);
227         int playercount = 0;
228         
229         FOREACH_CLIENT(true,
230         {
231                 if(IS_PLAYER(it) && !IS_DEAD(it))
232                 {
233                         ++playercount;
234                         it.mmm_status = MMM_STATUS_CIVILIAN;
235                 }
236                 else
237                         it.mmm_status = 0; // this is mostly a safety check; if a client manages to somehow maintain a mmm status, clear it before the round starts!
238                 it.mmm_validkills = 0;
239         });
240         
241         int civilian_count = bound(1, ((autocvar_g_mmm_civilian_count >= 1) ? autocvar_g_mmm_civilian_count : floor(playercount * autocvar_g_mmm_civilian_count)), playercount - 1); // 20%, but ensure at least 1 and less than total
242         int total_civilians = 0;
243         //int murderer_count = bound(1, ((autocvar_g_mmm_murderer_count >= 1) ? autocvar_g_mmm_murderer_count : floor(playercount * autocvar_g_mmm_murderer_count)), playercount - 1); // 20%, but ensure at least 1 and less than total
244         int total_murderers = 0;
245         //int sleuth_count = bound(1, ((autocvar_g_mmm_sleuth_count >= 1) ? autocvar_g_mmm_sleuth_count : floor(playercount * autocvar_g_mmm_sleuth_count)), playercount - 1); // 20%, but ensure at least 1 and less than total
246         int total_sleuths = 0;
247
248         //civilians TOTAL
249         FOREACH_CLIENT_RANDOM(IS_PLAYER(it) && !IS_DEAD(it),
250         {
251                 if(total_civilians >= civilian_count)
252                         break;
253                 //LegendGuard fixes the round start again 22-03-2021
254                 total_civilians++;
255                 if (total_civilians <= 1)
256                 {
257                         if (total_murderers <= 1)
258                         {
259                                 total_murderers++;
260                                 it.mmm_status = MMM_STATUS_MURDERER;
261                         }
262                 }
263                 else if (total_civilians == 2)
264                 {
265                         if (total_sleuths >= 1)
266                                 break;
267                         else
268                         {
269                                 total_sleuths++;
270                                 it.mmm_status = MMM_STATUS_SLEUTH;
271                         }
272                 }
273                 else if (total_civilians == 5)
274                 {
275                         if (total_murderers <= 2)
276                                 break;
277                         else
278                         {
279                                 total_murderers++;
280                                 it.mmm_status = MMM_STATUS_MURDERER;
281                         }
282                 }
283                 else if (total_civilians == 6)
284                 {
285                         if (total_sleuths >= 2)
286                                 break;
287                         else
288                         {
289                                 total_sleuths++;
290                                 it.mmm_status = MMM_STATUS_SLEUTH;
291                         }
292                 }
293                 else if (total_civilians == 7)
294                 {
295                         if (total_sleuths >= 3)
296                                 break;
297                         else if (total_murderers == 3)
298                         {
299                                 total_murderers++;
300                                 it.mmm_status = MMM_STATUS_MURDERER;
301                         }
302                         else
303                         {
304                                 total_sleuths++;
305                                 it.mmm_status = MMM_STATUS_SLEUTH;
306                         }
307                 }
308                 else if (total_civilians >= 8)
309                 {
310                         if (total_sleuths >= 4)
311                                 break;
312                         else if (total_murderers == 4)
313                         {
314                                 total_murderers++;
315                                 it.mmm_status = MMM_STATUS_MURDERER;
316                         }
317                         else
318                         {
319                                 total_sleuths++;
320                                 it.mmm_status = MMM_STATUS_SLEUTH;
321                         }
322                 }
323                 else
324                 {
325                         total_murderers++; 
326                         it.mmm_status = MMM_STATUS_MURDERER;
327                 }
328         });
329
330         FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
331         {
332                 float totalmeankarma = ((autocvar_g_mmm_max_karma_points + autocvar_g_mmm_min_karma_points + it.karmapoints) / 3);
333                 karma_Control(it);
334                 it.activekillerrole = false;
335
336                 if(it.mmm_status == MMM_STATUS_CIVILIAN)
337                 {
338                         SetResource(it, RES_SHELLS, 10);
339                         SetResource(it, RES_BULLETS, 20);
340                         SetResource(it, RES_ROCKETS, 10);
341                         SetResource(it, RES_CELLS, 10);
342                         if (it.karmapoints <= totalmeankarma)
343                         {
344                                 centerprint(it, strcat(BOLD_OPERATOR, "^1KARMA WARNING!\n^3Here, have the Rifle!"));
345                                 GiveWeapon(it, WEP_RIFLE.m_id, OP_PLUS, 1);
346                         }
347                         //Gives Mine Layer weapon to the player
348                         GiveWeapon(it, WEP_MINE_LAYER.m_id, OP_PLUS, 1);
349                         Send_Notification(NOTIF_ONE_ONLY, it, MSG_CENTER, CENTER_MMM_CIVILIAN);
350                         Send_Notification(NOTIF_ONE_ONLY, it, MSG_INFO, INFO_MMM_CIVILIAN);
351                         //PrintToChatAll(sprintf("^1DEBUG^7: %s is ^2Civilian^7!", it.netname));
352                 }
353                 else if(it.mmm_status == MMM_STATUS_MURDERER)
354                 {
355                         SetResource(it, RES_SHELLS, 10);
356                         SetResource(it, RES_BULLETS, 20);
357                         SetResource(it, RES_ROCKETS, 10);
358                         SetResource(it, RES_CELLS, 10);
359                         if (it.karmapoints <= totalmeankarma)
360                         {
361                                 centerprint(it, strcat(BOLD_OPERATOR, "^1KARMA WARNING!\n^3Here, have the Rifle!"));
362                                 GiveWeapon(it, WEP_RIFLE.m_id, OP_PLUS, 1);
363                         }
364                         //Gives Mine Layer weapon to the player
365                         GiveWeapon(it, WEP_MINE_LAYER.m_id, OP_PLUS, 1);
366                         Send_Notification(NOTIF_ONE_ONLY, it, MSG_CENTER, CENTER_MMM_MURDERER);
367                         Send_Notification(NOTIF_ONE_ONLY, it, MSG_INFO, INFO_MMM_MURDERER);
368                         //PrintToChatAll(sprintf("^1DEBUG^7: %s is ^1Murderer^7!", it.netname));
369                 }
370                 else if(it.mmm_status == MMM_STATUS_SLEUTH)
371                 {
372                         SetResource(it, RES_ROCKETS, 10);
373                         if (it.karmapoints <= totalmeankarma)
374                         {
375                                 centerprint(it, strcat(BOLD_OPERATOR, "^1KARMA WARNING!\n^3Here, have the Rifle!"));
376                                 GiveWeapon(it, WEP_RIFLE.m_id, OP_PLUS, 1);
377                         }
378                         //Gives Shockwave and Mine Layer weapon to the player
379                         GiveWeapon(it, WEP_SHOCKWAVE.m_id, OP_PLUS, 1);
380                         GiveWeapon(it, WEP_MINE_LAYER.m_id, OP_PLUS, 1);
381                         Send_Notification(NOTIF_ONE_ONLY, it, MSG_CENTER, CENTER_MMM_SLEUTH);
382                         Send_Notification(NOTIF_ONE_ONLY, it, MSG_INFO, INFO_MMM_SLEUTH);
383                         PrintToChatAll(sprintf("%s is ^4Sleuth^7!", it.netname));
384                 }
385                 mmm_FakeTimeLimit(it, round_handler_GetEndTime());
386         });
387 }
388
389 bool mmm_CheckPlayers()
390 {
391         static int prev_missing_players;
392         allowed_to_spawn = true;
393         int playercount = 0;
394
395         FOREACH_CLIENT(IS_PLAYER(it) && !IS_DEAD(it),
396         {       
397                 //PrintToChatAll(sprintf("it.karmapoints ^5begin: ^3%f",it.karmapoints));
398                 //Karma points start
399                 if (it.karmastarted != true)
400                 {
401                         GameRules_scoring_add(it, MMM_KARMA, autocvar_g_mmm_max_karma_points - it.karmapoints);
402                         it.karmapoints = autocvar_g_mmm_max_karma_points;
403                         it.karmastarted = true;
404                 }
405                 karma_Control(it);
406                 ++playercount;
407                 //PrintToChatAll(sprintf("it.karmapoints ^6end: ^3%f",it.karmapoints));
408         });
409
410         if (playercount >= 2)
411         {
412                 if(prev_missing_players > 0)
413                         Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_PLAYERS);
414                 prev_missing_players = -1;
415                 return true;
416         }
417
418         if(playercount == 0)
419         {
420                 if(prev_missing_players > 0)
421                         Kill_Notification(NOTIF_ALL, NULL, MSG_CENTER, CPID_MISSING_PLAYERS);
422                 prev_missing_players = -1;
423                 return false;
424         }
425
426         // if we get here, only 1 player is missing
427         if(prev_missing_players != 1)
428         {
429                 Send_Notification(NOTIF_ALL, NULL, MSG_CENTER, CENTER_MISSING_PLAYERS, 1);
430                 prev_missing_players = 1;
431         }
432         return false;
433 }
434
435 bool mmm_isEliminated(entity e)
436 {
437         if(e.caplayer == 1 && (IS_DEAD(e) || e.frags == FRAGS_PLAYER_OUT_OF_GAME))
438                 return true;
439         if(e.caplayer == 0.5)
440                 return true;
441         return false;
442 }
443
444 void mmm_Initialize() // run at the start of a match, initiates game mode
445 {
446         GameRules_scoring(0, SFL_SORT_PRIO_PRIMARY, 0, {
447                 field(SP_MMM_KARMA, "karma", SFL_SORT_PRIO_SECONDARY); //LegendGuard adds Karma points in the scoreboard 22-02-2021
448         });
449
450         allowed_to_spawn = true;
451         round_handler_Spawn(mmm_CheckPlayers, mmm_CheckWinner, mmm_RoundStart);
452         round_handler_Init(5, autocvar_g_mmm_warmup, autocvar_g_mmm_round_timelimit);
453         EliminatedPlayers_Init(mmm_isEliminated);
454 }
455
456 void checkWeaponDeathtype(entity target, float deathtype)
457 {
458         switch (deathtype)
459         {
460                 case WEP_ARC.m_id: case 276: case 788: target.killedwithweapon = "Impacted by the Arc's electric shock"; return;
461                 case WEP_BLASTER.m_id: case 513: target.killedwithweapon = "Blasted by the Blaster"; return;
462                 case WEP_CRYLINK.m_id: case 263: case 519: target.killedwithweapon = "Shot by the Crylink"; return;
463                 case WEP_DEVASTATOR.m_id: case 522: case 1546: target.killedwithweapon = "Bombarded by the Devastator"; return;
464                 case WEP_ELECTRO.m_id: case 262: case 518: case 1542: target.killedwithweapon = "Electrocuted by the Electro"; return;
465                 case WEP_FIREBALL.m_id: case 273: case 529: case 1297: target.killedwithweapon = "Burned by the Fireball"; return;
466                 case WEP_HAGAR.m_id: case 265: target.killedwithweapon = "Gunned by the Hagar"; return;
467                 case WEP_HLAC.m_id: case 270: case 526: target.killedwithweapon = "Cut down with the HLAC"; return;
468                 case WEP_HOOK.m_id: case 1805: target.killedwithweapon = "Caught in Hook gravity bomb"; return;
469                 case WEP_MACHINEGUN.m_id: target.activekillerrole = true; target.killedwithweapon = "Riddled full of holes by the Machine Gun"; return;
470                 case WEP_MINE_LAYER.m_id: case 517: case 1541: target.killedwithweapon = "Exploited by the Mine Layer"; return;
471                 case WEP_MORTAR.m_id: case 516: case 1284: target.killedwithweapon = "Blew up with the Mortar"; return;
472                 case WEP_OVERKILL_NEX.m_id: target.killedwithweapon = "Sniped by the Overkill Nex"; return;
473                 case WEP_RIFLE.m_id: case 272: target.activekillerrole = true; target.killedwithweapon = "Sniped by the Rifle"; return;
474                 case WEP_SEEKER.m_id: case 274: case 786: target.killedwithweapon = "Blasted by the Seeker"; return;
475                 case WEP_SHOCKWAVE.m_id: target.killedwithweapon = "Gunned down by the Shockwave"; return;
476                 case 275: target.killedwithweapon = "Knocked by the Shockwave"; return;
477                 case WEP_SHOTGUN.m_id: target.activekillerrole = true; target.killedwithweapon = "Shot by Shotgun"; return;
478                 case 258: target.killedwithweapon = "Knocked by the Shotgun"; return;
479                 case WEP_TUBA.m_id: target.killedwithweapon = "Ear pain by the @!#%'n Tuba"; return;
480                 case WEP_VAPORIZER.m_id: case 257: case 769: target.killedwithweapon = "Sniped by the Vaporizer"; return;
481                 case WEP_VORTEX.m_id: target.killedwithweapon = "Sniped by the Vortex"; return;
482                 case DEATH_FALL.m_id: target.killedwithweapon = "Fall"; return;
483                 case DEATH_FIRE.m_id: target.killedwithweapon = "Burned with the fire"; return;
484                 case DEATH_LAVA.m_id: target.killedwithweapon = "Burned in lava"; return;
485                 case DEATH_MIRRORDAMAGE.m_id: target.killedwithweapon = "Suicide"; return;
486                 case DEATH_SLIME.m_id: target.killedwithweapon = "Melted in slime"; return;
487                 case DEATH_TELEFRAG.m_id: target.killedwithweapon = "Telefragged"; return;
488                 case DEATH_NADE.m_id: target.killedwithweapon = "Blown up by the nade"; return;
489                 case DEATH_NADE_NAPALM.m_id: target.killedwithweapon = "Burned by the Napalm nade"; return;
490                 case DEATH_NADE_ICE.m_id: target.killedwithweapon = "Frozen by the Ice nade"; return;
491                 case DEATH_NADE_HEAL.m_id: target.killedwithweapon = "Sucked by the Heal nade"; return;
492                 default: target.killedwithweapon = "Unknown"; return;
493         }
494 }
495
496 void ReduceKarmaPointsandFrags(entity frag_attacker, entity frag_target, float frag_deathtype, entity wep_ent)
497 {
498         karmaLoseDifference(frag_attacker, frag_target);
499         GiveFrags(frag_attacker, frag_target, ((autocvar_g_mmm_punish_teamkill) ? -1 : -2), frag_deathtype, wep_ent.weaponentity_fld);
500         karma_Control(frag_attacker);
501         frag_target.whokilled = frag_attacker.netname;
502 }
503
504 // ==============
505 // Hook Functions
506 // ==============
507
508 MUTATOR_HOOKFUNCTION(mmm, ClientObituary)
509 {
510         // LegendGuard's IDEA: To adjust the grade of severity of karma, 
511         // we could add if sentence per weapons and adjust each weapon attack
512         // its own grade. Instead doing random decrease grade 22-02-2021
513         
514         // in mmm, announcing a frag would tell everyone who the murderer is
515         entity frag_attacker = M_ARGV(1, entity);
516         entity frag_target = M_ARGV(2, entity);
517         
518         if(IS_PLAYER(frag_attacker) && frag_attacker != frag_target)
519         {
520                 float frag_deathtype = M_ARGV(3, float);
521                 entity wep_ent = M_ARGV(4, entity);
522                 
523                 //PrintToChatAll(strcat("deathtype var: ", ftos(frag_deathtype)));
524                 checkWeaponDeathtype(frag_target, frag_deathtype);
525                 // "team" kill, a point is awarded to the player by default so we must take it away plus an extra one
526                 // unless the player is going to be punished for suicide, in which case just remove one
527                 if(frag_attacker.mmm_status == frag_target.mmm_status)
528                 {
529                         //PrintToChatAll("^1DEBUG^7: A ^2PLAYER^7 has fragged a ^2PLAYER OF HIS OWN TEAM^7, TOO BAD!");
530                         ReduceKarmaPointsandFrags(frag_attacker, frag_target, frag_deathtype, wep_ent);
531                         switch (frag_attacker.mmm_status)
532                         {
533                                 case MMM_STATUS_CIVILIAN: frag_target.killerrole = "\n^3Killer role: ^2Civilian"; return;
534                                 case MMM_STATUS_MURDERER: frag_target.killerrole = "\n^3Killer role: ^1Murderer"; return;
535                                 case MMM_STATUS_SLEUTH: frag_target.killerrole = "\n^3Killer role: ^4Sleuth"; return;
536                                 default: return;
537                         }
538                         //PrintToChatAll(sprintf("frag_attacker.karmapoints: ^1%f", frag_attacker.karmapoints));
539                 }
540
541                 if(frag_attacker.mmm_status == MMM_STATUS_SLEUTH)
542                 {
543                         if (frag_target.mmm_status == MMM_STATUS_CIVILIAN || frag_target.mmm_status == MMM_STATUS_SLEUTH)
544                         {       
545                                 //PrintToChatAll("^1DEBUG^7: A ^4Sleuth^7 fragged an ^2Civilian^7/^4Sleuth^7, TOO BAD!");
546                                 ReduceKarmaPointsandFrags(frag_attacker, frag_target, frag_deathtype, wep_ent);
547                                 frag_target.killerrole = "\n^3Killer role: ^4Sleuth";
548                                 //PrintToChatAll(sprintf("frag_attacker.karmapoints: ^1%f", frag_attacker.karmapoints));
549                         }
550                         else
551                         {
552                                 frag_target.whokilled = frag_attacker.netname;
553                                 frag_target.killerrole = "\n^3Killer role: ^4Sleuth";
554                         }
555                 }
556
557                 if (frag_attacker.mmm_status == MMM_STATUS_CIVILIAN)
558                 {
559                         if (frag_target.mmm_status == MMM_STATUS_SLEUTH)
560                         {
561                                 //PrintToChatAll("^1DEBUG^7: An ^2Civilian^7 fragged a ^4Sleuth^7, TOO BAD!");
562                                 ReduceKarmaPointsandFrags(frag_attacker, frag_target, frag_deathtype, wep_ent);
563                                 frag_target.killerrole = "\n^3Killer role: ^2Civilian";
564                         }
565                         else
566                         {
567                                 frag_target.whokilled = frag_attacker.netname;
568                                 frag_target.killerrole = "\n^3Killer role: ^2Civilian";
569                         }
570                 }
571                 
572                 if (frag_attacker.mmm_status == MMM_STATUS_MURDERER)
573                 {
574                         if (frag_target.mmm_status == MMM_STATUS_CIVILIAN)
575                         {
576                                 frag_target.whokilled = frag_attacker.netname;
577                                 frag_target.killerrole = "\n^3Killer role: ^1Murderer";
578                         }
579                         else
580                         {
581                                 frag_target.whokilled = frag_attacker.netname;
582                                 frag_target.killerrole = "\n^3Killer role: ^1Murderer";
583                         }
584                 }
585                 //if mmm_status is 1, means civilian, 2 means murderer, 3 means sleuth
586                 //PrintToChatAll(sprintf("^1DEBUG^7: frag_attacker.mmm_status is ^3%s^7",ftos(frag_attacker.mmm_status)));
587                 //PrintToChatAll(sprintf("^1DEBUG^7: frag_target.mmm_status is ^3%s^7",ftos(frag_target.mmm_status)));
588         }
589         else
590         {
591                 float frag_deathtype = M_ARGV(3, float);
592                 checkWeaponDeathtype(frag_target, frag_deathtype);
593         }
594
595         //TODO: try to do a "find out" if a sleuth can see who fragged to who if possible 21-02-2021
596         M_ARGV(5, bool) = true; // anonymous attacker
597 }
598
599 //karma weapon damage, halve the damage attack when player has low karma 20-03-2021
600 MUTATOR_HOOKFUNCTION(mmm, Damage_Calculate)
601 {
602         entity attacker = M_ARGV(1, entity);
603         entity target = M_ARGV(2, entity);
604         float deathtype = M_ARGV(3, float);
605         float damage = M_ARGV(4, float);
606         vector force = M_ARGV(6, vector);
607         string corpsemessagestrcat = "";
608
609         if (autocvar_g_mmm_karma_damageactive != false)
610         {
611                 if (IS_PLAYER(attacker))
612                 {
613                         if(target == attacker) // damage done to yourself
614                         {
615                                 damage /= autocvar_g_weapondamagefactor / (attacker.karmapoints / autocvar_g_mmm_max_karma_points);
616                                 force /= autocvar_g_weaponforcefactor / (attacker.karmapoints / autocvar_g_mmm_max_karma_points);
617                         }
618                         else if (target != attacker)
619                         {
620                                 damage /= autocvar_g_weapondamagefactor / (attacker.karmapoints / autocvar_g_mmm_max_karma_points);
621                                 force /= autocvar_g_weaponforcefactor / (attacker.karmapoints / autocvar_g_mmm_max_karma_points);
622                         }
623                         else
624                         {
625                                 damage *= autocvar_g_weapondamagefactor;
626                                 force *= autocvar_g_weaponforcefactor;
627                         }
628                 }
629         }
630
631         //SLEUTH CORPSE DETECTION SKILL 21-03-2021
632         if (attacker.mmm_status == MMM_STATUS_SLEUTH)
633         {
634                 if(IS_DEAD(target))
635                 {
636                         //Shockwave weapon as radar gun to check the corpses 22-03-2021
637                         if(DEATH_ISWEAPON(deathtype, WEP_SHOCKWAVE))
638                         {
639                                 if (target.killedwithweapon == "")
640                                         target.killedwithweapon = "UNKNOWN CAUSE";
641                                 
642                                 if (target.activekillerrole != true)
643                                 {
644                                         target.killerrole = "";
645                                         target.activekillerrole = false;
646                                 }
647
648                                 string killedbyphrase = strcat("\n^3Killed by:^7 ", target.whokilled, target.killerrole); 
649                                 string wepkilledphrase = strcat("\n^3Cause:^7 ", target.killedwithweapon);
650                                 if (target.whokilled == "")
651                                 {
652                                         killedbyphrase = "";
653                                         if (target.killedwithweapon == "")
654                                                 wepkilledphrase = "\n^3Cause:^7 UNKNOWN CAUSE";
655                                 }
656
657                                 damage = 0;
658                                 force = '0 0 0';
659                                 if (target.mmm_status == MMM_STATUS_CIVILIAN)
660                                 {
661                                         //try to add centerprint message for chat privately if possible
662                                         corpsemessagestrcat = strcat("\n^3Name:^7 ", target.netname, "\n^3Role: ^2Civilian", killedbyphrase, wepkilledphrase);
663                                         centerprint(attacker, strcat(BOLD_OPERATOR, corpsemessagestrcat));//("\n^6Name^3:^7 ", target.netname, "\n^5Role^3: ^2Civilian\n", "^1Killed by^3:^7 ", target.whokilled)));
664                                 }
665                                 else if (target.mmm_status == MMM_STATUS_MURDERER)
666                                 {
667                                         corpsemessagestrcat = strcat("\n^3Name:^7 ", target.netname, "\n^3Role: ^1Murderer", killedbyphrase, wepkilledphrase);
668                                         centerprint(attacker, strcat(BOLD_OPERATOR, corpsemessagestrcat));//("\n^6Name^3:^7 ", target.netname, "\n^5Role^3: ^1Murderer\n", "^1Killed by^3:^7 ", target.whokilled)));
669                                 }
670                                 else if (target.mmm_status == MMM_STATUS_SLEUTH)
671                                 {
672                                         corpsemessagestrcat = strcat("\n^3Name:^7 ", target.netname, "\n^3Role: ^4Sleuth", killedbyphrase, wepkilledphrase);
673                                         centerprint(attacker, strcat(BOLD_OPERATOR, corpsemessagestrcat));//("\n^6Name^3:^7 ", target.netname, "\n^5Role^3: ^4Sleuth\n", "^1Killed by^3:^7 ", target.whokilled)));
674                                 }
675                                 attacker.investigated = true;
676                         }
677                 }
678         }
679
680         M_ARGV(4, float) = damage;
681         M_ARGV(6, vector) = force;
682 }
683
684 MUTATOR_HOOKFUNCTION(mmm, PlayerPreThink)
685 {
686         entity player = M_ARGV(0, entity);
687         
688         if(IS_PLAYER(player) || player.caplayer)
689         {
690                 // update the scoreboard colour display to out the real killer at the end of the round
691                 // running this every frame to avoid cheats
692                 int plcolor = MMM_COLOR_CIVILIAN;
693                 if(player.mmm_status == MMM_STATUS_CIVILIAN && game_stopped) //Civilian status by default
694                         plcolor = MMM_COLOR_CIVILIAN;
695                 if(player.mmm_status == MMM_STATUS_MURDERER && game_stopped)
696                         plcolor = MMM_COLOR_MURDERER;
697                 //LegendGuard adds for Sleuth 21-02-2021
698                 if(player.mmm_status == MMM_STATUS_SLEUTH)// && game_stopped)
699                         plcolor = MMM_COLOR_SLEUTH;
700                 setcolor(player, plcolor);
701         }
702
703         //CORPSE FEATURE 10-03-2021
704         if (IS_DEAD(player))
705         {
706                 player.event_damage = func_null;
707                 //player.health = 0;
708                 player.solid = SOLID_CORPSE;
709                 set_movetype(player, MOVETYPE_STEP); //test with MOVETYPE_TOSS or MOVETYPE_WALK (it's like sliding object) or MOVETYPE_BOUNCE (maybe not good)
710         }
711 }
712
713 MUTATOR_HOOKFUNCTION(mmm, PlayerSpawn)
714 {
715         entity player = M_ARGV(0, entity);
716
717         player.mmm_status = 0;
718         player.mmm_validkills = 0;
719         player.caplayer = 1;
720         if (!warmup_stage)
721                 eliminatedPlayers.SendFlags |= 1;
722 }
723
724 MUTATOR_HOOKFUNCTION(mmm, ForbidSpawn)
725 {
726         entity player = M_ARGV(0, entity);
727
728         // spectators / observers that weren't playing can join; they are
729         // immediately forced to observe in the PutClientInServer hook
730         // this way they are put in a team and can play in the next round
731         if (!allowed_to_spawn && player.caplayer)
732                 return true;
733         return false;
734 }
735
736 MUTATOR_HOOKFUNCTION(mmm, PutClientInServer)
737 {
738         entity player = M_ARGV(0, entity);
739
740         if (!allowed_to_spawn && IS_PLAYER(player)) // this is true even when player is trying to join
741         {
742                 TRANSMUTE(Observer, player);
743                 if (CS(player).jointime != time && !player.caplayer) // not when connecting
744                 {
745                         player.caplayer = 0.5;
746                         Send_Notification(NOTIF_ONE_ONLY, player, MSG_INFO, INFO_CA_JOIN_LATE);
747                 }
748         }
749 }
750
751 MUTATOR_HOOKFUNCTION(mmm, reset_map_players)
752 {
753         FOREACH_CLIENT(true, {
754                 CS(it).killcount = 0;
755                 it.mmm_status = 0;
756                 mmm_FakeTimeLimit(it, -1); // restore original timelimit
757                 if (!it.caplayer && IS_BOT_CLIENT(it))
758                         it.caplayer = 1;
759                 if (it.caplayer)
760                 {
761                         TRANSMUTE(Player, it);
762                         it.caplayer = 1;
763                         it.respawn_flags = RESPAWN_SILENT; //CSQC print output respawn lib.qh error fix
764                         PutClientInServer(it);
765                 }
766         });
767         bot_relinkplayerlist();
768         return true;
769 }
770
771 MUTATOR_HOOKFUNCTION(mmm, reset_map_global)
772 {
773         allowed_to_spawn = true;
774         return true;
775 }
776
777 entity mmm_LastPlayerForTeam(entity this)
778 {
779         entity last_pl = NULL;
780         FOREACH_CLIENT(IS_PLAYER(it) && it != this, {
781                 if (!IS_DEAD(it) && this.mmm_status == it.mmm_status)
782                 {
783                         if (!last_pl)
784                         {
785                                 last_pl = it;
786                         }
787                         else
788                                 return NULL;
789                 }
790         });
791         return last_pl;
792 }
793
794 void mmm_LastPlayerForTeam_Notify(entity this)
795 {
796         if (!warmup_stage && round_handler_IsActive() && round_handler_IsRoundStarted())
797         {
798                 entity pl = mmm_LastPlayerForTeam(this);
799                 if (pl)
800                         Send_Notification(NOTIF_ONE_ONLY, pl, MSG_CENTER, CENTER_ALONE);
801         }
802 }
803
804 MUTATOR_HOOKFUNCTION(mmm, PlayerDies)
805 {
806         entity frag_attacker = M_ARGV(1, entity);
807         entity frag_target = M_ARGV(2, entity);
808         //float frag_deathtype = M_ARGV(3, float);
809
810         mmm_LastPlayerForTeam_Notify(frag_target);
811         if (!allowed_to_spawn)
812         {
813                 frag_target.respawn_flags = RESPAWN_DENY;
814                 // prevent unwanted sudden rejoin as spectator and movement of spectator camera
815                 frag_target.respawn_time = time + 2;
816         }
817         frag_target.respawn_flags |= RESPAWN_DENY;
818         frag_target.event_damage = func_null;
819         frag_target.health = 0;
820         
821         if (!warmup_stage)
822         {
823                 eliminatedPlayers.SendFlags |= 1;
824                 if (IS_BOT_CLIENT(frag_target))
825                         bot_clear(frag_target);
826         }
827         
828         //if(frag_attacker.mmm_status == frag_target.mmm_status)
829         // killed an ally! punishment is sentenced
830         if(frag_attacker.mmm_status == MMM_STATUS_SLEUTH)
831         {
832                 if (frag_target.mmm_status == MMM_STATUS_CIVILIAN)
833                 {
834                         //PrintToChatAll("^1DEBUG^7: ^4SLEUTH ^1DAMAGE/DEAD^7 HAS TAKEN!");
835                         //30 damage points deal
836                         Damage(frag_attacker, frag_attacker, frag_attacker, 30, DEATH_MIRRORDAMAGE.m_id, DMG_NOWEP, frag_attacker.origin, '0 0 0');
837                 }
838         }
839         if (frag_attacker.mmm_status == MMM_STATUS_CIVILIAN)
840         {
841                 if (frag_target.mmm_status == MMM_STATUS_SLEUTH)
842                 {
843                         //PrintToChatAll("^1DEBUG^7: ^2CIVILIAN ^1DAMAGE/DEAD^7 HAS TAKEN!");
844                         //30 damage points deal
845                         Damage(frag_attacker, frag_attacker, frag_attacker, 30, DEATH_MIRRORDAMAGE.m_id, DMG_NOWEP, frag_attacker.origin, '0 0 0');
846                 }
847         }
848         return true;
849 }
850
851 MUTATOR_HOOKFUNCTION(mmm, ClientDisconnect)
852 {
853         entity player = M_ARGV(0, entity);
854
855         if (IS_PLAYER(player) && !IS_DEAD(player))
856                 mmm_LastPlayerForTeam_Notify(player);
857         return true;
858 }
859
860 MUTATOR_HOOKFUNCTION(mmm, MakePlayerObserver)
861 {
862         // LegendGuard, here is where spectators shouldn't talk to any players to say the hints or who is who 21-10-2021
863         entity player = M_ARGV(0, entity);
864
865         if (IS_PLAYER(player) && !IS_DEAD(player))
866                 mmm_LastPlayerForTeam_Notify(player);
867         if (player.killindicator_teamchange == -2) // player wants to spectate
868                 player.caplayer = 0;
869         if (player.caplayer)
870                 player.frags = FRAGS_PLAYER_OUT_OF_GAME;
871         if (!warmup_stage)
872                 eliminatedPlayers.SendFlags |= 1;
873         if (!player.caplayer)
874         {
875                 player.mmm_validkills = 0;
876                 player.mmm_status = 0;
877                 mmm_FakeTimeLimit(player, -1); // restore original timelimit
878                 return false;  // allow team reset
879         }
880         return true;  // prevent team reset
881 }
882
883 MUTATOR_HOOKFUNCTION(mmm, Scores_CountFragsRemaining)
884 {
885         // announce remaining frags?
886         return true;
887 }
888
889 MUTATOR_HOOKFUNCTION(mmm, GiveFragsForKill, CBC_ORDER_FIRST)
890 {
891         entity frag_attacker = M_ARGV(0, entity);
892         if(!warmup_stage && round_handler_IsActive() && round_handler_IsRoundStarted())
893                 frag_attacker.mmm_validkills += M_ARGV(2, float);
894         M_ARGV(2, float) = 0; // score will be given to the winner when the round ends
895         return true;
896 }
897
898 MUTATOR_HOOKFUNCTION(mmm, AddPlayerScore)
899 {
900         // add scorefield for scoreboard here
901         entity scorefield = M_ARGV(0, entity);
902         if(scorefield == SP_KILLS || scorefield == SP_DEATHS || scorefield == SP_SUICIDES || scorefield == SP_DMG || scorefield == SP_DMGTAKEN)
903                 M_ARGV(1, float) = 0; // don't report that the player has killed or been killed, that would out them as a murderer!
904 }
905
906 MUTATOR_HOOKFUNCTION(mmm, CalculateRespawnTime)
907 {
908         // no respawn calculations needed, player is forced to spectate anyway
909         return true;
910 }
911
912 MUTATOR_HOOKFUNCTION(mmm, Bot_FixCount, CBC_ORDER_EXCLUSIVE)
913 {
914         FOREACH_CLIENT(IS_REAL_CLIENT(it), {
915                 if (IS_PLAYER(it) || it.caplayer == 1)
916                         ++M_ARGV(0, int);
917                 ++M_ARGV(1, int);
918         });
919         return true;
920 }
921
922 MUTATOR_HOOKFUNCTION(mmm, ClientCommand_Spectate)
923 {
924         entity player = M_ARGV(0, entity);
925
926         if (player.caplayer)
927         {
928                 // they're going to spec, we can do other checks
929                 if (autocvar_sv_spectate && (IS_SPEC(player) || IS_OBSERVER(player)))
930                         Send_Notification(NOTIF_ONE_ONLY, player, MSG_INFO, INFO_CA_LEAVE);
931                 return MUT_SPECCMD_FORCE;
932         }
933
934         return MUT_SPECCMD_CONTINUE;
935 }
936
937 MUTATOR_HOOKFUNCTION(mmm, GetPlayerStatus)
938 {
939         entity player = M_ARGV(0, entity);
940
941         return player.caplayer == 1;
942 }
943
944 MUTATOR_HOOKFUNCTION(mmm, BotShouldAttack)
945 {
946         entity bot = M_ARGV(0, entity);
947         entity targ = M_ARGV(1, entity);
948
949         if(targ.mmm_status == bot.mmm_status)
950         {
951                 return true;
952         }
953         
954         // LegendGuard fixed the problem of Sleuths and Civilians attacking each other 26-03-2021
955         if(bot.mmm_status == MMM_STATUS_SLEUTH)
956         {
957                 if(targ.mmm_status == MMM_STATUS_CIVILIAN)
958                         return true;
959         }
960 }