"snapshotmaps" command

Takes two screenshots for a list of maps that have an "Alternate View Point" thing with tag 0 -- one intended for level select pictures and another for Discord Rich Presence. If no view point exists, the map is skipped.
This commit is contained in:
Sally Coolatta 2024-07-21 08:54:49 -04:00
parent 00b0b7b848
commit 6b87b586d2
20 changed files with 278 additions and 15 deletions

View file

@ -1887,7 +1887,7 @@ void CON_Drawer(void)
{
Lock_state();
if (!con_started || !graphics_started)
if (!con_started || !graphics_started || g_takemapthumbnail != TMT_NO)
{
Unlock_state();
return;

View file

@ -365,6 +365,11 @@ static bool D_Display(bool world)
ZoneScoped;
if (g_takemapthumbnail != TMT_NO)
{
forcerefresh = true;
}
if (!dedicated)
{
if (nodrawers)
@ -638,6 +643,7 @@ static bool D_Display(bool world)
// rhi: display the software framebuffer to the screen
//if (rendermode == render_soft)
if (g_takemapthumbnail == TMT_NO)
{
// TODO: THIS SHOULD IDEALLY BE IN REGULAR HUD CODE !!
// (st_stuff.c ST_Drawer, also duplicated in k_podium.c)
@ -671,11 +677,11 @@ static bool D_Display(bool world)
V_DrawCustomFadeScreen("FADEMAP0", fade);
}
}
}
if (rendermode == render_soft)
{
VID_DisplaySoftwareScreen();
}
if (rendermode == render_soft)
{
VID_DisplaySoftwareScreen();
}
if (lastdraw)
@ -2319,3 +2325,58 @@ const char *D_Home(void)
if (usehome) return userhome;
else return NULL;
}
void D_TakeMapSnapshots(void)
{
// This function sucks ass!
const INT32 old_mode = vid.modenum;
player_t *const player = &players[consoleplayer];
mobj_t *newViewMobj = NULL;
newViewMobj = P_FindObjectTypeFromTag(MT_ALTVIEWMAN, 0);
if (newViewMobj == NULL)
{
// No camera? Skip.
CONS_Alert(CONS_WARNING, M_GetText("Map %s does not have an Alternate View Point with tag 0. Unable to create thumbnails.\n"), G_BuildMapName(gamemap));
return;
}
P_SetTarget(&player->awayview.mobj, newViewMobj);
player->awayview.tics = -1;
R_ResetViewInterpolation(0);
camera[0].x = player->awayview.mobj->x;
camera[0].y = player->awayview.mobj->y;
camera[0].z = player->awayview.mobj->z;
camera[0].angle = player->awayview.mobj->angle;
camera[0].aiming = player->awayview.mobj->pitch;
camera[0].subsector = player->awayview.mobj->subsector;
// Force Software mode
if (rendermode != 0)
{
setrenderneeded = 0;
D_Display(true);
}
// Render snapshot at game resolution (level select)
g_takemapthumbnail = TMT_PICTURE;
setmodeneeded = VID_GetModeForSize(BASEVIDWIDTH, BASEVIDHEIGHT) + 1;
D_Display(true);
I_CaptureVideoFrame();
// Render snapshot at 1024x1024 (rich presence)
g_takemapthumbnail = TMT_RICHPRES;
setmodeneeded = VID_GetModeForSize(1024, 1024) + 1;
D_Display(true);
I_CaptureVideoFrame();
// Revert mode to user preference
if (vid.modenum != old_mode)
{
setmodeneeded = old_mode + 1;
D_Display(true);
}
}

View file

@ -55,6 +55,8 @@ void D_ProcessEvents(boolean callresponders);
const char *D_Home(void);
void D_TakeMapSnapshots(void);
//
// BASE LEVEL
//

View file

