code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2010-2019 EDuke32 developers and contributors
Copyright (C) 2019 Nuke.YKT

This file is part of NBlood.

NBlood is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
//-------------------------------------------------------------------------
#pragma once
#include "gamemenu.h"

extern CGameMenu menuMain;
extern CGameMenu menuMainWithSave;
extern CGameMenu menuNetMain;
extern CGameMenu menuNetStart;
extern CGameMenu menuEpisode;
extern CGameMenu menuDifficulty;
extern CGameMenu menuOptionsOld;
extern CGameMenu menuControls;
extern CGameMenu menuMessages;
extern CGameMenu menuKeys;
extern CGameMenu menuSaveGame;
extern CGameMenu menuLoadGame;
extern CGameMenu menuLoading;
extern CGameMenu menuSounds;
extern CGameMenu menuQuit;
extern CGameMenu menuRestart;
extern CGameMenu menuCredits;
extern CGameMenu menuOrder;
extern CGameMenu menuPlayOnline;
extern CGameMenu menuParentalLock;
extern CGameMenu menuSorry;
extern CGameMenu menuSorry2;

extern CGameMenu menuOptions;
extern CGameMenu menuOptionsSound;
extern CGameMenu menuOptionsDisplayMode;
extern short gQuickLoadSlot;
extern short gQuickSaveSlot;
extern char strRestoreGameStrings[][16];
extern char restoreGameDifficulty[];
extern const char *zDiffStrings[];
extern char zNetAddressBuffer[16];
extern char zNetPortBuffer[6];
extern char zUserMapName[BMAX_PATH];
void drawLoadingScreen(void);
void SetupMenus(void);
void UpdateNetworkMenus(void);
void QuickSaveGame(void);
void QuickLoadGame(void);

repo_name: orochi/nblood

license: mit


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2010-2019 EDuke32 developers and contributors
Copyright (C) 2019 Nuke.YKT

This file is part of NBlood.

NBlood is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
//-------------------------------------------------------------------------
#include "build.h"
#include "mmulti.h"
#include "compat.h"
#include "keyboard.h"
#include "control.h"
#include "function.h"
#include "common_game.h"
#include "blood.h"
#include "config.h"
#include "demo.h"
#include "eventq.h"
#include "globals.h"
#include "levels.h"
#include "loadsave.h"
#include "menu.h"
#include "messages.h"
#include "network.h"
#include "player.h"
#include "view.h"

CPlayerMsg gPlayerMsg;
CCheatMgr gCheatMgr;

void sub_5A928(void)
{
    for (int i = 0; i < NUMGAMEFUNCTIONS-1; i++)
        CONTROL_ClearButton(i);
}

extern uint8_t KeyboardKeys[NUMGAMEFUNCTIONS][2];
void sub_5A944(char key)
{
    for (int i = 0; i < NUMGAMEFUNCTIONS-1; i++)
    {
        char key1, key2;
        key1 = KeyboardKeys[i][0];
        key2 = KeyboardKeys[i][1];
        if (key1 == key || key2 == key)
            CONTROL_ClearButton(i);
    }
}

void SetGodMode(bool god)
{
    playerSetGodMode(gMe, god);
    if (gMe->godMode)
        viewSetMessage("You are immortal.");
    else
        viewSetMessage("You are mortal.");
}

void SetClipMode(bool noclip)
{
    gNoClip = noclip;
    if (gNoClip)
        viewSetMessage("Unclipped movement.");
    else
        viewSetMessage("Normal movement.");
}

void packStuff(PLAYER *pPlayer)
{
    for (int i = 0; i < 5; i++)
        packAddItem(pPlayer, i);
}

void packClear(PLAYER *pPlayer)
{
    pPlayer->packItemId = 0;
    for (int i = 0; i < 5; i++)
    {
        pPlayer->packSlots[i].isActive = 0;
        pPlayer->packSlots[i].curAmount = 0;
    }
}

void SetAmmo(bool stat)
{
    if (stat)
    {
        for (int i = 0; i < 12; i++)
            gMe->ammoCount[i] = gAmmoInfo[i].max;
        viewSetMessage("You have full ammo.");
    }
    else
    {
        for (int i = 0; i < 12; i++)
            gMe->ammoCount[i] = 0;
        viewSetMessage("You have no ammo.");
    }
}

void SetWeapons(bool stat)
{
    for (int i = 0; i < kWeaponMax; i++)
    {
        gMe->hasWeapon[i] = stat;
    }
    SetAmmo(stat);
    if (stat)
        viewSetMessage("You have all weapons.");
    else
    {
        if (!VanillaMode())
        {
            // Keep the pitchfork to avoid freeze
            gMe->hasWeapon[kWeaponPitchfork] = 1;
            gMe->curWeapon = kWeaponNone;
            gMe->nextWeapon = kWeaponPitchfork;
        }
        viewSetMessage("You have no weapons.");
    }
}

void SetToys(bool stat)
{
    if (stat)
    {
        packStuff(gMe);
        viewSetMessage("Your inventory is full.");
    }
    else
    {
        packClear(gMe);
        viewSetMessage("Your inventory is empty.");
    }
}

void SetArmor(bool stat)
{
    int nAmount;
    if (stat)
    {
        viewSetMessage("You have full armor.");
        nAmount = 3200;
    }
    else
    {
        viewSetMessage("You have no armor.");
        nAmount = 0;
    }
    for (int i = 0; i < 3; i++)
        gMe->armor[i] = nAmount;
}

void SetKeys(bool stat)
{
    for (int i = 1; i <= 6; i++)
        gMe->hasKey[i] = stat;
    if (stat)
        viewSetMessage("You have all keys.");
    else
        viewSetMessage("You have no keys.");
}

void SetInfiniteAmmo(bool stat)
{
    gInfiniteAmmo = stat;
    if (gInfiniteAmmo)
        viewSetMessage("You have infinite ammo.");
    else
        viewSetMessage("You have limited ammo.");
}

void SetMap(bool stat)
{
    gFullMap = stat;
    if (gFullMap)
        viewSetMessage("You have the map.");
    else
        viewSetMessage("You have no map.");
}

void SetWooMode(bool stat)
{
    if (stat)
    {
        if (!powerupCheck(gMe, kPwUpTwoGuns))
            powerupActivate(gMe, kPwUpTwoGuns);
    }
    else
    {
        if (powerupCheck(gMe, kPwUpTwoGuns))
        {
            if (!VanillaMode())
                gMe->pwUpTime[kPwUpTwoGuns] = 0;
            powerupDeactivate(gMe, kPwUpTwoGuns);
        }
    }
}

void ToggleWooMode(void)
{
    SetWooMode(!(powerupCheck(gMe, kPwUpTwoGuns) != 0));
}

void ToggleBoots(void)
{
    if (powerupCheck(gMe, kPwUpJumpBoots))
    {
        viewSetMessage("You have no Jumping Boots.");
        if (!VanillaMode())
        {
            gMe->pwUpTime[kPwUpJumpBoots] = 0;
            gMe->packSlots[4].curAmount = 0;
        }
        powerupDeactivate(gMe, kPwUpJumpBoots);
    }
    else
    {
        viewSetMessage("You have the Jumping Boots.");
        if (!VanillaMode())
            gMe->pwUpTime[kPwUpJumpBoots] = gPowerUpInfo[kPwUpJumpBoots].bonusTime;
        powerupActivate(gMe, kPwUpJumpBoots);
    }
}

void ToggleInvisibility(void)
{
    if (powerupCheck(gMe, kPwUpShadowCloak))
    {
        viewSetMessage("You are visible.");
        if (!VanillaMode())
            gMe->pwUpTime[kPwUpShadowCloak] = 0;
        powerupDeactivate(gMe, kPwUpShadowCloak);
    }
    else
    {
        viewSetMessage("You are invisible.");
        powerupActivate(gMe, kPwUpShadowCloak);
    }
}

