Repository: fabiangreffrath/crispy-doom
Pull request #1287: Heretic: Map Marker
Author: login: Noseey
Created: 2025-03-23T20:39:36Z | Merged: 2025-03-31T11:12:11Z

Related Issue:
https://github.com/fabiangreffrath/crispy-doom/issues/1285

**Changes Summary**

- Brought back map markers.
- Enabled automap crosshair when followplayer is off.
- Introduced extsaveg for Heretic and stored mapmarkers, savegame compatible with vanilla.

Example map markers:
![2025-03-29 12_30_23-Window](https://github.com/user-attachments/assets/47492f84-27a4-433b-9ae4-8b8fe127fdd2)

Example storing of markers:
![2025-03-29 12_35_44-Window](https://github.com/user-attachments/assets/99441fee-67c6-498d-a92f-01b4444f1d79)





Diff:
diff --git a/src/heretic/CMakeLists.txt b/src/heretic/CMakeLists.txt
index e75fcdd87a..9f648e362a 100644
--- a/src/heretic/CMakeLists.txt
+++ b/src/heretic/CMakeLists.txt
@@ -26,6 +26,7 @@ add_library(heretic STATIC
             p_doors.c
             p_enemy.c
             p_extnodes.c        p_extnodes.h
+            p_extsaveg.c        p_extsaveg.h
             p_floor.c
             p_inter.c
             p_lights.c
diff --git a/src/heretic/Makefile.am b/src/heretic/Makefile.am
index 0939543c50..34de029558 100644
--- a/src/heretic/Makefile.am
+++ b/src/heretic/Makefile.am
@@ -47,6 +47,7 @@ p_mobj.c                                             \
 p_plats.c                                            \
 p_pspr.c                                             \
 p_saveg.c                                            \
+p_extsaveg.c           p_extsaveg.h                  \
 p_setup.c                                            \
 p_sight.c                                            \
 p_spec.c               p_spec.h                      \
diff --git a/src/heretic/am_map.c b/src/heretic/am_map.c
index 88d584b952..dbb9a26d21 100644
--- a/src/heretic/am_map.c
+++ b/src/heretic/am_map.c
@@ -29,6 +29,7 @@
 
 #include "doomkeys.h"
 #include "v_video.h"
+#include "m_misc.h" // [crispy]
 
 vertex_t KeyPoints[NUM_KEY_TYPES];
 
@@ -142,9 +143,10 @@ static int m_zoomin_mouse;
 static int m_zoomout_mouse;
 static boolean mousewheelzoom;
 
-//static patch_t *marknums[10]; // numbers used for marking by the automap
-//static mpoint_t markpoints[AM_NUMMARKPOINTS]; // where the points are
-//static int markpointnum = 0; // next point to be assigned
+// [crispy] restore mapmarker functionality
+static patch_t *marknums[10]; // numbers used for marking by the automap
+static mpoint_t markpoints[AM_NUMMARKPOINTS]; // where the points are
+static int markpointnum = 0; // next point to be assigned
 
 static int followplayer = 1;    // specifies whether to follow the player around
 
@@ -185,6 +187,7 @@ static short next_mapxstart, next_mapystart; // [crispy] for interpolation
 // [crispy] automap rotate
 void AM_rotate(fixed_t* x, fixed_t* y, angle_t a);
 static void AM_rotatePoint(mpoint_t *pt);
+static void AM_drawCrosshair(int color, boolean force); // [crispy] restore crosshair
 static mpoint_t mapcenter;
 static angle_t mapangle;
 
@@ -264,15 +267,24 @@ void AM_restoreScaleAndLoc(void)
 
 // adds a marker at the current location
 
-/*
+// [crispy] restore mapmarker functionality
 void AM_addMark(void)
 {
-  markpoints[markpointnum].x = m_x + m_w/2;
-  markpoints[markpointnum].y = m_y + m_h/2;
-  markpointnum = (markpointnum + 1) % AM_NUMMARKPOINTS;
-
+    // [crispy] keep the map static in overlay mode
+    // if not following the player
+    if (!(!followplayer && crispy->automapoverlay))
+    {
+        markpoints[markpointnum].x = m_x + m_w/2;
+        markpoints[markpointnum].y = m_y + m_h/2;
+    }
+    else
+    {
+        markpoints[markpointnum].x = plr->mo->x >> FRACTOMAPBITS;
+        markpoints[markpointnum].y = plr->mo->y >> FRACTOMAPBITS;
+    }
+    markpointnum = (markpointnum + 1) % AM_NUMMARKPOINTS;
 }
-*/
+
 void AM_findMinMaxBoundaries(void)
 {
     int i;
@@ -504,33 +516,42 @@ void AM_initVariables(void)
 //c  ST_Responder(&st_notify);
 }
 
+// [crispy] restore mapmarker functionality
 void AM_loadPics(void)
 {
-    //int i;
-    //char namebuf[9];
-/*  for (i=0;i<10;i++)
-  {
-    M_snprintf(namebuf, sizeof(namebuf), "AMMNUM%d", i);
-    marknums[i] = W_CacheLumpName(namebuf, PU_STATIC);
-  }*/
+    int i;
+    char namebuf[9];
+    for (i=0;i<10;i++)
+    {
+        M_snprintf(namebuf, sizeof(namebuf), "SMALLIN%d", i);
+        marknums[i] = W_CacheLumpName(namebuf, PU_STATIC);
+    }
     maplump = W_CacheLumpName(DEH_String("AUTOPAGE"), PU_STATIC);
 }
 
-/*void AM_unloadPics(void)
+// [crispy] restore mapmarker functionality
+void AM_unloadPics(void)
 {
-  int i;
-  for (i=0;i<10;i++) Z_ChangeTag(marknums[i], PU_CACHE);
-
-}*/
+    int i;
+    char namebuf[9];
+  
+    for (i=0;i<10;i++)
+    {
+        M_snprintf(namebuf, 9, "SMALLIN%d", i);
+        W_ReleaseLumpName(namebuf);
+    }
+}
 
-/*
+// [crispy] restore mapmarker functionality
 void AM_clearMarks(void)
 {
-  int i;
-  for (i=0;i<AM_NUMMARKPOINTS;i++) markpoints[i].x = -1; // means empty
-  markpointnum = 0;
+    int i;
+
+    for (i=0;i<AM_NUMMARKPOINTS;i++) 
+        markpoints[i].x = -1; // means empty
+    markpointnum = 0;
 }
-*/
+
 
 // should be called at the start of every level
 // right now, i figure it out myself
@@ -546,9 +567,11 @@ void AM_LevelInit(boolean reinit)
     f_w = finit_width;
     f_h = finit_height;
     next_mapxstart = next_mapystart = mapxstart = mapystart = 0;
+    fb = I_VideoBuffer; // [crispy] to consider highres toggle
 
-
-//  AM_clearMarks();
+    // [crispy] restore mapmarker functionality
+    if (!reinit)
+        AM_clearMarks();
 
     AM_findMinMaxBoundaries();
 
@@ -556,6 +579,7 @@ void AM_LevelInit(boolean reinit)
     if (reinit && f_h_old)
     {
         scale_mtof = scale_mtof * f_h / f_h_old;
+        AM_drawCrosshair(XHAIRCOLORS, true);
     }
     else
     {
@@ -578,17 +602,17 @@ void AM_Stop(void)
 {
     //static event_t st_notify = { 0, ev_keyup, AM_MSGEXITED };
 
-//  AM_unloadPics();
+    AM_unloadPics(); // [crispy] restore mapmarker functionality
     automapactive = false;
 //  ST_Responder(&st_notify);
     stopped = true;
     BorderNeedRefresh = true;
 }
 
+// [crispy] moved here for extended savegames
+static int lastlevel = -1, lastepisode = -1;
 void AM_Start(void)
 {
-    static int lastlevel = -1, lastepisode = -1;
-
     if (!stopped)
         AM_Stop();
     stopped = false;
@@ -630,6 +654,7 @@ boolean AM_Responder(event_t * ev)
     int key;
     static int bigstate = 0;
     static int joywait = 0;
+    static char buffer[20]; // [crispy] to store mapmarkers
     extern boolean speedkeydown (void);
 
     // [crispy] toggleable pan/zoom speed
@@ -801,20 +826,19 @@ boolean AM_Responder(event_t * ev)
                          grid ? AMSTR_GRIDON : AMSTR_GRIDOFF,
                          true);
         }
-        /*
+        // [crispy] restore mapmarker functionality
         else if (key == key_map_mark)
         {
             M_snprintf(buffer, sizeof(buffer), "%s %d",
                        AMSTR_MARKEDSPOT, markpointnum);
-            plr->message = buffer;
+            P_SetMessage(plr, buffer, true);
             AM_addMark();
         }
         else if (key == key_map_clearmark)
         {
             AM_clearMarks();
-            plr->message = AMSTR_MARKSCLEARED;
+            P_SetMessage(plr, AMSTR_MARKSCLEARED, true);
         }
-        */
         else if (key == key_map_overlay)
         {
             // [crispy] force redraw status bar
@@ -1866,25 +1890,34 @@ void AM_drawThings(int colors, int colorrange)
     }
 }
 
-/*
+// [crispy] restore mapmarker functionality
 void AM_drawMarks(void)
 {
-  int i, fx, fy, w, h;
+    int i, fx, fy, w, h;
+    mpoint_t pt;
 
-  for (i=0;i<AM_NUMMARKPOINTS;i++)
-  {
-    if (markpoints[i].x != -1)
+    for (i=0;i<AM_NUMMARKPOINTS;i++)
     {
-      w = SHORT(marknums[i]->width);
-      h = SHORT(marknums[i]->height);
-      fx = CXMTOF(markpoints[i].x);
-      fy = CYMTOF(markpoints[i].y);
-      if (fx >= f_x && fx <= f_w - w && fy >= f_y && fy <= f_h - h)
-  			V_DrawPatch(fx, fy, marknums[i]);
+        if (markpoints[i].x != -1)
+        {
+            //   w = SHORT(marknums[i]->width);
+            //   h = SHORT(marknums[i]->height);
+            w = 6; // [crispy] based on doom +1 for increased size
+            h = 7; // [crispy] based on doom +1 for increased size
+            // [crispy] center marks around player
+            pt.x = markpoints[i].x;
+            pt.y = markpoints[i].y;
+            if (crispy->automaprotate)
+            {
+                AM_rotatePoint(&pt);
+            }
+            fx = (CXMTOF(pt.x) >> crispy->hires) - 1;
+            fy = (CYMTOF(pt.y) >> crispy->hires) - 2;
+            if (fx >= f_x && fx <= (f_w >> crispy->hires) - w && fy >= f_y && fy <= (f_h >> crispy->hires) - h)
+                V_DrawPatch(fx - WIDESCREENDELTA, fy, marknums[i]);
+        }
     }
-  }
 }
-*/
 
 void AM_drawkeys(void)
 {
@@ -1925,9 +1958,34 @@ void AM_drawkeys(void)
     }
 }
 
