summaryrefslogtreecommitdiff
path: root/src/heretic/g_game.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/heretic/g_game.c')
-rw-r--r--src/heretic/g_game.c1791
1 files changed, 1791 insertions, 0 deletions
diff --git a/src/heretic/g_game.c b/src/heretic/g_game.c
new file mode 100644
index 00000000..a256b63b
--- /dev/null
+++ b/src/heretic/g_game.c
@@ -0,0 +1,1791 @@
+// Emacs style mode select -*- C++ -*-
+//-----------------------------------------------------------------------------
+//
+// Copyright(C) 1993-1996 Id Software, Inc.
+// Copyright(C) 1993-2008 Raven Software
+// Copyright(C) 2008 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.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+// 02111-1307, USA.
+//
+//-----------------------------------------------------------------------------
+
+// G_game.c
+
+#include <stdio.h>
+#include <string.h>
+#include "doomdef.h"
+#include "doomkeys.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 SVG_RAM 0
+#define SVG_FILE 1
+#define SAVE_GAME_TERMINATOR 0x1d
+#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_InitNew(skill_t skill, int episode, int map);
+
+void G_DoReborn(int playernum);
+
+void G_DoLoadLevel(void);
+void G_DoNewGame(void);
+void G_DoLoadGame(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
+{
+ mobjtype_t type;
+ 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
+};
+
+FILE *SaveGameFP;
+int SaveGameType;
+
+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
+
+short consistancy[MAXPLAYERS][BACKUPTICS];
+
+byte *savebuffer, *save_p;
+
+
+//
+// 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
+};
+
+#define SLOWTURNTICS 6
+
+#define NUMKEYS 256
+boolean gamekeydown[NUMKEYS];
+int turnheld; // for accelerative turning
+int lookheld;
+
+
+boolean mousearray[4];
+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
+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);
+}
+*/
+
+/*
+====================
+=
+= 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
+====================
+*/
+
+extern boolean inventory;
+extern int curpos;
+extern int inv_ptr;
+
+boolean usearti = true;
+
+void G_BuildTiccmd(ticcmd_t * cmd)
+{
+ int i;
+ boolean strafe, bstrafe;
+ int speed, tspeed, lspeed;
+ int forward, side;
+ int look, arti;
+ int flyheight;
+
+ extern boolean noartiskip;
+
+ // haleyjd: removed externdriver crap
+
+ 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];
+ 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])
+ side += sidemove[speed];
+ if (gamekeydown[key_strafeleft] || mousebuttons[mousebstrafeleft]
+ || joybuttons[joybstrafeleft])
+ 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
+ }
+
+ 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;
+ }
+ }
+
+
+//
+// mouse
+//
+ if (mousebuttons[mousebforward])
+ {
+ forward += forwardmove[speed];
+ }
+
+ if (mousebuttons[mousebbackward])
+ {
+ forward -= forwardmove[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;
+ }
+
+ 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 = 0;
+ mousex = mousey = 0;
+ sendpause = sendsave = paused = false;
+ memset(mousebuttons, 0, sizeof(mousebuttons));
+ memset(joybuttons, 0, sizeof(joybuttons));
+}
+
+static void SetJoyButtons(unsigned int buttons_mask)
+{
+ int i;
+
+ for (i=0; i<MAX_JOY_BUTTONS; ++i)
+ {
+ joybuttons[i] = (buttons_mask & (1 << i)) != 0;
+ }
+}
+
+/*
+===============================================================================
+=
+= G_Responder
+=
+= get info needed to make ticcmd_ts for the players
+=
+===============================================================================
+*/
+
+boolean G_Responder(event_t * ev)
+{
+ player_t *plr;
+ extern boolean MenuActive;
+
+ 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_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);
+ }
+ }
+
+ 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:
+ mousebuttons[0] = ev->data1 & 1;
+ mousebuttons[1] = ev->data1 & 2;
+ mousebuttons[2] = ev->data1 & 4;
+ 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;
+ 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.pcx");
+ 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][buf], 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)
+ {
+ strcpy(savedescription, "NET GAME");
+ }
+ else
+ {
+ strcpy(savedescription, "SAVE GAME");
+ }
+ }
+ 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)
+{
+ player_t *p;
+
+// set up the saved info
+ p = &players[player];
+
+// clear everything else to defaults
+ G_PlayerReborn(player);
+
+}
+
+
+/*
+====================
+=
+= G_PlayerFinishLevel
+=
+= Can when a player completes a level
+====================
+*/
+extern int curpos;
+extern int inv_ptr;
+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; // 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 = (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.
+//
+//---------------------------------------------------------------------------
+
+char savename[256];
+
+void G_LoadGame(char *name)
+{
+ strcpy(savename, name);
+ gameaction = ga_loadgame;
+}
+
+//---------------------------------------------------------------------------
+//
+// PROC G_DoLoadGame
+//
+// Called by G_Ticker based on gameaction.
+//
+//---------------------------------------------------------------------------
+
+#define VERSIONSIZE 16
+
+void G_DoLoadGame(void)
+{
+ int length;
+ int i;
+ int a, b, c;
+ char vcheck[VERSIONSIZE];
+
+ gameaction = ga_nothing;
+
+ length = M_ReadFile(savename, &savebuffer);
+ save_p = savebuffer + SAVESTRINGSIZE;
+ // Skip the description field
+ memset(vcheck, 0, sizeof(vcheck));
+ sprintf(vcheck, "version %i", HERETIC_VERSION);
+ if (strcmp((char *) save_p, vcheck) != 0)
+ { // Bad version
+ return;
+ }
+ save_p += VERSIONSIZE;
+ gameskill = *save_p++;
+ gameepisode = *save_p++;
+ gamemap = *save_p++;
+ for (i = 0; i < MAXPLAYERS; i++)
+ {
+ playeringame[i] = *save_p++;
+ }
+ // Load a base level
+ G_InitNew(gameskill, gameepisode, gamemap);
+
+ // Create leveltime
+ a = *save_p++;
+ b = *save_p++;
+ c = *save_p++;
+ leveltime = (a << 16) + (b << 8) + c;
+
+ // De-archive all the modifications
+ P_UnArchivePlayers();
+ P_UnArchiveWorld();
+ P_UnArchiveThinkers();
+ P_UnArchiveSpecials();
+
+ if (*save_p != SAVE_GAME_TERMINATOR)
+ { // Missing savegame termination marker
+ I_Error("Bad savegame");
+ }
+ Z_Free(savebuffer);
+}
+
+
+/*
+====================
+=
+= 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;
+}
+
+extern int skytexture;
+
+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("SKY1");
+ }
+ else
+ {
+ skytexture = R_TextureNumForName(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;
+ strcpy(demoname, name);
+ strcat(demoname, ".lmp");
+ 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;
+
+ demobuffer = demo_p = W_CacheLumpName(name, PU_STATIC);
+ skill = *demo_p++;
+ episode = *demo_p++;
+ map = *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;
+
+ if (timingdemo)
+ {
+ endtime = I_GetTime();
+ I_Error("timed %i gametics in %i realtics", gametic,
+ endtime - starttime);
+ }
+
+ 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. <description> is a 24 byte text string.
+//
+//==========================================================================
+
+void G_SaveGame(int slot, char *description)
+{
+ savegameslot = slot;
+ strcpy(savedescription, description);
+ sendsave = true;
+}
+
+//==========================================================================
+//
+// G_DoSaveGame
+//
+// Called by G_Ticker based on gameaction.
+//
+//==========================================================================
+
+void G_DoSaveGame(void)
+{
+ int i;
+ char name[100];
+ char verString[VERSIONSIZE];
+ char *description;
+
+ if (cdrom)
+ {
+ sprintf(name, SAVEGAMENAMECD "%d.hsg", savegameslot);
+ }
+ else
+ {
+ sprintf(name, SAVEGAMENAME "%d.hsg", savegameslot);
+ }
+ description = savedescription;
+
+ SV_Open(name);
+ SV_Write(description, SAVESTRINGSIZE);
+ memset(verString, 0, sizeof(verString));
+ sprintf(verString, "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(name);
+
+ gameaction = ga_nothing;
+ savedescription[0] = 0;
+ P_SetMessage(&players[consoleplayer], TXT_GAMESAVED, true);
+}
+
+//==========================================================================
+//
+// SV_Open
+//
+//==========================================================================
+
+void SV_Open(char *fileName)
+{
+ SaveGameType = SVG_FILE;
+ SaveGameFP = fopen(fileName, "wb");
+}
+
+//==========================================================================
+//
+// SV_Close
+//
+//==========================================================================
+
+void SV_Close(char *fileName)
+{
+ int length;
+
+ SV_WriteByte(SAVE_GAME_TERMINATOR);
+ if (SaveGameType == SVG_RAM)
+ {
+ length = save_p - savebuffer;
+ if (length > SAVEGAMESIZE)
+ {
+ I_Error("Savegame buffer overrun");
+ }
+ M_WriteFile(fileName, savebuffer, length);
+ Z_Free(savebuffer);
+ }
+ else
+ { // SVG_FILE
+ fclose(SaveGameFP);
+ }
+}
+
+//==========================================================================
+//
+// SV_Write
+//
+//==========================================================================
+
+void SV_Write(void *buffer, int size)
+{
+ if (SaveGameType == SVG_RAM)
+ {
+ memcpy(save_p, buffer, size);
+ save_p += size;
+ }
+ else
+ { // SVG_FILE
+ fwrite(buffer, size, 1, SaveGameFP);
+ }
+}
+
+void SV_WriteByte(byte val)
+{
+ SV_Write(&val, sizeof(byte));
+}
+
+void SV_WriteWord(unsigned short val)
+{
+ SV_Write(&val, sizeof(unsigned short));
+}
+
+void SV_WriteLong(unsigned int val)
+{
+ SV_Write(&val, sizeof(int));
+}