// // Copyright(C) 1993-1996 Id Software, Inc. // Copyright(C) 1993-2008 Raven Software // Copyright(C) 2005-2014 Simon Howard // // This program 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 2 // of the License, or (at your option) any later version. // // This program 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. // // G_game.c #include #include #include #include "doomdef.h" #include "doomkeys.h" #include "deh_str.h" #include "i_timer.h" #include "i_system.h" #include "m_controls.h" #include "m_misc.h" #include "m_random.h" #include "p_local.h" #include "s_sound.h" #include "v_video.h" // Macros #define AM_STARTKEY 9 // Functions boolean G_CheckDemoStatus(void); void G_ReadDemoTiccmd(ticcmd_t * cmd); void G_WriteDemoTiccmd(ticcmd_t * cmd); void G_PlayerReborn(int player); void G_DoReborn(int playernum); void G_DoLoadLevel(void); void G_DoNewGame(void); void G_DoPlayDemo(void); void G_DoCompleted(void); void G_DoVictory(void); void G_DoWorldDone(void); void G_DoSaveGame(void); void D_PageTicker(void); void D_AdvanceDemo(void); struct { int type; // mobjtype_t int speed[2]; } MonsterMissileInfo[] = { { MT_IMPBALL, { 10, 20 } }, { MT_MUMMYFX1, { 9, 18 } }, { MT_KNIGHTAXE, { 9, 18 } }, { MT_REDAXE, { 9, 18 } }, { MT_BEASTBALL, { 12, 20 } }, { MT_WIZFX1, { 18, 24 } }, { MT_SNAKEPRO_A, { 14, 20 } }, { MT_SNAKEPRO_B, { 14, 20 } }, { MT_HEADFX1, { 13, 20 } }, { MT_HEADFX3, { 10, 18 } }, { MT_MNTRFX1, { 20, 26 } }, { MT_MNTRFX2, { 14, 20 } }, { MT_SRCRFX1, { 20, 28 } }, { MT_SOR2FX1, { 20, 28 } }, { -1, { -1, -1 } } // Terminator }; gameaction_t gameaction; gamestate_t gamestate; skill_t gameskill; boolean respawnmonsters; int gameepisode; int gamemap; int prevmap; boolean paused; boolean sendpause; // send a pause event next tic boolean sendsave; // send a save event next tic boolean usergame; // ok to save / end game boolean timingdemo; // if true, exit with report on completion int starttime; // for comparative timing purposes boolean viewactive; boolean deathmatch; // only if started as net death boolean netgame; // only true if packets are broadcast boolean playeringame[MAXPLAYERS]; player_t players[MAXPLAYERS]; int consoleplayer; // player taking events and displaying int displayplayer; // view being displayed int gametic; int levelstarttic; // gametic at level start int totalkills, totalitems, totalsecret; // for intermission int mouseSensitivity; char demoname[32]; boolean demorecording; boolean demoplayback; byte *demobuffer, *demo_p; boolean singledemo; // quit after playing a demo from cmdline boolean precache = true; // if true, load all graphics at start // TODO: Heretic uses 16-bit shorts for consistency? byte consistancy[MAXPLAYERS][BACKUPTICS]; char *savegamedir; boolean testcontrols = false; int testcontrols_mousespeed; // // controls (have defaults) // #define MAXPLMOVE 0x32 fixed_t forwardmove[2] = { 0x19, 0x32 }; fixed_t sidemove[2] = { 0x18, 0x28 }; fixed_t angleturn[3] = { 640, 1280, 320 }; // + slow turn static int *weapon_keys[] = { &key_weapon1, &key_weapon2, &key_weapon3, &key_weapon4, &key_weapon5, &key_weapon6, &key_weapon7 }; // Set to -1 or +1 to switch to the previous or next weapon. static int next_weapon = 0; // Used for prev/next weapon keys. static const struct { weapontype_t weapon; weapontype_t weapon_num; } weapon_order_table[] = { { wp_staff, wp_staff }, { wp_gauntlets, wp_staff }, { wp_goldwand, wp_goldwand }, { wp_crossbow, wp_crossbow }, { wp_blaster, wp_blaster }, { wp_skullrod, wp_skullrod }, { wp_phoenixrod, wp_phoenixrod }, { wp_mace, wp_mace }, { wp_beak, wp_beak }, }; #define SLOWTURNTICS 6 #define NUMKEYS 256 boolean gamekeydown[NUMKEYS]; int turnheld; // for accelerative turning int lookheld; boolean mousearray[MAX_MOUSE_BUTTONS + 1]; boolean *mousebuttons = &mousearray[1]; // allow [-1] int mousex, mousey; // mouse values are used once int dclicktime, dclickstate, dclicks; int dclicktime2, dclickstate2, dclicks2; #define MAX_JOY_BUTTONS 20 int joyxmove, joyymove; // joystick values are repeated int joystrafemove; boolean joyarray[MAX_JOY_BUTTONS + 1]; boolean *joybuttons = &joyarray[1]; // allow [-1] int savegameslot; char savedescription[32]; int inventoryTics; // haleyjd: removed WATCOMC //============================================================================= // Not used - ripped out for Heretic /* int G_CmdChecksum(ticcmd_t *cmd) { int i; int sum; sum = 0; for(i = 0; i < sizeof(*cmd)/4-1; i++) { sum += ((int *)cmd)[i]; } return(sum); } */ static boolean WeaponSelectable(weapontype_t weapon) { if (weapon == wp_beak) { return false; } return players[consoleplayer].weaponowned[weapon]; } static int G_NextWeapon(int direction) { weapontype_t weapon; int start_i, i; // Find index in the table. if (players[consoleplayer].pendingweapon == wp_nochange) { weapon = players[consoleplayer].readyweapon; } else { weapon = players[consoleplayer].pendingweapon; } for (i=0; iconsistancy = // consistancy[consoleplayer][(maketic*ticdup)%BACKUPTICS]; cmd->consistancy = consistancy[consoleplayer][maketic % BACKUPTICS]; //printf ("cons: %i\n",cmd->consistancy); strafe = gamekeydown[key_strafe] || mousebuttons[mousebstrafe] || joybuttons[joybstrafe]; speed = joybspeed >= MAX_JOY_BUTTONS || gamekeydown[key_speed] || joybuttons[joybspeed]; // haleyjd: removed externdriver crap forward = side = look = arti = flyheight = 0; // // use two stage accelerative turning on the keyboard and joystick // if (joyxmove < 0 || joyxmove > 0 || gamekeydown[key_right] || gamekeydown[key_left]) turnheld += ticdup; else turnheld = 0; if (turnheld < SLOWTURNTICS) tspeed = 2; // slow turn else tspeed = speed; if (gamekeydown[key_lookdown] || gamekeydown[key_lookup]) { lookheld += ticdup; } else { lookheld = 0; } if (lookheld < SLOWTURNTICS) { lspeed = 1; } else { lspeed = 2; } // // let movement keys cancel each other out // if (strafe) { if (gamekeydown[key_right]) side += sidemove[speed]; if (gamekeydown[key_left]) side -= sidemove[speed]; if (joyxmove > 0) side += sidemove[speed]; if (joyxmove < 0) side -= sidemove[speed]; } else { if (gamekeydown[key_right]) cmd->angleturn -= angleturn[tspeed]; if (gamekeydown[key_left]) cmd->angleturn += angleturn[tspeed]; if (joyxmove > 0) cmd->angleturn -= angleturn[tspeed]; if (joyxmove < 0) cmd->angleturn += angleturn[tspeed]; } if (gamekeydown[key_up]) forward += forwardmove[speed]; if (gamekeydown[key_down]) forward -= forwardmove[speed]; if (joyymove < 0) forward += forwardmove[speed]; if (joyymove > 0) forward -= forwardmove[speed]; if (gamekeydown[key_straferight] || mousebuttons[mousebstraferight] || joybuttons[joybstraferight] || joystrafemove > 0) side += sidemove[speed]; if (gamekeydown[key_strafeleft] || mousebuttons[mousebstrafeleft] || joybuttons[joybstrafeleft] || joystrafemove < 0) side -= sidemove[speed]; // Look up/down/center keys if (gamekeydown[key_lookup]) { look = lspeed; } if (gamekeydown[key_lookdown]) { look = -lspeed; } // haleyjd: removed externdriver crap if (gamekeydown[key_lookcenter]) { look = TOCENTER; } // haleyjd: removed externdriver crap // Fly up/down/drop keys if (gamekeydown[key_flyup]) { flyheight = 5; // note that the actual flyheight will be twice this } if (gamekeydown[key_flydown]) { flyheight = -5; } if (gamekeydown[key_flycenter]) { flyheight = TOCENTER; // haleyjd: removed externdriver crap look = TOCENTER; } // Use artifact key if (gamekeydown[key_useartifact]) { if (gamekeydown[key_speed] && !noartiskip) { if (players[consoleplayer].inventory[inv_ptr].type != arti_none) { gamekeydown[key_useartifact] = false; cmd->arti = 0xff; // skip artifact code } } else { if (inventory) { players[consoleplayer].readyArtifact = players[consoleplayer].inventory[inv_ptr].type; inventory = false; cmd->arti = 0; usearti = false; } else if (usearti) { cmd->arti = players[consoleplayer].inventory[inv_ptr].type; usearti = false; } } } if (gamekeydown[127] && !cmd->arti && !players[consoleplayer].powers[pw_weaponlevel2]) { gamekeydown[127] = false; cmd->arti = arti_tomeofpower; } // // buttons // cmd->chatchar = CT_dequeueChatChar(); if (gamekeydown[key_fire] || mousebuttons[mousebfire] || joybuttons[joybfire]) cmd->buttons |= BT_ATTACK; if (gamekeydown[key_use] || joybuttons[joybuse] || mousebuttons[mousebuse]) { cmd->buttons |= BT_USE; dclicks = 0; // clear double clicks if hit use button } // If the previous or next weapon button is pressed, the // next_weapon variable is set to change weapons when // we generate a ticcmd. Choose a new weapon. // (Can't weapon cycle when the player is a chicken) if (gamestate == GS_LEVEL && players[consoleplayer].chickenTics == 0 && next_weapon != 0) { i = G_NextWeapon(next_weapon); cmd->buttons |= BT_CHANGE; cmd->buttons |= i << BT_WEAPONSHIFT; } else { for (i=0; ibuttons |= BT_CHANGE; cmd->buttons |= i< 1) { dclickstate = mousebuttons[mousebforward]; if (dclickstate) dclicks++; if (dclicks == 2) { cmd->buttons |= BT_USE; dclicks = 0; } else dclicktime = 0; } else { dclicktime += ticdup; if (dclicktime > 20) { dclicks = 0; dclickstate = 0; } } // // strafe double click // bstrafe = mousebuttons[mousebstrafe] || joybuttons[joybstrafe]; if (bstrafe != dclickstate2 && dclicktime2 > 1) { dclickstate2 = bstrafe; if (dclickstate2) dclicks2++; if (dclicks2 == 2) { cmd->buttons |= BT_USE; dclicks2 = 0; } else dclicktime2 = 0; } else { dclicktime2 += ticdup; if (dclicktime2 > 20) { dclicks2 = 0; dclickstate2 = 0; } } } if (strafe) { side += mousex * 2; } else { cmd->angleturn -= mousex * 0x8; } // No mouse movement in previous frame? if (mousex == 0) { testcontrols_mousespeed = 0; } forward += mousey; mousex = 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; if (players[consoleplayer].playerstate == PST_LIVE) { if (look < 0) { look += 16; } cmd->lookfly = look; } if (flyheight < 0) { flyheight += 16; } cmd->lookfly |= flyheight << 4; // // special buttons // if (sendpause) { sendpause = false; cmd->buttons = BT_SPECIAL | BTS_PAUSE; } if (sendsave) { sendsave = false; cmd->buttons = BT_SPECIAL | BTS_SAVEGAME | (savegameslot << BTS_SAVESHIFT); } } /* ============== = = G_DoLoadLevel = ============== */ void G_DoLoadLevel(void) { int i; levelstarttic = gametic; // for time calculation gamestate = GS_LEVEL; for (i = 0; i < MAXPLAYERS; i++) { if (playeringame[i] && players[i].playerstate == PST_DEAD) players[i].playerstate = PST_REBORN; memset(players[i].frags, 0, sizeof(players[i].frags)); } P_SetupLevel(gameepisode, gamemap, 0, gameskill); displayplayer = consoleplayer; // view the guy you are playing starttime = I_GetTime(); gameaction = ga_nothing; Z_CheckHeap(); // // clear cmd building stuff // memset(gamekeydown, 0, sizeof(gamekeydown)); joyxmove = joyymove = joystrafemove = 0; mousex = mousey = 0; sendpause = sendsave = paused = false; memset(mousearray, 0, sizeof(mousearray)); memset(joyarray, 0, sizeof(joyarray)); if (testcontrols) { P_SetMessage(&players[consoleplayer], "PRESS ESCAPE TO QUIT.", false); } } static void SetJoyButtons(unsigned int buttons_mask) { int i; for (i=0; itype == ev_keyup && ev->data1 == key_useartifact) { // flag to denote that it's okay to use an artifact if (!inventory) { plr->readyArtifact = plr->inventory[inv_ptr].type; } usearti = true; } // Check for spy mode player cycle if (gamestate == GS_LEVEL && ev->type == ev_keydown && ev->data1 == KEY_F12 && !deathmatch) { // Cycle the display player do { displayplayer++; if (displayplayer == MAXPLAYERS) { displayplayer = 0; } } while (!playeringame[displayplayer] && displayplayer != consoleplayer); return (true); } if (gamestate == GS_LEVEL) { if (CT_Responder(ev)) { // Chat ate the event return (true); } if (SB_Responder(ev)) { // Status bar ate the event return (true); } if (AM_Responder(ev)) { // Automap ate the event return (true); } } if (ev->type == ev_mouse) { testcontrols_mousespeed = abs(ev->data2); } if (ev->type == ev_keydown && ev->data1 == key_prevweapon) { next_weapon = -1; } else if (ev->type == ev_keydown && ev->data1 == key_nextweapon) { next_weapon = 1; } switch (ev->type) { case ev_keydown: if (ev->data1 == key_invleft) { inventoryTics = 5 * 35; if (!inventory) { inventory = true; break; } inv_ptr--; if (inv_ptr < 0) { inv_ptr = 0; } else { curpos--; if (curpos < 0) { curpos = 0; } } return (true); } if (ev->data1 == key_invright) { inventoryTics = 5 * 35; if (!inventory) { inventory = true; break; } inv_ptr++; if (inv_ptr >= plr->inventorySlotNum) { inv_ptr--; if (inv_ptr < 0) inv_ptr = 0; } else { curpos++; if (curpos > 6) { curpos = 6; } } return (true); } if (ev->data1 == key_pause && !MenuActive) { sendpause = true; return (true); } if (ev->data1 < NUMKEYS) { gamekeydown[ev->data1] = true; } return (true); // eat key down events case ev_keyup: if (ev->data1 < NUMKEYS) { gamekeydown[ev->data1] = false; } return (false); // always let key up events filter down case ev_mouse: SetMouseButtons(ev->data1); mousex = ev->data2 * (mouseSensitivity + 5) / 10; mousey = ev->data3 * (mouseSensitivity + 5) / 10; return (true); // eat events case ev_joystick: SetJoyButtons(ev->data1); joyxmove = ev->data2; joyymove = ev->data3; joystrafemove = ev->data4; return (true); // eat events default: break; } return (false); } /* =============================================================================== = = G_Ticker = =============================================================================== */ void G_Ticker(void) { int i, buf; ticcmd_t *cmd = NULL; // // do player reborns if needed // for (i = 0; i < MAXPLAYERS; i++) if (playeringame[i] && players[i].playerstate == PST_REBORN) G_DoReborn(i); // // do things to change the game state // while (gameaction != ga_nothing) { switch (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_screenshot: V_ScreenShot("HTIC%02i.%s"); gameaction = ga_nothing; break; case ga_completed: G_DoCompleted(); break; case ga_worlddone: G_DoWorldDone(); break; case ga_victory: F_StartFinale(); break; default: break; } } // // get commands, check consistancy, and build new consistancy check // //buf = gametic%BACKUPTICS; buf = (gametic / ticdup) % BACKUPTICS; for (i = 0; i < MAXPLAYERS; i++) if (playeringame[i]) { cmd = &players[i].cmd; memcpy(cmd, &netcmds[i], sizeof(ticcmd_t)); if (demoplayback) G_ReadDemoTiccmd(cmd); if (demorecording) G_WriteDemoTiccmd(cmd); if (netgame && !(gametic % ticdup)) { if (gametic > BACKUPTICS && consistancy[i][buf] != cmd->consistancy) { I_Error("consistency failure (%i should be %i)", cmd->consistancy, consistancy[i][buf]); } if (players[i].mo) consistancy[i][buf] = players[i].mo->x; else consistancy[i][buf] = rndindex; } } // // check for special buttons // for (i = 0; i < MAXPLAYERS; i++) if (playeringame[i]) { if (players[i].cmd.buttons & BT_SPECIAL) { switch (players[i].cmd.buttons & BT_SPECIALMASK) { case BTS_PAUSE: paused ^= 1; if (paused) { S_PauseSound(); } else { S_ResumeSound(); } break; case BTS_SAVEGAME: if (!savedescription[0]) { if (netgame) { M_StringCopy(savedescription, DEH_String("NET GAME"), sizeof(savedescription)); } else { M_StringCopy(savedescription, DEH_String("SAVE GAME"), sizeof(savedescription)); } } savegameslot = (players[i].cmd. buttons & BTS_SAVEMASK) >> BTS_SAVESHIFT; gameaction = ga_savegame; break; } } } // turn inventory off after a certain amount of time if (inventory && !(--inventoryTics)) { players[consoleplayer].readyArtifact = players[consoleplayer].inventory[inv_ptr].type; inventory = false; cmd->arti = 0; } // // do main actions // // // do main actions // switch (gamestate) { case GS_LEVEL: P_Ticker(); SB_Ticker(); AM_Ticker(); CT_Ticker(); break; case GS_INTERMISSION: IN_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) { // clear everything else to defaults G_PlayerReborn(player); } /* ==================== = = G_PlayerFinishLevel = = Can when a player completes a level ==================== */ extern int playerkeys; void G_PlayerFinishLevel(int player) { player_t *p; int i; /* // BIG HACK inv_ptr = 0; curpos = 0; */ // END HACK p = &players[player]; for (i = 0; i < p->inventorySlotNum; i++) { p->inventory[i].count = 1; } p->artifactCount = p->inventorySlotNum; if (!deathmatch) { for (i = 0; i < 16; i++) { P_PlayerUseArtifact(p, arti_fly); } } memset(p->powers, 0, sizeof(p->powers)); memset(p->keys, 0, sizeof(p->keys)); playerkeys = 0; // memset(p->inventory, 0, sizeof(p->inventory)); if (p->chickenTics) { p->readyweapon = p->mo->special1.i; // Restore weapon p->chickenTics = 0; } p->messageTics = 0; p->lookdir = 0; p->mo->flags &= ~MF_SHADOW; // Remove invisibility p->extralight = 0; // Remove weapon flashes p->fixedcolormap = 0; // Remove torch p->damagecount = 0; // No palette changes p->bonuscount = 0; p->rain1 = NULL; p->rain2 = NULL; if (p == &players[consoleplayer]) { SB_state = -1; // refresh the status bar } } /* ==================== = = 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, itemcount, secretcount; boolean secret; secret = false; memcpy(frags, players[player].frags, sizeof(frags)); killcount = players[player].killcount; itemcount = players[player].itemcount; secretcount = players[player].secretcount; p = &players[player]; if (p->didsecret) { secret = true; } memset(p, 0, sizeof(*p)); memcpy(players[player].frags, frags, sizeof(players[player].frags)); players[player].killcount = killcount; players[player].itemcount = itemcount; 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_goldwand; p->weaponowned[wp_staff] = true; p->weaponowned[wp_goldwand] = true; p->messageTics = 0; p->lookdir = 0; p->ammo[am_goldwand] = 50; for (i = 0; i < NUMAMMO; i++) { p->maxammo[i] = maxammo[i]; } if (gamemap == 9 || secret) { p->didsecret = true; } if (p == &players[consoleplayer]) { SB_state = -1; // refresh the status bar inv_ptr = 0; // reset the inventory pointer curpos = 0; } } /* ==================== = = 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); boolean G_CheckSpot(int playernum, mapthing_t * mthing) { fixed_t x, y; subsector_t *ss; unsigned an; mobj_t *mo; x = mthing->x << FRACBITS; y = mthing->y << FRACBITS; players[playernum].mo->flags2 &= ~MF2_PASSMOBJ; if (!P_CheckPosition(players[playernum].mo, x, y)) { players[playernum].mo->flags2 |= MF2_PASSMOBJ; return false; } players[playernum].mo->flags2 |= MF2_PASSMOBJ; // spawn a teleport fog ss = R_PointInSubsector(x, y); an = ((unsigned) ANG45 * (mthing->angle / 45)) >> ANGLETOFINESHIFT; mo = P_SpawnMobj(x + 20 * finecosine[an], y + 20 * finesine[an], ss->sector->floorheight + TELEFOGHEIGHT, MT_TFOG); if (players[consoleplayer].viewz != 1) S_StartSound(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 = deathmatch_p - deathmatchstarts; if (selections < 4) I_Error("Only %i deathmatch spots, 4 required", selections); for (j = 0; j < 20; j++) { i = P_Random() % selections; if (G_CheckSpot(playernum, &deathmatchstarts[i])) { deathmatchstarts[i].type = playernum + 1; P_SpawnPlayer(&deathmatchstarts[i]); return; } } // no good spot, so the player will probably get stuck P_SpawnPlayer(&playerstarts[playernum]); } /* ==================== = = G_DoReborn = ==================== */ void G_DoReborn(int playernum) { int i; if (G_CheckDemoStatus()) return; if (!netgame) gameaction = ga_loadlevel; // reload the level from scratch else { // respawn at the start players[playernum].mo->player = NULL; // dissasociate the corpse // spawn at random spot if in death match if (deathmatch) { G_DeathMatchSpawnPlayer(playernum); return; } if (G_CheckSpot(playernum, &playerstarts[playernum])) { P_SpawnPlayer(&playerstarts[playernum]); return; } // try to spawn at one of the other players spots for (i = 0; i < MAXPLAYERS; i++) if (G_CheckSpot(playernum, &playerstarts[i])) { playerstarts[i].type = playernum + 1; // fake as other player P_SpawnPlayer(&playerstarts[i]); playerstarts[i].type = i + 1; // restore return; } // he's going to be inside something. Too bad. P_SpawnPlayer(&playerstarts[playernum]); } } void G_ScreenShot(void) { gameaction = ga_screenshot; } /* ==================== = = G_DoCompleted = ==================== */ boolean secretexit; void G_ExitLevel(void) { secretexit = false; gameaction = ga_completed; } void G_SecretExitLevel(void) { secretexit = true; gameaction = ga_completed; } void G_DoCompleted(void) { int i; static int afterSecret[5] = { 7, 5, 5, 5, 4 }; gameaction = ga_nothing; if (G_CheckDemoStatus()) { return; } for (i = 0; i < MAXPLAYERS; i++) { if (playeringame[i]) { G_PlayerFinishLevel(i); } } prevmap = gamemap; if (secretexit == true) { gamemap = 9; } else if (gamemap == 9) { // Finished secret level gamemap = afterSecret[gameepisode - 1]; } else if (gamemap == 8) { gameaction = ga_victory; return; } else { gamemap++; } gamestate = GS_INTERMISSION; IN_Start(); } //============================================================================ // // G_WorldDone // //============================================================================ void G_WorldDone(void) { gameaction = ga_worlddone; } //============================================================================ // // G_DoWorldDone // //============================================================================ void G_DoWorldDone(void) { gamestate = GS_LEVEL; G_DoLoadLevel(); gameaction = ga_nothing; viewactive = true; } //--------------------------------------------------------------------------- // // PROC G_LoadGame // // Can be called by the startup code or the menu task. // //--------------------------------------------------------------------------- static char *savename = NULL; void G_LoadGame(char *name) { savename = M_StringDuplicate(name); gameaction = ga_loadgame; } //--------------------------------------------------------------------------- // // PROC G_DoLoadGame // // Called by G_Ticker based on gameaction. // //--------------------------------------------------------------------------- #define VERSIONSIZE 16 void G_DoLoadGame(void) { int i; int a, b, c; char savestr[SAVESTRINGSIZE]; char vcheck[VERSIONSIZE], readversion[VERSIONSIZE]; gameaction = ga_nothing; SV_OpenRead(savename); free(savename); savename = NULL; // Skip the description field SV_Read(savestr, SAVESTRINGSIZE); memset(vcheck, 0, sizeof(vcheck)); DEH_snprintf(vcheck, VERSIONSIZE, "version %i", HERETIC_VERSION); SV_Read(readversion, VERSIONSIZE); if (strncmp(readversion, vcheck, VERSIONSIZE) != 0) { // Bad version return; } gameskill = SV_ReadByte(); gameepisode = SV_ReadByte(); gamemap = SV_ReadByte(); for (i = 0; i < MAXPLAYERS; i++) { playeringame[i] = SV_ReadByte(); } // Load a base level G_InitNew(gameskill, gameepisode, gamemap); // Create leveltime a = SV_ReadByte(); b = SV_ReadByte(); c = SV_ReadByte(); leveltime = (a << 16) + (b << 8) + c; // De-archive all the modifications P_UnArchivePlayers(); P_UnArchiveWorld(); P_UnArchiveThinkers(); P_UnArchiveSpecials(); if (SV_ReadByte() != SAVE_GAME_TERMINATOR) { // Missing savegame termination marker I_Error("Bad savegame"); } } /* ==================== = = G_InitNew = = Can be called by the startup code or the menu task = consoleplayer, displayplayer, playeringame[] should be set ==================== */ skill_t d_skill; int d_episode; int d_map; void G_DeferedInitNew(skill_t skill, int episode, int map) { d_skill = skill; d_episode = episode; d_map = map; gameaction = ga_newgame; } void G_DoNewGame(void) { G_InitNew(d_skill, d_episode, d_map); gameaction = ga_nothing; } void G_InitNew(skill_t skill, int episode, int map) { int i; int speed; static char *skyLumpNames[5] = { "SKY1", "SKY2", "SKY3", "SKY1", "SKY3" }; if (paused) { paused = false; S_ResumeSound(); } if (skill < sk_baby) skill = sk_baby; if (skill > sk_nightmare) skill = sk_nightmare; if (episode < 1) episode = 1; // Up to 9 episodes for testing if (episode > 9) episode = 9; if (map < 1) map = 1; if (map > 9) map = 9; M_ClearRandom(); if (respawnparm) { respawnmonsters = true; } else { respawnmonsters = false; } // Set monster missile speeds speed = skill == sk_nightmare; for (i = 0; MonsterMissileInfo[i].type != -1; i++) { mobjinfo[MonsterMissileInfo[i].type].speed = MonsterMissileInfo[i].speed[speed] << FRACBITS; } // Force players to be initialized upon first level load for (i = 0; i < MAXPLAYERS; i++) { players[i].playerstate = PST_REBORN; players[i].didsecret = false; } // Set up a bunch of globals usergame = true; // will be set false if a demo paused = false; demorecording = false; demoplayback = false; viewactive = true; gameepisode = episode; gamemap = map; gameskill = skill; viewactive = true; BorderNeedRefresh = true; // Set the sky map if (episode > 5) { skytexture = R_TextureNumForName(DEH_String("SKY1")); } else { skytexture = R_TextureNumForName(DEH_String(skyLumpNames[episode - 1])); } // // give one null ticcmd_t // #if 0 gametic = 0; maketic = 1; for (i = 0; i < MAXPLAYERS; i++) nettics[i] = 1; // one null event for this gametic memset(localcmds, 0, sizeof(localcmds)); memset(netcmds, 0, sizeof(netcmds)); #endif G_DoLoadLevel(); } /* =============================================================================== DEMO RECORDING =============================================================================== */ #define DEMOMARKER 0x80 void G_ReadDemoTiccmd(ticcmd_t * cmd) { if (*demo_p == DEMOMARKER) { // end of demo data stream G_CheckDemoStatus(); return; } cmd->forwardmove = ((signed char) *demo_p++); cmd->sidemove = ((signed char) *demo_p++); cmd->angleturn = ((unsigned char) *demo_p++) << 8; cmd->buttons = (unsigned char) *demo_p++; cmd->lookfly = (unsigned char) *demo_p++; cmd->arti = (unsigned char) *demo_p++; } void G_WriteDemoTiccmd(ticcmd_t * cmd) { if (gamekeydown['q']) // press q to end demo recording G_CheckDemoStatus(); *demo_p++ = cmd->forwardmove; *demo_p++ = cmd->sidemove; *demo_p++ = cmd->angleturn >> 8; *demo_p++ = cmd->buttons; *demo_p++ = cmd->lookfly; *demo_p++ = cmd->arti; demo_p -= 6; G_ReadDemoTiccmd(cmd); // make SURE it is exactly the same } /* =================== = = G_RecordDemo = =================== */ void G_RecordDemo(skill_t skill, int numplayers, int episode, int map, char *name) { int i; G_InitNew(skill, episode, map); usergame = false; M_StringCopy(demoname, name, sizeof(demoname)); M_StringConcat(demoname, ".lmp", sizeof(demoname)); demobuffer = demo_p = Z_Malloc(0x20000, PU_STATIC, NULL); *demo_p++ = skill; *demo_p++ = episode; *demo_p++ = map; for (i = 0; i < MAXPLAYERS; i++) *demo_p++ = playeringame[i]; demorecording = true; } /* =================== = = G_PlayDemo = =================== */ char *defdemoname; void G_DeferedPlayDemo(char *name) { defdemoname = name; gameaction = ga_playdemo; } void G_DoPlayDemo(void) { skill_t skill; int i, episode, map; gameaction = ga_nothing; demobuffer = demo_p = W_CacheLumpName(defdemoname, PU_STATIC); skill = *demo_p++; episode = *demo_p++; map = *demo_p++; for (i = 0; i < MAXPLAYERS; i++) playeringame[i] = *demo_p++; precache = false; // don't spend a lot of time in loadlevel G_InitNew(skill, episode, map); precache = true; usergame = false; demoplayback = true; } /* =================== = = G_TimeDemo = =================== */ void G_TimeDemo(char *name) { skill_t skill; int episode, map, i; demobuffer = demo_p = W_CacheLumpName(name, PU_STATIC); skill = *demo_p++; episode = *demo_p++; map = *demo_p++; for (i = 0; i < MAXPLAYERS; i++) { playeringame[i] = *demo_p++; } G_InitNew(skill, episode, map); usergame = false; demoplayback = true; timingdemo = true; singletics = true; } /* =================== = = 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 =================== */ boolean G_CheckDemoStatus(void) { int endtime, realtics; if (timingdemo) { float fps; endtime = I_GetTime(); realtics = endtime - starttime; fps = ((float) gametic * TICRATE) / realtics; I_Error("timed %i gametics in %i realtics (%f fps)", gametic, realtics, fps); } if (demoplayback) { if (singledemo) I_Quit(); W_ReleaseLumpName(defdemoname); demoplayback = false; D_AdvanceDemo(); return true; } if (demorecording) { *demo_p++ = DEMOMARKER; M_WriteFile(demoname, demobuffer, demo_p - demobuffer); Z_Free(demobuffer); demorecording = false; I_Error("Demo %s recorded", demoname); } return false; } /**************************************************************************/ /**************************************************************************/ //========================================================================== // // G_SaveGame // // Called by the menu task. is a 24 byte text string. // //========================================================================== void G_SaveGame(int slot, char *description) { savegameslot = slot; M_StringCopy(savedescription, description, sizeof(savedescription)); sendsave = true; } //========================================================================== // // G_DoSaveGame // // Called by G_Ticker based on gameaction. // //========================================================================== void G_DoSaveGame(void) { int i; char *filename; char verString[VERSIONSIZE]; char *description; filename = SV_Filename(savegameslot); description = savedescription; SV_Open(filename); SV_Write(description, SAVESTRINGSIZE); memset(verString, 0, sizeof(verString)); DEH_snprintf(verString, VERSIONSIZE, "version %i", HERETIC_VERSION); SV_Write(verString, VERSIONSIZE); SV_WriteByte(gameskill); SV_WriteByte(gameepisode); SV_WriteByte(gamemap); for (i = 0; i < MAXPLAYERS; i++) { SV_WriteByte(playeringame[i]); } SV_WriteByte(leveltime >> 16); SV_WriteByte(leveltime >> 8); SV_WriteByte(leveltime); P_ArchivePlayers(); P_ArchiveWorld(); P_ArchiveThinkers(); P_ArchiveSpecials(); SV_Close(filename); gameaction = ga_nothing; savedescription[0] = 0; P_SetMessage(&players[consoleplayer], DEH_String(TXT_GAMESAVED), true); free(filename); }