First draft of namable savegames.
This code is loosely adapted from JMS's code in UQM-HD, but very little remains beyond the text input loop internals. Fully backwards compatible, but this format shouldn't survive to the final merge.
This commit is contained in:
+196
-12
@@ -34,6 +34,7 @@
|
||||
#include "sounds.h"
|
||||
#include "util.h"
|
||||
#include "libs/graphics/gfx_common.h"
|
||||
#include "libs/log.h"
|
||||
|
||||
#include <ctype.h>
|
||||
|
||||
@@ -352,10 +353,178 @@ NameCaptainOrShip (bool nameCaptain)
|
||||
|
||||
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;
|
||||
|
||||
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 = Str;
|
||||
lf.CharCount = (COUNT)~0;
|
||||
|
||||
if (!(state & DDSHS_EDIT))
|
||||
{
|
||||
//RECT r;
|
||||
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 (&t);
|
||||
}
|
||||
else
|
||||
{ // editing state
|
||||
COUNT i;
|
||||
RECT text_r;
|
||||
BYTE char_deltas[SAVE_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
|
||||
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 = 0;
|
||||
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
|
||||
@@ -825,9 +994,10 @@ DrawGameSelection (PICK_GAME_STATE *pickState, COUNT selSlot)
|
||||
{
|
||||
DateToString (buf2, sizeof buf2, desc->month_index,
|
||||
desc->day_index, desc->year_index);
|
||||
snprintf (buf, sizeof buf, "%s %s",
|
||||
GAME_STRING (SAVEGAME_STRING_BASE + 4), buf2);
|
||||
// "Saved Game - Date:"
|
||||
if (desc->SaveName[0])
|
||||
snprintf (buf, sizeof buf, "%s: %s", buf2, desc->SaveName);
|
||||
else
|
||||
snprintf (buf, sizeof buf, "%s", buf2);
|
||||
}
|
||||
font_DrawText (&t);
|
||||
}
|
||||
@@ -936,22 +1106,35 @@ DoPickGame (MENU_STATE *pMS)
|
||||
}
|
||||
|
||||
static BOOLEAN
|
||||
SaveLoadGame (PICK_GAME_STATE *pickState, COUNT gameIndex)
|
||||
SaveLoadGame (PICK_GAME_STATE *pickState, COUNT gameIndex, BOOLEAN *canceled_by_user)
|
||||
{
|
||||
SUMMARY_DESC *desc = pickState->summary + gameIndex;
|
||||
UNICODE nameBuf[SAVE_NAME_SIZE];
|
||||
STAMP saveStamp;
|
||||
BOOLEAN success;
|
||||
|
||||
saveStamp.frame = NULL;
|
||||
|
||||
// TODO: fix ConfirmSaveLoad() interface so it does not rely on
|
||||
// MsgStamp != NULL parameter.
|
||||
ConfirmSaveLoad (pickState->saving ? &saveStamp : NULL);
|
||||
|
||||
if (pickState->saving)
|
||||
success = SaveGame (gameIndex, desc);
|
||||
{
|
||||
nameBuf[0] = 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
|
||||
@@ -1032,6 +1215,8 @@ PickGame (BOOLEAN saving, BOOLEAN fromMainMenu)
|
||||
// Save/load retry loop
|
||||
while (1)
|
||||
{
|
||||
BOOLEAN canceled_by_user = FALSE;
|
||||
|
||||
pickState.success = FALSE;
|
||||
DoInput (&MenuState, TRUE);
|
||||
if (!pickState.success)
|
||||
@@ -1039,11 +1224,11 @@ PickGame (BOOLEAN saving, BOOLEAN fromMainMenu)
|
||||
|
||||
lastUsedSlot = MenuState.CurState;
|
||||
|
||||
if (SaveLoadGame (&pickState, MenuState.CurState))
|
||||
if (SaveLoadGame (&pickState, MenuState.CurState, &canceled_by_user))
|
||||
break; // all good
|
||||
|
||||
// something broke
|
||||
if (saving)
|
||||
if (saving && !canceled_by_user)
|
||||
SaveProblem ();
|
||||
// TODO: Shouldn't we have a Problem() equivalent for Load too?
|
||||
|
||||
@@ -1154,4 +1339,3 @@ GameOptions (void)
|
||||
|
||||
return !(GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD));
|
||||
}
|
||||
|
||||
|
||||
+29
-8
@@ -480,10 +480,10 @@ LoadGameState (GAME_STATE *GSPtr, DECODE_REF fh)
|
||||
}
|
||||
|
||||
static BOOLEAN
|
||||
LoadSisState (SIS_STATE *SSPtr, void *fp)
|
||||
LoadSisState (SIS_STATE *SSPtr, void *fp, SDWORD first)
|
||||
{
|
||||
SSPtr->log_x = first;
|
||||
if (
|
||||
read_32s (fp, &SSPtr->log_x) != 1 ||
|
||||
read_32s (fp, &SSPtr->log_y) != 1 ||
|
||||
read_32 (fp, &SSPtr->ResUnits) != 1 ||
|
||||
read_32 (fp, &SSPtr->FuelOnBoard) != 1 ||
|
||||
@@ -510,7 +510,26 @@ LoadSisState (SIS_STATE *SSPtr, void *fp)
|
||||
static BOOLEAN
|
||||
LoadSummary (SUMMARY_DESC *SummPtr, void *fp)
|
||||
{
|
||||
if (!LoadSisState (&SummPtr->SS, fp))
|
||||
SDWORD magic;
|
||||
BOOLEAN legacy;
|
||||
SummPtr->SaveName[0] = 0;
|
||||
if (!read_32s (fp, &magic))
|
||||
return FALSE;
|
||||
if (magic == SAVE_MAGIC)
|
||||
{
|
||||
legacy = FALSE;
|
||||
// Read in the real first value for LoadSisState
|
||||
if (read_32 (fp, &magic) != 1)
|
||||
return FALSE;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise, we're legacy and the "magic" number was
|
||||
// really LoadSisState's first value
|
||||
legacy = TRUE;
|
||||
}
|
||||
|
||||
if (!LoadSisState (&SummPtr->SS, fp, magic))
|
||||
return FALSE;
|
||||
|
||||
if (
|
||||
@@ -524,11 +543,15 @@ LoadSummary (SUMMARY_DESC *SummPtr, void *fp)
|
||||
read_8 (fp, &SummPtr->NumShips) != 1 ||
|
||||
read_8 (fp, &SummPtr->NumDevices) != 1 ||
|
||||
read_a8 (fp, SummPtr->ShipList, MAX_BUILT_SHIPS) != 1 ||
|
||||
read_a8 (fp, SummPtr->DeviceList, MAX_EXCLUSIVE_DEVICES) != 1 ||
|
||||
|
||||
read_16 (fp, NULL) != 1 /* padding */
|
||||
read_a8 (fp, SummPtr->DeviceList, MAX_EXCLUSIVE_DEVICES) != 1
|
||||
)
|
||||
return FALSE;
|
||||
if (!legacy && (read_a8 (fp, SummPtr->SaveName, SAVE_NAME_SIZE) != 1))
|
||||
return FALSE;
|
||||
// Don't trust the savefile to properly null-terminate!
|
||||
SummPtr->SaveName[SAVE_NAME_SIZE-1] = 0;
|
||||
if (read_16 (fp, NULL) != 1) /* padding */
|
||||
return FALSE;
|
||||
else
|
||||
return TRUE;
|
||||
}
|
||||
@@ -760,5 +783,3 @@ LoadGame (COUNT which_game, SUMMARY_DESC *SummPtr)
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
|
||||
+9
-4
@@ -443,6 +443,8 @@ SaveSisState (const SIS_STATE *SSPtr, void *fp)
|
||||
static BOOLEAN
|
||||
SaveSummary (const SUMMARY_DESC *SummPtr, void *fp)
|
||||
{
|
||||
if (write_32 (fp, SAVE_MAGIC) != 1)
|
||||
return FALSE;
|
||||
if (!SaveSisState (&SummPtr->SS, fp))
|
||||
return FALSE;
|
||||
|
||||
@@ -458,6 +460,7 @@ SaveSummary (const SUMMARY_DESC *SummPtr, void *fp)
|
||||
write_8 (fp, SummPtr->NumDevices) != 1 ||
|
||||
write_a8 (fp, SummPtr->ShipList, MAX_BUILT_SHIPS) != 1 ||
|
||||
write_a8 (fp, SummPtr->DeviceList, MAX_EXCLUSIVE_DEVICES) != 1 ||
|
||||
write_a8 (fp, SummPtr->SaveName, SAVE_NAME_SIZE) != 1 ||
|
||||
|
||||
write_16 (fp, 0) != 1 /* padding */
|
||||
)
|
||||
@@ -478,8 +481,9 @@ SaveStarDesc (const STAR_DESC *SDPtr, DECODE_REF fh)
|
||||
}
|
||||
|
||||
static void
|
||||
PrepareSummary (SUMMARY_DESC *SummPtr)
|
||||
PrepareSummary (SUMMARY_DESC *SummPtr, const char *name)
|
||||
{
|
||||
int i;
|
||||
SummPtr->SS = GlobData.SIS_state;
|
||||
|
||||
SummPtr->Activity = LOBYTE (GLOBAL (CurrentActivity));
|
||||
@@ -535,6 +539,8 @@ PrepareSummary (SUMMARY_DESC *SummPtr)
|
||||
SummPtr->day_index = GLOBAL (GameClock.day_index);
|
||||
SummPtr->month_index = GLOBAL (GameClock.month_index);
|
||||
SummPtr->year_index = GLOBAL (GameClock.year_index);
|
||||
SummPtr->SaveName[SAVE_NAME_SIZE-1] = 0;
|
||||
strncpy (SummPtr->SaveName, name, SAVE_NAME_SIZE-1);
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -635,7 +641,7 @@ SaveFlagshipState (void)
|
||||
// This function first writes to a memory file, and then writes the whole
|
||||
// lot to the actual save file at once.
|
||||
BOOLEAN
|
||||
SaveGame (COUNT which_game, SUMMARY_DESC *SummPtr)
|
||||
SaveGame (COUNT which_game, SUMMARY_DESC *SummPtr, const char *name)
|
||||
{
|
||||
BOOLEAN success, made_room;
|
||||
void *out_fp, *h;
|
||||
@@ -833,7 +839,7 @@ RetrySave:
|
||||
flen + sizeof (*SummPtr));
|
||||
if (flen && (out_fp = res_OpenResFile (saveDir, file, "wb")))
|
||||
{
|
||||
PrepareSummary (SummPtr);
|
||||
PrepareSummary (SummPtr, name);
|
||||
|
||||
success = SaveSummary (SummPtr, out_fp);
|
||||
// Then write the rest of the data.
|
||||
@@ -858,4 +864,3 @@ RetrySave:
|
||||
|
||||
return (success);
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ extern "C" {
|
||||
#endif
|
||||
|
||||
extern void SaveProblem (void);
|
||||
extern BOOLEAN SaveGame (COUNT which_game, SUMMARY_DESC *summary_desc);
|
||||
extern BOOLEAN SaveGame (COUNT which_game, SUMMARY_DESC *summary_desc, const char *name);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
|
||||
@@ -160,6 +160,8 @@ typedef struct
|
||||
// is only used for displaying savegame summaries. There is also
|
||||
// room for only 16 devices on screen.
|
||||
#define MAX_EXCLUSIVE_DEVICES 16
|
||||
#define SAVE_MAGIC 0x01534d55
|
||||
#define SAVE_NAME_SIZE 24
|
||||
|
||||
typedef struct
|
||||
{
|
||||
@@ -172,6 +174,7 @@ typedef struct
|
||||
BYTE NumShips, NumDevices;
|
||||
BYTE ShipList[MAX_BUILT_SHIPS];
|
||||
BYTE DeviceList[MAX_EXCLUSIVE_DEVICES];
|
||||
UNICODE SaveName[SAVE_NAME_SIZE];
|
||||
} SUMMARY_DESC;
|
||||
|
||||
#define OVERRIDE_LANDER_FLAGS (1 << 7)
|
||||
|
||||
Reference in New Issue
Block a user