void ToggleInvulnerability(void)
{
    if (powerupCheck(gMe, kPwUpDeathMask))
    {
        viewSetMessage("You are vulnerable.");
        if (!VanillaMode())
            gMe->pwUpTime[kPwUpDeathMask] = 0;
        powerupDeactivate(gMe, kPwUpDeathMask);
    }
    else
    {
        viewSetMessage("You are invulnerable.");
        powerupActivate(gMe, kPwUpDeathMask);
    }
}

void ToggleDelirium(void)
{
    if (powerupCheck(gMe, kPwUpDeliriumShroom))
    {
        viewSetMessage("You are not delirious.");
        if (!VanillaMode())
            gMe->pwUpTime[kPwUpDeliriumShroom] = 0;
        powerupDeactivate(gMe, kPwUpDeliriumShroom);
    }
    else
    {
        viewSetMessage("You are delirious.");
        powerupActivate(gMe, kPwUpDeliriumShroom);
    }
}

void LevelWarp(int nEpisode, int nLevel)
{
    levelSetupOptions(nEpisode, nLevel);
    StartLevel(&gGameOptions);
    viewResizeView(gViewSize);
}

void LevelWarpAndRecord(int nEpisode, int nLevel)
{
    char buffer[BMAX_PATH];
    levelSetupOptions(nEpisode, nLevel);
    gGameOptions.uGameFlags = kGameFlagNone;
    gGameStarted = false;
    gCheatMgr.ResetCheats();
    strcpy(buffer, levelGetFilename(nEpisode, nLevel));
    ChangeExtension(buffer, ".DEM");
    gDemo.Create(buffer);
    StartLevel(&gGameOptions);
    viewResizeView(gViewSize);
}

CGameMessageMgr::CGameMessageMgr()
{
    if (!VanillaMode())
        Clear();
    x = 1;
    y = 0;
    at9 = 0;
    atd = 0;
    nFont = 0;
    fontHeight = 8;
    maxNumberOfMessagesToDisplay = 4;
    visibilityDurationInSecs = 5;
    messageFlags = 15;
    numberOfDisplayedMessages = 0;
    nextMessagesIndex = messagesIndex = 0;
}

void CGameMessageMgr::SetState(char state)
{
    if (this->state && !state)
    {
        this->state = 0;
        Clear();
    }
    else if (!this->state && state)
        this->state = 1;
}

void CGameMessageMgr::Add(const char *pText, char a2, const int pal, const MESSAGE_PRIORITY priority)
{
    if (a2 && messageFlags)
    {
        messageStruct *pMessage = &messages[nextMessagesIndex];
        strncpy(pMessage->text, pText, kMaxMessageTextLength-1);
        pMessage->text[kMaxMessageTextLength-1] = 0;
        pMessage->lastTickWhenVisible = gFrameClock + visibilityDurationInSecs*kTicRate;
        pMessage->pal = pal;
        pMessage->priority = priority;
        pMessage->deleted = false;
        nextMessagesIndex = (nextMessagesIndex+1)%kMessageLogSize;
        if (VanillaMode())
        {
            numberOfDisplayedMessages++;
            if (numberOfDisplayedMessages > maxNumberOfMessagesToDisplay)
            {
                messagesIndex = (messagesIndex+1)%kMessageLogSize;
                atd = 0;
                numberOfDisplayedMessages = maxNumberOfMessagesToDisplay;
                at9 = fontHeight;
            }
        }
    }
}

void CGameMessageMgr::Display(void)
{
    if (VanillaMode())
    {
        if (numberOfDisplayedMessages && this->state && gInputMode != INPUT_MODE_2)
        {
            int initialNrOfDisplayedMsgs = numberOfDisplayedMessages;
            int initialMessagesIndex = messagesIndex;
            int shade = ClipHigh(initialNrOfDisplayedMsgs*8, 48);
            int x = gViewMode == 3 ? gViewX0S : 0;
            int y = (gViewMode == 3 ? this->y : 0) + (int)at9;
            for (int i = 0; i < initialNrOfDisplayedMsgs; i++)
            {
                messageStruct* pMessage = &messages[(initialMessagesIndex+i)%kMessageLogSize];
                if (pMessage->lastTickWhenVisible <= gFrameClock)
                {
                    messagesIndex = (messagesIndex+1)%kMessageLogSize;
                    numberOfDisplayedMessages--;
                    continue;
                }
                viewDrawText(nFont, pMessage->text, x+1, y, shade, pMessage->pal, 0, false, 256);
                if (gViewMode == 3)
                {
                    int height;
                    gMenuTextMgr.GetFontInfo(nFont, pMessage->text, &height, NULL);
                    if (x+height > gViewX1S)
                        viewUpdatePages();
                }
                y += fontHeight;
                shade = ClipLow(shade-64/initialNrOfDisplayedMsgs, -128);
            }
        }
    }
    else
    {
        if (this->state && gInputMode != INPUT_MODE_2)
        {
            messageStruct* currentMessages[kMessageLogSize];
            int currentMessagesCount = 0;
            for (int i = 0; i < kMessageLogSize; i++)
            {
                messageStruct* pMessage = &messages[i];
                if (gFrameClock < pMessage->lastTickWhenVisible && !pMessage->deleted)
                {
                    currentMessages[currentMessagesCount++] = pMessage;
                }
            }

            SortMessagesByPriority(currentMessages, currentMessagesCount);

            messageStruct* messagesToDisplay[kMessageLogSize];
            int messagesToDisplayCount = 0;
            for (int i = 0; i < currentMessagesCount && messagesToDisplayCount < maxNumberOfMessagesToDisplay; i++)
            {
                messagesToDisplay[messagesToDisplayCount++] = currentMessages[i];
            }

            SortMessagesByTime(messagesToDisplay, messagesToDisplayCount);

            int shade = ClipHigh(messagesToDisplayCount*8, 48);
            int x = gViewMode == 3 ? gViewX0S : 0;
            int y = (gViewMode == 3 ? this->y : 0) + (int)at9;
            for (int i = 0; i < messagesToDisplayCount; i++)
            {
                messageStruct* pMessage = messagesToDisplay[i];
                viewDrawText(nFont, pMessage->text, x+1, y, shade, pMessage->pal, 0, false, 256);
                if (gViewMode == 3)
                {
                    int height;
                    gMenuTextMgr.GetFontInfo(nFont, pMessage->text, &height, NULL);
                    if (x+height > gViewX1S)
                        viewUpdatePages();
                }
                y += fontHeight;
                shade = ClipLow(shade-64/messagesToDisplayCount, -128);
            }
        }
    }
    if (at9 != 0)
    {
        at9 = fontHeight*atd/kTicRate;
        atd += gFrameTicks;
    }
}

void CGameMessageMgr::Clear(void)
{
    if (VanillaMode())
    {
        messagesIndex = nextMessagesIndex = numberOfDisplayedMessages = 0;
    }
    else
    {
        for (int i = 0; i < kMessageLogSize; i++)
        {
            messageStruct* pMessage = &messages[i];
            pMessage->deleted = true;
        }
    }
}

void CGameMessageMgr::SetMaxMessages(int nMessages)
{
    maxNumberOfMessagesToDisplay = ClipRange(nMessages, 1, 16);
}

void CGameMessageMgr::SetFont(int nFont)
{
    this->nFont = nFont;
    fontHeight = gFont[nFont].ySize;
}

void CGameMessageMgr::SetCoordinates(int x, int y)
{
    this->x = ClipRange(x, 0, gViewX1S);
    this->y = ClipRange(y, 0, gViewY1S);
}

void CGameMessageMgr::SetMessageTime(int nTime)
{
    visibilityDurationInSecs = ClipRange(nTime, 1, 8);
}

void CGameMessageMgr::SetMessageFlags(unsigned int nFlags)
{
    messageFlags = nFlags&0xf;
}