-void AM_drawCrosshair(int color)
+static void AM_drawCrosshair(int color, boolean force)
 {
-    fb[(f_w * (f_h + 1)) / 2] = color;  // single point for now
+    // [crispy] draw an actual crosshair
+    if (!followplayer || force)
+    {
+        static fline_t h, v;
+
+        if (!h.a.x || force)
+        {
+            h.a.x = h.b.x = v.a.x = v.b.x = f_x + f_w / 2;
+            h.a.y = h.b.y = v.a.y = v.b.y = f_y + f_h / 2;
+            h.a.x -= 2; h.b.x += 2;
+            v.a.y -= 2; v.b.y += 2;
+        }
+
+        AM_drawFline(&h, color);
+        AM_drawFline(&v, color);
+    }
+// [crispy] do not draw the useless dot on the player arrow
+/*
+    else
+#ifndef CRISPY_TRUECOLOR
+    fb[(f_w*(f_h+1))/2] = color; // single point for now
+#else
+    fb[(f_w*(f_h+1))/2] = pal_color[color]; // single point for now
+#endif
+*/
+
 }
 
 void AM_Drawer(void)
@@ -1971,12 +2029,12 @@ void AM_Drawer(void)
     if (grid)
         AM_drawGrid(GRIDCOLORS);
     AM_drawWalls();