@ -194,6 +194,7 @@ static void Command_Automate_Set(void);
static void Command_Eval(void);
static void Command_WriteTextmap(void);
static void Command_SnapshotMaps(void);
#ifdef DEVELOP
static void Command_FastForward(void);
@ -446,6 +447,7 @@ void D_RegisterServerCommands(void)
COM_AddDebugCommand("eval", Command_Eval);
COM_AddCommand("writetextmap", Command_WriteTextmap);
COM_AddCommand("snapshotmaps", Command_SnapshotMaps);
// for master server connection
AddMServCommands();
@ -6823,6 +6825,76 @@ ROUNDQUEUE_MAX
CON_ToggleOff();
}
static void Command_SnapshotMaps(void)
{
if (COM_Argc() < 2)
{
CONS_Printf(
"snapshotmaps <map> [map2...]: Create a thumbnail screenshot for the specified levels.\n"
"- Use the full map name, e.g. RR_TestRun.\n"
"- You can give this command UP TO %d map names and it will create images for all of them.\n"
"- This command generates two images -- one 320x200 for the PICTURE lump, another 1024x1024 for rich presence.\n"
"- The map requires a \"snapshot camera\" object to have been placed.\n"
"- The location of the generated images will appear in the console.\n",
ROUNDQUEUE_MAX
);
return;
}
if (Playing())
{
CONS_Alert(CONS_ERROR, "This command cannot be used in-game. Return to the titlescreen first!\n");
return;
}
if (COM_Argc() - 1 > ROUNDQUEUE_MAX)
{
CONS_Alert(CONS_ERROR, "Cannot snapshot more than %d maps. Try again.\n", ROUNDQUEUE_MAX);
return;
}
// Start up a "minor" grand prix session
memset(&grandprixinfo, 0, sizeof(struct grandprixinfo));
memset(&roundqueue, 0, sizeof(struct roundqueue));
grandprixinfo.gamespeed = KARTSPEED_NORMAL;
grandprixinfo.masterbots = false;
grandprixinfo.gp = true;
grandprixinfo.cup = NULL;
grandprixinfo.wonround = false;
grandprixinfo.initalize = true;
roundqueue.position = 1;
roundqueue.roundnum = 1;
roundqueue.snapshotmaps = true;
size_t i;
for (i = 1; i < COM_Argc(); ++i)
{
INT32 map = G_MapNumber(COM_Argv(i));
if (map < 0 || map >= nummapheaders)
{
CONS_Alert(CONS_WARNING, "%s: Map doesn't exist. Not doing anything.\n", COM_Argv(i));
// clear round queue (to be safe)
memset(&roundqueue, 0, sizeof(struct roundqueue));
return;
}
INT32 gt = G_GuessGametypeByTOL(mapheaderinfo[map]->typeoflevel);
G_MapIntoRoundQueue(map, gt != -1 ? gt : GT_RACE, false, false);
}
D_MapChange(1 + roundqueue.entries[0].mapnum, roundqueue.entries[0].gametype, false, true, 1, false, false);
CON_ToggleOff();
}
#ifdef DEVELOP
static void Command_FastForward(void)
{

View file

@ -1296,6 +1296,10 @@ void G_PreLevelTitleCard(void)
//
boolean G_IsTitleCardAvailable(void)
{
// We're trying to take map pictures dude
if (roundqueue.snapshotmaps == true)
return false;
// Don't show for attract demos
if (demo.attract)
return false;

View file

@ -71,6 +71,7 @@ extern struct roundqueue
UINT8 size; // Number of entries in the round queue
boolean netcommunicate; // As server, should we net-communicate this in XD_MAP?
boolean writetextmap; // This queue is for automated map conversion
boolean snapshotmaps; // This queue is for automated map thumbnails
roundentry_t entries[ROUNDQUEUE_MAX]; // Entries in the round queue
} roundqueue;

View file

@ -23,7 +23,7 @@ ScreenshotPass::~ScreenshotPass() = default;
void ScreenshotPass::capture(Rhi& rhi, Handle<GraphicsContext> ctx)
{
bool doing_screenshot = takescreenshot || moviemode != MM_OFF;
bool doing_screenshot = takescreenshot || moviemode != MM_OFF || g_takemapthumbnail != TMT_NO;
if (!doing_screenshot)
{
@ -48,6 +48,11 @@ void ScreenshotPass::capture(Rhi& rhi, Handle<GraphicsContext> ctx)
std::move(&pixel_data_[pixel_data_row * read_stride], &pixel_data_[pixel_data_row * read_stride + stride], &packed_data_[row * stride]);
}
if (g_takemapthumbnail != TMT_NO)
{
M_SaveMapThumbnail(width_, height_, tcb::as_bytes(tcb::span(packed_data_)));
}
if (takescreenshot)
{
M_DoScreenShot(width_, height_, tcb::as_bytes(tcb::span(packed_data_)));

View file

@ -6124,7 +6124,7 @@ void K_drawKartHUD(void)
K_drawKartPlayerCheck();
// nametags
if (LUA_HudEnabled(hud_names) && cv_drawpickups.value)
if (LUA_HudEnabled(hud_names) && R_DrawPickups())
K_drawKartNameTags();
// Draw WANTED status

View file

@ -989,7 +989,7 @@ void M_Drawer(void)
}
// draw pause pic
if (paused && !demo.playback && (menuactive || cv_showhud.value))
if (paused && !demo.playback && (menuactive || R_ShowHUD()))
{
M_DrawPausedText(0);
}

View file

@ -156,6 +156,8 @@ boolean takescreenshot = false; // Take a screenshot this tic
moviemode_t moviemode = MM_OFF;
g_takemapthumbnail_t g_takemapthumbnail = TMT_NO;
char joinedIPlist[NUMLOGIP][2][MAX_LOGIP];
char joinedIP[MAX_LOGIP];
@ -1854,6 +1856,46 @@ failure:
#endif
}
void M_SaveMapThumbnail(UINT32 width, UINT32 height, tcb::span<const std::byte> data)
{
#ifdef USE_PNG
#if NUMSCREENS > 2
char *filepath;
switch (g_takemapthumbnail)
{
case TMT_PICTURE:
default:
{
filepath = va("%s" PATHSEP "PICTURE_%s.png", srb2home, G_BuildMapName(gamemap));
break;
}
case TMT_RICHPRES:
{
filepath = va("%s" PATHSEP "map_%s.png", srb2home, G_BuildMapName(gamemap));
break;
}
}
// save the file
const void* pixel_data = static_cast<const void*>(data.data());
boolean ret = M_SavePNG(filepath, pixel_data, width, height, NULL);
if (ret)
{
CONS_Printf(M_GetText("Created thumbnail at \"%s\"\n"), filepath);
}
else
{
CONS_Alert(CONS_ERROR, M_GetText("Couldn't create %s\n"), filepath);
}
g_takemapthumbnail = TMT_NO;
#endif // #ifdef USE_PNG
#endif // #if NUMSCREENS > 2
}
void M_ScreenshotTicker(void)
{
const UINT8 pid = 0; // TODO: should splitscreen players be allowed to use this too?

View file

@ -31,6 +31,8 @@
void M_DoScreenShot(uint32_t width, uint32_t height, tcb::span<const std::byte> data);
void M_SaveFrame(uint32_t width, uint32_t height, tcb::span<const std::byte> data);
void M_SaveMapThumbnail(uint32_t width, uint32_t height, tcb::span<const std::byte> data);
extern "C" {
#endif
@ -43,6 +45,13 @@ typedef enum {
} moviemode_t;
extern moviemode_t moviemode;
typedef enum {
TMT_NO = 0,
TMT_PICTURE,
TMT_RICHPRES,
} g_takemapthumbnail_t;
extern g_takemapthumbnail_t g_takemapthumbnail;
extern consvar_t cv_screenshot_colorprofile;
extern consvar_t cv_lossless_recorder;
extern consvar_t cv_zlib_memory, cv_zlib_level, cv_zlib_strategy, cv_zlib_window_bits;

View file

@ -14090,6 +14090,17 @@ static mobj_t *P_SpawnMobjFromMapThing(mapthing_t *mthing, fixed_t x, fixed_t y,
return mobj;
}
static boolean P_DoomEdNumIsNOP(UINT16 type)
{
if (type == EDITOR_CAM_DOOMEDNUM)
{
// 3D Mode start Thing
return true;
}
return (type == 0);
}
//
// P_SpawnMapThing
// The fields of the mapthing should
@ -14101,12 +14112,9 @@ mobj_t *P_SpawnMapThing(mapthing_t *mthing)
mobj_t *mobj = NULL;
fixed_t x, y, z;
if (!mthing->type)
if (P_DoomEdNumIsNOP(mthing->type))
return mobj; // Ignore type-0 things as NOPs
if (mthing->type == 3328) // 3D Mode start Thing
return mobj;
if (!objectplacing && P_SpawnNonMobjMapThing(mthing))
return mobj;

View file

@ -595,6 +595,8 @@ extern INT32 numcheatchecks;
extern UINT16 bossdisabled;
extern boolean stoppedclock;
#define EDITOR_CAM_DOOMEDNUM (3328)
#ifdef __cplusplus
} // extern "C"
#endif

View file

@ -8920,6 +8920,35 @@ void P_PostLoadLevel(void)
// We're now done loading the level.
levelloading = false;
if (roundqueue.snapshotmaps == true)
{
if (roundqueue.size > 0)
{
D_TakeMapSnapshots();
G_GetNextMap();
// roundqueue is wiped after the last round, but
// preserve this to track state into the Podium!
roundqueue.snapshotmaps = true;
G_NextLevel();
return;
}
else
{
// Podium: snapshotmaps is finished. Yay!
HU_DoTitlecardCEcho(NULL, va("Congratulations,\\%s!\\Check the console!", cv_playername[0].string), true);
livestudioaudience_timer = 0;
LiveStudioAudience();
CONS_Printf("\n\n\x83""snapshotmaps: Find your images in %s\n", srb2home);
roundqueue.snapshotmaps = false;
}
}
TracyCZoneEnd(__zone);
}

View file

@ -3200,6 +3200,9 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
thiscam->old_angle = thiscam->angle;
thiscam->old_aiming = thiscam->aiming;
if (roundqueue.snapshotmaps == true)
return true;
// We probably shouldn't move the camera if there is no player or player mobj somehow
if (!player || !player->mo)
return true;

View file

@ -41,6 +41,7 @@
#include "doomstat.h" // MAXSPLITSCREENPLAYERS
#include "r_fps.h" // Frame interpolation/uncapped
#include "core/thread_pool.h"
#include "m_misc.h"
#ifdef HWRENDER
#include "hardware/hw_main.h"
@ -969,6 +970,16 @@ void R_CheckFOV(void)
}
}
boolean R_ShowHUD(void)
{
if (g_takemapthumbnail != TMT_NO)
{
return false;
}
return (boolean)cv_showhud.value;
}
//
// R_ExecuteSetViewSize
//
@ -988,7 +999,7 @@ void R_ExecuteSetViewSize(void)
return;
// status bar overlay
st_overlay = cv_showhud.value;
st_overlay = R_ShowHUD();
scaledviewwidth = vid.width;
viewheight = vid.height;

