SRB2/src/d_main.c

1721 lines
44 KiB
C
Raw Normal View History

2014-03-15 09:59:03 -07:00
// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 1993-1996 by id Software, Inc.
// Copyright (C) 1998-2000 by DooM Legacy Team.
2020-02-19 14:08:45 -08:00
// Copyright (C) 1999-2020 by Sonic Team Junior.
2014-03-15 09:59:03 -07:00
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
/// \file d_main.c
/// \brief SRB2 main program
///
/// SRB2 main program (D_SRB2Main) and game loop (D_SRB2Loop),
/// plus functions to parse command line parameters, configure game
/// parameters, and call the startup functions.
#if (defined (__unix__) && !defined (MSDOS)) || defined(__APPLE__) || defined (UNIXCOMMON)
#include <sys/stat.h>
#include <sys/types.h>
#endif
#ifdef __GNUC__
#include <unistd.h> // for getcwd
#endif
2017-09-29 15:25:34 -07:00
#ifdef _WIN32
2014-03-15 09:59:03 -07:00
#include <direct.h>
#include <malloc.h>
#endif
#include <time.h>
#include "doomdef.h"
#include "am_map.h"
#include "console.h"
#include "d_net.h"
#include "f_finale.h"
#include "g_game.h"
#include "hu_stuff.h"
#include "i_sound.h"
#include "i_system.h"
#include "i_threads.h"
2014-03-15 09:59:03 -07:00
#include "i_video.h"
#include "m_argv.h"
#include "m_menu.h"
#include "m_misc.h"
#include "p_setup.h"
#include "p_saveg.h"
#include "r_main.h"
#include "r_local.h"
#include "s_sound.h"
#include "st_stuff.h"
#include "v_video.h"
#include "w_wad.h"
#include "z_zone.h"
#include "d_main.h"
#include "d_netfil.h"
#include "m_cheat.h"
#include "y_inter.h"
#include "p_local.h" // chasecam
#include "mserv.h" // ms_RoomId
#include "m_misc.h" // screenshot functionality
#include "dehacked.h" // Dehacked list test
#include "m_cond.h" // condition initialization
#include "fastcmp.h"
#include "keys.h"
#include "filesrch.h" // refreshdirmenu, mainwadstally
#include "g_input.h" // tutorial mode control scheming
2014-03-15 09:59:03 -07:00
#ifdef CMAKECONFIG
#include "config.h"
#else
#include "config.h.in"
#endif
2014-03-15 09:59:03 -07:00
#ifdef HWRENDER
#include "hardware/hw_main.h" // 3D View Rendering
#endif
#ifdef _WINDOWS
#include "win32/win_main.h" // I_DoStartupMouse
#endif
#ifdef HW3SOUND
#include "hardware/hw3sound.h"
#endif
#include "lua_script.h"
// Version numbers for netplay :upside_down_face:
int VERSION;
int SUBVERSION;
2015-01-01 11:50:31 -08:00
// platform independant focus loss
UINT8 window_notinfocus = false;
2014-03-15 09:59:03 -07:00
//
// DEMO LOOP
//
static char *startupwadfiles[MAX_WADFILES];
static char *startuppwads[MAX_WADFILES];
2014-03-15 09:59:03 -07:00
boolean devparm = false; // started game with -devparm
boolean singletics = false; // timedemo
boolean lastdraw = false;
2019-09-09 12:20:17 -07:00
static void D_CheckRendererState(void);
2014-03-15 09:59:03 -07:00
postimg_t postimgtype = postimg_none;
INT32 postimgparam;
postimg_t postimgtype2 = postimg_none;
INT32 postimgparam2;
// These variables are in effect
// whether the respective sound system is disabled
// or they're init'ed, but the player just toggled them
boolean midi_disabled = false;
2014-03-15 09:59:03 -07:00
boolean sound_disabled = false;
boolean digital_disabled = false;
boolean advancedemo;
#ifdef DEBUGFILE
INT32 debugload = 0;
#endif
UINT16 numskincolors;
menucolor_t *menucolorhead, *menucolortail;
char savegamename[256];
char liveeventbackup[256];
2014-03-15 09:59:03 -07:00
char srb2home[256] = ".";
char srb2path[256] = ".";
boolean usehome = true;
const char *pandf = "%s" PATHSEP "%s";
2019-09-07 22:29:09 -07:00
static char addonsdir[MAX_WADPATH];
2014-03-15 09:59:03 -07:00
//
// EVENT HANDLING
//
// Events are asynchronous inputs generally generated by the game user.
// Events can be discarded if no responder claims them
// referenced from i_system.c for I_GetKey()
event_t events[MAXEVENTS];
INT32 eventhead, eventtail;
boolean dedicated = false;
//
// D_PostEvent
// Called by the I/O functions when input is detected
//
void D_PostEvent(const event_t *ev)
{
events[eventhead] = *ev;
eventhead = (eventhead+1) & (MAXEVENTS-1);
}
// modifier keys
2019-01-17 16:47:01 -08:00
// Now handled in I_OsPolling
UINT8 shiftdown = 0; // 0x1 left, 0x2 right
UINT8 ctrldown = 0; // 0x1 left, 0x2 right
UINT8 altdown = 0; // 0x1 left, 0x2 right
boolean capslock = 0; // gee i wonder what this does.
2014-03-15 09:59:03 -07:00
//
// D_ProcessEvents
// Send all the events of the given timestamp down the responder chain
//
void D_ProcessEvents(void)
{
event_t *ev;
boolean eaten;
2014-03-15 09:59:03 -07:00
for (; eventtail != eventhead; eventtail = (eventtail+1) & (MAXEVENTS-1))
{
ev = &events[eventtail];
// Screenshots over everything so that they can be taken anywhere.
if (M_ScreenshotResponder(ev))
continue; // ate the event
if (gameaction == ga_nothing && gamestate == GS_TITLESCREEN)
{
if (cht_Responder(ev))
continue;
}
2020-01-09 22:42:00 -08:00
// Menu input
#ifdef HAVE_THREADS
I_lock_mutex(&m_menu_mutex);
#endif
{
eaten = M_Responder(ev);
}
#ifdef HAVE_THREADS
I_unlock_mutex(m_menu_mutex);
#endif
if (eaten)
2020-01-09 22:42:00 -08:00
continue; // menu ate the event
// console input
#ifdef HAVE_THREADS
I_lock_mutex(&con_mutex);
#endif
{
eaten = CON_Responder(ev);
}
#ifdef HAVE_THREADS
I_unlock_mutex(con_mutex);
#endif
if (eaten)
continue; // ate the event
2014-03-15 09:59:03 -07:00
G_Responder(ev);
}
}
//
// D_Display
// draw current display, possibly wiping it from the previous
//
// wipegamestate can be set to -1 to force a wipe on the next draw
// added comment : there is a wipe eatch change of the gamestate
gamestate_t wipegamestate = GS_LEVEL;
// -1: Default; 0-n: Wipe index; INT16_MAX: do not wipe
INT16 wipetypepre = -1;
INT16 wipetypepost = -1;
2014-03-15 09:59:03 -07:00
static void D_Display(void)
{
2019-09-09 12:20:17 -07:00
INT32 setrenderstillneeded = 0;
boolean forcerefresh = false;
2014-03-15 09:59:03 -07:00
static boolean wipe = false;
INT32 wipedefindex = 0;
if (dedicated)
return;
if (nodrawers)
return; // for comparative timing/profiling
2019-12-17 11:14:26 -08:00
// Lactozilla: Switching renderers works by checking
2019-09-09 15:27:08 -07:00
// if the game has to do it right when the frame
// needs to render. If so, five things will happen:
// 1. Interface functions will be called so
// that switching to OpenGL creates a
// GL context, and switching to Software
// allocates screen buffers.
// 2. Software will set drawer functions,
// and OpenGL will load textures and
// create plane polygons, if necessary.
// 3. Functions related to switching video
// modes (resolution) are called.
// 4. Patch data is freed from memory,
// and recached if necessary.
// 5. The frame is ready to be drawn!
2019-09-08 14:27:35 -07:00
// stop movie if needs to change renderer
if (setrenderneeded && (moviemode == MM_APNG))
2019-09-08 14:27:35 -07:00
M_StopMovie();
2019-09-09 12:20:17 -07:00
// check for change of renderer or screen size (video mode)
if ((setrenderneeded || setmodeneeded) && !wipe)
{
if (setrenderneeded)
{
CONS_Debug(DBG_RENDER, "setrenderneeded set (%d)\n", setrenderneeded);
setrenderstillneeded = setrenderneeded;
}
2014-03-15 09:59:03 -07:00
SCR_SetMode(); // change video mode
2019-09-09 12:20:17 -07:00
}
2014-03-15 09:59:03 -07:00
2019-09-08 14:27:35 -07:00
if (vid.recalc || setrenderstillneeded)
{
2014-03-15 09:59:03 -07:00
SCR_Recalc(); // NOTE! setsizeneeded is set by SCR_Recalc()
#ifdef HWRENDER
// Shoot! The screen texture was flushed!
if ((rendermode == render_opengl) && (gamestate == GS_INTERMISSION))
usebuffer = false;
#endif
}
2014-03-15 09:59:03 -07:00
2020-01-14 21:29:56 -08:00
if (rendermode == render_soft && !splitscreen)
R_CheckViewMorph();
2014-03-15 09:59:03 -07:00
// change the view size if needed
2019-09-08 14:27:35 -07:00
if (setsizeneeded || setrenderstillneeded)
2014-03-15 09:59:03 -07:00
{
R_ExecuteSetViewSize();
forcerefresh = true; // force background redraw
2014-03-15 09:59:03 -07:00
}
2019-12-17 11:14:26 -08:00
// Lactozilla: Renderer switching
2019-09-09 12:20:17 -07:00
D_CheckRendererState();
2014-03-15 09:59:03 -07:00
// draw buffered stuff to screen
// Used only by linux GGI version
I_UpdateNoBlit();
// save the current screen if about to wipe
wipe = (gamestate != wipegamestate);
if (wipe && wipetypepre != INT16_MAX)
2014-03-15 09:59:03 -07:00
{
// set for all later
wipedefindex = gamestate; // wipe_xxx_toblack
if (gamestate == GS_INTERMISSION)
{
if (intertype == int_spec) // Special Stage
wipedefindex = wipe_specinter_toblack;
else if (intertype != int_coop) // Multiplayer
wipedefindex = wipe_multinter_toblack;
}
if (wipetypepre < 0 || !F_WipeExists(wipetypepre))
wipetypepre = wipedefs[wipedefindex];
2014-03-15 09:59:03 -07:00
if (rendermode != render_none)
{
// Fade to black first
2018-11-18 01:32:38 -08:00
if ((wipegamestate == (gamestate_t)FORCEWIPE ||
(wipegamestate != (gamestate_t)FORCEWIPEOFF
&& !(gamestate == GS_LEVEL || (gamestate == GS_TITLESCREEN && titlemapinaction)))
) // fades to black on its own timing, always
&& wipetypepre != UINT8_MAX)
2014-03-15 09:59:03 -07:00
{
F_WipeStartScreen();
2019-12-04 14:25:39 -08:00
// Check for Mega Genesis fade
wipestyleflags = WSF_FADEOUT;
2020-02-24 16:00:52 -08:00
if (wipegamestate == (gamestate_t)FORCEWIPE)
F_WipeColorFill(31);
else if (F_TryColormapFade(31))
2019-12-04 14:25:39 -08:00
wipetypepost = -1; // Don't run the fade below this one
2014-03-15 09:59:03 -07:00
F_WipeEndScreen();
F_RunWipe(wipetypepre, gamestate != GS_TIMEATTACK && gamestate != GS_TITLESCREEN);
2014-03-15 09:59:03 -07:00
}
F_WipeStartScreen();
}
wipetypepre = -1;
2014-03-15 09:59:03 -07:00
}
else
wipetypepre = -1;
2014-03-15 09:59:03 -07:00
// do buffered drawing
switch (gamestate)
{
2017-04-18 14:23:23 -07:00
case GS_TITLESCREEN:
if (!titlemapinaction || !curbghide) {
F_TitleScreenDrawer();
break;
}
/* FALLTHRU */
2014-03-15 09:59:03 -07:00
case GS_LEVEL:
if (!gametic)
break;
HU_Erase();
2018-12-12 13:53:13 -08:00
AM_Drawer();
2014-03-15 09:59:03 -07:00
break;
case GS_INTERMISSION:
Y_IntermissionDrawer();
HU_Erase();
HU_Drawer();
break;
case GS_TIMEATTACK:
break;
case GS_INTRO:
F_IntroDrawer();
if (wipegamestate == (gamestate_t)-1)
wipe = true;
break;
case GS_ENDING:
F_EndingDrawer();
HU_Erase();
HU_Drawer();
break;
2014-03-15 09:59:03 -07:00
case GS_CUTSCENE:
F_CutsceneDrawer();
HU_Erase();
HU_Drawer();
break;
case GS_GAMEEND:
F_GameEndDrawer();
break;
case GS_EVALUATION:
F_GameEvaluationDrawer();
HU_Erase();
2014-03-15 09:59:03 -07:00
HU_Drawer();
break;
case GS_CONTINUING:
F_ContinueDrawer();
break;
case GS_CREDITS:
F_CreditDrawer();
HU_Erase();
HU_Drawer();
break;
case GS_WAITINGPLAYERS:
// The clientconnect drawer is independent...
case GS_DEDICATEDSERVER:
case GS_NULL:
break;
}
// STUPID race condition...
if (wipegamestate == GS_INTRO && gamestate == GS_TITLESCREEN)
wipegamestate = FORCEWIPEOFF;
else
2014-03-15 09:59:03 -07:00
{
wipegamestate = gamestate;
2017-04-18 14:23:23 -07:00
// clean up border stuff
// see if the border needs to be initially drawn
if (gamestate == GS_LEVEL || (gamestate == GS_TITLESCREEN && titlemapinaction && curbghide && (!hidetitlemap)))
2014-03-15 09:59:03 -07:00
{
// draw the view directly
2014-03-15 09:59:03 -07:00
if (!automapactive && !dedicated && cv_renderview.value)
{
2020-04-18 15:25:28 -07:00
rs_rendercalltime = I_GetTimeMicros();
if (players[displayplayer].mo || players[displayplayer].playerstate == PST_DEAD)
{
topleft = screens[0] + viewwindowy*vid.width + viewwindowx;
objectsdrawn = 0;
#ifdef HWRENDER
if (rendermode != render_soft)
HWR_RenderPlayerView(0, &players[displayplayer]);
else
#endif
if (rendermode != render_none)
R_RenderPlayerView(&players[displayplayer]);
}
// render the second screen
if (splitscreen && players[secondarydisplayplayer].mo)
{
#ifdef HWRENDER
if (rendermode != render_soft)
HWR_RenderPlayerView(1, &players[secondarydisplayplayer]);
else
#endif
if (rendermode != render_none)
{
viewwindowy = vid.height / 2;
M_Memcpy(ylookup, ylookup2, viewheight*sizeof (ylookup[0]));
topleft = screens[0] + viewwindowy*vid.width + viewwindowx;
R_RenderPlayerView(&players[secondarydisplayplayer]);
viewwindowy = 0;
M_Memcpy(ylookup, ylookup1, viewheight*sizeof (ylookup[0]));
}
}
// Image postprocessing effect
if (rendermode == render_soft)
{
2020-01-14 21:29:56 -08:00
if (!splitscreen)
R_ApplyViewMorph();
if (postimgtype)
V_DoPostProcessor(0, postimgtype, postimgparam);
if (postimgtype2)
V_DoPostProcessor(1, postimgtype2, postimgparam2);
}
2020-04-18 15:25:28 -07:00
rs_rendercalltime = I_GetTimeMicros() - rs_rendercalltime;
}
2014-03-15 09:59:03 -07:00
if (lastdraw)
{
if (rendermode == render_soft)
{
VID_BlitLinearScreen(screens[0], screens[1], vid.width*vid.bpp, vid.height, vid.width*vid.bpp, vid.rowbytes);
Y_ConsiderScreenBuffer();
usebuffer = true;
}
lastdraw = false;
}
2014-03-15 09:59:03 -07:00
2020-08-23 10:09:55 -07:00
rs_uitime = I_GetTimeMicros();
if (gamestate == GS_LEVEL)
{
ST_Drawer();
F_TextPromptDrawer();
HU_Drawer();
}
else
F_TitleScreenDrawer();
}
2020-08-23 10:09:55 -07:00
else
{
rs_uitime = I_GetTimeMicros();
}
2014-03-15 09:59:03 -07:00
}
// change gamma if needed
// (GS_LEVEL handles this already due to level-specific palettes)
if (forcerefresh && !(gamestate == GS_LEVEL || (gamestate == GS_TITLESCREEN && titlemapinaction)))
2014-03-15 09:59:03 -07:00
V_SetPalette(0);
// draw pause pic
if (paused && cv_showhud.value && (!menuactive || netgame))
{
#if 0
2014-03-15 09:59:03 -07:00
INT32 py;
patch_t *patch;
if (automapactive)
py = 4;
else
py = viewwindowy + 4;
2019-09-08 14:27:35 -07:00
patch = W_CachePatchName("M_PAUSE", PU_PATCH);
2014-03-15 09:59:03 -07:00
V_DrawScaledPatch(viewwindowx + (BASEVIDWIDTH - SHORT(patch->width))/2, py, 0, patch);
#else
INT32 y = ((automapactive) ? (32) : (BASEVIDHEIGHT/2));
M_DrawTextBox((BASEVIDWIDTH/2) - (60), y - (16), 13, 2);
V_DrawCenteredString(BASEVIDWIDTH/2, y - (4), V_YELLOWMAP, "Game Paused");
#endif
2014-03-15 09:59:03 -07:00
}
// vid size change is now finished if it was on...
vid.recalc = 0;
#ifdef HAVE_THREADS
I_lock_mutex(&m_menu_mutex);
#endif
M_Drawer(); // menu is drawn even on top of everything
#ifdef HAVE_THREADS
I_unlock_mutex(m_menu_mutex);
#endif
// focus lost moved to M_Drawer
CON_Drawer();
2020-08-23 10:09:55 -07:00
rs_uitime = I_GetTimeMicros() - rs_uitime;
//
// wipe update
//
if (wipe && wipetypepost != INT16_MAX)
2015-01-01 11:50:31 -08:00
{
// note: moved up here because NetUpdate does input changes
// and input during wipe tends to mess things up
wipedefindex += WIPEFINALSHIFT;
if (wipetypepost < 0 || !F_WipeExists(wipetypepost))
wipetypepost = wipedefs[wipedefindex];
if (rendermode != render_none)
{
F_WipeEndScreen();
2019-12-04 14:25:39 -08:00
2019-11-18 06:39:54 -08:00
// Funny.
if (WipeStageTitle && st_overlay)
{
lt_ticker--;
lt_lasttic = lt_ticker;
ST_preLevelTitleCardDrawer();
2019-11-18 06:39:54 -08:00
V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, levelfadecol);
F_WipeStartScreen();
}
2019-12-04 14:25:39 -08:00
// Check for Mega Genesis fade
if (F_ShouldColormapFade())
{
wipestyleflags |= WSF_FADEIN;
wipestyleflags &= ~WSF_FADEOUT;
}
F_RunWipe(wipetypepost, gamestate != GS_TIMEATTACK && gamestate != GS_TITLESCREEN);
}
2018-12-27 20:23:09 -08:00
// reset counters so timedemo doesn't count the wipe duration
if (timingdemo)
{
framecount = 0;
demostarttime = I_GetTime();
}
2019-11-14 16:31:20 -08:00
wipetypepost = -1;
2015-01-01 11:50:31 -08:00
}
else
wipetypepost = -1;
2015-01-01 11:50:31 -08:00
2014-03-15 09:59:03 -07:00
NetUpdate(); // send out any new accumulation
// It's safe to end the game now.
if (G_GetExitGameFlag())
{
Command_ExitGame_f();
G_ClearExitGameFlag();
}
//
// normal update
//
if (!wipe)
{
if (cv_netstat.value)
{
char s[50];
Net_GetNetStat();
s[sizeof s - 1] = '\0';
snprintf(s, sizeof s - 1, "get %d b/s", getbps);
V_DrawRightAlignedString(BASEVIDWIDTH, BASEVIDHEIGHT-ST_HEIGHT-40, V_YELLOWMAP, s);
snprintf(s, sizeof s - 1, "send %d b/s", sendbps);
V_DrawRightAlignedString(BASEVIDWIDTH, BASEVIDHEIGHT-ST_HEIGHT-30, V_YELLOWMAP, s);
snprintf(s, sizeof s - 1, "GameMiss %.2f%%", gamelostpercent);
V_DrawRightAlignedString(BASEVIDWIDTH, BASEVIDHEIGHT-ST_HEIGHT-20, V_YELLOWMAP, s);
snprintf(s, sizeof s - 1, "SysMiss %.2f%%", lostpercent);
V_DrawRightAlignedString(BASEVIDWIDTH, BASEVIDHEIGHT-ST_HEIGHT-10, V_YELLOWMAP, s);
}
2020-07-05 21:15:08 -07:00
2020-04-18 15:25:28 -07:00
if (cv_renderstats.value)
{
char s[50];
int frametime = I_GetTimeMicros() - rs_prevframetime;
int divisor = 1;
rs_prevframetime = I_GetTimeMicros();
if (rs_rendercalltime > 10000) divisor = 1000;
2020-07-05 21:15:08 -07:00
2020-04-18 15:25:28 -07:00
snprintf(s, sizeof s - 1, "ft %d", frametime / divisor);
V_DrawThinString(30, 10, V_MONOSPACE | V_YELLOWMAP, s);
snprintf(s, sizeof s - 1, "rtot %d", rs_rendercalltime / divisor);
V_DrawThinString(30, 20, V_MONOSPACE | V_YELLOWMAP, s);
snprintf(s, sizeof s - 1, "bsp %d", rs_bsptime / divisor);
V_DrawThinString(30, 30, V_MONOSPACE | V_YELLOWMAP, s);
snprintf(s, sizeof s - 1, "nbsp %d", rs_numbspcalls);
V_DrawThinString(80, 10, V_MONOSPACE | V_BLUEMAP, s);
snprintf(s, sizeof s - 1, "nspr %d", rs_numsprites);
V_DrawThinString(80, 20, V_MONOSPACE | V_BLUEMAP, s);
snprintf(s, sizeof s - 1, "nnod %d", rs_numdrawnodes);
V_DrawThinString(80, 30, V_MONOSPACE | V_BLUEMAP, s);
snprintf(s, sizeof s - 1, "npob %d", rs_numpolyobjects);
V_DrawThinString(80, 40, V_MONOSPACE | V_BLUEMAP, s);
if (rendermode == render_opengl) // OpenGL specific stats
2020-04-18 15:25:28 -07:00
{
2020-08-31 16:09:41 -07:00
#ifdef HWRENDER
snprintf(s, sizeof s - 1, "nsrt %d", rs_hw_nodesorttime / divisor);
2020-04-18 15:25:28 -07:00
V_DrawThinString(30, 40, V_MONOSPACE | V_YELLOWMAP, s);
snprintf(s, sizeof s - 1, "ndrw %d", rs_hw_nodedrawtime / divisor);
2020-04-18 15:25:28 -07:00
V_DrawThinString(30, 50, V_MONOSPACE | V_YELLOWMAP, s);
snprintf(s, sizeof s - 1, "ssrt %d", rs_hw_spritesorttime / divisor);
2020-04-18 15:25:28 -07:00
V_DrawThinString(30, 60, V_MONOSPACE | V_YELLOWMAP, s);
snprintf(s, sizeof s - 1, "sdrw %d", rs_hw_spritedrawtime / divisor);
2020-04-18 15:25:28 -07:00
V_DrawThinString(30, 70, V_MONOSPACE | V_YELLOWMAP, s);
2020-08-23 10:09:55 -07:00
snprintf(s, sizeof s - 1, "ui %d", rs_uitime / divisor);
2020-04-18 15:25:28 -07:00
V_DrawThinString(30, 80, V_MONOSPACE | V_YELLOWMAP, s);
2020-08-23 10:09:55 -07:00
snprintf(s, sizeof s - 1, "fin %d", rs_swaptime / divisor);
V_DrawThinString(30, 90, V_MONOSPACE | V_YELLOWMAP, s);
snprintf(s, sizeof s - 1, "tic %d", rs_tictime / divisor);
V_DrawThinString(30, 105, V_MONOSPACE | V_GRAYMAP, s);
2020-07-05 21:15:08 -07:00
if (cv_glbatching.value)
2020-06-07 11:20:52 -07:00
{
snprintf(s, sizeof s - 1, "bsrt %d", rs_hw_batchsorttime / divisor);
V_DrawThinString(80, 55, V_MONOSPACE | V_REDMAP, s);
snprintf(s, sizeof s - 1, "bdrw %d", rs_hw_batchdrawtime / divisor);
V_DrawThinString(80, 65, V_MONOSPACE | V_REDMAP, s);
snprintf(s, sizeof s - 1, "npol %d", rs_hw_numpolys);
V_DrawThinString(130, 10, V_MONOSPACE | V_PURPLEMAP, s);
snprintf(s, sizeof s - 1, "ndc %d", rs_hw_numcalls);
V_DrawThinString(130, 20, V_MONOSPACE | V_PURPLEMAP, s);
snprintf(s, sizeof s - 1, "nshd %d", rs_hw_numshaders);
V_DrawThinString(130, 30, V_MONOSPACE | V_PURPLEMAP, s);
snprintf(s, sizeof s - 1, "nvrt %d", rs_hw_numverts);
V_DrawThinString(130, 40, V_MONOSPACE | V_PURPLEMAP, s);
snprintf(s, sizeof s - 1, "ntex %d", rs_hw_numtextures);
V_DrawThinString(185, 10, V_MONOSPACE | V_PURPLEMAP, s);
snprintf(s, sizeof s - 1, "npf %d", rs_hw_numpolyflags);
V_DrawThinString(185, 20, V_MONOSPACE | V_PURPLEMAP, s);
snprintf(s, sizeof s - 1, "ncol %d", rs_hw_numcolors);
V_DrawThinString(185, 30, V_MONOSPACE | V_PURPLEMAP, s);
}
2020-08-31 16:09:41 -07:00
#endif
2020-04-18 15:25:28 -07:00
}
else // software specific stats
2020-04-18 15:25:28 -07:00
{
snprintf(s, sizeof s - 1, "prtl %d", rs_sw_portaltime / divisor);
V_DrawThinString(30, 40, V_MONOSPACE | V_YELLOWMAP, s);
snprintf(s, sizeof s - 1, "plns %d", rs_sw_planetime / divisor);
V_DrawThinString(30, 50, V_MONOSPACE | V_YELLOWMAP, s);
snprintf(s, sizeof s - 1, "mskd %d", rs_sw_maskedtime / divisor);
V_DrawThinString(30, 60, V_MONOSPACE | V_YELLOWMAP, s);
2020-08-23 10:09:55 -07:00
snprintf(s, sizeof s - 1, "ui %d", rs_uitime / divisor);
V_DrawThinString(30, 70, V_MONOSPACE | V_YELLOWMAP, s);
2020-08-23 10:09:55 -07:00
snprintf(s, sizeof s - 1, "fin %d", rs_swaptime / divisor);
V_DrawThinString(30, 80, V_MONOSPACE | V_YELLOWMAP, s);
snprintf(s, sizeof s - 1, "tic %d", rs_tictime / divisor);
V_DrawThinString(30, 95, V_MONOSPACE | V_GRAYMAP, s);
}
2020-04-18 15:25:28 -07:00
}
2014-03-15 09:59:03 -07:00
rs_swaptime = I_GetTimeMicros();
2014-03-15 09:59:03 -07:00
I_FinishUpdate(); // page flip or blit buffer
rs_swaptime = I_GetTimeMicros() - rs_swaptime;
2014-03-15 09:59:03 -07:00
}
2019-09-08 14:27:35 -07:00
needpatchflush = false;
needpatchrecache = false;
2014-03-15 09:59:03 -07:00
}
2020-02-08 13:19:27 -08:00
// Check the renderer's state
2019-09-09 15:27:08 -07:00
// after a possible renderer switch.
2019-09-09 12:20:17 -07:00
void D_CheckRendererState(void)
{
// flush all patches from memory
if (needpatchflush)
2020-02-08 13:19:27 -08:00
{
2019-09-09 12:20:17 -07:00
Z_FlushCachedPatches();
2020-02-08 13:19:27 -08:00
needpatchflush = false;
}
2019-09-09 12:20:17 -07:00
// some patches have been freed,
// so cache them again
if (needpatchrecache)
R_ReloadHUDGraphics();
2014-03-15 09:59:03 -07:00
}
// =========================================================================
// D_SRB2Loop
// =========================================================================
tic_t rendergametic;
void D_SRB2Loop(void)
{
tic_t oldentertics = 0, entertic = 0, realtics = 0, rendertimeout = INFTICS;
static lumpnum_t gstartuplumpnum;
2014-03-15 09:59:03 -07:00
if (dedicated)
server = true;
// Pushing of + parameters is now done back in D_SRB2Main, not here.
#ifdef _WINDOWS
CONS_Printf("I_StartupMouse()...\n");
I_DoStartupMouse();
#endif
oldentertics = I_GetTime();
// end of loading screen: CONS_Printf() will no more call FinishUpdate()
con_startup = false;
// make sure to do a d_display to init mode _before_ load a level
SCR_SetMode(); // change video mode
SCR_Recalc();
// Check and print which version is executed.
// Use this as the border between setup and the main game loop being entered.
CONS_Printf(
"===========================================================================\n"
" We hope you enjoy this game as\n"
" much as we did making it!\n"
" ...wait. =P\n"
"===========================================================================\n");
// hack to start on a nice clear console screen.
COM_ImmedExecute("cls;version");
I_FinishUpdate(); // page flip or blit buffer
/*
LMFAO this was showing garbage under OpenGL
because I_FinishUpdate was called afterward
*/
2020-01-21 03:28:33 -08:00
/* Smells like a hack... Don't fade Sonic's ass into the title screen. */
if (gamestate != GS_TITLESCREEN)
{
gstartuplumpnum = W_CheckNumForName("STARTUP");
if (gstartuplumpnum == LUMPERROR)
gstartuplumpnum = W_GetNumForName("MISSING");
V_DrawScaledPatch(0, 0, 0, W_CachePatchNum(gstartuplumpnum, PU_PATCH));
}
2014-03-15 09:59:03 -07:00
for (;;)
{
if (lastwipetic)
{
oldentertics = lastwipetic;
lastwipetic = 0;
}
// get real tics
entertic = I_GetTime();
realtics = entertic - oldentertics;
oldentertics = entertic;
refreshdirmenu = 0; // not sure where to put this, here as good as any?
2014-03-15 09:59:03 -07:00
#ifdef DEBUGFILE
if (!realtics)
if (debugload)
debugload--;
#endif
if (!realtics && !singletics)
{
I_Sleep();
continue;
}
#ifdef HW3SOUND
HW3S_BeginFrameUpdate();
#endif
// don't skip more than 10 frames at a time
// (fadein / fadeout cause massive frame skip!)
if (realtics > 8)
realtics = 1;
// process tics (but maybe not if realtic == 0)
TryRunTics(realtics);
if (lastdraw || singletics || gametic > rendergametic)
{
rendergametic = gametic;
rendertimeout = entertic+TICRATE/17;
// Update display, next frame, with current state.
D_Display();
if (moviemode)
M_SaveFrame();
if (takescreenshot) // Only take screenshots after drawing.
M_DoScreenShot();
}
else if (rendertimeout < entertic) // in case the server hang or netsplit
{
// Lagless camera! Yay!
if (gamestate == GS_LEVEL && netgame)
{
if (splitscreen && camera2.chase)
P_MoveChaseCamera(&players[secondarydisplayplayer], &camera2, false);
if (camera.chase)
P_MoveChaseCamera(&players[displayplayer], &camera, false);
}
D_Display();
if (moviemode)
M_SaveFrame();
if (takescreenshot) // Only take screenshots after drawing.
M_DoScreenShot();
}
// consoleplayer -> displayplayer (hear sounds from viewpoint)
S_UpdateSounds(); // move positional sounds
2019-11-25 15:16:17 -08:00
S_UpdateClosedCaptions();
2014-03-15 09:59:03 -07:00
#ifdef HW3SOUND
HW3S_EndFrameUpdate();
#endif
LUA_Step();
2014-03-15 09:59:03 -07:00
}
}
//
// D_AdvanceDemo
// Called after each demo or intro demosequence finishes
//
void D_AdvanceDemo(void)
{
advancedemo = true;
}
// =========================================================================
// D_SRB2Main
// =========================================================================
//
// D_StartTitle
//
void D_StartTitle(void)
{
2015-01-01 11:50:31 -08:00
INT32 i;
S_StopMusic();
2014-03-15 09:59:03 -07:00
if (netgame)
{
if (gametyperules & GTR_CAMPAIGN)
2014-03-15 09:59:03 -07:00
{
G_SetGamestate(GS_WAITINGPLAYERS); // hack to prevent a command repeat
if (server)
{
char mapname[6];
strlcpy(mapname, G_BuildMapName(spstage_start), sizeof (mapname));
strlwr(mapname);
mapname[5] = '\0';
COM_BufAddText(va("map %s\n", mapname));
}
}
return;
}
// okay, stop now
// (otherwise the game still thinks we're playing!)
SV_StopServer();
2014-08-03 20:49:33 -07:00
SV_ResetServer();
2014-03-15 09:59:03 -07:00
2015-01-01 11:50:31 -08:00
for (i = 0; i < MAXPLAYERS; i++)
CL_ClearPlayer(i);
players[consoleplayer].availabilities = players[1].availabilities = R_GetSkinAvailabilities(); // players[1] is supposed to be for 2p
2015-01-01 11:50:31 -08:00
splitscreen = false;
SplitScreen_OnChange();
botingame = false;
botskin = 0;
cv_debug = 0;
emeralds = 0;
memset(&luabanks, 0, sizeof(luabanks));
lastmaploaded = 0;
2015-01-01 11:50:31 -08:00
2014-03-15 09:59:03 -07:00
// In case someone exits out at the same time they start a time attack run,
// reset modeattacking
modeattacking = ATTACKING_NONE;
Introducing Marathon Run. (I was going to call it Marathon Mode, but NiGHTS Mode being right next to it on the menu looked terrible.) Basically a dedicated Record Attack-like experience for speedrunning the game as a continuous chunk rather than ILs. Has several quality of life features. Benefits include: * An unambiguous real-time bar across the bottom of the screen, always displaying the current time, ticking up until you reach the ending. * Disable the console (pausing is still allowed, but the timer will still increment). * Automatically skip intermissions as if you're holding down the spin button. * Show centiseconds on HUD automatically, like record attack. * "Live Event Backups" - a category of run fit for major events like GDQ, where recovery from crashes or chokes makes for better entertainment. Essentially a modified SP savefile, down to using the same basic functions, but has its own filename and tweaked internal layout. * "spmarathon_start" MainCfg block parameter and "marathonnext" mapheader parameter, allowing for a customised flow (makes this fit for purpose for an eventual SUGOI port). * Disabling inter-level custom cutscenes by default with a menu option to toggle this (won't show up if the mod doesn't *have* any custom cutscenes), although either way ending cutscenes (vanilla or custom) remain intact since is time is called before them. * Won't show up if you have a mod that consists of only one level (determined by spmarathon_start's nextlevel; this won't trip if you manually set its marathonnext). * Unconditional gratitude on the evaluation screen, instead of a negging "Try again..." if you didn't get all the emeralds (which you may not have been aiming for). * Gorgeous new menu (no new assets required, unless you wanna give it a header later). Changes which were required for the above but affect other areas of the game include: * "useBlackRock" MainCFG block parameter, which can be used to disable the presence of the Black Rock or Egg Rock in both the Evaluation screen and the Marathon Run menu (for total conversions with different stories). * Disabling Continues in NiGHTS mode, to match the most common singleplayer experience post 2.2.4's release (is reverted if useContinues is set to true). * Hiding the exitmove "powerup" outside of multiplayer. (Okay, this isn't really related, I just saw this bug in action a lot while doing test runs and got annoyed enough to fix it here.) * The ability to use V_DrawPromptBack (in hardcode only at the moment, but) to draw in terms of pixels rather than rows of text, by providing negative instead of positive inputs). * A refactoring of redundant game saves smattered across the ending, credits, and evaluation - in addition to saving the game slightly earlier. * Minor m_menu.c touchups and refactorings here and there. Built using feedback from the official server's #speedruns channel, among other places.
2020-05-14 15:10:00 -07:00
marathonmode = 0;
2014-03-15 09:59:03 -07:00
// empty maptol so mario/etc sounds don't play in sound test when they shouldn't
maptol = 0;
gameaction = ga_nothing;
displayplayer = consoleplayer = 0;
G_SetGametype(GT_COOP);
2014-03-15 09:59:03 -07:00
paused = false;
advancedemo = false;
F_InitMenuPresValues();
2014-03-15 09:59:03 -07:00
F_StartTitleScreen();
2018-11-16 15:26:26 -08:00
currentMenu = &MainDef; // reset the current menu ID
2014-03-15 09:59:03 -07:00
// Reset the palette
if (rendermode != render_none)
V_SetPaletteLump("PLAYPAL");
// The title screen is obviously not a tutorial! (Unless I'm mistaken)
if (tutorialmode && tutorialgcs)
{
2018-11-12 20:25:49 -08:00
G_CopyControls(gamecontrol, gamecontroldefault[gcs_custom], gcl_tutorial_full, num_gcl_tutorial_full); // using gcs_custom as temp storage
CV_SetValue(&cv_usemouse, tutorialusemouse);
CV_SetValue(&cv_alwaysfreelook, tutorialfreelook);
CV_SetValue(&cv_mousemove, tutorialmousemove);
CV_SetValue(&cv_analog[0], tutorialanalog);
M_StartMessage("Do you want to \x82save the recommended \x82movement controls?\x80\n\nPress 'Y' or 'Enter' to confirm\nPress 'N' or any key to keep \nyour current controls",
M_TutorialSaveControlResponse, MM_YESNO);
}
tutorialmode = false;
2014-03-15 09:59:03 -07:00
}
//
// D_AddFile
//
static void D_AddFile(char **list, const char *file)
2014-03-15 09:59:03 -07:00
{
size_t pnumwadfiles;
char *newfile;
for (pnumwadfiles = 0; list[pnumwadfiles]; pnumwadfiles++)
2014-03-15 09:59:03 -07:00
;
newfile = malloc(strlen(file) + 1);
if (!newfile)
{
I_Error("No more free memory to AddFile %s",file);
}
strcpy(newfile, file);
list[pnumwadfiles] = newfile;
2014-03-15 09:59:03 -07:00
}
static inline void D_CleanFile(char **list)
2014-03-15 09:59:03 -07:00
{
size_t pnumwadfiles;
for (pnumwadfiles = 0; list[pnumwadfiles]; pnumwadfiles++)
2014-03-15 09:59:03 -07:00
{
free(list[pnumwadfiles]);
list[pnumwadfiles] = NULL;
2014-03-15 09:59:03 -07:00
}
}
///\brief Checks if a netgame URL is being handled, and changes working directory to the EXE's if so.
/// Done because browsers (at least, Firefox on Windows) launch the game from the browser's directory, which causes problems.
static void ChangeDirForUrlHandler(void)
2014-03-15 09:59:03 -07:00
{
// URL handlers are opened by web browsers (at least Firefox) from the browser's working directory, not the game's stored directory,
// so chdir to that directory unless overridden.
if (M_GetUrlProtocolArg() != NULL && !M_CheckParm("-nochdir"))
{
size_t i;
CONS_Printf("%s connect links load game files from the SRB2 application's stored directory. Switching to ", SERVER_URL_PROTOCOL);
strlcpy(srb2path, myargv[0], sizeof(srb2path));
// Get just the directory, minus the EXE name
for (i = strlen(srb2path)-1; i > 0; i--)
{
if (srb2path[i] == '/' || srb2path[i] == '\\')
{
srb2path[i] = '\0';
break;
}
}
CONS_Printf("%s\n", srb2path);
#if defined (_WIN32)
SetCurrentDirectoryA(srb2path);
#else
if (chdir(srb2path) == -1)
I_OutputMsg("Couldn't change working directory\n");
#endif
}
}
2014-03-15 09:59:03 -07:00
// ==========================================================================
// Identify the SRB2 version, and IWAD file to use.
// ==========================================================================
static void IdentifyVersion(void)
{
char *srb2wad;
2014-03-15 09:59:03 -07:00
const char *srb2waddir = NULL;
2014-07-25 16:10:24 -07:00
#if (defined (__unix__) && !defined (MSDOS)) || defined (UNIXCOMMON) || defined (HAVE_SDL)
// change to the directory where 'srb2.pk3' is found
2014-03-15 09:59:03 -07:00
srb2waddir = I_LocateWad();
#endif
// get the current directory (possible problem on NT with "." as current dir)
if (srb2waddir)
{
strlcpy(srb2path,srb2waddir,sizeof (srb2path));
}
else
{
if (getcwd(srb2path, 256) != NULL)
srb2waddir = srb2path;
else
{
srb2waddir = ".";
}
}
2014-07-25 16:10:24 -07:00
#if defined (macintosh) && !defined (HAVE_SDL)
2014-03-15 09:59:03 -07:00
// cwd is always "/" when app is dbl-clicked
if (!stricmp(srb2waddir, "/"))
srb2waddir = I_GetWadDir();
#endif
// Commercial.
srb2wad = malloc(strlen(srb2waddir)+1+8+1);
if (srb2wad == NULL)
2014-03-15 09:59:03 -07:00
I_Error("No more free memory to look in %s", srb2waddir);
else
sprintf(srb2wad, pandf, srb2waddir, "srb2.pk3");
2014-03-15 09:59:03 -07:00
// will be overwritten in case of -cdrom or unix/win home
snprintf(configfile, sizeof configfile, "%s" PATHSEP CONFIGFILENAME, srb2waddir);
configfile[sizeof configfile - 1] = '\0';
// Load the IWAD
if (srb2wad != NULL && FIL_ReadFileOK(srb2wad))
D_AddFile(startupwadfiles, srb2wad);
2014-03-15 09:59:03 -07:00
else
I_Error("srb2.pk3 not found! Expected in %s, ss file: %s\n", srb2waddir, srb2wad);
2014-03-15 09:59:03 -07:00
if (srb2wad)
free(srb2wad);
2014-03-15 09:59:03 -07:00
// if you change the ordering of this or add/remove a file, be sure to update the md5
// checking in D_SRB2Main
// Add the maps
D_AddFile(startupwadfiles, va(pandf,srb2waddir,"zones.pk3"));
2014-03-15 09:59:03 -07:00
// Add the players
D_AddFile(startupwadfiles, va(pandf,srb2waddir, "player.dta"));
2014-03-15 09:59:03 -07:00
#ifdef USE_PATCH_DTA
2014-03-15 15:55:07 -07:00
// Add our crappy patches to fix our bugs
D_AddFile(startupwadfiles, va(pandf,srb2waddir,"patch.pk3"));
#endif
2014-03-15 15:55:07 -07:00
2014-07-25 16:10:24 -07:00
#if !defined (HAVE_SDL) || defined (HAVE_MIXER)
2014-03-15 09:59:03 -07:00
{
#define MUSICTEST(str) \
{\
const char *musicpath = va(pandf,srb2waddir,str);\
int ms = W_VerifyNMUSlumps(musicpath); \
if (ms == 1) \
D_AddFile(startupwadfiles, musicpath); \
else if (ms == 0) \
I_Error("File "str" has been modified with non-music/sound lumps"); \
}
MUSICTEST("music.dta")
2020-02-22 14:43:41 -08:00
MUSICTEST("patch_music.pk3")
#ifdef DEVELOP // remove when music_new.dta is merged into music.dta
MUSICTEST("music_new.dta")
2014-03-15 09:59:03 -07:00
#endif
}
#endif
}
static void
D_ConvertVersionNumbers (void)
{
/* leave at defaults (0) under DEVELOP */
#ifndef DEVELOP
int major;
int minor;
sscanf(SRB2VERSION, "%d.%d.%d", &major, &minor, &SUBVERSION);
/* this is stupid */
VERSION = ( major * 100 ) + minor;
#endif
}
2014-03-15 09:59:03 -07:00
//
// D_SRB2Main
//
void D_SRB2Main(void)
{
INT32 p;
INT32 pstartmap = 1;
boolean autostart = false;
/* break the version string into version numbers, for netplay */
D_ConvertVersionNumbers();
// Print GPL notice for our console users (Linux)
CONS_Printf(
"\n\nSonic Robo Blast 2\n"
2020-02-19 14:08:45 -08:00
"Copyright (C) 1998-2020 by Sonic Team Junior\n\n"
2018-11-25 05:17:57 -08:00
"This program comes with ABSOLUTELY NO WARRANTY.\n\n"
"This is free software, and you are welcome to redistribute it\n"
"and/or modify it under the terms of the GNU General Public License\n"
"as published by the Free Software Foundation; either version 2 of\n"
"the License, or (at your option) any later version.\n"
"See the 'LICENSE.txt' file for details.\n\n"
"Sonic the Hedgehog and related characters are trademarks of SEGA.\n"
"We do not claim ownership of SEGA's intellectual property used\n"
"in this program.\n\n");
2014-03-15 09:59:03 -07:00
// keep error messages until the final flush(stderr)
#if !defined(NOTERMIOS)
2014-03-15 09:59:03 -07:00
if (setvbuf(stderr, NULL, _IOFBF, 1000))
I_OutputMsg("setvbuf didnt work\n");
#endif
// initialise locale code
M_StartupLocale();
// get parameters from a response file (eg: srb2 @parms.txt)
M_FindResponseFile();
// MAINCFG is now taken care of where "OBJCTCFG" is handled
G_LoadGameSettings();
// Test Dehacked lists
DEH_Check();
// Netgame URL special case: change working dir to EXE folder.
ChangeDirForUrlHandler();
2014-03-15 09:59:03 -07:00
// identify the main IWAD file to use
IdentifyVersion();
2017-09-29 15:25:34 -07:00
#if !defined(NOTERMIOS)
2014-03-15 09:59:03 -07:00
setbuf(stdout, NULL); // non-buffered output
#endif
2017-09-29 15:25:34 -07:00
#if 0 //defined (_DEBUG)
devparm = M_CheckParm("-nodebug") == 0;
2014-03-15 09:59:03 -07:00
#else
devparm = M_CheckParm("-debug") != 0;
2014-03-15 09:59:03 -07:00
#endif
// for dedicated server
#if !defined (_WINDOWS) //already check in win_main.c
dedicated = M_CheckParm("-dedicated") != 0;
#endif
if (devparm)
CONS_Printf(M_GetText("Development mode ON.\n"));
// default savegame
strcpy(savegamename, SAVEGAMENAME"%u.ssg");
strcpy(liveeventbackup, "live"SAVEGAMENAME".bkp"); // intentionally not ending with .ssg
2014-03-15 09:59:03 -07:00
{
const char *userhome = D_Home(); //Alam: path to home
if (!userhome)
{
2017-09-29 14:27:08 -07:00
#if ((defined (__unix__) && !defined (MSDOS)) || defined(__APPLE__) || defined (UNIXCOMMON)) && !defined (__CYGWIN__)
2014-03-15 09:59:03 -07:00
I_Error("Please set $HOME to your home directory\n");
#else
if (dedicated)
snprintf(configfile, sizeof configfile, "d"CONFIGFILENAME);
else
snprintf(configfile, sizeof configfile, CONFIGFILENAME);
#endif
}
else
{
// use user specific config file
#ifdef DEFAULTDIR
snprintf(srb2home, sizeof srb2home, "%s" PATHSEP DEFAULTDIR, userhome);
snprintf(downloaddir, sizeof downloaddir, "%s" PATHSEP "DOWNLOAD", srb2home);
if (dedicated)
snprintf(configfile, sizeof configfile, "%s" PATHSEP "d"CONFIGFILENAME, srb2home);
else
snprintf(configfile, sizeof configfile, "%s" PATHSEP CONFIGFILENAME, srb2home);
// can't use sprintf since there is %u in savegamename
strcatbf(savegamename, srb2home, PATHSEP);
Introducing Marathon Run. (I was going to call it Marathon Mode, but NiGHTS Mode being right next to it on the menu looked terrible.) Basically a dedicated Record Attack-like experience for speedrunning the game as a continuous chunk rather than ILs. Has several quality of life features. Benefits include: * An unambiguous real-time bar across the bottom of the screen, always displaying the current time, ticking up until you reach the ending. * Disable the console (pausing is still allowed, but the timer will still increment). * Automatically skip intermissions as if you're holding down the spin button. * Show centiseconds on HUD automatically, like record attack. * "Live Event Backups" - a category of run fit for major events like GDQ, where recovery from crashes or chokes makes for better entertainment. Essentially a modified SP savefile, down to using the same basic functions, but has its own filename and tweaked internal layout. * "spmarathon_start" MainCfg block parameter and "marathonnext" mapheader parameter, allowing for a customised flow (makes this fit for purpose for an eventual SUGOI port). * Disabling inter-level custom cutscenes by default with a menu option to toggle this (won't show up if the mod doesn't *have* any custom cutscenes), although either way ending cutscenes (vanilla or custom) remain intact since is time is called before them. * Won't show up if you have a mod that consists of only one level (determined by spmarathon_start's nextlevel; this won't trip if you manually set its marathonnext). * Unconditional gratitude on the evaluation screen, instead of a negging "Try again..." if you didn't get all the emeralds (which you may not have been aiming for). * Gorgeous new menu (no new assets required, unless you wanna give it a header later). Changes which were required for the above but affect other areas of the game include: * "useBlackRock" MainCFG block parameter, which can be used to disable the presence of the Black Rock or Egg Rock in both the Evaluation screen and the Marathon Run menu (for total conversions with different stories). * Disabling Continues in NiGHTS mode, to match the most common singleplayer experience post 2.2.4's release (is reverted if useContinues is set to true). * Hiding the exitmove "powerup" outside of multiplayer. (Okay, this isn't really related, I just saw this bug in action a lot while doing test runs and got annoyed enough to fix it here.) * The ability to use V_DrawPromptBack (in hardcode only at the moment, but) to draw in terms of pixels rather than rows of text, by providing negative instead of positive inputs). * A refactoring of redundant game saves smattered across the ending, credits, and evaluation - in addition to saving the game slightly earlier. * Minor m_menu.c touchups and refactorings here and there. Built using feedback from the official server's #speedruns channel, among other places.
2020-05-14 15:10:00 -07:00
strcatbf(liveeventbackup, srb2home, PATHSEP);
2014-03-15 09:59:03 -07:00
2020-01-22 14:08:57 -08:00
snprintf(luafiledir, sizeof luafiledir, "%s" PATHSEP "luafiles", srb2home);
2020-02-21 08:17:39 -08:00
#else // DEFAULTDIR
2014-03-15 09:59:03 -07:00
snprintf(srb2home, sizeof srb2home, "%s", userhome);
snprintf(downloaddir, sizeof downloaddir, "%s", userhome);
if (dedicated)
snprintf(configfile, sizeof configfile, "%s" PATHSEP "d"CONFIGFILENAME, userhome);
else
snprintf(configfile, sizeof configfile, "%s" PATHSEP CONFIGFILENAME, userhome);
// can't use sprintf since there is %u in savegamename
strcatbf(savegamename, userhome, PATHSEP);
Introducing Marathon Run. (I was going to call it Marathon Mode, but NiGHTS Mode being right next to it on the menu looked terrible.) Basically a dedicated Record Attack-like experience for speedrunning the game as a continuous chunk rather than ILs. Has several quality of life features. Benefits include: * An unambiguous real-time bar across the bottom of the screen, always displaying the current time, ticking up until you reach the ending. * Disable the console (pausing is still allowed, but the timer will still increment). * Automatically skip intermissions as if you're holding down the spin button. * Show centiseconds on HUD automatically, like record attack. * "Live Event Backups" - a category of run fit for major events like GDQ, where recovery from crashes or chokes makes for better entertainment. Essentially a modified SP savefile, down to using the same basic functions, but has its own filename and tweaked internal layout. * "spmarathon_start" MainCfg block parameter and "marathonnext" mapheader parameter, allowing for a customised flow (makes this fit for purpose for an eventual SUGOI port). * Disabling inter-level custom cutscenes by default with a menu option to toggle this (won't show up if the mod doesn't *have* any custom cutscenes), although either way ending cutscenes (vanilla or custom) remain intact since is time is called before them. * Won't show up if you have a mod that consists of only one level (determined by spmarathon_start's nextlevel; this won't trip if you manually set its marathonnext). * Unconditional gratitude on the evaluation screen, instead of a negging "Try again..." if you didn't get all the emeralds (which you may not have been aiming for). * Gorgeous new menu (no new assets required, unless you wanna give it a header later). Changes which were required for the above but affect other areas of the game include: * "useBlackRock" MainCFG block parameter, which can be used to disable the presence of the Black Rock or Egg Rock in both the Evaluation screen and the Marathon Run menu (for total conversions with different stories). * Disabling Continues in NiGHTS mode, to match the most common singleplayer experience post 2.2.4's release (is reverted if useContinues is set to true). * Hiding the exitmove "powerup" outside of multiplayer. (Okay, this isn't really related, I just saw this bug in action a lot while doing test runs and got annoyed enough to fix it here.) * The ability to use V_DrawPromptBack (in hardcode only at the moment, but) to draw in terms of pixels rather than rows of text, by providing negative instead of positive inputs). * A refactoring of redundant game saves smattered across the ending, credits, and evaluation - in addition to saving the game slightly earlier. * Minor m_menu.c touchups and refactorings here and there. Built using feedback from the official server's #speedruns channel, among other places.
2020-05-14 15:10:00 -07:00
strcatbf(liveeventbackup, userhome, PATHSEP);
2020-01-22 14:08:57 -08:00
snprintf(luafiledir, sizeof luafiledir, "%s" PATHSEP "luafiles", userhome);
2020-02-21 08:17:39 -08:00
#endif // DEFAULTDIR
2014-03-15 09:59:03 -07:00
}
configfile[sizeof configfile - 1] = '\0';
}
2019-09-07 22:29:09 -07:00
// Create addons dir
snprintf(addonsdir, sizeof addonsdir, "%s%s%s", srb2home, PATHSEP, "addons");
I_mkdir(addonsdir, 0755);
2014-03-15 09:59:03 -07:00
// rand() needs seeded regardless of password
srand((unsigned int)time(NULL));
rand();
rand();
rand();
2014-03-15 09:59:03 -07:00
if (M_CheckParm("-password") && M_IsNextParm())
D_SetPassword(M_GetNextParm());
// player setup menu colors must be initialized before
// any wad file is added, as they may contain colors themselves
M_InitPlayerSetupColors();
2014-03-15 09:59:03 -07:00
CONS_Printf("Z_Init(): Init zone memory allocation daemon. \n");
Z_Init();
// Do this up here so that WADs loaded through the command line can use ExecCfg
COM_Init();
2014-03-15 09:59:03 -07:00
// add any files specified on the command line with -file wadfile
// to the wad list
if (!((M_GetUrlProtocolArg() || M_CheckParm("-connect")) && !M_CheckParm("-server")))
2014-03-15 09:59:03 -07:00
{
if (M_CheckParm("-file"))
{
// the parms after p are wadfile/lump names,
// until end of parms or another - preceded parm
while (M_IsNextParm())
{
const char *s = M_GetNextParm();
if (s) // Check for NULL?
{
if (!W_VerifyNMUSlumps(s))
G_SetGameModified(true);
D_AddFile(startuppwads, s);
2014-03-15 09:59:03 -07:00
}
}
}
}
// get map from parms
if (M_CheckParm("-server") || dedicated)
netgame = server = true;
// adapt tables to SRB2's needs, including extra slots for dehacked file support
P_PatchInfoTables();
// initiate menu metadata before SOCcing them
M_InitMenuPresTables();
// init title screen display params
if (M_GetUrlProtocolArg() || M_CheckParm("-connect"))
F_InitMenuPresValues();
2014-03-15 09:59:03 -07:00
//---------------------------------------------------- READY TIME
// we need to check for dedicated before initialization of some subsystems
CONS_Printf("I_StartupTimer()...\n");
I_StartupTimer();
// Make backups of some SOCcable tables.
P_BackupTables();
// Setup character tables
// Have to be done here before files are loaded
M_InitCharacterTables();
2014-03-15 09:59:03 -07:00
mainwads = 3; // doesn't include music.dta
#ifdef USE_PATCH_DTA
mainwads++;
#endif
2018-12-22 21:29:59 -08:00
// load wad, including the main wad file
CONS_Printf("W_InitMultipleFiles(): Adding IWAD and main PWADs.\n");
W_InitMultipleFiles(startupwadfiles);
D_CleanFile(startupwadfiles);
2020-02-22 13:42:24 -08:00
#ifndef DEVELOP // md5s last updated 22/02/20 (ddmmyy)
// Check MD5s of autoloaded files
W_VerifyFileMD5(0, ASSET_HASH_SRB2_PK3); // srb2.pk3
W_VerifyFileMD5(1, ASSET_HASH_ZONES_PK3); // zones.pk3
W_VerifyFileMD5(2, ASSET_HASH_PLAYER_DTA); // player.dta
#ifdef USE_PATCH_DTA
2020-02-22 13:42:24 -08:00
W_VerifyFileMD5(3, ASSET_HASH_PATCH_PK3); // patch.pk3
#endif
// don't check music.dta because people like to modify it, and it doesn't matter if they do
// ...except it does if they slip maps in there, and that's what W_VerifyNMUSlumps is for.
#endif //ifndef DEVELOP
mainwadstally = packetsizetally; // technically not accurate atm, remember to port the two-stage -file process from kart in 2.2.x
2014-03-15 09:59:03 -07:00
cht_Init();
//---------------------------------------------------- READY SCREEN
// we need to check for dedicated before initialization of some subsystems
CONS_Printf("I_StartupGraphics()...\n");
I_StartupGraphics();
#ifdef HWRENDER
// Lactozilla: Add every hardware mode CVAR and CCMD.
// Has to be done before the configuration file loads,
// but after the OpenGL library loads.
HWR_AddCommands();
#endif
2014-03-15 09:59:03 -07:00
//--------------------------------------------------------- CONSOLE
// setup loading screen
SCR_Startup();
HU_Init();
CON_Init();
D_RegisterServerCommands();
D_RegisterClientCommands(); // be sure that this is called before D_CheckNetGame
R_RegisterEngineStuff();
S_RegisterSoundStuff();
I_RegisterSysCommands();
CONS_Printf("W_InitMultipleFiles(): Adding extra PWADs.\n");
W_InitMultipleFiles(startuppwads);
D_CleanFile(startuppwads);
CONS_Printf("HU_LoadGraphics()...\n");
HU_LoadGraphics();
2014-03-15 09:59:03 -07:00
//--------------------------------------------------------- CONFIG.CFG
M_FirstLoadConfig(); // WARNING : this do a "COM_BufExecute()"
G_LoadGameData();
2014-07-25 16:10:24 -07:00
#if (defined (__unix__) && !defined (MSDOS)) || defined (UNIXCOMMON) || defined (HAVE_SDL)
2014-03-15 09:59:03 -07:00
VID_PrepareModeList(); // Regenerate Modelist according to cv_fullscreen
#endif
// set user default mode or mode set at cmdline
SCR_CheckDefaultMode();
2019-12-17 11:14:26 -08:00
// Lactozilla: Does the render mode need to change?
2019-09-09 12:20:17 -07:00
if ((setrenderneeded != 0) && (setrenderneeded != rendermode))
{
2020-02-17 07:14:51 -08:00
CONS_Printf(M_GetText("Switching the renderer...\n"));
Z_PreparePatchFlush();
// set needpatchflush / needpatchrecache true for D_CheckRendererState
2019-09-09 12:20:17 -07:00
needpatchflush = true;
needpatchrecache = true;
2020-02-17 07:14:51 -08:00
// Set cv_renderer to the new render mode
2019-09-09 12:20:17 -07:00
VID_CheckRenderer();
SCR_ChangeRendererCVars(rendermode);
2020-02-17 07:14:51 -08:00
// check the renderer's state
D_CheckRendererState();
2019-09-09 12:20:17 -07:00
}
2014-03-15 09:59:03 -07:00
wipegamestate = gamestate;
savedata.lives = 0; // flag this as not-used
//------------------------------------------------ COMMAND LINE PARAMS
// this must be done after loading gamedata,
// to avoid setting off the corrupted gamedata code in G_LoadGameData if a SOC with custom gamedata is added
// -- Monster Iestyn 20/02/20
if (M_CheckParm("-warp") && M_IsNextParm())
{
const char *word = M_GetNextParm();
pstartmap = G_FindMapByNameOrCode(word, 0);
if (! pstartmap)
I_Error("Cannot find a map remotely named '%s'\n", word);
else
{
if (!M_CheckParm("-server"))
G_SetGameModified(true);
autostart = true;
}
}
2014-03-15 09:59:03 -07:00
if (M_CheckParm("-noupload"))
COM_BufAddText("downloading 0\n");
CONS_Printf("M_Init(): Init miscellaneous info.\n");
M_Init();
CONS_Printf("R_Init(): Init SRB2 refresh daemon.\n");
R_Init();
// setting up sound
if (dedicated)
{
sound_disabled = true;
midi_disabled = digital_disabled = true;
}
if (M_CheckParm("-noaudio")) // combines -nosound and -nomusic
2018-04-03 13:11:07 -07:00
{
sound_disabled = true;
digital_disabled = true;
midi_disabled = true;
}
2014-03-15 09:59:03 -07:00
else
{
if (M_CheckParm("-nosound"))
sound_disabled = true;
if (M_CheckParm("-nomusic")) // combines -nomidimusic and -nodigmusic
{
digital_disabled = true;
midi_disabled = true;
}
else
{
if (M_CheckParm("-nomidimusic"))
midi_disabled = true; // WARNING: DOS version initmusic in I_StartupSound
if (M_CheckParm("-nodigmusic"))
digital_disabled = true; // WARNING: DOS version initmusic in I_StartupSound
}
2014-03-15 09:59:03 -07:00
}
if (!( sound_disabled && digital_disabled
#ifndef NO_MIDI
&& midi_disabled
#endif
))
{
CONS_Printf("S_InitSfxChannels(): Setting up sound channels.\n");
I_StartupSound();
I_InitMusic();
S_InitSfxChannels(cv_soundvolume.value);
2014-03-15 09:59:03 -07:00
}
S_InitMusicDefs();
2014-03-15 09:59:03 -07:00
CONS_Printf("ST_Init(): Init status bar.\n");
ST_Init();
if (M_CheckParm("-room"))
{
if (!M_IsNextParm())
I_Error("usage: -room <room_id>\nCheck the Master Server's webpage for room ID numbers.\n");
2014-03-18 10:56:54 -07:00
ms_RoomId = atoi(M_GetNextParm());
2014-03-15 09:59:03 -07:00
#ifdef UPDATE_ALERT
GetMODVersion_Console();
#endif
}
// init all NETWORK
CONS_Printf("D_CheckNetGame(): Checking network game status.\n");
if (D_CheckNetGame())
autostart = true;
// check for a driver that wants intermission stats
// start the apropriate game based on parms
if (M_CheckParm("-metal"))
{
G_RecordMetal();
autostart = true;
}
else if (M_CheckParm("-record") && M_IsNextParm())
{
G_RecordDemo(M_GetNextParm());
autostart = true;
}
// user settings come before "+" parameters.
if (dedicated)
COM_ImmedExecute(va("exec \"%s"PATHSEP"adedserv.cfg\"\n", srb2home));
else
COM_ImmedExecute(va("exec \"%s"PATHSEP"autoexec.cfg\" -noerror\n", srb2home));
if (!autostart)
M_PushSpecialParameters(); // push all "+" parameters at the command buffer
// demo doesn't need anymore to be added with D_AddFile()
p = M_CheckParm("-playdemo");
if (!p)
p = M_CheckParm("-timedemo");
if (p && M_IsNextParm())
{
char tmp[MAX_WADPATH];
// add .lmp to identify the EXTERNAL demo file
// it is NOT possible to play an internal demo using -playdemo,
// rather push a playdemo command.. to do.
strcpy(tmp, M_GetNextParm());
// get spaced filename or directory
while (M_IsNextParm())
{
strcat(tmp, " ");
strcat(tmp, M_GetNextParm());
}
FIL_DefaultExtension(tmp, ".lmp");
CONS_Printf(M_GetText("Playing demo %s.\n"), tmp);
if (M_CheckParm("-playdemo"))
{
singledemo = true; // quit after one demo
G_DeferedPlayDemo(tmp);
}
else
G_TimeDemo(tmp);
G_SetGamestate(GS_NULL);
wipegamestate = GS_NULL;
return;
}
if (M_CheckParm("-ultimatemode"))
{
autostart = true;
ultimatemode = true;
}
2017-05-12 17:47:42 -07:00
// rei/miru: bootmap (Idea: starts the game on a predefined map)
if (bootmap && !(M_CheckParm("-warp") && M_IsNextParm()))
{
pstartmap = bootmap;
if (pstartmap < 1 || pstartmap > NUMMAPS)
I_Error("Cannot warp to map %d (out of range)\n", pstartmap);
else
{
autostart = true;
}
}
2018-03-23 18:06:32 -07:00
if (autostart || netgame)
2014-03-15 09:59:03 -07:00
{
gameaction = ga_nothing;
CV_ClearChangedFlags();
// Do this here so if you run SRB2 with eg +timelimit 5, the time limit counts
// as having been modified for the first game.
M_PushSpecialParameters(); // push all "+" parameter at the command buffer
if (M_CheckParm("-gametype") && M_IsNextParm())
{
// from Command_Map_f
INT32 j;
INT16 newgametype = -1;
const char *sgametype = M_GetNextParm();
newgametype = G_GetGametypeByName(sgametype);
if (newgametype == -1) // reached end of the list with no match
2014-03-15 09:59:03 -07:00
{
j = atoi(sgametype); // assume they gave us a gametype number, which is okay too
2019-12-18 07:30:01 -08:00
if (j >= 0 && j < gametypecount)
2014-03-15 09:59:03 -07:00
newgametype = (INT16)j;
}
if (newgametype != -1)
{
j = gametype;
G_SetGametype(newgametype);
2014-03-15 09:59:03 -07:00
D_GameTypeChanged(j);
}
}
2018-03-23 18:06:32 -07:00
if (server && !M_CheckParm("+map"))
2014-03-15 09:59:03 -07:00
{
// Prevent warping to nonexistent levels
if (W_CheckNumForName(G_BuildMapName(pstartmap)) == LUMPERROR)
I_Error("Could not warp to %s (map not found)\n", G_BuildMapName(pstartmap));
// Prevent warping to locked levels
// ... unless you're in a dedicated server. Yes, technically this means you can view any level by
// running a dedicated server and joining it yourself, but that's better than making dedicated server's
// lives hell.
else if (!dedicated && M_MapLocked(pstartmap))
I_Error("You need to unlock this level before you can warp to it!\n");
else
{
2014-03-15 09:59:03 -07:00
D_MapChange(pstartmap, gametype, ultimatemode, true, 0, false, false);
}
2014-03-15 09:59:03 -07:00
}
}
else if (M_CheckParm("-skipintro"))
{
F_InitMenuPresValues();
2014-03-15 09:59:03 -07:00
F_StartTitleScreen();
}
else
F_StartIntro(); // Tails 03-03-2002
2019-03-26 13:04:11 -07:00
CON_ToggleOff();
2014-03-15 09:59:03 -07:00
if (dedicated && server)
{
levelstarttic = gametic;
G_SetGamestate(GS_LEVEL);
if (!P_LoadLevel(false))
2014-03-15 09:59:03 -07:00
I_Quit(); // fail so reset game stuff
}
}
const char *D_Home(void)
{
const char *userhome = NULL;
#ifdef ANDROID
return "/data/data/org.srb2/";
#endif
if (M_CheckParm("-home") && M_IsNextParm())
userhome = M_GetNextParm();
else
{
2017-09-29 15:25:34 -07:00
#if !((defined (__unix__) && !defined (MSDOS)) || defined(__APPLE__) || defined (UNIXCOMMON)) && !defined (__APPLE__)
2014-03-15 09:59:03 -07:00
if (FIL_FileOK(CONFIGFILENAME))
usehome = false; // Let's NOT use home
else
#endif
userhome = I_GetEnv("HOME"); //Alam: my new HOME for srb2
}
2017-09-29 15:25:34 -07:00
#ifdef _WIN32 //Alam: only Win32 have APPDATA and USERPROFILE
2014-03-15 09:59:03 -07:00
if (!userhome && usehome) //Alam: Still not?
{
char *testhome = NULL;
testhome = I_GetEnv("APPDATA");
if (testhome != NULL
&& (FIL_FileOK(va("%s" PATHSEP "%s" PATHSEP CONFIGFILENAME, testhome, DEFAULTDIR))))
{
userhome = testhome;
}
}
#ifndef __CYGWIN__
if (!userhome && usehome) //Alam: All else fails?
{
char *testhome = NULL;
testhome = I_GetEnv("USERPROFILE");
if (testhome != NULL
&& (FIL_FileOK(va("%s" PATHSEP "%s" PATHSEP CONFIGFILENAME, testhome, DEFAULTDIR))))
{
userhome = testhome;
}
}
#endif// !__CYGWIN__
#endif// _WIN32
if (usehome) return userhome;
else return NULL;
}