+    AM_drawMarks(); // [crispy] restore mapmarker functionality
     AM_drawPlayers();
     if (cheating == 2)
         AM_drawThings(THINGCOLORS, THINGRANGE);
-//  AM_drawCrosshair(XHAIRCOLORS);
+    AM_drawCrosshair(XHAIRCOLORS, false);
 
-//  AM_drawMarks();
     if (gameskill == sk_baby || crispy->keysloc)
     {
         AM_drawkeys();
@@ -2002,3 +2060,39 @@ void AM_Drawer(void)
 //  I_Update();
 //  V_MarkRect(f_x, f_y, f_w, f_h);
 }
+
+// [crispy] extended savegames
+void AM_GetMarkPoints (int *n, long *p)
+{
+    int i;
+
+    *n = markpointnum;
+    *p = -1L;
+
+    // [crispy] prevent saving markpoints from previous map
+    if (lastlevel == gamemap && lastepisode == gameepisode)
+    {
+        for (i = 0; i < AM_NUMMARKPOINTS; i++)
+        {
+            *p++ = (long)markpoints[i].x;
+            *p++ = (markpoints[i].x == -1) ? 0L : (long)markpoints[i].y;
+        }
+    }
+}
+
+void AM_SetMarkPoints (int n, long *p)
+{
+    int i;
+
+    AM_LevelInit(false);
+    lastlevel = gamemap;
+    lastepisode = gameepisode;
+
+    markpointnum = n;
+
+    for (i = 0; i < AM_NUMMARKPOINTS; i++)
+    {
+        markpoints[i].x = (int64_t)*p++;
+        markpoints[i].y = (int64_t)*p++;
+    }
+}
diff --git a/src/heretic/am_map.h b/src/heretic/am_map.h
index 3a5bdc62b7..dea440c58f 100644
--- a/src/heretic/am_map.h
+++ b/src/heretic/am_map.h
@@ -59,7 +59,7 @@
 #define SECRETWALLRANGE WALLRANGE
 #define GRIDCOLORS	(GRAYS + GRAYSRANGE/2)
 #define GRIDRANGE	0