View file

@ -182,6 +182,8 @@ void R_ExecuteSetViewSize(void);
fixed_t R_FOV(int split);
void R_CheckFOV(void);
boolean R_ShowHUD(void);
void R_SetupFrame(int split);
void R_SkyboxFrame(int split);

View file

@ -3765,13 +3765,23 @@ void R_ClipSprites(drawseg_t* dsstart, portal_t* portal)
}
}
boolean R_DrawPickups(void)
{
if (g_takemapthumbnail != TMT_NO)
{
return false;
}
return (boolean)cv_drawpickups.value;
}
/* Check if thing may be drawn from our current view. */
boolean R_ThingVisible (mobj_t *thing)
{
if (thing->sprite == SPR_NULL)
return false;
if (!cv_drawpickups.value)
if (!R_DrawPickups())
{
switch (thing->type)
{

View file

@ -76,6 +76,7 @@ void R_ClearSprites(void);
UINT8 R_GetBoundingBoxColor(mobj_t *thing);
boolean R_ThingBoundingBoxVisible(mobj_t *thing);
boolean R_DrawPickups(void);
boolean R_ThingVisible (mobj_t *thing);
boolean R_ThingWithinDist (mobj_t *thing,

View file

@ -91,7 +91,7 @@
#endif
// maximum number of windowed modes (see windowedModes[][])
#define MAXWINMODES (18)
#define MAXWINMODES (19)
using namespace srb2;
@ -158,6 +158,7 @@ static INT32 windowedModes[MAXWINMODES][2] =
{1280, 800}, // 1.60,4.00
{1280, 720}, // 1.66
{1152, 864}, // 1.33,3.60
{1024,1024}, // SPECIAL, for snapshot taker
{1024, 768}, // 1.33,3.20
{ 800, 600}, // 1.33,2.50
{ 640, 480}, // 1.33,2.00