If you have comments or questions concerning this source file, discuss them in the forum.
/*
Copyright (c) 2002 Nicolai Haehnle

See the license.txt for details. If that file was not included in the
source distributions, please email <prefect@rtts.org>
*/
// main.c

#include "rtts.h"

#include <SDL.h>

#include "gui_label.h"
#include "gui_button.h"
#include "gui_inputbox.h"

#include "rttspanels.h"


LConfig *config;


/*
==============
RttsButton::RttsButton

Initialize a button with the default menu colors
==============
*/
RttsButton::RttsButton(GPanel *pParent, int x, int y, int w, int h, const char *szText,
                       int iAlign, int id)
    : GButton(pParent, x, y, w, h, szText, iAlign, id)
{
    SetColor(192, 32, 0);
    SetDisabledColor(96, 96, 96);
    SetFocusColor(255, 112, 0);
    SetMouseOverColor(255, 32, 0);
    SetClickedColor(255, 192, 0);

    AdjustSize();
}

RttsRadioButton::RttsRadioButton(GPanel *pParent, int x, int y, int w, int h,
                                 const char *szText, int iAlign, int id)
    : GRadioButton(pParent, x, y, w, h, szText, iAlign, id)
{
    SetColor(192, 32, 0);
    SetDisabledColor(96, 96, 96);
    SetFocusColor(255, 112, 0);
    SetMouseOverColor(255, 32, 0);
    SetClickedColor(255, 192, 0);

    AdjustSize();
}

RttsCheckBox::RttsCheckBox(GPanel *pParent, int x, int y, int w, int h,
                                 const char *szText, int iAlign, int id)
    : GCheckBox(pParent, x, y, w, h, szText, iAlign, id)
{
    SetColor(192, 32, 0);
    SetDisabledColor(96, 96, 96);
    SetFocusColor(255, 112, 0);
    SetMouseOverColor(255, 32, 0);
    SetClickedColor(255, 192, 0);

    AdjustSize();
}

/*
==============
RttsDialog::RttsDialog

Initialize the common dialog currently only a darkened but translucent frame
==============
*/
RttsDialog::RttsDialog(GPanel *pParent, int x, int y, int w, int h, bool bOverlap)
    : GPanel(pParent, x, y, w, h, bOverlap)
{
    SetCanFocus(true);
    SetVisible(false);

    m_pDefaultButton = 0;
}


/*
==============
RttsDialog::SetDefaultButton

Sets the default button. The default button will be activated when RETURN is
pressed, and the currently focussed UI item doesn't claim the keypress.
==============
*/
void RttsDialog::SetDefaultButton(GButton* pBtn)
{
    m_pDefaultButton = pBtn;
}


/*
==============
RttsDialog::Draw

Draw the background
==============
*/
void RttsDialog::Draw()
{
    hal->FillRect(0, 0, m_rc.size[0], m_rc.size[1], 0, 0, 0, 200);
}


/*
==============
RttsDialog::Key

The escape key ends the dialog with code -1
==============
*/
bool RttsDialog::Key(int code, char c, bool down)
{
    if (code == KEY_ESCAPE)
        {
        if (down)
            EndModal(-1);
        return true;
        }
    if (code == KEY_RETURN)
        {
        if (down && m_pDefaultButton)
            m_pDefaultButton->Fire();

        return true;
        }

    return GPanel::Key(code, c, down);
}

/*
==============
RttsMessageBox::RttsMessageBox

Create a generic message box
==============
*/
RttsMessageBox::RttsMessageBox(GPanel *pParent, const char *pszText, ...)
    : RttsDialog(pParent, 50, 140, 540, 200)
{
    GLabel *pLabel;
    GButton *pBtn;
    int numbtns;
    int x, w;
    va_list va;

    pLabel = new GLabel(this, 30, 30, 480, 110, pszText, align_wrap);

    va_start(va, pszText);
    numbtns = 0;
    for(;;) {
        if (!va_arg(va, const char *))
            break;
        va_arg(va, int);
        numbtns++;
    }
    va_end(va);

    if (numbtns) {
        w = 480 / numbtns;
        x = 30;

        va_start(va, pszText);
        for(;;) {
            const char *text;
            int id;

            text = va_arg(va, const char *);
            if (!text)
                break;
            id = va_arg(va, int);

            pBtn = new RttsButton(this, x, 160, w, 30, text, halign_center|valign_center, id);
            pBtn->Clicked.Connect(this, &RttsMessageBox::EndModal);
            SetFocus(pBtn);

            x += w;
        }
        va_end(va);
    }
}