-#define XHAIRCOLORS	GRAYS
+#define XHAIRCOLORS	WHITE
 
 // drawing stuff
 #define	FB		0
diff --git a/src/heretic/doomdef.h b/src/heretic/doomdef.h
index adf64c172e..e976e06400 100644
--- a/src/heretic/doomdef.h
+++ b/src/heretic/doomdef.h
@@ -615,6 +615,8 @@ extern int testcontrols_mousespeed;
 extern int vanilla_savegame_limit;
 extern int vanilla_demo_limit;
 
+extern FILE *SaveGameFP; // [crispy] for usage in extsavg
+
 /*
 ===============================================================================
 
diff --git a/src/heretic/g_game.c b/src/heretic/g_game.c
index 7be08d3eee..0a4398ddd4 100644
--- a/src/heretic/g_game.c
+++ b/src/heretic/g_game.c
@@ -37,6 +37,7 @@
 
 #include "deh_main.h" // [crispy] for demo footer
 #include "memio.h"
+#include "p_extsaveg.h" // [crispy] for extended savegame information
 
 // Macros
 
@@ -2162,10 +2163,12 @@ void G_DoLoadGame(void)
         I_Error("Bad savegame");
     }
 
+    // [crispy] read more extended savegame data
+    P_ReadExtendedSaveGameData();
+
     SV_Close();
 }
 
-
 /*
 ====================
 =
@@ -2892,6 +2895,8 @@ void G_DoSaveGame(void)
     P_ArchiveThinkers();
     P_ArchiveSpecials();
     SV_WriteSaveGameEOF();
+    // [crispy] write extended savegame data
+    P_WriteExtendedSaveGameData();
     SV_Close();
 
     gameaction = ga_nothing;
diff --git a/src/heretic/p_extsaveg.c b/src/heretic/p_extsaveg.c
new file mode 100644
index 0000000000..7f3c3c72f8
--- /dev/null
+++ b/src/heretic/p_extsaveg.c
@@ -0,0 +1,145 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 1993-2008 Raven Software
+// Copyright(C) 2005-2014 Simon Howard
+// Copyright(C) 2016 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.
+//
+// DESCRIPTION:
+//	[crispy] Archiving: Extended SaveGame I/O.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "config.h"
+#include "doomtype.h"
+#include "doomdef.h"
+#include "deh_main.h"
+#include "m_misc.h"
+#include "p_extsaveg.h"
+#include "p_local.h"
+#include "z_zone.h"
+
+#define MAX_LINE_LEN 260
+#define MAX_STRING_LEN 80
+
+static char *line, *string;
+
+static void P_WritePackageTarname (const char *key)
+{
+    M_snprintf(line, MAX_LINE_LEN, "%s %s\n", key, PACKAGE_VERSION);
+    fputs(line, SaveGameFP);
+}
+
+// markpoints[]
+
+extern void AM_GetMarkPoints (int *n, long *p);
+extern void AM_SetMarkPoints (int n, long *p);
+
+static void P_WriteMarkPoints (const char *key)
+{
+    int n;
+    long p[20];
+
+    AM_GetMarkPoints(&n, p);
+
+    if (p[0] != -1)
+    {
+        M_snprintf(line, MAX_LINE_LEN, "%s %d %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld\n",
+                   key, n,
+                   p[0], p[1], p[2], p[3], p[4],
+                   p[5], p[6], p[7], p[8], p[9],
+                   p[10], p[11], p[12], p[13], p[14],
+                   p[15], p[16], p[17], p[18], p[19]);
+        fputs(line, SaveGameFP);
+    }
+}
+
+static void P_ReadMarkPoints (const char *key)
+{
+    int n;
+    long p[20];
+
+    if (sscanf(line, "%s %d %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld\n",
+               string, &n,
+               &p[0], &p[1], &p[2], &p[3], &p[4],
+               &p[5], &p[6], &p[7], &p[8], &p[9],
+               &p[10], &p[11], &p[12], &p[13], &p[14],
+               &p[15], &p[16], &p[17], &p[18], &p[19]) == 22 &&
+        !strncmp(string, key, MAX_STRING_LEN))
+    {
+        AM_SetMarkPoints(n, p);
+    }
+}
+
+typedef struct
+{
+    const char *key;
+    void (* extsavegwritefn) (const char *key);
+    void (* extsavegreadfn) (const char *key);
+    const int pass;
+} extsavegdata_t;
+
+static const extsavegdata_t extsavegdata[] =
+{
+    // [crispy] @FORKS: please change this if you are going to introduce incompatible changes!
+    {"crispy-heretic", P_WritePackageTarname, NULL, 0},
+    {"markpoints", P_WriteMarkPoints, P_ReadMarkPoints, 1},
+};
+
+void P_WriteExtendedSaveGameData (void)
+{
+    int i;
+
+    line = malloc(MAX_LINE_LEN);
+
+    for (i = 0; i < arrlen(extsavegdata); i++)
+    {
+        extsavegdata[i].extsavegwritefn(extsavegdata[i].key);
+    }
+
+    free(line);
+}
+
+static void P_ReadKeyValuePairs (int pass)
+{
+    while (fgets(line, MAX_LINE_LEN, SaveGameFP))
+    {
+        if (sscanf(line, "%s", string) == 1)
+        {
+            int i;
+
+            for (i = 1; i < arrlen(extsavegdata); i++)
+            {
+                if (extsavegdata[i].extsavegreadfn &&
+                    extsavegdata[i].pass == pass &&
+                    !strncmp(string, extsavegdata[i].key, MAX_STRING_LEN))
+                {
+                    extsavegdata[i].extsavegreadfn(extsavegdata[i].key);
+                }
+            }
+        }
+    }
+}
+
+void P_ReadExtendedSaveGameData (void)
+{
+    line = malloc(MAX_LINE_LEN);
+    string = malloc(MAX_STRING_LEN);
+
+    // [crispy] only second pass for Heretic
+    P_ReadKeyValuePairs(1);
+
+    free(line);
+    free(string);
+}
diff --git a/src/heretic/p_extsaveg.h b/src/heretic/p_extsaveg.h
new file mode 100644
index 0000000000..a0de68fb89
--- /dev/null
+++ b/src/heretic/p_extsaveg.h
@@ -0,0 +1,27 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 1993-2008 Raven Software
+// Copyright(C) 2005-2014 Simon Howard
+// Copyright(C) 2016 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.
+//
+// DESCRIPTION:
+//	[crispy] Archiving: Extended SaveGame I/O.
+//
+
+#ifndef __P_EXTSAVEG__
+#define __P_EXTSAVEG__
+
+extern void P_WriteExtendedSaveGameData (void);
+extern void P_ReadExtendedSaveGameData (void);
+
+#endif
diff --git a/src/heretic/p_saveg.c b/src/heretic/p_saveg.c
index b5b6930525..21f1bd1c45 100644
--- a/src/heretic/p_saveg.c
+++ b/src/heretic/p_saveg.c
@@ -26,7 +26,7 @@
 #include "v_video.h"
 #include "a11y.h"
 
-static FILE *SaveGameFP;
+FILE *SaveGameFP;
 
 int vanilla_savegame_limit = 1;
 


Reviews:
user: login: Noseey
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-23T20:41:05Z
user: login: Noseey
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-23T20:42:25Z
user: login: fabiangreffrath
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-24T08:13:35Z
user: login: fabiangreffrath
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-24T08:14:15Z
user: login: fabiangreffrath
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-24T08:15:37Z
user: login: fabiangreffrath
body: Thank you very much for this, it's looking pretty good already!

Two general remarks:
* Please do not introduce dead code, as in: do not introduce new code just to have it commented out.
* The chances to mix up savegames between an ExMy and MAPxy level scheme, or between IWAD and PWAD levels, are negligible in Heretic. Thus, I don't think we will never need the first pass ext-savegame parsing at all. I'd be fine if you just remove all of this from the Heretic implementation.
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-24T08:20:41Z
user: login: Noseey
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-24T16:35:24Z
user: login: fabiangreffrath
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-24T17:14:41Z
user: login: Noseey
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-24T18:14:11Z
user: login: Noseey
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-24T18:14:38Z
user: login: fabiangreffrath
body: Yep, looks good. Thank you!
state: APPROVED
author_association: OWNER
submitted_at: 2025-03-26T21:06:53Z
user: login: fabiangreffrath
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-31T09:38:18Z
user: login: fabiangreffrath
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-31T09:38:35Z
user: login: Noseey
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-31T11:07:19Z

Review comments:
diff_hunk: @@ -1971,12 +2028,12 @@ void AM_Drawer(void)
     if (grid)
         AM_drawGrid(GRIDCOLORS);
     AM_drawWalls();
+    AM_drawMarks(); // [crispy] restore mapmarker functionality
path: src/heretic/am_map.c
user: login: Noseey
body: I decided to draw the Markers before the Player position, since the small-numbers from Heretic are a bit clunky and tend to obfuscate the player position. 
created_at: 2025-03-23T20:41:05Z
author_association: COLLABORATOR
diff_hunk: @@ -2161,8 +2166,12 @@ void G_DoLoadGame(void)
     {                           // Missing savegame termination marker
         I_Error("Bad savegame");
     }
-}
+    
+    // [crispy] read more extended savegame data
+    P_ReadExtendedSaveGameData(1);
 
+    fclose(SaveGameFP);
path: src/heretic/g_game.c
user: login: Noseey
body: As far as I have seen, there was no fclose called after loading a game, so I added it. If requested, I can also make this a separate PR later on. 
created_at: 2025-03-23T20:42:25Z
author_association: COLLABORATOR
diff_hunk: @@ -1971,12 +2028,12 @@ void AM_Drawer(void)
     if (grid)
         AM_drawGrid(GRIDCOLORS);
     AM_drawWalls();
+    AM_drawMarks(); // [crispy] restore mapmarker functionality
path: src/heretic/am_map.c
user: login: fabiangreffrath
body: Fine with me.
created_at: 2025-03-24T08:13:35Z
author_association: OWNER
diff_hunk: @@ -1999,3 +2056,39 @@ void AM_Drawer(void)
 //  I_Update();
 //  V_MarkRect(f_x, f_y, f_w, f_h);
 }
+
+// [crispy] extended savegames
+void AM_GetMarkPoints (int *n, long *p)
+{
+	int i;
+
+	*n = markpointnum;
+	*p = -1L;
+
+	// [crispy] prevent saving markpoints from previous map
+	if (lastlevel == gamemap && lastepisode == gameepisode)
+	{
+		for (i = 0; i < AM_NUMMARKPOINTS; i++)
+		{
+			*p++ = (long)markpoints[i].x;
+			*p++ = (markpoints[i].x == -1) ? 0L : (long)markpoints[i].y;
+		}
+	}
+}
+
+void AM_SetMarkPoints (int n, long *p)
+{
+	int i;
+
+	AM_LevelInit(false);
+	lastlevel = gamemap;
+	lastepisode = gameepisode;
+
+	markpointnum = n;
+
+	for (i = 0; i < AM_NUMMARKPOINTS; i++)
+	{
+		markpoints[i].x = (int64_t)*p++;
+		markpoints[i].y = (int64_t)*p++;
+	}
+}
path: src/heretic/am_map.c
user: login: fabiangreffrath
body: Missing newline at EOF.
created_at: 2025-03-24T08:14:15Z
author_association: OWNER
diff_hunk: @@ -2161,8 +2166,12 @@ void G_DoLoadGame(void)
     {                           // Missing savegame termination marker
         I_Error("Bad savegame");
     }
-}
+    
+    // [crispy] read more extended savegame data
+    P_ReadExtendedSaveGameData(1);
 
+    fclose(SaveGameFP);
path: src/heretic/g_game.c
user: login: fabiangreffrath
body: Oops, this was an oversight, better fix it ASAP. Is this already in Chocolate Heretic?
created_at: 2025-03-24T08:15:37Z
author_association: OWNER
diff_hunk: @@ -2161,8 +2166,12 @@ void G_DoLoadGame(void)
     {                           // Missing savegame termination marker
         I_Error("Bad savegame");
     }
-}
+    
+    // [crispy] read more extended savegame data
+    P_ReadExtendedSaveGameData(1);
 
+    fclose(SaveGameFP);
path: src/heretic/g_game.c
user: login: Noseey
body: I just checked and yes, this seems to have been forgotten upstream in Chocolate Heretic. It only checks the save game terminator and then the function ends, while before an fopen took place as ready-only when calling SV_OpenRead.

Chocolate Heretic g_game.c:
```C
void G_DoLoadGame(void)
{
    int i;
    int a, b, c;
    char savestr[SAVESTRINGSIZE];
    char vcheck[VERSIONSIZE], readversion[VERSIONSIZE];

    gameaction = ga_nothing;

    SV_OpenRead(savename);

    free(savename);
    savename = NULL;

    // Skip the description field
    SV_Read(savestr, SAVESTRINGSIZE);

    memset(vcheck, 0, sizeof(vcheck));
    DEH_snprintf(vcheck, VERSIONSIZE, "version %i", HERETIC_VERSION);
    SV_Read(readversion, VERSIONSIZE);

    if (strncmp(readversion, vcheck, VERSIONSIZE) != 0)
    {                           // Bad version
        return;
    }
    gameskill = SV_ReadByte();
    gameepisode = SV_ReadByte();
    gamemap = SV_ReadByte();
    for (i = 0; i < MAXPLAYERS; i++)
    {
        playeringame[i] = SV_ReadByte();
    }
    // Load a base level
    G_InitNew(gameskill, gameepisode, gamemap);

    // Create leveltime
    a = SV_ReadByte();
    b = SV_ReadByte();
    c = SV_ReadByte();
    leveltime = (a << 16) + (b << 8) + c;

    // De-archive all the modifications
    P_UnArchivePlayers();
    P_UnArchiveWorld();
    P_UnArchiveThinkers();
    P_UnArchiveSpecials();

    if (SV_ReadByte() != SAVE_GAME_TERMINATOR)
    {                           // Missing savegame termination marker
        I_Error("Bad savegame");
    }
}
```
created_at: 2025-03-24T16:35:24Z
author_association: COLLABORATOR
diff_hunk: @@ -2161,8 +2166,12 @@ void G_DoLoadGame(void)
     {                           // Missing savegame termination marker
         I_Error("Bad savegame");
     }
-}
+    
+    // [crispy] read more extended savegame data
+    P_ReadExtendedSaveGameData(1);
 
+    fclose(SaveGameFP);
path: src/heretic/g_game.c
user: login: fabiangreffrath
body: Could you do me a favour and fix this in Chocolate Doom first, please?
created_at: 2025-03-24T17:14:41Z
author_association: OWNER
diff_hunk: @@ -2161,8 +2166,12 @@ void G_DoLoadGame(void)
     {                           // Missing savegame termination marker
         I_Error("Bad savegame");
     }
-}
+    
+    // [crispy] read more extended savegame data
+    P_ReadExtendedSaveGameData(1);
 
+    fclose(SaveGameFP);
path: src/heretic/g_game.c
user: login: Noseey
body: Ok, I have removed it here and will create a PR for Chocolate Doom.
created_at: 2025-03-24T18:14:11Z
author_association: COLLABORATOR
diff_hunk: @@ -1999,3 +2056,39 @@ void AM_Drawer(void)
 //  I_Update();
 //  V_MarkRect(f_x, f_y, f_w, f_h);
 }
+
+// [crispy] extended savegames
+void AM_GetMarkPoints (int *n, long *p)
+{
+	int i;
+
+	*n = markpointnum;
+	*p = -1L;
+
+	// [crispy] prevent saving markpoints from previous map
+	if (lastlevel == gamemap && lastepisode == gameepisode)
+	{
+		for (i = 0; i < AM_NUMMARKPOINTS; i++)
+		{
+			*p++ = (long)markpoints[i].x;
+			*p++ = (markpoints[i].x == -1) ? 0L : (long)markpoints[i].y;
+		}
+	}
+}
+
+void AM_SetMarkPoints (int n, long *p)
+{
+	int i;
+
+	AM_LevelInit(false);
+	lastlevel = gamemap;
+	lastepisode = gameepisode;
+
+	markpointnum = n;
+
+	for (i = 0; i < AM_NUMMARKPOINTS; i++)
+	{
+		markpoints[i].x = (int64_t)*p++;
+		markpoints[i].y = (int64_t)*p++;
+	}
+}
path: src/heretic/am_map.c
user: login: Noseey
body: The newline has been added!
created_at: 2025-03-24T18:14:37Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,148 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 1993-2008 Raven Software
+// Copyright(C) 2005-2014 Simon Howard
+// Copyright(C) 2016 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.
+//
+// DESCRIPTION:
+//	[crispy] Archiving: Extended SaveGame I/O.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "config.h"
+#include "doomtype.h"
+#include "doomdef.h"
+#include "deh_main.h"
+#include "m_misc.h"
+#include "p_extsaveg.h"
+#include "p_local.h"
+#include "z_zone.h"
+
+#define MAX_LINE_LEN 260
+#define MAX_STRING_LEN 80
+
+static char *line, *string;
+
+static void P_WritePackageTarname (const char *key)
+{
+    M_snprintf(line, MAX_LINE_LEN, "%s %s\n", key, PACKAGE_VERSION);
+    fputs(line, SaveGameFP);
+}
+
+// markpoints[]
+
+extern void AM_GetMarkPoints (int *n, long *p);
+extern void AM_SetMarkPoints (int n, long *p);
+
+static void P_WriteMarkPoints (const char *key)
+{
+    int n;
+    long p[20];
+
+    AM_GetMarkPoints(&n, p);
+
+    if (p[0] != -1)
+    {
+        M_snprintf(line, MAX_LINE_LEN, "%s %d %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld\n",
+                   key, n,
+                   p[0], p[1], p[2], p[3], p[4],
+                   p[5], p[6], p[7], p[8], p[9],
+                   p[10], p[11], p[12], p[13], p[14],
+                   p[15], p[16], p[17], p[18], p[19]);
+        fputs(line, SaveGameFP);
+    }
+}
+
+static void P_ReadMarkPoints (const char *key)
+{
+    int n;
+    long p[20];
+
+    if (sscanf(line, "%s %d %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld\n",
+               string, &n,
+               &p[0], &p[1], &p[2], &p[3], &p[4],
+               &p[5], &p[6], &p[7], &p[8], &p[9],
+               &p[10], &p[11], &p[12], &p[13], &p[14],
+               &p[15], &p[16], &p[17], &p[18], &p[19]) == 22 &&
+        !strncmp(string, key, MAX_STRING_LEN))
+    {
+        AM_SetMarkPoints(n, p);
+    }
+}
+
+typedef struct
+{
+    const char *key;
+    void (* extsavegwritefn) (const char *key);
+    void (* extsavegreadfn) (const char *key);
+    const int pass;
+} extsavegdata_t;
+
+static const extsavegdata_t extsavegdata[] =
+{
+    // [crispy] @FORKS: please change this if you are going to introduce incompatible changes!
+    {"crispy-heretic", P_WritePackageTarname, NULL, 0},
+    {"markpoints", P_WriteMarkPoints, P_ReadMarkPoints, 1},
+};
+
+void P_WriteExtendedSaveGameData (void)
+{
+    int i;
+
+    line = malloc(MAX_LINE_LEN);
+
+    for (i = 0; i < arrlen(extsavegdata); i++)
+    {
+        extsavegdata[i].extsavegwritefn(extsavegdata[i].key);
+    }
+
+    free(line);
+}
+
+static void P_ReadKeyValuePairs (int pass)
+{
+    while (fgets(line, MAX_LINE_LEN, SaveGameFP))
+    {
+        if (sscanf(line, "%s", string) == 1)
+        {
+            int i;
+
+            for (i = 1; i < arrlen(extsavegdata); i++)
+            {
+                if (extsavegdata[i].extsavegreadfn &&
+                    extsavegdata[i].pass == pass &&
+                    !strncmp(string, extsavegdata[i].key, MAX_STRING_LEN))
+                {
+                    extsavegdata[i].extsavegreadfn(extsavegdata[i].key);
+                }
+            }
+        }
+    }
+}
+
+// [crispy] pointer to the info struct for the map lump about to load
+lumpinfo_t *savemaplumpinfo = NULL;
path: src/heretic/p_extsaveg.c
user: login: fabiangreffrath
body: I think we don't need this.
created_at: 2025-03-31T09:38:18Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,38 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 1993-2008 Raven Software
+// Copyright(C) 2005-2014 Simon Howard
+// Copyright(C) 2016 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.
+//
+// DESCRIPTION:
+//	[crispy] Archiving: Extended SaveGame I/O.
+//
+
+#ifndef __P_EXTSAVEG__
+#define __P_EXTSAVEG__
+
+/* p_extsaveg.c */
+extern char *savewadfilename;
path: src/heretic/p_extsaveg.h
user: login: fabiangreffrath
body: And we don't need this either.
created_at: 2025-03-31T09:38:35Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,38 @@
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 1993-2008 Raven Software
+// Copyright(C) 2005-2014 Simon Howard
+// Copyright(C) 2016 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.
+//
+// DESCRIPTION:
+//	[crispy] Archiving: Extended SaveGame I/O.
+//
+
+#ifndef __P_EXTSAVEG__
+#define __P_EXTSAVEG__
+
+/* p_extsaveg.c */
+extern char *savewadfilename;
path: src/heretic/p_extsaveg.h
user: login: Noseey
body: Good point, I removed both of them. 
created_at: 2025-03-31T11:07:19Z
author_association: COLLABORATOR

PR comments:
user: login: Noseey
created_at: 2025-03-24T18:17:57Z
body: >  I don't think we will never need the first pass ext-savegame parsing at all. I'd be fine if you just remove all of this from the Heretic implementation.

I now removed it from the P_ReadExtendedSaveGameData but left it in place for the P_ReadKeyValuePairs and extsavegdata_t, so it can be easily brought back if we get some killer vanilla PWADs for Heretic some day. 😉 

author_association: COLLABORATOR
user: login: Noseey
created_at: 2025-03-26T21:13:00Z
body: > Yep, looks good. Thank you!

I will do some final checks during the weekend and then set it to RfR. 
author_association: COLLABORATOR
user: login: Noseey
created_at: 2025-03-29T12:18:13Z
body: I moved it now to RfR state. 
author_association: COLLABORATOR
user: login: fabiangreffrath
created_at: 2025-03-31T11:12:18Z
body: Thank you very much!
author_association: OWNER