/* =========================================================================== Doom 3 BFG Edition GPL Source Code Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Doom 3 BFG Edition Source Code. If not, see . In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. =========================================================================== */ #include "Precompiled.h" #include "globaldata.h" #include "Main.h" #include "sys/sys_signin.h" #include "d3xp/Game_local.h" #include #include #include "doomdef.h" #include "doomstat.h" #include "z_zone.h" #include "f_finale.h" #include "m_argv.h" #include "m_misc.h" #include "m_menu.h" #include "m_random.h" #include "i_system.h" #include "p_setup.h" #include "p_saveg.h" #include "p_tick.h" #include "d_main.h" #include "wi_stuff.h" #include "hu_stuff.h" #include "st_stuff.h" #include "am_map.h" // Needs access to LFB. #include "v_video.h" #include "w_wad.h" #include "p_local.h" #include "s_sound.h" // Data. #include "dstrings.h" #include "sounds.h" // SKY handling - still the wrong place. #include "r_data.h" #include "r_sky.h" #include "g_game.h" #include "framework/Common.h" #include "sys/sys_lobby.h" #include extern bool waitingForWipe; bool loadingGame = false; byte demoversion = 0; qboolean G_CheckDemoStatus (void); void G_ReadDemoTiccmd (ticcmd_t* cmd); void G_WriteDemoTiccmd (ticcmd_t* cmd); void G_PlayerReborn (int player); void G_InitNew (skill_t skill, int episode, int map ); void G_DoReborn (int playernum); void G_DoLoadLevel (); void G_DoNewGame (void); qboolean G_DoLoadGame (); void G_DoPlayDemo (void); void G_DoCompleted (void); void G_DoVictory (void); void G_DoWorldDone (void); qboolean G_DoSaveGame (void); #define DEBUG_DEMOS #define DEBUG_DEMOS_WRITE #ifdef DEBUG_DEMOS unsigned char testprndindex = 0; int printErrorCount = 0; bool demoDebugOn = false; #endif // // controls (have defaults) // // mouse values are used once // joystick values are repeated int G_CmdChecksum (ticcmd_t* cmd) { int i; int sum = 0; for (i=0 ; i< sizeof(*cmd)/4 - 1 ; i++) sum += ((int *)cmd)[i]; return sum; } // jedi academy meets doom hehehehehehehe void G_MouseClamp(int *x, int *y) { float ax = (float)fabs((float)*x); float ay = (float)fabs((float)*y); ax = (ax-10)*(0.04676) * (ax-10) * (ax > 10); ay = (ay-10)*(0.04676) * (ay-10) * (ay > 10); if (*x < 0) *x = static_cast(-ax); else *x = static_cast(ax); if (*y < 0) *y = static_cast(-ay); else *y = static_cast(ay); } /* ======================== Returns true if the player is holding down the run button, or if they have set "Always run" in the options. Returns false otherwise. ======================== */ bool IsPlayerRunning( const usercmd_t & command ) { if( DoomLib::GetPlayer() < 0 ) { return false; } // DHM - Nerve :: Always Run setting idLocalUser * user = session->GetSignInManager().GetLocalUserByIndex( DoomLib::GetPlayer() ); bool autorun = false; // TODO: PC #if 0 if( user ) { idPlayerProfileDoom * profile = static_cast< idPlayerProfileDoom * >( user->GetProfile() ); if( profile && profile->GetAlwaysRun() ) { autorun = true; } } #endif if ( command.buttons & BUTTON_RUN ) { return !autorun; } return autorun; } /* ======================== G_PerformImpulse ======================== */ void G_PerformImpulse( const int impulse, ticcmd_t* cmd ) { if( impulse == IMPULSE_15 ) { cmd->buttons |= BT_CHANGE; cmd->nextPrevWeapon = 1 ; } else if( impulse == IMPULSE_14 ) { cmd->buttons |= BT_CHANGE; cmd->nextPrevWeapon = 2 ; } } /* ======================== Converts a degree value to DOOM format angle value. ======================== */ fixed_t DegreesToDoomAngleTurn( float degrees ) { const float anglefrac = degrees / 360.0f; const fixed_t doomangle = anglefrac * std::numeric_limits::max(); return doomangle; } // // G_BuildTiccmd // Builds a ticcmd from all of the available inputs // or reads it from the demo buffer. // If recording a demo, write it out // void G_BuildTiccmd (ticcmd_t* cmd, idUserCmdMgr * userCmdMgr, int newTics ) { int i; int speed; int tspeed; int forward; int side; ticcmd_t* base; base = I_BaseTiccmd (); // empty, or external driver memcpy (cmd,base,sizeof(*cmd)); cmd->consistancy = ::g->consistancy[::g->consoleplayer][::g->maketic%BACKUPTICS]; // Grab the tech5 tic so we can convert it to a doom tic. if ( userCmdMgr != NULL ) { const int playerIndex = DoomLib::GetPlayer(); if( playerIndex < 0 ) { return; } #ifdef ID_ENABLE_NETWORKING const int lobbyIndex = gameLocal->GetLobbyIndexFromDoomLibIndex( playerIndex ); const idLocalUser * const localUser = session->GetGameLobbyBase().GetLocalUserFromLobbyUser( lobbyIndex ); #else const int lobbyIndex = 0; const idLocalUser * const localUser = session->GetSignInManager().GetMasterLocalUser(); #endif if ( localUser == NULL ) { return; } usercmd_t * tech5commands[2] = { 0, 0 }; const int numCommands = userCmdMgr->GetPlayerCmds( lobbyIndex, tech5commands, 2 ); usercmd_t prevTech5Command; usercmd_t curTech5Command; // Use default commands if the manager didn't have enough. if ( numCommands == 1 ) { curTech5Command = *(tech5commands)[0]; } if ( numCommands == 2 ) { prevTech5Command = *(tech5commands)[0]; curTech5Command = *(tech5commands)[1]; } const bool isRunning = IsPlayerRunning( curTech5Command ); // tech5 move commands range from -127 o 127. Scale to doom range of -25 to 25. const float scaledForward = curTech5Command.forwardmove / 127.0f; if ( isRunning ) { cmd->forwardmove = scaledForward * 50.0f; } else { cmd->forwardmove = scaledForward * 25.0f; } // tech5 move commands range from -127 o 127. Scale to doom range of -24 to 24. const float scaledSide = curTech5Command.rightmove / 127.0f; if ( isRunning ) { cmd->sidemove = scaledSide * 40.0f; } else { cmd->sidemove = scaledSide * 24.0f; } idAngles angleDelta; angleDelta.pitch = SHORT2ANGLE( curTech5Command.angles[ 0 ] ) - SHORT2ANGLE( prevTech5Command.angles[ 0 ] ); angleDelta.yaw = SHORT2ANGLE( curTech5Command.angles[ 1 ] ) - SHORT2ANGLE( prevTech5Command.angles[ 1 ] ); angleDelta.roll = 0.0f; angleDelta.Normalize180(); // We will be running a number of tics equal to newTics before we get a new command from tech5. // So to keep input smooth, divide the angles between all the newTics. if ( newTics > 0 ) { angleDelta.yaw /= newTics; } // idAngles is stored in degrees. Convert to doom format. cmd->angleturn = DegreesToDoomAngleTurn( angleDelta.yaw ); // Translate buttons //if ( curTech5Command.inhibited == false ) { // Attack 1 attacks always, whether in the automap or not. if ( curTech5Command.buttons & BUTTON_ATTACK ) { cmd->buttons |= BT_ATTACK; } #if 0 // Attack 2 only attacks if not in the automap, because when in the automap, // it is the zoom function. if ( curTech5Command.buttons & BUTTON_ATTACK2 ) { if ( !::g->automapactive ) { cmd->buttons |= BT_ATTACK; } } #endif // Try to read any impulses that have happened. static int oldImpulseSequence = 0; if( oldImpulseSequence != curTech5Command.impulseSequence ) { G_PerformImpulse( curTech5Command.impulse, cmd ); } oldImpulseSequence = curTech5Command.impulseSequence; // weapon toggle for (i=0 ; iKeyState( i + 1 ) ) { cmd->buttons |= BT_CHANGE; cmd->buttons |= (i - 1) <buttons |= BT_USE; } // TODO: PC #if 0 if ( curTech5Command.buttons & BUTTON_WEAP_NEXT ) { cmd->buttons |= BT_CHANGE; cmd->buttons |= 1 << BT_WEAPONSHIFT; } if ( curTech5Command.buttons & BUTTON_WEAP_PREV ) { cmd->buttons |= BT_CHANGE; cmd->buttons |= 0 << BT_WEAPONSHIFT; } if( curTech5Command.buttons & BUTTON_WEAP_0 ) { cmd->buttons |= BT_CHANGE; cmd->buttons |= 2 << BT_WEAPONSHIFT; } if( curTech5Command.buttons & BUTTON_WEAP_1 ) { cmd->buttons |= BT_CHANGE; cmd->buttons |= 3 << BT_WEAPONSHIFT; } if( curTech5Command.buttons & BUTTON_WEAP_2 ) { cmd->buttons |= BT_CHANGE; cmd->buttons |= 4 << BT_WEAPONSHIFT; } if( curTech5Command.buttons & BUTTON_WEAP_3 ) { cmd->buttons |= BT_CHANGE; cmd->buttons |= 5 << BT_WEAPONSHIFT; } #endif //} return; } // DHM - Nerve :: Always Run setting idLocalUser * user = session->GetSignInManager().GetLocalUserByIndex( DoomLib::GetPlayer() ); if( user ) { // TODO: PC #if 0 idPlayerProfileDoom * profile = static_cast< idPlayerProfileDoom * >( user->GetProfile() ); if( profile && profile->GetAlwaysRun() ) { speed = !::g->gamekeydown[::g->key_speed]; } else #endif { speed = ::g->gamekeydown[::g->key_speed]; } } else { // Should not happen. speed = !::g->gamekeydown[::g->key_speed]; } forward = side = 0; // use two stage accelerative turning // on the keyboard and joystick if (/*:g->joyxmove != 0 ||*/ ::g->gamekeydown[::g->key_right] || ::g->gamekeydown[::g->key_left] || ::g->mousex != 0) ::g->turnheld += ::g->ticdup; else ::g->turnheld = 0; if (::g->turnheld < SLOWTURNTICS) tspeed = 2; // slow turn else tspeed = speed; // clamp for turning int mousex = ::g->mousex; int mousey = ::g->mousey; G_MouseClamp( &mousex, &mousey ); if (::g->gamekeydown[::g->key_right] /*|| ::g->joyxmove > 0*/) cmd->angleturn -= ::g->angleturn[tspeed]; else if (::g->mousex > 0) { cmd->angleturn -= tspeed == 1 ? 2 * mousex : mousex; } if (::g->gamekeydown[::g->key_left] /*|| ::g->joyxmove < 0*/) cmd->angleturn += ::g->angleturn[tspeed]; else if (::g->mousex < 0) { cmd->angleturn += tspeed == 1 ? -2 * mousex : -mousex; } if (::g->mousey > 0 || ::g->mousey < 0) { //forward += ::g->forwardmove[speed]; forward += speed == 1 ? 2 * ::g->mousey : ::g->mousey; } /* if (::g->mousey < 0) { forward -= ::g->forwardmove[speed]; } */ /* if (::g->gamekeydown[::g->key_straferight]) side += ::g->sidemove[speed]; if (::g->gamekeydown[::g->key_strafeleft]) side -= ::g->sidemove[speed]; */ if ( ::g->joyxmove > 0 || ::g->joyxmove < 0 ) { side += speed == 1 ? 2 * ::g->joyxmove : ::g->joyxmove; } // buttons if (::g->gamekeydown[::g->key_fire] || ::g->mousebuttons[::g->mousebfire] || ::g->joybuttons[::g->joybfire]) cmd->buttons |= BT_ATTACK; if (::g->gamekeydown[::g->key_use] || ::g->joybuttons[::g->joybuse] ) cmd->buttons |= BT_USE; // DHM - Nerve :: In the intermission or finale screens, make START also create a 'use' command. if ( (::g->gamestate == GS_INTERMISSION || ::g->gamestate == GS_FINALE) && ::g->gamekeydown[KEY_ESCAPE] ) { cmd->buttons |= BT_USE; } // weapon toggle for (i=0 ; igamekeydown['1'+i]) { cmd->buttons |= BT_CHANGE; cmd->buttons |= i<mousex = ::g->mousey = 0; if (forward > MAXPLMOVE) forward = MAXPLMOVE; else if (forward < -MAXPLMOVE) forward = -MAXPLMOVE; if (side > MAXPLMOVE) side = MAXPLMOVE; else if (side < -MAXPLMOVE) side = -MAXPLMOVE; cmd->forwardmove += forward; cmd->sidemove += side; // special buttons if (::g->sendpause) { ::g->sendpause = false; cmd->buttons = BT_SPECIAL | BTS_PAUSE; } if (::g->sendsave) { ::g->sendsave = false; cmd->buttons = BT_SPECIAL | BTS_SAVEGAME | (::g->savegameslot<skyflatnum = R_FlatNumForName ( SKYFLATNAME ); // DOOM determines the sky texture to be used // depending on the current episode, and the game version. if ( ::g->gamemode == commercial ) { ::g->skytexture = R_TextureNumForName ("SKY3"); if (::g->gamemap < 12) { ::g->skytexture = R_TextureNumForName ("SKY1"); } else if (::g->gamemap < 21) { ::g->skytexture = R_TextureNumForName ("SKY2"); } } ::g->levelstarttic = ::g->gametic; // for time calculation if (::g->wipegamestate == GS_LEVEL) { ::g->wipegamestate = (gamestate_t)-1; // force a wipe } else if ( ::g->netgame ) { ::g->wipegamestate = GS_LEVEL; } ::g->gamestate = GS_LEVEL; for (i=0 ; iplayeringame[i] && ::g->players[i].playerstate == PST_DEAD) ::g->players[i].playerstate = PST_REBORN; memset (::g->players[i].frags,0,sizeof(::g->players[i].frags)); memset (&(::g->players[i].cmd),0,sizeof(::g->players[i].cmd)); } const char * difficultyNames[] = { "I'm Too Young To Die!", "Hey, Not Too Rough!", "Hurt Me Plenty!", "Ultra-Violence", "Nightmare" }; const ExpansionData * expansion = DoomLib::GetCurrentExpansion(); int truemap = ::g->gamemap; if( ::g->gamemission == doom ) { truemap = ( ::g->gameepisode - 1 ) * 9 + ( ::g->gamemap ); } idMatchParameters newParms = session->GetActingGameStateLobbyBase().GetMatchParms(); DoomLib::SetCurrentMapName( expansion->mapNames[ truemap - 1 ] ); DoomLib::SetCurrentDifficulty( difficultyNames[ ::g->gameskill ] ); P_SetupLevel (::g->gameepisode, ::g->gamemap, 0, ::g->gameskill); ::g->displayplayer = ::g->consoleplayer; // view the guy you are playing ::g->starttime = I_GetTime (); ::g->gameaction = ga_nothing; // clear cmd building stuff memset (::g->gamekeydown, 0, sizeof(::g->gamekeydown)); ::g->joyxmove = ::g->joyymove = 0; ::g->mousex = ::g->mousey = 0; ::g->sendpause = ::g->sendsave = ::g->paused = false; memset (::g->mousebuttons, 0, sizeof(::g->mousebuttons)); memset (::g->joybuttons, 0, sizeof(::g->joybuttons)); } // // G_Responder // Get info needed to make ticcmd_ts for the ::g->players. // qboolean G_Responder (event_t* ev) { // allow spy mode changes even during the demo if (::g->gamestate == GS_LEVEL && ev->type == ev_keydown && ev->data1 == KEY_F12 && (::g->singledemo || !::g->deathmatch) ) { // spy mode do { ::g->displayplayer++; if (::g->displayplayer == MAXPLAYERS) ::g->displayplayer = 0; } while (!::g->playeringame[::g->displayplayer] && ::g->displayplayer != ::g->consoleplayer); return true; } // any other key pops up menu if in demos if (::g->gameaction == ga_nothing && !::g->singledemo && (::g->demoplayback || ::g->gamestate == GS_DEMOSCREEN) ) { if (ev->type == ev_keydown || (ev->type == ev_mouse && ev->data1) || (ev->type == ev_joystick && ev->data1) ) { M_StartControlPanel (); return true; } return false; } if (::g->gamestate == GS_LEVEL && ( ::g->usergame || ::g->netgame || ::g->demoplayback )) { #if 0 if (::g->devparm && ev->type == ev_keydown && ev->data1 == ';') { G_DeathMatchSpawnPlayer (0); return true; } #endif if (HU_Responder (ev)) return true; // chat ate the event if (ST_Responder (ev)) return true; // status window ate it if (AM_Responder (ev)) return true; // automap ate it } if (::g->gamestate == GS_FINALE) { if (F_Responder (ev)) return true; // finale ate the event } switch (ev->type) { case ev_keydown: if (ev->data1 == KEY_PAUSE) { ::g->sendpause = true; return true; } if (ev->data1 gamekeydown[ev->data1] = true; return true; // eat key down ::g->events case ev_keyup: // DHM - Nerve :: Old School! //if ( ev->data1 == '-' ) { //App->Renderer->oldSchool = !App->Renderer->oldSchool; //} if (ev->data1 gamekeydown[ev->data1] = false; return false; // always let key up ::g->events filter down case ev_mouse: ::g->mousebuttons[0] = ev->data1 & 1; ::g->mousebuttons[1] = ev->data1 & 2; ::g->mousebuttons[2] = ev->data1 & 4; ::g->mousex = ev->data2*(::g->mouseSensitivity+5)/10; ::g->mousey = ev->data3*(::g->mouseSensitivity+5)/10; return true; // eat ::g->events case ev_joystick: ::g->joybuttons[0] = ev->data1 & 1; ::g->joybuttons[1] = ev->data1 & 2; ::g->joybuttons[2] = ev->data1 & 4; ::g->joybuttons[3] = ev->data1 & 8; ::g->joyxmove = ev->data2; /* ::g->gamekeydown[::g->key_straferight] = ::g->gamekeydown[::g->key_strafeleft] = 0; if (ev->data2 > 0) ::g->gamekeydown[::g->key_straferight] = 1; else if (ev->data2 < 0) ::g->gamekeydown[::g->key_strafeleft] = 1; */ ::g->joyymove = ev->data3; return true; // eat ::g->events default: break; } return false; } // // G_Ticker // Make ticcmd_ts for the ::g->players. // void G_Ticker (void) { int i; int buf; ticcmd_t* cmd; // do player reborns if needed for (i=0 ; iplayeringame[i] && ::g->players[i].playerstate == PST_REBORN) G_DoReborn (i); // do things to change the game state while (::g->gameaction != ga_nothing) { switch (::g->gameaction) { case ga_loadlevel: G_DoLoadLevel (); break; case ga_newgame: G_DoNewGame (); break; case ga_loadgame: G_DoLoadGame (); break; case ga_savegame: G_DoSaveGame (); break; case ga_playdemo: G_DoPlayDemo (); break; case ga_completed: G_DoCompleted (); break; case ga_victory: F_StartFinale (); break; case ga_worlddone: G_DoWorldDone (); break; case ga_screenshot: M_ScreenShot (); ::g->gameaction = ga_nothing; break; case ga_nothing: break; } } // get commands, check ::g->consistancy, // and build new ::g->consistancy check buf = (::g->gametic/::g->ticdup)%BACKUPTICS; for (i=0 ; iplayeringame[i]) { cmd = &::g->players[i].cmd; memcpy (cmd, &::g->netcmds[i][buf], sizeof(ticcmd_t)); if ( ::g->demoplayback ) { G_ReadDemoTiccmd( cmd ); #ifdef DEBUG_DEMOS if ( demoDebugOn && testprndindex != ::g->prndindex && printErrorCount++ < 10 ) { I_Printf( "time: %d, g->prndindex(%d) does not match demo prndindex(%d)!\n", ::g->leveltime, ::g->prndindex, testprndindex ); } #endif } if ( ::g->demorecording ) { G_WriteDemoTiccmd (cmd); } // HACK ALERT ( the GS_FINALE CRAP IS A HACK.. ) if (::g->netgame && !::g->netdemo && !(::g->gametic % ::g->ticdup) && !(::g->gamestate == GS_FINALE ) ) { if (::g->gametic > BACKUPTICS && ::g->consistancy[i][buf] != cmd->consistancy) { printf ("consistency failure (%i should be %i)", cmd->consistancy, ::g->consistancy[i][buf]); // TODO: If we ever support splitscreen and online, // we'll have to call D_QuitNetGame for all local players. D_QuitNetGame(); session->QuitMatch(); common->Dialog().AddDialog( GDM_CONNECTION_LOST_HOST, DIALOG_ACCEPT, NULL, NULL, false ); } if (::g->players[i].mo) ::g->consistancy[i][buf] = ::g->players[i].mo->x; else ::g->consistancy[i][buf] = ::g->rndindex; } } } // check for special buttons for (i=0 ; iplayeringame[i]) { if (::g->players[i].cmd.buttons & BT_SPECIAL) { switch (::g->players[i].cmd.buttons & BT_SPECIALMASK) { case BTS_PAUSE: ::g->paused ^= 1; // DHM - Nerve :: Don't pause the music /* if (::g->paused) S_PauseSound (); else S_ResumeSound (); */ break; case BTS_SAVEGAME: if (!::g->savedescription[0]) strcpy (::g->savedescription, "NET GAME"); ::g->savegameslot = (::g->players[i].cmd.buttons & BTS_SAVEMASK)>>BTS_SAVESHIFT; ::g->gameaction = ga_savegame; break; } } } } // do main actions switch (::g->gamestate) { case GS_LEVEL: P_Ticker (); ST_Ticker (); AM_Ticker (); HU_Ticker (); break; case GS_INTERMISSION: WI_Ticker (); break; case GS_FINALE: F_Ticker (); break; case GS_DEMOSCREEN: D_PageTicker (); break; } } // // PLAYER STRUCTURE FUNCTIONS // also see P_SpawnPlayer in P_Things // // // G_InitPlayer // Called at the start. // Called by the game initialization functions. // void G_InitPlayer (int player) { player_t* p; // set up the saved info p = &::g->players[player]; // clear everything else to defaults G_PlayerReborn (player); } // // G_PlayerFinishLevel // Can when a player completes a level. // void G_PlayerFinishLevel (int player) { player_t* p; p = &::g->players[player]; memset (p->powers, 0, sizeof (p->powers)); memset (p->cards, 0, sizeof (p->cards)); p->mo->flags &= ~MF_SHADOW; // cancel invisibility p->extralight = 0; // cancel gun flashes p->fixedcolormap = 0; // cancel ir gogles p->damagecount = 0; // no palette changes p->bonuscount = 0; } // // G_PlayerReborn // Called after a player dies // almost everything is cleared and initialized // void G_PlayerReborn (int player) { player_t* p; int i; int frags[MAXPLAYERS]; int killcount; int itemcount; int secretcount; // DHM - Nerve :: cards are saved across death in coop multiplayer qboolean cards[NUMCARDS]; bool hasMapPowerup = false; hasMapPowerup = ::g->players[player].powers[pw_allmap] != 0; memcpy( cards, ::g->players[player].cards, sizeof(cards) ); memcpy( frags, ::g->players[player].frags, sizeof(frags) ); killcount = ::g->players[player].killcount; itemcount = ::g->players[player].itemcount; secretcount = ::g->players[player].secretcount; p = &::g->players[player]; memset (p, 0, sizeof(*p)); // DHM - Nerve :: restore cards in multiplayer // TODO: Networking #ifdef ID_ENABLE_DOOM_CLASSIC_NETWORKING if ( common->IsMultiplayer() || gameLocal->IsSplitscreen() || (::g->demoplayback && ::g->netdemo) ) { if ( hasMapPowerup ) { ::g->players[player].powers[pw_allmap] = 1; } memcpy (::g->players[player].cards, cards, sizeof(::g->players[player].cards)); } #endif memcpy (::g->players[player].frags, frags, sizeof(::g->players[player].frags)); ::g->players[player].killcount = killcount; ::g->players[player].itemcount = itemcount; ::g->players[player].secretcount = secretcount; p->usedown = p->attackdown = true; // don't do anything immediately p->playerstate = PST_LIVE; p->health = MAXHEALTH; p->readyweapon = p->pendingweapon = wp_pistol; p->weaponowned[wp_fist] = true; p->weaponowned[wp_pistol] = true; p->ammo[am_clip] = 50; // TODO: PC #if 0 p->cheats = gameLocal->cheats; #else p->cheats = 0; #endif for (i=0 ; imaxammo[i] = maxammo[i]; } // // G_CheckSpot // Returns false if the player cannot be respawned // at the given mapthing_t spot // because something is occupying it // void P_SpawnPlayer (mapthing_t* mthing); qboolean G_CheckSpot ( int playernum, mapthing_t* mthing ) { fixed_t x; fixed_t y; subsector_t* ss; unsigned an; mobj_t* mo; int i; if (!::g->players[playernum].mo) { // first spawn of level, before corpses for (i=0 ; iplayers[i].mo->x == mthing->x << FRACBITS && ::g->players[i].mo->y == mthing->y << FRACBITS) return false; return true; } x = mthing->x << FRACBITS; y = mthing->y << FRACBITS; if (!P_CheckPosition (::g->players[playernum].mo, x, y) ) return false; // flush an old corpse if needed if (::g->bodyqueslot >= BODYQUESIZE) P_RemoveMobj (::g->bodyque[::g->bodyqueslot%BODYQUESIZE]); ::g->bodyque[::g->bodyqueslot%BODYQUESIZE] = ::g->players[playernum].mo; ::g->bodyqueslot++; // spawn a teleport fog ss = R_PointInSubsector (x,y); an = ( ANG45 * (mthing->angle/45) ) >> ANGLETOFINESHIFT; mo = P_SpawnMobj (x+20*finecosine[an], y+20*finesine[an] , ss->sector->floorheight , MT_TFOG); if (::g->players[::g->consoleplayer].viewz != 1 && (playernum == ::g->consoleplayer)) S_StartSound (::g->players[::g->consoleplayer].mo, sfx_telept); // don't start sound on first frame return true; } // // G_DeathMatchSpawnPlayer // Spawns a player at one of the random death match spots // called at level load and each death // void G_DeathMatchSpawnPlayer (int playernum) { int i,j; int selections; selections = ::g->deathmatch_p - ::g->deathmatchstarts; if (selections < 4) I_Error ("Only %i ::g->deathmatch spots, 4 required", selections); for (j=0 ; j<20 ; j++) { i = P_Random() % selections; if (G_CheckSpot (playernum, &::g->deathmatchstarts[i]) ) { ::g->deathmatchstarts[i].type = playernum+1; P_SpawnPlayer (&::g->deathmatchstarts[i]); return; } } // no good spot, so the player will probably get stuck P_SpawnPlayer (&::g->playerstarts[playernum]); } // // G_DoReborn // void G_DoReborn (int playernum) { int i; if (!::g->netgame) { // reload the level from scratch ::g->gameaction = ga_loadlevel; } else { // respawn at the start // first dissasociate the corpse ::g->players[playernum].mo->player = NULL; // spawn at random spot if in death match if (::g->deathmatch) { G_DeathMatchSpawnPlayer (playernum); return; } if (G_CheckSpot (playernum, &::g->playerstarts[playernum]) ) { P_SpawnPlayer (&::g->playerstarts[playernum]); return; } // try to spawn at one of the other ::g->players spots for (i=0 ; iplayerstarts[i]) ) { ::g->playerstarts[i].type = playernum+1; // fake as other player P_SpawnPlayer (&::g->playerstarts[i]); ::g->playerstarts[i].type = i+1; // restore return; } // he's going to be inside something. Too bad. } P_SpawnPlayer (&::g->playerstarts[playernum]); } } void G_ScreenShot (void) { ::g->gameaction = ga_screenshot; } // DHM - Nerve :: Added episode 4 par times // DOOM Par Times const int pars[5][10] = { {0}, {0,30,75,120,90,165,180,180,30,165}, {0,90,90,90,120,90,360,240,30,170}, {0,90,45,90,150,90,90,165,30,135}, {0,165,255,135,150,180,390,135,360,180} }; // DOOM II Par Times const int cpars[32] = { 30,90,120,120,90,150,120,120,270,90, // 1-10 210,150,150,150,210,150,420,150,210,150, // 11-20 240,150,180,150,150,300,330,420,300,180, // 21-30 120,30 // 31-32 }; // // G_DoCompleted // void G_ExitLevel (void) { ::g->secretexit = false; ::g->gameaction = ga_completed; } // Here's for the german edition. void G_SecretExitLevel (void) { // IF NO WOLF3D LEVELS, NO SECRET EXIT! if ( (::g->gamemode == commercial) && (W_CheckNumForName("map31")<0)) ::g->secretexit = false; else ::g->secretexit = true; ::g->gameaction = ga_completed; } void G_DoCompleted (void) { int i; ::g->gameaction = ga_nothing; for (i=0 ; iplayeringame[i]) { G_PlayerFinishLevel (i); // take away cards and stuff } } if (::g->automapactive) { AM_Stop(); } if ( ::g->demoplayback ) { G_CheckDemoStatus(); return; } if ( ::g->demorecording ) { G_CheckDemoStatus(); } // DHM - Nerve :: Deathmatch doesn't go to finale screen, just do intermission if ( ::g->gamemode != commercial && !::g->deathmatch ) { switch(::g->gamemap) { case 8: ::g->gameaction = ga_victory; return; case 9: for (i=0 ; iplayers[i].didsecret = true; break; } } ::g->wminfo.didsecret = ::g->players[::g->consoleplayer].didsecret; ::g->wminfo.epsd = ::g->gameepisode -1; ::g->wminfo.last = ::g->gamemap -1; // ::g->wminfo.next is 0 biased, unlike ::g->gamemap if ( ::g->gamemode == commercial) { if (::g->secretexit) { if ( ::g->gamemission == doom2 ) { switch(::g->gamemap) { case 15: ::g->wminfo.next = 30; break; case 31: ::g->wminfo.next = 31; break; } } else if( ::g->gamemission == pack_nerve ) { // DHM - Nerve :: Secret level is always level 9 on extra Doom2 missions ::g->wminfo.next = 8; } } else { if ( ::g->gamemission == doom2 ) { switch(::g->gamemap) { case 31: case 32: ::g->wminfo.next = 15; break; default: ::g->wminfo.next = ::g->gamemap; } } else if( ::g->gamemission == pack_nerve) { switch(::g->gamemap) { case 9: ::g->wminfo.next = 4; break; default: ::g->wminfo.next = ::g->gamemap; break; } } else { ::g->wminfo.next = ::g->gamemap; } } } else { if (::g->secretexit) { ::g->wminfo.next = 8; // go to secret level } else if (::g->gamemap == 9 ) { // returning from secret level switch (::g->gameepisode) { case 1: ::g->wminfo.next = 3; break; case 2: ::g->wminfo.next = 5; break; case 3: ::g->wminfo.next = 6; break; case 4: ::g->wminfo.next = 2; break; } } else ::g->wminfo.next = ::g->gamemap; // go to next level } // DHM - Nerve :: In deathmatch, repeat the current level. User must exit and choose a new level. if ( ::g->deathmatch ) { ::g->wminfo.next = ::g->wminfo.last; } ::g->wminfo.maxkills = ::g->totalkills; ::g->wminfo.maxitems = ::g->totalitems; ::g->wminfo.maxsecret = ::g->totalsecret; ::g->wminfo.maxfrags = 0; if ( ::g->gamemode == commercial ) { ::g->wminfo.partime = TICRATE *cpars[::g->gamemap-1]; } else ::g->wminfo.partime = TICRATE * pars[::g->gameepisode][::g->gamemap]; ::g->wminfo.pnum = ::g->consoleplayer; for (i=0 ; iwminfo.plyr[i].in = ::g->playeringame[i]; ::g->wminfo.plyr[i].skills = ::g->players[i].killcount; ::g->wminfo.plyr[i].sitems = ::g->players[i].itemcount; ::g->wminfo.plyr[i].ssecret = ::g->players[i].secretcount; ::g->wminfo.plyr[i].stime = ::g->leveltime; memcpy (::g->wminfo.plyr[i].frags, ::g->players[i].frags , sizeof(::g->wminfo.plyr[i].frags)); } ::g->gamestate = GS_INTERMISSION; ::g->viewactive = false; ::g->automapactive = false; WI_Start (&::g->wminfo); } // // G_WorldDone // void G_WorldDone (void) { ::g->gameaction = ga_worlddone; if (::g->secretexit) ::g->players[::g->consoleplayer].didsecret = true; if ( ::g->gamemode == commercial ) { if ( ::g->gamemission == doom2 || ::g->gamemission == pack_tnt || ::g->gamemission == pack_plut ) { switch (::g->gamemap) { case 15: case 31: if (!::g->secretexit) break; case 6: case 11: case 20: case 30: F_StartFinale (); break; } } else if ( ::g->gamemission == pack_nerve ) { if ( ::g->gamemap == 8 ) { F_StartFinale(); } } else if ( ::g->gamemission == pack_master ) { if ( ::g->gamemap == 21 ) { F_StartFinale(); } } else { // DHM - NERVE :: Downloadable content needs to set these up if different than initial extended episode if ( ::g->gamemap == 8 ) { F_StartFinale(); } } } } void G_DoWorldDone (void) { ::g->gamestate = GS_LEVEL; ::g->gamemap = ::g->wminfo.next+1; M_ClearRandom(); for ( int i = 0; i < MAXPLAYERS; i++ ) { if ( ::g->playeringame[i] ) { ::g->players[i].usedown = ::g->players[i].attackdown = true; // don't do anything immediately } } G_DoLoadLevel (); ::g->gameaction = ga_nothing; ::g->viewactive = true; } // // G_InitFromSavegame // Can be called by the startup code or the menu task. // void R_ExecuteSetViewSize (void); void G_LoadGame (char* name) { strcpy (::g->savename, name); ::g->gameaction = ga_loadgame; } qboolean G_DoLoadGame () { int i; int a,b,c; char vcheck[VERSIONSIZE]; loadingGame = true; ::g->gameaction = ga_nothing; M_ReadFile (::g->savename, &::g->savebuffer); waitingForWipe = true; // DHM - Nerve :: Clear possible net demo state ::g->netdemo = false; ::g->netgame = false; ::g->deathmatch = false; ::g->playeringame[1] = ::g->playeringame[2] = ::g->playeringame[3] = 0; ::g->respawnparm = false; ::g->fastparm = false; ::g->nomonsters = false; ::g->consoleplayer = 0; ::g->save_p = ::g->savebuffer + SAVESTRINGSIZE; // skip the description field memset (vcheck,0,sizeof(vcheck)); sprintf (vcheck,"version %i",VERSION); if (strcmp ((char *)::g->save_p, vcheck)) { loadingGame = false; waitingForWipe = false; return false; // bad version } ::g->save_p += VERSIONSIZE; ::g->gameskill = (skill_t)*::g->save_p++; ::g->gameepisode = *::g->save_p++; ::g->gamemission = *::g->save_p++; ::g->gamemap = *::g->save_p++; for (i=0 ; iplayeringame[i] = *::g->save_p++; // load a base level G_InitNew (::g->gameskill, ::g->gameepisode, ::g->gamemap ); // get the times a = *::g->save_p++; b = *::g->save_p++; c = *::g->save_p++; ::g->leveltime = (a<<16) + (b<<8) + c; // dearchive all the modifications P_UnArchivePlayers (); P_UnArchiveWorld (); P_UnArchiveThinkers (); // specials are archived with thinkers //P_UnArchiveSpecials (); if (*::g->save_p != 0x1d) I_Error ("Bad savegame"); if (::g->setsizeneeded) R_ExecuteSetViewSize (); // draw the pattern into the back screen R_FillBackScreen (); loadingGame = false; Z_Free(g->savebuffer); return true; } // // G_SaveGame // Called by the menu task. // Description is a 24 byte text string // void G_SaveGame ( int slot, char* description ) { ::g->savegameslot = slot; strcpy (::g->savedescription, description); ::g->sendsave = true; ::g->gameaction = ga_savegame; } qboolean G_DoSaveGame (void) { char name[100]; char name2[VERSIONSIZE]; char* description; int length; int i; qboolean bResult = true; if ( ::g->gamestate != GS_LEVEL ) { return false; } description = ::g->savedescription; if( common->GetCurrentGame() == DOOM_CLASSIC ) { sprintf(name,"DOOM\\%s%d.dsg", SAVEGAMENAME,::g->savegameslot ); } else { if( DoomLib::expansionSelected == doom2 ) { sprintf(name,"DOOM2\\%s%d.dsg", SAVEGAMENAME,::g->savegameslot ); } else { sprintf(name,"DOOM2_NRFTL\\%s%d.dsg", SAVEGAMENAME,::g->savegameslot ); } } ::g->save_p = ::g->savebuffer = ::g->screens[1]; memcpy (::g->save_p, description, SAVESTRINGSIZE); ::g->save_p += SAVESTRINGSIZE; memset (name2,0,sizeof(name2)); sprintf (name2,"version %i",VERSION); memcpy (::g->save_p, name2, VERSIONSIZE); ::g->save_p += VERSIONSIZE; *::g->save_p++ = ::g->gameskill; *::g->save_p++ = ::g->gameepisode; *::g->save_p++ = ::g->gamemission; *::g->save_p++ = ::g->gamemap; for (i=0 ; isave_p++ = ::g->playeringame[i]; } *::g->save_p++ = ::g->leveltime>>16; *::g->save_p++ = ::g->leveltime>>8; *::g->save_p++ = ::g->leveltime; P_ArchivePlayers (); P_ArchiveWorld (); P_ArchiveThinkers (); // specials are archived with thinkers //P_ArchiveSpecials (); *::g->save_p++ = 0x1d; // ::g->consistancy marker length = ::g->save_p - ::g->savebuffer; if (length > SAVEGAMESIZE) I_Error ("Savegame buffer overrun"); ::g->savebufferSize = length; M_WriteFile (name, ::g->savebuffer, length); ::g->gameaction = ga_nothing; ::g->savedescription[0] = 0; // draw the pattern into the back screen R_FillBackScreen (); return bResult; } // // G_InitNew // Can be called by the startup code or the menu task, // ::g->consoleplayer, ::g->displayplayer, ::g->playeringame[] should be set. // void G_DeferedInitNew ( skill_t skill, int episode, int map) { ::g->d_skill = skill; ::g->d_episode = episode; ::g->d_map = map; //::g->d_map = 30; ::g->gameaction = ga_newgame; } void G_DoNewGame (void) { ::g->demoplayback = false; ::g->netdemo = false; ::g->netgame = false; ::g->deathmatch = false; ::g->playeringame[1] = ::g->playeringame[2] = ::g->playeringame[3] = 0; ::g->respawnparm = false; ::g->fastparm = false; ::g->nomonsters = false; ::g->consoleplayer = 0; G_InitNew (::g->d_skill, ::g->d_episode, ::g->d_map ); ::g->gameaction = ga_nothing; } // The sky texture to be used instead of the F_SKY1 dummy. void G_InitNew ( skill_t skill, int episode, int map ) { int i; m_inDemoMode.SetBool( false ); R_SetViewSize (::g->screenblocks, ::g->detailLevel); if (::g->paused) { ::g->paused = false; S_ResumeSound (); } if (skill > sk_nightmare) skill = sk_nightmare; // This was quite messy with SPECIAL and commented parts. // Supposedly hacks to make the latest edition work. // It might not work properly. if (episode < 1) episode = 1; if ( ::g->gamemode == retail ) { if (episode > 4) episode = 4; } else if ( ::g->gamemode == shareware ) { if (episode > 1) episode = 1; // only start episode 1 on shareware } else { if (episode > 3) episode = 3; } if (map < 1) map = 1; if (skill == sk_nightmare || ::g->respawnparm ) ::g->respawnmonsters = true; else ::g->respawnmonsters = false; // force ::g->players to be initialized upon first level load for (i=0 ; iplayers[i].playerstate = PST_REBORN; ::g->usergame = true; // will be set false if a demo ::g->paused = false; ::g->demoplayback = false; ::g->advancedemo = false; ::g->automapactive = false; ::g->viewactive = true; ::g->gameepisode = episode; //::g->gamemission = expansion->pack_type; ::g->gamemap = map; ::g->gameskill = skill; ::g->viewactive = true; // set the sky map for the episode if ( ::g->gamemode == commercial) { ::g->skytexture = R_TextureNumForName ("SKY3"); if (::g->gamemap < 12) { ::g->skytexture = R_TextureNumForName ("SKY1"); } else if (::g->gamemap < 21) { ::g->skytexture = R_TextureNumForName ("SKY2"); } } else { switch (episode) { case 1: ::g->skytexture = R_TextureNumForName ("SKY1"); break; case 2: ::g->skytexture = R_TextureNumForName ("SKY2"); break; case 3: ::g->skytexture = R_TextureNumForName ("SKY3"); break; case 4: // Special Edition sky ::g->skytexture = R_TextureNumForName ("SKY4"); break; default: ::g->skytexture = R_TextureNumForName ("SKY1"); break; } } G_DoLoadLevel( ); } // // DEMO RECORDING // void G_ReadDemoTiccmd (ticcmd_t* cmd) { if (*::g->demo_p == DEMOMARKER) { // end of demo data stream G_CheckDemoStatus (); return; } cmd->forwardmove = ((signed char)*::g->demo_p++); cmd->sidemove = ((signed char)*::g->demo_p++); if ( demoversion == VERSION ) { short *temp = (short *)(::g->demo_p); cmd->angleturn = *temp; ::g->demo_p += 2; } else { // DHM - Nerve :: Old format cmd->angleturn = ((unsigned char)*::g->demo_p++)<<8; } cmd->buttons = (unsigned char)*::g->demo_p++; #ifdef DEBUG_DEMOS // TESTING if ( demoDebugOn ) { testprndindex = (unsigned char)*::g->demo_p++; } #endif } void G_WriteDemoTiccmd (ticcmd_t* cmd) { *::g->demo_p++ = cmd->forwardmove; *::g->demo_p++ = cmd->sidemove; // NEW VERSION short *temp = (short *)(::g->demo_p); *temp = cmd->angleturn; ::g->demo_p += 2; // OLD VERSION //*::g->demo_p++ = (cmd->angleturn+128)>>8; *::g->demo_p++ = cmd->buttons; int cmdSize = 5; #ifdef DEBUG_DEMOS_WRITE // TESTING *::g->demo_p++ = ::g->prndindex; cmdSize++; #endif ::g->demo_p -= cmdSize; if (::g->demo_p > ::g->demoend - (cmdSize * 4)) { // no more space G_CheckDemoStatus (); return; } G_ReadDemoTiccmd (cmd); // make SURE it is exactly the same } // // G_RecordDemo // void G_RecordDemo (char* name) { //::g->usergame = false; strcpy( ::g->demoname, name ); strcat( ::g->demoname, ".lmp" ); ::g->demobuffer = new byte[ MAXDEMOSIZE ]; ::g->demoend = ::g->demobuffer + MAXDEMOSIZE; demoversion = VERSION; ::g->demorecording = true; } void G_BeginRecording (void) { int i; ::g->demo_p = ::g->demobuffer; #ifdef DEBUG_DEMOS #ifdef DEBUG_DEMOS_WRITE demoDebugOn = true; *::g->demo_p++ = VERSION + 1; #else *::g->demo_p++ = VERSION; #endif #endif *::g->demo_p++ = ::g->gameskill; *::g->demo_p++ = ::g->gameepisode; *::g->demo_p++ = ::g->gamemission; *::g->demo_p++ = ::g->gamemap; *::g->demo_p++ = ::g->deathmatch; *::g->demo_p++ = ::g->respawnparm; *::g->demo_p++ = ::g->fastparm; *::g->demo_p++ = ::g->nomonsters; *::g->demo_p++ = ::g->consoleplayer; for ( i=0 ; idemo_p++ = ::g->playeringame[i]; } for ( i=0 ; iplayeringame[i] ) { int* dest = (int *)::g->demo_p; *dest++ = ::g->players[i].health; *dest++ = ::g->players[i].armorpoints; *dest++ = ::g->players[i].armortype; *dest++ = ::g->players[i].readyweapon; for ( int j = 0; j < NUMWEAPONS; j++ ) { *dest++ = ::g->players[i].weaponowned[j]; } for ( int j = 0; j < NUMAMMO; j++ ) { *dest++ = ::g->players[i].ammo[j]; *dest++ = ::g->players[i].maxammo[j]; } ::g->demo_p = (byte *)dest; } } } // // G_PlayDemo // void G_DeferedPlayDemo (char* name) { ::g->defdemoname = name; ::g->gameaction = ga_playdemo; } void G_DoPlayDemo (void) { skill_t skill; int i, episode, map, mission; ::g->gameaction = ga_nothing; // TODO: Networking #if ID_ENABLE_DOOM_CLASSIC_NETWORKING if ( gameLocal->IsSplitscreen() && DoomLib::GetPlayer() > 0 ) { return; } #endif // DEMO Testing bool useOriginalDemo = true; if ( useOriginalDemo ) { int demolump = W_GetNumForName( ::g->defdemoname ); int demosize = W_LumpLength( demolump ); ::g->demobuffer = ::g->demo_p = new byte[ demosize ]; W_ReadLump( demolump, ::g->demobuffer ); } // DHM - Nerve :: We support old and new demo versions demoversion = *::g->demo_p++; skill = (skill_t)*::g->demo_p++; episode = *::g->demo_p++; if ( demoversion == VERSION ) { mission = *::g->demo_p++; } else { mission = 0; } map = *::g->demo_p++; ::g->deathmatch = *::g->demo_p++; ::g->respawnparm = *::g->demo_p++; ::g->fastparm = *::g->demo_p++; ::g->nomonsters = *::g->demo_p++; ::g->consoleplayer = *::g->demo_p++; for ( i=0 ; iplayeringame[i] = *::g->demo_p++; } ::g->netgame = false; ::g->netdemo = false; if (::g->playeringame[1]) { ::g->netgame = true; ::g->netdemo = true; } // don't spend a lot of time in loadlevel ::g->precache = false; G_InitNew (skill, episode, map ); R_SetViewSize (::g->screenblocks + 1, ::g->detailLevel); m_inDemoMode.SetBool( true ); // JAF - Dont show messages when in Demo Mode. ::g->showMessages = false; ::g->precache = true; // DHM - Nerve :: We now read in the player state from the demo if ( demoversion == VERSION ) { for ( i=0 ; iplayeringame[i] ) { int* src = (int *)::g->demo_p; ::g->players[i].health = *src++; ::g->players[i].mo->health = ::g->players[i].health; ::g->players[i].armorpoints = *src++; ::g->players[i].armortype = *src++; ::g->players[i].readyweapon = (weapontype_t)*src++; for ( int j = 0; j < NUMWEAPONS; j++ ) { ::g->players[i].weaponowned[j] = *src++; } for ( int j = 0; j < NUMAMMO; j++ ) { ::g->players[i].ammo[j] = *src++; ::g->players[i].maxammo[j] = *src++; } ::g->demo_p = (byte *)src; P_SetupPsprites( &::g->players[i] ); } } } ::g->usergame = false; ::g->demoplayback = true; } // // G_TimeDemo // void G_TimeDemo (char* name) { ::g->nodrawers = M_CheckParm ("-nodraw"); ::g->noblit = M_CheckParm ("-noblit"); ::g->timingdemo = true; ::g->singletics = true; ::g->defdemoname = name; ::g->gameaction = ga_playdemo; } /* =================== = = G_CheckDemoStatus = = Called after a death or level completion to allow demos to be cleaned up = Returns true if a new demo loop action will take place =================== */ qboolean G_CheckDemoStatus (void) { if (::g->demoplayback) { delete ::g->demobuffer; ::g->demobuffer = NULL; ::g->demo_p = NULL; ::g->demoend = NULL; ::g->demoplayback = false; ::g->netdemo = false; ::g->netgame = false; ::g->deathmatch = false; ::g->playeringame[1] = ::g->playeringame[2] = ::g->playeringame[3] = 0; ::g->respawnparm = false; ::g->fastparm = false; ::g->nomonsters = false; ::g->consoleplayer = 0; D_AdvanceDemo (); return true; } /* if (::g->demorecording) { *::g->demo_p++ = DEMOMARKER; if ( ::g->leveltime > (TICRATE * 9) ) { gameLocal->DoomStoreDemoBuffer( gameLocal->GetPortForPlayer( DoomLib::GetPlayer() ), ::g->demobuffer, ::g->demo_p - ::g->demobuffer ); } delete ::g->demobuffer; ::g->demobuffer = NULL; ::g->demo_p = NULL; ::g->demoend = NULL; ::g->demorecording = false; } */ return false; }