All this texture load blocking var does is cause issues due to order of operations and what not. Lets just kill it. Thanks Alug for looking at this stuff for me!
2256 lines
54 KiB
C++
2256 lines
54 KiB
C++
// BLANKART
|
|
//-----------------------------------------------------------------------------
|
|
// Copyright (C) 1993-1996 by id Software, Inc.
|
|
// Copyright (C) 1998-2000 by DooM Legacy Team.
|
|
// Copyright (C) 1999-2020 by Sonic Team Junior.
|
|
//
|
|
// 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
|
|
|
|
#ifdef _WIN32
|
|
#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 "d_netfil.h"
|
|
#include "f_finale.h"
|
|
#include "f_dscredits.hpp"
|
|
#include "g_game.h"
|
|
#include "hu_stuff.h"
|
|
#include "m_emotes.h"
|
|
#include "i_sound.h"
|
|
#include "i_system.h"
|
|
#include "i_time.h"
|
|
#include "i_threads.h"
|
|
#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 "m_misc.h" // screenshot functionality
|
|
#include "deh_tables.h" // Dehacked list test
|
|
#include "m_cond.h" // condition initialization
|
|
#include "r_fps.h" // Frame interpolation/uncapped
|
|
#include "keys.h"
|
|
#include "filesrch.h" // refreshdirmenu
|
|
#include "g_input.h" // tutorial mode control scheming
|
|
#include "m_perfstats.h"
|
|
#include "core/memory.h"
|
|
|
|
// SRB2Kart
|
|
#include "k_grandprix.h"
|
|
#include "k_boss.h"
|
|
#include "k_hud.h"
|
|
#include "doomstat.h"
|
|
#include "m_random.h" // P_ClearRandom
|
|
#include "acs/interface.h"
|
|
#include "k_specialstage.h"
|
|
|
|
#define __STDC_FORMAT_MACROS
|
|
#include <inttypes.h>
|
|
|
|
#include <tracy/tracy/Tracy.hpp>
|
|
|
|
// Put hashes here to get them out of header hell.
|
|
#define ASSET_HASH_SRB2_SRB 0xf3ec1ea4d0eca4a9
|
|
#define ASSET_HASH_GFX_KART 0xc91b0d43f5ba131f
|
|
#define ASSET_HASH_TEXTURES_KART 0xb4211b2f32b6a291
|
|
#define ASSET_HASH_CHARS_KART 0x1e68a3e01aa5c68b
|
|
#define ASSET_HASH_MAPS_KART 0x38558ed00da41ce9
|
|
#define ASSET_HASH_MAIN_PK3 0xebb499f36118246b
|
|
#define ASSET_HASH_MAPPATCH_PK3 0xbbc2c6a7a685da3a
|
|
#define ASSET_HASH_BONUSCHARS_KART 0x60e6f13d822a7461
|
|
#ifdef USE_PATCH_FILE
|
|
#define ASSET_HASH_PATCH_PK3 0x0000000000000000
|
|
#endif
|
|
|
|
#ifdef CMAKECONFIG
|
|
#include "config.h"
|
|
#else
|
|
#include "config.h.in"
|
|
#endif
|
|
|
|
#ifdef HWRENDER
|
|
#include "hardware/hw_main.h" // 3D View Rendering
|
|
#endif
|
|
|
|
#ifdef HW3SOUND
|
|
#include "hardware/hw3sound.h"
|
|
#endif
|
|
|
|
#include "lua_script.h"
|
|
|
|
// Version numbers for netplay :upside_down_face:
|
|
int VERSION;
|
|
int SUBVERSION;
|
|
#ifdef COMMITVERSION
|
|
UINT8 comprevision_abbrev_bin[GIT_SHA_ABBREV];
|
|
#endif
|
|
|
|
#ifdef HAVE_DISCORDRPC
|
|
#include "discord.h"
|
|
#endif
|
|
|
|
// platform independant focus loss
|
|
UINT8 window_notinfocus = false;
|
|
|
|
//
|
|
// DEMO LOOP
|
|
//
|
|
static char *startupiwads[MAX_WADFILES];
|
|
static char *startuppwads[MAX_WADFILES];
|
|
|
|
boolean devparm = false; // started game with -devparm
|
|
|
|
boolean singletics = false; // timedemo
|
|
boolean lastdraw = false;
|
|
|
|
static INT32 lastwipetic = 0;
|
|
|
|
INT32 postimgparam[MAXSPLITSCREENPLAYERS];
|
|
|
|
// These variables are in effect
|
|
// whether the respective sound system is disabled
|
|
// or they're init'ed, but the player just toggled them
|
|
|
|
boolean sound_disabled = false;
|
|
boolean digital_disabled = false;
|
|
|
|
#ifdef DEBUGFILE
|
|
INT32 debugload = 0;
|
|
#endif
|
|
|
|
UINT16 numskincolors;
|
|
menucolor_t *menucolorhead, *menucolortail;
|
|
|
|
char savegamename[256];
|
|
char liveeventbackup[256];
|
|
|
|
char srb2home[256] = ".";
|
|
char srb2path[256] = ".";
|
|
boolean usehome = true;
|
|
const char *pandf = "%s" PATHSEP "%s";
|
|
static char addonsdir[MAX_WADPATH];
|
|
char downloaddir[sizeof addonsdir + sizeof DOWNLOADDIR_PART] = "DOWNLOAD";
|
|
|
|
//
|
|
// 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
|
|
// 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.
|
|
|
|
static boolean recursioncheck = false;
|
|
|
|
//
|
|
// D_ProcessEvents
|
|
// Send all the events of the given timestamp down the responder chain
|
|
//
|
|
void D_ProcessEvents(void)
|
|
{
|
|
event_t *ev;
|
|
|
|
boolean eaten = false;
|
|
|
|
if (recursioncheck == true)
|
|
I_Error("D_ProcessEvents recursion detected");
|
|
|
|
recursioncheck = true;
|
|
|
|
// i have to reset this somewhere or else your camera just glides away!
|
|
for (size_t i = 0; i < 4; i++)
|
|
gamekeydown[0][KEY_MOUSEMOVE + i] = 0;
|
|
|
|
memset(deviceResponding, false, sizeof (deviceResponding));
|
|
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;
|
|
}
|
|
|
|
if (demo.savemode == demovars_s::DSM_TITLEENTRY)
|
|
{
|
|
if (G_DemoTitleResponder(ev))
|
|
continue;
|
|
}
|
|
|
|
// Menu input
|
|
if (WipeInAction < 2)
|
|
{
|
|
#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)
|
|
continue; // menu ate the event
|
|
|
|
// Demo input:
|
|
if (demo.playback)
|
|
if (M_DemoResponder(ev))
|
|
continue; // demo 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)
|
|
{
|
|
hu_keystrokes = true;
|
|
continue; // ate the event
|
|
}
|
|
|
|
G_Responder(ev);
|
|
}
|
|
|
|
recursioncheck = false;
|
|
}
|
|
|
|
boolean D_RenderLevel(void)
|
|
{
|
|
UINT8 i;
|
|
|
|
if (automapactive || dedicated || !cv_renderview.value || !levelloaded)
|
|
return false;
|
|
|
|
R_ApplyLevelInterpolators(R_GetTimeFrac(RTF_LEVEL));
|
|
|
|
viewwindowy = 0;
|
|
viewwindowx = 0;
|
|
|
|
objectsdrawn = 0;
|
|
|
|
ps_rendercalltime = I_GetPreciseTime();
|
|
|
|
if (rendermode == render_soft)
|
|
{
|
|
if (cv_homremoval.value)
|
|
{
|
|
if (cv_homremoval.value == 1)
|
|
{
|
|
// Clear the software screen buffer to remove HOM
|
|
memset(vid.screens[0], 31, vid.width * vid.height);
|
|
}
|
|
else
|
|
{
|
|
//'development' HOM removal -- makes it blindingly obvious if HOM is spotted.
|
|
memset(vid.screens[0], 32+(timeinmap&15), vid.width * vid.height);
|
|
}
|
|
}
|
|
|
|
if (r_splitscreen == 2) // Draw over the fourth screen so you don't have to stare at a HOM :V
|
|
{
|
|
// V_DrawPatchFill, but for the fourth screen only
|
|
patch_t *pat = static_cast<patch_t*>(W_CachePatchName("SRB2BACK", PU_CACHE));
|
|
INT32 dupz = (vid.dupx < vid.dupy ? vid.dupx : vid.dupy);
|
|
INT32 x, y, pw = SHORT(pat->width) * dupz, ph = SHORT(pat->height) * dupz;
|
|
|
|
for (x = vid.width>>1; x < vid.width; x += pw)
|
|
{
|
|
for (y = vid.height>>1; y < vid.height; y += ph)
|
|
V_DrawScaledPatch(x, y, V_NOSCALESTART, pat);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (i = 0; i <= r_splitscreen; i++)
|
|
{
|
|
if (players[displayplayers[i]].mo || players[displayplayers[i]].playerstate == PST_DEAD)
|
|
{
|
|
viewssnum = i;
|
|
|
|
#ifdef HWRENDER
|
|
if (rendermode == render_opengl)
|
|
HWR_RenderPlayerView();
|
|
else
|
|
#endif
|
|
if (rendermode != render_none)
|
|
{
|
|
if (i > 0) // Splitscreen-specific
|
|
{
|
|
switch (i)
|
|
{
|
|
case 1:
|
|
if (r_splitscreen > 1)
|
|
{
|
|
viewwindowx = viewwidth;
|
|
viewwindowy = 0;
|
|
}
|
|
else
|
|
{
|
|
viewwindowx = 0;
|
|
viewwindowy = viewheight;
|
|
}
|
|
break;
|
|
case 2:
|
|
viewwindowx = 0;
|
|
viewwindowy = viewheight;
|
|
break;
|
|
case 3:
|
|
viewwindowx = viewwidth;
|
|
viewwindowy = viewheight;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
R_RenderPlayerView();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (rendermode == render_soft)
|
|
{
|
|
for (i = 0; i <= r_splitscreen; i++)
|
|
{
|
|
R_ApplyViewMorph(i);
|
|
|
|
V_DoPostProcessor(i, postimgparam[i]);
|
|
}
|
|
}
|
|
|
|
ps_rendercalltime = I_GetPreciseTime() - ps_rendercalltime;
|
|
R_RestoreLevelInterpolators();
|
|
return true;
|
|
}
|
|
|
|
//
|
|
// 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;
|
|
|
|
static void D_Display(void)
|
|
{
|
|
boolean forcerefresh = false;
|
|
static boolean wipe = false;
|
|
INT32 wipedefindex = 0;
|
|
UINT8 i;
|
|
|
|
ZoneScoped;
|
|
|
|
if (!dedicated)
|
|
{
|
|
if (nodrawers)
|
|
return; // for comparative timing/profiling
|
|
|
|
// Lactozilla: Switching renderers works by checking
|
|
// 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. The frame is ready to be drawn!
|
|
|
|
// Check for change of renderer or screen size (video mode)
|
|
if ((setrenderneeded || setmodeneeded) && !wipe)
|
|
SCR_SetMode(); // change video mode
|
|
|
|
// Recalc the screen
|
|
if (vid.recalc)
|
|
SCR_Recalc(); // NOTE! setsizeneeded is set by SCR_Recalc()
|
|
|
|
if (rendermode == render_soft)
|
|
{
|
|
for (i = 0; i <= r_splitscreen; ++i)
|
|
{
|
|
R_SetViewContext(static_cast<viewcontext_e>(VIEWCONTEXT_PLAYER1 + i));
|
|
R_InterpolateViewRollAngle(R_GetTimeFrac(RTF_LEVEL));
|
|
R_CheckViewMorph(i);
|
|
}
|
|
}
|
|
|
|
// Change the view size if needed
|
|
// Set by changing video mode or renderer
|
|
if (setsizeneeded)
|
|
{
|
|
R_ExecuteSetViewSize();
|
|
forcerefresh = true; // force background redraw
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
// set for all later
|
|
wipedefindex = gamestate; // wipe_xxx_toblack
|
|
if (gamestate == GS_TITLESCREEN && wipegamestate != GS_INTRO)
|
|
wipedefindex = wipe_timeattack_toblack;
|
|
|
|
if (wipetypepre < 0 || !F_WipeExists(wipetypepre))
|
|
wipetypepre = wipedefs[wipedefindex];
|
|
|
|
// Fade to black first
|
|
if ((wipegamestate == FORCEWIPE || !G_GamestateUsesLevel()) // fades to black on its own timing, always
|
|
&& wipetypepre != UINT8_MAX)
|
|
{
|
|
if (rendermode != render_none)
|
|
{
|
|
F_WipeStartScreen();
|
|
F_WipeColorFill(31);
|
|
F_WipeEndScreen();
|
|
}
|
|
F_RunWipe(wipetypepre, gamestate != GS_TIMEATTACK && gamestate != GS_TITLESCREEN);
|
|
}
|
|
|
|
if (rendermode != render_none)
|
|
{
|
|
if (gamestate != GS_LEVEL)
|
|
{
|
|
V_SetPaletteLump("PLAYPAL"); // Reset the palette
|
|
R_ReInitColormaps(0, NULL, 0, false);
|
|
}
|
|
|
|
F_WipeStartScreen();
|
|
}
|
|
else
|
|
wipegamestate = gamestate;
|
|
}
|
|
|
|
wipetypepre = -1;
|
|
|
|
if (dedicated) //bail out after wipe logic
|
|
goto dedipostwipe; // WAIT! don't forget about post wipes!
|
|
|
|
// Catch runaway clipping rectangles.
|
|
V_ClearClipRect();
|
|
|
|
// intermission background
|
|
if (lastdraw)
|
|
{
|
|
Y_ConsiderScreenBuffer();
|
|
lastdraw = false;
|
|
}
|
|
|
|
// do buffered drawing
|
|
switch (gamestate)
|
|
{
|
|
case GS_TITLESCREEN:
|
|
if (!titlemapinaction || !curbghide) {
|
|
F_TitleScreenDrawer();
|
|
break;
|
|
}
|
|
/* FALLTHRU */
|
|
case GS_LEVEL:
|
|
if (!gametic)
|
|
break;
|
|
AM_Drawer();
|
|
break;
|
|
|
|
case GS_INTERMISSION:
|
|
Y_IntermissionDrawer();
|
|
HU_Drawer();
|
|
break;
|
|
|
|
case GS_VOTING:
|
|
Y_VoteDrawer();
|
|
HU_Drawer();
|
|
break;
|
|
|
|
case GS_TIMEATTACK:
|
|
break;
|
|
|
|
case GS_INTRO:
|
|
F_IntroDrawer();
|
|
if (wipegamestate == (gamestate_t)-1)
|
|
{
|
|
wipe = true;
|
|
wipedefindex = gamestate; // wipe_xxx_toblack
|
|
}
|
|
break;
|
|
|
|
case GS_CUTSCENE:
|
|
F_CutsceneDrawer();
|
|
HU_Drawer();
|
|
break;
|
|
|
|
case GS_EVALUATION:
|
|
F_GameEvaluationDrawer();
|
|
HU_Drawer();
|
|
break;
|
|
|
|
case GS_CREDITS:
|
|
F_CreditDrawer();
|
|
HU_Drawer();
|
|
break;
|
|
|
|
case GS_BLANCREDITS:
|
|
F_BlanCreditDrawer();
|
|
HU_Drawer();
|
|
break;
|
|
|
|
case GS_SECRETCREDITS:
|
|
F_SecretCreditsDrawer();
|
|
HU_Drawer();
|
|
break;
|
|
|
|
case GS_WAITINGPLAYERS:
|
|
// The clientconnect drawer is independent...
|
|
if (netgame)
|
|
{
|
|
// I don't think HOM from nothing drawing is independent...
|
|
F_WaitingPlayersDrawer();
|
|
HU_Drawer();
|
|
}
|
|
case GS_DEDICATEDSERVER:
|
|
case GS_NULL:
|
|
case FORCEWIPE:
|
|
break;
|
|
}
|
|
|
|
// STUPID race condition...
|
|
{
|
|
wipegamestate = gamestate;
|
|
|
|
// clean up border stuff
|
|
// see if the border needs to be initially drawn
|
|
if (gamestate == GS_LEVEL || (gamestate == GS_TITLESCREEN && titlemapinaction && curbghide && (!hidetitlemap)))
|
|
{
|
|
|
|
D_RenderLevel();
|
|
|
|
ps_uitime = I_GetPreciseTime();
|
|
|
|
if (gamestate == GS_LEVEL)
|
|
{
|
|
ST_Drawer();
|
|
F_TextPromptDrawer();
|
|
HU_Drawer();
|
|
}
|
|
else
|
|
F_TitleScreenDrawer();
|
|
}
|
|
else
|
|
{
|
|
ps_uitime = I_GetPreciseTime();
|
|
}
|
|
}
|
|
|
|
// change gamma if needed
|
|
// (GS_LEVEL handles this already due to level-specific palettes)
|
|
if (forcerefresh && !G_GamestateUsesLevel())
|
|
V_SetPalette(0);
|
|
|
|
// draw pause pic
|
|
if (paused && cv_showhud.value && !demo.playback)
|
|
{
|
|
INT32 py;
|
|
patch_t *patch;
|
|
if (automapactive)
|
|
py = 4;
|
|
else
|
|
py = viewwindowy + 4;
|
|
patch = static_cast<patch_t*>(W_CachePatchName("M_PAUSE", PU_PATCH));
|
|
V_DrawScaledPatch(viewwindowx + (BASEVIDWIDTH - patch->width)/2, py, 0, patch);
|
|
}
|
|
|
|
if (demo.rewinding)
|
|
V_DrawFadeScreen(TC_RAINBOW, (leveltime & 0x20) ? SKINCOLOR_PASTEL : SKINCOLOR_MOONSLAM);
|
|
|
|
// vid size change is now finished if it was on...
|
|
vid.recalc = false;
|
|
|
|
#ifdef HAVE_THREADS
|
|
I_lock_mutex(&m_menu_mutex);
|
|
#endif
|
|
M_Drawer(); // menu is drawn even on top of everything
|
|
if (cv_songcredits.value && !( (G_GamestateUsesLevel() && hu_showscores) && (netgame || multiplayer) ))
|
|
HU_DrawSongCredits(); // As are music credits.
|
|
#ifdef HAVE_THREADS
|
|
I_unlock_mutex(m_menu_mutex);
|
|
#endif
|
|
// focus lost moved to M_Drawer
|
|
|
|
CON_Drawer();
|
|
|
|
ps_uitime = I_GetPreciseTime() - ps_uitime;
|
|
|
|
//
|
|
// wipe update
|
|
//
|
|
dedipostwipe:
|
|
if (wipe && wipetypepost != INT16_MAX)
|
|
{
|
|
// 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();
|
|
|
|
F_RunWipe(wipetypepost, gamestate != GS_TIMEATTACK && gamestate != GS_INTRO);
|
|
|
|
// reset counters so timedemo doesn't count the wipe duration
|
|
if (demo.timing)
|
|
{
|
|
framecount = 0;
|
|
demostarttime = I_GetTime();
|
|
}
|
|
}
|
|
|
|
wipetypepost = -1;
|
|
|
|
if (dedicated)
|
|
return; // NOW we can bail
|
|
|
|
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_shittyscreen.value)
|
|
V_DrawVhsEffect(cv_shittyscreen.value == 2);
|
|
|
|
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);
|
|
}
|
|
|
|
if (cv_perfstats.value)
|
|
{
|
|
M_DrawPerfStats();
|
|
}
|
|
|
|
ps_swaptime = I_GetPreciseTime();
|
|
I_FinishUpdate(); // page flip or blit buffer
|
|
ps_swaptime = I_GetPreciseTime() - ps_swaptime;
|
|
}
|
|
}
|
|
|
|
static void D_WipeTick(boolean menu)
|
|
{
|
|
I_OsPolling();
|
|
D_ProcessEvents();
|
|
|
|
// Update the network so we don't cause timeouts
|
|
NetKeepAlive();
|
|
|
|
HU_Ticker();
|
|
if (menu)
|
|
{
|
|
#ifdef HAVE_THREADS
|
|
I_lock_mutex(&m_menu_mutex);
|
|
#endif
|
|
M_Ticker();
|
|
#ifdef HAVE_THREADS
|
|
I_unlock_mutex(m_menu_mutex);
|
|
#endif
|
|
}
|
|
CON_Ticker();
|
|
}
|
|
|
|
static void D_WipeDraw(boolean menu)
|
|
{
|
|
if (rendermode == render_none)
|
|
return;
|
|
|
|
I_UpdateNoBlit();
|
|
|
|
HU_Drawer();
|
|
if (menu)
|
|
{
|
|
#ifdef HAVE_THREADS
|
|
I_lock_mutex(&m_menu_mutex);
|
|
#endif
|
|
M_Drawer();
|
|
#ifdef HAVE_THREADS
|
|
I_unlock_mutex(m_menu_mutex);
|
|
#endif
|
|
}
|
|
CON_Drawer();
|
|
|
|
I_FinishUpdate(); // page flip or blit buffer
|
|
}
|
|
|
|
static double D_EndFrame(precise_t enterprecise, int *frameskip)
|
|
{
|
|
// Fully completed frame made.
|
|
precise_t finishprecise = I_GetPreciseTime();
|
|
|
|
// Use the time before sleep for frameskip calculations:
|
|
// post-sleep time is literally being intentionally wasted
|
|
double deltasecs = (double)((INT64)(finishprecise - enterprecise)) / I_GetPrecisePrecision();
|
|
double deltatics = deltasecs * NEWTICRATE;
|
|
|
|
// If time spent this game loop exceeds a single tic,
|
|
// it's probably because of rendering.
|
|
//
|
|
// Skip rendering the next frame, up to a limit of 3
|
|
// frames before a frame is rendered no matter what.
|
|
if (frameskip)
|
|
{
|
|
if (*frameskip < 3 && deltatics > 1.0)
|
|
*frameskip += 1;
|
|
else
|
|
*frameskip = 0;
|
|
}
|
|
|
|
if (!singletics)
|
|
{
|
|
precise_t elapsed = finishprecise - enterprecise;
|
|
|
|
// capbudget is the minimum precise_t duration of a single loop iteration
|
|
double capbudget = ((R_GetFramerateCap() == 0) ? 0.0 : round((1.0 / R_GetFramerateCap()) * I_GetPrecisePrecision()));
|
|
|
|
// in the case of "match refresh rate" + vsync, don't sleep at all
|
|
const boolean vsync_with_match_refresh = cv_vidwait.value && cv_fpscap.value == 0;
|
|
|
|
if (elapsed > 0 && capbudget > elapsed && !vsync_with_match_refresh)
|
|
{
|
|
I_SleepDuration(capbudget - (finishprecise - enterprecise));
|
|
}
|
|
}
|
|
|
|
// Capture the time once more to get the real delta time.
|
|
finishprecise = I_GetPreciseTime();
|
|
deltasecs = (double)((INT64)(finishprecise - enterprecise)) / I_GetPrecisePrecision();
|
|
return deltasecs * NEWTICRATE;
|
|
}
|
|
|
|
static INT32 endtimes[] = {
|
|
UINT8_MAX, // WIPELOOP_RUNWIPE
|
|
PRELEVELTIME*NEWTICRATERATIO, // WIPELOOP_TITLECARD
|
|
3*TICRATE/2, // WIPELOOP_ENCORE
|
|
NEWTICRATE, // WIPELOOP_TITLEBLACK
|
|
};
|
|
|
|
static bool ranwipe = false;
|
|
|
|
// one single function for all the extra main loops in this god-forsaken codebase
|
|
// should make it easier to fold these back into D_SRB2Loop at some point...
|
|
void D_WipeLoop(wipelooptype_t type, UINT8 wipetype, boolean drawMenu)
|
|
{
|
|
tic_t nowtime, endtime;
|
|
UINT8 wipeframe = 0;
|
|
fademask_t *fmask;
|
|
double delta = 0.0;
|
|
|
|
nowtime = lastwipetic = I_GetTime();
|
|
endtime = nowtime + endtimes[type];
|
|
|
|
if (gamestate != GS_TITLESCREEN && gamestate != GS_TIMEATTACK && gamestate != GS_INTRO)
|
|
drawMenu = true;
|
|
|
|
WipeInAction = 1 + !drawMenu;
|
|
|
|
// lastwipetic should either be 0 or the tic we last wiped
|
|
// on for fade-to-black
|
|
while (nowtime < endtime)
|
|
{
|
|
precise_t enterprecise = I_GetPreciseTime();
|
|
I_UpdateTime(cv_timescale.value);
|
|
|
|
nowtime = I_GetTime();
|
|
|
|
// wait loop
|
|
if (nowtime - lastwipetic)
|
|
{
|
|
renderisnewtic = true;
|
|
wipeframe++;
|
|
D_WipeTick(drawMenu);
|
|
if (type == WIPELOOP_TITLECARD)
|
|
ST_runTitleCard();
|
|
}
|
|
else
|
|
renderisnewtic = false;
|
|
|
|
// draw loop
|
|
R_SetTimeFrac(g_time.timefrac);
|
|
renderdeltatics = FLOAT_TO_FIXED(delta);
|
|
|
|
#ifndef NOWIPE
|
|
if (type == WIPELOOP_RUNWIPE)
|
|
{
|
|
// get fademask first so we can tell if it exists or not
|
|
fmask = F_GetFadeMask(wipetype, wipeframe);
|
|
if (!fmask)
|
|
break;
|
|
|
|
#ifdef HWRENDER
|
|
if (rendermode == render_opengl)
|
|
HWR_DoWipe(wipetype, wipeframe); // send in the wipe type and wipeframe because we need to cache the graphic
|
|
else
|
|
#endif
|
|
if (rendermode != render_none) //this allows F_RunWipe to be called in dedicated servers
|
|
F_DoWipe(fmask);
|
|
}
|
|
#endif
|
|
|
|
if (rendermode != render_none && type == WIPELOOP_TITLECARD)
|
|
ST_preLevelTitleCardDrawer();
|
|
|
|
D_WipeDraw(drawMenu);
|
|
I_UpdateSound();
|
|
|
|
// Only take screenshots after drawing.
|
|
if (moviemode)
|
|
M_SaveFrame();
|
|
if (takescreenshot)
|
|
M_DoScreenShot();
|
|
|
|
delta = D_EndFrame(enterprecise, NULL);
|
|
lastwipetic = nowtime;
|
|
}
|
|
|
|
ranwipe = true;
|
|
WipeInAction = 0;
|
|
}
|
|
|
|
// =========================================================================
|
|
// D_SRB2Loop
|
|
// =========================================================================
|
|
|
|
tic_t rendergametic;
|
|
|
|
void D_SRB2Loop(void)
|
|
{
|
|
tic_t entertic = 0, oldentertics = 0, realtics = 0, rendertimeout = INFTICS;
|
|
double deltatics = 0.0;
|
|
|
|
boolean interp = false;
|
|
boolean doDisplay = false;
|
|
int frameskip = 0;
|
|
|
|
if (dedicated)
|
|
server = true;
|
|
connectedtodedicated = dedicated;
|
|
|
|
// Pushing of + parameters is now done back in D_SRB2Main, not here.
|
|
|
|
I_UpdateTime(cv_timescale.value);
|
|
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();
|
|
|
|
chosenrendermode = render_none;
|
|
|
|
// Check and print which version is executed.
|
|
// Use this as the border between setup and the main game loop being entered.
|
|
#ifdef BOOTDEVNAG
|
|
CONS_Printf(
|
|
"===========================================================================\n"
|
|
BLANNAME " is still in active development. Things may break or crash!\n");
|
|
#endif
|
|
CONS_Printf(
|
|
"===========================================================================\n"
|
|
" We hope you enjoy this game as\n"
|
|
" much as we did making it!\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
|
|
*/
|
|
|
|
#if 0
|
|
/* Smells like a hack... Don't fade Sonic's ass into the title screen. */
|
|
if (gamestate != GS_TITLESCREEN)
|
|
{
|
|
static lumpnum_t gstartuplumpnum = W_CheckNumForName("STARTUP");
|
|
if (gstartuplumpnum == LUMPERROR)
|
|
gstartuplumpnum = W_GetNumForName("MISSING");
|
|
V_DrawScaledPatch(0, 0, 0, W_CachePatchNum(gstartuplumpnum, PU_PATCH));
|
|
}
|
|
#endif
|
|
|
|
for (;;)
|
|
{
|
|
if (I_Interrupted())
|
|
I_Quit();
|
|
|
|
precise_t enterprecise = I_GetPreciseTime();
|
|
|
|
memset(&g_dc, 0, sizeof(g_dc));
|
|
Z_Frame_Reset();
|
|
|
|
I_UpdateTime(cv_timescale.value);
|
|
|
|
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?
|
|
|
|
if (demo.playback && gamestate == GS_LEVEL)
|
|
{
|
|
// Nicer place to put this.
|
|
realtics = realtics * cv_playbackspeed.value;
|
|
}
|
|
|
|
#ifdef DEBUGFILE
|
|
if (!realtics)
|
|
if (debugload)
|
|
debugload--;
|
|
#endif
|
|
|
|
interp = !dedicated && (R_GetFramerateCap() != TICRATE || cv_timescale.value < FRACUNIT);
|
|
doDisplay = false;
|
|
|
|
#ifdef HW3SOUND
|
|
HW3S_BeginFrameUpdate();
|
|
#endif
|
|
|
|
if (realtics > 0 || singletics)
|
|
{
|
|
// 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)
|
|
{
|
|
ZoneScopedN("TryRunTics");
|
|
TryRunTics(realtics);
|
|
}
|
|
|
|
if (lastdraw || singletics || gametic > rendergametic)
|
|
{
|
|
rendergametic = gametic;
|
|
rendertimeout = entertic + TICRATE/17;
|
|
|
|
doDisplay = true;
|
|
}
|
|
else if (rendertimeout < entertic) // in case the server hang or netsplit
|
|
{
|
|
// Lagless camera! Yay!
|
|
if (cv_laglesscam.value && gamestate == GS_LEVEL && netgame)
|
|
{
|
|
// Evaluate the chase cam once for every local realtic
|
|
// This might actually be better suited inside G_Ticker or TryRunTics
|
|
for (tic_t chasecamtics = 0; chasecamtics < realtics; chasecamtics++)
|
|
{
|
|
P_RunChaseCameras();
|
|
}
|
|
R_UpdateViewInterpolation();
|
|
}
|
|
|
|
doDisplay = true;
|
|
}
|
|
|
|
renderisnewtic = true;
|
|
|
|
if (!dedicated)
|
|
{
|
|
G_DeviceLEDTick();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
renderisnewtic = false;
|
|
}
|
|
|
|
if (interp)
|
|
{
|
|
renderdeltatics = FLOAT_TO_FIXED(deltatics);
|
|
|
|
const boolean lagging = ((deltatics >= 1.0) || hu_stopped);
|
|
|
|
R_SetTimeFrac(lagging ? FRACUNIT : I_GetTimeFrac());
|
|
}
|
|
else
|
|
{
|
|
renderdeltatics = realtics * FRACUNIT;
|
|
R_SetTimeFrac(FRACUNIT);
|
|
}
|
|
|
|
if (interp || doDisplay)
|
|
{
|
|
if (!frameskip)
|
|
{
|
|
D_Display();
|
|
}
|
|
else if (!dedicated && frameskip)
|
|
{
|
|
// always update console movement
|
|
// otherwise it will takes literal ages to open
|
|
CON_MoveConsole();
|
|
}
|
|
}
|
|
|
|
// Only take screenshots after drawing.
|
|
if (moviemode)
|
|
M_SaveFrame();
|
|
if (takescreenshot)
|
|
M_DoScreenShot();
|
|
|
|
// consoleplayer -> displayplayers (hear sounds from viewpoint)
|
|
S_UpdateSounds(); // move positional sounds
|
|
if (renderisnewtic)
|
|
S_UpdateClosedCaptions();
|
|
|
|
#ifdef HW3SOUND
|
|
HW3S_EndFrameUpdate();
|
|
#endif
|
|
|
|
LUA_Step();
|
|
|
|
#ifdef HAVE_DISCORDRPC
|
|
if (! dedicated)
|
|
{
|
|
Discord_RunCallbacks();
|
|
}
|
|
#endif
|
|
|
|
deltatics = D_EndFrame(enterprecise, &frameskip);
|
|
|
|
// Wipes run an inner loop and artificially increase
|
|
// the measured time.
|
|
if (ranwipe)
|
|
{
|
|
double deltamath = (R_GetFramerateCap() == 0) ? 35.0 : 35.0 / R_GetFramerateCap();
|
|
deltatics = deltamath;
|
|
frameskip = 0;
|
|
ranwipe = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// =========================================================================
|
|
// D_SRB2Main
|
|
// =========================================================================
|
|
|
|
//
|
|
// D_StartTitle
|
|
//
|
|
void D_StartTitle(void)
|
|
{
|
|
if (dedicated)
|
|
I_Error("D_StartTitle is called on dedicated server");
|
|
|
|
INT32 i;
|
|
|
|
S_StopMusic();
|
|
|
|
if (netgame)
|
|
{
|
|
G_SetGamestate(GS_WAITINGPLAYERS); // hack to prevent a command repeat
|
|
|
|
if (server)
|
|
{
|
|
i = G_GetFirstMapOfGametype(gametype)+1;
|
|
|
|
if (i > nummapheaders)
|
|
I_Error("D_StartTitle: No valid map ID found!?");
|
|
|
|
COM_BufAddText(va("map %s\n", G_BuildMapName(i)));
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// okay, stop now
|
|
// (otherwise the game still thinks we're playing!)
|
|
CURLAbortFile();
|
|
SV_StopServer();
|
|
SV_ResetServer();
|
|
|
|
for (i = 0; i < MAXPLAYERS; i++)
|
|
CL_ClearPlayer(i);
|
|
|
|
splitscreen = 0;
|
|
SplitScreen_OnChange();
|
|
|
|
cht_debug = 0;
|
|
emeralds = 0;
|
|
memset(&luabanks, 0, sizeof(luabanks));
|
|
lastmaploaded = 0;
|
|
|
|
// In case someone exits out at the same time they start a time attack run,
|
|
// reset modeattacking
|
|
modeattacking = ATTACKING_NONE;
|
|
marathonmode = static_cast<marathonmode_t>(0);
|
|
|
|
// Reset GP
|
|
memset(&grandprixinfo, 0, sizeof(struct grandprixinfo));
|
|
|
|
// Reset Server mods
|
|
numcustomservermods = 0;
|
|
memset(customservermods, 0, sizeof(customservermods));
|
|
|
|
// Reset boss info
|
|
K_ResetBossInfo();
|
|
|
|
// Reset Special Stage
|
|
K_ResetSpecialStage();
|
|
|
|
// empty maptol so mario/etc sounds don't play in sound test when they shouldn't
|
|
maptol = 0;
|
|
|
|
gameaction = ga_nothing;
|
|
memset(displayplayers, 0, sizeof(displayplayers));
|
|
memset(g_localplayers, 0, sizeof g_localplayers);
|
|
consoleplayer = 0;
|
|
//demosequence = -1;
|
|
G_SetGametype(GT_RACE); // SRB2kart
|
|
paused = false;
|
|
F_InitMenuPresValues(true);
|
|
|
|
// clear cmd building stuff
|
|
memset(gamekeydown, 0, sizeof(gamekeydown));
|
|
memset(deviceResponding, false, sizeof (deviceResponding));
|
|
|
|
F_StartTitleScreen();
|
|
|
|
// Reset the palette
|
|
if (rendermode != render_none)
|
|
V_SetPaletteLump("PLAYPAL");
|
|
|
|
G_ResetAllDeviceRumbles();
|
|
|
|
// The title screen is obviously not a tutorial! (Unless I'm mistaken)
|
|
tutorialmode = false;
|
|
|
|
// map palettes affect this
|
|
G_ResetDeviceLED();
|
|
}
|
|
|
|
//
|
|
// D_AddFile
|
|
//
|
|
static void D_AddFile(char **list, const char *file)
|
|
{
|
|
size_t pnumwadfiles;
|
|
char *newfile;
|
|
|
|
for (pnumwadfiles = 0; list[pnumwadfiles]; pnumwadfiles++)
|
|
;
|
|
|
|
newfile = static_cast<char*>(malloc(strlen(file) + 1));
|
|
if (!newfile)
|
|
{
|
|
I_Error("No more free memory to AddFile %s",file);
|
|
}
|
|
strcpy(newfile, file);
|
|
|
|
list[pnumwadfiles] = newfile;
|
|
}
|
|
|
|
static void D_CleanFile(char **list)
|
|
{
|
|
size_t pnumwadfiles;
|
|
for (pnumwadfiles = 0; list[pnumwadfiles]; pnumwadfiles++)
|
|
{
|
|
free(list[pnumwadfiles]);
|
|
list[pnumwadfiles] = NULL;
|
|
}
|
|
}
|
|
|
|
///\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)
|
|
{
|
|
// 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
|
|
}
|
|
}
|
|
|
|
// ==========================================================================
|
|
// Identify the SRB2 version, and IWAD file to use.
|
|
// ==========================================================================
|
|
|
|
static boolean AddIWAD(void)
|
|
{
|
|
char * path = va(pandf,srb2path, SRB2NAME);
|
|
|
|
if (FIL_ReadFileOK(path))
|
|
{
|
|
D_AddFile(startupiwads, path);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static void IdentifyVersion(void)
|
|
{
|
|
const char *srb2waddir = NULL;
|
|
|
|
#if (defined (__unix__) && !defined (MSDOS)) || defined (UNIXCOMMON) || defined (HAVE_SDL)
|
|
// change to the directory where 'srb2.srb' is found
|
|
srb2waddir = I_LocateWad();
|
|
#endif
|
|
|
|
char tempsrb2path[256] = ".";
|
|
if (getcwd(tempsrb2path, 256) == NULL)
|
|
strcpy(tempsrb2path, ".");
|
|
|
|
// get the current directory (possible problem on NT with "." as current dir)
|
|
if (!srb2waddir)
|
|
{
|
|
srb2waddir = tempsrb2path;
|
|
}
|
|
|
|
#if (1) // reduce the amount of findfile by only using full cwd in this func
|
|
if (!fastcmp(tempsrb2path, srb2waddir))
|
|
#endif
|
|
{
|
|
strlcpy(srb2path, srb2waddir, sizeof (srb2path));
|
|
}
|
|
|
|
// Load the IWAD
|
|
if (! AddIWAD())
|
|
{
|
|
I_Error("%s not found! Expected in %s\n", SRB2NAME, srb2waddir);
|
|
}
|
|
|
|
// will be overwritten in case of -cdrom or unix/win home
|
|
snprintf(configfile, sizeof configfile, "%s" PATHSEP CONFIGFILENAME, srb2waddir);
|
|
configfile[sizeof configfile - 1] = '\0';
|
|
|
|
// if you change the ordering of this or add/remove a file, be sure to update the hash
|
|
// checking in D_SRB2Main
|
|
|
|
D_AddFile(startupiwads, va(pandf,srb2waddir,GRAPHICSNAME));
|
|
D_AddFile(startupiwads, va(pandf,srb2waddir,TEXTURESNAME));
|
|
D_AddFile(startupiwads, va(pandf,srb2waddir,CHARSNAME));
|
|
D_AddFile(startupiwads, va(pandf,srb2waddir,MAPSNAME));
|
|
D_AddFile(startupiwads, va(pandf,srb2waddir,MAINNAME));
|
|
D_AddFile(startupiwads, va(pandf,srb2waddir,MAPPATCHNAME));
|
|
D_AddFile(startupiwads, va(pandf,srb2waddir,BONUSCHARSNAME));
|
|
#ifdef USE_PATCH_FILE
|
|
D_AddFile(startupiwads, va(pandf,srb2waddir,PATCHNAME));
|
|
#endif
|
|
////
|
|
|
|
#if !defined (HAVE_SDL) || defined (HAVE_MIXER) || defined (HAVE_OPENAL)
|
|
|
|
#define MUSICTEST(str) \
|
|
{\
|
|
const char *musicpath = va(pandf,srb2waddir,str);\
|
|
int ms = W_VerifyNMUSlumps(musicpath, false); \
|
|
if (ms == 1) \
|
|
D_AddFile(startupiwads, musicpath); \
|
|
else if (ms == 0) \
|
|
I_Error("File " str " has been modified with non-music/sound lumps"); \
|
|
}
|
|
|
|
MUSICTEST(SOUNDSNAME)
|
|
MUSICTEST(MUSICNAME)
|
|
MUSICTEST(BLANMUSICNAME)
|
|
|
|
#undef MUSICTEST
|
|
|
|
#endif
|
|
}
|
|
|
|
#ifdef COMMITVERSION
|
|
static void D_AbbrevCommit (void)
|
|
{
|
|
UINT8 i;
|
|
|
|
for (i = 0; i < GIT_SHA_ABBREV; ++i)
|
|
{
|
|
sscanf(&comprevision[i * 2], "%2hhx",
|
|
&comprevision_abbrev_bin[i]);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static void
|
|
D_ConvertVersionNumbers (void)
|
|
{
|
|
/* leave at defaults (0) under DEVELOP */
|
|
#ifndef DEVELOP
|
|
sscanf(SRB2VERSION, "%d.%d", &VERSION, &SUBVERSION);
|
|
#endif
|
|
}
|
|
|
|
const char *D_GetFancyBranchName(void)
|
|
{
|
|
if (compbranch[0] == '\0')
|
|
{
|
|
// \x8b = aqua highlight
|
|
return "\x8b" "detached HEAD" "\x80";
|
|
}
|
|
|
|
return compbranch;
|
|
}
|
|
|
|
static void Command_assert(void)
|
|
{
|
|
#if !defined(NDEBUG) || defined(PARANOIA)
|
|
CONS_Printf("Yes, assertions are enabled.\n");
|
|
#else
|
|
CONS_Printf("No, ssertions are NOT enabled.\n");
|
|
#endif
|
|
}
|
|
|
|
#ifdef DEVELOP
|
|
static void Command_crash(void)
|
|
{
|
|
I_Error("The game crashed on PURPOSE, because of the 'crash' command. (This is only enabled in DEVELOP builds.)");
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// 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();
|
|
|
|
#ifdef COMMITVERSION
|
|
D_AbbrevCommit();
|
|
#endif
|
|
|
|
// Print GPL notice for our console users (Linux)
|
|
CONS_Printf(
|
|
"\n\nSonic Robo Blast 2 Kart\n"
|
|
"Copyright (C) 1998-2022 by Kart Krew & STJr\n\n"
|
|
"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");
|
|
|
|
// keep error messages until the final flush(stderr)
|
|
#if !defined(NOTERMIOS)
|
|
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();
|
|
|
|
// Netgame URL special case: change working dir to EXE folder.
|
|
ChangeDirForUrlHandler();
|
|
|
|
// identify the main IWAD file to use
|
|
IdentifyVersion();
|
|
|
|
#if !defined(NOTERMIOS)
|
|
setbuf(stdout, NULL); // non-buffered output
|
|
#endif
|
|
|
|
#if 0 //defined (_DEBUG)
|
|
devparm = M_CheckParm("-nodebug") == 0;
|
|
#else
|
|
devparm = M_CheckParm("-debug") != 0;
|
|
#endif
|
|
|
|
// for dedicated server
|
|
dedicated = M_CheckParm("-dedicated") != 0;
|
|
connectedtodedicated = dedicated;
|
|
|
|
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
|
|
|
|
{
|
|
const char *userhome = D_Home(); //Alam: path to home
|
|
|
|
if (!userhome)
|
|
{
|
|
#if ((defined (__unix__) && !defined (MSDOS)) || defined(__APPLE__) || defined (UNIXCOMMON)) && !defined (__CYGWIN__)
|
|
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);
|
|
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);
|
|
strcatbf(liveeventbackup, srb2home, PATHSEP);
|
|
|
|
snprintf(luafiledir, sizeof luafiledir, "%s" PATHSEP "luafiles", srb2home);
|
|
#else // DEFAULTDIR
|
|
snprintf(srb2home, sizeof srb2home, "%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);
|
|
strcatbf(liveeventbackup, userhome, PATHSEP);
|
|
|
|
snprintf(luafiledir, sizeof luafiledir, "%s" PATHSEP "luafiles", userhome);
|
|
#endif // DEFAULTDIR
|
|
}
|
|
|
|
configfile[sizeof configfile - 1] = '\0';
|
|
}
|
|
|
|
// If config isn't writable, tons of behavior will be broken.
|
|
// Fail loudly before things get confusing!
|
|
{
|
|
FILE *tmpfile;
|
|
char testfile[MAX_WADPATH];
|
|
|
|
snprintf(testfile, sizeof testfile, "%s" PATHSEP "file.tmp", srb2home);
|
|
testfile[sizeof testfile - 1] = '\0';
|
|
|
|
tmpfile = fopen(testfile, "w");
|
|
if (tmpfile == NULL)
|
|
{
|
|
#if defined (_WIN32)
|
|
I_Error("Couldn't write game config.\nMake sure the game is installed somewhere it has write permissions.\n\n(Don't use the Downloads folder, Program Files, or your desktop!\nIf unsure, we recommend making a subfolder in your Documents folder.)");
|
|
#else
|
|
I_Error("Couldn't write game config.\nMake sure you've installed the game somewhere it has write permissions.");
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
fclose(tmpfile);
|
|
remove(testfile);
|
|
}
|
|
}
|
|
|
|
// Create addons dir
|
|
snprintf(addonsdir, sizeof addonsdir, "%s%s%s", srb2home, PATHSEP, "addons");
|
|
I_mkdir(addonsdir, 0755);
|
|
|
|
/* and downloads in a subdirectory */
|
|
snprintf(downloaddir, sizeof downloaddir, "%s%s%s",
|
|
addonsdir, PATHSEP, DOWNLOADDIR_PART);
|
|
|
|
// rand() needs seeded regardless of password
|
|
srand((unsigned int)time(NULL));
|
|
rand();
|
|
rand();
|
|
rand();
|
|
|
|
if (M_CheckParm("-password") && M_IsNextParm())
|
|
D_SetPassword(M_GetNextParm());
|
|
|
|
CONS_Printf("Z_Init(): Init zone memory allocation daemon. \n");
|
|
Z_Init();
|
|
CON_SetLoadingProgress(LOADED_ZINIT);
|
|
|
|
// Do this up here so that WADs loaded through the command line can use ExecCfg
|
|
COM_Init();
|
|
|
|
COM_AddCommand("assert", Command_assert);
|
|
#ifdef DEVELOP
|
|
COM_AddCommand("crash", Command_crash);
|
|
#endif
|
|
|
|
// add any files specified on the command line with -file wadfile
|
|
// to the wad list
|
|
if (!((M_GetUrlProtocolArg() || M_CheckParm("-connect")) && !M_CheckParm("-server")))
|
|
{
|
|
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?
|
|
D_AddFile(startuppwads, s);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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_ResetData(15);
|
|
|
|
// init title screen display params
|
|
if (M_GetUrlProtocolArg() || M_CheckParm("-connect"))
|
|
{
|
|
F_InitMenuPresValues(true);
|
|
}
|
|
|
|
//---------------------------------------------------- READY TIME
|
|
// we need to check for dedicated before initialization of some subsystems
|
|
|
|
CONS_Printf("I_InitializeTime()...\n");
|
|
I_InitializeTime();
|
|
CON_SetLoadingProgress(LOADED_ISTARTUPTIMER);
|
|
|
|
D_RegisterServerCommands();
|
|
D_RegisterClientCommands(); // be sure that this is called before D_CheckNetGame
|
|
R_RegisterEngineStuff();
|
|
S_RegisterSoundStuff();
|
|
CON_Register();
|
|
M_Init();
|
|
if (!dedicated)
|
|
{
|
|
CV_RegisterVar(&cv_ticrate);
|
|
CV_RegisterVar(&cv_accuratefps);
|
|
CV_RegisterVar(&cv_constextsize);
|
|
}
|
|
|
|
I_RegisterSysCommands();
|
|
|
|
#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.~~ G: no lol
|
|
HWR_AddCommands();
|
|
#endif
|
|
|
|
// load wad, including the main wad file
|
|
CONS_Printf("W_InitMultipleFiles(): Adding IWAD and main PWADs.\n");
|
|
W_InitMultipleFiles(startupiwads, false);
|
|
D_CleanFile(startupiwads);
|
|
|
|
#ifndef DEVELOP
|
|
// Check hashes of autoloaded files
|
|
// Note: Do not add any files that ignore hashing!
|
|
W_VerifyFileHash(MAINWAD_SRB2, ASSET_HASH_SRB2_SRB);
|
|
W_VerifyFileHash(MAINWAD_GFX, ASSET_HASH_GFX_KART);
|
|
W_VerifyFileHash(MAINWAD_TEXTURES, ASSET_HASH_TEXTURES_KART);
|
|
W_VerifyFileHash(MAINWAD_CHARS, ASSET_HASH_CHARS_KART);
|
|
W_VerifyFileHash(MAINWAD_MAPS, ASSET_HASH_MAPS_KART);
|
|
W_VerifyFileHash(MAINWAD_MAIN, ASSET_HASH_MAIN_PK3);
|
|
W_VerifyFileHash(MAINWAD_MAPPATCH, ASSET_HASH_MAPPATCH_PK3);
|
|
W_VerifyFileHash(MAINWAD_BONUSCHARS, ASSET_HASH_BONUSCHARS_KART);
|
|
#ifdef USE_PATCH_FILE
|
|
W_VerifyFileHash(MAINWAD_PATCH, ASSET_HASH_PATCH_PK3);
|
|
#endif
|
|
#endif //ifndef DEVELOP
|
|
|
|
wadfiles[MAINWAD_SRB2]->compatmode = true;
|
|
wadfiles[MAINWAD_GFX]->compatmode = true;
|
|
wadfiles[MAINWAD_TEXTURES]->compatmode = true;
|
|
wadfiles[MAINWAD_CHARS]->compatmode = true;
|
|
wadfiles[MAINWAD_MAPS]->compatmode = true;
|
|
wadfiles[MAINWAD_MAIN]->compatmode = false;
|
|
wadfiles[MAINWAD_MAPPATCH]->compatmode = false;
|
|
wadfiles[MAINWAD_BONUSCHARS]->compatmode = true;
|
|
#ifdef USE_PATCH_FILE
|
|
wadfiles[MAINWAD_PATCH]->compatmode = false;
|
|
#endif
|
|
|
|
R_InitPaletteRemap();
|
|
|
|
M_InitPlayerSetupColors();
|
|
|
|
K_ReloadHUDColorCvar();
|
|
|
|
// Do it before P_InitMapData because PNG patch
|
|
// conversion sometimes needs the palette
|
|
V_ReloadPalette();
|
|
|
|
P_InitMapData(false);
|
|
|
|
CON_SetLoadingProgress(LOADED_IWAD);
|
|
|
|
cht_Init();
|
|
|
|
//---------------------------------------------------- READY SCREEN
|
|
// we need to check for dedicated before initialization of some subsystems
|
|
|
|
CONS_Printf("I_StartupGraphics()...\n");
|
|
I_StartupGraphics();
|
|
|
|
//--------------------------------------------------------- CONSOLE
|
|
// setup loading screen
|
|
SCR_Startup();
|
|
|
|
// Do this in background; lots of number crunching
|
|
R_InitTranslucencyTables();
|
|
|
|
CON_SetLoadingProgress(LOADED_ISTARTUPGRAPHICS);
|
|
|
|
CONS_Printf("HU_Init()...\n");
|
|
HU_Init();
|
|
|
|
CON_Init();
|
|
|
|
memset(timelimits, 0, sizeof(timelimits));
|
|
memset(pointlimits, 0, sizeof(pointlimits));
|
|
|
|
CON_SetLoadingProgress(LOADED_HUINIT);
|
|
|
|
if (modifiedgame)
|
|
I_Error("modifiedgame set during startup!");
|
|
|
|
CONS_Printf("W_InitMultipleFiles(): Adding external PWADs.\n");
|
|
|
|
// HACK: Refer to https://git.do.srb2.org/KartKrew/RingRacers/-/merge_requests/29#note_61574
|
|
partadd_earliestfile = numwadfiles;
|
|
W_InitMultipleFiles(startuppwads, true);
|
|
|
|
// Only search for pwad maps and reload graphics if we actually have a pwad added
|
|
if (startuppwads[0] != NULL)
|
|
{
|
|
//
|
|
// search for pwad maps
|
|
//
|
|
P_InitMapData(true);
|
|
HU_LoadGraphics();
|
|
}
|
|
|
|
D_CleanFile(startuppwads);
|
|
partadd_earliestfile = UINT16_MAX;
|
|
|
|
CON_SetLoadingProgress(LOADED_PWAD);
|
|
|
|
//--------------------------------------------------------- CONFIG.CFG
|
|
M_FirstLoadConfig(); // WARNING : this do a "COM_BufExecute()"
|
|
|
|
VID_PrepareModeList(); // Regenerate Modelist according to cv_fullscreen
|
|
|
|
// set user default mode or mode set at cmdline
|
|
SCR_CheckDefaultMode();
|
|
|
|
if (M_CheckParm("-noupload"))
|
|
COM_BufAddText("downloading 0\n");
|
|
|
|
G_LoadGameData();
|
|
|
|
wipegamestate = gamestate;
|
|
|
|
savedata.lives = 0; // flag this as not-used
|
|
|
|
CON_SetLoadingProgress(LOADED_CONFIG);
|
|
|
|
CONS_Printf("R_InitTextureData()...\n");
|
|
R_InitTextureData(); // seperated out from below because it takes ages by itself
|
|
CON_SetLoadingProgress(LOADED_INITTEXTUREDATA);
|
|
|
|
CONS_Printf("R_InitSprites()...\n");
|
|
R_InitSprites(); // ditto
|
|
CON_SetLoadingProgress(LOADED_INITSPRITES);
|
|
|
|
CONS_Printf("R_InitSkins()...\n");
|
|
R_InitSkins(); // ditto
|
|
CON_SetLoadingProgress(LOADED_INITSKINS);
|
|
|
|
CONS_Printf("R_Init(): Init SRB2 refresh daemon.\n");
|
|
R_Init();
|
|
CON_SetLoadingProgress(LOADED_RINIT);
|
|
|
|
// setting up sound
|
|
if (dedicated)
|
|
{
|
|
sound_disabled = true;
|
|
digital_disabled = true;
|
|
}
|
|
|
|
if (M_CheckParm("-noaudio")) // combines -nosound and -nomusic
|
|
{
|
|
sound_disabled = true;
|
|
digital_disabled = true;
|
|
}
|
|
else
|
|
{
|
|
if (M_CheckParm("-nosound"))
|
|
sound_disabled = true;
|
|
if (M_CheckParm("-nomusic")) // combines -nomidimusic and -nodigmusic
|
|
{
|
|
digital_disabled = true;
|
|
}
|
|
else
|
|
{
|
|
if (M_CheckParm("-nodigmusic"))
|
|
digital_disabled = true; // WARNING: DOS version initmusic in I_StartupSound
|
|
}
|
|
}
|
|
|
|
if (!( sound_disabled && digital_disabled ))
|
|
{
|
|
CONS_Printf("S_InitSfxChannels(): Setting up sound channels.\n");
|
|
I_StartupSound();
|
|
I_InitMusic();
|
|
S_InitSfxChannels(cv_soundvolume.value);
|
|
S_InitMusicVolume();
|
|
}
|
|
CON_SetLoadingProgress(LOADED_SINITSFXCHANNELS);
|
|
|
|
S_InitMusicDefs();
|
|
|
|
M_InitEmotes();
|
|
|
|
CONS_Printf("ST_Init(): Init status bar.\n");
|
|
ST_Init();
|
|
CON_SetLoadingProgress(LOADED_STINIT);
|
|
|
|
CONS_Printf("FixedSqrt of 32767 fracunits: %d; FixedSqrt64: %" PRId64 "\n",
|
|
FixedSqrt(0x7FFF0000),
|
|
FixedSqrt64(0x7FFF0000));
|
|
|
|
if (FixedSqrt(0x7FFF0000) == FixedSqrt64(0x7FFF0000))
|
|
{
|
|
CONS_Printf("\x83" "32767: Test OK!" "\x80" "\n");
|
|
}
|
|
else
|
|
{
|
|
CONS_Printf("\x85" "32767: Test NG!" "\x80" "\n");
|
|
}
|
|
|
|
CONS_Printf("FixedSqrt of 4 fracunits: %d; FixedSqrt64: %" PRId64 "\n",
|
|
FixedSqrt(0x40000),
|
|
FixedSqrt64(0x40000));
|
|
|
|
if (FixedSqrt(0x40000) == FixedSqrt64(0x40000))
|
|
{
|
|
CONS_Printf("\x83" "4: Test OK!" "\x80" "\n");
|
|
}
|
|
else
|
|
{
|
|
CONS_Printf("\x85" "4: Test NG!" "\x80" "\n");
|
|
}
|
|
|
|
// You should probably generate the weird number with RANDOM.org
|
|
#define WEIRDNUMBER 3886284 /* 59.3 fracunits; this should approximate to around 7 */
|
|
CONS_Printf("FixedSqrt of 59.3 fracunits: %d; FixedSqrt64: %" PRId64 "\n",
|
|
FixedSqrt(WEIRDNUMBER),
|
|
FixedSqrt64(WEIRDNUMBER));
|
|
|
|
if (FixedSqrt(WEIRDNUMBER) == FixedSqrt64(WEIRDNUMBER))
|
|
{
|
|
CONS_Printf("\x83" "59.3: Test OK!" "\x80" "\n");
|
|
}
|
|
else
|
|
{
|
|
CONS_Printf("\x85" "59.3: Test NG!" "\x80" "\n");
|
|
}
|
|
#undef WEIRDNUMBER
|
|
|
|
CONS_Printf("FixedSqrt64 of 65535 fracunits: %" PRId64 "(not possible at 32-bit scale)\n",
|
|
FixedSqrt64(0xFFFFFFFF));
|
|
|
|
CONS_Printf("IntSqrt of 32767: %d; IntSqrt64: %" PRId64 "\n",
|
|
IntSqrt(0x7FFF),
|
|
IntSqrt64(0x7FFF));
|
|
|
|
CONS_Printf("IntSqrt of 4: %d; IntSqrt64: %" PRId64 "\n",
|
|
IntSqrt(4),
|
|
IntSqrt64(4));
|
|
|
|
CON_SetLoadingProgress(LOADED_MATHINIT);
|
|
|
|
CONS_Printf("ACS_Init(): Init Action Code Script VM.\n");
|
|
ACS_Init();
|
|
CON_SetLoadingProgress(LOADED_ACSINIT);
|
|
|
|
//------------------------------------------------ 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();
|
|
|
|
if (WADNAMECHECK(word))
|
|
{
|
|
if (!(pstartmap = wadnamemap))
|
|
I_Error("Bad '%s' level warp; wadnamemap is %d.\n"
|
|
#if (defined (_WIN32)) || (defined(__linux__))
|
|
"Are you using MSDOS 8.3 filenames in Zone Builder?\n"
|
|
"\n"
|
|
"To check: edit the BlanKart game configuration in Ultimate Lowee Builder.\n"
|
|
"Go to the Testing tab and make sure \"Use short paths and file names\" is turned off.\n"
|
|
"(The option is hidden by default. Check \"Customize parameters\" to show it.)\n"
|
|
"\n"
|
|
"If the above is correct, make sure you have an SOC file in your resources for this map."
|
|
#endif
|
|
, word, wadnamemap);
|
|
}
|
|
else
|
|
{
|
|
if (!(pstartmap = G_FindMapByNameOrCode(word, 0)))
|
|
I_Error("Cannot find a map remotely named '%s'\n", word);
|
|
}
|
|
|
|
if (!M_CheckParm("-server") && !M_CheckParm("-dedicated") && !M_CheckParm("-nograndprix"))
|
|
{
|
|
G_SetGameModified(true, true);
|
|
|
|
// Start up a "minor" grand prix session
|
|
memset(&grandprixinfo, 0, sizeof(struct grandprixinfo));
|
|
|
|
grandprixinfo.gamespeed = KARTSPEED_HARD;
|
|
grandprixinfo.encore = false;
|
|
grandprixinfo.masterbots = false;
|
|
grandprixinfo.lunaticmode = false;
|
|
|
|
grandprixinfo.gp = true;
|
|
grandprixinfo.roundnum = 0;
|
|
grandprixinfo.cup = NULL;
|
|
grandprixinfo.wonround = false;
|
|
|
|
grandprixinfo.initalize = true;
|
|
}
|
|
|
|
autostart = true;
|
|
}
|
|
|
|
// Set up splitscreen players before joining!
|
|
if (!dedicated && (M_CheckParm("-splitscreen") && M_IsNextParm()))
|
|
{
|
|
UINT8 num = atoi(M_GetNextParm());
|
|
if (num >= 1 && num <= 4)
|
|
{
|
|
CV_StealthSetValue(&cv_splitplayers, num);
|
|
splitscreen = num-1;
|
|
SplitScreen_OnChange();
|
|
}
|
|
}
|
|
|
|
// init all NETWORK
|
|
CONS_Printf("D_CheckNetGame(): Checking network game status.\n");
|
|
if (D_CheckNetGame())
|
|
autostart = true;
|
|
CON_SetLoadingProgress(LOADED_DCHECKNETGAME);
|
|
|
|
if (splitscreen && !M_CheckParm("-connect")) // Make sure multiplayer & autostart is set if you have splitscreen, even after D_CheckNetGame
|
|
multiplayer = 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(va("%s.lmp", M_GetNextParm()));
|
|
autostart = true;
|
|
}
|
|
|
|
// user settings come before "+" parameters.
|
|
if (dedicated)
|
|
COM_ImmedExecute(va("exec \"%s" PATHSEP "kartserv.cfg\"\n", srb2home));
|
|
else
|
|
COM_ImmedExecute(va("exec \"%s" PATHSEP "kartexec.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"))
|
|
{
|
|
demo.quitafterplaying = 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;
|
|
}*/
|
|
|
|
// rei/miru: bootmap (Idea: starts the game on a predefined map)
|
|
if (bootmap && !(M_CheckParm("-warp") && M_IsNextParm()))
|
|
{
|
|
pstartmap = G_MapNumber(bootmap)+1;
|
|
|
|
if (pstartmap > nummapheaders)
|
|
{
|
|
I_Error("Cannot warp to map %s (not found)\n", bootmap);
|
|
}
|
|
|
|
autostart = true;
|
|
}
|
|
|
|
if (autostart || netgame)
|
|
{
|
|
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
|
|
|
|
COM_BufExecute(); // ensure the command buffer gets executed before the map starts (+skin)
|
|
|
|
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
|
|
{
|
|
j = atoi(sgametype); // assume they gave us a gametype number, which is okay too
|
|
if (j >= 0 && j < gametypecount)
|
|
newgametype = (INT16)j;
|
|
}
|
|
|
|
if (newgametype != -1)
|
|
{
|
|
j = gametype;
|
|
G_SetGametype(newgametype);
|
|
D_GameTypeChanged(j);
|
|
}
|
|
}
|
|
|
|
if (M_CheckParm("-skill") && M_IsNextParm())
|
|
{
|
|
INT32 j;
|
|
INT16 newskill = -1;
|
|
const char *sskill = M_GetNextParm();
|
|
|
|
for (j = 0; gpdifficulty_cons_t[j].strvalue; j++)
|
|
{
|
|
if (!strcasecmp(gpdifficulty_cons_t[j].strvalue, sskill))
|
|
{
|
|
newskill = (INT16)gpdifficulty_cons_t[j].value;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!gpdifficulty_cons_t[j].strvalue) // reached end of the list with no match
|
|
{
|
|
j = atoi(sskill); // assume they gave us a skill number, which is okay too
|
|
if (j >= KARTSPEED_EASY && j <= KARTGP_MANIAC)
|
|
newskill = (INT16)j;
|
|
}
|
|
|
|
if (grandprixinfo.gp == true)
|
|
{
|
|
if (newskill == KARTGP_MASTER)
|
|
{
|
|
grandprixinfo.masterbots = true;
|
|
newskill = KARTSPEED_HARD;
|
|
}
|
|
else if (newskill == KARTGP_NIGHTMARE)
|
|
{
|
|
grandprixinfo.masterbots = true;
|
|
newskill = KARTSPEED_EXPERT;
|
|
}
|
|
else if (newskill == KARTGP_LUNATIC)
|
|
{
|
|
grandprixinfo.masterbots = true;
|
|
grandprixinfo.lunaticmode = true;
|
|
newskill = KARTSPEED_HARD;
|
|
}
|
|
else if (newskill == KARTGP_MANIAC)
|
|
{
|
|
grandprixinfo.masterbots = true;
|
|
grandprixinfo.lunaticmode = true;
|
|
newskill = KARTSPEED_EXPERT;
|
|
}
|
|
|
|
grandprixinfo.gamespeed = newskill;
|
|
}
|
|
else if (newskill == KARTGP_MASTER)
|
|
{
|
|
newskill = KARTSPEED_HARD;
|
|
}
|
|
else if (newskill == KARTGP_NIGHTMARE)
|
|
{
|
|
newskill = KARTSPEED_EXPERT;
|
|
}
|
|
else if (newskill == KARTGP_LUNATIC)
|
|
{
|
|
newskill = KARTSPEED_HARD;
|
|
}
|
|
else if (newskill == KARTGP_MANIAC)
|
|
{
|
|
newskill = KARTSPEED_EXPERT;
|
|
}
|
|
|
|
if (newskill != -1)
|
|
CV_SetValue(&cv_kartspeed, newskill);
|
|
}
|
|
|
|
if (server && !M_CheckParm("+map"))
|
|
{
|
|
// 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.
|
|
#if 0
|
|
if (!dedicated && M_MapLocked(pstartmap))
|
|
I_Error("You need to unlock this level before you can warp to it!\n");
|
|
else
|
|
#endif
|
|
{
|
|
D_MapChange(pstartmap, gametype, (cv_kartencore.value == 1), true, 0, false, false);
|
|
}
|
|
}
|
|
}
|
|
else if (M_CheckParm("-skipintro"))
|
|
{
|
|
F_InitMenuPresValues(true);
|
|
F_StartTitleScreen();
|
|
}
|
|
else
|
|
F_StartIntro(); // Tails 03-03-2002
|
|
|
|
CON_ToggleOff();
|
|
|
|
#ifdef HAVE_DISCORDRPC
|
|
if (! dedicated)
|
|
{
|
|
DRPC_Init();
|
|
}
|
|
#endif
|
|
|
|
if (con_startup_loadprogress != LOADED_ALLDONE)
|
|
{
|
|
I_Error("Something is wrong with the loading bar! (got %d, expected %d)\n", con_startup_loadprogress, LOADED_ALLDONE);
|
|
return;
|
|
}
|
|
}
|
|
|
|
const char *D_Home(void)
|
|
{
|
|
const char *userhome = NULL;
|
|
|
|
if (M_CheckParm("-home") && M_IsNextParm())
|
|
userhome = M_GetNextParm();
|
|
else
|
|
{
|
|
#if !((defined (__unix__) && !defined (MSDOS)) || defined(__APPLE__) || defined (UNIXCOMMON)) && !defined (__APPLE__)
|
|
if (FIL_FileOK(CONFIGFILENAME))
|
|
usehome = false; // Let's NOT use home
|
|
else
|
|
#endif
|
|
userhome = I_GetEnv("HOME"); //Alam: my new HOME for srb2
|
|
}
|
|
#ifdef _WIN32 //Alam: only Win32 have APPDATA and USERPROFILE
|
|
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;
|
|
}
|