summaryrefslogtreecommitdiff
path: root/src/uqm/gameopt.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/uqm/gameopt.c')
-rw-r--r--src/uqm/gameopt.c1347
1 files changed, 1347 insertions, 0 deletions
diff --git a/src/uqm/gameopt.c b/src/uqm/gameopt.c
new file mode 100644
index 0000000..ba42a3b
--- /dev/null
+++ b/src/uqm/gameopt.c
@@ -0,0 +1,1347 @@
+//Copyright Paul Reiche, Fred Ford. 1992-2002
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "gameopt.h"
+
+#include "build.h"
+#include "colors.h"
+#include "controls.h"
+#include "starmap.h"
+#include "menustat.h"
+#include "sis.h"
+#include "units.h"
+#include "gamestr.h"
+#include "options.h"
+#include "save.h"
+#include "settings.h"
+#include "setup.h"
+#include "sounds.h"
+#include "util.h"
+#include "libs/graphics/gfx_common.h"
+
+#include <ctype.h>
+
+extern FRAME PlayFrame;
+
+#define MAX_SAVED_GAMES 50
+#define SUMMARY_X_OFFS 14
+#define SUMMARY_SIDE_OFFS 7
+#define SAVES_PER_PAGE 5
+
+#define MAX_NAME_SIZE SIS_NAME_SIZE
+
+static COUNT lastUsedSlot;
+
+static NamingCallback *namingCB;
+
+void
+ConfirmSaveLoad (STAMP *MsgStamp)
+{
+ RECT r, clip_r;
+ TEXT t;
+
+ SetContextFont (StarConFont);
+ GetContextClipRect (&clip_r);
+
+ t.baseline.x = clip_r.extent.width >> 1;
+ t.baseline.y = (clip_r.extent.height >> 1) + 3;
+ t.align = ALIGN_CENTER;
+ t.CharCount = (COUNT)~0;
+ if (MsgStamp)
+ t.pStr = GAME_STRING (SAVEGAME_STRING_BASE + 0);
+ // "Saving . . ."
+ else
+ t.pStr = GAME_STRING (SAVEGAME_STRING_BASE + 1);
+ // "Loading . . ."
+ TextRect (&t, &r, NULL);
+ r.corner.x -= 4;
+ r.corner.y -= 4;
+ r.extent.width += 8;
+ r.extent.height += 8;
+ if (MsgStamp)
+ {
+ *MsgStamp = SaveContextFrame (&r);
+ }
+ DrawStarConBox (&r, 2,
+ BUILD_COLOR (MAKE_RGB15 (0x10, 0x10, 0x10), 0x19),
+ BUILD_COLOR (MAKE_RGB15 (0x08, 0x08, 0x08), 0x1F),
+ TRUE, BUILD_COLOR (MAKE_RGB15 (0x0A, 0x0A, 0x0A), 0x08));
+ SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x14, 0x14, 0x14), 0x0F));
+ font_DrawText (&t);
+}
+
+enum
+{
+ SAVE_GAME = 0,
+ LOAD_GAME,
+ QUIT_GAME,
+ SETTINGS,
+ EXIT_GAME_MENU,
+};
+
+enum
+{
+ SOUND_ON_SETTING,
+ SOUND_OFF_SETTING,
+ MUSIC_ON_SETTING,
+ MUSIC_OFF_SETTING,
+ CYBORG_OFF_SETTING,
+ CYBORG_NORMAL_SETTING,
+ CYBORG_DOUBLE_SETTING,
+ CYBORG_SUPER_SETTING,
+ CHANGE_CAPTAIN_SETTING,
+ CHANGE_SHIP_SETTING,
+ EXIT_SETTINGS_MENU,
+};
+
+static void
+FeedbackSetting (BYTE which_setting)
+{
+ UNICODE buf[128];
+ const char *tmpstr;
+
+ buf[0] = '\0';
+ // pre-terminate buffer in case snprintf() overflows
+ buf[sizeof (buf) - 1] = '\0';
+
+ switch (which_setting)
+ {
+ case SOUND_ON_SETTING:
+ case SOUND_OFF_SETTING:
+ snprintf (buf, sizeof (buf) - 1, "%s %s",
+ GAME_STRING (OPTION_STRING_BASE + 0),
+ GLOBAL (glob_flags) & SOUND_DISABLED
+ ? GAME_STRING (OPTION_STRING_BASE + 3) :
+ GAME_STRING (OPTION_STRING_BASE + 4));
+ break;
+ case MUSIC_ON_SETTING:
+ case MUSIC_OFF_SETTING:
+ snprintf (buf, sizeof (buf) - 1, "%s %s",
+ GAME_STRING (OPTION_STRING_BASE + 1),
+ GLOBAL (glob_flags) & MUSIC_DISABLED
+ ? GAME_STRING (OPTION_STRING_BASE + 3) :
+ GAME_STRING (OPTION_STRING_BASE + 4));
+ break;
+ case CYBORG_OFF_SETTING:
+ case CYBORG_NORMAL_SETTING:
+ case CYBORG_DOUBLE_SETTING:
+ case CYBORG_SUPER_SETTING:
+ if (optWhichMenu == OPT_PC &&
+ which_setting > CYBORG_NORMAL_SETTING)
+ {
+ if (which_setting == CYBORG_DOUBLE_SETTING)
+ tmpstr = "+";
+ else
+ tmpstr = "++";
+ }
+ else
+ tmpstr = "";
+ snprintf (buf, sizeof (buf) - 1, "%s %s%s",
+ GAME_STRING (OPTION_STRING_BASE + 2),
+ !(GLOBAL (glob_flags) & CYBORG_ENABLED)
+ ? GAME_STRING (OPTION_STRING_BASE + 3) :
+ GAME_STRING (OPTION_STRING_BASE + 4),
+ tmpstr);
+ break;
+ case CHANGE_CAPTAIN_SETTING:
+ case CHANGE_SHIP_SETTING:
+ utf8StringCopy (buf, sizeof (buf),
+ GAME_STRING (NAMING_STRING_BASE + 0));
+ break;
+ }
+
+ DrawStatusMessage (buf);
+}
+
+#define DDSHS_NORMAL 0
+#define DDSHS_EDIT 1
+#define DDSHS_BLOCKCUR 2
+
+static const RECT captainNameRect = {
+ /* .corner = */ {
+ /* .x = */ 3,
+ /* .y = */ 10
+ }, /* .extent = */ {
+ /* .width = */ SHIP_NAME_WIDTH - 2,
+ /* .height = */ SHIP_NAME_HEIGHT
+ }
+};
+static const RECT shipNameRect = {
+ /* .corner = */ {
+ /* .x = */ 2,
+ /* .y = */ 20
+ }, /* .extent = */ {
+ /* .width = */ SHIP_NAME_WIDTH,
+ /* .height = */ SHIP_NAME_HEIGHT
+ }
+};
+
+
+static BOOLEAN
+DrawNameString (bool nameCaptain, UNICODE *Str, COUNT CursorPos,
+ COUNT state)
+{
+ RECT r;
+ TEXT lf;
+ Color BackGround, ForeGround;
+ FONT Font;
+
+ {
+ if (nameCaptain)
+ { // Naming the captain
+ Font = TinyFont;
+ r = captainNameRect;
+ lf.baseline.x = r.corner.x + (r.extent.width >> 1) - 1;
+
+ BackGround = BUILD_COLOR (MAKE_RGB15 (0x0A, 0x0A, 0x1F), 0x09);
+ ForeGround = BUILD_COLOR (MAKE_RGB15 (0x0A, 0x1F, 0x1F), 0x0B);
+ }
+ else
+ { // Naming the flagship
+ Font = StarConFont;
+ r = shipNameRect;
+ lf.baseline.x = r.corner.x + (r.extent.width >> 1);
+
+ BackGround = BUILD_COLOR (MAKE_RGB15 (0x0F, 0x00, 0x00), 0x2D);
+ ForeGround = BUILD_COLOR (MAKE_RGB15 (0x1F, 0x0A, 0x00), 0x7D);
+ }
+
+ lf.baseline.y = r.corner.y + r.extent.height - 1;
+ lf.align = ALIGN_CENTER;
+ }
+
+ SetContext (StatusContext);
+ SetContextFont (Font);
+ lf.pStr = Str;
+ lf.CharCount = (COUNT)~0;
+
+ if (!(state & DDSHS_EDIT))
+ { // normal state
+ if (nameCaptain)
+ DrawCaptainsName ();
+ else
+ DrawFlagshipName (TRUE);
+ }
+ else
+ { // editing state
+ COUNT i;
+ RECT text_r;
+ BYTE char_deltas[MAX_NAME_SIZE];
+ BYTE *pchar_deltas;
+
+ TextRect (&lf, &text_r, char_deltas);
+ if ((text_r.extent.width + 2) >= r.extent.width)
+ { // the text does not fit the input box size and so
+ // will not fit when displayed later
+ // disallow the change
+ return (FALSE);
+ }
+
+ PreUpdateFlashRect ();
+
+ SetContextForeGroundColor (BackGround);
+ DrawFilledRectangle (&r);
+
+ pchar_deltas = char_deltas;
+ for (i = CursorPos; i > 0; --i)
+ text_r.corner.x += *pchar_deltas++;
+ if (CursorPos < lf.CharCount) /* end of line */
+ --text_r.corner.x;
+
+ if (state & DDSHS_BLOCKCUR)
+ { // Use block cursor for keyboardless systems
+ if (CursorPos == lf.CharCount)
+ { // cursor at end-line -- use insertion point
+ text_r.extent.width = 1;
+ }
+ else if (CursorPos + 1 == lf.CharCount)
+ { // extra pixel for last char margin
+ text_r.extent.width = (SIZE)*pchar_deltas + 2;
+ }
+ else
+ { // normal mid-line char
+ text_r.extent.width = (SIZE)*pchar_deltas + 1;
+ }
+ }
+ else
+ { // Insertion point cursor
+ text_r.extent.width = 1;
+ }
+
+ text_r.corner.y = r.corner.y;
+ text_r.extent.height = r.extent.height;
+ SetContextForeGroundColor (BLACK_COLOR);
+ DrawFilledRectangle (&text_r);
+
+ SetContextForeGroundColor (ForeGround);
+ font_DrawText (&lf);
+
+ PostUpdateFlashRect ();
+ }
+
+ return (TRUE);
+}
+
+static BOOLEAN
+OnNameChange (TEXTENTRY_STATE *pTES)
+{
+ bool nameCaptain = (bool) pTES->CbParam;
+ COUNT hl = DDSHS_EDIT;
+
+ if (pTES->JoystickMode)
+ hl |= DDSHS_BLOCKCUR;
+
+ return DrawNameString (nameCaptain, pTES->BaseStr, pTES->CursorPos, hl);
+}
+
+static void
+NameCaptainOrShip (bool nameCaptain)
+{
+ UNICODE buf[MAX_NAME_SIZE] = "";
+ TEXTENTRY_STATE tes;
+ UNICODE *Setting;
+
+ SetContext (StatusContext);
+ SetFlashRect (nameCaptain ? &captainNameRect : &shipNameRect);
+
+ DrawNameString (nameCaptain, buf, 0, DDSHS_EDIT);
+
+ DrawStatusMessage (GAME_STRING (NAMING_STRING_BASE + 0));
+
+ if (nameCaptain)
+ {
+ Setting = GLOBAL_SIS (CommanderName);
+ tes.MaxSize = sizeof (GLOBAL_SIS (CommanderName));
+ }
+ else
+ {
+ Setting = GLOBAL_SIS (ShipName);
+ tes.MaxSize = sizeof (GLOBAL_SIS (ShipName));
+ }
+
+ // text entry setup
+ tes.Initialized = FALSE;
+ tes.BaseStr = buf;
+ tes.CursorPos = 0;
+ tes.CbParam = (void*) nameCaptain;
+ tes.ChangeCallback = OnNameChange;
+ tes.FrameCallback = 0;
+
+ if (DoTextEntry (&tes))
+ utf8StringCopy (Setting, tes.MaxSize, buf);
+ else
+ utf8StringCopy (buf, sizeof (buf), Setting);
+
+ SetFlashRect (SFR_MENU_3DO);
+
+ DrawNameString (nameCaptain, buf, 0, DDSHS_NORMAL);
+
+ if (namingCB)
+ namingCB ();
+}
+
+static BOOLEAN
+DrawSaveNameString (UNICODE *Str, COUNT CursorPos, COUNT state, COUNT gameIndex)
+{
+ RECT r;
+ TEXT lf;
+ Color BackGround, ForeGround;
+ FONT Font;
+ UNICODE fullStr[256], dateStr[80];
+
+ DateToString (dateStr, sizeof dateStr, GLOBAL(GameClock.month_index),
+ GLOBAL(GameClock.day_index), GLOBAL(GameClock.year_index));
+ strncat (dateStr, ": ", sizeof(dateStr) - strlen(dateStr) -1);
+ snprintf (fullStr, sizeof fullStr, "%s%s", dateStr, Str);
+
+ SetContextForeGroundColor (BUILD_COLOR (MAKE_RGB15 (0x1B, 0x00, 0x1B), 0x33));
+ r.extent.width = 15;
+ if (MAX_SAVED_GAMES > 99)
+ r.extent.width += 5;
+ r.extent.height = 11;
+ r.corner.x = 8;
+ r.corner.y = (160 + ((gameIndex % SAVES_PER_PAGE) * 13));
+ DrawRectangle (&r);
+
+ r.extent.width = (204 - SAFE_X);
+ r.corner.x = (30 + SAFE_X);
+ DrawRectangle (&r);
+
+ Font = TinyFont;
+ lf.baseline.x = r.corner.x + 3;
+ lf.baseline.y = r.corner.y + 8;
+
+ BackGround = BUILD_COLOR (MAKE_RGB15 (0x1B, 0x00, 0x1B), 0x33);
+ ForeGround = BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x14), 0x01);
+
+ lf.align = ALIGN_LEFT;
+
+ SetContextFont (Font);
+ lf.pStr = fullStr;
+ lf.CharCount = (COUNT)~0;
+
+ if (!(state & DDSHS_EDIT))
+ {
+ TEXT t;
+
+ SetContextForeGroundColor (BLACK_COLOR);
+ DrawFilledRectangle (&r);
+
+ t.baseline.x = r.corner.x + 3;
+ t.baseline.y = r.corner.y + 8;
+ t.align = ALIGN_LEFT;
+ t.pStr = Str;
+ t.CharCount = (COUNT)~0;
+ SetContextForeGroundColor (CAPTAIN_NAME_TEXT_COLOR);
+ font_DrawText (&lf);
+ }
+ else
+ { // editing state
+ COUNT i, FullCursorPos;
+ RECT text_r;
+ BYTE char_deltas[256];
+ BYTE *pchar_deltas;
+
+ TextRect (&lf, &text_r, char_deltas);
+ if ((text_r.extent.width + 2) >= r.extent.width)
+ { // the text does not fit the input box size and so
+ // will not fit when displayed later
+ // disallow the change
+ return (FALSE);
+ }
+
+ PreUpdateFlashRect ();
+
+ SetContextForeGroundColor (BackGround);
+ DrawFilledRectangle (&r);
+
+ pchar_deltas = char_deltas;
+
+ FullCursorPos = CursorPos + strlen(dateStr) - 1;
+ for (i = FullCursorPos; i > 0; --i)
+ text_r.corner.x += *pchar_deltas++;
+
+ if (FullCursorPos < lf.CharCount) /* end of line */
+ --text_r.corner.x;
+
+ if (state & DDSHS_BLOCKCUR)
+ { // Use block cursor for keyboardless systems
+ if (FullCursorPos == lf.CharCount)
+ { // cursor at end-line -- use insertion point
+ text_r.extent.width = 1;
+ }
+ else if (FullCursorPos + 1 == lf.CharCount)
+ { // extra pixel for last char margin
+ text_r.extent.width = (SIZE)*pchar_deltas + 2;
+ }
+ else
+ { // normal mid-line char
+ text_r.extent.width = (SIZE)*pchar_deltas + 1;
+ }
+ }
+ else
+ { // Insertion point cursor
+ text_r.extent.width = 1;
+ }
+
+ text_r.corner.y = r.corner.y;
+ text_r.extent.height = r.extent.height;
+ SetContextForeGroundColor (BLACK_COLOR);
+ DrawFilledRectangle (&text_r);
+
+ SetContextForeGroundColor (ForeGround);
+ font_DrawText (&lf);
+ PostUpdateFlashRect ();
+ }
+
+ return (TRUE);
+}
+
+static BOOLEAN
+OnSaveNameChange (TEXTENTRY_STATE *pTES)
+{
+ COUNT hl = DDSHS_EDIT;
+ COUNT *gameIndex = pTES->CbParam;
+
+ if (pTES->JoystickMode)
+ hl |= DDSHS_BLOCKCUR;
+
+ return DrawSaveNameString (pTES->BaseStr, pTES->CursorPos, hl, *gameIndex);
+}
+
+static BOOLEAN
+NameSaveGame (COUNT gameIndex, UNICODE *buf)
+{
+ TEXTENTRY_STATE tes;
+ COUNT CursPos = strlen(buf);
+ COUNT *gIndex = HMalloc (sizeof (COUNT));
+ RECT r;
+ *gIndex = gameIndex;
+
+ DrawSaveNameString (buf, CursPos, DDSHS_EDIT, gameIndex);
+
+ tes.MaxSize = SAVE_NAME_SIZE;
+
+ // text entry setup
+ tes.Initialized = FALSE;
+ tes.BaseStr = buf;
+ tes.CursorPos = CursPos;
+ tes.CbParam = gIndex;
+ tes.ChangeCallback = OnSaveNameChange;
+ tes.FrameCallback = 0;
+ r.extent.width = (204 - SAFE_X);
+ r.extent.height = 11;
+ r.corner.x = (30 + SAFE_X);
+ r.corner.y = (160 + ((gameIndex % SAVES_PER_PAGE) * 13));
+ SetFlashRect (&r);
+
+ if (!DoTextEntry (&tes))
+ buf[0] = 0;
+
+ SetFlashRect(NULL);
+
+ DrawSaveNameString (buf, CursPos, DDSHS_NORMAL, gameIndex);
+
+ if (namingCB)
+ namingCB ();
+
+ SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT);
+
+ HFree (gIndex);
+
+ SetFlashRect (NULL);
+
+ if (tes.Success)
+ return (TRUE);
+ else
+ return (FALSE);
+}
+
+void
+SetNamingCallback (NamingCallback *callback)
+{
+ namingCB = callback;
+}
+
+static BOOLEAN
+DoSettings (MENU_STATE *pMS)
+{
+ BYTE cur_speed;
+
+ if (GLOBAL (CurrentActivity) & CHECK_ABORT)
+ return FALSE;
+
+ cur_speed = (GLOBAL (glob_flags) & COMBAT_SPEED_MASK) >> COMBAT_SPEED_SHIFT;
+
+ if (PulsedInputState.menu[KEY_MENU_CANCEL]
+ || (PulsedInputState.menu[KEY_MENU_SELECT]
+ && pMS->CurState == EXIT_SETTINGS_MENU))
+ {
+ return FALSE;
+ }
+ else if (PulsedInputState.menu[KEY_MENU_SELECT])
+ {
+ switch (pMS->CurState)
+ {
+ case SOUND_ON_SETTING:
+ case SOUND_OFF_SETTING:
+ ToggleSoundEffect ();
+ pMS->CurState ^= 1;
+ DrawMenuStateStrings (PM_SOUND_ON, pMS->CurState);
+ break;
+ case MUSIC_ON_SETTING:
+ case MUSIC_OFF_SETTING:
+ ToggleMusic ();
+ pMS->CurState ^= 1;
+ DrawMenuStateStrings (PM_SOUND_ON, pMS->CurState);
+ break;
+ case CHANGE_CAPTAIN_SETTING:
+ case CHANGE_SHIP_SETTING:
+ NameCaptainOrShip (pMS->CurState == CHANGE_CAPTAIN_SETTING);
+ break;
+ default:
+ if (cur_speed++ < NUM_COMBAT_SPEEDS - 1)
+ GLOBAL (glob_flags) |= CYBORG_ENABLED;
+ else
+ {
+ cur_speed = 0;
+ GLOBAL (glob_flags) &= ~CYBORG_ENABLED;
+ }
+ GLOBAL (glob_flags) =
+ ((GLOBAL (glob_flags) & ~COMBAT_SPEED_MASK)
+ | (cur_speed << COMBAT_SPEED_SHIFT));
+ pMS->CurState = CYBORG_OFF_SETTING + cur_speed;
+ DrawMenuStateStrings (PM_SOUND_ON, pMS->CurState);
+ }
+
+ FeedbackSetting (pMS->CurState);
+ }
+ else if (DoMenuChooser (pMS, PM_SOUND_ON))
+ FeedbackSetting (pMS->CurState);
+
+ return TRUE;
+}
+
+static void
+SettingsMenu (void)
+{
+ MENU_STATE MenuState;
+
+ memset (&MenuState, 0, sizeof MenuState);
+ MenuState.CurState = SOUND_ON_SETTING;
+
+ DrawMenuStateStrings (PM_SOUND_ON, MenuState.CurState);
+ FeedbackSetting (MenuState.CurState);
+
+ MenuState.InputFunc = DoSettings;
+ DoInput (&MenuState, FALSE);
+
+ DrawStatusMessage (NULL);
+}
+
+typedef struct
+{
+ SUMMARY_DESC summary[MAX_SAVED_GAMES];
+ BOOLEAN saving;
+ // TRUE when saving, FALSE when loading
+ BOOLEAN success;
+ // TRUE when load/save succeeded
+ FRAME SummaryFrame;
+
+} PICK_GAME_STATE;
+
+static void
+DrawBlankSavegameDisplay (PICK_GAME_STATE *pickState)
+{
+ STAMP s;
+
+ s.origin.x = 0;
+ s.origin.y = 0;
+ s.frame = SetAbsFrameIndex (pickState->SummaryFrame,
+ GetFrameCount (pickState->SummaryFrame) - 1);
+ DrawStamp (&s);
+}
+
+static void
+DrawSaveLoad (PICK_GAME_STATE *pickState)
+{
+ STAMP s;
+
+ s.origin.x = SUMMARY_X_OFFS + 1;
+ s.origin.y = 0;
+ s.frame = SetAbsFrameIndex (pickState->SummaryFrame,
+ GetFrameCount (pickState->SummaryFrame) - 2);
+ if (pickState->saving)
+ s.frame = DecFrameIndex (s.frame);
+ DrawStamp (&s);
+}
+
+static void
+DrawSavegameCargo (SIS_STATE *sisState)
+{
+ COUNT i;
+ STAMP s;
+ TEXT t;
+ UNICODE buf[40];
+ static const Color cargo_color[] =
+ {
+ BUILD_COLOR (MAKE_RGB15_INIT (0x02, 0x0E, 0x13), 0x00),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x19, 0x00, 0x00), 0x00),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x10, 0x10, 0x10), 0x00),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x03, 0x05, 0x1E), 0x00),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x00, 0x18, 0x00), 0x00),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1B, 0x1B, 0x00), 0x00),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x1E, 0x0D, 0x00), 0x00),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x14, 0x00, 0x14), 0x05),
+ BUILD_COLOR (MAKE_RGB15_INIT (0x0F, 0x00, 0x19), 0x00),
+ };
+#define ELEMENT_ORG_Y 17
+#define ELEMENT_SPACING_Y 12
+#define ELEMENT_SPACING_X 36
+
+ SetContext (SpaceContext);
+ BatchGraphics ();
+ SetContextFont (StarConFont);
+
+ // setup element icons
+ s.frame = SetAbsFrameIndex (MiscDataFrame,
+ (NUM_SCANDOT_TRANSITIONS << 1) + 3);
+ s.origin.x = 7 + SUMMARY_X_OFFS - SUMMARY_SIDE_OFFS + 3;
+ s.origin.y = ELEMENT_ORG_Y;
+ // setup element amounts
+ t.baseline.x = 33 + SUMMARY_X_OFFS - SUMMARY_SIDE_OFFS + 3;
+ t.baseline.y = ELEMENT_ORG_Y + 3;
+ t.align = ALIGN_RIGHT;
+ t.pStr = buf;
+
+ // draw element icons and amounts
+ for (i = 0; i < NUM_ELEMENT_CATEGORIES; ++i)
+ {
+ if (i == NUM_ELEMENT_CATEGORIES / 2)
+ {
+ s.origin.x += ELEMENT_SPACING_X;
+ s.origin.y = ELEMENT_ORG_Y;
+ t.baseline.x += ELEMENT_SPACING_X;
+ t.baseline.y = ELEMENT_ORG_Y + 3;
+ }
+ // draw element icon
+ DrawStamp (&s);
+ s.frame = SetRelFrameIndex (s.frame, 5);
+ s.origin.y += ELEMENT_SPACING_Y;
+ // print element amount
+ SetContextForeGroundColor (cargo_color[i]);
+ snprintf (buf, sizeof buf, "%u", sisState->ElementAmounts[i]);
+ t.CharCount = (COUNT)~0;
+ font_DrawText (&t);
+ t.baseline.y += ELEMENT_SPACING_Y;
+ }
+
+ // draw Bio icon
+ s.origin.x = 24 + SUMMARY_X_OFFS - SUMMARY_SIDE_OFFS;
+ s.origin.y = 68;
+ s.frame = SetAbsFrameIndex (s.frame, 68);
+ DrawStamp (&s);
+ // print Bio amount
+ t.baseline.x = 50 + SUMMARY_X_OFFS;
+ t.baseline.y = s.origin.y + 3;
+ SetContextForeGroundColor (cargo_color[i]);
+ snprintf (buf, sizeof buf, "%u", sisState->TotalBioMass);
+ t.CharCount = (COUNT)~0;
+ font_DrawText (&t);
+
+ UnbatchGraphics ();
+}
+
+static void
+DrawSavegameSummary (PICK_GAME_STATE *pickState, COUNT gameIndex)
+{
+ SUMMARY_DESC *pSD = pickState->summary + gameIndex;
+ RECT r;
+ STAMP s;
+
+ BatchGraphics ();
+
+ if (pSD->year_index == 0)
+ {
+ // Unused save slot, draw 'Empty Game' message.
+ s.origin.x = 0;
+ s.origin.y = 0;
+ s.frame = SetAbsFrameIndex (pickState->SummaryFrame,
+ GetFrameCount (pickState->SummaryFrame) - 4);
+ DrawStamp (&s);
+ }
+ else
+ {
+ // Game slot used, draw information about save game.
+ COUNT i;
+ RECT OldRect;
+ TEXT t;
+ QUEUE player_q;
+ CONTEXT OldContext;
+ SIS_STATE SaveSS;
+ UNICODE buf[256];
+ POINT starPt;
+
+ // Save the states because we will hack them
+ SaveSS = GlobData.SIS_state;
+ player_q = GLOBAL (built_ship_q);
+
+ OldContext = SetContext (StatusContext);
+ // Hack StatusContext so we can use standard SIS display funcs
+ GetContextClipRect (&OldRect);
+ r.corner.x = SIS_ORG_X + ((SIS_SCREEN_WIDTH - STATUS_WIDTH) >> 1) +
+ SAFE_X - 16 + SUMMARY_X_OFFS;
+// r.corner.x = SIS_ORG_X + ((SIS_SCREEN_WIDTH - STATUS_WIDTH) >> 1);
+ r.corner.y = SIS_ORG_Y;
+ r.extent.width = STATUS_WIDTH;
+ r.extent.height = STATUS_HEIGHT;
+ SetContextClipRect (&r);
+
+ // Hack the states so that we can use standard SIS display funcs
+ GlobData.SIS_state = pSD->SS;
+ InitQueue (&GLOBAL (built_ship_q),
+ MAX_BUILT_SHIPS, sizeof (SHIP_FRAGMENT));
+ for (i = 0; i < pSD->NumShips; ++i)
+ CloneShipFragment (pSD->ShipList[i], &GLOBAL (built_ship_q), 0);
+ DateToString (buf, sizeof buf,
+ pSD->month_index, pSD->day_index, pSD->year_index),
+ ClearSISRect (DRAW_SIS_DISPLAY);
+ DrawStatusMessage (buf);
+ UninitQueue (&GLOBAL (built_ship_q));
+
+ SetContextClipRect (&OldRect);
+
+ SetContext (SpaceContext);
+ // draw devices
+ s.origin.y = 13;
+ for (i = 0; i < 4; ++i)
+ {
+ COUNT j;
+
+ s.origin.x = 140 + SUMMARY_X_OFFS + SUMMARY_SIDE_OFFS;
+ for (j = 0; j < 4; ++j)
+ {
+ COUNT devIndex = (i * 4) + j;
+ if (devIndex < pSD->NumDevices)
+ {
+ s.frame = SetAbsFrameIndex (MiscDataFrame, 77
+ + pSD->DeviceList[devIndex]);
+ DrawStamp (&s);
+ }
+ s.origin.x += 18;
+ }
+ s.origin.y += 18;
+ }
+
+ SetContextFont (StarConFont);
+ t.baseline.x = 173 + SUMMARY_X_OFFS + SUMMARY_SIDE_OFFS;
+ t.align = ALIGN_CENTER;
+ t.CharCount = (COUNT)~0;
+ t.pStr = buf;
+ if (pSD->Flags & AFTER_BOMB_INSTALLED)
+ {
+ // draw the bomb and the escape pod
+ s.origin.x = SUMMARY_X_OFFS - SUMMARY_SIDE_OFFS + 6;
+ s.origin.y = 0;
+ s.frame = SetRelFrameIndex (pickState->SummaryFrame, 0);
+ DrawStamp (&s);
+ // draw RU "NO LIMIT"
+ s.origin.x = SUMMARY_X_OFFS + SUMMARY_SIDE_OFFS;
+ s.frame = IncFrameIndex (s.frame);
+ DrawStamp (&s);
+ }
+ else
+ {
+ DrawSavegameCargo (&pSD->SS);
+
+ SetContext (RadarContext);
+ // Hack RadarContext so we can use standard Lander display funcs
+ GetContextClipRect (&OldRect);
+ r.corner.x = SIS_ORG_X + 10 + SUMMARY_X_OFFS - SUMMARY_SIDE_OFFS;
+ r.corner.y = SIS_ORG_Y + 84;
+ r.extent = OldRect.extent;
+ SetContextClipRect (&r);
+ // draw the lander with upgrades
+ InitLander (pSD->Flags | OVERRIDE_LANDER_FLAGS);
+ SetContextClipRect (&OldRect);
+ SetContext (SpaceContext);
+
+ snprintf (buf, sizeof buf, "%u", pSD->SS.ResUnits);
+ t.baseline.y = 102;
+ SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x10, 0x00, 0x10), 0x01));
+ font_DrawText (&t);
+ t.CharCount = (COUNT)~0;
+ }
+ t.baseline.y = 126;
+ snprintf (buf, sizeof buf, "%u",
+ MAKE_WORD (pSD->MCreditLo, pSD->MCreditHi));
+ SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x10, 0x00, 0x10), 0x01));
+ font_DrawText (&t);
+
+ // print the location
+ t.baseline.x = 6;
+ t.baseline.y = 139 + 6;
+ t.align = ALIGN_LEFT;
+ t.pStr = buf;
+ starPt.x = LOGX_TO_UNIVERSE (pSD->SS.log_x);
+ starPt.y = LOGY_TO_UNIVERSE (pSD->SS.log_y);
+ switch (pSD->Activity)
+ {
+ case IN_LAST_BATTLE:
+ case IN_INTERPLANETARY:
+ case IN_PLANET_ORBIT:
+ case IN_STARBASE:
+ {
+ BYTE QuasiState;
+ STAR_DESC *SDPtr;
+
+ QuasiState = GET_GAME_STATE (ARILOU_SPACE_SIDE);
+ SET_GAME_STATE (ARILOU_SPACE_SIDE, 0);
+ SDPtr = FindStar (NULL, &starPt, 1, 1);
+ SET_GAME_STATE (ARILOU_SPACE_SIDE, QuasiState);
+ if (SDPtr)
+ {
+ GetClusterName (SDPtr, buf);
+ starPt = SDPtr->star_pt;
+ break;
+ }
+ }
+ default:
+ buf[0] = '\0';
+ break;
+ case IN_HYPERSPACE:
+ utf8StringCopy (buf, sizeof (buf),
+ GAME_STRING (NAVIGATION_STRING_BASE + 0));
+ break;
+ case IN_QUASISPACE:
+ utf8StringCopy (buf, sizeof (buf),
+ GAME_STRING (NAVIGATION_STRING_BASE + 1));
+ break;
+ }
+
+ SetContextFont (TinyFont);
+ SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x1B, 0x00, 0x1B), 0x33));
+ t.CharCount = (COUNT)~0;
+ font_DrawText (&t);
+ t.align = ALIGN_CENTER;
+ t.baseline.x = SIS_SCREEN_WIDTH - SIS_TITLE_BOX_WIDTH - 4
+ + (SIS_TITLE_WIDTH >> 1);
+ switch (pSD->Activity)
+ {
+ case IN_STARBASE:
+ utf8StringCopy (buf, sizeof (buf), // Starbase
+ GAME_STRING (STARBASE_STRING_BASE));
+ break;
+ case IN_LAST_BATTLE:
+ utf8StringCopy (buf, sizeof (buf), // Sa-Matra
+ GAME_STRING (PLANET_NUMBER_BASE + 32));
+ break;
+ case IN_PLANET_ORBIT:
+ utf8StringCopy (buf, sizeof (buf), pSD->SS.PlanetName);
+ break;
+ default:
+ snprintf (buf, sizeof buf, "%03u.%01u : %03u.%01u",
+ starPt.x / 10, starPt.x % 10,
+ starPt.y / 10, starPt.y % 10);
+ }
+ t.CharCount = (COUNT)~0;
+ font_DrawText (&t);
+
+ SetContext (OldContext);
+
+ // Restore the states because we hacked them
+ GLOBAL (built_ship_q) = player_q;
+ GlobData.SIS_state = SaveSS;
+ }
+
+ UnbatchGraphics ();
+}
+
+static void
+DrawGameSelection (PICK_GAME_STATE *pickState, COUNT selSlot)
+{
+ RECT r;
+ TEXT t;
+ COUNT i;
+ COUNT curSlot;
+ UNICODE buf[256];
+ UNICODE buf2[80];
+
+ BatchGraphics ();
+
+ SetContextFont (TinyFont);
+
+ // Erase the selection menu
+ r.extent.width = 240;
+ r.extent.height = 65;
+ r.corner.x = 1;
+ r.corner.y = 160;
+ SetContextForeGroundColor (BLACK_COLOR);
+ DrawFilledRectangle (&r);
+
+ t.CharCount = (COUNT)~0;
+ t.pStr = buf;
+ t.align = ALIGN_LEFT;
+
+ // Draw savegame slots info
+ curSlot = selSlot - (selSlot % SAVES_PER_PAGE);
+ for (i = 0; i < SAVES_PER_PAGE && curSlot < MAX_SAVED_GAMES;
+ ++i, ++curSlot)
+ {
+ SUMMARY_DESC *desc = &pickState->summary[curSlot];
+
+ SetContextForeGroundColor ((curSlot == selSlot) ?
+ (BUILD_COLOR (MAKE_RGB15 (0x1B, 0x00, 0x1B), 0x33)):
+ (BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x14), 0x01)));
+ r.extent.width = 15;
+ if (MAX_SAVED_GAMES > 99)
+ r.extent.width += 5;
+ r.extent.height = 11;
+ r.corner.x = 8;
+ r.corner.y = 160 + (i * 13);
+ DrawRectangle (&r);
+
+ t.baseline.x = r.corner.x + 3;
+ t.baseline.y = r.corner.y + 8;
+ snprintf (buf, sizeof buf, (MAX_SAVED_GAMES > 99) ? "%03u" : "%02u",
+ curSlot);
+ font_DrawText (&t);
+
+ r.extent.width = 204 - SAFE_X;
+ r.corner.x = 30 + SAFE_X;
+ DrawRectangle (&r);
+
+ t.baseline.x = r.corner.x + 3;
+ if (desc->year_index == 0)
+ {
+ utf8StringCopy (buf, sizeof buf,
+ GAME_STRING (SAVEGAME_STRING_BASE + 3)); // "Empty Slot"
+ }
+ else
+ {
+ DateToString (buf2, sizeof buf2, desc->month_index,
+ desc->day_index, desc->year_index);
+ snprintf (buf, sizeof buf, "%s: %s", buf2, desc->SaveName[0] ? desc->SaveName : GAME_STRING (SAVEGAME_STRING_BASE + 4));
+ }
+ font_DrawText (&t);
+ }
+
+ UnbatchGraphics ();
+}
+
+static void
+RedrawPickDisplay (PICK_GAME_STATE *pickState, COUNT selSlot)
+{
+ BatchGraphics ();
+ DrawBlankSavegameDisplay (pickState);
+ DrawSavegameSummary (pickState, selSlot);
+ DrawGameSelection (pickState, selSlot);
+ UnbatchGraphics ();
+}
+
+static void
+LoadGameDescriptions (SUMMARY_DESC *pSD)
+{
+ COUNT i;
+
+ for (i = 0; i < MAX_SAVED_GAMES; ++i, ++pSD)
+ {
+ if (!LoadGame (i, pSD))
+ pSD->year_index = 0;
+ }
+}
+
+static BOOLEAN
+DoPickGame (MENU_STATE *pMS)
+{
+ PICK_GAME_STATE *pickState = pMS->privData;
+ BYTE NewState;
+ SUMMARY_DESC *pSD;
+ DWORD TimeIn = GetTimeCounter ();
+
+ if (GLOBAL (CurrentActivity) & CHECK_ABORT)
+ return FALSE;
+
+ if (PulsedInputState.menu[KEY_MENU_CANCEL])
+ {
+ pickState->success = FALSE;
+ return FALSE;
+ }
+ else if (PulsedInputState.menu[KEY_MENU_SELECT])
+ {
+ pSD = &pickState->summary[pMS->CurState];
+ if (pickState->saving || pSD->year_index)
+ { // valid slot
+ PlayMenuSound (MENU_SOUND_SUCCESS);
+ pickState->success = TRUE;
+ return FALSE;
+ }
+ PlayMenuSound (MENU_SOUND_FAILURE);
+ }
+ else
+ {
+ NewState = pMS->CurState;
+ if (PulsedInputState.menu[KEY_MENU_LEFT]
+ || PulsedInputState.menu[KEY_MENU_PAGE_UP])
+ {
+ if (NewState == 0)
+ NewState = MAX_SAVED_GAMES - 1;
+ else if ((NewState - SAVES_PER_PAGE) > 0)
+ NewState -= SAVES_PER_PAGE;
+ else
+ NewState = 0;
+ }
+ else if (PulsedInputState.menu[KEY_MENU_RIGHT]
+ || PulsedInputState.menu[KEY_MENU_PAGE_DOWN])
+ {
+ if (NewState == MAX_SAVED_GAMES - 1)
+ NewState = 0;
+ else if ((NewState + SAVES_PER_PAGE) < MAX_SAVED_GAMES - 1)
+ NewState += SAVES_PER_PAGE;
+ else
+ NewState = MAX_SAVED_GAMES - 1;
+ }
+ else if (PulsedInputState.menu[KEY_MENU_UP])
+ {
+ if (NewState == 0)
+ NewState = MAX_SAVED_GAMES - 1;
+ else
+ NewState--;
+ }
+ else if (PulsedInputState.menu[KEY_MENU_DOWN])
+ {
+ if (NewState == MAX_SAVED_GAMES - 1)
+ NewState = 0;
+ else
+ NewState++;
+ }
+
+ if (NewState != pMS->CurState)
+ {
+ pMS->CurState = NewState;
+ SetContext (SpaceContext);
+ RedrawPickDisplay (pickState, pMS->CurState);
+ }
+
+ SleepThreadUntil (TimeIn + ONE_SECOND / 30);
+ }
+
+ return TRUE;
+}
+
+static BOOLEAN
+SaveLoadGame (PICK_GAME_STATE *pickState, COUNT gameIndex, BOOLEAN *canceled_by_user)
+{
+ SUMMARY_DESC *desc = pickState->summary + gameIndex;
+ UNICODE nameBuf[256];
+ STAMP saveStamp;
+ BOOLEAN success;
+
+ saveStamp.frame = NULL;
+
+ if (pickState->saving)
+ {
+ // Initialize the save name with whatever name is there already
+ // SAVE_NAME_SIZE is less than 256, so this is safe.
+ strncpy(nameBuf, desc->SaveName, SAVE_NAME_SIZE);
+ nameBuf[SAVE_NAME_SIZE] = 0;
+ if (NameSaveGame (gameIndex, nameBuf))
+ {
+ PlayMenuSound (MENU_SOUND_SUCCESS);
+ ConfirmSaveLoad (pickState->saving ? &saveStamp : NULL);
+ success = SaveGame (gameIndex, desc, nameBuf);
+ }
+ else
+ {
+ success = FALSE;
+ *canceled_by_user = TRUE;
+ }
+ }
+ else
+ {
+ ConfirmSaveLoad (pickState->saving ? &saveStamp : NULL);
+ success = LoadGame (gameIndex, NULL);
+ }
+
+ // TODO: the same should be done for both save and load if we also
+ // display a load problem message
+ if (pickState->saving)
+ { // restore the screen under "SAVING..." message
+ DrawStamp (&saveStamp);
+ }
+
+ DestroyDrawable (ReleaseDrawable (saveStamp.frame));
+
+ return success;
+}
+
+static BOOLEAN
+PickGame (BOOLEAN saving, BOOLEAN fromMainMenu)
+{
+ CONTEXT OldContext;
+ MENU_STATE MenuState;
+ PICK_GAME_STATE pickState;
+ RECT DlgRect;
+ STAMP DlgStamp;
+ TimeCount TimeOut;
+ InputFrameCallback *oldCallback;
+
+ memset (&pickState, 0, sizeof pickState);
+ pickState.saving = saving;
+ pickState.SummaryFrame = SetAbsFrameIndex (PlayFrame, 39);
+
+ memset (&MenuState, 0, sizeof MenuState);
+ MenuState.privData = &pickState;
+ // select the last used slot
+ MenuState.CurState = lastUsedSlot;
+
+ TimeOut = FadeMusic (0, ONE_SECOND / 2);
+
+ // Deactivate any background drawing, like planet rotation
+ oldCallback = SetInputCallback (NULL);
+
+ LoadGameDescriptions (pickState.summary);
+
+ OldContext = SetContext (SpaceContext);
+ // Save the current state of the screen for later restoration
+ DlgStamp = SaveContextFrame (NULL);
+ GetContextClipRect (&DlgRect);
+
+ SleepThreadUntil (TimeOut);
+ PauseMusic ();
+ StopSound ();
+ FadeMusic (NORMAL_VOLUME, 0);
+
+ // draw the current savegame and fade in
+ SetTransitionSource (NULL);
+ BatchGraphics ();
+
+ SetContextBackGroundColor (BLACK_COLOR);
+ ClearDrawable ();
+ RedrawPickDisplay (&pickState, MenuState.CurState);
+ DrawSaveLoad (&pickState);
+
+ if (fromMainMenu)
+ {
+ UnbatchGraphics ();
+ FadeScreen (FadeAllToColor, ONE_SECOND / 2);
+ }
+ else
+ {
+ RECT ctxRect;
+
+ GetContextClipRect (&ctxRect);
+ ScreenTransition (3, &ctxRect);
+ UnbatchGraphics ();
+ }
+
+ SetMenuSounds (MENU_SOUND_ARROWS | MENU_SOUND_PAGEUP | MENU_SOUND_PAGEDOWN,
+ 0);
+ MenuState.InputFunc = DoPickGame;
+
+ // Save/load retry loop
+ while (1)
+ {
+ BOOLEAN canceled_by_user = FALSE;
+
+ pickState.success = FALSE;
+ DoInput (&MenuState, TRUE);
+ if (!pickState.success)
+ break; // canceled
+
+ lastUsedSlot = MenuState.CurState;
+
+ if (SaveLoadGame (&pickState, MenuState.CurState, &canceled_by_user))
+ break; // all good
+
+ // something broke
+ if (saving && !canceled_by_user)
+ SaveProblem ();
+ // TODO: Shouldn't we have a Problem() equivalent for Load too?
+
+ // reload and redraw everything
+ LoadGameDescriptions (pickState.summary);
+ RedrawPickDisplay (&pickState, MenuState.CurState);
+ }
+
+ SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT);
+
+ if (pickState.success && !saving)
+ { // Load succeeded, signal up the chain
+ GLOBAL (CurrentActivity) |= CHECK_LOAD;
+ }
+
+ if (!(GLOBAL (CurrentActivity) & CHECK_ABORT) &&
+ (saving || (!pickState.success && !fromMainMenu)))
+ { // Restore previous screen
+ SetTransitionSource (&DlgRect);
+ BatchGraphics ();
+ DrawStamp (&DlgStamp);
+ ScreenTransition (3, &DlgRect);
+ UnbatchGraphics ();
+ }
+
+ DestroyDrawable (ReleaseDrawable (DlgStamp.frame));
+
+ SetContext (OldContext);
+
+ ResumeMusic ();
+
+ // Reactivate any background drawing, like planet rotation
+ SetInputCallback (oldCallback);
+
+ return pickState.success;
+}
+
+static BOOLEAN
+DoGameOptions (MENU_STATE *pMS)
+{
+ if (GLOBAL (CurrentActivity) & CHECK_ABORT)
+ return FALSE;
+
+ if (PulsedInputState.menu[KEY_MENU_CANCEL]
+ || (PulsedInputState.menu[KEY_MENU_SELECT]
+ && pMS->CurState == EXIT_GAME_MENU))
+ {
+ return FALSE;
+ }
+ else if (PulsedInputState.menu[KEY_MENU_SELECT])
+ {
+ switch (pMS->CurState)
+ {
+ case SAVE_GAME:
+ case LOAD_GAME:
+ SetFlashRect (NULL);
+ if (PickGame (pMS->CurState == SAVE_GAME, FALSE))
+ return FALSE;
+ SetFlashRect (SFR_MENU_3DO);
+ break;
+ case QUIT_GAME:
+ if (ConfirmExit ())
+ return FALSE;
+ break;
+ case SETTINGS:
+ SettingsMenu ();
+ DrawMenuStateStrings (PM_SAVE_GAME, pMS->CurState);
+ break;
+ }
+ }
+ else
+ DoMenuChooser (pMS, PM_SAVE_GAME);
+
+ return TRUE;
+}
+
+// Returns TRUE when the owner menu should continue
+BOOLEAN
+GameOptions (void)
+{
+ MENU_STATE MenuState;
+
+ memset (&MenuState, 0, sizeof MenuState);
+
+ if (LastActivity == CHECK_LOAD)
+ { // Selected LOAD from main menu
+ BOOLEAN success;
+
+ DrawMenuStateStrings (PM_SAVE_GAME, LOAD_GAME);
+ success = PickGame (FALSE, TRUE);
+ if (!success)
+ { // Selected LOAD from main menu, and canceled
+ GLOBAL (CurrentActivity) |= CHECK_ABORT;
+ }
+ return FALSE;
+ }
+
+ MenuState.CurState = SAVE_GAME;
+ DrawMenuStateStrings (PM_SAVE_GAME, MenuState.CurState);
+
+ SetFlashRect (SFR_MENU_3DO);
+
+ SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT);
+ MenuState.InputFunc = DoGameOptions;
+ DoInput (&MenuState, TRUE);
+
+ SetFlashRect (NULL);
+
+ return !(GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD));
+}