RingRacers/src/d_main.c
2022-11-17 16:37:09 +00:00

1935 lines
46 KiB
C

// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// 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 "f_finale.h"
#include "g_game.h"
#include "hu_stuff.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 "k_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 "fastcmp.h"
#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"
// SRB2Kart
#include "k_grandprix.h"
#include "k_boss.h"
#include "doomstat.h"
#include "m_random.h" // P_ClearRandom
#include "k_specialstage.h"
#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 DEVELOP
UINT8 comprevision_abbrev_bin[GIT_SHA_ABBREV];
#endif
#ifdef HAVE_DISCORDRPC
#include "discord.h"
#endif
// platform independant focus loss
UINT8 window_notinfocus = false;
INT32 window_x;
INT32 window_y;
//
// 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;
postimg_t postimgtype[MAXSPLITSCREENPLAYERS];
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];
//
// 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.
//
// D_ProcessEvents
// Send all the events of the given timestamp down the responder chain
//
void D_ProcessEvents(void)
{
event_t *ev;
boolean eaten;
boolean menuresponse = false;
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 == DSM_TITLEENTRY)
{
if (G_DemoTitleResponder(ev))
continue;
}
// 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
}
// Menu input
menuresponse = true;
#ifdef HAVE_THREADS
I_lock_mutex(&k_menu_mutex);
#endif
{
eaten = M_Responder(ev);
}
#ifdef HAVE_THREADS
I_unlock_mutex(k_menu_mutex);
#endif
if (eaten)
continue; // menu ate the event
// Demo input:
/*
if (demo.playback)
if (M_DemoResponder(ev))
continue; // demo ate the event
*/
G_Responder(ev);
}
// Reset menu controls when no event is processed
if (!menuresponse)
{
M_MapMenuControls(NULL);
}
}
//
// 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;
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(VIEWCONTEXT_PLAYER1 + i);
R_InterpolateViewRollAngle(rendertimefrac);
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_titlescreen_toblack;
if (wipetypepre < 0 || !F_WipeExists(wipetypepre))
wipetypepre = wipedefs[wipedefindex];
if (rendermode != render_none)
{
// Fade to black first
if (!(gamestate == GS_LEVEL || (gamestate == GS_TITLESCREEN && titlemapinaction)) // fades to black on its own timing, always
&& wipetypepre != UINT8_MAX)
{
F_WipeStartScreen();
F_WipeColorFill(31);
F_WipeEndScreen();
F_RunWipe(wipetypepre, gamestate != GS_MENU, "FADEMAP0", false, false);
}
if (gamestate != GS_LEVEL && rendermode != render_none)
{
V_SetPaletteLump("PLAYPAL"); // Reset the palette
R_ReInitColormaps(0, NULL, 0);
}
F_WipeStartScreen();
}
else //dedicated servers
{
F_RunWipe(wipedefs[wipedefindex], gamestate != GS_MENU, "FADEMAP0", false, false);
wipegamestate = gamestate;
}
wipetypepre = -1;
}
else
wipetypepre = -1;
if (dedicated) //bail out after wipe logic
return;
// do buffered drawing
switch (gamestate)
{
case GS_TITLESCREEN:
if (!titlemapinaction || !curbghide) {
F_TitleScreenDrawer();
break;
}
/* FALLTHRU */
case GS_LEVEL:
if (!gametic)
break;
HU_Erase();
AM_Drawer();
break;
case GS_INTERMISSION:
Y_IntermissionDrawer();
HU_Erase();
HU_Drawer();
break;
case GS_VOTING:
Y_VoteDrawer();
HU_Erase();
HU_Drawer();
break;
case GS_MENU:
break;
case GS_INTRO:
F_IntroDrawer();
if (wipegamestate == (gamestate_t)-1)
{
wipe = true;
wipedefindex = gamestate; // wipe_xxx_toblack
}
break;
case GS_ENDING:
F_EndingDrawer();
HU_Erase();
HU_Drawer();
break;
case GS_CUTSCENE:
F_CutsceneDrawer();
HU_Erase();
HU_Drawer();
break;
case GS_GAMEEND:
F_GameEndDrawer();
break;
case GS_EVALUATION:
F_GameEvaluationDrawer();
HU_Erase();
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...
if (netgame)
{
// I don't think HOM from nothing drawing is independent...
F_WaitingPlayersDrawer();
HU_Erase();
HU_Drawer();
}
case GS_DEDICATEDSERVER:
case GS_NULL:
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)))
{
if (!automapactive && !dedicated && cv_renderview.value)
{
R_ApplyLevelInterpolators(R_UsingFrameInterpolation() ? rendertimefrac : FRACUNIT);
viewwindowy = 0;
viewwindowx = 0;
topleft = screens[0] + viewwindowy*vid.width + viewwindowx;
objectsdrawn = 0;
ps_rendercalltime = I_GetPreciseTime();
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_soft)
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;
}
M_Memcpy(ylookup, ylookup2, viewheight*sizeof (ylookup[0]));
break;
case 2:
viewwindowx = 0;
viewwindowy = viewheight;
M_Memcpy(ylookup, ylookup3, viewheight*sizeof (ylookup[0]));
break;
case 3:
viewwindowx = viewwidth;
viewwindowy = viewheight;
M_Memcpy(ylookup, ylookup4, viewheight*sizeof (ylookup[0]));
default:
break;
}
topleft = screens[0] + viewwindowy*vid.width + viewwindowx;
}
R_RenderPlayerView();
if (i > 0)
M_Memcpy(ylookup, ylookup1, viewheight*sizeof (ylookup[0]));
}
}
}
if (rendermode == render_soft)
{
for (i = 0; i <= r_splitscreen; i++)
{
R_ApplyViewMorph(i);
if (postimgtype[i])
V_DoPostProcessor(i, postimgtype[i], postimgparam[i]);
}
}
ps_rendercalltime = I_GetPreciseTime() - ps_rendercalltime;
R_RestoreLevelInterpolators();
}
if (lastdraw)
{
if (rendermode == render_soft)
{
VID_BlitLinearScreen(screens[0], screens[1], vid.width*vid.bpp, vid.height, vid.width*vid.bpp, vid.rowbytes);
}
lastdraw = false;
}
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 && !(gamestate == GS_LEVEL || (gamestate == GS_TITLESCREEN && titlemapinaction)))
V_SetPalette(0);
// draw pause pic
if (paused && cv_showhud.value && !demo.playback)
{
#if 0
INT32 py;
patch_t *patch;
if (automapactive)
py = 4;
else
py = viewwindowy + 4;
patch = W_CachePatchName("M_PAUSE", PU_PATCH);
V_DrawScaledPatch(viewwindowx + (BASEVIDWIDTH - 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
}
if (demo.rewinding)
V_DrawFadeScreen(TC_RAINBOW, (leveltime & 0x20) ? SKINCOLOR_PASTEL : SKINCOLOR_MOONSET);
// vid size change is now finished if it was on...
vid.recalc = 0;
#ifdef HAVE_THREADS
I_lock_mutex(&k_menu_mutex);
#endif
M_Drawer(); // menu is drawn even on top of everything
#ifdef HAVE_THREADS
I_unlock_mutex(k_menu_mutex);
#endif
// focus lost moved to M_Drawer
CON_Drawer();
ps_uitime = I_GetPreciseTime() - ps_uitime;
//
// wipe update
//
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(wipedefs[wipedefindex], gamestate != GS_MENU && gamestate != GS_TITLESCREEN, "FADEMAP0", true, false);
}
// reset counters so timedemo doesn't count the wipe duration
if (demo.timing)
{
framecount = 0;
demostarttime = I_GetTime();
}
wipetypepost = -1;
}
else
wipetypepost = -1;
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;
}
}
// =========================================================================
// D_SRB2Loop
// =========================================================================
tic_t rendergametic;
void D_SRB2Loop(void)
{
tic_t entertic = 0, oldentertics = 0, realtics = 0, rendertimeout = INFTICS;
double deltatics = 0.0;
double deltasecs = 0.0;
boolean interp = false;
boolean doDisplay = false;
if (dedicated)
server = true;
// 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.
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 (;;)
{
// capbudget is the minimum precise_t duration of a single loop iteration
precise_t capbudget;
precise_t enterprecise = I_GetPreciseTime();
precise_t finishprecise = enterprecise;
{
// Casting the return value of a function is bad practice (apparently)
double budget = round((1.0 / R_GetFramerateCap()) * I_GetPrecisePrecision());
capbudget = (precise_t) budget;
}
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 = R_UsingFrameInterpolation() && !dedicated;
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)
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 (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;
}
else
{
renderisnewtic = false;
}
if (interp)
{
renderdeltatics = FLOAT_TO_FIXED(deltatics);
if (!(paused || P_AutoPause()) && !hu_stopped)
{
rendertimefrac = g_time.timefrac;
}
else
{
rendertimefrac = FRACUNIT;
}
}
else
{
renderdeltatics = realtics * FRACUNIT;
rendertimefrac = FRACUNIT;
}
if (interp || doDisplay)
{
D_Display();
}
// 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 (realtics > 0 || singletics)
S_UpdateClosedCaptions();
#ifdef HW3SOUND
HW3S_EndFrameUpdate();
#endif
LUA_Step();
#ifdef HAVE_DISCORDRPC
if (! dedicated)
{
Discord_RunCallbacks();
}
#endif
// Fully completed frame made.
finishprecise = I_GetPreciseTime();
if (!singletics)
{
INT64 elapsed = (INT64)(finishprecise - enterprecise);
// 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 && (INT64)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();
deltatics = deltasecs * NEWTICRATE;
}
}
// =========================================================================
// D_SRB2Main
// =========================================================================
//
// D_StartTitle
//
void D_StartTitle(void)
{
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!)
SV_StopServer();
SV_ResetServer();
for (i = 0; i < MAXPLAYERS; i++)
CL_ClearPlayer(i);
for (i = 0; i < MAXSPLITSCREENPLAYERS; i++)
{
players[g_localplayers[i]].availabilities = R_GetSkinAvailabilities();
}
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 = 0;
// Reset GP
memset(&grandprixinfo, 0, sizeof(struct grandprixinfo));
// 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;
// clear cmd building stuff
memset(gamekeydown, 0, sizeof (gamekeydown));
memset(deviceResponding, false, sizeof (deviceResponding));
F_StartTitleScreen();
M_ClearMenus(false);
// Reset the palette
if (rendermode != render_none)
V_SetPaletteLump("PLAYPAL");
// The title screen is obviously not a tutorial! (Unless I'm mistaken)
tutorialmode = false;
}
//
// D_AddFile
//
static void D_AddFile(char **list, const char *file)
{
size_t pnumwadfiles;
char *newfile;
for (pnumwadfiles = 0; list[pnumwadfiles]; pnumwadfiles++)
;
newfile = malloc(strlen(file) + 1);
if (!newfile)
{
I_Error("No more free memory to AddFile %s",file);
}
strcpy(newfile, file);
list[pnumwadfiles] = newfile;
}
static inline 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,"main.kart");
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 'main.kart' is found
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 = ".";
}
// Load the IWAD
if (! AddIWAD())
{
I_Error("MAIN.KART not found! Expected in %s\n", 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 md5
// checking in D_SRB2Main
#if defined (TESTERS) || defined (HOSTTESTERS)
////
#define TEXTURESNAME "MISC_TEXTURES.pk3"
#define MAPSNAME "MISC_MAPS.pk3"
#define PATCHNAME "MISC_PATCH.pk3"
#define MUSICNAME "MISC_MUSIC.PK3"
////
#else
////
#define TEXTURESNAME "textures.pk3"
#define MAPSNAME "maps.pk3"
#define PATCHNAME "patch.pk3"
#define MUSICNAME "music.pk3"
////
#endif
////
#if !defined (TESTERS) && !defined (HOSTTESTERS)
D_AddFile(startupiwads, va(pandf,srb2waddir,"gfx.pk3"));
#endif
D_AddFile(startupiwads, va(pandf,srb2waddir,TEXTURESNAME));
D_AddFile(startupiwads, va(pandf,srb2waddir,"chars.pk3"));
D_AddFile(startupiwads, va(pandf,srb2waddir,MAPSNAME));
D_AddFile(startupiwads, va(pandf,srb2waddir,"followers.pk3"));
#ifdef USE_PATCH_FILE
D_AddFile(startupiwads, va(pandf,srb2waddir,PATCHNAME));
#endif
////
#undef TEXTURESNAME
#undef MAPSNAME
#undef PATCHNAME
#if !defined (HAVE_SDL) || defined (HAVE_MIXER)
#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("sounds.pk3")
MUSICTEST(MUSICNAME)
#undef MUSICNAME
#undef MUSICTEST
#endif
}
#ifdef DEVELOP
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
}
//
// D_SRB2Main
//
void D_SRB2Main(void)
{
INT32 i, p;
INT32 pstartmap = 1;
boolean autostart = false;
/* break the version string into version numbers, for netplay */
D_ConvertVersionNumbers();
#ifdef DEVELOP
D_AbbrevCommit();
#endif
// Print GPL notice for our console users (Linux)
CONS_Printf(
"\n\nDr. Robotnik's Ring Racers\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();
// This will be done more properly on
// level load, but for now at least make
// sure that it is initalized at all
P_ClearRandom(0);
// 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_TableCheck();
// 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;
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
// Init the joined IP table for quick rejoining of past games.
M_InitJoinedIPArray();
{
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);
}
}
M_LoadJoinedIPs(); // load joined ips
// 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, "downloads");
// rand() needs seeded regardless of password
srand((unsigned int)time(NULL));
rand();
rand();
rand();
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();
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();
#ifndef TESTERS
// 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);
}
}
}
#endif
// 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();
//---------------------------------------------------- READY TIME
// we need to check for dedicated before initialization of some subsystems
CONS_Printf("I_InitializeTime()...\n");
I_InitializeTime();
CON_SetLoadingProgress(LOADED_ISTARTUPTIMER);
// Make backups of some SOCcable tables.
P_BackupTables();
// load wad, including the main wad file
CONS_Printf("W_InitMultipleFiles(): Adding IWAD and main PWADs.\n");
W_InitMultipleFiles(startupiwads, false);
D_CleanFile(startupiwads);
mainwads = 0;
#ifndef DEVELOP
// Check MD5s of autoloaded files
// Note: Do not add any files that ignore MD5!
W_VerifyFileMD5(mainwads, ASSET_HASH_MAIN_KART); // main.kart
mainwads++; W_VerifyFileMD5(mainwads, ASSET_HASH_GFX_PK3); // gfx.pk3
mainwads++; W_VerifyFileMD5(mainwads, ASSET_HASH_TEXTURES_PK3); // textures.pk3
mainwads++; W_VerifyFileMD5(mainwads, ASSET_HASH_CHARS_PK3); // chars.pk3
mainwads++; W_VerifyFileMD5(mainwads, ASSET_HASH_MAPS_PK3); // maps.pk3 -- 4 - If you touch this, make sure to touch up the majormods stuff below.
mainwads++; W_VerifyFileMd5(mainwads, ASSET_HASH_FOLLOWERS_PK3); // followers.pk3
#ifdef USE_PATCH_FILE
mainwads++; W_VerifyFileMD5(mainwads, ASSET_HASH_PATCH_PK3); // patch.pk3
#endif
#else
#if !defined (TESTERS) && !defined (HOSTTESTERS)
mainwads++; // gfx.pk3
#endif
mainwads++; // textures.pk3
mainwads++; // chars.pk3
mainwads++; // maps.pk3
mainwads++; // followers.pk3
#ifdef USE_PATCH_FILE
mainwads++; // patch.pk3
#endif
#endif //ifndef DEVELOP
// Do it before P_InitMapData because PNG patch
// conversion sometimes needs the palette
V_ReloadPalette();
//
// search for mainwad maps
//
P_InitMapData(false);
CON_SetLoadingProgress(LOADED_IWAD);
CONS_Printf("W_InitMultipleFiles(): Adding external PWADs.\n");
W_InitMultipleFiles(startuppwads, true);
D_CleanFile(startuppwads);
//
// search for pwad maps
//
P_InitMapData(true);
CON_SetLoadingProgress(LOADED_PWAD);
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
//--------------------------------------------------------- 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();
CON_SetLoadingProgress(LOADED_HUINIT);
memset(timelimits, 0, sizeof(timelimits));
memset(pointlimits, 0, sizeof(pointlimits));
timelimits[GT_BATTLE] = 2;
D_RegisterServerCommands();
D_RegisterClientCommands(); // be sure that this is called before D_CheckNetGame
R_RegisterEngineStuff();
S_RegisterSoundStuff();
I_RegisterSysCommands();
//--------------------------------------------------------- CONFIG.CFG
M_FirstLoadConfig(); // WARNING : this do a "COM_BufExecute()"
// Load Profiles now that default controls have been defined
PR_LoadProfiles(); // load control profiles
M_Init();
#if (defined (__unix__) && !defined (MSDOS)) || defined (UNIXCOMMON) || defined (HAVE_SDL)
VID_PrepareModeList(); // Regenerate Modelist according to cv_fullscreen
#endif
// set user default mode or mode set at cmdline
SCR_CheckDefaultMode();
if (M_CheckParm("-noupload"))
COM_BufAddText("downloading 0\n");
if (M_CheckParm("-gamedata") && M_IsNextParm())
{
// Moved from G_LoadGameData itself, as it would cause some crazy
// confusion issues when loading mods.
strlcpy(gamedatafilename, M_GetNextParm(), sizeof gamedatafilename);
}
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);
}
CON_SetLoadingProgress(LOADED_SINITSFXCHANNELS);
S_InitMusicDefs();
CONS_Printf("ST_Init(): Init status bar.\n");
ST_Init();
CON_SetLoadingProgress(LOADED_STINIT);
//------------------------------------------------ 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.\n"
#if defined (_WIN32)
"Are you using MSDOS 8.3 filenames in Zone Builder?\n"
#endif
, word);
}
else
{
if (!(pstartmap = G_FindMapByNameOrCode(word, 0)))
I_Error("Cannot find a map remotely named '%s'\n", word);
}
{
if (!M_CheckParm("-server"))
{
G_SetUsedCheats();
// Start up a "minor" grand prix session
memset(&grandprixinfo, 0, sizeof(struct grandprixinfo));
grandprixinfo.gamespeed = KARTSPEED_NORMAL;
grandprixinfo.encore = false;
grandprixinfo.masterbots = 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(M_GetNextParm());
autostart = true;
}
// user settings come before "+" parameters.
if (dedicated)
COM_ImmedExecute(va("exec \"%s"PATHSEP"ringserv.cfg\"\n", srb2home));
else
COM_ImmedExecute(va("exec \"%s"PATHSEP"ringexec.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;
}
// Has to be done before anything else so skin, color, etc in command buffer has an affect.
// ttlprofilen used because it's roughly equivalent in functionality - a QoL aid for quickly getting from startup to action
PR_ApplyProfile(cv_ttlprofilen.value, 0);
for (i = 1; i < cv_splitplayers.value; i++)
{
PR_ApplyProfile(cv_lastprofile[i].value, i);
}
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)
strncpy(connectedservername, cv_servername.string, MAXSERVERNAME);
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_MASTER)
newskill = (INT16)j;
}
if (grandprixinfo.gp == true)
{
if (newskill == KARTGP_MASTER)
{
grandprixinfo.masterbots = true;
newskill = KARTSPEED_HARD;
}
grandprixinfo.gamespeed = newskill;
}
else if (newskill == KARTGP_MASTER)
{
newskill = KARTSPEED_HARD;
}
if (newskill != -1)
CV_SetValue(&cv_kartspeed, newskill);
}
if (server && !M_CheckParm("+map"))
{
if (M_MapLocked(pstartmap))
{
G_SetUsedCheats();
}
D_MapChange(pstartmap, gametype, (cv_kartencore.value == 1), true, 0, false, false);
}
}
else if (M_CheckParm("-skipintro"))
{
F_StartTitleScreen();
CV_StealthSetValue(&cv_currprofile, -1);
}
else
{
F_StartIntro(); // Tails 03-03-2002
CV_StealthSetValue(&cv_currprofile, -1);
}
CON_ToggleOff();
if (dedicated && server)
{
levelstarttic = gametic;
G_SetGamestate(GS_LEVEL);
if (!P_LoadLevel(false, false))
I_Quit(); // fail so reset game stuff
}
#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;
#ifdef ANDROID
return "/data/data/org.srb2/";
#endif
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;
}