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
}