diff --git a/sc2/src/uqm/gameopt.c b/sc2/src/uqm/gameopt.c index 726f0b2ce..80070e7bf 100644 --- a/sc2/src/uqm/gameopt.c +++ b/sc2/src/uqm/gameopt.c @@ -34,6 +34,7 @@ #include "sounds.h" #include "util.h" #include "libs/graphics/gfx_common.h" +#include "libs/log.h" #include @@ -253,7 +254,7 @@ DrawNameString (bool nameCaptain, UNICODE *Str, COUNT CursorPos, // disallow the change return (FALSE); } - + PreUpdateFlashRect (); SetContextForeGroundColor (BackGround); @@ -264,7 +265,7 @@ DrawNameString (bool nameCaptain, UNICODE *Str, COUNT CursorPos, 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) @@ -284,7 +285,7 @@ DrawNameString (bool nameCaptain, UNICODE *Str, COUNT CursorPos, { // 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); @@ -292,7 +293,7 @@ DrawNameString (bool nameCaptain, UNICODE *Str, COUNT CursorPos, SetContextForeGroundColor (ForeGround); font_DrawText (&lf); - + PostUpdateFlashRect (); } @@ -347,15 +348,183 @@ NameCaptainOrShip (bool nameCaptain) 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; + + 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 @@ -611,7 +780,7 @@ DrawSavegameSummary (PICK_GAME_STATE *pickState, COUNT gameIndex) UninitQueue (&GLOBAL (built_ship_q)); SetContextClipRect (&OldRect); - + SetContext (SpaceContext); // draw devices s.origin.y = 13; @@ -680,7 +849,7 @@ DrawSavegameSummary (PICK_GAME_STATE *pickState, COUNT gameIndex) SetContextForeGroundColor ( BUILD_COLOR (MAKE_RGB15 (0x10, 0x00, 0x10), 0x01)); font_DrawText (&t); - + // print the location t.baseline.x = 6; t.baseline.y = 139 + 6; @@ -697,7 +866,7 @@ DrawSavegameSummary (PICK_GAME_STATE *pickState, COUNT gameIndex) { 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); @@ -770,11 +939,11 @@ DrawGameSelection (PICK_GAME_STATE *pickState, COUNT selSlot) COUNT curSlot; UNICODE buf[256]; UNICODE buf2[80]; - + BatchGraphics (); SetContextFont (TinyFont); - + // Erase the selection menu r.extent.width = 240; r.extent.height = 65; @@ -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); } @@ -928,7 +1098,7 @@ DoPickGame (MENU_STATE *pMS) SetContext (SpaceContext); RedrawPickDisplay (pickState, pMS->CurState); } - + SleepThreadUntil (TimeIn + ONE_SECOND / 30); } @@ -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 @@ -1005,12 +1188,12 @@ PickGame (BOOLEAN saving, BOOLEAN fromMainMenu) // draw the current savegame and fade in SetTransitionSource (NULL); BatchGraphics (); - + SetContextBackGroundColor (BLACK_COLOR); ClearDrawable (); RedrawPickDisplay (&pickState, MenuState.CurState); DrawSaveLoad (&pickState); - + if (fromMainMenu) { UnbatchGraphics (); @@ -1028,10 +1211,12 @@ PickGame (BOOLEAN saving, BOOLEAN fromMainMenu) 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) @@ -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? @@ -1058,7 +1243,7 @@ PickGame (BOOLEAN saving, BOOLEAN fromMainMenu) { // Load succeeded, signal up the chain GLOBAL (CurrentActivity) |= CHECK_LOAD; } - + if (!(GLOBAL (CurrentActivity) & CHECK_ABORT) && (saving || (!pickState.success && !fromMainMenu))) { // Restore previous screen @@ -1145,7 +1330,7 @@ GameOptions (void) DrawMenuStateStrings (PM_SAVE_GAME, MenuState.CurState); SetFlashRect (SFR_MENU_3DO); - + SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT); MenuState.InputFunc = DoGameOptions; DoInput (&MenuState, TRUE); @@ -1154,4 +1339,3 @@ GameOptions (void) return !(GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD)); } - diff --git a/sc2/src/uqm/load.c b/sc2/src/uqm/load.c index 98924f173..c7a58c6d0 100644 --- a/sc2/src/uqm/load.c +++ b/sc2/src/uqm/load.c @@ -368,7 +368,7 @@ LoadEncounter (ENCOUNTER *EncounterPtr, DECODE_REF fh) cread_ptr (fh); /* useless ptr; FRAME icons */ cread_ptr (fh); /* useless ptr; FRAME melee_icon */ } - + // Load the stuff after the BRIEF_SHIP_INFO array cread_32s (fh, &EncounterPtr->log_x); cread_32s (fh, &EncounterPtr->log_y); @@ -405,7 +405,7 @@ DummyLoadQueue (QUEUE *QueuePtr, DECODE_REF fh) cread_16 (fh, NULL); /* MEM_HANDLE hq_tab */ cread_16 (fh, NULL); /* COUNT object_size */ cread_8 (fh, NULL); /* BYTE num_objects */ - + cread_8 (fh, NULL); /* padding */ cread_16 (fh, NULL); /* padding */ } @@ -438,7 +438,7 @@ LoadGameState (GAME_STATE *GSPtr, DECODE_REF fh) cread_a8 (fh, GSPtr->ElementWorth, NUM_ELEMENT_CATEGORIES); cread_ptr (fh); /* not loading ptr; PRIMITIVE *DisplayArray */ cread_16 (fh, &GSPtr->CurrentActivity); - + cread_16 (fh, NULL); /* CLOCK_STATE alignment padding */ LoadClockState (&GSPtr->GameClock, fh); @@ -466,7 +466,7 @@ LoadGameState (GAME_STATE *GSPtr, DECODE_REF fh) cread_16 (fh, NULL); /* VELOCITY_DESC padding */ cread_32 (fh, &GSPtr->BattleGroupRef); - + DummyLoadQueue (&GSPtr->avail_race_q, fh); DummyLoadQueue (&GSPtr->npc_built_ship_q, fh); // Not loading ip_group_q, was not there originally @@ -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; } - - diff --git a/sc2/src/uqm/save.c b/sc2/src/uqm/save.c index c49fe505e..95be06eda 100644 --- a/sc2/src/uqm/save.c +++ b/sc2/src/uqm/save.c @@ -305,7 +305,7 @@ SaveEncounter (const ENCOUNTER *EncounterPtr, DECODE_REF fh) cwrite_ptr (fh); /* useless ptr; FRAME icons */ cwrite_ptr (fh); /* useless ptr; FRAME melee_icon */ } - + // Save the stuff after the BRIEF_SHIP_INFO array cwrite_32 (fh, EncounterPtr->log_x); cwrite_32 (fh, EncounterPtr->log_y); @@ -340,7 +340,7 @@ DummySaveQueue (const QUEUE *QueuePtr, DECODE_REF fh) cwrite_16 (fh, 0); /* MEM_HANDLE hq_tab */ cwrite_16 (fh, 0); /* COUNT object_size */ cwrite_8 (fh, 0); /* BYTE num_objects */ - + cwrite_8 (fh, 0); /* padding */ cwrite_16 (fh, 0); /* padding */ } @@ -371,7 +371,7 @@ SaveGameState (const GAME_STATE *GSPtr, DECODE_REF fh) cwrite_a8 (fh, GSPtr->ElementWorth, NUM_ELEMENT_CATEGORIES); cwrite_ptr (fh); /* useless ptr; PRIMITIVE *DisplayArray */ cwrite_16 (fh, GSPtr->CurrentActivity); - + cwrite_16 (fh, 0); /* CLOCK_STATE alignment padding */ SaveClockState (&GSPtr->GameClock, fh); @@ -399,7 +399,7 @@ SaveGameState (const GAME_STATE *GSPtr, DECODE_REF fh) cwrite_16 (fh, 0); /* VELOCITY_DESC padding */ cwrite_32 (fh, GSPtr->BattleGroupRef); - + DummySaveQueue (&GSPtr->avail_race_q, fh); DummySaveQueue (&GSPtr->npc_built_ship_q, fh); // Not saving ip_group_q, was not there originally @@ -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 @@ -549,7 +555,7 @@ SaveProblemMessage (STAMP *MsgStamp) // TODO: This should probably just use DoPopupWindow() ppStr[0] = GAME_STRING (SAVEGAME_STRING_BASE + 2); - + SetContextFont (StarConFont); t.baseline.x = t.baseline.y = 0; @@ -579,7 +585,7 @@ SaveProblemMessage (STAMP *MsgStamp) r.extent.height += 8; *MsgStamp = SaveContextFrame (&r); - + BatchGraphics (); DrawStarConBox (&r, 2, BUILD_COLOR (MAKE_RGB15 (0x10, 0x10, 0x10), 0x19), @@ -605,7 +611,7 @@ SaveProblem (void) { STAMP s; CONTEXT OldContext; - + OldContext = SetContext (SpaceContext); SaveProblemMessage (&s); FlushGraphics (); @@ -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. @@ -846,7 +852,7 @@ RetrySave: } else success = FALSE; - + if (!success) DeleteResFile (saveDir, file); } @@ -858,4 +864,3 @@ RetrySave: return (success); } - diff --git a/sc2/src/uqm/save.h b/sc2/src/uqm/save.h index bb7084b18..7a3c3c2ac 100644 --- a/sc2/src/uqm/save.h +++ b/sc2/src/uqm/save.h @@ -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) } diff --git a/sc2/src/uqm/sis.h b/sc2/src/uqm/sis.h index 06dea4265..723081887 100644 --- a/sc2/src/uqm/sis.h +++ b/sc2/src/uqm/sis.h @@ -76,7 +76,7 @@ enum CANNON_WEAPON, TRACKING_SYSTEM, ANTIMISSILE_DEFENSE, - + NUM_PURCHASE_MODULES, BOMB_MODULE_0 = NUM_PURCHASE_MODULES, @@ -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)