Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
DOOM/linuxdoom-1.10/g_game.c
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
1690 lines (1394 sloc)
35.6 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Emacs style mode select -*- C++ -*- | |
//----------------------------------------------------------------------------- | |
// | |
// $Id:$ | |
// | |
// Copyright (C) 1993-1996 by id Software, Inc. | |
// | |
// This source is available for distribution and/or modification | |
// only under the terms of the DOOM Source Code License as | |
// published by id Software. All rights reserved. | |
// | |
// The source is distributed in the hope that it will be useful, | |
// but WITHOUT ANY WARRANTY; without even the implied warranty of | |
// FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License | |
// for more details. | |
// | |
// $Log:$ | |
// | |
// DESCRIPTION: none | |
// | |
//----------------------------------------------------------------------------- | |
static const char | |
rcsid[] = "$Id: g_game.c,v 1.8 1997/02/03 22:45:09 b1 Exp $"; | |
#include <string.h> | |
#include <stdlib.h> | |
#include "doomdef.h" | |
#include "doomstat.h" | |
#include "z_zone.h" | |
#include "f_finale.h" | |
#include "m_argv.h" | |
#include "m_misc.h" | |
#include "m_menu.h" | |
#include "m_random.h" | |
#include "i_system.h" | |
#include "p_setup.h" | |
#include "p_saveg.h" | |
#include "p_tick.h" | |
#include "d_main.h" | |
#include "wi_stuff.h" | |
#include "hu_stuff.h" | |
#include "st_stuff.h" | |
#include "am_map.h" | |
// Needs access to LFB. | |
#include "v_video.h" | |
#include "w_wad.h" | |
#include "p_local.h" | |
#include "s_sound.h" | |
// Data. | |
#include "dstrings.h" | |
#include "sounds.h" | |
// SKY handling - still the wrong place. | |
#include "r_data.h" | |
#include "r_sky.h" | |
#include "g_game.h" | |
#define SAVEGAMESIZE 0x2c000 | |
#define SAVESTRINGSIZE 24 | |
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); | |
gameaction_t gameaction; | |
gamestate_t gamestate; | |
skill_t gameskill; | |
boolean respawnmonsters; | |
int gameepisode; | |
int gamemap; | |
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 | |
boolean nodrawers; // for comparative timing purposes | |
boolean noblit; // for comparative timing purposes | |
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 | |
char demoname[32]; | |
boolean demorecording; | |
boolean demoplayback; | |
boolean netdemo; | |
byte* demobuffer; | |
byte* demo_p; | |
byte* demoend; | |
boolean singledemo; // quit after playing a demo from cmdline | |
boolean precache = true; // if true, load all graphics at start | |
wbstartstruct_t wminfo; // parms for world map / intermission | |
short consistancy[MAXPLAYERS][BACKUPTICS]; | |
byte* savebuffer; | |
// | |
// controls (have defaults) | |
// | |
int key_right; | |
int key_left; | |
int key_up; | |
int key_down; | |
int key_strafeleft; | |
int key_straferight; | |
int key_fire; | |
int key_use; | |
int key_strafe; | |
int key_speed; | |
int mousebfire; | |
int mousebstrafe; | |
int mousebforward; | |
int joybfire; | |
int joybstrafe; | |
int joybuse; | |
int joybspeed; | |
#define MAXPLMOVE (forwardmove[1]) | |
#define TURBOTHRESHOLD 0x32 | |
fixed_t forwardmove[2] = {0x19, 0x32}; | |
fixed_t sidemove[2] = {0x18, 0x28}; | |
fixed_t angleturn[3] = {640, 1280, 320}; // + slow turn | |
#define SLOWTURNTICS 6 | |
#define NUMKEYS 256 | |
boolean gamekeydown[NUMKEYS]; | |
int turnheld; // for accelerative turning | |
boolean mousearray[4]; | |
boolean* mousebuttons = &mousearray[1]; // allow [-1] | |
// mouse values are used once | |
int mousex; | |
int mousey; | |
int dclicktime; | |
int dclickstate; | |
int dclicks; | |
int dclicktime2; | |
int dclickstate2; | |
int dclicks2; | |
// joystick values are repeated | |
int joyxmove; | |
int joyymove; | |
boolean joyarray[5]; | |
boolean* joybuttons = &joyarray[1]; // allow [-1] | |
int savegameslot; | |
char savedescription[32]; | |
#define BODYQUESIZE 32 | |
mobj_t* bodyque[BODYQUESIZE]; | |
int bodyqueslot; | |
void* statcopy; // for statistics driver | |
int G_CmdChecksum (ticcmd_t* cmd) | |
{ | |
int i; | |
int sum = 0; | |
for (i=0 ; i< sizeof(*cmd)/4 - 1 ; i++) | |
sum += ((int *)cmd)[i]; | |
return sum; | |
} | |
// | |
// G_BuildTiccmd | |
// Builds a ticcmd from all of the available inputs | |
// or reads it from the demo buffer. | |
// If recording a demo, write it out | |
// | |
void G_BuildTiccmd (ticcmd_t* cmd) | |
{ | |
int i; | |
boolean strafe; | |
boolean bstrafe; | |
int speed; | |
int tspeed; | |
int forward; | |
int side; | |
ticcmd_t* base; | |
base = I_BaseTiccmd (); // empty, or external driver | |
memcpy (cmd,base,sizeof(*cmd)); | |
cmd->consistancy = | |
consistancy[consoleplayer][maketic%BACKUPTICS]; | |
strafe = gamekeydown[key_strafe] || mousebuttons[mousebstrafe] | |
|| joybuttons[joybstrafe]; | |
speed = gamekeydown[key_speed] || joybuttons[joybspeed]; | |
forward = side = 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; | |
// let movement keys cancel each other out | |
if (strafe) | |
{ | |
if (gamekeydown[key_right]) | |
{ | |
// fprintf(stderr, "strafe right\n"); | |
side += sidemove[speed]; | |
} | |
if (gamekeydown[key_left]) | |
{ | |
// fprintf(stderr, "strafe left\n"); | |
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]) | |
{ | |
// fprintf(stderr, "up\n"); | |
forward += forwardmove[speed]; | |
} | |
if (gamekeydown[key_down]) | |
{ | |
// fprintf(stderr, "down\n"); | |
forward -= forwardmove[speed]; | |
} | |
if (joyymove < 0) | |
forward += forwardmove[speed]; | |
if (joyymove > 0) | |
forward -= forwardmove[speed]; | |
if (gamekeydown[key_straferight]) | |
side += sidemove[speed]; | |
if (gamekeydown[key_strafeleft]) | |
side -= sidemove[speed]; | |
// buttons | |
cmd->chatchar = HU_dequeueChatChar(); | |
if (gamekeydown[key_fire] || mousebuttons[mousebfire] | |
|| joybuttons[joybfire]) | |
cmd->buttons |= BT_ATTACK; | |
if (gamekeydown[key_use] || joybuttons[joybuse] ) | |
{ | |
cmd->buttons |= BT_USE; | |
// clear double clicks if hit use button | |
dclicks = 0; | |
} | |
// chainsaw overrides | |
for (i=0 ; i<NUMWEAPONS-1 ; i++) | |
if (gamekeydown['1'+i]) | |
{ | |
cmd->buttons |= BT_CHANGE; | |
cmd->buttons |= i<<BT_WEAPONSHIFT; | |
break; | |
} | |
// mouse | |
if (mousebuttons[mousebforward]) | |
forward += forwardmove[speed]; | |
// 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; | |
} | |
} | |
forward += mousey; | |
if (strafe) | |
side += mousex*2; | |
else | |
cmd->angleturn -= mousex*0x8; | |
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; | |
// 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 | |
// | |
extern gamestate_t wipegamestate; | |
void G_DoLoadLevel (void) | |
{ | |
int i; | |
// Set the sky map. | |
// First thing, we have a dummy sky texture name, | |
// a flat. The data is in the WAD only because | |
// we look for an actual index, instead of simply | |
// setting one. | |
skyflatnum = R_FlatNumForName ( SKYFLATNAME ); | |
// DOOM determines the sky texture to be used | |
// depending on the current episode, and the game version. | |
if ( (gamemode == commercial) | |
|| ( gamemode == pack_tnt ) | |
|| ( gamemode == pack_plut ) ) | |
{ | |
skytexture = R_TextureNumForName ("SKY3"); | |
if (gamemap < 12) | |
skytexture = R_TextureNumForName ("SKY1"); | |
else | |
if (gamemap < 21) | |
skytexture = R_TextureNumForName ("SKY2"); | |
} | |
levelstarttic = gametic; // for time calculation | |
if (wipegamestate == GS_LEVEL) | |
wipegamestate = -1; // force a wipe | |
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)); | |
} | |
// | |
// G_Responder | |
// Get info needed to make ticcmd_ts for the players. | |
// | |
boolean G_Responder (event_t* ev) | |
{ | |
// allow spy mode changes even during the demo | |
if (gamestate == GS_LEVEL && ev->type == ev_keydown | |
&& ev->data1 == KEY_F12 && (singledemo || !deathmatch) ) | |
{ | |
// spy mode | |
do | |
{ | |
displayplayer++; | |
if (displayplayer == MAXPLAYERS) | |
displayplayer = 0; | |
} while (!playeringame[displayplayer] && displayplayer != consoleplayer); | |
return true; | |
} | |
// any other key pops up menu if in demos | |
if (gameaction == ga_nothing && !singledemo && | |
(demoplayback || gamestate == GS_DEMOSCREEN) | |
) | |
{ | |
if (ev->type == ev_keydown || | |
(ev->type == ev_mouse && ev->data1) || | |
(ev->type == ev_joystick && ev->data1) ) | |
{ | |
M_StartControlPanel (); | |
return true; | |
} | |
return false; | |
} | |
if (gamestate == GS_LEVEL) | |
{ | |
#if 0 | |
if (devparm && ev->type == ev_keydown && ev->data1 == ';') | |
{ | |
G_DeathMatchSpawnPlayer (0); | |
return true; | |
} | |
#endif | |
if (HU_Responder (ev)) | |
return true; // chat ate the event | |
if (ST_Responder (ev)) | |
return true; // status window ate it | |
if (AM_Responder (ev)) | |
return true; // automap ate it | |
} | |
if (gamestate == GS_FINALE) | |
{ | |
if (F_Responder (ev)) | |
return true; // finale ate the event | |
} | |
switch (ev->type) | |
{ | |
case ev_keydown: | |
if (ev->data1 == KEY_PAUSE) | |
{ | |
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: | |
joybuttons[0] = ev->data1 & 1; | |
joybuttons[1] = ev->data1 & 2; | |
joybuttons[2] = ev->data1 & 4; | |
joybuttons[3] = ev->data1 & 8; | |
joyxmove = ev->data2; | |
joyymove = ev->data3; | |
return true; // eat events | |
default: | |
break; | |
} | |
return false; | |
} | |
// | |
// G_Ticker | |
// Make ticcmd_ts for the players. | |
// | |
void G_Ticker (void) | |
{ | |
int i; | |
int buf; | |
ticcmd_t* cmd; | |
// 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_completed: | |
G_DoCompleted (); | |
break; | |
case ga_victory: | |
F_StartFinale (); | |
break; | |
case ga_worlddone: | |
G_DoWorldDone (); | |
break; | |
case ga_screenshot: | |
M_ScreenShot (); | |
gameaction = ga_nothing; | |
break; | |
case ga_nothing: | |
break; | |
} | |
} | |
// get commands, check consistancy, | |
// and build new consistancy check | |
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); | |
// check for turbo cheats | |
if (cmd->forwardmove > TURBOTHRESHOLD | |
&& !(gametic&31) && ((gametic>>5)&3) == i ) | |
{ | |
static char turbomessage[80]; | |
extern char *player_names[4]; | |
sprintf (turbomessage, "%s is turbo!",player_names[i]); | |
players[consoleplayer].message = turbomessage; | |
} | |
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]) | |
strcpy (savedescription, "NET GAME"); | |
savegameslot = | |
(players[i].cmd.buttons & BTS_SAVEMASK)>>BTS_SAVESHIFT; | |
gameaction = ga_savegame; | |
break; | |
} | |
} | |
} | |
} | |
// do main actions | |
switch (gamestate) | |
{ | |
case GS_LEVEL: | |
P_Ticker (); | |
ST_Ticker (); | |
AM_Ticker (); | |
HU_Ticker (); | |
break; | |
case GS_INTERMISSION: | |
WI_Ticker (); | |
break; | |
case GS_FINALE: | |
F_Ticker (); | |
break; | |
case GS_DEMOSCREEN: | |
D_PageTicker (); | |
break; | |
} | |
} | |
// | |
// PLAYER STRUCTURE FUNCTIONS | |
// also see P_SpawnPlayer in P_Things | |
// | |
// | |
// G_InitPlayer | |
// Called at the start. | |
// Called by the game initialization functions. | |
// | |
void G_InitPlayer (int player) | |
{ | |
player_t* p; | |
// set up the saved info | |
p = &players[player]; | |
// clear everything else to defaults | |
G_PlayerReborn (player); | |
} | |
// | |
// G_PlayerFinishLevel | |
// Can when a player completes a level. | |
// | |
void G_PlayerFinishLevel (int player) | |
{ | |
player_t* p; | |
p = &players[player]; | |
memset (p->powers, 0, sizeof (p->powers)); | |
memset (p->cards, 0, sizeof (p->cards)); | |
p->mo->flags &= ~MF_SHADOW; // cancel invisibility | |
p->extralight = 0; // cancel gun flashes | |
p->fixedcolormap = 0; // cancel ir gogles | |
p->damagecount = 0; // no palette changes | |
p->bonuscount = 0; | |
} | |
// | |
// G_PlayerReborn | |
// Called after a player dies | |
// almost everything is cleared and initialized | |
// | |
void G_PlayerReborn (int player) | |
{ | |
player_t* p; | |
int i; | |
int frags[MAXPLAYERS]; | |
int killcount; | |
int itemcount; | |
int secretcount; | |
memcpy (frags,players[player].frags,sizeof(frags)); | |
killcount = players[player].killcount; | |
itemcount = players[player].itemcount; | |
secretcount = players[player].secretcount; | |
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; | |
p->usedown = p->attackdown = true; // don't do anything immediately | |
p->playerstate = PST_LIVE; | |
p->health = MAXHEALTH; | |
p->readyweapon = p->pendingweapon = wp_pistol; | |
p->weaponowned[wp_fist] = true; | |
p->weaponowned[wp_pistol] = true; | |
p->ammo[am_clip] = 50; | |
for (i=0 ; i<NUMAMMO ; i++) | |
p->maxammo[i] = maxammo[i]; | |
} | |
// | |
// G_CheckSpot | |
// Returns false if the player cannot be respawned | |
// at the given mapthing_t spot | |
// because something is occupying it | |
// | |
void P_SpawnPlayer (mapthing_t* mthing); | |
boolean | |
G_CheckSpot | |
( int playernum, | |
mapthing_t* mthing ) | |
{ | |
fixed_t x; | |
fixed_t y; | |
subsector_t* ss; | |
unsigned an; | |
mobj_t* mo; | |
int i; | |
if (!players[playernum].mo) | |
{ | |
// first spawn of level, before corpses | |
for (i=0 ; i<playernum ; i++) | |
if (players[i].mo->x == mthing->x << FRACBITS | |
&& players[i].mo->y == mthing->y << FRACBITS) | |
return false; | |
return true; | |
} | |
x = mthing->x << FRACBITS; | |
y = mthing->y << FRACBITS; | |
if (!P_CheckPosition (players[playernum].mo, x, y) ) | |
return false; | |
// flush an old corpse if needed | |
if (bodyqueslot >= BODYQUESIZE) | |
P_RemoveMobj (bodyque[bodyqueslot%BODYQUESIZE]); | |
bodyque[bodyqueslot%BODYQUESIZE] = players[playernum].mo; | |
bodyqueslot++; | |
// spawn a teleport fog | |
ss = R_PointInSubsector (x,y); | |
an = ( ANG45 * (mthing->angle/45) ) >> ANGLETOFINESHIFT; | |
mo = P_SpawnMobj (x+20*finecosine[an], y+20*finesine[an] | |
, ss->sector->floorheight | |
, MT_TFOG); | |
if (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 (!netgame) | |
{ | |
// reload the level from scratch | |
gameaction = ga_loadlevel; | |
} | |
else | |
{ | |
// respawn at the start | |
// first dissasociate the corpse | |
players[playernum].mo->player = NULL; | |
// 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; | |
} | |
// DOOM Par Times | |
int pars[4][10] = | |
{ | |
{0}, | |
{0,30,75,120,90,165,180,180,30,165}, | |
{0,90,90,90,120,90,360,240,30,170}, | |
{0,90,45,90,150,90,90,165,30,135} | |
}; | |
// DOOM II Par Times | |
int cpars[32] = | |
{ | |
30,90,120,120,90,150,120,120,270,90, // 1-10 | |
210,150,150,150,210,150,420,150,210,150, // 11-20 | |
240,150,180,150,150,300,330,420,300,180, // 21-30 | |
120,30 // 31-32 | |
}; | |
// | |
// G_DoCompleted | |
// | |
boolean secretexit; | |
extern char* pagename; | |
void G_ExitLevel (void) | |
{ | |
secretexit = false; | |
gameaction = ga_completed; | |
} | |
// Here's for the german edition. | |
void G_SecretExitLevel (void) | |
{ | |
// IF NO WOLF3D LEVELS, NO SECRET EXIT! | |
if ( (gamemode == commercial) | |
&& (W_CheckNumForName("map31")<0)) | |
secretexit = false; | |
else | |
secretexit = true; | |
gameaction = ga_completed; | |
} | |
void G_DoCompleted (void) | |
{ | |
int i; | |
gameaction = ga_nothing; | |
for (i=0 ; i<MAXPLAYERS ; i++) | |
if (playeringame[i]) | |
G_PlayerFinishLevel (i); // take away cards and stuff | |
if (automapactive) | |
AM_Stop (); | |
if ( gamemode != commercial) | |
switch(gamemap) | |
{ | |
case 8: | |
gameaction = ga_victory; | |
return; | |
case 9: | |
for (i=0 ; i<MAXPLAYERS ; i++) | |
players[i].didsecret = true; | |
break; | |
} | |
//#if 0 Hmmm - why? | |
if ( (gamemap == 8) | |
&& (gamemode != commercial) ) | |
{ | |
// victory | |
gameaction = ga_victory; | |
return; | |
} | |
if ( (gamemap == 9) | |
&& (gamemode != commercial) ) | |
{ | |
// exit secret level | |
for (i=0 ; i<MAXPLAYERS ; i++) | |
players[i].didsecret = true; | |
} | |
//#endif | |
wminfo.didsecret = players[consoleplayer].didsecret; | |
wminfo.epsd = gameepisode -1; | |
wminfo.last = gamemap -1; | |
// wminfo.next is 0 biased, unlike gamemap | |
if ( gamemode == commercial) | |
{ | |
if (secretexit) | |
switch(gamemap) | |
{ | |
case 15: wminfo.next = 30; break; | |
case 31: wminfo.next = 31; break; | |
} | |
else | |
switch(gamemap) | |
{ | |
case 31: | |
case 32: wminfo.next = 15; break; | |
default: wminfo.next = gamemap; | |
} | |
} | |
else | |
{ | |
if (secretexit) | |
wminfo.next = 8; // go to secret level | |
else if (gamemap == 9) | |
{ | |
// returning from secret level | |
switch (gameepisode) | |
{ | |
case 1: | |
wminfo.next = 3; | |
break; | |
case 2: | |
wminfo.next = 5; | |
break; | |
case 3: | |
wminfo.next = 6; | |
break; | |
case 4: | |
wminfo.next = 2; | |
break; | |
} | |
} | |
else | |
wminfo.next = gamemap; // go to next level | |
} | |
wminfo.maxkills = totalkills; | |
wminfo.maxitems = totalitems; | |
wminfo.maxsecret = totalsecret; | |
wminfo.maxfrags = 0; | |
if ( gamemode == commercial ) | |
wminfo.partime = 35*cpars[gamemap-1]; | |
else | |
wminfo.partime = 35*pars[gameepisode][gamemap]; | |
wminfo.pnum = consoleplayer; | |
for (i=0 ; i<MAXPLAYERS ; i++) | |
{ | |
wminfo.plyr[i].in = playeringame[i]; | |
wminfo.plyr[i].skills = players[i].killcount; | |
wminfo.plyr[i].sitems = players[i].itemcount; | |
wminfo.plyr[i].ssecret = players[i].secretcount; | |
wminfo.plyr[i].stime = leveltime; | |
memcpy (wminfo.plyr[i].frags, players[i].frags | |
, sizeof(wminfo.plyr[i].frags)); | |
} | |
gamestate = GS_INTERMISSION; | |
viewactive = false; | |
automapactive = false; | |
if (statcopy) | |
memcpy (statcopy, &wminfo, sizeof(wminfo)); | |
WI_Start (&wminfo); | |
} | |
// | |
// G_WorldDone | |
// | |
void G_WorldDone (void) | |
{ | |
gameaction = ga_worlddone; | |
if (secretexit) | |
players[consoleplayer].didsecret = true; | |
if ( gamemode == commercial ) | |
{ | |
switch (gamemap) | |
{ | |
case 15: | |
case 31: | |
if (!secretexit) | |
break; | |
case 6: | |
case 11: | |
case 20: | |
case 30: | |
F_StartFinale (); | |
break; | |
} | |
} | |
} | |
void G_DoWorldDone (void) | |
{ | |
gamestate = GS_LEVEL; | |
gamemap = wminfo.next+1; | |
G_DoLoadLevel (); | |
gameaction = ga_nothing; | |
viewactive = true; | |
} | |
// | |
// G_InitFromSavegame | |
// Can be called by the startup code or the menu task. | |
// | |
extern boolean setsizeneeded; | |
void R_ExecuteSetViewSize (void); | |
char savename[256]; | |
void G_LoadGame (char* name) | |
{ | |
strcpy (savename, name); | |
gameaction = ga_loadgame; | |
} | |
#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",VERSION); | |
if (strcmp (save_p, vcheck)) | |
return; // bad version | |
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); | |
// get the times | |
a = *save_p++; | |
b = *save_p++; | |
c = *save_p++; | |
leveltime = (a<<16) + (b<<8) + c; | |
// dearchive all the modifications | |
P_UnArchivePlayers (); | |
P_UnArchiveWorld (); | |
P_UnArchiveThinkers (); | |
P_UnArchiveSpecials (); | |
if (*save_p != 0x1d) | |
I_Error ("Bad savegame"); | |
// done | |
Z_Free (savebuffer); | |
if (setsizeneeded) | |
R_ExecuteSetViewSize (); | |
// draw the pattern into the back screen | |
R_FillBackScreen (); | |
} | |
// | |
// 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; | |
} | |
void G_DoSaveGame (void) | |
{ | |
char name[100]; | |
char name2[VERSIONSIZE]; | |
char* description; | |
int length; | |
int i; | |
if (M_CheckParm("-cdrom")) | |
sprintf(name,"c:\\doomdata\\"SAVEGAMENAME"%d.dsg",savegameslot); | |
else | |
sprintf (name,SAVEGAMENAME"%d.dsg",savegameslot); | |
description = savedescription; | |
save_p = savebuffer = screens[1]+0x4000; | |
memcpy (save_p, description, SAVESTRINGSIZE); | |
save_p += SAVESTRINGSIZE; | |
memset (name2,0,sizeof(name2)); | |
sprintf (name2,"version %i",VERSION); | |
memcpy (save_p, name2, VERSIONSIZE); | |
save_p += VERSIONSIZE; | |
*save_p++ = gameskill; | |
*save_p++ = gameepisode; | |
*save_p++ = gamemap; | |
for (i=0 ; i<MAXPLAYERS ; i++) | |
*save_p++ = playeringame[i]; | |
*save_p++ = leveltime>>16; | |
*save_p++ = leveltime>>8; | |
*save_p++ = leveltime; | |
P_ArchivePlayers (); | |
P_ArchiveWorld (); | |
P_ArchiveThinkers (); | |
P_ArchiveSpecials (); | |
*save_p++ = 0x1d; // consistancy marker | |
length = save_p - savebuffer; | |
if (length > SAVEGAMESIZE) | |
I_Error ("Savegame buffer overrun"); | |
M_WriteFile (name, savebuffer, length); | |
gameaction = ga_nothing; | |
savedescription[0] = 0; | |
players[consoleplayer].message = GGSAVED; | |
// draw the pattern into the back screen | |
R_FillBackScreen (); | |
} | |
// | |
// 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) | |
{ | |
demoplayback = false; | |
netdemo = false; | |
netgame = false; | |
deathmatch = false; | |
playeringame[1] = playeringame[2] = playeringame[3] = 0; | |
respawnparm = false; | |
fastparm = false; | |
nomonsters = false; | |
consoleplayer = 0; | |
G_InitNew (d_skill, d_episode, d_map); | |
gameaction = ga_nothing; | |
} | |
// The sky texture to be used instead of the F_SKY1 dummy. | |
extern int skytexture; | |
void | |
G_InitNew | |
( skill_t skill, | |
int episode, | |
int map ) | |
{ | |
int i; | |
if (paused) | |
{ | |
paused = false; | |
S_ResumeSound (); | |
} | |
if (skill > sk_nightmare) | |
skill = sk_nightmare; | |
// This was quite messy with SPECIAL and commented parts. | |
// Supposedly hacks to make the latest edition work. | |
// It might not work properly. | |
if (episode < 1) | |
episode = 1; | |
if ( gamemode == retail ) | |
{ | |
if (episode > 4) | |
episode = 4; | |
} | |
else if ( gamemode == shareware ) | |
{ | |
if (episode > 1) | |
episode = 1; // only start episode 1 on shareware | |
} | |
else | |
{ | |
if (episode > 3) | |
episode = 3; | |
} | |
if (map < 1) | |
map = 1; | |
if ( (map > 9) | |
&& ( gamemode != commercial) ) | |
map = 9; | |
M_ClearRandom (); | |
if (skill == sk_nightmare || respawnparm ) | |
respawnmonsters = true; | |
else | |
respawnmonsters = false; | |
if (fastparm || (skill == sk_nightmare && gameskill != sk_nightmare) ) | |
{ | |
for (i=S_SARG_RUN1 ; i<=S_SARG_PAIN2 ; i++) | |
states[i].tics >>= 1; | |
mobjinfo[MT_BRUISERSHOT].speed = 20*FRACUNIT; | |
mobjinfo[MT_HEADSHOT].speed = 20*FRACUNIT; | |
mobjinfo[MT_TROOPSHOT].speed = 20*FRACUNIT; | |
} | |
else if (skill != sk_nightmare && gameskill == sk_nightmare) | |
{ | |
for (i=S_SARG_RUN1 ; i<=S_SARG_PAIN2 ; i++) | |
states[i].tics <<= 1; | |
mobjinfo[MT_BRUISERSHOT].speed = 15*FRACUNIT; | |
mobjinfo[MT_HEADSHOT].speed = 10*FRACUNIT; | |
mobjinfo[MT_TROOPSHOT].speed = 10*FRACUNIT; | |
} | |
// force players to be initialized upon first level load | |
for (i=0 ; i<MAXPLAYERS ; i++) | |
players[i].playerstate = PST_REBORN; | |
usergame = true; // will be set false if a demo | |
paused = false; | |
demoplayback = false; | |
automapactive = false; | |
viewactive = true; | |
gameepisode = episode; | |
gamemap = map; | |
gameskill = skill; | |
viewactive = true; | |
// set the sky map for the episode | |
if ( gamemode == commercial) | |
{ | |
skytexture = R_TextureNumForName ("SKY3"); | |
if (gamemap < 12) | |
skytexture = R_TextureNumForName ("SKY1"); | |
else | |
if (gamemap < 21) | |
skytexture = R_TextureNumForName ("SKY2"); | |
} | |
else | |
switch (episode) | |
{ | |
case 1: | |
skytexture = R_TextureNumForName ("SKY1"); | |
break; | |
case 2: | |
skytexture = R_TextureNumForName ("SKY2"); | |
break; | |
case 3: | |
skytexture = R_TextureNumForName ("SKY3"); | |
break; | |
case 4: // Special Edition sky | |
skytexture = R_TextureNumForName ("SKY4"); | |
break; | |
} | |
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++; | |
} | |
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+128)>>8; | |
*demo_p++ = cmd->buttons; | |
demo_p -= 4; | |
if (demo_p > demoend - 16) | |
{ | |
// no more space | |
G_CheckDemoStatus (); | |
return; | |
} | |
G_ReadDemoTiccmd (cmd); // make SURE it is exactly the same | |
} | |
// | |
// G_RecordDemo | |
// | |
void G_RecordDemo (char* name) | |
{ | |
int i; | |
int maxsize; | |
usergame = false; | |
strcpy (demoname, name); | |
strcat (demoname, ".lmp"); | |
maxsize = 0x20000; | |
i = M_CheckParm ("-maxdemo"); | |
if (i && i<myargc-1) | |
maxsize = atoi(myargv[i+1])*1024; | |
demobuffer = Z_Malloc (maxsize,PU_STATIC,NULL); | |
demoend = demobuffer + maxsize; | |
demorecording = true; | |
} | |
void G_BeginRecording (void) | |
{ | |
int i; | |
demo_p = demobuffer; | |
*demo_p++ = VERSION; | |
*demo_p++ = gameskill; | |
*demo_p++ = gameepisode; | |
*demo_p++ = gamemap; | |
*demo_p++ = deathmatch; | |
*demo_p++ = respawnparm; | |
*demo_p++ = fastparm; | |
*demo_p++ = nomonsters; | |
*demo_p++ = consoleplayer; | |
for (i=0 ; i<MAXPLAYERS ; i++) | |
*demo_p++ = playeringame[i]; | |
} | |
// | |
// 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); | |
if ( *demo_p++ != VERSION) | |
{ | |
fprintf( stderr, "Demo is from a different game version!\n"); | |
gameaction = ga_nothing; | |
return; | |
} | |
skill = *demo_p++; | |
episode = *demo_p++; | |
map = *demo_p++; | |
deathmatch = *demo_p++; | |
respawnparm = *demo_p++; | |
fastparm = *demo_p++; | |
nomonsters = *demo_p++; | |
consoleplayer = *demo_p++; | |
for (i=0 ; i<MAXPLAYERS ; i++) | |
playeringame[i] = *demo_p++; | |
if (playeringame[1]) | |
{ | |
netgame = true; | |
netdemo = true; | |
} | |
// don't spend a lot of time in loadlevel | |
precache = false; | |
G_InitNew (skill, episode, map); | |
precache = true; | |
usergame = false; | |
demoplayback = true; | |
} | |
// | |
// G_TimeDemo | |
// | |
void G_TimeDemo (char* name) | |
{ | |
nodrawers = M_CheckParm ("-nodraw"); | |
noblit = M_CheckParm ("-noblit"); | |
timingdemo = true; | |
singletics = true; | |
defdemoname = name; | |
gameaction = ga_playdemo; | |
} | |
/* | |
=================== | |
= | |
= G_CheckDemoStatus | |
= | |
= Called after a death or level completion to allow demos to be cleaned up | |
= Returns true if a new demo loop action will take place | |
=================== | |
*/ | |
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 (); | |
Z_ChangeTag (demobuffer, PU_CACHE); | |
demoplayback = false; | |
netdemo = false; | |
netgame = false; | |
deathmatch = false; | |
playeringame[1] = playeringame[2] = playeringame[3] = 0; | |
respawnparm = false; | |
fastparm = false; | |
nomonsters = false; | |
consoleplayer = 0; | |
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; | |
} | |