/*
==============
RttsMessageBox::Ok
RttsMessageBox::YesNo

Static functions for convenience
==============
*/
void RttsMessageBox::Ok(GPanel *pParent, const char *pszText)
{
    RttsMessageBox *mb;

    mb = new RttsMessageBox(pParent, pszText, "OK", 1, 0);

    try {
        mb->Run();
    } catch(...) {
        delete mb;
        throw;
    }

    delete mb;
}

bool RttsMessageBox::YesNo(GPanel *pParent, const char *pszText)
{
    RttsMessageBox *mb;
    int code;

    mb = new RttsMessageBox(pParent, pszText, "YES", 1, "NO", 0, 0);

    try {
        code = mb->Run();
    } catch(...) {
        delete mb;
        throw;
    }

    delete mb;
    return code > 0;
}

/*
==============================================================================

PICKKEY DIALOG

==============================================================================
*/

class PickKeyDlg : public RttsDialog {
public:
    PickKeyDlg(GPanel *pParent, int x, int y);

    virtual bool Key(int code, char c, bool down);
};

PickKeyDlg::PickKeyDlg(GPanel *pParent, int x, int y)
    : RttsDialog(pParent, x - 130, y - 30, 260, 60)
{
    GLabel *pLabel;

    pLabel = new GLabel(this, 0, 0, m_rc.size[0], m_rc.size[1], "Press a key!", halign_center|valign_center);
    pLabel->SetColor(192, 32, 0);
    pLabel->AdjustSize();
}

bool PickKeyDlg::Key(int code, char c, bool down)
{
    if (!down)
        return false;

    if (code == KEY_ESCAPE)
        EndModal(-1);
    else
        EndModal(code);
    return true;
}


/*
==============================================================================

CONTROLS DIALOG

==============================================================================
*/

#define NUM_DLGKEYS     6

typedef struct {
    char    *name;
    char    *config;
} dlgkey_t;

dlgkey_t dlgkeys[6] = {
    { "LEFT",   "left" },
    { "RIGHT",  "right" },
    { "UP",     "up" },
    { "DOWN",   "down" },
    { "FIRE",   "fire" },
    { "WEAPON", "weaponswitch" }
};

class ControlsDlg : public RttsDialog {
public:
    plrcontrol_t    m_ctrl;

    int             m_binds[NUM_DLGKEYS];
    GLabel          *m_pLabels[NUM_DLGKEYS];

public:
    ControlsDlg(GPanel *pParent);

    void Refresh();

    void Poke(const LConfig *cfg);
    void Peek(LConfig *cfg);

    void Select(int id);
};

/*
==============
ControlsDlg::ControlsDlg

Create dialog elements
==============
*/
ControlsDlg::ControlsDlg(GPanel *pParent)
    : RttsDialog(pParent, 100, 100, 440, 280)
{
    int i;
    GButton *pBtn;
    int y;

    memset(m_binds, 0, sizeof(m_binds));

    y = 30;
    for(i = 0; i < NUM_DLGKEYS; i++, y += 25) {
        pBtn = new RttsButton(this, 30, y, 200, 25, dlgkeys[i].name, valign_center, i);
        pBtn->Clicked.Connect(this, &ControlsDlg::Select);

        m_pLabels[i] = new GLabel(this, 210, y, 200, 25, 0, valign_center|halign_right);
        m_pLabels[i]->SetColor(192, 192, 0);
    }

    pBtn = new RttsButton(this, 30, 225, 200, 25, "OK", valign_center, 1);
    pBtn->Clicked.Connect(this, &ControlsDlg::EndModal);

    pBtn = new RttsButton(this, 210, 225, 200, 25, "CANCEL", valign_center|halign_right, 0);
    pBtn->Clicked.Connect(this, &ControlsDlg::EndModal);

    Refresh();
}

/*
==============
ControlsDlg::Refresh

Called whenever one of the bindings changes
==============
*/
void ControlsDlg::Refresh()
{
    int i;

    for(i = 0; i < NUM_DLGKEYS; i++) {
        m_pLabels[i]->SetText(HAL_NameForKey(m_binds[i]));
        m_pLabels[i]->AdjustSize();
    }
}

