diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 047ef545..51af6f26 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -50,6 +50,7 @@ set(WOOF_SOURCES m_misc.c m_misc.h m_misc2.c m_misc2.h m_random.c m_random.h + m_snapshot.c m_snapshot.h m_swap.h memio.c memio.h midifile.c midifile.h diff --git a/src/g_game.c b/src/g_game.c index 8ca211bc..638e5411 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -65,6 +65,7 @@ #include "u_mapinfo.h" #include "m_input.h" #include "memio.h" +#include "m_snapshot.h" #define SAVEGAMESIZE 0x20000 #define SAVESTRINGSIZE 24 @@ -1909,6 +1910,11 @@ static void G_DoSaveGame(void) CheckSaveGame(sizeof extrakills); saveg_write32(extrakills); + // [FG] save snapshot + CheckSaveGame(M_SnapshotDataSize()); + M_WriteSnapshot(save_p); + save_p += M_SnapshotDataSize(); + length = save_p - savebuffer; if (!M_WriteFile(name, savebuffer, length)) diff --git a/src/m_io.c b/src/m_io.c index a71a2527..962686af 100644 --- a/src/m_io.c +++ b/src/m_io.c @@ -201,6 +201,7 @@ int M_stat(const char *path, struct stat *buf) // incompatible with struct stat*. We copy only the required compatible // field. buf->st_mode = wbuf.st_mode; + buf->st_mtime = wbuf.st_mtime; free(wpath); diff --git a/src/m_menu.c b/src/m_menu.c index 428f7857..51ba733b 100644 --- a/src/m_menu.c +++ b/src/m_menu.c @@ -61,6 +61,7 @@ #include "r_sky.h" // [FG] R_InitSkyMap() #include "r_plane.h" // [FG] R_InitPlanes() #include "m_argv.h" +#include "m_snapshot.h" // [crispy] remove DOS reference from the game quit confirmation dialogs #include "SDL_platform.h" @@ -145,6 +146,8 @@ boolean menuactive; // The menus are up #define M_THRM_STEP 6 #define M_THRM_WIDTH (M_THRM_STEP * (M_THRM_SIZE + 2)) #define M_X_THRM (M_X - M_THRM_WIDTH) +#define M_X_LOADSAVE 80 +#define M_LOADSAVE_WIDTH (24 * 8 + 8) // [FG] c.f. M_DrawSaveLoadBorder() #define DISABLE_ITEM(condition, item) \ ((condition) ? (item.m_flags |= S_DISABLE) : (item.m_flags &= ~S_DISABLE)) @@ -797,12 +800,78 @@ menu_t LoadDef = &MainDef, LoadMenu, M_DrawLoad, - 80,34, //jff 3/15/98 move menu up + M_X_LOADSAVE,34, //jff 3/15/98 move menu up 0 }; #define LOADGRAPHIC_Y 8 +// [FG] draw a snapshot of the n'th savegame into a separate window, +// or fill window with solid color and "n/a" if snapshot not available + +static int snapshot_width, snapshot_height; + +static void M_DrawBorderedSnapshot (int n) +{ + int x, y; + patch_t *patch; + char *txt; + + const int snapshot_x = MAX((WIDESCREENDELTA + SaveDef.x + SKULLXOFF - snapshot_width) / 2, 8); + const int snapshot_y = LoadDef.y + MAX((load_end * LINEHEIGHT - snapshot_height) * n / load_end, 0); + + // [FG] a snapshot window smaller than 80*48 px is considered too small + if (snapshot_width < ORIGWIDTH/4) + return; + + if (!M_DrawSnapshot(n, snapshot_x, snapshot_y, snapshot_width, snapshot_height)) + { + txt = "n/a"; + M_WriteText(snapshot_x + snapshot_width/2 - M_StringWidth(txt)/2 - WIDESCREENDELTA, + snapshot_y + snapshot_height/2 - M_StringHeight(txt)/2, + txt); + } + + txt = M_GetSavegameTime(n); + M_DrawString(snapshot_x + snapshot_width/2 - M_GetPixelWidth(txt)/2 - WIDESCREENDELTA, + snapshot_y + snapshot_height + M_StringHeight(txt), + CR_GOLD, txt); + + // [FG] draw the view border around the snapshot + + patch = W_CacheLumpName("brdr_t", PU_CACHE); + for (x = 0; x < snapshot_width; x += 8) + V_DrawPatch(snapshot_x + x - WIDESCREENDELTA, snapshot_y - 8, 0, patch); + + patch = W_CacheLumpName("brdr_b", PU_CACHE); + for (x = 0; x < snapshot_width; x += 8) + V_DrawPatch(snapshot_x + x - WIDESCREENDELTA, snapshot_y + snapshot_height, 0, patch); + + patch = W_CacheLumpName("brdr_l", PU_CACHE); + for (y = 0; y < snapshot_height; y += 8) + V_DrawPatch(snapshot_x - 8 - WIDESCREENDELTA, snapshot_y + y, 0, patch); + + patch = W_CacheLumpName("brdr_r", PU_CACHE); + for (y = 0; y < snapshot_height; y += 8) + V_DrawPatch(snapshot_x + snapshot_width - WIDESCREENDELTA, snapshot_y + y, 0, patch); + + V_DrawPatch(snapshot_x - 8 - WIDESCREENDELTA, + snapshot_y - 8, + 0, W_CacheLumpName("brdr_tl", PU_CACHE)); + + V_DrawPatch(snapshot_x + snapshot_width - WIDESCREENDELTA, + snapshot_y - 8, + 0, W_CacheLumpName("brdr_tr", PU_CACHE)); + + V_DrawPatch(snapshot_x - 8 - WIDESCREENDELTA, + snapshot_y + snapshot_height, + 0, W_CacheLumpName("brdr_bl", PU_CACHE)); + + V_DrawPatch(snapshot_x + snapshot_width - WIDESCREENDELTA, + snapshot_y + snapshot_height, + 0, W_CacheLumpName("brdr_br", PU_CACHE)); +} + // [FG] delete a savegame static boolean delete_verify = false; @@ -839,7 +908,7 @@ void M_DrawSaveLoadBottomLine(void) M_DrawString(LoadDef.x+(SAVESTRINGSIZE-2)*8, y, CR_GOLD, "->"); M_snprintf(pagestr, sizeof(pagestr), "page %d/%d", savepage + 1, savepage_max + 1); - M_DrawString(ORIGWIDTH/2-M_StringWidth(pagestr)/2, y, CR_GOLD, pagestr); + M_DrawString(LoadDef.x+M_LOADSAVE_WIDTH/2-M_StringWidth(pagestr)/2, y, CR_GOLD, pagestr); } // @@ -858,6 +927,8 @@ void M_DrawLoad(void) M_WriteText(LoadDef.x,LoadDef.y+LINEHEIGHT*i,savegamestrings[i]); } + M_DrawBorderedSnapshot(itemOn); + M_DrawSaveLoadBottomLine(); if (delete_verify) @@ -983,7 +1054,7 @@ menu_t SaveDef = &MainDef, SaveMenu, M_DrawSave, - 80,34, //jff 3/15/98 move menu up + M_X_LOADSAVE,34, //jff 3/15/98 move menu up 0 }; @@ -995,14 +1066,25 @@ void M_ReadSaveStrings(void) { int i; + // [FG] shift savegame descriptions a bit to the right + // to make room for the snapshots on the left + SaveDef.x = LoadDef.x = M_X_LOADSAVE + MIN(M_LOADSAVE_WIDTH/2, WIDESCREENDELTA); + + // [FG] fit the snapshots into the resulting space + snapshot_width = MIN((WIDESCREENDELTA + SaveDef.x + 2 * SKULLXOFF) & ~7, ORIGWIDTH/2); // [FG] multiple of 8 + snapshot_height = MIN((snapshot_width * ORIGHEIGHT / ORIGWIDTH) & ~7, ORIGHEIGHT/2); + for (i = 0 ; i < load_end ; i++) { FILE *fp; // killough 11/98: change to use stdio char *name = G_SaveGameName(i); // killough 3/22/98 fp = M_fopen(name,"rb"); + M_ReadSavegameTime(i, name); if (name) free(name); + M_ResetSnapshot(i); + if (!fp) { // Ty 03/27/98 - externalized: name = G_MBFSaveGameName(i); @@ -1022,6 +1104,10 @@ void M_ReadSaveStrings(void) LoadMenu[i].status = 0; continue; } + + if (!M_ReadSnapshot(i, fp)) + M_ResetSnapshot(i); + fclose(fp); LoadMenu[i].status = 1; } @@ -1048,6 +1134,8 @@ void M_DrawSave(void) M_WriteText(LoadDef.x + i,LoadDef.y+LINEHEIGHT*saveSlot,"_"); } + M_DrawBorderedSnapshot(itemOn); + M_DrawSaveLoadBottomLine(); if (delete_verify) diff --git a/src/m_snapshot.c b/src/m_snapshot.c new file mode 100644 index 00000000..d33000b7 --- /dev/null +++ b/src/m_snapshot.c @@ -0,0 +1,180 @@ +// +// Copyright (C) 2022 Fabian Greffrath +// +// 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. +// +// DESCRIPTION: +// Savegame snapshots +// + +#include +#include + +#include "doomtype.h" + +#include "i_video.h" +#include "m_io.h" +#include "v_video.h" + +static const char snapshot_str[] = "WOOF_SNAPSHOT"; +static const int snapshot_len = arrlen(snapshot_str); +static const int snapshot_size = ORIGWIDTH * ORIGHEIGHT; + +static byte *snapshots[10]; +static byte *current_snapshot; +static char savegametimes[10][32]; + +const int M_SnapshotDataSize (void) +{ + return snapshot_len + snapshot_size; +} + +void M_ResetSnapshot (int i) +{ + if (snapshots[i]) + { + free(snapshots[i]); + snapshots[i] = NULL; + } +} + +// [FG] try to read snapshot data from the end of a savegame file + +boolean M_ReadSnapshot (int i, FILE *fp) +{ + char str[16] = {0}; + + M_ResetSnapshot (i); + + if (fseek(fp, -M_SnapshotDataSize(), SEEK_END) != 0) + return false; + + if (fread(str, 1, snapshot_len, fp) != snapshot_len) + return false; + + if (strncasecmp(str, snapshot_str, snapshot_len) != 0) + return false; + + if ((snapshots[i] = malloc(snapshot_size * sizeof(**snapshots))) == NULL) + return false; + + if (fread(snapshots[i], 1, snapshot_size, fp) != snapshot_size) + return false; + + return true; +} + +void M_ReadSavegameTime (int i, char *name) +{ + struct stat st; + + if (M_stat(name, &st) == -1) + savegametimes[i][0] = '\0'; + else +// [FG] suppress the most useless compiler warning ever +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-y2k" +#endif + strftime(savegametimes[i], sizeof(savegametimes[i]), "%x %X", localtime(&st.st_mtime)); +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif +} + +char *M_GetSavegameTime (int i) +{ + return savegametimes[i]; +} + +// [FG] take a snapshot in ORIGWIDTH*ORIGHEIGHT resolution, i.e. +// in hires mode only only each second pixel in each second row is saved, +// in widescreen mode only the non-widescreen part in the middle is saved + +static void M_TakeSnapshot (void) +{ + const int inc = hires ? 2 : 1; + int x, y; + byte *p; + const byte *s = screens[0]; + + if (!current_snapshot) + { + current_snapshot = malloc(snapshot_size * sizeof(**snapshots)); + } + p = current_snapshot; + + for (y = 0; y < (SCREENHEIGHT << hires); y += inc) + { + for (x = 0; x < (NONWIDEWIDTH << hires); x += inc) + { + *p++ = s[y * (SCREENWIDTH << hires) + (WIDESCREENDELTA << hires) + x]; + } + } +} + +void M_WriteSnapshot (byte *p) +{ + M_TakeSnapshot(); + + memcpy(p, snapshot_str, snapshot_len); + p += snapshot_len; + + memcpy(p, current_snapshot, snapshot_size); + p += snapshot_size; +} + +// [FG] draw snapshot for the n'th savegame, if no snapshot is found +// fill the area with palette index 0 (i.e. mostly black) + +boolean M_DrawSnapshot (int n, int x, int y, int w, int h) +{ + byte *dest = screens[0] + y * (SCREENWIDTH << (2 * hires)) + (x << hires); + + if (!snapshots[n]) + { + int desty; + + for (desty = 0; desty < (h << hires); desty++) + { + memset(dest, 0, w << hires); + dest += SCREENWIDTH << hires; + } + + return false; + } + else + { + const fixed_t step_x = (ORIGWIDTH << FRACBITS) / (w << hires); + const fixed_t step_y = (ORIGHEIGHT << FRACBITS) / (h << hires); + int destx, desty; + fixed_t srcx, srcy; + byte *destline, *srcline; + + for (desty = 0, srcy = 0; desty < (h << hires); desty++, srcy += step_y) + { + destline = dest + desty * (SCREENWIDTH << hires); + srcline = snapshots[n] + (srcy >> FRACBITS) * ORIGWIDTH; + + for (destx = 0, srcx = 0; destx < (w << hires); destx++, srcx += step_x) + { + *destline++ = srcline[srcx >> FRACBITS]; + } + } + } + + return true; +} diff --git a/src/m_snapshot.h b/src/m_snapshot.h new file mode 100644 index 00000000..d24aae10 --- /dev/null +++ b/src/m_snapshot.h @@ -0,0 +1,35 @@ +// +// Copyright (C) 2022 Fabian Greffrath +// +// 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. +// +// DESCRIPTION: +// Savegame snapshots +// + +#ifndef __M_SNAPSHOT__ +#define __M_SNAPSHOT__ + +const int M_SnapshotDataSize (void); +void M_ResetSnapshot (int i); +boolean M_ReadSnapshot (int i, FILE *fp); +void M_WriteSnapshot (byte *p); +boolean M_DrawSnapshot (int i, int x, int y, int w, int h); + +void M_ReadSavegameTime (int i, char *name); +char *M_GetSavegameTime (int i); + +#endif