void CGameMessageMgr::SortMessagesByPriority(messageStruct** messages, int count) {
    for (int i = 1; i < count; i++)
    {
        for (int j = 0; j < count - i; j++)
        {
            if (messages[j]->priority != messages[j + 1]->priority ? messages[j]->priority < messages[j + 1]->priority : messages[j]->lastTickWhenVisible < messages[j + 1]->lastTickWhenVisible)
            {
                messageStruct* temp = messages[j];
                messages[j] = messages[j + 1];
                messages[j + 1] = temp;
            }
        }
    }
}

void CGameMessageMgr::SortMessagesByTime(messageStruct** messages, int count) {
    for (int i = 1; i < count; i++)
    {
        for (int j = 0; j < count - i; j++)
        {
            if (messages[j]->lastTickWhenVisible > messages[j + 1]->lastTickWhenVisible)
            {
                messageStruct* temp = messages[j];
                messages[j] = messages[j + 1];
                messages[j + 1] = temp;
            }
        }
    }
}

void CPlayerMsg::Clear(void)
{
    text[0] = 0;
    at0 = 0;
}

void CPlayerMsg::Term(void)
{
    Clear();
    gInputMode = INPUT_MODE_0;
}

void CPlayerMsg::Draw(void)
{
    char buffer[44];
    strcpy(buffer, text);
    if ((int)totalclock & 16)
        strcat(buffer, "_");
    int x = gViewMode == 3 ? gViewX0S : 0;
    int y = gViewMode == 3 ? gViewY0S : 0;
    if (gViewSize >= 1)
        y += tilesiz[2229].y*((gNetPlayers+3)/4);
    viewDrawText(0, buffer, x+1,y+1, -128, 0, 0, false, 256);
    viewUpdatePages();
}

bool CPlayerMsg::AddChar(char ch)
{
    if (at0 < 40)
    {
        text[at0++] = ch;
        text[at0] = 0;
        return true;
    }
    return false;
}

void CPlayerMsg::DelChar(void)
{
    if (at0 > 0)
        text[--at0] = 0;
}

void CPlayerMsg::Set(const char * pzString)
{
    strncpy(text, pzString, 40);
    at0 = ClipHigh(strlen(pzString), 40);
    text[at0] = 0;
}

void CPlayerMsg::Send(void)
{
    if (VanillaMode() || !IsWhitespaceOnly(text))
    {
        netBroadcastMessage(myconnectindex, text);
        if (!VanillaMode())
        {
            char *myName = gProfile[myconnectindex].name;
            char szTemp[128];
            sprintf(szTemp, "%s: %s", myName, text);
            viewSetMessage(szTemp, 10); // 10: dark blue
        }
        else
            viewSetMessage(text);
    }

    Term();
    keyFlushScans();
}

void CPlayerMsg::ProcessKeys(void)
{
    int key = keyGetScan();
    char ch;
    if (key != 0)
    {
        bool UNUSED(alt) = keystatus[sc_LeftAlt] || keystatus[sc_RightAlt];
        bool ctrl = keystatus[sc_LeftControl] || keystatus[sc_RightControl];
        bool shift = keystatus[sc_LeftShift] || keystatus[sc_RightShift];
        switch (key)
        {
        case sc_Escape:
            Term();
            break;
        case sc_F1:
        case sc_F2:
        case sc_F3:
        case sc_F4:
        case sc_F5:
        case sc_F6:
        case sc_F7:
        case sc_F8:
        case sc_F9:
        case sc_F10:
            CONTROL_ClearButton(gamefunc_See_Chase_View);
            Set(CommbatMacro[key-sc_F1]);
            Send();
            keystatus[key] = 0;
            break;
        case sc_BackSpace:
            if (ctrl)
                Clear();
            else
                DelChar();
            break;
        case sc_Enter:
        case sc_kpad_Enter:
            if ((gGameOptions.nGameType == kGameTypeSinglePlayer) && gCheatMgr.Check(text))
                Term();
            else
                Send();
            break;
        default:
            if (key < 128)
            {
                ch =  shift ? g_keyAsciiTableShift[key] : g_keyAsciiTable[key];
                if (ch)
                    AddChar(ch);
            }
            break;
        }
        sub_5A944(key);
    }
}

bool CPlayerMsg::IsWhitespaceOnly(const char * const pzString)
{
    const char *p = pzString;
    while (*p != 0)
        if (*p++ > 32)
            return false;
    return true;
}