/*
==============
ControlsDlg::Poke

Copy the given keybindings over to our local copy
==============
*/
void ControlsDlg::Poke(const LConfig *cfg)
{
    int i;

    for(i = 0; i < NUM_DLGKEYS; i++)
        m_binds[i] = cfg->GetInt(dlgkeys[i].config);

    Refresh();
}

/*
==============
ControlsDlg::Peek

Copy the dialog's bindings into the given configuration tree
==============
*/
void ControlsDlg::Peek(LConfig *cfg)
{
    int i;

    for(i = 0; i < NUM_DLGKEYS; i++)
        cfg->SetInt(dlgkeys[i].config, m_binds[i]);
}

/*
==============
ControlsDlg::Select

Opens the pick a key dialog
==============
*/
void ControlsDlg::Select(int id)
{
    PickKeyDlg *pk;

    pk = new PickKeyDlg(this, 320, 240);

    try {
        int code;
        code = pk->Run();
        if (code > 0)
            m_binds[id] = code;
    } catch(...) {
        delete pk;
        throw;
    }

    delete pk;
    Refresh();
}

/*
==============================================================================

NEW GAME DIALOG

==============================================================================
*/

class NewGameDlg : public RttsDialog {
public:
    GInputBox   *m_pPlayerName;
    GInputBox   *m_pCallsign;
    GButton     *m_pOk;

public:
    NewGameDlg(GPanel *pParent);

    void Refresh();

    const char *GetPlayerName() { return m_pPlayerName->GetText(); }
    const char *GetCallsign() { return m_pCallsign->GetText(); }

    void Ok(int id);
};

/*
==============
NewGameDlg::NewGameDlg

Build the new game dialog with all its elements
==============
*/
NewGameDlg::NewGameDlg(GPanel *pParent)
    : RttsDialog(pParent, 100, 100, 440, 280)
{
    GLabel *pLabel;
    GButton *pBtn;

    pLabel = new GLabel(this, 30, 30, 440, 16, "Enter your name:", 0);
    pLabel->SetColor(192, 32, 0);
    pLabel->AdjustSize();

    m_pPlayerName = new GInputBox(this, 30, 52, 380, 24, 0, valign_center);
    m_pPlayerName->SetColor(192, 192, 192);
    m_pPlayerName->SetMaxLen(31);
    m_pPlayerName->Changed.Connect(this, &NewGameDlg::Refresh);

    pLabel = new GLabel(this, 30, 90, 440, 16, "Enter callsign:", 0);
    pLabel->SetColor(192, 32, 0);
    pLabel->AdjustSize();

    m_pCallsign = new GInputBox(this, 30, 112, 380, 24, 0, valign_center);
    m_pCallsign->SetColor(192, 192, 192);
    m_pCallsign->SetMaxLen(15);
    m_pCallsign->Changed.Connect(this, &NewGameDlg::Refresh);

    m_pOk = new RttsButton(this, 30, 225, 200, 25, "OK", valign_center, 1);
    m_pOk->Clicked.Connect(this, &NewGameDlg::Ok);

    pBtn = new RttsButton(this, 210, 225, 200, 25, "CANCEL", valign_center|halign_right, -1);
    pBtn->Clicked.Connect(this, &NewGameDlg::EndModal);

    Refresh();

    SetFocus(m_pPlayerName);
}

/*
==============
NewGameDlg::Refresh

Update member states
==============
*/
void NewGameDlg::Refresh()
{
    bool valid;

    valid = true;
    if (!m_pPlayerName->GetTextLength())
        valid = false;
    if (!m_pCallsign->GetTextLength())
        valid = false;

    m_pOk->SetOblivious(!valid);
}

/*
==============
NewGameDlg::Ok

Check that the entered data is valid, and end the dialog if that's the case
==============
*/
void NewGameDlg::Ok(int id)
{
    char buf[MAX_OSPATH];

    assert(m_pPlayerName->GetTextLength());
    assert(m_pCallsign->GetTextLength());

    snprintf(buf, sizeof(buf), "%s.%s.sav", m_pPlayerName->GetText(), m_pCallsign->GetText());
    if (L_FileExists(buf)) {
        RttsMessageBox::Ok(this, "That player already exists!");
        return;
    }

    EndModal(1);
}

/*
==============================================================================

LOAD GAME DIALOG

==============================================================================
*/

class LoadGameDlg : public RttsDialog {
public:
    int     m_iSelect;
    int     m_iNumSavegames;
    char    **m_pSavegames;

