1 //-------------------------------------------------------------------------
2 /*
3 Copyright (C) 2010-2019 EDuke32 developers and contributors
4 Copyright (C) 2019 Nuke.YKT
5 
6 This file is part of NBlood.
7 
8 NBlood is free software; you can redistribute it and/or
9 modify it under the terms of the GNU General Public License version 2
10 as published by the Free Software Foundation.
11 
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
15 
16 See the GNU General Public License for more details.
17 
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
21 */
22 //-------------------------------------------------------------------------
23 #include "build.h"
24 #include "mmulti.h"
25 #include "compat.h"
26 #include "keyboard.h"
27 #include "control.h"
28 #include "function.h"
29 #include "common_game.h"
30 #include "blood.h"
31 #include "config.h"
32 #include "demo.h"
33 #include "eventq.h"
34 #include "globals.h"
35 #include "levels.h"
36 #include "loadsave.h"
37 #include "menu.h"
38 #include "messages.h"
39 #include "network.h"
40 #include "player.h"
41 #include "view.h"
42 
43 CPlayerMsg gPlayerMsg;
44 CCheatMgr gCheatMgr;
45 
sub_5A928(void)46 void sub_5A928(void)
47 {
48     for (int i = 0; i < NUMGAMEFUNCTIONS-1; i++)
49         CONTROL_ClearButton(i);
50 }
51 
52 extern uint8_t KeyboardKeys[NUMGAMEFUNCTIONS][2];
sub_5A944(char key)53 void sub_5A944(char key)
54 {
55     for (int i = 0; i < NUMGAMEFUNCTIONS-1; i++)
56     {
57         char key1, key2;
58         key1 = KeyboardKeys[i][0];
59         key2 = KeyboardKeys[i][1];
60         if (key1 == key || key2 == key)
61             CONTROL_ClearButton(i);
62     }
63 }
64 
SetGodMode(bool god)65 void SetGodMode(bool god)
66 {
67     playerSetGodMode(gMe, god);
68     if (gMe->godMode)
69         viewSetMessage("You are immortal.");
70     else
71         viewSetMessage("You are mortal.");
72 }
73 
SetClipMode(bool noclip)74 void SetClipMode(bool noclip)
75 {
76     gNoClip = noclip;
77     if (gNoClip)
78         viewSetMessage("Unclipped movement.");
79     else
80         viewSetMessage("Normal movement.");
81 }
82 
packStuff(PLAYER * pPlayer)83 void packStuff(PLAYER *pPlayer)
84 {
85     for (int i = 0; i < 5; i++)
86         packAddItem(pPlayer, i);
87 }
88 
packClear(PLAYER * pPlayer)89 void packClear(PLAYER *pPlayer)
90 {
91     pPlayer->packItemId = 0;
92     for (int i = 0; i < 5; i++)
93     {
94         pPlayer->packSlots[i].isActive = 0;
95         pPlayer->packSlots[i].curAmount = 0;
96     }
97 }
98 
SetAmmo(bool stat)99 void SetAmmo(bool stat)
100 {
101     if (stat)
102     {
103         for (int i = 0; i < 12; i++)
104             gMe->ammoCount[i] = gAmmoInfo[i].max;
105         viewSetMessage("You have full ammo.");
106     }
107     else
108     {
109         for (int i = 0; i < 12; i++)
110             gMe->ammoCount[i] = 0;
111         viewSetMessage("You have no ammo.");
112     }
113 }
114 
SetWeapons(bool stat)115 void SetWeapons(bool stat)
116 {
117     for (int i = 0; i < 14; i++)
118     {
119         gMe->hasWeapon[i] = stat;
120     }
121     SetAmmo(stat);
122     if (stat)
123         viewSetMessage("You have all weapons.");
124     else
125     {
126         if (!VanillaMode())
127         {
128             // Keep the pitchfork to avoid freeze
129             gMe->hasWeapon[1] = 1;
130             gMe->curWeapon = 0;
131             gMe->nextWeapon = 1;
132         }
133         viewSetMessage("You have no weapons.");
134     }
135 }
136 
SetToys(bool stat)137 void SetToys(bool stat)
138 {
139     if (stat)
140     {
141         packStuff(gMe);
142         viewSetMessage("Your inventory is full.");
143     }
144     else
145     {
146         packClear(gMe);
147         viewSetMessage("Your inventory is empty.");
148     }
149 }
150 
SetArmor(bool stat)151 void SetArmor(bool stat)
152 {
153     int nAmount;
154     if (stat)
155     {
156         viewSetMessage("You have full armor.");
157         nAmount = 3200;
158     }
159     else
160     {
161         viewSetMessage("You have no armor.");
162         nAmount = 0;
163     }
164     for (int i = 0; i < 3; i++)
165         gMe->armor[i] = nAmount;
166 }
167 
SetKeys(bool stat)168 void SetKeys(bool stat)
169 {
170     for (int i = 1; i <= 6; i++)
171         gMe->hasKey[i] = stat;
172     if (stat)
173         viewSetMessage("You have all keys.");
174     else
175         viewSetMessage("You have no keys.");
176 }
177 
SetInfiniteAmmo(bool stat)178 void SetInfiniteAmmo(bool stat)
179 {
180     gInfiniteAmmo = stat;
181     if (gInfiniteAmmo)
182         viewSetMessage("You have infinite ammo.");
183     else
184         viewSetMessage("You have limited ammo.");
185 }
186 
SetMap(bool stat)187 void SetMap(bool stat)
188 {
189     gFullMap = stat;
190     if (gFullMap)
191         viewSetMessage("You have the map.");
192     else
193         viewSetMessage("You have no map.");
194 }
195 
SetWooMode(bool stat)196 void SetWooMode(bool stat)
197 {
198     if (stat)
199     {
200         if (!powerupCheck(gMe, kPwUpTwoGuns))
201             powerupActivate(gMe, kPwUpTwoGuns);
202     }
203     else
204     {
205         if (powerupCheck(gMe, kPwUpTwoGuns))
206         {
207             if (!VanillaMode())
208                 gMe->pwUpTime[kPwUpTwoGuns] = 0;
209             powerupDeactivate(gMe, kPwUpTwoGuns);
210         }
211     }
212 }
213 
ToggleWooMode(void)214 void ToggleWooMode(void)
215 {
216     SetWooMode(!(powerupCheck(gMe, kPwUpTwoGuns) != 0));
217 }
218 
ToggleBoots(void)219 void ToggleBoots(void)
220 {
221     if (powerupCheck(gMe, kPwUpJumpBoots))
222     {
223         viewSetMessage("You have no Jumping Boots.");
224         if (!VanillaMode())
225         {
226             gMe->pwUpTime[kPwUpJumpBoots] = 0;
227             gMe->packSlots[4].curAmount = 0;
228         }
229         powerupDeactivate(gMe, kPwUpJumpBoots);
230     }
231     else
232     {
233         viewSetMessage("You have the Jumping Boots.");
234         if (!VanillaMode())
235             gMe->pwUpTime[kPwUpJumpBoots] = gPowerUpInfo[kPwUpJumpBoots].bonusTime;
236         powerupActivate(gMe, kPwUpJumpBoots);
237     }
238 }
239 
ToggleInvisibility(void)240 void ToggleInvisibility(void)
241 {
242     if (powerupCheck(gMe, kPwUpShadowCloak))
243     {
244         viewSetMessage("You are visible.");
245         if (!VanillaMode())
246             gMe->pwUpTime[kPwUpShadowCloak] = 0;
247         powerupDeactivate(gMe, kPwUpShadowCloak);
248     }
249     else
250     {
251         viewSetMessage("You are invisible.");
252         powerupActivate(gMe, kPwUpShadowCloak);
253     }
254 }
255 
ToggleInvulnerability(void)256 void ToggleInvulnerability(void)
257 {
258     if (powerupCheck(gMe, kPwUpDeathMask))
259     {
260         viewSetMessage("You are vulnerable.");
261         if (!VanillaMode())
262             gMe->pwUpTime[kPwUpDeathMask] = 0;
263         powerupDeactivate(gMe, kPwUpDeathMask);
264     }
265     else
266     {
267         viewSetMessage("You are invulnerable.");
268         powerupActivate(gMe, kPwUpDeathMask);
269     }
270 }
271 
ToggleDelirium(void)272 void ToggleDelirium(void)
273 {
274     if (powerupCheck(gMe, kPwUpDeliriumShroom))
275     {
276         viewSetMessage("You are not delirious.");
277         if (!VanillaMode())
278             gMe->pwUpTime[kPwUpDeliriumShroom] = 0;
279         powerupDeactivate(gMe, kPwUpDeliriumShroom);
280     }
281     else
282     {
283         viewSetMessage("You are delirious.");
284         powerupActivate(gMe, kPwUpDeliriumShroom);
285     }
286 }
287 
LevelWarp(int nEpisode,int nLevel)288 void LevelWarp(int nEpisode, int nLevel)
289 {
290     levelSetupOptions(nEpisode, nLevel);
291     StartLevel(&gGameOptions);
292     viewResizeView(gViewSize);
293 }
294 
LevelWarpAndRecord(int nEpisode,int nLevel)295 void LevelWarpAndRecord(int nEpisode, int nLevel)
296 {
297     char buffer[BMAX_PATH];
298     levelSetupOptions(nEpisode, nLevel);
299     gGameStarted = false;
300     strcpy(buffer, levelGetFilename(nEpisode, nLevel));
301     ChangeExtension(buffer, ".DEM");
302     gDemo.Create(buffer);
303     StartLevel(&gGameOptions);
304     viewResizeView(gViewSize);
305 }
306 
CGameMessageMgr()307 CGameMessageMgr::CGameMessageMgr()
308 {
309     if (!VanillaMode())
310         Clear();
311     x = 1;
312     y = 0;
313     at9 = 0;
314     atd = 0;
315     nFont = 0;
316     fontHeight = 8;
317     maxNumberOfMessagesToDisplay = 4;
318     visibilityDurationInSecs = 5;
319     messageFlags = 15;
320     numberOfDisplayedMessages = 0;
321     nextMessagesIndex = messagesIndex = 0;
322 }
323 
SetState(char state)324 void CGameMessageMgr::SetState(char state)
325 {
326     if (this->state && !state)
327     {
328         this->state = 0;
329         Clear();
330     }
331     else if (!this->state && state)
332         this->state = 1;
333 }
334 
Add(const char * pText,char a2,const int pal,const MESSAGE_PRIORITY priority)335 void CGameMessageMgr::Add(const char *pText, char a2, const int pal, const MESSAGE_PRIORITY priority)
336 {
337     if (a2 && messageFlags)
338     {
339         messageStruct *pMessage = &messages[nextMessagesIndex];
340         strncpy(pMessage->text, pText, kMaxMessageTextLength-1);
341         pMessage->text[kMaxMessageTextLength-1] = 0;
342         pMessage->lastTickWhenVisible = gFrameClock + visibilityDurationInSecs*kTicRate;
343         pMessage->pal = pal;
344         pMessage->priority = priority;
345         pMessage->deleted = false;
346         nextMessagesIndex = (nextMessagesIndex+1)%kMessageLogSize;
347         if (VanillaMode())
348         {
349             numberOfDisplayedMessages++;
350             if (numberOfDisplayedMessages > maxNumberOfMessagesToDisplay)
351             {
352                 messagesIndex = (messagesIndex+1)%kMessageLogSize;
353                 atd = 0;
354                 numberOfDisplayedMessages = maxNumberOfMessagesToDisplay;
355                 at9 = fontHeight;
356             }
357         }
358     }
359 }
360 
Display(void)361 void CGameMessageMgr::Display(void)
362 {
363     if (VanillaMode())
364     {
365         if (numberOfDisplayedMessages && this->state && gInputMode != INPUT_MODE_2)
366         {
367             int initialNrOfDisplayedMsgs = numberOfDisplayedMessages;
368             int initialMessagesIndex = messagesIndex;
369             int shade = ClipHigh(initialNrOfDisplayedMsgs*8, 48);
370             int x = gViewMode == 3 ? gViewX0S : 0;
371             int y = (gViewMode == 3 ? this->y : 0) + (int)at9;
372             for (int i = 0; i < initialNrOfDisplayedMsgs; i++)
373             {
374                 messageStruct* pMessage = &messages[(initialMessagesIndex+i)%kMessageLogSize];
375                 if (pMessage->lastTickWhenVisible < gFrameClock)
376                 {
377                     messagesIndex = (messagesIndex+1)%kMessageLogSize;
378                     numberOfDisplayedMessages--;
379                     continue;
380                 }
381                 viewDrawText(nFont, pMessage->text, x, y, shade, pMessage->pal, 0, false, 256);
382                 if (gViewMode == 3)
383                 {
384                     int height;
385                     gMenuTextMgr.GetFontInfo(nFont, pMessage->text, &height, NULL);
386                     if (x+height > gViewX1S)
387                         viewUpdatePages();
388                 }
389                 y += fontHeight;
390                 shade = ClipLow(shade-64/initialNrOfDisplayedMsgs, -128);
391             }
392         }
393     }
394     else
395     {
396         if (this->state && gInputMode != INPUT_MODE_2)
397         {
398             messageStruct* currentMessages[kMessageLogSize];
399             int currentMessagesCount = 0;
400             for (int i = 0; i < kMessageLogSize; i++)
401             {
402                 messageStruct* pMessage = &messages[i];
403                 if (gFrameClock < pMessage->lastTickWhenVisible && !pMessage->deleted)
404                 {
405                     currentMessages[currentMessagesCount++] = pMessage;
406                 }
407             }
408 
409             SortMessagesByPriority(currentMessages, currentMessagesCount);
410 
411             messageStruct* messagesToDisplay[kMessageLogSize];
412             int messagesToDisplayCount = 0;
413             for (int i = 0; i < currentMessagesCount && messagesToDisplayCount < maxNumberOfMessagesToDisplay; i++)
414             {
415                 messagesToDisplay[messagesToDisplayCount++] = currentMessages[i];
416             }
417 
418             SortMessagesByTime(messagesToDisplay, messagesToDisplayCount);
419 
420             int shade = ClipHigh(messagesToDisplayCount*8, 48);
421             int x = gViewMode == 3 ? gViewX0S : 0;
422             int y = (gViewMode == 3 ? this->y : 0) + (int)at9;
423             for (int i = 0; i < messagesToDisplayCount; i++)
424             {
425                 messageStruct* pMessage = messagesToDisplay[i];
426                 viewDrawText(nFont, pMessage->text, x, y, shade, pMessage->pal, 0, false, 256);
427                 if (gViewMode == 3)
428                 {
429                     int height;
430                     gMenuTextMgr.GetFontInfo(nFont, pMessage->text, &height, NULL);
431                     if (x+height > gViewX1S)
432                         viewUpdatePages();
433                 }
434                 y += fontHeight;
435                 shade = ClipLow(shade-64/messagesToDisplayCount, -128);
436             }
437         }
438     }
439     if (at9 != 0)
440     {
441         at9 = fontHeight*at9/kTicRate;
442         atd += gFrameTicks;
443     }
444 }
445 
Clear(void)446 void CGameMessageMgr::Clear(void)
447 {
448     if (VanillaMode())
449     {
450         messagesIndex = nextMessagesIndex = numberOfDisplayedMessages = 0;
451     }
452     else
453     {
454         for (int i = 0; i < kMessageLogSize; i++)
455         {
456             messageStruct* pMessage = &messages[i];
457             pMessage->deleted = true;
458         }
459     }
460 }
461 
SetMaxMessages(int nMessages)462 void CGameMessageMgr::SetMaxMessages(int nMessages)
463 {
464     maxNumberOfMessagesToDisplay = ClipRange(nMessages, 1, 16);
465 }
466 
SetFont(int nFont)467 void CGameMessageMgr::SetFont(int nFont)
468 {
469     this->nFont = nFont;
470     fontHeight = gFont[nFont].ySize;
471 }
472 
SetCoordinates(int x,int y)473 void CGameMessageMgr::SetCoordinates(int x, int y)
474 {
475     this->x = ClipRange(x, 0, gViewX1S);
476     this->y = ClipRange(y, 0, gViewY1S);
477 }
478 
SetMessageTime(int nTime)479 void CGameMessageMgr::SetMessageTime(int nTime)
480 {
481     visibilityDurationInSecs = ClipRange(nTime, 1, 8);
482 }
483 
SetMessageFlags(unsigned int nFlags)484 void CGameMessageMgr::SetMessageFlags(unsigned int nFlags)
485 {
486     messageFlags = nFlags&0xf;
487 }
488 
SortMessagesByPriority(messageStruct ** messages,int count)489 void CGameMessageMgr::SortMessagesByPriority(messageStruct** messages, int count) {
490     for (int i = 1; i < count; i++)
491     {
492         for (int j = 0; j < count - i; j++)
493         {
494             if (messages[j]->priority != messages[j + 1]->priority ? messages[j]->priority < messages[j + 1]->priority : messages[j]->lastTickWhenVisible < messages[j + 1]->lastTickWhenVisible)
495             {
496                 messageStruct* temp = messages[j];
497                 messages[j] = messages[j + 1];
498                 messages[j + 1] = temp;
499             }
500         }
501     }
502 }
503 
SortMessagesByTime(messageStruct ** messages,int count)504 void CGameMessageMgr::SortMessagesByTime(messageStruct** messages, int count) {
505     for (int i = 1; i < count; i++)
506     {
507         for (int j = 0; j < count - i; j++)
508         {
509             if (messages[j]->lastTickWhenVisible > messages[j + 1]->lastTickWhenVisible)
510             {
511                 messageStruct* temp = messages[j];
512                 messages[j] = messages[j + 1];
513                 messages[j + 1] = temp;
514             }
515         }
516     }
517 }
518 
Clear(void)519 void CPlayerMsg::Clear(void)
520 {
521     text[0] = 0;
522     at0 = 0;
523 }
524 
Term(void)525 void CPlayerMsg::Term(void)
526 {
527     Clear();
528     gInputMode = INPUT_MODE_0;
529 }
530 
Draw(void)531 void CPlayerMsg::Draw(void)
532 {
533     char buffer[44];
534     strcpy(buffer, text);
535     if ((int)totalclock & 16)
536         strcat(buffer, "_");
537     int x = gViewMode == 3 ? gViewX0S : 0;
538     int y = gViewMode == 3 ? gViewY0S : 0;
539     if (gViewSize >= 1)
540         y += tilesiz[2229].y*((gNetPlayers+3)/4);
541     viewDrawText(0, buffer, x+1,y+1, -128, 0, 0, false, 256);
542     viewUpdatePages();
543 }
544 
AddChar(char ch)545 bool CPlayerMsg::AddChar(char ch)
546 {
547     if (at0 < 40)
548     {
549         text[at0++] = ch;
550         text[at0] = 0;
551         return true;
552     }
553     return false;
554 }
555 
DelChar(void)556 void CPlayerMsg::DelChar(void)
557 {
558     if (at0 > 0)
559         text[--at0] = 0;
560 }
561 
Set(const char * pzString)562 void CPlayerMsg::Set(const char * pzString)
563 {
564     strncpy(text, pzString, 40);
565     at0 = ClipHigh(strlen(pzString), 40);
566     text[at0] = 0;
567 }
568 
Send(void)569 void CPlayerMsg::Send(void)
570 {
571     if (VanillaMode() || !IsWhitespaceOnly(text))
572     {
573         netBroadcastMessage(myconnectindex, text);
574         if (!VanillaMode())
575         {
576             char *myName = gProfile[myconnectindex].name;
577             char szTemp[128];
578             sprintf(szTemp, "%s: %s", myName, text);
579             viewSetMessage(szTemp, 10); // 10: dark blue
580         }
581         else
582             viewSetMessage(text);
583     }
584 
585     Term();
586     keyFlushScans();
587 }
588 
ProcessKeys(void)589 void CPlayerMsg::ProcessKeys(void)
590 {
591     int key = keyGetScan();
592     char ch;
593     if (key != 0)
594     {
595         bool UNUSED(alt) = keystatus[sc_LeftAlt] || keystatus[sc_RightAlt];
596         bool ctrl = keystatus[sc_LeftControl] || keystatus[sc_RightControl];
597         bool shift = keystatus[sc_LeftShift] || keystatus[sc_RightShift];
598         switch (key)
599         {
600         case sc_Escape:
601             Term();
602             break;
603         case sc_F1:
604         case sc_F2:
605         case sc_F3:
606         case sc_F4:
607         case sc_F5:
608         case sc_F6:
609         case sc_F7:
610         case sc_F8:
611         case sc_F9:
612         case sc_F10:
613             CONTROL_ClearButton(gamefunc_See_Chase_View);
614             Set(CommbatMacro[key-sc_F1]);
615             Send();
616             keystatus[key] = 0;
617             break;
618         case sc_BackSpace:
619             if (ctrl)
620                 Clear();
621             else
622                 DelChar();
623             break;
624         case sc_Enter:
625         case sc_kpad_Enter:
626             if (gCheatMgr.Check(text))
627                 Term();
628             else
629                 Send();
630             break;
631         default:
632             if (key < 128)
633             {
634                 ch =  shift ? g_keyAsciiTableShift[key] : g_keyAsciiTable[key];
635                 if (ch)
636                     AddChar(ch);
637             }
638             break;
639         }
640         sub_5A944(key);
641     }
642 }
643 
IsWhitespaceOnly(const char * const pzString)644 bool CPlayerMsg::IsWhitespaceOnly(const char * const pzString)
645 {
646     const char *p = pzString;
647     while (*p != 0)
648         if (*p++ > 32)
649             return false;
650     return true;
651 }
652 
653 CCheatMgr::CHEATINFO CCheatMgr::s_CheatInfo[] = {
654     {"NQLGB", kCheatMpkfa, 0 }, // MPKFA (Invincibility)
655     {"DBQJONZBTT", kCheatCapInMyAss, 0 }, // CAPINMYASS (Disable invincibility )
656     {"OPDBQJONZBTT", kCheatNoCapInMyAss, 0 }, // NOCAPINMYASS (Invincibility)
657     {"J!XBOOB!CF!MJLF!LFWJO", kCheatNoCapInMyAss, 0 }, // I WANNA BE LIKE KEVIN (Invincibility)
658     {"JEBIP", kCheatIdaho, 0 }, // IDAHO (All weapons and full ammo)
659     {"NPOUBOB", kCheatMontana, 0 }, // MONTANA (All weapons, full ammo and all items)
660     {"HSJTXPME", kCheatGriswold, 0 }, // GRISWOLD (Full armor (same effect as getting super armor))
661     {"FENBSL", kCheatEdmark, 0 }, // EDMARK (Does a lot of fire damage to you (if you have 200HP and 200 fire armor then you can survive). Displays the message "THOSE WERE THE DAYS".)
662     {"UFRVJMB", kCheatTequila, 0 }, // TEQUILA (Guns akimbo power-up)
663     {"CVO[", kCheatBunz, 0 }, // BUNZ (All weapons, full ammo, and guns akimbo power-up)
664     {"GVOLZ!TIPFT", kCheatFunkyShoes, 0 }, // FUNKY SHOES (Gives jump boots item and activates it)
665     {"HBUFLFFQFS", kCheatGateKeeper, 0 }, // GATEKEEPER (Sets the you cheated flag to true, at the end of the level you will see that you have cheated)
666     {"LFZNBTUFS", kCheatKeyMaster, 0 }, // KEYMASTER (All keys)
667     {"KPKP", kCheatJoJo, 0 }, // JOJO (Drunk mode (same effect as getting bitten by red spider))
668     {"TBUDIFM", kCheatSatchel, 0 }, // SATCHEL (Full inventory)
669     {"TQPSL", kCheatSpork, 0 }, // SPORK (200% health (same effect as getting life seed))
670     {"POFSJOH", kCheatOneRing, 0 }, // ONERING (Cloak of invisibility power-up)
671     {"NBSJP", kCheatMario, 1 }, // MARIO (Warp to level E M, e.g.: MARIO 1 3 will take you to Phantom Express)
672     {"DBMHPO", kCheatCalgon, 1 }, // CALGON (Jumps to next level or can be used like MARIO with parameters)
673     {"LFWPSLJBO", kCheatKevorkian, 0 }, // KEVORKIAN (Does a lot of physical damage to you (if you have 200HP and 200 fire armor then you can survive). Displays the message "KEVORKIAN APPROVES".)
674     {"NDHFF", kCheatMcGee, 0 }, // MCGEE (Sets you on fire. Displays the message "YOU'RE FIRED".)
675     {"LSVFHFS", kCheatKrueger, 0 }, // KRUEGER (200% health, but sets you on fire. Displays the message "FLAME RETARDANT".)
676     {"DIFFTFIFBE", kCheatCheeseHead, 0 }, // CHEESEHEAD (100% diving suit)
677     {"DPVTUFBV", kCheatCousteau, 0 }, // COUSTEAU (200% health and diving suit)
678     {"WPPSIFFT", kCheatVoorhees, 0 }, // VOORHEES (Death mask power-up)
679     {"MBSB!DSPGU", kCheatLaraCroft, 0 }, // LARA CROFT (All weapons and infinite ammo. Displays the message "LARA RULES". Typing it the second time will lose all weapons and ammo.)
680     {"IPOHLPOH", kCheatHongKong, 0 }, // HONGKONG (All weapons and infinite ammo)
681     {"GSBOLFOTUFJO", kCheatFrankenstein, 0 }, // FRANKENSTEIN (100% med-kit)
682     {"TUFSOP", kCheatSterno, 0 }, // STERNO (Temporary blindness (same effect as getting bitten by green spider))
683     {"DMBSJDF", kCheatClarice, 0 }, // CLARICE (Gives 100% body armor, 100% fire armor, 100% spirit armor)
684     {"GPSL!ZPV", kCheatForkYou, 0 }, // FORK YOU (Drunk mode, 1HP, no armor, no weapons, no ammo, no items, no keys, no map, guns akimbo power-up)
685     {"MJFCFSNBO", kCheatLieberMan, 0 }, // LIEBERMAN (Sets the you cheated flag to true, at the end of the level you will see that you have cheated)
686     {"FWB!HBMMJ", kCheatEvaGalli, 0 }, // EVA GALLI (Disable/enable clipping (grant the ability to walk through walls))
687     {"SBUF", kCheatRate, 0 }, // RATE (Display frame rate (doesn't count as a cheat))
688     {"HPPOJFT", kCheatGoonies, 0 }, // GOONIES (Enable full map. Displays the message "YOU HAVE THE MAP".)
689     {"TQJFMCFSH", kCheatSpielberg, 1 }, // SPIELBERG (Disables all cheats. If number values corresponding to a level and episode number are entered after the cheat word (i.e. "spielberg 1 3" for Phantom Express), you will be spawned to said level and the game will begin recording a demo from your actions.)
690 };
691 
692 bool CCheatMgr::m_bPlayerCheated = false;
693 
Check(char * pzString)694 bool CCheatMgr::Check(char *pzString)
695 {
696     char buffer[80];
697     strcpy(buffer, pzString);
698     Bstrupr(buffer);
699     for (size_t i = 0; i < strlen(pzString); i++)
700         buffer[i]++;
701     for (int i = 0; i < 36; i++)
702     {
703         int nCheatLen = strlen(s_CheatInfo[i].pzString);
704         if (s_CheatInfo[i].flags & 1)
705         {
706             if (!strncmp(buffer, s_CheatInfo[i].pzString, nCheatLen))
707             {
708                 Process(s_CheatInfo[i].id, buffer+nCheatLen);
709                 return true;
710             }
711         }
712         if (!strcmp(buffer, s_CheatInfo[i].pzString))
713         {
714             Process(s_CheatInfo[i].id, NULL);
715             return true;
716         }
717     }
718     return false;
719 }
720 
parseArgs(char * pzArgs,int * nArg1,int * nArg2)721 int parseArgs(char *pzArgs, int *nArg1, int *nArg2)
722 {
723     if (!nArg1 || !nArg2)
724         return -1;
725     int nLength = strlen(pzArgs);
726     for (int i = 0; i < nLength; i++)
727         pzArgs[i]--;
728     int stat = sscanf(pzArgs, " %d %d", nArg1, nArg2);
729     if (stat == 2 && (*nArg1 == 0 || *nArg2 == 0))
730         return -1;
731     *nArg1 = ClipRange(*nArg1-1, 0, gEpisodeCount-1);
732     *nArg2 = ClipRange(*nArg2-1, 0, gEpisodeInfo[*nArg1].nLevels-1);
733     return stat;
734 }
735 
Process(CCheatMgr::CHEATCODE nCheatCode,char * pzArgs)736 void CCheatMgr::Process(CCheatMgr::CHEATCODE nCheatCode, char* pzArgs)
737 {
738     dassert(nCheatCode > kCheatNone && nCheatCode < kCheatMax);
739 
740     if (gDemo.at0) return;
741     if (nCheatCode == kCheatRate)
742     {
743         gShowFps = !gShowFps;
744         return;
745     }
746     if (gGameOptions.nGameType != 0)
747         return;
748     int nEpisode, nLevel;
749     switch (nCheatCode)
750     {
751     case kCheatSpielberg:
752         if (parseArgs(pzArgs, &nEpisode, &nLevel) == 2)
753             LevelWarpAndRecord(nEpisode, nLevel);
754         break;
755     case kCheat1:
756         SetAmmo(true);
757         break;
758     case kCheatGriswold:
759         SetArmor(true);
760         break;
761     case kCheatSatchel:
762         SetToys(true);
763         break;
764     case kCheatEvaGalli:
765         SetClipMode(!gNoClip);
766         break;
767     case kCheatMpkfa:
768         SetGodMode(!gMe->godMode);
769         break;
770     case kCheatCapInMyAss:
771         SetGodMode(false);
772         break;
773     case kCheatNoCapInMyAss:
774         SetGodMode(true);
775         break;
776     case kCheatIdaho:
777         SetWeapons(true);
778         break;
779     case kCheatKevorkian:
780         actDamageSprite(gMe->nSprite, gMe->pSprite, kDamageBullet, 8000);
781         viewSetMessage("Kevorkian approves.");
782         break;
783     case kCheatMcGee:
784     {
785         if (!gMe->pXSprite->burnTime)
786             evPost(gMe->nSprite, 3, 0, kCallbackFXFlameLick);
787         actBurnSprite(actSpriteIdToOwnerId(gMe->nSprite), gMe->pXSprite, 2400);
788         viewSetMessage("You're fired!");
789         break;
790     }
791     case kCheatEdmark:
792         actDamageSprite(gMe->nSprite, gMe->pSprite, kDamageExplode, 8000);
793         viewSetMessage("Ahhh...those were the days.");
794         break;
795     case kCheatKrueger:
796     {
797         actHealDude(gMe->pXSprite, 200, 200);
798         gMe->armor[1] = VanillaMode() ? 200 : 3200;
799         if (!gMe->pXSprite->burnTime)
800             evPost(gMe->nSprite, 3, 0, kCallbackFXFlameLick);
801         actBurnSprite(actSpriteIdToOwnerId(gMe->nSprite), gMe->pXSprite, 2400);
802         viewSetMessage("Flame retardant!");
803         break;
804     }
805     case kCheatSterno:
806         gMe->blindEffect = 250;
807         break;
808     case kCheat14: // quakeEffect (causing a little flickerEffect), not used by any cheat code (dead code)
809         gMe->flickerEffect = 360;
810         break;
811     case kCheatSpork:
812         actHealDude(gMe->pXSprite, 200, 200);
813         break;
814     case kCheatGoonies:
815         SetMap(!gFullMap);
816         break;
817     case kCheatClarice:
818         if (!VanillaMode())
819         {
820             viewSetMessage("You have half armor.");
821             for (int i = 0; i < 3; i++)
822                 gMe->armor[i] = 1600;
823         }
824         break;
825     case kCheatFrankenstein:
826         gMe->packSlots[0].curAmount = 100;
827         break;
828     case kCheatCheeseHead:
829         gMe->packSlots[1].curAmount = 100;
830         if (!VanillaMode())
831             gMe->pwUpTime[kPwUpDivingSuit] = gPowerUpInfo[kPwUpDivingSuit].bonusTime;
832         break;
833     case kCheatTequila:
834         ToggleWooMode();
835         break;
836     case kCheatFunkyShoes:
837         ToggleBoots();
838         break;
839     case kCheatKeyMaster:
840         SetKeys(true);
841         break;
842     case kCheatOneRing:
843         ToggleInvisibility();
844         break;
845     case kCheatVoorhees:
846         ToggleInvulnerability();
847         break;
848     case kCheatJoJo:
849         ToggleDelirium();
850         break;
851     case kCheatRate: // show FPS, handled before (dead code), leave here for safety
852         return;
853     case kCheatMario:
854         if (parseArgs(pzArgs, &nEpisode, &nLevel) == 2)
855             LevelWarp(nEpisode, nLevel);
856         break;
857     case kCheatCalgon:
858         if (parseArgs(pzArgs, &nEpisode, &nLevel) == 2)
859             LevelWarp(nEpisode, nLevel);
860         else
861             if (!VanillaMode())
862                 levelEndLevel(kLevelExitNormal);
863         break;
864     case kCheatLaraCroft:
865         SetInfiniteAmmo(!gInfiniteAmmo);
866         SetWeapons(gInfiniteAmmo);
867         break;
868     case kCheatHongKong:
869         SetWeapons(true);
870         SetInfiniteAmmo(true);
871         break;
872     case kCheatMontana:
873         SetWeapons(true);
874         SetToys(true);
875         break;
876     case kCheatBunz:
877         SetWeapons(true);
878         SetWooMode(true);
879         break;
880     case kCheatCousteau:
881         actHealDude(gMe->pXSprite,200,200);
882         gMe->packSlots[1].curAmount = 100;
883         if (!VanillaMode())
884             gMe->pwUpTime[kPwUpDivingSuit] = gPowerUpInfo[kPwUpDivingSuit].bonusTime;
885         break;
886     case kCheatForkYou:
887         SetInfiniteAmmo(false);
888         SetMap(false);
889         SetWeapons(false);
890         SetAmmo(false);
891         SetArmor(false);
892         SetToys(false);
893         SetKeys(false);
894         SetWooMode(true);
895         powerupActivate(gMe, kPwUpDeliriumShroom);
896         gMe->pXSprite->health = 16;
897         gMe->hasWeapon[1] = 1;
898         gMe->curWeapon = 0;
899         gMe->nextWeapon = 1;
900         break;
901     default:
902         break;
903     }
904     m_bPlayerCheated = true;
905 }
906 
sub_5BCF4(void)907 void CCheatMgr::sub_5BCF4(void)
908 {
909     m_bPlayerCheated = 0;
910     playerSetGodMode(gMe, 0);
911     gNoClip = 0;
912     packClear(gMe);
913     gInfiniteAmmo = 0;
914     gFullMap = 0;
915 }
916 
917 class MessagesLoadSave : public LoadSave
918 {
919 public:
920     virtual void Load();
921     virtual void Save();
922 };
923 
Load()924 void MessagesLoadSave::Load()
925 {
926     Read(&CCheatMgr::m_bPlayerCheated, sizeof(CCheatMgr::m_bPlayerCheated));
927 }
928 
Save()929 void MessagesLoadSave::Save()
930 {
931     Write(&CCheatMgr::m_bPlayerCheated, sizeof(CCheatMgr::m_bPlayerCheated));
932 }
933 
934 static MessagesLoadSave *myLoadSave;
935 
MessagesLoadSaveConstruct(void)936 void MessagesLoadSaveConstruct(void)
937 {
938     myLoadSave = new MessagesLoadSave();
939 }
940