CCheatMgr::CHEATINFO CCheatMgr::s_CheatInfo[] = {
    {"NQLGB", kCheatMpkfa, 0 }, // MPKFA (Invincibility)
    {"DBQJONZBTT", kCheatCapInMyAss, 0 }, // CAPINMYASS (Disable invincibility )
    {"OPDBQJONZBTT", kCheatNoCapInMyAss, 0 }, // NOCAPINMYASS (Invincibility)
    {"J!XBOOB!CF!MJLF!LFWJO", kCheatNoCapInMyAss, 0 }, // I WANNA BE LIKE KEVIN (Invincibility)
    {"JEBIP", kCheatIdaho, 0 }, // IDAHO (All weapons and full ammo)
    {"NPOUBOB", kCheatMontana, 0 }, // MONTANA (All weapons, full ammo and all items)
    {"HSJTXPME", kCheatGriswold, 0 }, // GRISWOLD (Full armor (same effect as getting super armor))
    {"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".)
    {"UFRVJMB", kCheatTequila, 0 }, // TEQUILA (Guns akimbo power-up)
    {"CVO[", kCheatBunz, 0 }, // BUNZ (All weapons, full ammo, and guns akimbo power-up)
    {"GVOLZ!TIPFT", kCheatFunkyShoes, 0 }, // FUNKY SHOES (Gives jump boots item and activates it)
    {"HBUFLFFQFS", kCheatGateKeeper, 0 }, // GATEKEEPER (Sets the you cheated flag to true, at the end of the level you will see that you have cheated)
    {"LFZNBTUFS", kCheatKeyMaster, 0 }, // KEYMASTER (All keys)
    {"KPKP", kCheatJoJo, 0 }, // JOJO (Drunk mode (same effect as getting bitten by red spider))
    {"TBUDIFM", kCheatSatchel, 0 }, // SATCHEL (Full inventory)
    {"TQPSL", kCheatSpork, 0 }, // SPORK (200% health (same effect as getting life seed))
    {"POFSJOH", kCheatOneRing, 0 }, // ONERING (Cloak of invisibility power-up)
    {"NBSJP", kCheatMario, 1 }, // MARIO (Warp to level E M, e.g.: MARIO 1 3 will take you to Phantom Express)
    {"DBMHPO", kCheatCalgon, 1 }, // CALGON (Jumps to next level or can be used like MARIO with parameters)
    {"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".)
    {"NDHFF", kCheatMcGee, 0 }, // MCGEE (Sets you on fire. Displays the message "YOU'RE FIRED".)
    {"LSVFHFS", kCheatKrueger, 0 }, // KRUEGER (200% health, but sets you on fire. Displays the message "FLAME RETARDANT".)
    {"DIFFTFIFBE", kCheatCheeseHead, 0 }, // CHEESEHEAD (100% diving suit)
    {"DPVTUFBV", kCheatCousteau, 0 }, // COUSTEAU (200% health and diving suit)
    {"WPPSIFFT", kCheatVoorhees, 0 }, // VOORHEES (Death mask power-up)
    {"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.)
    {"IPOHLPOH", kCheatHongKong, 0 }, // HONGKONG (All weapons and infinite ammo)
    {"GSBOLFOTUFJO", kCheatFrankenstein, 0 }, // FRANKENSTEIN (100% med-kit)
    {"TUFSOP", kCheatSterno, 0 }, // STERNO (Temporary blindness (same effect as getting bitten by green spider))
    {"DMBSJDF", kCheatClarice, 0 }, // CLARICE (Gives 100% body armor, 100% fire armor, 100% spirit armor)
    {"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)
    {"MJFCFSNBO", kCheatLieberMan, 0 }, // LIEBERMAN (Sets the you cheated flag to true, at the end of the level you will see that you have cheated)
    {"FWB!HBMMJ", kCheatEvaGalli, 0 }, // EVA GALLI (Disable/enable clipping (grant the ability to walk through walls))
    {"SBUF", kCheatRate, 0 }, // RATE (Display frame rate (doesn't count as a cheat))
    {"HPPOJFT", kCheatGoonies, 0 }, // GOONIES (Enable full map. Displays the message "YOU HAVE THE MAP".)
    {"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.)
};

bool CCheatMgr::m_bPlayerCheated = false;

bool CCheatMgr::Check(char *pzString)
{
    char buffer[80];
    strcpy(buffer, pzString);
    Bstrupr(buffer);
    for (size_t i = 0; i < strlen(pzString); i++)
        buffer[i]++;
    for (int i = 0; i < 36; i++)
    {
        int nCheatLen = strlen(s_CheatInfo[i].pzString);
        if (s_CheatInfo[i].flags & 1)
        {
            if (!strncmp(buffer, s_CheatInfo[i].pzString, nCheatLen))
            {
                Process(s_CheatInfo[i].id, buffer+nCheatLen);
                return true;
            }
        }
        if (!strcmp(buffer, s_CheatInfo[i].pzString))
        {
            Process(s_CheatInfo[i].id, NULL);
            return true;
        }
    }
    return false;
}

int parseArgs(char *pzArgs, int *nArg1, int *nArg2)
{
    if (!nArg1 || !nArg2)
        return -1;
    int nLength = strlen(pzArgs);
    for (int i = 0; i < nLength; i++)
        pzArgs[i]--;
    int stat = sscanf(pzArgs, " %d %d", nArg1, nArg2);
    if (stat == 2 && (*nArg1 == 0 || *nArg2 == 0))
        return -1;
    *nArg1 = ClipRange(*nArg1-1, 0, gEpisodeCount-1);
    *nArg2 = ClipRange(*nArg2-1, 0, gEpisodeInfo[*nArg1].nLevels-1);
    return stat;
}

void CCheatMgr::Process(CCheatMgr::CHEATCODE nCheatCode, char* pzArgs)
{
    dassert(nCheatCode > kCheatNone && nCheatCode < kCheatMax);

    if (gDemo.at0) return;
    if (nCheatCode == kCheatRate)
    {
        gShowFps = !gShowFps;
        return;
    }
    if (gGameOptions.nGameType != kGameTypeSinglePlayer)
        return;
    int nEpisode, nLevel;
    switch (nCheatCode)
    {
    case kCheatSpielberg:
        if (parseArgs(pzArgs, &nEpisode, &nLevel) == 2)
            LevelWarpAndRecord(nEpisode, nLevel);
        break;
    case kCheat1:
        SetAmmo(true);
        break;
    case kCheatGriswold:
        SetArmor(true);
        break;
    case kCheatSatchel:
        SetToys(true);
        break;
    case kCheatEvaGalli:
        SetClipMode(!gNoClip);
        break;
    case kCheatMpkfa:
        SetGodMode(!gMe->godMode);
        break;
    case kCheatCapInMyAss:
        SetGodMode(false);
        break;
    case kCheatNoCapInMyAss:
        SetGodMode(true);
        break;
    case kCheatIdaho:
        SetWeapons(true);
        break;
    case kCheatKevorkian:
        actDamageSprite(gMe->nSprite, gMe->pSprite, kDamageBullet, 8000);
        viewSetMessage("Kevorkian approves.");
        break;
    case kCheatMcGee:
    {
        if (!gMe->pXSprite->burnTime)
            evPost(gMe->nSprite, 3, 0, kCallbackFXFlameLick);
        actBurnSprite(actSpriteIdToOwnerId(gMe->nSprite), gMe->pXSprite, 2400);
        viewSetMessage("You're fired!");
        break;
    }
    case kCheatEdmark:
        actDamageSprite(gMe->nSprite, gMe->pSprite, kDamageExplode, 8000);
        viewSetMessage("Ahhh...those were the days.");
        break;
    case kCheatKrueger:
    {
        actHealDude(gMe->pXSprite, 200, 200);
        gMe->armor[1] = VanillaMode() ? 200 : 3200;
        if (!gMe->pXSprite->burnTime)
            evPost(gMe->nSprite, 3, 0, kCallbackFXFlameLick);
        actBurnSprite(actSpriteIdToOwnerId(gMe->nSprite), gMe->pXSprite, 2400);
        viewSetMessage("Flame retardant!");
        break;
    }
    case kCheatSterno:
        gMe->blindEffect = 250;
        break;
    case kCheat14: // quakeEffect (causing a little flickerEffect), not used by any cheat code (dead code)
        gMe->flickerEffect = 360;
        break;
    case kCheatSpork:
        actHealDude(gMe->pXSprite, 200, 200);
        break;
    case kCheatGoonies:
        SetMap(!gFullMap);
        break;
    case kCheatClarice:
        if (!VanillaMode())
        {
            viewSetMessage("You have half armor.");
            for (int i = 0; i < 3; i++)
                gMe->armor[i] = 1600;
        }
        break;
    case kCheatFrankenstein:
        gMe->packSlots[0].curAmount = 100;
        break;
    case kCheatCheeseHead:
        gMe->packSlots[1].curAmount = 100;
        if (!VanillaMode())
            gMe->pwUpTime[kPwUpDivingSuit] = gPowerUpInfo[kPwUpDivingSuit].bonusTime;
        break;
    case kCheatTequila:
        ToggleWooMode();
        break;
    case kCheatFunkyShoes:
        ToggleBoots();
        break;
    case kCheatKeyMaster:
        SetKeys(true);
        break;
    case kCheatOneRing:
        ToggleInvisibility();
        break;
    case kCheatVoorhees:
        ToggleInvulnerability();
        break;
    case kCheatJoJo:
        ToggleDelirium();
        break;
    case kCheatRate: // show FPS, handled before (dead code), leave here for safety
        return;
    case kCheatMario:
        if (parseArgs(pzArgs, &nEpisode, &nLevel) == 2)
            LevelWarp(nEpisode, nLevel);
        break;
    case kCheatCalgon:
        if (parseArgs(pzArgs, &nEpisode, &nLevel) == 2)
            LevelWarp(nEpisode, nLevel);
        else
            if (!VanillaMode())
                levelEndLevel(kLevelExitNormal);
        break;
    case kCheatLaraCroft:
        SetInfiniteAmmo(!gInfiniteAmmo);
        SetWeapons(gInfiniteAmmo);
        break;
    case kCheatHongKong:
        SetWeapons(true);
        SetInfiniteAmmo(true);
        break;
    case kCheatMontana:
        SetWeapons(true);
        SetToys(true);
        break;
    case kCheatBunz:
        SetWeapons(true);
        SetWooMode(true);
        break;
    case kCheatCousteau:
        actHealDude(gMe->pXSprite,200,200);
        gMe->packSlots[1].curAmount = 100;
        if (!VanillaMode())
            gMe->pwUpTime[kPwUpDivingSuit] = gPowerUpInfo[kPwUpDivingSuit].bonusTime;
        break;
    case kCheatForkYou:
        SetInfiniteAmmo(false);
        SetMap(false);
        SetWeapons(false);
        SetAmmo(false);
        SetArmor(false);
        SetToys(false);
        SetKeys(false);
        SetWooMode(true);
        powerupActivate(gMe, kPwUpDeliriumShroom);
        gMe->pXSprite->health = 16;
        gMe->hasWeapon[kWeaponPitchfork] = 1;
        gMe->curWeapon = kWeaponNone;
        gMe->nextWeapon = kWeaponPitchfork;
        break;
    default:
        break;
    }
    m_bPlayerCheated = true;
}

void CCheatMgr::ResetCheats(void)
{
    m_bPlayerCheated = 0;
    playerSetGodMode(gMe, 0);
    gNoClip = 0;
    packClear(gMe);
    gInfiniteAmmo = 0;
    gFullMap = 0;
}

class MessagesLoadSave : public LoadSave
{
public:
    virtual void Load();
    virtual void Save();
};

void MessagesLoadSave::Load()
{
    Read(&CCheatMgr::m_bPlayerCheated, sizeof(CCheatMgr::m_bPlayerCheated));
}

void MessagesLoadSave::Save()
{
    Write(&CCheatMgr::m_bPlayerCheated, sizeof(CCheatMgr::m_bPlayerCheated));
}

static MessagesLoadSave *myLoadSave;

void MessagesLoadSaveConstruct(void)
{
    myLoadSave = new MessagesLoadSave();
}


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2010-2019 EDuke32 developers and contributors
Copyright (C) 2019 Nuke.YKT

This file is part of NBlood.

NBlood is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
//-------------------------------------------------------------------------
#pragma once

#include "compat.h"
#include "build.h"
#include "player.h"

#define kMessageLogSize 32
#define kMaxMessageTextLength 81

enum MESSAGE_PRIORITY {
    MESSAGE_PRIORITY_PICKUP = -10,
    MESSAGE_PRIORITY_NORMAL = 0,
    MESSAGE_PRIORITY_SECRET = 10,
    MESSAGE_PRIORITY_INI = 20,
    MESSAGE_PRIORITY_SYSTEM = 100
};

class CGameMessageMgr
{
public:
    struct messageStruct
    {
        ClockTicks lastTickWhenVisible;
        char text[kMaxMessageTextLength];
        int pal;
        MESSAGE_PRIORITY priority;
        bool deleted = false;
    };
    char state;
    int x;
    int y;
    ClockTicks at9;
    ClockTicks atd;
    int nFont;
    int fontHeight;
    int maxNumberOfMessagesToDisplay;
    int visibilityDurationInSecs;
    char messageFlags;
    int numberOfDisplayedMessages;
    int messagesIndex;
    int nextMessagesIndex;
    messageStruct messages[kMessageLogSize];
    CGameMessageMgr();
    void SetState(char state);
    void Add(const char *pText, char a2, const int pal = 0, const MESSAGE_PRIORITY priority = MESSAGE_PRIORITY_NORMAL);
    void Display(void);
    void Clear();
    void SetMaxMessages(int nMessages);
    void SetFont(int nFont);
    void SetCoordinates(int x, int y);
    void SetMessageTime(int nTime);
    void SetMessageFlags(unsigned int nFlags);
private:
    void SortMessagesByPriority(messageStruct** messages, int count);
    void SortMessagesByTime(messageStruct** messages, int count);
};

class CPlayerMsg
{
public:
    int at0;
    char text[41];
    CPlayerMsg() { at0 = 0; text[0] = 0; }
    void Clear(void);
    void Term(void);
    void Draw(void);
    bool AddChar(char);
    void DelChar(void);
    void Set(const char *pzString);
    void Send(void);
    void ProcessKeys(void);
private:
    bool IsWhitespaceOnly(const char* const pzString);
};

class CCheatMgr
{
public:
    static bool m_bPlayerCheated;
    enum CHEATCODE
    {
        kCheatNone = 0,
        kCheat1, // refills ammo, no cheat code for it
        kCheatGriswold,
        kCheatSatchel,
        kCheatEvaGalli,
        kCheatMpkfa,
        kCheatCapInMyAss,
        kCheatNoCapInMyAss,
        kCheatIdaho,
        kCheatKevorkian,
        kCheatMcGee,
        kCheatEdmark,
        kCheatKrueger,
        kCheatSterno,
        kCheat14, // quake effect, not used
        kCheatSpork,
        kCheatGoonies,
        kCheatClarice,
        kCheatFrankenstein,
        kCheatCheeseHead,
        kCheatTequila,
        kCheatFunkyShoes,
        kCheatKeyMaster,
        kCheatOneRing,
        kCheatVoorhees,
        kCheatJoJo,
        kCheatGateKeeper,
        kCheatRate,
        kCheatMario,
        kCheatLaraCroft,
        kCheatHongKong,
        kCheatMontana,
        kCheatBunz,
        kCheatCousteau,
        kCheatForkYou,
        kCheatLieberMan,
        kCheatSpielberg,
        kCheatCalgon,
        kCheatMax
    };
    struct CHEATINFO
    {
        const char* pzString;
        CHEATCODE id;
        int flags;
    };
    static CHEATINFO s_CheatInfo[];
    CCheatMgr() {}
    bool Check(char *pzString);
    void Process(CHEATCODE nCheatCode, char* pzArgs);
    void ResetCheats(void);
};

extern CPlayerMsg gPlayerMsg;
extern CCheatMgr gCheatMgr;

void SetAmmo(bool stat);
void SetWeapons(bool stat);
void SetToys(bool stat);
void SetArmor(bool stat);
void SetKeys(bool stat);
void SetGodMode(bool god);
void SetClipMode(bool noclip);


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2010-2019 EDuke32 developers and contributors
Copyright (C) 2019 Nuke.YKT

This file is part of NBlood.

NBlood is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
//-------------------------------------------------------------------------
#include "build.h"
#include "compat.h"
#ifdef POLYMER
#include "polymer.h"
#endif
#include "common_game.h"
#include "blood.h"
#include "db.h"
#include "gameutil.h"
#include "loadsave.h"
#include "player.h"
#include "trig.h"
#include "view.h"
#include "warp.h"

int mirrorcnt, mirrorsector, mirrorwall[4];

typedef struct
{
    int at0;
    int at4;
    int at8;
    int atc;
    int at10;
    int at14;
} MIRROR;

MIRROR mirror[16];

#ifdef POLYMER
void PolymerRORCallback(int16_t sectnum, int16_t wallnum, int8_t rorstat, int16_t* msectnum, int32_t* gx, int32_t* gy, int32_t* gz)
{
    UNREFERENCED_PARAMETER(wallnum);
    int nMirror;
    switch (rorstat)
    {
    case 1:
        nMirror = sector[sectnum].ceilingpicnum-4080;
        *msectnum = mirror[nMirror].at4;
        *gx += mirror[nMirror].at8;
        *gy += mirror[nMirror].atc;
        *gz += mirror[nMirror].at10;
        break;
    case 2:
        nMirror = sector[sectnum].floorpicnum-4080;
        *msectnum = mirror[nMirror].at4;
        *gx += mirror[nMirror].at8;
        *gy += mirror[nMirror].atc;
        *gz += mirror[nMirror].at10;
        break;
    }
}
#endif

void InitMirrors(void)
{

#ifdef USE_OPENGL
    r_rortexture = 4080;
    r_rortexturerange = 16;
#ifdef  POLYMER
    polymer_setrorcallback(PolymerRORCallback);
#endif //  POLYMER

#endif
    mirrorcnt = 0;
    tilesiz[504].x = 0;
    tilesiz[504].y = 0;
    tileDelete(504);

    for(int i = 0; i < 16; i++)
        tilesiz[4080+i].x = 0, tilesiz[4080+i].y = 0;
    for (int i = numwalls - 1; i >= 0; i--)
    {
        if (mirrorcnt == 16)
            break;
        int nTile = 4080+mirrorcnt;
        if (wall[i].overpicnum == 504)
        {
            if (wall[i].extra > 0 && GetWallType(i) == kWallStack)
            {
                wall[i].overpicnum = nTile;
                mirror[mirrorcnt].at14 = i;
                mirror[mirrorcnt].at0 = 0;
                wall[i].cstat |= 32;
                int tmp = xwall[wall[i].extra].data;
                int j;
                for (j = numwalls - 1; j >= 0; j--)
                {
                    if (j == i)
                        continue;
                    if (wall[j].extra > 0 && GetWallType(i) == kWallStack)
                    {
                        if (tmp != xwall[wall[j].extra].data)
                            continue;
                        wall[i].hitag = j;
                        wall[j].hitag = i;
                        mirror[mirrorcnt].at4 = j;
                        break;
                    }
                }
                if (j < 0)
                    ThrowError("wall[%d] has no matching wall link! (data=%d)\n", i, tmp);
                mirrorcnt++;
            }
            continue;
        }
        if (wall[i].picnum == 504)
        {
            mirror[mirrorcnt].at4 = i;
            mirror[mirrorcnt].at14 = i;
            wall[i].picnum = nTile;
            mirror[mirrorcnt].at0 = 0;
            wall[i].cstat |= 32;
            mirrorcnt++;
            continue;
        }
    }
    for (int i = numsectors - 1; i >= 0; i--)
    {
        if (mirrorcnt >= 15)
            break;

        if (sector[i].floorpicnum == 504)
        {
            int nLink = gUpperLink[i];
            if (nLink < 0)
                continue;
            int nLink2 = sprite[nLink].owner;
            int j = sprite[nLink2].sectnum;
            if (sector[j].ceilingpicnum != 504)
                ThrowError("Lower link sector %d doesn't have mirror picnum\n", j);
            mirror[mirrorcnt].at0 = 2;
            mirror[mirrorcnt].at8 = sprite[nLink2].x-sprite[nLink].x;
            mirror[mirrorcnt].atc = sprite[nLink2].y-sprite[nLink].y;
            mirror[mirrorcnt].at10 = sprite[nLink2].z-sprite[nLink].z;
            mirror[mirrorcnt].at14 = i;
            mirror[mirrorcnt].at4 = j;
            sector[i].floorpicnum = 4080+mirrorcnt;
            mirrorcnt++;
            mirror[mirrorcnt].at0 = 1;
            mirror[mirrorcnt].at8 = sprite[nLink].x-sprite[nLink2].x;
            mirror[mirrorcnt].atc = sprite[nLink].y-sprite[nLink2].y;
            mirror[mirrorcnt].at10 = sprite[nLink].z-sprite[nLink2].z;
            mirror[mirrorcnt].at14 = j;
            mirror[mirrorcnt].at4 = i;
            sector[j].ceilingpicnum = 4080+mirrorcnt;
            mirrorcnt++;
        }
    }
    mirrorsector = numsectors;
    for (int i = 0; i < 4; i++)
    {
        mirrorwall[i] = numwalls+i;
        wall[mirrorwall[i]].picnum = 504;
        wall[mirrorwall[i]].overpicnum = 504;
        wall[mirrorwall[i]].cstat = 0;
        wall[mirrorwall[i]].nextsector = -1;
        wall[mirrorwall[i]].nextwall = -1;
        wall[mirrorwall[i]].point2 = numwalls+i+1;
    }
    wall[mirrorwall[3]].point2 = mirrorwall[0];
    sector[mirrorsector].ceilingpicnum = 504;
    sector[mirrorsector].floorpicnum = 504;
    sector[mirrorsector].wallptr = mirrorwall[0];
    sector[mirrorsector].wallnum = 4;
}

void TranslateMirrorColors(int nShade, int nPalette)
{
    if (videoGetRenderMode() != REND_CLASSIC)
        return;
    videoBeginDrawing();
    nShade = ClipRange(nShade, 0, 63);
    char *pMap = palookup[nPalette] + (nShade<<8);
    extern intptr_t frameplace;
    char *pFrame = (char*)frameplace;
    unsigned int nPixels = xdim*ydim;
    for (unsigned int i = 0; i < nPixels; i++, pFrame++)
    {
        *pFrame = pMap[*pFrame];
    }
    videoEndDrawing();
}

void sub_5571C(char mode)
{
    for (int i = mirrorcnt-1; i >= 0; i--)
    {
        int nTile = 4080+i;
        if (TestBitString(gotpic, nTile))
        {
            switch (mirror[i].at0)
            {
                case 1:
                    if (mode)
                        sector[mirror[i].at14].ceilingstat |= 1;
                    else
                        sector[mirror[i].at14].ceilingstat &= ~1;
                    break;
                case 2:
                    if (mode)
                        sector[mirror[i].at14].floorstat |= 1;
                    else
                        sector[mirror[i].at14].floorstat &= ~1;
                    break;
            }
        }
    }
}

void sub_557C4(int x, int y, int interpolation)
{
    if (spritesortcnt == 0) return;
    int nViewSprites = spritesortcnt;
    for (int nTSprite = nViewSprites-1; nTSprite >= 0; nTSprite--)
    {
        tspritetype *pTSprite = &tsprite[nTSprite];
        pTSprite->xrepeat = pTSprite->yrepeat = 0;
    }
    for (int i = mirrorcnt-1; i >= 0; i--)
    {
        int nTile = 4080+i;
        if (TestBitString(gotpic, nTile))
        {
            if (mirror[i].at0 == 1 || mirror[i].at0 == 2)
            {
                int nSector = mirror[i].at4;
                int nSector2 = mirror[i].at14;
                for (int nSprite = headspritesect[nSector]; nSprite >= 0; nSprite = nextspritesect[nSprite])
                {
                    spritetype *pSprite = &sprite[nSprite];
                    if (pSprite == gView->pSprite)
                        continue;
                    int top, bottom;
                    GetSpriteExtents(pSprite, &top, &bottom);
                    int zCeil, zFloor;
                    getzsofslope(nSector, pSprite->x, pSprite->y, &zCeil, &zFloor);
                    if (pSprite->statnum == kStatDude && (top < zCeil || bottom > zFloor))
                    {
                        int j = i;
                        if (mirror[i].at0 == 2)
                            j++;
                        else
                            j--;
                        int dx = mirror[j].at8;
                        int dy = mirror[j].atc;
                        int dz = mirror[j].at10;
                        if (spritesortcnt < maxspritesonscreen)
                        {
                            tspritetype *pTSprite = &tsprite[spritesortcnt];
                            memset(pTSprite, 0, sizeof(tspritetype));
                            pTSprite->type = pSprite->type;
                            pTSprite->index = pSprite->index;
                            pTSprite->sectnum = nSector2;
                            pTSprite->x = pSprite->x+dx;
                            pTSprite->y = pSprite->y+dy;
                            pTSprite->z = pSprite->z+dz;
                            pTSprite->ang = pSprite->ang;
                            pTSprite->picnum = pSprite->picnum;
                            pTSprite->shade = pSprite->shade;
                            pTSprite->pal = pSprite->pal;
                            pTSprite->xrepeat = pSprite->xrepeat;
                            pTSprite->yrepeat = pSprite->yrepeat;
                            pTSprite->xoffset = pSprite->xoffset;
                            pTSprite->yoffset = pSprite->yoffset;
                            pTSprite->cstat = pSprite->cstat;
                            pTSprite->statnum = kStatDecoration;
                            pTSprite->owner = pSprite->index;
                            pTSprite->extra = pSprite->extra;
                            pTSprite->flags = pSprite->hitag|0x200;
                            if (gViewInterpolate)
                            {
                                LOCATION *pLocation = &gPrevSpriteLoc[pSprite->index];
                                pTSprite->x = dx+interpolate(pLocation->x, pSprite->x, interpolation);
                                pTSprite->y = dy+interpolate(pLocation->y, pSprite->y, interpolation);
                                pTSprite->z = dz+interpolate(pLocation->z, pSprite->z, interpolation);
                                pTSprite->ang = pLocation->ang+mulscale16(((pSprite->ang-pLocation->ang+1024)&2047)-1024,interpolation);
                            }
                            spritesortcnt++;
                        }
                    }
                }
            }
        }
    }
    for (int nTSprite = spritesortcnt-1; nTSprite >= nViewSprites; nTSprite--)
    {
        tspritetype *pTSprite = &tsprite[nTSprite];
        int nAnim = 0;
        switch (picanm[pTSprite->picnum].extra&7)
        {
        case 1:
        {
            int dX = x - pTSprite->x;
            int dY = y - pTSprite->y;
            RotateVector(&dX, &dY, 128 - pTSprite->ang);
            nAnim = GetOctant(dX, dY);
            if (nAnim <= 4)
            {
                pTSprite->cstat &= ~4;
            }
            else
            {
                nAnim = 8 - nAnim;
                pTSprite->cstat |= 4;
            }
            break;
        }
        case 2:
        {
            int dX = x - pTSprite->x;
            int dY = y - pTSprite->y;
            RotateVector(&dX, &dY, 128 - pTSprite->ang);
            nAnim = GetOctant(dX, dY);
            break;
        }
        }
        while (nAnim > 0)
        {
            pTSprite->picnum += picanm[pTSprite->picnum].num+1;
            nAnim--;
        }
    }
}

void DrawMirrors(int x, int y, int z, fix16_t a, fix16_t horiz, int smooth, int viewPlayer)
{
    if (videoGetRenderMode() == REND_POLYMER)
        return;
    for (int i = mirrorcnt - 1; i >= 0; i--)
    {
        int nTile = 4080+i;
        if (TestBitString(gotpic, nTile))
        {
            ClearBitString(gotpic, nTile);
            switch (mirror[i].at0)
            {
            case 0:
            {
                int nWall = mirror[i].at4;
                int nSector = sectorofwall(nWall);
                walltype *pWall = &wall[nWall];
                int nNextWall = pWall->nextwall;
                int nNextSector = pWall->nextsector;
                pWall->nextwall = mirrorwall[0];
                pWall->nextsector = mirrorsector;
                wall[mirrorwall[0]].nextwall = nWall;
                wall[mirrorwall[0]].nextsector = nSector;
                wall[mirrorwall[0]].x = wall[pWall->point2].x;
                wall[mirrorwall[0]].y = wall[pWall->point2].y;
                wall[mirrorwall[1]].x = pWall->x;
                wall[mirrorwall[1]].y = pWall->y;
                wall[mirrorwall[2]].x = wall[mirrorwall[1]].x+(wall[mirrorwall[1]].x-wall[mirrorwall[0]].x)*16;
                wall[mirrorwall[2]].y = wall[mirrorwall[1]].y+(wall[mirrorwall[1]].y-wall[mirrorwall[0]].y)*16;
                wall[mirrorwall[3]].x = wall[mirrorwall[0]].x+(wall[mirrorwall[0]].x-wall[mirrorwall[1]].x)*16;
                wall[mirrorwall[3]].y = wall[mirrorwall[0]].y+(wall[mirrorwall[0]].y-wall[mirrorwall[1]].y)*16;
                sector[mirrorsector].floorz = sector[nSector].floorz;
                sector[mirrorsector].ceilingz = sector[nSector].ceilingz;
                int cx, cy, ca;
                if (GetWallType(nWall) == kWallStack)
                {
                     cx = x - (wall[pWall->hitag].x-wall[pWall->point2].x);
                     cy = y - (wall[pWall->hitag].y-wall[pWall->point2].y);
                     ca = a;
                }
                else
                {
                    //renderPrepareMirror(x,y, fix16_from_int(a),nWall,&cx,&cy,&ca);
                    renderPrepareMirror(x,y,z,a,horiz,nWall,&cx,&cy,&ca);
                }
#ifdef POLYMER
                if (videoGetRenderMode() == REND_POLYMER)
                    polymer_setanimatesprites(viewProcessSprites, cx, cy, z, fix16_to_int(ca), smooth);
#endif
                yax_preparedrawrooms();
                int32_t didmirror = renderDrawRoomsQ16(cx, cy, z, ca,horiz,mirrorsector|MAXSECTORS);
                yax_drawrooms(viewProcessSprites, mirrorsector, didmirror, smooth);
                viewProcessSprites(cx,cy,z,fix16_to_int(ca),smooth);
                renderDrawMasks();
                if (GetWallType(nWall) != kWallStack)
                    renderCompleteMirror();
                if (wall[nWall].pal != 0 || wall[nWall].shade != 0)
                    TranslateMirrorColors(wall[nWall].shade, wall[nWall].pal);
                pWall->nextwall = nNextWall;
                pWall->nextsector = nNextSector;
                return;
            }
            case 1:
            {
#ifdef USE_OPENGL
                r_rorphase = 1;
#endif
                int nSector = mirror[i].at4;
                int bakCstat;
                if (viewPlayer >= 0)
                {
                    bakCstat = gPlayer[viewPlayer].pSprite->cstat;
                    if (gViewPos == 0)
                    {
                        gPlayer[viewPlayer].pSprite->cstat |= CSTAT_SPRITE_INVISIBLE;
                    }
                    else
                    {
                        gPlayer[viewPlayer].pSprite->cstat |= CSTAT_SPRITE_TRANSLUCENT_INVERT | CSTAT_SPRITE_TRANSLUCENT;
                    }
                }
#ifdef POLYMER
                if (videoGetRenderMode() == REND_POLYMER)
                    polymer_setanimatesprites(viewProcessSprites, x+mirror[i].at8, y+mirror[i].atc, z+mirror[i].at10, fix16_to_int(a), smooth);
#endif
                yax_preparedrawrooms();
                renderDrawRoomsQ16(x+mirror[i].at8, y+mirror[i].atc, z+mirror[i].at10, a, horiz, nSector|MAXSECTORS);
                yax_drawrooms(viewProcessSprites, nSector, 0, smooth);
                viewProcessSprites(x+mirror[i].at8, y+mirror[i].atc, z+mirror[i].at10, fix16_to_int(a), smooth);
                short fstat = sector[nSector].floorstat;
                sector[nSector].floorstat |= 1;
                renderDrawMasks();
                sector[nSector].floorstat = fstat;
                for (int i = 0; i < 16; i++)
                    ClearBitString(gotpic, 4080+i);
                if (viewPlayer >= 0)
                {
                    gPlayer[viewPlayer].pSprite->cstat = bakCstat;
                }
#ifdef USE_OPENGL
                r_rorphase = 0;
#endif
                return;
            }
            case 2:
            {
#ifdef USE_OPENGL
                r_rorphase = 1;
#endif
                int nSector = mirror[i].at4;
                int bakCstat;
                if (viewPlayer >= 0)
                {
                    bakCstat = gPlayer[viewPlayer].pSprite->cstat;
                    if (gViewPos == 0)
                    {
                        gPlayer[viewPlayer].pSprite->cstat |= CSTAT_SPRITE_INVISIBLE;
                    }
                    else
                    {
                        gPlayer[viewPlayer].pSprite->cstat |= CSTAT_SPRITE_TRANSLUCENT_INVERT | CSTAT_SPRITE_TRANSLUCENT;
                    }
                }
#ifdef POLYMER
                if (videoGetRenderMode() == REND_POLYMER)
                    polymer_setanimatesprites(viewProcessSprites, x+mirror[i].at8, y+mirror[i].atc, z+mirror[i].at10, fix16_to_int(a), smooth);
#endif
                yax_preparedrawrooms();
                renderDrawRoomsQ16(x+mirror[i].at8, y+mirror[i].atc, z+mirror[i].at10, a, horiz, nSector|MAXSECTORS);
                yax_drawrooms(viewProcessSprites, nSector, 0, smooth);
                viewProcessSprites(x+mirror[i].at8, y+mirror[i].atc, z+mirror[i].at10, fix16_to_int(a), smooth);
                short cstat = sector[nSector].ceilingstat;
                sector[nSector].ceilingstat |= 1;
                renderDrawMasks();
                sector[nSector].ceilingstat = cstat;
                for (int i = 0; i < 16; i++)
                    ClearBitString(gotpic, 4080+i);
                if (viewPlayer >= 0)
                {
                    gPlayer[viewPlayer].pSprite->cstat = bakCstat;
                }
#ifdef USE_OPENGL
                r_rorphase = 0;
#endif
                return;
            }
            }
        }
    }
}

class MirrorLoadSave : public LoadSave {
public:
    void Load(void);
    void Save(void);
};

static MirrorLoadSave *myLoadSave;

void MirrorLoadSave::Load(void)
{
#ifdef USE_OPENGL
    r_rortexture = 4080;
    r_rortexturerange = 16;
#ifdef  POLYMER
    polymer_setrorcallback(PolymerRORCallback);
#endif //  POLYMER

#endif
    Read(&mirrorcnt,sizeof(mirrorcnt));
    Read(&mirrorsector,sizeof(mirrorsector));
    Read(mirror, sizeof(mirror));
    Read(mirrorwall, sizeof(mirrorwall));
    tilesiz[504].x = 0;
    tilesiz[504].y = 0;

    for (int i = 0; i < 16; i++)
        tilesiz[4080 + i].x = 0, tilesiz[4080 + i].y = 0;
    for (int i = 0; i < 4; i++)
    {
        wall[mirrorwall[i]].picnum = 504;
        wall[mirrorwall[i]].overpicnum = 504;
        wall[mirrorwall[i]].cstat = 0;
        wall[mirrorwall[i]].nextsector = -1;
        wall[mirrorwall[i]].nextwall = -1;
        wall[mirrorwall[i]].point2 = numwalls+i+1;
    }
    wall[mirrorwall[3]].point2 = mirrorwall[0];
    sector[mirrorsector].ceilingpicnum = 504;
    sector[mirrorsector].floorpicnum = 504;
    sector[mirrorsector].wallptr = mirrorwall[0];
    sector[mirrorsector].wallnum = 4;
}

void MirrorLoadSave::Save(void)
{
    Write(&mirrorcnt,sizeof(mirrorcnt));
    Write(&mirrorsector,sizeof(mirrorsector));
    Write(mirror, sizeof(mirror));
    Write(mirrorwall, sizeof(mirrorwall));
}

void MirrorLoadSaveConstruct(void)
{
    myLoadSave = new MirrorLoadSave();
}


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2010-2019 EDuke32 developers and contributors
Copyright (C) 2019 Nuke.YKT

This file is part of NBlood.

NBlood is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
//-------------------------------------------------------------------------
#pragma once
#include "fix16.h"
void InitMirrors(void);
void sub_5571C(char mode);
void sub_557C4(int x, int y, int interpolation);
void DrawMirrors(int x, int y, int z, fix16_t a, fix16_t horiz, int smooth, int viewPlayer);


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2010-2019 EDuke32 developers and contributors
Copyright (C) 2019 Nuke.YKT

This file is part of NBlood.

NBlood is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
//-------------------------------------------------------------------------
#include <stdio.h>
#include <string.h>
#include "common_game.h"

#include "misc.h"

void *ResReadLine(char *buffer, unsigned int nBytes, void **pRes)
{
    unsigned int i;
    char ch;
    if (!pRes || !*pRes || *((char*)*pRes) == 0)
        return NULL;
    for (i = 0; i < nBytes; i++)
    {
        ch = *((char*)*pRes);
        if(ch == 0 || ch == '\n')
            break;
        buffer[i] = ch;
        *pRes = ((char*)*pRes)+1;
    }
    if (*((char*)*pRes) == '\n' && i < nBytes)
    {
        ch = *((char*)*pRes);
        buffer[i] = ch;
        *pRes = ((char*)*pRes)+1;
        i++;
    }
    else
    {
        while (true)
        {
            ch = *((char*)*pRes);
            if (ch == 0 || ch == '\n')
                break;
            *pRes = ((char*)*pRes)+1;
        }
        if (*((char*)*pRes) == '\n')
            *pRes = ((char*)*pRes)+1;
    }
    if (i < nBytes)
        buffer[i] = 0;
    return *pRes;
}

bool FileRead(FILE *handle, void *buffer, unsigned int size)
{
    return fread(buffer, 1, size, handle) == size;
}

bool FileWrite(FILE *handle, void *buffer, unsigned int size)
{
    return fwrite(buffer, 1, size, handle) == size;
}

bool FileLoad(const char *name, void *buffer, unsigned int size)
{
    dassert(buffer != NULL);

    FILE *handle = fopen(name, "rb");
    if (!handle)
        return false;

    unsigned int nread = fread(buffer, 1, size, handle);
    fclose(handle);
    return nread == size;
}

int FileLength(FILE *handle)
{
    if (!handle)
        return 0;
    int nPos = ftell(handle);
    fseek(handle, 0, SEEK_END);
    int nLength = ftell(handle);
    fseek(handle, nPos, SEEK_SET);
    return nLength;
}

unsigned int randSeed = 1;

unsigned int qrand(void)
{
    if (randSeed&0x80000000)
        randSeed = ((randSeed<<1)^0x20000004)|0x1;
    else
        randSeed = randSeed<<1;
    return randSeed&0x7fff;
}

void ChangeExtension(char *pzFile, const char *pzExt)
{
#if 0
    char drive[BMAX_PATH];
    char dir[BMAX_PATH];
    char filename[BMAX_PATH];
    _splitpath(pzFile, drive, dir, filename, NULL);
    _makepath(pzFile, drive, dir, filename, pzExt);
#else
    int const nLength = Bstrlen(pzFile);
    char * pDot = pzFile+nLength;
    for (int i = nLength-1; i >= 0; i--)
    {
        if (pzFile[i] == '/' || pzFile[i] == '\\')
            break;
        if (pzFile[i] == '.')
        {
            pDot = pzFile+i;
            break;
        }
    }
    *pDot = '\0';
    Bstrcat(pDot, pzExt);
#endif
}

void SplitPath(const char *pzPath, char *pzDirectory, char *pzFile, char *pzType)
{
    int const nLength = Bstrlen(pzPath);
    //const char *pDirectory = pzPath+nLength;
    const char *pDot = NULL;
    for (int i = nLength-1; i >= 0; i--)
    {
        if (pzPath[i] == '/' || pzPath[i] == '\\')
        {
            Bstrncpy(pzDirectory, pzPath, i);
            pzDirectory[i] = 0;
            if (!pDot)
            {
                Bstrcpy(pzFile, pzPath+i+1);
                Bstrcpy(pzType, "");
            }
            else
            {
                Bstrncpy(pzFile, pzPath+i+1, pDot-(pzPath+i+1));
                pzFile[pDot-(pzPath+i+1)] = 0;
                Bstrcpy(pzType, pDot+1);
            }

            return;
        }
        else if (pzPath[i] == '.')
        {
            pDot = pzPath+i;
        }
    }
    Bstrcpy(pzDirectory, "/");
    if (!pDot)
    {
        Bstrcpy(pzFile, pzPath);
        Bstrcpy(pzType, "");
    }
    else
    {
        Bstrncpy(pzFile, pzPath, pDot-pzPath);
        pzFile[pDot-pzPath] = 0;
        Bstrcpy(pzType, pDot+1);
    }
}

void ConcatPath(const char *pzPath1, const char *pzPath2, char *pzConcatPath)
{
    int n1 = Bstrlen(pzPath1), n2 = Bstrlen(pzPath2);
    int i = n1, j = 0;
    while (i > 0 && (pzPath1[i-1] == '/' || pzPath1[i-1] == '\\'))
    {
        i--;
    }
    while (j < n2 && (pzPath2[j] == '/' || pzPath1[j] == '\\'))
    {
        j++;
    }
    Bstrncpy(pzConcatPath, pzPath1, i);
    pzConcatPath[i] = 0;
    Bstrcat(pzConcatPath, "/");
    Bstrcat(pzConcatPath, pzPath2+j);
}


code:
//-------------------------------------------------------------------------
/*
Copyright (C) 2010-2019 EDuke32 developers and contributors
Copyright (C) 2019 Nuke.YKT

This file is part of NBlood.

NBlood is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
//-------------------------------------------------------------------------
#pragma once
void *ResReadLine(char *buffer, unsigned int nBytes, void **pRes);
bool FileRead(FILE *, void *, unsigned int);
bool FileWrite(FILE *, void *, unsigned int);
bool FileLoad(const char *, void *, unsigned int);
int FileLength(FILE *);
unsigned int qrand(void);
void ChangeExtension(char *pzFile, const char *pzExt);
void SplitPath(const char *pzPath, char *pzDirectory, char *pzFile, char *pzType);
void ConcatPath(const char* pzPath1, const char* pzPath2, char* pzConcatPath);