    GLabel  *m_pName;
    GLabel  *m_pCallsign;
    GLabel  *m_pMoney;
    GButton *m_pLoad;

public:
    LoadGameDlg(GPanel *pParent);
    ~LoadGameDlg();

    void Refresh();

    void Peek(player_t *plr);

    void LeftBtn(int id);
    void RightBtn(int id);

    virtual bool Key(int code, char c, bool down);
};


/*
==============
LoadGameDlg::LoadGameDlg

Scan the directory for save games to build the list, and create panels
==============
*/
LoadGameDlg::LoadGameDlg(GPanel *pParent)
    : RttsDialog(pParent, 100, 100, 440, 280)
{
    GButton *pBtn;

    m_iSelect = 0;
    m_iNumSavegames = L_FindFiles(0, "*.sav", &m_pSavegames);

    m_pName = new GLabel(this, 50, 50, 340, 16, 0, valign_center);
    m_pName->SetColor(192, 192, 192);

    m_pCallsign = new GLabel(this, 50, 75, 340, 16, 0, valign_center);
    m_pCallsign->SetColor(192, 192, 192);

    m_pMoney = new GLabel(this, 50, 100, 340, 16, 0, valign_center);
    m_pMoney->SetColor(192, 192, 192);

    pBtn = new RttsButton(this, 30, 230, 30, 30, "\x01", valign_center|halign_center);
    pBtn->Clicked.Connect(this, &LoadGameDlg::LeftBtn);

    pBtn = new RttsButton(this, 380, 230, 30, 20, "\x02", valign_center|halign_center);
    pBtn->Clicked.Connect(this, &LoadGameDlg::RightBtn);

    m_pLoad = new RttsButton(this, 60, 230, 160, 30, "LOAD",
                        valign_center|halign_center, 1);
    m_pLoad->Clicked.Connect(this, &LoadGameDlg::EndModal);

    SetDefaultButton(m_pLoad);

    pBtn = new RttsButton(this, 220, 230, 160, 30, "CANCEL",
                        valign_center|halign_center, -1);
    pBtn->Clicked.Connect(this, &LoadGameDlg::EndModal);

    Refresh();
}

/*
==============
LoadGameDlg::~LoadGameDlg

Free allocated resources
==============
*/
LoadGameDlg::~LoadGameDlg()
{
    L_FreeFindFiles(m_pSavegames);
}

/*
==============
LoadGameDlg::Refresh

Try to load the currently selected game and set labels accordingly
==============
*/
void LoadGameDlg::Refresh()
{
    char buf[128];
    player_t plr;

    if (!m_iNumSavegames) {
        m_pLoad->SetOblivious(true);
        return;
    }

    if (G_LoadPlayer(&plr, m_pSavegames[m_iSelect]))
    {
        snprintf(buf, sizeof(buf), "Name: %s", plr.name);
        m_pName->SetText(buf);

        snprintf(buf, sizeof(buf), "Callsign: %s", plr.callsign);
        m_pCallsign->SetText(buf);

        snprintf(buf, sizeof(buf), "Money: %i\x80", plr.money);
        m_pMoney->SetText(buf);

        m_pLoad->SetOblivious(false);
    }
    else
    {
        m_pName->SetText(m_pSavegames[m_iSelect]);
        m_pCallsign->SetText("savegame corrupt");
        m_pMoney->SetText(0);
        m_pLoad->SetOblivious(true);
    }
}

/*
==============
LoadGameDlg::Peek

Retrieve the currently selected savegame
==============
*/
void LoadGameDlg::Peek(player_t *plr)
{
    lassert(m_iSelect < m_iNumSavegames);

    if (!G_LoadPlayer(plr, m_pSavegames[m_iSelect]))
        throw LError("savegame corrupt");
}

/*
==============
LoadGameDlg::LeftBtn
LoadGameDlg::RightBtn

Move through savegames
==============
*/
void LoadGameDlg::LeftBtn(int id)
{
    m_iSelect--;
    if (m_iSelect < 0)
        m_iSelect = m_iNumSavegames - 1;

    Refresh();
}

void LoadGameDlg::RightBtn(int id)
{
    m_iSelect++;
    if (m_iSelect >= m_iNumSavegames)
        m_iSelect = 0;

    Refresh();
}

/*
==============
LoadGameDlg::Key

Cursor keys move through savegames
==============
*/
bool LoadGameDlg::Key(int code, char c, bool down)
{
    if (down) {
        switch(code) {
        case KEY_LEFT: LeftBtn(-1); return true;
        case KEY_RIGHT: RightBtn(-1); return true;
        }
    }

    return RttsDialog::Key(code, c, down);
}

