ref: 8d3f31b70dd68e1bb49cbad976acc0119e329ef8
dir: /src/hexen/g_game.c/
// // 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. // #include <string.h> #include "m_random.h" #include "h2def.h" #include "s_sound.h" #include "doomkeys.h" #include "i_input.h" #include "i_video.h" #include "i_system.h" #include "i_timer.h" #include "m_argv.h" #include "m_controls.h" #include "m_misc.h" #include "p_local.h" #include "v_video.h" #define AM_STARTKEY 9 // External functions // Functions void G_ReadDemoTiccmd(ticcmd_t * cmd); void G_WriteDemoTiccmd(ticcmd_t * cmd); void G_DoReborn(int playernum); void G_DoLoadLevel(void); void G_DoInitNew(void); void G_DoNewGame(void); void G_DoPlayDemo(void); void G_DoTeleportNewMap(void); void G_DoCompleted(void); void G_DoVictory(void); void G_DoWorldDone(void); void G_DoSaveGame(void); void G_DoSingleReborn(void); void H2_PageTicker(void); void H2_AdvanceDemo(void); 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]; pclass_t PlayerClass[MAXPLAYERS]; // Position indicator for cooperative net-play reborn int RebornPosition; int consoleplayer; // player taking events and displaying int displayplayer; // view being displayed int levelstarttic; // gametic at level start char *demoname; boolean demorecording; boolean longtics; // specify high resolution turning in demos boolean lowres_turn; boolean shortticfix; // calculate lowres turning like doom boolean demoplayback; boolean demoextend; boolean netdemo; byte *demobuffer, *demo_p, *demoend; boolean singledemo; // quit after playing a demo from cmdline boolean precache = true; // if true, load all graphics at start // TODO: Hexen uses 16-bit shorts for consistancy? byte consistancy[MAXPLAYERS][BACKUPTICS]; int mouseSensitivity = 5; int LeaveMap; static int LeavePosition; //#define MAXPLMOVE 0x32 // Old Heretic Max move fixed_t MaxPlayerMove[NUMCLASSES] = { 0x3C, 0x32, 0x2D, 0x31 }; fixed_t forwardmove[NUMCLASSES][2] = { {0x1D, 0x3C}, {0x19, 0x32}, {0x16, 0x2E}, {0x18, 0x31} }; fixed_t sidemove[NUMCLASSES][2] = { {0x1B, 0x3B}, {0x18, 0x28}, {0x15, 0x25}, {0x17, 0x27} }; fixed_t angleturn[3] = { 640, 1280, 320 }; // + slow turn static int *weapon_keys[] = { &key_weapon1, &key_weapon2, &key_weapon3, &key_weapon4, }; static int next_weapon = 0; #define SLOWTURNTICS 6 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; int joylook; boolean joyarray[MAX_JOY_BUTTONS + 1]; boolean *joybuttons = &joyarray[1]; // allow [-1] int savegameslot; char savedescription[32]; int vanilla_demo_limit = 1; int inventoryTics; // haleyjd: removed externdriver crap static skill_t TempSkill; static int TempEpisode; static int TempMap; boolean testcontrols = false; int testcontrols_mousespeed; //============================================================================= /* ==================== = = 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 ==================== */ boolean usearti = true; void G_BuildTiccmd(ticcmd_t *cmd, int maketic) { int i; boolean strafe, bstrafe; int speed, tspeed, lspeed; int forward, side; int look, arti; int flyheight; int pClass; // haleyjd: removed externdriver crap pClass = players[consoleplayer].class; memset(cmd, 0, sizeof(*cmd)); // cmd->consistancy = // 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]; // Allow joybspeed hack. speed = key_speed >= NUMKEYS || joybspeed >= MAX_JOY_BUTTONS || gamekeydown[key_speed] || joybuttons[joybspeed] || mousebuttons[mousebspeed]; // 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] || mousebuttons[mousebturnright] || mousebuttons[mousebturnleft]) 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; // 3; } else { lspeed = 2; // 5; } // // let movement keys cancel each other out // if (strafe) { if (gamekeydown[key_right] || mousebuttons[mousebturnright]) { side += sidemove[pClass][speed]; } if (gamekeydown[key_left] || mousebuttons[mousebturnleft]) { side -= sidemove[pClass][speed]; } if (joyxmove > 0) { side += sidemove[pClass][speed]; } if (joyxmove < 0) { side -= sidemove[pClass][speed]; } } else { if (gamekeydown[key_right] || mousebuttons[mousebturnright]) cmd->angleturn -= angleturn[tspeed]; if (gamekeydown[key_left] || mousebuttons[mousebturnleft]) cmd->angleturn += angleturn[tspeed]; if (joyxmove > 0) cmd->angleturn -= angleturn[tspeed]; if (joyxmove < 0) cmd->angleturn += angleturn[tspeed]; } if (gamekeydown[key_up]) { forward += forwardmove[pClass][speed]; } if (gamekeydown[key_down]) { forward -= forwardmove[pClass][speed]; } if (joyymove < 0) { forward += forwardmove[pClass][speed]; } if (joyymove > 0) { forward -= forwardmove[pClass][speed]; } if (gamekeydown[key_straferight] || mousebuttons[mousebstraferight] || joystrafemove > 0 || joybuttons[joybstraferight]) { side += sidemove[pClass][speed]; } if (gamekeydown[key_strafeleft] || mousebuttons[mousebstrafeleft] || joystrafemove < 0 || joybuttons[joybstrafeleft]) { side -= sidemove[pClass][speed]; } // Look up/down/center keys if (gamekeydown[key_lookup] || joylook < 0) { look = lspeed; } if (gamekeydown[key_lookdown] || joylook > 0) { 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] || mousebuttons[mousebuseartifact]) { if (gamekeydown[key_speed] && artiskip) { if (players[consoleplayer].inventory[inv_ptr].type != arti_none) { // Skip an artifact gamekeydown[key_useartifact] = false; mousebuttons[mousebuseartifact] = false; P_PlayerNextArtifact(&players[consoleplayer]); } } 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 & AFLAG_MASK; usearti = false; } } } if (gamekeydown[key_jump] || mousebuttons[mousebjump] || joybuttons[joybjump]) { cmd->arti |= AFLAG_JUMP; } if (mn_SuicideConsole) { cmd->arti |= AFLAG_SUICIDE; mn_SuicideConsole = false; } // Artifact hot keys if (gamekeydown[key_arti_all] && !cmd->arti) { gamekeydown[key_arti_all] = false; // Use one of each artifact cmd->arti = NUMARTIFACTS; } else if (gamekeydown[key_arti_health] && !cmd->arti && (players[consoleplayer].mo->health < MAXHEALTH)) { gamekeydown[key_arti_health] = false; cmd->arti = arti_health; } else if (gamekeydown[key_arti_poisonbag] && !cmd->arti) { gamekeydown[key_arti_poisonbag] = false; cmd->arti = arti_poisonbag; } else if (gamekeydown[key_arti_blastradius] && !cmd->arti) { gamekeydown[key_arti_blastradius] = false; cmd->arti = arti_blastradius; } else if (gamekeydown[key_arti_teleport] && !cmd->arti) { gamekeydown[key_arti_teleport] = false; cmd->arti = arti_teleport; } else if (gamekeydown[key_arti_teleportother] && !cmd->arti) { gamekeydown[key_arti_teleportother] = false; cmd->arti = arti_teleportother; } else if (gamekeydown[key_arti_egg] && !cmd->arti) { gamekeydown[key_arti_egg] = false; cmd->arti = arti_egg; } else if (gamekeydown[key_arti_invulnerability] && !cmd->arti && !players[consoleplayer].powers[pw_invulnerability]) { gamekeydown[key_arti_invulnerability] = false; cmd->arti = arti_invulnerability; } // // 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 } // Weapon cycling. Switch to previous or next weapon. // (Disabled when player is a pig). if (gamestate == GS_LEVEL && players[consoleplayer].morphTics == 0 && next_weapon != 0) { int start_i; if (players[consoleplayer].pendingweapon == WP_NOCHANGE) { i = players[consoleplayer].readyweapon; } else { i = players[consoleplayer].pendingweapon; } // Don't loop forever. start_i = i; do { i = (i + next_weapon + NUMWEAPONS) % NUMWEAPONS; } while (i != start_i && !players[consoleplayer].weaponowned[i]); cmd->buttons |= BT_CHANGE; cmd->buttons |= i << BT_WEAPONSHIFT; } else { for (i=0; i<arrlen(weapon_keys); ++i) { int key = *weapon_keys[i]; if (gamekeydown[key]) { cmd->buttons |= BT_CHANGE; cmd->buttons |= i<<BT_WEAPONSHIFT; break; } } } next_weapon = 0; // // mouse // if (mousebuttons[mousebforward]) { forward += forwardmove[pClass][speed]; } if (mousebuttons[mousebbackward]) { forward -= forwardmove[pClass][speed]; } // Double click to use can be disabled if (dclick_use) { // // forward double click // if (mousebuttons[mousebforward] != dclickstate && dclicktime > 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; } if (mousex == 0) { testcontrols_mousespeed = 0; } forward += mousey; mousex = mousey = 0; if (forward > MaxPlayerMove[pClass]) { forward = MaxPlayerMove[pClass]; } else if (forward < -MaxPlayerMove[pClass]) { forward = -MaxPlayerMove[pClass]; } if (side > MaxPlayerMove[pClass]) { side = MaxPlayerMove[pClass]; } else if (side < -MaxPlayerMove[pClass]) { side = -MaxPlayerMove[pClass]; } if (players[consoleplayer].powers[pw_speed] && !players[consoleplayer].morphTics) { // Adjust for a player with a speed artifact forward = (3 * forward) >> 1; side = (3 * side) >> 1; } 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); } if (lowres_turn) { if (shortticfix) { static signed short carry = 0; signed short desired_angleturn; desired_angleturn = cmd->angleturn + carry; // round angleturn to the nearest 256 unit boundary // for recording demos with single byte values for turn cmd->angleturn = (desired_angleturn + 128) & 0xff00; // Carry forward the error from the reduced resolution to the // next tic, so that successive small movements can accumulate. carry = desired_angleturn - cmd->angleturn; } else { // truncate angleturn to the nearest 256 boundary // for recording demos with single byte values for turn cmd->angleturn &= 0xff00; } } } /* ============== = = 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)); } SN_StopAllSequences(); P_SetupLevel(gameepisode, gamemap, 0, gameskill); displayplayer = consoleplayer; // view the guy you are playing gameaction = ga_nothing; Z_CheckHeap(); // // clear cmd building stuff // memset(gamekeydown, 0, sizeof(gamekeydown)); joyxmove = joyymove = joystrafemove = joylook = 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; i<MAX_JOY_BUTTONS; ++i) { int button_on = (buttons_mask & (1 << i)) != 0; // Detect button press: if (!joybuttons[i] && button_on) { // Weapon cycling: if (i == joybprevweapon) { next_weapon = -1; } else if (i == joybnextweapon) { next_weapon = 1; } } joybuttons[i] = button_on; } } // If an InventoryMove*() function is called when the inventory is not active, // it will instead activate the inventory without attempting to change the // selected item. This action is indicated by a return value of false. // Otherwise, it attempts to change items and will return a value of true. static boolean InventoryMoveLeft() { inventoryTics = 5 * 35; if (!inventory) { inventory = true; return false; } inv_ptr--; if (inv_ptr < 0) { inv_ptr = 0; } else { curpos--; if (curpos < 0) { curpos = 0; } } return true; } static boolean InventoryMoveRight() { player_t *plr; plr = &players[consoleplayer]; inventoryTics = 5 * 35; if (!inventory) { inventory = true; return false; } 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; } static void SetMouseButtons(unsigned int buttons_mask) { int i; player_t *plr; plr = &players[consoleplayer]; for (i=0; i<MAX_MOUSE_BUTTONS; ++i) { unsigned int button_on = (buttons_mask & (1 << i)) != 0; // Detect button press: if (!mousebuttons[i] && button_on) { if (i == mousebprevweapon) { next_weapon = -1; } else if (i == mousebnextweapon) { next_weapon = 1; } else if (i == mousebinvleft) { InventoryMoveLeft(); } else if (i == mousebinvright) { InventoryMoveRight(); } else if (i == mousebuseartifact) { if (!inventory) { plr->readyArtifact = plr->inventory[inv_ptr].type; } usearti = true; } } mousebuttons[i] = button_on; } } /* =============================================================================== = = G_Responder = = get info needed to make ticcmd_ts for the players = =============================================================================== */ boolean G_Responder(event_t * ev) { player_t *plr; plr = &players[consoleplayer]; if (ev->type == 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_spy && !deathmatch) { // Cycle the display player do { displayplayer++; if (displayplayer == maxplayers) { displayplayer = 0; } } while (!playeringame[displayplayer] && displayplayer != consoleplayer); return (true); } if (CT_Responder(ev)) { // Chat ate the event return (true); } if (gamestate == GS_LEVEL) { 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) { if (InventoryMoveLeft()) { return (true); } break; } if (ev->data1 == key_invright) { if (InventoryMoveRight()) { return (true); } break; } 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; joylook = ev->data5; 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_initnew: G_DoInitNew(); break; case ga_newgame: G_DoNewGame(); break; case ga_loadgame: Draw_LoadIcon(); G_DoLoadGame(); break; case ga_savegame: Draw_SaveIcon(); G_DoSaveGame(); break; case ga_singlereborn: G_DoSingleReborn(); break; case ga_playdemo: G_DoPlayDemo(); break; case ga_screenshot: V_ScreenShot("HEXEN%02i.%s"); P_SetMessage(&players[consoleplayer], "SCREEN SHOT", false); gameaction = ga_nothing; break; case ga_leavemap: Draw_TeleportIcon(); G_DoTeleportNewMap(); 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 && !netdemo && !(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, "NET GAME", sizeof(savedescription)); } else { M_StringCopy(savedescription, "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: H2_PageTicker(); break; } } /* ============================================================================== PLAYER STRUCTURE FUNCTIONS also see P_SpawnPlayer in P_Things ============================================================================== */ //========================================================================== // // G_PlayerExitMap // // Called when the player leaves a map. // //========================================================================== void G_PlayerExitMap(int playerNumber) { int i; player_t *player; int flightPower; player = &players[playerNumber]; // if(deathmatch) // { // // Strip all but one of each type of artifact // for(i = 0; i < player->inventorySlotNum; i++) // { // player->inventory[i].count = 1; // } // player->artifactCount = player->inventorySlotNum; // } // else // Strip all current powers (retain flight) flightPower = player->powers[pw_flight]; memset(player->powers, 0, sizeof(player->powers)); player->powers[pw_flight] = flightPower; if (deathmatch) { player->powers[pw_flight] = 0; } else { if (P_GetMapCluster(gamemap) != P_GetMapCluster(LeaveMap)) { // Entering new cluster // Strip all keys player->keys = 0; // Strip flight artifact for (i = 0; i < 25; i++) { player->powers[pw_flight] = 0; P_PlayerUseArtifact(player, arti_fly); } player->powers[pw_flight] = 0; } } if (player->morphTics) { player->readyweapon = player->mo->special1.i; // Restore weapon player->morphTics = 0; } player->messageTics = 0; player->lookdir = 0; player->mo->flags &= ~MF_SHADOW; // Remove invisibility player->extralight = 0; // Remove weapon flashes player->fixedcolormap = 0; // Remove torch player->damagecount = 0; // No palette changes player->bonuscount = 0; player->poisoncount = 0; if (player == &players[consoleplayer]) { SB_state = -1; // refresh the status bar viewangleoffset = 0; } } //========================================================================== // // G_PlayerReborn // // Called after a player dies. Almost everything is cleared and // initialized. // //========================================================================== void G_PlayerReborn(int player) { player_t *p; int frags[MAXPLAYERS]; int killcount, itemcount, secretcount; unsigned int worldTimer; memcpy(frags, players[player].frags, sizeof(frags)); killcount = players[player].killcount; itemcount = players[player].itemcount; secretcount = players[player].secretcount; worldTimer = players[player].worldTimer; p = &players[player]; 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; players[player].worldTimer = worldTimer; players[player].class = PlayerClass[player]; p->usedown = p->attackdown = true; // don't do anything immediately p->playerstate = PST_LIVE; p->health = MAXHEALTH; p->readyweapon = p->pendingweapon = WP_FIRST; p->weaponowned[WP_FIRST] = true; p->messageTics = 0; p->lookdir = 0; localQuakeHappening[player] = false; if (p == &players[consoleplayer]) { SB_state = -1; // refresh the status bar inv_ptr = 0; // reset the inventory pointer curpos = 0; viewangleoffset = 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_TELEPORT); // 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; // This check has been moved to p_setup.c:P_LoadThings() //if (selections < 8) // I_Error ("Only %i deathmatch spots, 8 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[0][playernum]); } //========================================================================== // // G_DoReborn // //========================================================================== void G_DoReborn(int playernum) { int i; boolean oldWeaponowned[NUMWEAPONS]; int oldKeys; int oldPieces; boolean foundSpot; int bestWeapon; // quit demo unless -demoextend if (!demoextend && G_CheckDemoStatus()) { return; } if (!netgame) { if (SV_RebornSlotAvailable()) { // Use the reborn code if the slot is available gameaction = ga_singlereborn; } else { // Start a new game if there's no reborn info gameaction = ga_newgame; } } else { // Net-game players[playernum].mo->player = NULL; // Dissassociate the corpse if (deathmatch) { // Spawn at random spot if in death match G_DeathMatchSpawnPlayer(playernum); return; } // Cooperative net-play, retain keys and weapons oldKeys = players[playernum].keys; oldPieces = players[playernum].pieces; for (i = 0; i < NUMWEAPONS; i++) { oldWeaponowned[i] = players[playernum].weaponowned[i]; } foundSpot = false; if (G_CheckSpot(playernum, &playerstarts[RebornPosition][playernum])) { // Appropriate player start spot is open P_SpawnPlayer(&playerstarts[RebornPosition][playernum]); foundSpot = true; } else { // Try to spawn at one of the other player start spots for (i = 0; i < maxplayers; i++) { if (G_CheckSpot(playernum, &playerstarts[RebornPosition][i])) { // Found an open start spot // Fake as other player playerstarts[RebornPosition][i].type = playernum + 1; P_SpawnPlayer(&playerstarts[RebornPosition][i]); // Restore proper player type playerstarts[RebornPosition][i].type = i + 1; foundSpot = true; break; } } } if (foundSpot == false) { // Player's going to be inside something P_SpawnPlayer(&playerstarts[RebornPosition][playernum]); } // Restore keys and weapons players[playernum].keys = oldKeys; players[playernum].pieces = oldPieces; for (bestWeapon = 0, i = 0; i < NUMWEAPONS; i++) { if (oldWeaponowned[i]) { bestWeapon = i; players[playernum].weaponowned[i] = true; } } players[playernum].mana[MANA_1] = 25; players[playernum].mana[MANA_2] = 25; if (bestWeapon) { // Bring up the best weapon players[playernum].pendingweapon = bestWeapon; } } } void G_ScreenShot(void) { gameaction = ga_screenshot; } //========================================================================== // // G_StartNewInit // //========================================================================== void G_StartNewInit(void) { SV_InitBaseSlot(); SV_ClearRebornSlot(); P_ACSInitNewGame(); // Default the player start spot group to 0 RebornPosition = 0; } //========================================================================== // // G_StartNewGame // //========================================================================== void G_StartNewGame(skill_t skill) { int realMap; G_StartNewInit(); realMap = P_TranslateMap(1); if (realMap == -1) { realMap = 1; } G_InitNew(TempSkill, 1, realMap); } //========================================================================== // // G_TeleportNewMap // // Only called by the warp cheat code. Works just like normal map to map // teleporting, but doesn't do any interlude stuff. // //========================================================================== void G_TeleportNewMap(int map, int position) { gameaction = ga_leavemap; LeaveMap = map; LeavePosition = position; } //========================================================================== // // G_DoTeleportNewMap // //========================================================================== void G_DoTeleportNewMap(void) { SV_MapTeleport(LeaveMap, LeavePosition); gamestate = GS_LEVEL; gameaction = ga_nothing; RebornPosition = LeavePosition; } /* boolean secretexit; void G_ExitLevel (void) { secretexit = false; gameaction = ga_completed; } void G_SecretExitLevel (void) { secretexit = true; gameaction = ga_completed; } */ //========================================================================== // // G_Completed // // Starts intermission routine, which is used only during hub exits, // and DeathMatch games. //========================================================================== void G_Completed(int map, int position) { if (gamemode == shareware && map > 4) { P_SetMessage(&players[consoleplayer], "ACCESS DENIED -- DEMO", true); S_StartSound(NULL, SFX_CHAT); return; } gameaction = ga_completed; LeaveMap = map; LeavePosition = position; } void G_DoCompleted(void) { int i; gameaction = ga_nothing; // quit demo unless -demoextend if (!demoextend && G_CheckDemoStatus()) { return; } for (i = 0; i < maxplayers; i++) { if (playeringame[i]) { G_PlayerExitMap(i); } } if (LeaveMap == -1 && LeavePosition == -1) { gameaction = ga_victory; return; } else { gamestate = GS_INTERMISSION; IN_Start(); } /* int i; static int afterSecret[3] = { 7, 5, 5 }; 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; } //========================================================================== // // G_DoSingleReborn // // Called by G_Ticker based on gameaction. Loads a game from the reborn // save slot. // //========================================================================== void G_DoSingleReborn(void) { gameaction = ga_nothing; SV_LoadGame(SV_GetRebornSlot()); SB_SetClassData(); } //========================================================================== // // G_LoadGame // // Can be called by the startup code or the menu task. // //========================================================================== static int GameLoadSlot; void G_LoadGame(int slot) { GameLoadSlot = slot; gameaction = ga_loadgame; } //========================================================================== // // G_DoLoadGame // // Called by G_Ticker based on gameaction. // //========================================================================== void G_DoLoadGame(void) { gameaction = ga_nothing; SV_LoadGame(GameLoadSlot); if (!netgame) { // Copy the base slot to the reborn slot SV_UpdateRebornSlot(); } SB_SetClassData(); } //========================================================================== // // G_SaveGame // // Called by the menu task. <description> 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) { SV_SaveGame(savegameslot, savedescription); gameaction = ga_nothing; savedescription[0] = 0; P_SetMessage(&players[consoleplayer], TXT_GAMESAVED, true); } //========================================================================== // // G_DeferredNewGame // //========================================================================== void G_DeferredNewGame(skill_t skill) { TempSkill = skill; gameaction = ga_newgame; } //========================================================================== // // G_DoNewGame // //========================================================================== void G_DoNewGame(void) { G_StartNewGame(TempSkill); gameaction = ga_nothing; } /* ==================== = = G_InitNew = = Can be called by the startup code or the menu task = consoleplayer, displayplayer, playeringame[] should be set ==================== */ void G_DeferedInitNew(skill_t skill, int episode, int map) { TempSkill = skill; TempEpisode = episode; TempMap = map; gameaction = ga_initnew; } void G_DoInitNew(void) { SV_InitBaseSlot(); G_InitNew(TempSkill, TempEpisode, TempMap); gameaction = ga_nothing; } void G_InitNew(skill_t skill, int episode, int map) { int i; if (paused) { paused = false; S_ResumeSound(); } if (skill < sk_baby) { skill = sk_baby; } if (skill > sk_nightmare) { skill = sk_nightmare; } if (map < 1) { map = 1; } if (map > 99) { map = 99; } M_ClearRandom(); // Force players to be initialized upon first level load for (i = 0; i < maxplayers; i++) { players[i].playerstate = PST_REBORN; players[i].worldTimer = 0; } // Set up a bunch of globals if (!demoextend) { // This prevents map-loading from interrupting a demo. // demoextend is set back to false only if starting a new game or // loading a saved one from the menu, and only during playback. demorecording = false; demoplayback = false; netdemo = false; usergame = true; // will be set false if a demo } paused = false; viewactive = true; gameepisode = episode; gamemap = map; gameskill = skill; BorderNeedRefresh = true; // Initialize the sky R_InitSky(map); // Give one null ticcmd_t //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)); G_DoLoadLevel(); } /* =============================================================================== DEMO RECORDING =============================================================================== */ #define DEMOMARKER 0x80 #define DEMOHEADER_RESPAWN 0x20 #define DEMOHEADER_LONGTICS 0x10 #define DEMOHEADER_NOMONSTERS 0x02 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++); // If this is a longtics demo, read back in higher resolution if (longtics) { cmd->angleturn = *demo_p++; cmd->angleturn |= (*demo_p++) << 8; } else { 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++; } // Increase the size of the demo buffer to allow unlimited demos static void IncreaseDemoBuffer(void) { int current_length; byte *new_demobuffer; byte *new_demop; int new_length; // Find the current size current_length = demoend - demobuffer; // Generate a new buffer twice the size new_length = current_length * 2; new_demobuffer = Z_Malloc(new_length, PU_STATIC, 0); new_demop = new_demobuffer + (demo_p - demobuffer); // Copy over the old data memcpy(new_demobuffer, demobuffer, current_length); // Free the old buffer and point the demo pointers at the new buffer. Z_Free(demobuffer); demobuffer = new_demobuffer; demo_p = new_demop; demoend = demobuffer + new_length; } void G_WriteDemoTiccmd(ticcmd_t * cmd) { byte *demo_start; if (gamekeydown[key_demo_quit]) // press to end demo recording G_CheckDemoStatus(); demo_start = demo_p; *demo_p++ = cmd->forwardmove; *demo_p++ = cmd->sidemove; // If this is a longtics demo, record in higher resolution if (longtics) { *demo_p++ = (cmd->angleturn & 0xff); *demo_p++ = (cmd->angleturn >> 8) & 0xff; } else { *demo_p++ = cmd->angleturn >> 8; } *demo_p++ = cmd->buttons; *demo_p++ = cmd->lookfly; *demo_p++ = cmd->arti; // reset demo pointer back demo_p = demo_start; if (demo_p > demoend - 16) { if (vanilla_demo_limit) { // no more space G_CheckDemoStatus(); return; } else { // Vanilla demo limit disabled: unlimited // demo lengths! IncreaseDemoBuffer(); } } G_ReadDemoTiccmd(cmd); // make SURE it is exactly the same } /* =================== = = G_RecordDemo = =================== */ void G_RecordDemo(skill_t skill, int numplayers, int episode, int map, const char *name) { size_t demoname_size; int i; int maxsize; //! // @category demo // // Record or playback a demo with high resolution turning. // longtics = D_NonVanillaRecord(M_ParmExists("-longtics"), "vvHeretic longtics demo"); // If not recording a longtics demo, record in low res lowres_turn = !longtics; //! // @category demo // // Smooth out low resolution turning when recording a demo. // shortticfix = M_ParmExists("-shortticfix"); G_InitNew(skill, episode, map); usergame = false; demoname_size = strlen(name) + 5; demoname = Z_Malloc(demoname_size, PU_STATIC, NULL); M_snprintf(demoname, demoname_size, "%s.lmp", name); maxsize = 0x20000; //! // @arg <size> // @category demo // @vanilla // // Specify the demo buffer size (KiB) // i = M_CheckParmWithArgs("-maxdemo", 1); if (i) maxsize = atoi(myargv[i + 1]) * 1024; demobuffer = Z_Malloc(maxsize, PU_STATIC, NULL); demoend = demobuffer + maxsize; demo_p = demobuffer; *demo_p++ = skill; *demo_p++ = episode; *demo_p++ = map; // Write special parameter bits onto player one byte. // This aligns with vvHeretic demo usage. Hexen demo support has no // precedent here so consistency with another game is chosen: // 0x20 = -respawn // 0x10 = -longtics // 0x02 = -nomonsters *demo_p = 1; // assume player one exists if (D_NonVanillaRecord(respawnparm, "vvHeretic -respawn header flag")) { *demo_p |= DEMOHEADER_RESPAWN; } if (longtics) { *demo_p |= DEMOHEADER_LONGTICS; } if (D_NonVanillaRecord(nomonsters, "vvHeretic -nomonsters header flag")) { *demo_p |= DEMOHEADER_NOMONSTERS; } demo_p++; *demo_p++ = PlayerClass[0]; for (i = 1; i < maxplayers; i++) { *demo_p++ = playeringame[i]; *demo_p++ = PlayerClass[i]; } demorecording = true; } /* =================== = = G_PlayDemo = =================== */ static const char *defdemoname; void G_DeferedPlayDemo(const char *name) { defdemoname = name; gameaction = ga_playdemo; } void G_DoPlayDemo(void) { skill_t skill; int i, lumpnum, episode, map; gameaction = ga_nothing; lumpnum = W_GetNumForName(defdemoname); demobuffer = W_CacheLumpNum(lumpnum, PU_STATIC); demo_p = demobuffer; skill = *demo_p++; episode = *demo_p++; map = *demo_p++; // When recording we store some extra options inside the upper bits // of the player 1 present byte. However, this is a non-vanilla extension. // Note references to vvHeretic here; these are the extensions used by // vvHeretic, which we're just reusing for Hexen demos too. There is no // vvHexen. if (D_NonVanillaPlayback((*demo_p & DEMOHEADER_LONGTICS) != 0, lumpnum, "vvHeretic longtics demo")) { longtics = true; } if (D_NonVanillaPlayback((*demo_p & DEMOHEADER_RESPAWN) != 0, lumpnum, "vvHeretic -respawn header flag")) { respawnparm = true; } if (D_NonVanillaPlayback((*demo_p & DEMOHEADER_NOMONSTERS) != 0, lumpnum, "vvHeretic -nomonsters header flag")) { nomonsters = true; } for (i = 0; i < maxplayers; i++) { playeringame[i] = (*demo_p++) != 0; PlayerClass[i] = *demo_p++; } if (playeringame[1] || M_ParmExists("-solo-net") || M_ParmExists("-netdemo")) { netgame = true; } // Initialize world info, etc. G_StartNewInit(); precache = false; // don't spend a lot of time in loadlevel G_InitNew(skill, episode, map); precache = true; usergame = false; demoplayback = true; if (netgame) { netdemo = 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++; // Read special parameter bits: see G_RecordDemo() for details. longtics = (*demo_p & DEMOHEADER_LONGTICS) != 0; // don't overwrite arguments from the command line respawnparm |= (*demo_p & DEMOHEADER_RESPAWN) != 0; nomonsters |= (*demo_p & DEMOHEADER_NOMONSTERS) != 0; for (i = 0; i < maxplayers; i++) { playeringame[i] = (*demo_p++) != 0; PlayerClass[i] = *demo_p++; } if (playeringame[1] || M_ParmExists("-solo-net") || M_ParmExists("-netdemo")) { netgame = true; } G_InitNew(skill, episode, map); starttime = I_GetTime(); usergame = false; demoplayback = true; timingdemo = true; singletics = true; if (netgame) { netdemo = 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; netdemo = false; netgame = false; H2_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; }