/*
==============================================================================

MAIN MENU

==============================================================================
*/

class MainMenu : public GLabel {
public:
    MainMenu();

    void NewGame(int id);
    void LoadGame(int id);
    void Controls(int id);
    void Quit(int id);
    void Edit(int id);

    virtual bool Key(int code, char c, bool down);
};

/*
==============
MainMenu::MainMenu

Create the main menu, with layout and everything.
=============
*/
MainMenu::MainMenu()
    : GLabel(0, "splash", pic_normal, 0, 0, 0, 640, 480)
{
    GButton *pBtn;
    GLabel *pLabel;

    SetCanFocus(true); // must be true because we're going modal

    pLabel = new GLabel(this, 0, 50, 640, 16, "Return to the Shadows", halign_center);
    pLabel->SetColor(255, 128, 0);

    pBtn = new RttsButton(this, 200, 195, 300, 25, "NEW GAME", valign_center);
    pBtn->Clicked.Connect(this, &MainMenu::NewGame);

    pBtn = new RttsButton(this, 200, 230, 300, 25, "LOAD GAME", valign_center);
    pBtn->Clicked.Connect(this, &MainMenu::LoadGame);

    pBtn = new RttsButton(this, 200, 265, 300, 25, "CONTROLS", valign_center);
    pBtn->Clicked.Connect(this, &MainMenu::Controls);

    pBtn = new RttsButton(this, 200, 300, 300, 25, "QUIT", valign_center);
    pBtn->Clicked.Connect(this, &MainMenu::Quit);

    if (game.running)
    {
        pBtn = new RttsButton(this, 200, 335, 300, 25, "RETURN TO HANGAR",
                                    valign_center, MM_HANGAR);
        pBtn->Clicked.Connect(this, &MainMenu::EndModal);
    }

    pBtn = new RttsButton(this, 200, 405, 300, 25, "EDIT", valign_center, MM_EDITOR);
    pBtn->Clicked.Connect(this, &MainMenu::EndModal);
}

/*
==============
MainMenu::NewGame
MainMenu::Quit
MainMenu::ReturnToHangar

Button action handling
==============
*/
void MainMenu::NewGame(int id)
{
    const char *playername, *callsign;
    int code;

    if (game.running) {
        if (!RttsMessageBox::YesNo(this, "This will stop the current game. Continue?"))
            return;
    }

    NewGameDlg dlg(this);
    code = dlg.Run();

    if (code > 0) {
        playername = dlg.GetPlayerName();
        callsign = dlg.GetCallsign();
        G_NewGame(playername, callsign);

        EndModal(MM_HANGAR);
    }
}

void MainMenu::LoadGame(int id)
{
    int code;

    if (game.running) {
        if (!RttsMessageBox::YesNo(this, "This will stop the current game. Continue?"))
            return;
    }

    LoadGameDlg dlg(this);
    code = dlg.Run();

    if (code > 0) {
        G_NewGame(0, 0);
        dlg.Peek(&game.player);

        EndModal(MM_HANGAR);
    }
}

void MainMenu::Controls(int id)
{
    ControlsDlg dlg(this);

    dlg.Poke(config->GetItem("controls"));
    if (dlg.Run() > 0)
        dlg.Peek(config->GetItem("controls"));
}

void MainMenu::Quit(int id)
{
    if (RttsMessageBox::YesNo(this, "Do you really want to quit?"))
        EndModal(MM_QUIT);
}


/*
==============
MainMenu::Key

Handle keypresses
==============
*/
bool MainMenu::Key(int code, char c, bool down)
{
    if (!down)
        return false;

    switch(code) {
    case KEY_ESCAPE:
        if (game.running)
            EndModal(MM_HANGAR);
        else
            Quit(-1);
        return true;
    }

    return GLabel::Key(code, c, down);
}


/*
==============================================================================

COMMON SETUP ROUTINES

==============================================================================
*/

l_configitem_t controlsdef[] = {
    { CI_INT,       "left",         "276", 0 }, // KEY_LEFT
    { CI_INT,       "right",        "275", 0 }, // KEY_RIGHT
    { CI_INT,       "up",           "273", 0 }, // KEY_UP
    { CI_INT,       "down",         "274", 0 }, // KEY_DOWN
    { CI_INT,       "fire",         "306", 0 }, // KEY_RCTRL
    { CI_INT,       "weaponswitch", "308", 0 }, // KEY_RSHIFT

    { 0, 0, }
};
l_configrecord_t controlsrec = { controlsdef };

l_configitem_t haldef[] = {
    { CI_INT,       "res",          "640", 0 },
    { CI_INT,       "bpp",          "32", 0 },
    { CI_BOOL,      "window",       "0", 0 },
    { CI_BOOL,      "nosubpixel",   "0", 0 },
    { CI_BOOL,      "noborder",     "0", 0 },

    { 0, 0, }
};
l_configrecord_t halrec = { haldef };

l_configitem_t globaldef[] = {
    { CI_RECORD,    "controls",     0, &controlsrec },
    { CI_RECORD,    "hal",          0, &halrec },

        // must be last
    { 0, 0, }
};
l_configrecord_t globalrec = { globaldef };

/*
==============
Com_Init

Initialize all subsystems
==============
*/
void Com_Init(int argc, char **argv)
{
    int i;

    config = LConfig::Create(&globalrec);
    config->TryLoad("config.cfg");

    for(i = 1; i < argc; i++) {
        if (!strcmp(argv[i], "-window")) {
            config->SetBool("hal.window", true);
        } else if (!strcmp(argv[i], "-fullscreen")) {
            config->SetBool("hal.window", false);
        } else if (!strcmp(argv[i], "-16bpp")) {
            config->SetInt("hal.bpp", 16);
        } else if (!strcmp(argv[i], "-32bpp")) {
            config->SetInt("hal.bpp", 32);
        } else if (!strcmp(argv[i], "-nosubpixel")) {
            config->SetBool("hal.nosubpixel", true);
            L_Printf("nosubpixel\n");
        } else if (!strcmp(argv[i], "-subpixel")) {
            config->SetBool("hal.nosubpixel", false);
        } else if (!strcmp(argv[i], "-noborder")) {
            config->SetBool("hal.noborder", true);
            L_Printf("noborder\n");
        } else if (!strcmp(argv[i], "-border")) {
            config->SetBool("hal.noborder", false);
        } else if (!strcmp(argv[i], "-res")) {
            config->SetInt("hal.res", atoi(argv[i+1]));
            i++;
        } else {
            L_Printf("Unknown parameter: %s\n", argv[i]);
            break;
        }
    }

    HAL_Start(config->GetItem("hal"));

    G_Init();
}

/*
==============
Com_Shutdown

Quit all subsystems
==============
*/
void Com_Shutdown()
{
    try
    {
        G_Shutdown();

        if (config) {
            config->TrySave("config.cfg");
            delete config;
        }

        HAL_Shutdown();

        L_TagFree(-1, -1);
    }
    catch(LError &err)
    {
        fprintf(stderr, "Fatal error during shutdown: %s\n", err.Get());
    }

    exit(0);
}


/*
==============
main

main program and game loop
==============
*/
int main(int argc, char **argv)
{
    GPanel *panel;

    srand(time(0));

    panel = 0;

    try
    {
        // Macro main loop as a FSM
        enum { s_quit, s_mainmenu, s_hangar, s_game, s_editor } state = s_mainmenu;
        int code;

        Com_Init(argc, argv);

        while(state != s_quit) {
            switch(state) {
            case s_mainmenu:
                panel = new MainMenu;
                code = panel->Run();
                delete panel;
                panel = 0;

                if (code == MM_HANGAR)
                    state = s_hangar;
                else if (code == MM_EDITOR)
                    state = s_editor;
                else
                    state = s_quit;
                break;

            case s_hangar:
                panel = MakeHangar();
                code = panel->Run();
                delete panel;
                panel = 0;

                if (code == HANGAR_GAME)
                    state = s_game;
                else
                    state = s_mainmenu;
                break;

            case s_game:
                code = G_Run();

                if (code == GAME_LOOSE)
                    state = s_mainmenu;
                else
                    state = s_hangar;
                break;

            case s_editor:
                panel = MakeEditor();
                panel->Run();
                delete panel;
                panel = 0;

                state = s_mainmenu;
                break;
            }

            hal->CacheCycle();
        }
    }
    catch(LError &err)
    {
        fprintf(stderr, "Fatal error: %s\n", err.Get());
    }

    if (panel)
        delete panel;

    Com_Shutdown();

    return 0; // keep the compiler happy
}