mirror of
https://github.com/id-Software/DOOM-iOS.git
synced 2024-11-12 23:44:06 +00:00
2999 lines
85 KiB
C
2999 lines
85 KiB
C
/* Emacs style mode select -*- C++ -*-
|
|
*-----------------------------------------------------------------------------
|
|
*
|
|
*
|
|
* PrBoom: a Doom port merged with LxDoom and LSDLDoom
|
|
* based on BOOM, a modified and improved DOOM engine
|
|
* Copyright (C) 1999 by
|
|
* id Software, Chi Hoang, Lee Killough, Jim Flynn, Rand Phares, Ty Halderman
|
|
* Copyright (C) 1999-2004 by
|
|
* Jess Haas, Nicolas Kalkhof, Colin Phipps, Florian Schulze
|
|
* Copyright 2005, 2006 by
|
|
* Florian Schulze, Colin Phipps, Neil Stevens, Andrey Budko
|
|
*
|
|
* 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.
|
|
*
|
|
* DESCRIPTION: none
|
|
* The original Doom description was none, basically because this file
|
|
* has everything. This ties up the game logic, linking the menu and
|
|
* input code to the underlying game by creating & respawning players,
|
|
* building game tics, calling the underlying thing logic.
|
|
*
|
|
*-----------------------------------------------------------------------------
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdarg.h>
|
|
#include <stdlib.h>
|
|
#ifdef _MSC_VER
|
|
#define F_OK 0 /* Check for file existence */
|
|
#define W_OK 2 /* Check for write permission */
|
|
#define R_OK 4 /* Check for read permission */
|
|
#include <io.h>
|
|
#else
|
|
#include <unistd.h>
|
|
#endif
|
|
#include <fcntl.h>
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include "doomstat.h"
|
|
#include "d_net.h"
|
|
#include "f_finale.h"
|
|
#include "m_argv.h"
|
|
#include "m_misc.h"
|
|
#include "m_menu.h"
|
|
#include "m_random.h"
|
|
#include "p_setup.h"
|
|
#include "p_saveg.h"
|
|
#include "p_tick.h"
|
|
#include "p_map.h"
|
|
#include "p_checksum.h"
|
|
#include "d_main.h"
|
|
#include "wi_stuff.h"
|
|
#include "hu_stuff.h"
|
|
#include "st_stuff.h"
|
|
#include "am_map.h"
|
|
#include "w_wad.h"
|
|
#include "r_main.h"
|
|
#include "r_draw.h"
|
|
#include "p_map.h"
|
|
#include "s_sound.h"
|
|
#include "dstrings.h"
|
|
#include "sounds.h"
|
|
#include "r_data.h"
|
|
#include "r_sky.h"
|
|
#include "d_deh.h" // Ty 3/27/98 deh declarations
|
|
#include "p_inter.h"
|
|
#include "g_game.h"
|
|
#include "lprintf.h"
|
|
#include "i_main.h"
|
|
#include "i_system.h"
|
|
#include "r_demo.h"
|
|
#include "r_fps.h"
|
|
|
|
#define SAVEGAMESIZE 0x20000
|
|
#define SAVESTRINGSIZE 24
|
|
|
|
static size_t savegamesize = SAVEGAMESIZE; // killough
|
|
static boolean netdemo;
|
|
static const byte *demobuffer; /* cph - only used for playback */
|
|
static int demolength; // check for overrun (missing DEMOMARKER)
|
|
static FILE *demofp; /* cph - record straight to file */
|
|
static const byte *demo_p;
|
|
/* static JDC removed */ short consistancy[MAXPLAYERS][BACKUPTICS];
|
|
|
|
gameaction_t gameaction;
|
|
gamestate_t gamestate;
|
|
skill_t gameskill;
|
|
boolean respawnmonsters;
|
|
int gameepisode;
|
|
int gamemap;
|
|
boolean paused;
|
|
// CPhipps - moved *_loadgame vars here
|
|
static boolean forced_loadgame = false;
|
|
static boolean command_loadgame = false;
|
|
|
|
boolean usergame; // ok to save / end game
|
|
boolean timingdemo; // if true, exit with report on completion
|
|
boolean fastdemo; // if true, run at full speed -- killough
|
|
boolean nodrawers; // for comparative timing purposes
|
|
boolean noblit; // for comparative timing purposes
|
|
int starttime; // for comparative timing purposes
|
|
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 basetic; /* killough 9/29/98: for demo sync */
|
|
int totalkills, totallive, totalitems, totalsecret; // for intermission
|
|
boolean demorecording;
|
|
boolean demoplayback;
|
|
int demover;
|
|
boolean singledemo; // quit after playing a demo from cmdline
|
|
wbstartstruct_t wminfo; // parms for world map / intermission
|
|
boolean haswolflevels = false;// jff 4/18/98 wolf levels present
|
|
static byte *savebuffer; // CPhipps - static
|
|
int autorun = false; // always running? // phares
|
|
int totalleveltimes; // CPhipps - total time for all completed levels
|
|
int longtics;
|
|
|
|
//
|
|
// controls (have defaults)
|
|
//
|
|
|
|
int key_right;
|
|
int key_left;
|
|
int key_up;
|
|
int key_down;
|
|
int key_menu_right; // phares 3/7/98
|
|
int key_menu_left; // |
|
|
int key_menu_up; // V
|
|
int key_menu_down;
|
|
int key_menu_backspace; // ^
|
|
int key_menu_escape; // |
|
|
int key_menu_enter; // phares 3/7/98
|
|
int key_strafeleft;
|
|
int key_straferight;
|
|
int key_fire;
|
|
int key_use;
|
|
int key_strafe;
|
|
int key_speed;
|
|
int key_escape = KEYD_ESCAPE; // phares 4/13/98
|
|
int key_savegame; // phares
|
|
int key_loadgame; // |
|
|
int key_autorun; // V
|
|
int key_reverse;
|
|
int key_zoomin;
|
|
int key_zoomout;
|
|
int key_chat;
|
|
int key_backspace;
|
|
int key_enter;
|
|
int key_map_right;
|
|
int key_map_left;
|
|
int key_map_up;
|
|
int key_map_down;
|
|
int key_map_zoomin;
|
|
int key_map_zoomout;
|
|
int key_map;
|
|
int key_map_gobig;
|
|
int key_map_follow;
|
|
int key_map_mark;
|
|
int key_map_clear;
|
|
int key_map_grid;
|
|
int key_map_overlay; // cph - map overlay
|
|
int key_map_rotate; // cph - map rotation
|
|
int key_help = KEYD_F1; // phares 4/13/98
|
|
int key_soundvolume;
|
|
int key_hud;
|
|
int key_quicksave;
|
|
int key_endgame;
|
|
int key_messages;
|
|
int key_quickload;
|
|
int key_quit;
|
|
int key_gamma;
|
|
int key_spy;
|
|
int key_pause;
|
|
int key_setup;
|
|
int destination_keys[MAXPLAYERS];
|
|
int key_weapontoggle;
|
|
int key_weapon1;
|
|
int key_weapon2;
|
|
int key_weapon3;
|
|
int key_weapon4;
|
|
int key_weapon5;
|
|
int key_weapon6;
|
|
int key_weapon7; // ^
|
|
int key_weapon8; // |
|
|
int key_weapon9; // phares
|
|
|
|
int key_screenshot; // killough 2/22/98: screenshot key
|
|
int mousebfire;
|
|
int mousebstrafe;
|
|
int mousebforward;
|
|
int joybfire;
|
|
int joybstrafe;
|
|
int joybuse;
|
|
int joybspeed;
|
|
|
|
#define MAXPLMOVE (forwardmove[1])
|
|
#define TURBOTHRESHOLD 0x32
|
|
#define SLOWTURNTICS 6
|
|
#define QUICKREVERSE (short)32768 // 180 degree reverse // phares
|
|
#define NUMKEYS 512
|
|
|
|
fixed_t forwardmove[2] = {0x19, 0x32};
|
|
fixed_t sidemove[2] = {0x18, 0x28};
|
|
fixed_t angleturn[3] = {640, 1280, 320}; // + slow turn
|
|
|
|
// CPhipps - made lots of key/button state vars static
|
|
static boolean gamekeydown[NUMKEYS];
|
|
static int turnheld; // for accelerative turning
|
|
|
|
static boolean mousearray[4];
|
|
static boolean *mousebuttons = &mousearray[1]; // allow [-1]
|
|
|
|
// mouse values are used once
|
|
static int mousex;
|
|
static int mousey;
|
|
static int dclicktime;
|
|
static int dclickstate;
|
|
static int dclicks;
|
|
static int dclicktime2;
|
|
static int dclickstate2;
|
|
static int dclicks2;
|
|
|
|
// joystick values are repeated
|
|
static int joyxmove;
|
|
static int joyymove;
|
|
static boolean joyarray[5];
|
|
static boolean *joybuttons = &joyarray[1]; // allow [-1]
|
|
|
|
// Game events info
|
|
static buttoncode_t special_event; // Event triggered by local player, to send
|
|
static byte savegameslot; // Slot to load if gameaction == ga_loadgame
|
|
char savedescription[SAVEDESCLEN]; // Description to save in savegame if gameaction == ga_savegame
|
|
|
|
//jff 3/24/98 define defaultskill here
|
|
int defaultskill; //note 1-based
|
|
|
|
// killough 2/8/98: make corpse queue variable in size
|
|
int bodyqueslot, bodyquesize; // killough 2/8/98
|
|
mobj_t **bodyque = 0; // phares 8/10/98
|
|
|
|
/* JDC: removed static */ void G_DoSaveGame (boolean menu);
|
|
static const byte* G_ReadDemoHeader(const byte* demo_p, size_t size, boolean failonerror);
|
|
|
|
//
|
|
// 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
|
|
//
|
|
static inline signed char fudgef(signed char b)
|
|
{
|
|
static int c;
|
|
if (!b || !demo_compatibility || longtics) return b;
|
|
if (++c & 0x1f) return b;
|
|
b |= 1; if (b>2) b-=2;
|
|
return b;
|
|
}
|
|
|
|
static inline signed short fudgea(signed short b)
|
|
{
|
|
if (!b || !demo_compatibility || !longtics) return b;
|
|
b |= 1; if (b>2) b-=2;
|
|
return b;
|
|
}
|
|
|
|
|
|
void G_BuildTiccmd(ticcmd_t* cmd)
|
|
{
|
|
boolean strafe;
|
|
boolean bstrafe;
|
|
int speed;
|
|
int tspeed;
|
|
int forward;
|
|
int side;
|
|
int newweapon; // phares
|
|
/* cphipps - remove needless I_BaseTiccmd call, just set the ticcmd to zero */
|
|
memset(cmd,0,sizeof*cmd);
|
|
cmd->consistancy = consistancy[consoleplayer][maketic%BACKUPTICS];
|
|
|
|
strafe = gamekeydown[key_strafe] || mousebuttons[mousebstrafe]
|
|
|| joybuttons[joybstrafe];
|
|
//e6y: the "RUN" key inverts the autorun state
|
|
speed = (gamekeydown[key_speed] || joybuttons[joybspeed] ? !autorun : autorun); // phares
|
|
|
|
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;
|
|
|
|
// turn 180 degrees in one keystroke? // phares
|
|
// |
|
|
if (gamekeydown[key_reverse]) // V
|
|
{
|
|
cmd->angleturn += QUICKREVERSE; // ^
|
|
gamekeydown[key_reverse] = false; // |
|
|
} // phares
|
|
|
|
// 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])
|
|
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;
|
|
}
|
|
|
|
// Toggle between the top 2 favorite weapons. // phares
|
|
// If not currently aiming one of these, switch to // phares
|
|
// the favorite. Only switch if you possess the weapon. // phares
|
|
|
|
// killough 3/22/98:
|
|
//
|
|
// Perform automatic weapons switch here rather than in p_pspr.c,
|
|
// except in demo_compatibility mode.
|
|
//
|
|
// killough 3/26/98, 4/2/98: fix autoswitch when no weapons are left
|
|
|
|
if ((!demo_compatibility && players[consoleplayer].attackdown && // killough
|
|
!P_CheckAmmo(&players[consoleplayer])) || gamekeydown[key_weapontoggle])
|
|
newweapon = P_SwitchWeapon(&players[consoleplayer]); // phares
|
|
else
|
|
{ // phares 02/26/98: Added gamemode checks
|
|
newweapon =
|
|
gamekeydown[key_weapon1] ? wp_fist : // killough 5/2/98: reformatted
|
|
gamekeydown[key_weapon2] ? wp_pistol :
|
|
gamekeydown[key_weapon3] ? wp_shotgun :
|
|
gamekeydown[key_weapon4] ? wp_chaingun :
|
|
gamekeydown[key_weapon5] ? wp_missile :
|
|
gamekeydown[key_weapon6] && gamemode != shareware ? wp_plasma :
|
|
gamekeydown[key_weapon7] && gamemode != shareware ? wp_bfg :
|
|
gamekeydown[key_weapon8] ? wp_chainsaw :
|
|
(!demo_compatibility && gamekeydown[key_weapon9] && gamemode == commercial) ? wp_supershotgun :
|
|
wp_nochange;
|
|
|
|
// killough 3/22/98: For network and demo consistency with the
|
|
// new weapons preferences, we must do the weapons switches here
|
|
// instead of in p_user.c. But for old demos we must do it in
|
|
// p_user.c according to the old rules. Therefore demo_compatibility
|
|
// determines where the weapons switch is made.
|
|
|
|
// killough 2/8/98:
|
|
// Allow user to switch to fist even if they have chainsaw.
|
|
// Switch to fist or chainsaw based on preferences.
|
|
// Switch to shotgun or SSG based on preferences.
|
|
|
|
if (!demo_compatibility)
|
|
{
|
|
const player_t *player = &players[consoleplayer];
|
|
|
|
// only select chainsaw from '1' if it's owned, it's
|
|
// not already in use, and the player prefers it or
|
|
// the fist is already in use, or the player does not
|
|
// have the berserker strength.
|
|
|
|
if (newweapon==wp_fist && player->weaponowned[wp_chainsaw] &&
|
|
player->readyweapon!=wp_chainsaw &&
|
|
(player->readyweapon==wp_fist ||
|
|
!player->powers[pw_strength] ||
|
|
P_WeaponPreferred(wp_chainsaw, wp_fist)))
|
|
newweapon = wp_chainsaw;
|
|
|
|
// Select SSG from '3' only if it's owned and the player
|
|
// does not have a shotgun, or if the shotgun is already
|
|
// in use, or if the SSG is not already in use and the
|
|
// player prefers it.
|
|
|
|
if (newweapon == wp_shotgun && gamemode == commercial &&
|
|
player->weaponowned[wp_supershotgun] &&
|
|
(!player->weaponowned[wp_shotgun] ||
|
|
player->readyweapon == wp_shotgun ||
|
|
(player->readyweapon != wp_supershotgun &&
|
|
P_WeaponPreferred(wp_supershotgun, wp_shotgun))))
|
|
newweapon = wp_supershotgun;
|
|
}
|
|
// killough 2/8/98, 3/22/98 -- end of weapon selection changes
|
|
}
|
|
|
|
if (newweapon != wp_nochange)
|
|
{
|
|
cmd->buttons |= BT_CHANGE;
|
|
cmd->buttons |= newweapon<<BT_WEAPONSHIFT;
|
|
}
|
|
|
|
// 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
|
|
if ((dclicktime += ticdup) > 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
|
|
if ((dclicktime2 += ticdup) > 20)
|
|
{
|
|
dclicks2 = 0;
|
|
dclickstate2 = 0;
|
|
}
|
|
forward += mousey;
|
|
if (strafe)
|
|
side += mousex / 4; /* mead Don't want to strafe as fast as turns.*/
|
|
else
|
|
cmd->angleturn -= mousex; /* mead now have enough dynamic range 2-10-00 */
|
|
|
|
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 += fudgef((signed char)forward);
|
|
cmd->sidemove += side;
|
|
cmd->angleturn = fudgea(cmd->angleturn);
|
|
|
|
// CPhipps - special events (game new/load/save/pause)
|
|
if (special_event & BT_SPECIAL) {
|
|
cmd->buttons = special_event;
|
|
special_event = 0;
|
|
}
|
|
}
|
|
|
|
//
|
|
// G_RestartLevel
|
|
//
|
|
|
|
void G_RestartLevel(void)
|
|
{
|
|
special_event = BT_SPECIAL | (BTS_RESTARTLEVEL & BT_SPECIALMASK);
|
|
}
|
|
|
|
#include "z_bmalloc.h"
|
|
//
|
|
// G_DoLoadLevel
|
|
//
|
|
|
|
static 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 //jff 3/27/98 sorry guys pack_tnt,pack_plut
|
|
// || gamemode == pack_plut) //aren't gamemodes, this was matching retail
|
|
{
|
|
skytexture = R_TextureNumForName ("SKY3");
|
|
if (gamemap < 12)
|
|
skytexture = R_TextureNumForName ("SKY1");
|
|
else
|
|
if (gamemap < 21)
|
|
skytexture = R_TextureNumForName ("SKY2");
|
|
}
|
|
else //jff 3/27/98 and lets not forget about DOOM and Ultimate DOOM huh?
|
|
switch (gameepisode)
|
|
{
|
|
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;
|
|
}//jff 3/27/98 end sky setting fix
|
|
|
|
/* cph 2006/07/31 - took out unused levelstarttic variable */
|
|
|
|
if (!demo_compatibility && !mbf_features) // killough 9/29/98
|
|
basetic = gametic;
|
|
|
|
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));
|
|
}
|
|
|
|
// initialize the msecnode_t freelist. phares 3/25/98
|
|
// any nodes in the freelist are gone by now, cleared
|
|
// by Z_FreeTags() when the previous level ended or player
|
|
// died.
|
|
|
|
{
|
|
DECLARE_BLOCK_MEMORY_ALLOC_ZONE(secnodezone);
|
|
NULL_BLOCK_MEMORY_ALLOC_ZONE(secnodezone);
|
|
//extern msecnode_t *headsecnode; // phares 3/25/98
|
|
//headsecnode = NULL;
|
|
}
|
|
|
|
P_SetupLevel (gameepisode, gamemap, 0, gameskill);
|
|
if (!demoplayback) // Don't switch views if playing a demo
|
|
displayplayer = consoleplayer; // view the guy you are playing
|
|
gameaction = ga_nothing;
|
|
Z_CheckHeap ();
|
|
|
|
// clear cmd building stuff
|
|
memset (gamekeydown, 0, sizeof(gamekeydown));
|
|
joyxmove = joyymove = 0;
|
|
mousex = mousey = 0;
|
|
special_event = 0; paused = false;
|
|
memset (mousebuttons, 0, sizeof(mousebuttons));
|
|
memset (joybuttons, 0, sizeof(joybuttons));
|
|
|
|
// killough 5/13/98: in case netdemo has consoleplayer other than green
|
|
ST_Start();
|
|
HU_Start();
|
|
}
|
|
|
|
|
|
//
|
|
// 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
|
|
// killough 2/22/98: even during DM demo
|
|
//
|
|
// killough 11/98: don't autorepeat spy mode switch
|
|
|
|
if (ev->data1 == key_spy && netgame && (demoplayback || !deathmatch) &&
|
|
gamestate == GS_LEVEL)
|
|
{
|
|
if (ev->type == ev_keyup)
|
|
gamekeydown[key_spy] = false;
|
|
if (ev->type == ev_keydown && !gamekeydown[key_spy])
|
|
{
|
|
gamekeydown[key_spy] = true;
|
|
do // spy mode
|
|
if (++displayplayer >= MAXPLAYERS)
|
|
displayplayer = 0;
|
|
while (!playeringame[displayplayer] && displayplayer!=consoleplayer);
|
|
|
|
ST_Start(); // killough 3/7/98: switch status bar views too
|
|
HU_Start();
|
|
S_UpdateSounds(players[displayplayer].mo);
|
|
R_ActivateSectorInterpolations();
|
|
R_SmoothPlaying_Reset(NULL);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// any other key pops up menu if in demos
|
|
//
|
|
// killough 8/2/98: enable automap in -timedemo demos
|
|
//
|
|
// killough 9/29/98: make any key pop up menu regardless of
|
|
// which kind of demo, and allow other events during playback
|
|
|
|
if (gameaction == ga_nothing && (demoplayback || gamestate == GS_DEMOSCREEN))
|
|
{
|
|
// killough 9/29/98: allow user to pause demos during playback
|
|
if (ev->type == ev_keydown && ev->data1 == key_pause)
|
|
{
|
|
if (paused ^= 2)
|
|
S_PauseSound();
|
|
else
|
|
S_ResumeSound();
|
|
return true;
|
|
}
|
|
|
|
// killough 10/98:
|
|
// Don't pop up menu, if paused in middle
|
|
// of demo playback, or if automap active.
|
|
// Don't suck up keys, which may be cheats
|
|
|
|
return gamestate == GS_DEMOSCREEN &&
|
|
!(paused & 2) && !(automapmode & am_active) &&
|
|
((ev->type == ev_keydown) ||
|
|
(ev->type == ev_mouse && ev->data1) ||
|
|
(ev->type == ev_joystick && ev->data1)) ?
|
|
M_StartControlPanel(), true : false;
|
|
}
|
|
|
|
if (gamestate == GS_FINALE && F_Responder(ev))
|
|
return true; // finale ate the event
|
|
|
|
switch (ev->type)
|
|
{
|
|
case ev_keydown:
|
|
if (ev->data1 == key_pause) // phares
|
|
{
|
|
special_event = BT_SPECIAL | (BTS_PAUSE & BT_SPECIALMASK);
|
|
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;
|
|
/*
|
|
* bmead@surfree.com
|
|
* Modified by Barry Mead after adding vastly more resolution
|
|
* to the Mouse Sensitivity Slider in the options menu 1-9-2000
|
|
* Removed the mouseSensitivity "*4" to allow more low end
|
|
* sensitivity resolution especially for lsdoom users.
|
|
*/
|
|
mousex += (ev->data2*(mouseSensitivity_horiz))/10; /* killough */
|
|
mousey += (ev->data3*(mouseSensitivity_vert))/10; /*Mead rm *4 */
|
|
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;
|
|
static gamestate_t prevgamestate;
|
|
|
|
// CPhipps - player colour changing
|
|
if (!demoplayback && mapcolor_plyr[consoleplayer] != mapcolor_me) {
|
|
// Changed my multiplayer colour - Inform the whole game
|
|
int net_cl = LONG(mapcolor_me);
|
|
#ifdef HAVE_NET
|
|
D_NetSendMisc(nm_plcolour, sizeof(net_cl), &net_cl);
|
|
#endif
|
|
G_ChangedPlayerColour(consoleplayer, mapcolor_me);
|
|
}
|
|
P_MapStart();
|
|
// do player reborns if needed
|
|
for (i=0 ; i<MAXPLAYERS ; i++)
|
|
if (playeringame[i] && players[i].playerstate == PST_REBORN)
|
|
G_DoReborn (i);
|
|
P_MapEnd();
|
|
|
|
// do things to change the game state
|
|
while (gameaction != ga_nothing)
|
|
{
|
|
switch (gameaction)
|
|
{
|
|
case ga_loadlevel:
|
|
// force players to be initialized on level reload
|
|
for (i=0 ; i<MAXPLAYERS ; i++)
|
|
players[i].playerstate = PST_REBORN;
|
|
G_DoLoadLevel ();
|
|
break;
|
|
case ga_newgame:
|
|
G_DoNewGame ();
|
|
break;
|
|
case ga_loadgame:
|
|
G_DoLoadGame ();
|
|
break;
|
|
case ga_savegame:
|
|
G_DoSaveGame (false);
|
|
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_nothing:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (paused & 2 || (!demoplayback && menuactive && !netgame))
|
|
basetic++; // For revenant tracers and RNG -- we must maintain sync
|
|
else {
|
|
// get commands, check consistancy, and build new consistancy check
|
|
int buf = (gametic/ticdup)%BACKUPTICS;
|
|
|
|
for (i=0 ; i<MAXPLAYERS ; i++) {
|
|
if (playeringame[i])
|
|
{
|
|
ticcmd_t *cmd = &players[i].cmd;
|
|
|
|
memcpy(cmd, &netcmds[i][buf], sizeof *cmd);
|
|
|
|
if (demoplayback)
|
|
G_ReadDemoTiccmd (cmd);
|
|
if (demorecording)
|
|
G_WriteDemoTiccmd (cmd);
|
|
|
|
// check for turbo cheats
|
|
// killough 2/14/98, 2/20/98 -- only warn in netgames and demos
|
|
|
|
if ((netgame || demoplayback) && cmd->forwardmove > TURBOTHRESHOLD &&
|
|
!(gametic&31) && ((gametic>>5)&3) == i )
|
|
{
|
|
extern char *player_names[];
|
|
/* cph - don't use sprintf, use doom_printf */
|
|
doom_printf ("%s is turbo!", player_names[i]);
|
|
}
|
|
|
|
if (netgame && !netdemo && !(gametic%ticdup) )
|
|
{
|
|
#ifndef IPHONE // consistency checks are handled in AsyncTic() on packet receive
|
|
if (gametic > BACKUPTICS
|
|
&& consistancy[i][buf] != cmd->consistancy)
|
|
I_Error("G_Ticker: Consistency failure (%i should be %i)",
|
|
cmd->consistancy, consistancy[i][buf]);
|
|
#endif
|
|
if (players[i].mo)
|
|
consistancy[i][buf] = players[i].mo->x;
|
|
else
|
|
consistancy[i][buf] = 0; // killough 2/14/98
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
|
|
// CPhipps - remote loadgame request
|
|
case BTS_LOADGAME:
|
|
savegameslot =
|
|
(players[i].cmd.buttons & BTS_SAVEMASK)>>BTS_SAVESHIFT;
|
|
gameaction = ga_loadgame;
|
|
forced_loadgame = netgame; // Force if a netgame
|
|
command_loadgame = false;
|
|
break;
|
|
|
|
// CPhipps - Restart the level
|
|
case BTS_RESTARTLEVEL:
|
|
if (demoplayback || (compatibility_level < lxdoom_1_compatibility))
|
|
break; // CPhipps - Ignore in demos or old games
|
|
gameaction = ga_loadlevel;
|
|
break;
|
|
}
|
|
players[i].cmd.buttons = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// cph - if the gamestate changed, we may need to clean up the old gamestate
|
|
if (gamestate != prevgamestate) {
|
|
switch (prevgamestate) {
|
|
case GS_LEVEL:
|
|
// This causes crashes at level end - Neil Stevens
|
|
// The crash is because the sounds aren't stopped before freeing them
|
|
// the following is a possible fix
|
|
// This fix does avoid the crash wowever, with this fix in, the exit
|
|
// switch sound is cut off
|
|
// S_Stop();
|
|
// Z_FreeTags(PU_LEVEL, PU_PURGELEVEL-1);
|
|
break;
|
|
case GS_INTERMISSION:
|
|
WI_End();
|
|
default:
|
|
break;
|
|
}
|
|
prevgamestate = gamestate;
|
|
}
|
|
|
|
// e6y
|
|
// do nothing if a pause has been pressed during playback
|
|
// pausing during intermission can cause desynchs without that
|
|
if (paused & 2 && gamestate != GS_LEVEL)
|
|
return;
|
|
|
|
// 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_PlayerFinishLevel
|
|
// Can when a player completes a level.
|
|
//
|
|
|
|
static void G_PlayerFinishLevel(int player)
|
|
{
|
|
player_t *p = &players[player];
|
|
memset(p->powers, 0, sizeof p->powers);
|
|
memset(p->cards, 0, sizeof p->cards);
|
|
p->mo = NULL; // cph - this is allocated PU_LEVEL so it's gone
|
|
p->extralight = 0; // cancel gun flashes
|
|
p->fixedcolormap = 0; // cancel ir gogles
|
|
p->damagecount = 0; // no palette changes
|
|
p->bonuscount = 0;
|
|
}
|
|
|
|
// CPhipps - G_SetPlayerColour
|
|
// Player colours stuff
|
|
//
|
|
// G_SetPlayerColour
|
|
|
|
#include "r_draw.h"
|
|
|
|
void G_ChangedPlayerColour(int pn, int cl)
|
|
{
|
|
int i;
|
|
|
|
if (!netgame) return;
|
|
|
|
mapcolor_plyr[pn] = cl;
|
|
|
|
// Rebuild colour translation tables accordingly
|
|
R_InitTranslationTables();
|
|
// Change translations on existing player mobj's
|
|
for (i=0; i<MAXPLAYERS; i++) {
|
|
if ((gamestate == GS_LEVEL) && playeringame[i] && (players[i].mo != NULL)) {
|
|
players[i].mo->flags &= ~MF_TRANSLATION;
|
|
players[i].mo->flags |= playernumtotrans[i] << MF_TRANSSHIFT;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// 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];
|
|
|
|
// killough 3/10/98,3/21/98: preserve cheats across idclev
|
|
{
|
|
int cheats = p->cheats;
|
|
memset (p, 0, sizeof(*p));
|
|
p->cheats = cheats;
|
|
}
|
|
|
|
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 = initial_health; // Ty 03/12/98 - use dehacked values
|
|
p->readyweapon = p->pendingweapon = wp_pistol;
|
|
p->weaponowned[wp_fist] = true;
|
|
p->weaponowned[wp_pistol] = true;
|
|
p->ammo[am_clip] = initial_bullets; // Ty 03/12/98 - use dehacked values
|
|
|
|
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
|
|
//
|
|
|
|
static boolean G_CheckSpot(int playernum, mapthing_t *mthing)
|
|
{
|
|
fixed_t x,y;
|
|
subsector_t *ss;
|
|
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;
|
|
|
|
// killough 4/2/98: fix bug where P_CheckPosition() uses a non-solid
|
|
// corpse to detect collisions with other players in DM starts
|
|
//
|
|
// Old code:
|
|
// if (!P_CheckPosition (players[playernum].mo, x, y))
|
|
// return false;
|
|
|
|
players[playernum].mo->flags |= MF_SOLID;
|
|
i = P_CheckPosition(players[playernum].mo, x, y);
|
|
players[playernum].mo->flags &= ~MF_SOLID;
|
|
if (!i)
|
|
return false;
|
|
|
|
// flush an old corpse if needed
|
|
// killough 2/8/98: make corpse queue have an adjustable limit
|
|
// killough 8/1/98: Fix bugs causing strange crashes
|
|
|
|
if (bodyquesize > 0)
|
|
{
|
|
static int queuesize;
|
|
if (queuesize < bodyquesize)
|
|
{
|
|
bodyque = realloc(bodyque, bodyquesize*sizeof*bodyque);
|
|
memset(bodyque+queuesize, 0,
|
|
(bodyquesize-queuesize)*sizeof*bodyque);
|
|
queuesize = bodyquesize;
|
|
}
|
|
if (bodyqueslot >= bodyquesize)
|
|
P_RemoveMobj(bodyque[bodyqueslot % bodyquesize]);
|
|
bodyque[bodyqueslot++ % bodyquesize] = players[playernum].mo;
|
|
}
|
|
else
|
|
if (!bodyquesize)
|
|
P_RemoveMobj(players[playernum].mo);
|
|
|
|
// spawn a teleport fog
|
|
ss = R_PointInSubsector (x,y);
|
|
{ // Teleport fog at respawn point
|
|
fixed_t xa,ya;
|
|
int an;
|
|
mobj_t *mo;
|
|
|
|
/* BUG: an can end up negative, because mthing->angle is (signed) short.
|
|
* We have to emulate original Doom's behaviour, deferencing past the start
|
|
* of the array, into the previous array (finetangent) */
|
|
an = ( ANG45 * ((signed)mthing->angle/45) ) >> ANGLETOFINESHIFT;
|
|
xa = finecosine[an];
|
|
ya = finesine[an];
|
|
|
|
if (compatibility_level <= finaldoom_compatibility || compatibility_level == prboom_4_compatibility)
|
|
switch (an) {
|
|
case -4096: xa = finetangent[2048]; // finecosine[-4096]
|
|
ya = finetangent[0]; // finesine[-4096]
|
|
break;
|
|
case -3072: xa = finetangent[3072]; // finecosine[-3072]
|
|
ya = finetangent[1024]; // finesine[-3072]
|
|
break;
|
|
case -2048: xa = finesine[0]; // finecosine[-2048]
|
|
ya = finetangent[2048]; // finesine[-2048]
|
|
break;
|
|
case -1024: xa = finesine[1024]; // finecosine[-1024]
|
|
ya = finetangent[3072]; // finesine[-1024]
|
|
break;
|
|
case 1024:
|
|
case 2048:
|
|
case 3072:
|
|
case 4096:
|
|
case 0: break; /* correct angles set above */
|
|
default: I_Error("G_CheckSpot: unexpected angle %d\n",an);
|
|
}
|
|
|
|
mo = P_SpawnMobj(x+20*xa, y+20*ya, 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 j, selections = deathmatch_p - deathmatchstarts;
|
|
|
|
if (selections < MAXPLAYERS)
|
|
I_Error("G_DeathMatchSpawnPlayer: Only %i deathmatch spots, %d required",
|
|
selections, MAXPLAYERS);
|
|
|
|
for (j=0 ; j<20 ; j++)
|
|
{
|
|
int i = P_Random(pr_dmspawn) % selections;
|
|
if (G_CheckSpot (playernum, &deathmatchstarts[i]) )
|
|
{
|
|
deathmatchstarts[i].type = playernum+1;
|
|
P_SpawnPlayer (playernum, &deathmatchstarts[i]);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// no good spot, so the player will probably get stuck
|
|
P_SpawnPlayer (playernum, &playerstarts[playernum]);
|
|
}
|
|
|
|
//
|
|
// G_DoReborn
|
|
//
|
|
|
|
void G_DoReborn (int playernum)
|
|
{
|
|
if (!netgame)
|
|
gameaction = ga_loadlevel; // reload the level from scratch
|
|
else
|
|
{ // respawn at the start
|
|
int i;
|
|
|
|
// 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 (playernum, &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]) )
|
|
{
|
|
P_SpawnPlayer (playernum, &playerstarts[i]);
|
|
return;
|
|
}
|
|
// he's going to be inside something. Too bad.
|
|
}
|
|
P_SpawnPlayer (playernum, &playerstarts[playernum]);
|
|
}
|
|
}
|
|
|
|
// 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
|
|
};
|
|
|
|
static boolean secretexit;
|
|
|
|
void G_ExitLevel (void)
|
|
{
|
|
secretexit = false;
|
|
gameaction = ga_completed;
|
|
}
|
|
|
|
// Here's for the german edition.
|
|
// IF NO WOLF3D LEVELS, NO SECRET EXIT!
|
|
|
|
void G_SecretExitLevel (void)
|
|
{
|
|
if (gamemode!=commercial || haswolflevels)
|
|
secretexit = true;
|
|
else
|
|
secretexit = false;
|
|
gameaction = ga_completed;
|
|
}
|
|
|
|
//
|
|
// G_DoCompleted
|
|
//
|
|
|
|
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 (automapmode & am_active)
|
|
AM_Stop();
|
|
|
|
if (gamemode != commercial) // kilough 2/7/98
|
|
switch(gamemap)
|
|
{
|
|
// cph - Remove ExM8 special case, so it gets summary screen displayed
|
|
case 9:
|
|
for (i=0 ; i<MAXPLAYERS ; i++)
|
|
players[i].didsecret = true;
|
|
break;
|
|
}
|
|
|
|
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 = TICRATE*cpars[gamemap-1];
|
|
else
|
|
wminfo.partime = TICRATE*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));
|
|
}
|
|
|
|
/* cph - modified so that only whole seconds are added to the totalleveltimes
|
|
* value; so our total is compatible with the "naive" total of just adding
|
|
* the times in seconds shown for each level. Also means our total time
|
|
* will agree with Compet-n.
|
|
*/
|
|
wminfo.totaltimes = (totalleveltimes += (leveltime - leveltime%35));
|
|
|
|
gamestate = GS_INTERMISSION;
|
|
automapmode &= ~am_active;
|
|
|
|
// lmpwatch.pl engine-side demo testing support
|
|
// print "FINISHED: <mapname>" when the player exits the current map
|
|
if (nodrawers && (demoplayback || timingdemo)) {
|
|
if (gamemode == commercial)
|
|
lprintf(LO_INFO, "FINISHED: MAP%02d\n", gamemap);
|
|
else
|
|
lprintf(LO_INFO, "FINISHED: E%dM%d\n", gameepisode, gamemap);
|
|
}
|
|
|
|
WI_Start (&wminfo);
|
|
|
|
#ifdef IPHONE
|
|
iphoneIntermission( &wminfo );
|
|
#endif
|
|
}
|
|
|
|
//
|
|
// 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;
|
|
}
|
|
}
|
|
else if (gamemap == 8)
|
|
gameaction = ga_victory; // cph - after ExM8 summary screen, show victory stuff
|
|
}
|
|
|
|
void G_DoWorldDone (void)
|
|
{
|
|
idmusnum = -1; //jff 3/17/98 allow new level's music to be loaded
|
|
gamestate = GS_LEVEL;
|
|
gamemap = wminfo.next+1;
|
|
G_DoLoadLevel();
|
|
gameaction = ga_nothing;
|
|
AM_clearMarks(); //jff 4/12/98 clear any marks on the automap
|
|
|
|
iphoneStartLevel(); // JDC: update playState and set the "tried level" flag
|
|
}
|
|
|
|
// killough 2/28/98: A ridiculously large number
|
|
// of players, the most you'll ever need in a demo
|
|
// or savegame. This is used to prevent problems, in
|
|
// case more players in a game are supported later.
|
|
|
|
#define MIN_MAXPLAYERS 32
|
|
|
|
extern boolean setsizeneeded;
|
|
|
|
//CPhipps - savename variable redundant
|
|
|
|
/* killough 12/98:
|
|
* This function returns a signature for the current wad.
|
|
* It is used to distinguish between wads, for the purposes
|
|
* of savegame compatibility warnings, and options lookups.
|
|
*/
|
|
|
|
static uint_64_t G_UpdateSignature(uint_64_t s, const char *name)
|
|
{
|
|
int i, lump = W_CheckNumForName(name);
|
|
if (lump != -1 && (i = lump+10) < numlumps)
|
|
do
|
|
{
|
|
int size = W_LumpLength(i);
|
|
const byte *p = W_CacheLumpNum(i);
|
|
while (size--)
|
|
s <<= 1, s += *p++;
|
|
W_UnlockLumpNum(i);
|
|
}
|
|
while (--i > lump);
|
|
return s;
|
|
}
|
|
|
|
static uint_64_t G_Signature(void)
|
|
{
|
|
static uint_64_t s = 0;
|
|
static boolean computed = false;
|
|
char name[9];
|
|
int episode, map;
|
|
|
|
if (!computed) {
|
|
computed = true;
|
|
if (gamemode == commercial)
|
|
for (map = haswolflevels ? 32 : 30; map; map--)
|
|
sprintf(name, "map%02d", map), s = G_UpdateSignature(s, name);
|
|
else
|
|
for (episode = gamemode==retail ? 4 :
|
|
gamemode==shareware ? 1 : 3; episode; episode--)
|
|
for (map = 9; map; map--)
|
|
sprintf(name, "E%dM%d", episode, map), s = G_UpdateSignature(s, name);
|
|
}
|
|
return s;
|
|
}
|
|
|
|
//
|
|
// killough 5/15/98: add forced loadgames, which allow user to override checks
|
|
//
|
|
|
|
void G_ForcedLoadGame(void)
|
|
{
|
|
// CPhipps - net loadgames are always forced, so we only reach here
|
|
// in single player
|
|
gameaction = ga_loadgame;
|
|
forced_loadgame = true;
|
|
}
|
|
|
|
// killough 3/16/98: add slot info
|
|
// killough 5/15/98: add command-line
|
|
void G_LoadGame(int slot, boolean command)
|
|
{
|
|
if (!demoplayback && !command) {
|
|
// CPhipps - handle savegame filename in G_DoLoadGame
|
|
// - Delay load so it can be communicated in net game
|
|
// - store info in special_event
|
|
special_event = BT_SPECIAL | (BTS_LOADGAME & BT_SPECIALMASK) |
|
|
((slot << BTS_SAVESHIFT) & BTS_SAVEMASK);
|
|
forced_loadgame = netgame; // CPhipps - always force load netgames
|
|
} else {
|
|
// Do the old thing, immediate load
|
|
gameaction = ga_loadgame;
|
|
forced_loadgame = false;
|
|
savegameslot = slot;
|
|
demoplayback = false;
|
|
// Don't stay in netgame state if loading single player save
|
|
// while watching multiplayer demo
|
|
netgame = false;
|
|
}
|
|
command_loadgame = command;
|
|
R_SmoothPlaying_Reset(NULL); // e6y
|
|
}
|
|
|
|
// killough 5/15/98:
|
|
// Consistency Error when attempting to load savegame.
|
|
|
|
static void G_LoadGameErr(const char *msg)
|
|
{
|
|
Z_Free(savebuffer); // Free the savegame buffer
|
|
M_ForcedLoadGame(msg); // Print message asking for 'Y' to force
|
|
if (command_loadgame) // If this was a command-line -loadgame
|
|
{
|
|
D_StartTitle(); // Start the title screen
|
|
gamestate = GS_DEMOSCREEN; // And set the game state accordingly
|
|
}
|
|
}
|
|
|
|
// CPhipps - size of version header
|
|
#define VERSIONSIZE 16
|
|
|
|
const char * comp_lev_str[MAX_COMPATIBILITY_LEVEL] =
|
|
{ "doom v1.2", "doom v1.666", "doom/doom2 v1.9", "ultimate doom", "final doom",
|
|
"dosdoom compatibility", "tasdoom compatibility", "\"boom compatibility\"", "boom v2.01", "boom v2.02", "lxdoom v1.3.2+",
|
|
"MBF", "PrBoom 2.03beta", "PrBoom v2.1.0-2.1.1", "PrBoom v2.1.2-v2.2.6",
|
|
"PrBoom v2.3.x", "PrBoom 2.4.0", "Current PrBoom" };
|
|
|
|
// comp_options_by_version removed - see G_Compatibility
|
|
|
|
static byte map_old_comp_levels[] =
|
|
{ 0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 };
|
|
|
|
static const struct {
|
|
int comp_level;
|
|
const char* ver_printf;
|
|
int version;
|
|
} version_headers[] = {
|
|
/* cph - we don't need a new version_header for prboom_3_comp/v2.1.1, since
|
|
* the file format is unchanged. */
|
|
{ prboom_3_compatibility, "PrBoom %d", 210},
|
|
{ prboom_5_compatibility, "PrBoom %d", 211},
|
|
{ prboom_6_compatibility, "PrBoom %d", 212}
|
|
};
|
|
|
|
static const size_t num_version_headers = sizeof(version_headers) / sizeof(version_headers[0]);
|
|
|
|
|
|
boolean G_SaveGameValid() {
|
|
|
|
int length, i;
|
|
// CPhipps - do savegame filename stuff here
|
|
char name[PATH_MAX+1]; // killough 3/22/98
|
|
int savegame_compatibility = -1;
|
|
|
|
G_SaveGameName(name,sizeof(name),savegameslot, demoplayback);
|
|
|
|
gameaction = ga_nothing;
|
|
|
|
length = M_ReadFile(name, &savebuffer);
|
|
if (length<=0) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
void G_DoLoadGame(void)
|
|
{
|
|
int length, i;
|
|
// CPhipps - do savegame filename stuff here
|
|
char name[PATH_MAX+1]; // killough 3/22/98
|
|
int savegame_compatibility = -1;
|
|
|
|
G_SaveGameName(name,sizeof(name),savegameslot, demoplayback);
|
|
|
|
gameaction = ga_nothing;
|
|
|
|
length = M_ReadFile(name, &savebuffer);
|
|
if (length<=0)
|
|
I_Error("Couldn't read file %s: %s", name, "(Unknown Error)");
|
|
save_p = savebuffer + SAVESTRINGSIZE;
|
|
|
|
// CPhipps - read the description field, compare with supported ones
|
|
for (i=0; (size_t)i<num_version_headers; i++) {
|
|
char vcheck[VERSIONSIZE];
|
|
// killough 2/22/98: "proprietary" version string :-)
|
|
sprintf (vcheck, version_headers[i].ver_printf, version_headers[i].version);
|
|
|
|
if (!strncmp(save_p, vcheck, VERSIONSIZE)) {
|
|
savegame_compatibility = version_headers[i].comp_level;
|
|
i = num_version_headers;
|
|
}
|
|
}
|
|
if (savegame_compatibility == -1) {
|
|
if (forced_loadgame) {
|
|
savegame_compatibility = MAX_COMPATIBILITY_LEVEL-1;
|
|
} else {
|
|
G_LoadGameErr("Unrecognised savegame version!\nAre you sure? (y/n) ");
|
|
return;
|
|
}
|
|
}
|
|
|
|
save_p += VERSIONSIZE;
|
|
|
|
// CPhipps - always check savegames even when forced,
|
|
// only print a warning if forced
|
|
{ // killough 3/16/98: check lump name checksum (independent of order)
|
|
uint_64_t checksum = 0;
|
|
|
|
checksum = G_Signature();
|
|
|
|
if (memcmp(&checksum, save_p, sizeof checksum)) {
|
|
if (!forced_loadgame) {
|
|
char *msg = malloc(strlen(save_p + sizeof checksum) + 128);
|
|
strcpy(msg,"Incompatible Savegame!!!\n");
|
|
if (save_p[sizeof checksum])
|
|
strcat(strcat(msg,"Wads expected:\n\n"), save_p + sizeof checksum);
|
|
strcat(msg, "\nAre you sure?");
|
|
G_LoadGameErr(msg);
|
|
free(msg);
|
|
return;
|
|
} else
|
|
lprintf(LO_WARN, "G_DoLoadGame: Incompatible savegame\n");
|
|
}
|
|
save_p += sizeof checksum;
|
|
}
|
|
|
|
save_p += strlen(save_p)+1;
|
|
|
|
compatibility_level = (savegame_compatibility >= prboom_4_compatibility) ? *save_p : savegame_compatibility;
|
|
if (savegame_compatibility < prboom_6_compatibility)
|
|
compatibility_level = map_old_comp_levels[compatibility_level];
|
|
save_p++;
|
|
|
|
gameskill = *save_p++;
|
|
gameepisode = *save_p++;
|
|
gamemap = *save_p++;
|
|
|
|
for (i=0 ; i<MAXPLAYERS ; i++)
|
|
playeringame[i] = *save_p++;
|
|
save_p += MIN_MAXPLAYERS-MAXPLAYERS; // killough 2/28/98
|
|
|
|
idmusnum = *save_p++; // jff 3/17/98 restore idmus music
|
|
if (idmusnum==255) idmusnum=-1; // jff 3/18/98 account for unsigned byte
|
|
|
|
/* killough 3/1/98: Read game options
|
|
* killough 11/98: move down to here
|
|
*/
|
|
save_p = (char*)G_ReadOptions(save_p);
|
|
|
|
// load a base level
|
|
G_InitNew (gameskill, gameepisode, gamemap);
|
|
|
|
/* get the times - killough 11/98: save entire word */
|
|
memcpy(&leveltime, save_p, sizeof leveltime);
|
|
save_p += sizeof leveltime;
|
|
|
|
/* cph - total episode time */
|
|
if (compatibility_level >= prboom_2_compatibility) {
|
|
memcpy(&totalleveltimes, save_p, sizeof totalleveltimes);
|
|
save_p += sizeof totalleveltimes;
|
|
}
|
|
else totalleveltimes = 0;
|
|
|
|
// killough 11/98: load revenant tracer state
|
|
basetic = gametic - *save_p++;
|
|
|
|
// dearchive all the modifications
|
|
P_MapStart();
|
|
P_UnArchivePlayers ();
|
|
P_UnArchiveWorld ();
|
|
P_UnArchiveThinkers ();
|
|
P_UnArchiveSpecials ();
|
|
P_UnArchiveRNG (); // killough 1/18/98: load RNG information
|
|
P_UnArchiveMap (); // killough 1/22/98: load automap information
|
|
P_MapEnd();
|
|
R_SmoothPlaying_Reset(NULL); // e6y
|
|
|
|
if (*save_p != 0xe6)
|
|
I_Error ("G_DoLoadGame: Bad savegame");
|
|
|
|
// done
|
|
Z_Free (savebuffer);
|
|
|
|
if (setsizeneeded)
|
|
R_ExecuteSetViewSize ();
|
|
|
|
// draw the pattern into the back screen
|
|
R_FillBackScreen ();
|
|
|
|
/* killough 12/98: support -recordfrom and -loadgame -playdemo */
|
|
if (!command_loadgame)
|
|
singledemo = false; /* Clear singledemo flag if loading from menu */
|
|
else
|
|
if (singledemo) {
|
|
gameaction = ga_loadgame; /* Mark that we're loading a game before demo */
|
|
G_DoPlayDemo(); /* This will detect it and won't reinit level */
|
|
} else /* Command line + record means it's a recordfrom */
|
|
if (demorecording)
|
|
G_BeginRecording();
|
|
}
|
|
|
|
//
|
|
// G_SaveGame
|
|
// Called by the menu task.
|
|
// Description is a 24 byte text string
|
|
//
|
|
|
|
void G_SaveGame(int slot, char *description)
|
|
{
|
|
strcpy(savedescription, description);
|
|
if (demoplayback) {
|
|
/* cph - We're doing a user-initiated save game while a demo is
|
|
* running so, go outside normal mechanisms
|
|
*/
|
|
savegameslot = slot;
|
|
G_DoSaveGame(true);
|
|
}
|
|
// CPhipps - store info in special_event
|
|
special_event = BT_SPECIAL | (BTS_SAVEGAME & BT_SPECIALMASK) |
|
|
((slot << BTS_SAVESHIFT) & BTS_SAVEMASK);
|
|
#ifdef HAVE_NET
|
|
D_NetSendMisc(nm_savegamename, strlen(savedescription)+1, savedescription);
|
|
#endif
|
|
}
|
|
|
|
// Check for overrun and realloc if necessary -- Lee Killough 1/22/98
|
|
void (CheckSaveGame)(size_t size, const char* file, int line)
|
|
{
|
|
size_t pos = save_p - savebuffer;
|
|
|
|
#ifdef RANGECHECK
|
|
/* cph 2006/08/07 - after-the-fact sanity checking of CheckSaveGame calls */
|
|
static size_t prev_check;
|
|
static const char* prevf;
|
|
static int prevl;
|
|
|
|
if (pos > prev_check)
|
|
I_Error("CheckSaveGame at %s:%d called for insufficient buffer (%u < %u)", prevf, prevl, prev_check, pos);
|
|
prev_check = size + pos;
|
|
prevf = file;
|
|
prevl = line;
|
|
#endif
|
|
|
|
size += 1024; // breathing room
|
|
if (pos+size > savegamesize)
|
|
save_p = (savebuffer = realloc(savebuffer,
|
|
savegamesize += (size+1023) & ~1023)) + pos;
|
|
}
|
|
|
|
/* killough 3/22/98: form savegame name in one location
|
|
* (previously code was scattered around in multiple places)
|
|
* cph - Avoid possible buffer overflow problems by passing
|
|
* size to this function and using snprintf */
|
|
|
|
void G_SaveGameName(char *name, size_t size, int slot, boolean demoplayback)
|
|
{
|
|
const char* sgn = demoplayback ? "demosav" : savegamename;
|
|
#ifdef HAVE_SNPRINTF
|
|
snprintf (name, size, "%s/%s%d.dsg", basesavegame, sgn, slot);
|
|
#else
|
|
sprintf (name, "%s/%s%d.dsg", basesavegame, sgn, slot);
|
|
#endif
|
|
}
|
|
|
|
/* JDC: removed static */ void G_DoSaveGame (boolean menu)
|
|
{
|
|
char name[PATH_MAX+1];
|
|
char name2[VERSIONSIZE];
|
|
char *description;
|
|
int length, i;
|
|
|
|
gameaction = ga_nothing; // cph - cancel savegame at top of this function,
|
|
// in case later problems cause a premature exit
|
|
|
|
G_SaveGameName(name,sizeof(name),savegameslot, demoplayback && !menu);
|
|
|
|
description = savedescription;
|
|
|
|
save_p = savebuffer = malloc(savegamesize);
|
|
|
|
CheckSaveGame(SAVESTRINGSIZE+VERSIONSIZE+sizeof(uint_64_t));
|
|
memcpy (save_p, description, SAVESTRINGSIZE);
|
|
save_p += SAVESTRINGSIZE;
|
|
memset (name2,0,sizeof(name2));
|
|
|
|
// CPhipps - scan for the version header
|
|
for (i=0; (size_t)i<num_version_headers; i++)
|
|
if (version_headers[i].comp_level == best_compatibility) {
|
|
// killough 2/22/98: "proprietary" version string :-)
|
|
sprintf (name2,version_headers[i].ver_printf,version_headers[i].version);
|
|
memcpy (save_p, name2, VERSIONSIZE);
|
|
i = num_version_headers+1;
|
|
}
|
|
|
|
save_p += VERSIONSIZE;
|
|
|
|
{ /* killough 3/16/98, 12/98: store lump name checksum */
|
|
uint_64_t checksum = G_Signature();
|
|
memcpy(save_p, &checksum, sizeof checksum);
|
|
save_p += sizeof checksum;
|
|
}
|
|
|
|
// killough 3/16/98: store pwad filenames in savegame
|
|
{
|
|
// CPhipps - changed for new wadfiles handling
|
|
size_t i;
|
|
for (i = 0; i<numwadfiles; i++)
|
|
{
|
|
const char *const w = wadfiles[i].name;
|
|
CheckSaveGame(strlen(w)+2);
|
|
strcpy(save_p, w);
|
|
save_p += strlen(save_p);
|
|
*save_p++ = '\n';
|
|
}
|
|
*save_p++ = 0;
|
|
}
|
|
|
|
CheckSaveGame(GAME_OPTION_SIZE+MIN_MAXPLAYERS+14);
|
|
|
|
*save_p++ = compatibility_level;
|
|
|
|
*save_p++ = gameskill;
|
|
*save_p++ = gameepisode;
|
|
*save_p++ = gamemap;
|
|
|
|
for (i=0 ; i<MAXPLAYERS ; i++)
|
|
*save_p++ = playeringame[i];
|
|
|
|
for (;i<MIN_MAXPLAYERS;i++) // killough 2/28/98
|
|
*save_p++ = 0;
|
|
|
|
*save_p++ = idmusnum; // jff 3/17/98 save idmus state
|
|
|
|
save_p = G_WriteOptions(save_p); // killough 3/1/98: save game options
|
|
|
|
/* cph - FIXME - endianness? */
|
|
/* killough 11/98: save entire word */
|
|
memcpy(save_p, &leveltime, sizeof leveltime);
|
|
save_p += sizeof leveltime;
|
|
|
|
/* cph - total episode time */
|
|
if (compatibility_level >= prboom_2_compatibility) {
|
|
memcpy(save_p, &totalleveltimes, sizeof totalleveltimes);
|
|
save_p += sizeof totalleveltimes;
|
|
}
|
|
else totalleveltimes = 0;
|
|
|
|
// killough 11/98: save revenant tracer state
|
|
*save_p++ = (gametic-basetic) & 255;
|
|
|
|
// killough 3/22/98: add Z_CheckHeap after each call to ensure consistency
|
|
Z_CheckHeap();
|
|
P_ArchivePlayers();
|
|
Z_CheckHeap();
|
|
|
|
// phares 9/13/98: Move mobj_t->index out of P_ArchiveThinkers so the
|
|
// indices can be used by P_ArchiveWorld when the sectors are saved.
|
|
// This is so we can save the index of the mobj_t of the thinker that
|
|
// caused a sound, referenced by sector_t->soundtarget.
|
|
P_ThinkerToIndex();
|
|
|
|
P_ArchiveWorld();
|
|
Z_CheckHeap();
|
|
P_ArchiveThinkers();
|
|
|
|
// phares 9/13/98: Move index->mobj_t out of P_ArchiveThinkers, simply
|
|
// for symmetry with the P_ThinkerToIndex call above.
|
|
|
|
P_IndexToThinker();
|
|
|
|
Z_CheckHeap();
|
|
P_ArchiveSpecials();
|
|
P_ArchiveRNG(); // killough 1/18/98: save RNG information
|
|
Z_CheckHeap();
|
|
P_ArchiveMap(); // killough 1/22/98: save automap information
|
|
|
|
*save_p++ = 0xe6; // consistancy marker
|
|
|
|
length = save_p - savebuffer;
|
|
|
|
Z_CheckHeap();
|
|
doom_printf( "%s", M_WriteFile(name, savebuffer, length)
|
|
? s_GGSAVED /* Ty - externalised */
|
|
: "Game save failed!"); // CPhipps - not externalised
|
|
|
|
free(savebuffer); // killough
|
|
savebuffer = save_p = NULL;
|
|
|
|
savedescription[0] = 0;
|
|
}
|
|
|
|
static skill_t d_skill;
|
|
static int d_episode;
|
|
static 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;
|
|
}
|
|
|
|
/* cph -
|
|
* G_Compatibility
|
|
*
|
|
* Initialises the comp[] array based on the compatibility_level
|
|
* For reference, MBF did:
|
|
* for (i=0; i < COMP_TOTAL; i++)
|
|
* comp[i] = compatibility;
|
|
*
|
|
* Instead, we have a lookup table showing at what version a fix was
|
|
* introduced, and made optional (replaces comp_options_by_version)
|
|
*/
|
|
|
|
void G_Compatibility(void)
|
|
{
|
|
static const struct {
|
|
complevel_t fix; // level at which fix/change was introduced
|
|
complevel_t opt; // level at which fix/change was made optional
|
|
} levels[] = {
|
|
// comp_telefrag - monsters used to telefrag only on MAP30, now they do it for spawners only
|
|
{ mbf_compatibility, mbf_compatibility },
|
|
// comp_dropoff - MBF encourages things to drop off of overhangs
|
|
{ mbf_compatibility, mbf_compatibility },
|
|
// comp_vile - original Doom archville bugs like ghosts
|
|
{ boom_compatibility, mbf_compatibility },
|
|
// comp_pain - original Doom limits Pain Elementals from spawning too many skulls
|
|
{ boom_compatibility, mbf_compatibility },
|
|
// comp_skull - original Doom let skulls be spit through walls by Pain Elementals
|
|
{ boom_compatibility, mbf_compatibility },
|
|
// comp_blazing - original Doom duplicated blazing door sound
|
|
{ boom_compatibility, mbf_compatibility },
|
|
// e6y: "Tagged doors don't trigger special lighting" handled wrong
|
|
// http://sourceforge.net/tracker/index.php?func=detail&aid=1411400&group_id=148658&atid=772943
|
|
// comp_doorlight - MBF made door lighting changes more gradual
|
|
{ boom_compatibility, mbf_compatibility },
|
|
// comp_model - improvements to the game physics
|
|
{ boom_compatibility, mbf_compatibility },
|
|
// comp_god - fixes to God mode
|
|
{ boom_compatibility, mbf_compatibility },
|
|
// comp_falloff - MBF encourages things to drop off of overhangs
|
|
{ mbf_compatibility, mbf_compatibility },
|
|
// comp_floors - fixes for moving floors bugs
|
|
{ boom_compatibility_compatibility, mbf_compatibility },
|
|
// comp_skymap
|
|
{ boom_compatibility, mbf_compatibility },
|
|
// comp_pursuit - MBF AI change, limited pursuit?
|
|
{ mbf_compatibility, mbf_compatibility },
|
|
// comp_doorstuck - monsters stuck in doors fix
|
|
{ boom_202_compatibility, mbf_compatibility },
|
|
// comp_staylift - MBF AI change, monsters try to stay on lifts
|
|
{ mbf_compatibility, mbf_compatibility },
|
|
// comp_zombie - prevent dead players triggering stuff
|
|
{ lxdoom_1_compatibility, mbf_compatibility },
|
|
// comp_stairs - see p_floor.c
|
|
{ boom_202_compatibility, mbf_compatibility },
|
|
// comp_infcheat - FIXME
|
|
{ mbf_compatibility, mbf_compatibility },
|
|
// comp_zerotags - allow zero tags in wads */
|
|
{ boom_compatibility, mbf_compatibility },
|
|
// comp_moveblock - enables keygrab and mancubi shots going thru walls
|
|
{ lxdoom_1_compatibility, prboom_2_compatibility },
|
|
// comp_respawn - objects which aren't on the map at game start respawn at (0,0)
|
|
{ prboom_2_compatibility, prboom_2_compatibility },
|
|
// comp_sound - see s_sound.c
|
|
{ boom_compatibility_compatibility, prboom_3_compatibility },
|
|
// comp_666 - enables tag 666 in non-ExM8 levels
|
|
{ ultdoom_compatibility, prboom_4_compatibility },
|
|
// comp_soul - enables lost souls bouncing (see P_ZMovement)
|
|
{ prboom_4_compatibility, prboom_4_compatibility },
|
|
// comp_maskedanim - 2s mid textures don't animate
|
|
{ doom_1666_compatibility, prboom_4_compatibility },
|
|
};
|
|
int i;
|
|
|
|
if (sizeof(levels)/sizeof(*levels) != COMP_NUM)
|
|
I_Error("G_Compatibility: consistency error");
|
|
|
|
for (i = 0; i < sizeof(levels)/sizeof(*levels); i++)
|
|
if (compatibility_level < levels[i].opt)
|
|
comp[i] = (compatibility_level < levels[i].fix);
|
|
|
|
if (!mbf_features) {
|
|
monster_infighting = 1;
|
|
monster_backing = 0;
|
|
monster_avoid_hazards = 0;
|
|
monster_friction = 0;
|
|
help_friends = 0;
|
|
|
|
#ifdef DOGS
|
|
dogs = 0;
|
|
dog_jumping = 0;
|
|
#endif
|
|
|
|
monkeys = 0;
|
|
}
|
|
}
|
|
|
|
#ifdef DOGS
|
|
/* killough 7/19/98: Marine's best friend :) */
|
|
static int G_GetHelpers(void)
|
|
{
|
|
int j = M_CheckParm ("-dog");
|
|
|
|
if (!j)
|
|
j = M_CheckParm ("-dogs");
|
|
return j ? j+1 < myargc ? atoi(myargv[j+1]) : 1 : default_dogs;
|
|
}
|
|
#endif
|
|
|
|
// killough 3/1/98: function to reload all the default parameter
|
|
// settings before a new game begins
|
|
|
|
void G_ReloadDefaults(void)
|
|
{
|
|
// killough 3/1/98: Initialize options based on config file
|
|
// (allows functions above to load different values for demos
|
|
// and savegames without messing up defaults).
|
|
|
|
weapon_recoil = default_weapon_recoil; // weapon recoil
|
|
|
|
player_bobbing = default_player_bobbing; // whether player bobs or not
|
|
|
|
/* cph 2007/06/31 - for some reason, the default_* of the next 2 vars was never implemented */
|
|
variable_friction = default_variable_friction;
|
|
allow_pushers = default_allow_pushers;
|
|
|
|
|
|
monsters_remember = default_monsters_remember; // remember former enemies
|
|
|
|
monster_infighting = default_monster_infighting; // killough 7/19/98
|
|
|
|
#ifdef DOGS
|
|
dogs = netgame ? 0 : G_GetHelpers(); // killough 7/19/98
|
|
dog_jumping = default_dog_jumping;
|
|
#endif
|
|
|
|
distfriend = default_distfriend; // killough 8/8/98
|
|
|
|
monster_backing = default_monster_backing; // killough 9/8/98
|
|
|
|
monster_avoid_hazards = default_monster_avoid_hazards; // killough 9/9/98
|
|
|
|
monster_friction = default_monster_friction; // killough 10/98
|
|
|
|
help_friends = default_help_friends; // killough 9/9/98
|
|
|
|
monkeys = default_monkeys;
|
|
|
|
// jff 1/24/98 reset play mode to command line spec'd version
|
|
// killough 3/1/98: moved to here
|
|
respawnparm = clrespawnparm;
|
|
fastparm = clfastparm;
|
|
nomonsters = clnomonsters;
|
|
|
|
//jff 3/24/98 set startskill from defaultskill in config file, unless
|
|
// it has already been set by a -skill parameter
|
|
if (startskill==sk_none)
|
|
startskill = (skill_t)(defaultskill-1);
|
|
|
|
demoplayback = false;
|
|
singledemo = false; // killough 9/29/98: don't stop after 1 demo
|
|
netdemo = false;
|
|
|
|
// killough 2/21/98:
|
|
memset(playeringame+1, 0, sizeof(*playeringame)*(MAXPLAYERS-1));
|
|
|
|
consoleplayer = 0;
|
|
|
|
compatibility_level = default_compatibility_level;
|
|
{
|
|
int i = M_CheckParm("-complevel");
|
|
if (i && (1+i) < myargc) {
|
|
int l = atoi(myargv[i+1]);;
|
|
if (l >= -1) compatibility_level = l;
|
|
}
|
|
}
|
|
if (compatibility_level == -1)
|
|
compatibility_level = best_compatibility;
|
|
|
|
if (mbf_features)
|
|
memcpy(comp, default_comp, sizeof comp);
|
|
G_Compatibility();
|
|
|
|
// killough 3/31/98, 4/5/98: demo sync insurance
|
|
demo_insurance = default_demo_insurance == 1;
|
|
|
|
rngseed += I_GetRandomTimeSeed() + gametic; // CPhipps
|
|
}
|
|
|
|
void G_DoNewGame (void)
|
|
{
|
|
G_ReloadDefaults(); // killough 3/1/98
|
|
netgame = false; // killough 3/29/98
|
|
deathmatch = false;
|
|
G_InitNew (d_skill, d_episode, d_map);
|
|
gameaction = ga_nothing;
|
|
|
|
//jff 4/26/98 wake up the status bar in case were coming out of a DM demo
|
|
ST_Start();
|
|
}
|
|
|
|
// killough 4/10/98: New function to fix bug which caused Doom
|
|
// lockups when idclev was used in conjunction with -fast.
|
|
|
|
void G_SetFastParms(int fast_pending)
|
|
{
|
|
static int fast = 0; // remembers fast state
|
|
int i;
|
|
if (fast != fast_pending) { /* only change if necessary */
|
|
if ((fast = fast_pending))
|
|
{
|
|
for (i=S_SARG_RUN1; i<=S_SARG_PAIN2; i++)
|
|
if (states[i].tics != 1 || demo_compatibility) // killough 4/10/98
|
|
states[i].tics >>= 1; // don't change 1->0 since it causes cycles
|
|
mobjinfo[MT_BRUISERSHOT].speed = 20*FRACUNIT;
|
|
mobjinfo[MT_HEADSHOT].speed = 20*FRACUNIT;
|
|
mobjinfo[MT_TROOPSHOT].speed = 20*FRACUNIT;
|
|
}
|
|
else
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// G_InitNew
|
|
// Can be called by the startup code or the menu task,
|
|
// consoleplayer, displayplayer, playeringame[] should be set.
|
|
//
|
|
|
|
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;
|
|
|
|
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;
|
|
|
|
G_SetFastParms(fastparm || skill == sk_nightmare); // killough 4/10/98
|
|
|
|
M_ClearRandom();
|
|
|
|
respawnmonsters = skill == sk_nightmare || respawnparm;
|
|
|
|
// 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;
|
|
automapmode &= ~am_active;
|
|
gameepisode = episode;
|
|
gamemap = map;
|
|
gameskill = skill;
|
|
|
|
totalleveltimes = 0; // cph
|
|
|
|
//jff 4/16/98 force marks on automap cleared every new level start
|
|
AM_clearMarks();
|
|
|
|
G_DoLoadLevel ();
|
|
}
|
|
|
|
//
|
|
// DEMO RECORDING
|
|
//
|
|
|
|
#define DEMOMARKER 0x80
|
|
|
|
void G_ReadDemoTiccmd (ticcmd_t* cmd)
|
|
{
|
|
unsigned char at; // e6y: tasdoom stuff
|
|
|
|
if (*demo_p == DEMOMARKER)
|
|
G_CheckDemoStatus(); // end of demo data stream
|
|
else if (demoplayback && demo_p + (longtics?5:4) > demobuffer + demolength)
|
|
{
|
|
lprintf(LO_WARN, "G_ReadDemoTiccmd: missing DEMOMARKER\n");
|
|
G_CheckDemoStatus();
|
|
}
|
|
else
|
|
{
|
|
cmd->forwardmove = ((signed char)*demo_p++);
|
|
cmd->sidemove = ((signed char)*demo_p++);
|
|
if (!longtics) {
|
|
cmd->angleturn = ((unsigned char)(at = *demo_p++))<<8;
|
|
} else {
|
|
unsigned int lowbyte = (unsigned char)*demo_p++;
|
|
cmd->angleturn = (((signed int)(*demo_p++))<<8) + lowbyte;
|
|
}
|
|
cmd->buttons = (unsigned char)*demo_p++;
|
|
// e6y: ability to play tasdoom demos directly
|
|
if (compatibility_level == tasdoom_compatibility)
|
|
{
|
|
signed char k = cmd->forwardmove;
|
|
cmd->forwardmove = cmd->sidemove;
|
|
cmd->sidemove = (signed char)at;
|
|
cmd->angleturn = ((unsigned char)cmd->buttons)<<8;
|
|
cmd->buttons = (byte)k;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Demo limits removed -- killough
|
|
* cph - record straight to file
|
|
*/
|
|
void G_WriteDemoTiccmd (ticcmd_t* cmd)
|
|
{
|
|
char buf[5];
|
|
char *p = buf;
|
|
|
|
*p++ = cmd->forwardmove;
|
|
*p++ = cmd->sidemove;
|
|
if (!longtics) {
|
|
*p++ = (cmd->angleturn+128)>>8;
|
|
} else {
|
|
signed short a = cmd->angleturn;
|
|
*p++ = a & 0xff;
|
|
*p++ = (a >> 8) & 0xff;
|
|
}
|
|
*p++ = cmd->buttons;
|
|
if (fwrite(buf, p-buf, 1, demofp) != 1)
|
|
I_Error("G_WriteDemoTiccmd: error writing demo");
|
|
|
|
/* cph - alias demo_p to it so we can read it back */
|
|
demo_p = buf;
|
|
G_ReadDemoTiccmd (cmd); // make SURE it is exactly the same
|
|
}
|
|
|
|
//
|
|
// G_RecordDemo
|
|
//
|
|
|
|
void G_RecordDemo (const char* name)
|
|
{
|
|
char demoname[PATH_MAX];
|
|
usergame = false;
|
|
AddDefaultExtension(strcpy(demoname, name), ".lmp"); // 1/18/98 killough
|
|
demorecording = true;
|
|
/* cph - Record demos straight to file
|
|
* If file already exists, try to continue existing demo
|
|
*/
|
|
if (access(demoname, F_OK)) {
|
|
demofp = fopen(demoname, "wb");
|
|
} else {
|
|
demofp = fopen(demoname, "r+");
|
|
if (demofp) {
|
|
int slot = -1;
|
|
int rc;
|
|
int bytes_per_tic;
|
|
const byte* pos;
|
|
|
|
{ /* Read the demo header for options etc */
|
|
byte buf[200];
|
|
size_t len = fread(buf, 1, sizeof(buf), demofp);
|
|
pos = G_ReadDemoHeader(buf, len, false);
|
|
if (pos)
|
|
{
|
|
fseek(demofp, pos - buf, SEEK_SET);
|
|
}
|
|
}
|
|
bytes_per_tic = longtics ? 5 : 4;
|
|
if (pos)
|
|
/* Now read the demo to find the last save slot */
|
|
do {
|
|
byte buf[5];
|
|
|
|
rc = fread(buf, 1, bytes_per_tic, demofp);
|
|
if (buf[0] == DEMOMARKER) break;
|
|
if (buf[bytes_per_tic-1] & BT_SPECIAL)
|
|
if ((buf[bytes_per_tic-1] & BT_SPECIALMASK) == BTS_SAVEGAME)
|
|
slot = (buf[bytes_per_tic-1] & BTS_SAVEMASK)>>BTS_SAVESHIFT;
|
|
} while (rc == bytes_per_tic);
|
|
|
|
if (slot == -1) I_Error("G_RecordDemo: No save in demo, can't continue");
|
|
|
|
/* Return to the last save position, and load the relevant savegame */
|
|
fseek(demofp, -rc, SEEK_CUR);
|
|
G_LoadGame(slot, false);
|
|
autostart = false;
|
|
}
|
|
}
|
|
if (!demofp) I_Error("G_RecordDemo: failed to open %s", name);
|
|
}
|
|
|
|
// These functions are used to read and write game-specific options in demos
|
|
// and savegames so that demo sync is preserved and savegame restoration is
|
|
// complete. Not all options (for example "compatibility"), however, should
|
|
// be loaded and saved here. It is extremely important to use the same
|
|
// positions as before for the variables, so if one becomes obsolete, the
|
|
// byte(s) should still be skipped over or padded with 0's.
|
|
// Lee Killough 3/1/98
|
|
|
|
extern int forceOldBsp;
|
|
|
|
byte *G_WriteOptions(byte *demo_p)
|
|
{
|
|
byte *target = demo_p + GAME_OPTION_SIZE;
|
|
|
|
*demo_p++ = monsters_remember; // part of monster AI
|
|
|
|
*demo_p++ = variable_friction; // ice & mud
|
|
|
|
*demo_p++ = weapon_recoil; // weapon recoil
|
|
|
|
*demo_p++ = allow_pushers; // MT_PUSH Things
|
|
|
|
*demo_p++ = 0;
|
|
|
|
*demo_p++ = player_bobbing; // whether player bobs or not
|
|
|
|
// killough 3/6/98: add parameters to savegame, move around some in demos
|
|
*demo_p++ = respawnparm;
|
|
*demo_p++ = fastparm;
|
|
*demo_p++ = nomonsters;
|
|
|
|
*demo_p++ = demo_insurance; // killough 3/31/98
|
|
|
|
// killough 3/26/98: Added rngseed. 3/31/98: moved here
|
|
*demo_p++ = (byte)((rngseed >> 24) & 0xff);
|
|
*demo_p++ = (byte)((rngseed >> 16) & 0xff);
|
|
*demo_p++ = (byte)((rngseed >> 8) & 0xff);
|
|
*demo_p++ = (byte)( rngseed & 0xff);
|
|
|
|
// Options new to v2.03 begin here
|
|
|
|
*demo_p++ = monster_infighting; // killough 7/19/98
|
|
|
|
#ifdef DOGS
|
|
*demo_p++ = dogs; // killough 7/19/98
|
|
#else
|
|
*demo_p++ = 0;
|
|
#endif
|
|
|
|
*demo_p++ = 0;
|
|
*demo_p++ = 0;
|
|
|
|
*demo_p++ = (distfriend >> 8) & 0xff; // killough 8/8/98
|
|
*demo_p++ = distfriend & 0xff; // killough 8/8/98
|
|
|
|
*demo_p++ = monster_backing; // killough 9/8/98
|
|
|
|
*demo_p++ = monster_avoid_hazards; // killough 9/9/98
|
|
|
|
*demo_p++ = monster_friction; // killough 10/98
|
|
|
|
*demo_p++ = help_friends; // killough 9/9/98
|
|
|
|
#ifdef DOGS
|
|
*demo_p++ = dog_jumping;
|
|
#else
|
|
*demo_p++ = 0;
|
|
#endif
|
|
|
|
*demo_p++ = monkeys;
|
|
|
|
{ // killough 10/98: a compatibility vector now
|
|
int i;
|
|
for (i=0; i < COMP_TOTAL; i++)
|
|
*demo_p++ = comp[i] != 0;
|
|
}
|
|
|
|
*demo_p++ = (compatibility_level >= prboom_2_compatibility) && forceOldBsp; // cph 2002/07/20
|
|
|
|
//----------------
|
|
// Padding at end
|
|
//----------------
|
|
while (demo_p < target)
|
|
*demo_p++ = 0;
|
|
|
|
if (demo_p != target)
|
|
I_Error("G_WriteOptions: GAME_OPTION_SIZE is too small");
|
|
|
|
return target;
|
|
}
|
|
|
|
/* Same, but read instead of write
|
|
* cph - const byte*'s
|
|
*/
|
|
|
|
const byte *G_ReadOptions(const byte *demo_p)
|
|
{
|
|
const byte *target = demo_p + GAME_OPTION_SIZE;
|
|
|
|
monsters_remember = *demo_p++;
|
|
|
|
variable_friction = *demo_p; // ice & mud
|
|
demo_p++;
|
|
|
|
weapon_recoil = *demo_p; // weapon recoil
|
|
demo_p++;
|
|
|
|
allow_pushers = *demo_p; // MT_PUSH Things
|
|
demo_p++;
|
|
|
|
demo_p++;
|
|
|
|
player_bobbing = *demo_p; // whether player bobs or not
|
|
demo_p++;
|
|
|
|
// killough 3/6/98: add parameters to savegame, move from demo
|
|
respawnparm = *demo_p++;
|
|
fastparm = *demo_p++;
|
|
nomonsters = *demo_p++;
|
|
|
|
demo_insurance = *demo_p++; // killough 3/31/98
|
|
|
|
// killough 3/26/98: Added rngseed to demos; 3/31/98: moved here
|
|
|
|
rngseed = *demo_p++ & 0xff;
|
|
rngseed <<= 8;
|
|
rngseed += *demo_p++ & 0xff;
|
|
rngseed <<= 8;
|
|
rngseed += *demo_p++ & 0xff;
|
|
rngseed <<= 8;
|
|
rngseed += *demo_p++ & 0xff;
|
|
|
|
// Options new to v2.03
|
|
if (mbf_features)
|
|
{
|
|
monster_infighting = *demo_p++; // killough 7/19/98
|
|
|
|
#ifdef DOGS
|
|
dogs = *demo_p++; // killough 7/19/98
|
|
#else
|
|
demo_p++;
|
|
#endif
|
|
|
|
demo_p += 2;
|
|
|
|
distfriend = *demo_p++ << 8; // killough 8/8/98
|
|
distfriend+= *demo_p++;
|
|
|
|
monster_backing = *demo_p++; // killough 9/8/98
|
|
|
|
monster_avoid_hazards = *demo_p++; // killough 9/9/98
|
|
|
|
monster_friction = *demo_p++; // killough 10/98
|
|
|
|
help_friends = *demo_p++; // killough 9/9/98
|
|
|
|
#ifdef DOGS
|
|
dog_jumping = *demo_p++; // killough 10/98
|
|
#else
|
|
demo_p++;
|
|
#endif
|
|
|
|
monkeys = *demo_p++;
|
|
|
|
{ // killough 10/98: a compatibility vector now
|
|
int i;
|
|
for (i=0; i < COMP_TOTAL; i++)
|
|
comp[i] = *demo_p++;
|
|
}
|
|
|
|
forceOldBsp = *demo_p++; // cph 2002/07/20
|
|
}
|
|
else /* defaults for versions <= 2.02 */
|
|
{
|
|
/* G_Compatibility will set these */
|
|
}
|
|
|
|
G_Compatibility();
|
|
return target;
|
|
}
|
|
|
|
void G_BeginRecording (void)
|
|
{
|
|
int i;
|
|
byte *demostart, *demo_p;
|
|
demostart = demo_p = malloc(1000);
|
|
longtics = 0;
|
|
|
|
/* cph - 3 demo record formats supported: MBF+, BOOM, and Doom v1.9 */
|
|
if (mbf_features) {
|
|
{ /* Write version code into demo */
|
|
unsigned char v;
|
|
switch(compatibility_level) {
|
|
case mbf_compatibility: v = 203; break; // e6y: Bug in MBF compatibility mode fixed
|
|
case prboom_2_compatibility: v = 210; break;
|
|
case prboom_3_compatibility: v = 211; break;
|
|
case prboom_4_compatibility: v = 212; break;
|
|
case prboom_5_compatibility: v = 213; break;
|
|
case prboom_6_compatibility:
|
|
v = 214;
|
|
longtics = 1;
|
|
break;
|
|
}
|
|
*demo_p++ = v;
|
|
}
|
|
|
|
// signature
|
|
*demo_p++ = 0x1d;
|
|
*demo_p++ = 'M';
|
|
*demo_p++ = 'B';
|
|
*demo_p++ = 'F';
|
|
*demo_p++ = 0xe6;
|
|
*demo_p++ = '\0';
|
|
|
|
/* killough 2/22/98: save compatibility flag in new demos
|
|
* cph - FIXME? MBF demos will always be not in compat. mode */
|
|
*demo_p++ = 0;
|
|
|
|
*demo_p++ = gameskill;
|
|
*demo_p++ = gameepisode;
|
|
*demo_p++ = gamemap;
|
|
*demo_p++ = deathmatch;
|
|
*demo_p++ = consoleplayer;
|
|
|
|
demo_p = G_WriteOptions(demo_p); // killough 3/1/98: Save game options
|
|
|
|
for (i=0 ; i<MAXPLAYERS ; i++)
|
|
*demo_p++ = playeringame[i];
|
|
|
|
// killough 2/28/98:
|
|
// We always store at least MIN_MAXPLAYERS bytes in demo, to
|
|
// support enhancements later w/o losing demo compatibility
|
|
|
|
for (; i<MIN_MAXPLAYERS; i++)
|
|
*demo_p++ = 0;
|
|
|
|
} else if (compatibility_level > boom_compatibility_compatibility) {
|
|
byte v, c; /* Nominally, version and compatibility bits */
|
|
switch (compatibility_level) {
|
|
case boom_compatibility_compatibility: v = 202, c = 1; break;
|
|
case boom_201_compatibility: v = 201; c = 0; break;
|
|
case boom_202_compatibility: v = 202, c = 0; break;
|
|
default: I_Error("G_BeginRecording: Boom compatibility level unrecognised?");
|
|
}
|
|
*demo_p++ = v;
|
|
|
|
// signature
|
|
*demo_p++ = 0x1d;
|
|
*demo_p++ = 'B';
|
|
*demo_p++ = 'o';
|
|
*demo_p++ = 'o';
|
|
*demo_p++ = 'm';
|
|
*demo_p++ = 0xe6;
|
|
|
|
/* CPhipps - save compatibility level in demos */
|
|
*demo_p++ = c;
|
|
|
|
*demo_p++ = gameskill;
|
|
*demo_p++ = gameepisode;
|
|
*demo_p++ = gamemap;
|
|
*demo_p++ = deathmatch;
|
|
*demo_p++ = consoleplayer;
|
|
|
|
demo_p = G_WriteOptions(demo_p); // killough 3/1/98: Save game options
|
|
|
|
for (i=0 ; i<MAXPLAYERS ; i++)
|
|
*demo_p++ = playeringame[i];
|
|
|
|
// killough 2/28/98:
|
|
// We always store at least MIN_MAXPLAYERS bytes in demo, to
|
|
// support enhancements later w/o losing demo compatibility
|
|
|
|
for (; i<MIN_MAXPLAYERS; i++)
|
|
*demo_p++ = 0;
|
|
} else { // cph - write old v1.9 demos (might even sync)
|
|
longtics = M_CheckParm("-longtics");
|
|
*demo_p++ = longtics ? 111 : 109; // v1.9 has best chance of syncing these
|
|
*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<4; i++) // intentionally hard-coded 4 -- killough
|
|
*demo_p++ = playeringame[i];
|
|
}
|
|
|
|
if (fwrite(demostart, 1, demo_p-demostart, demofp) != (size_t)(demo_p-demostart))
|
|
I_Error("G_BeginRecording: Error writing demo header");
|
|
free(demostart);
|
|
}
|
|
|
|
//
|
|
// G_PlayDemo
|
|
//
|
|
|
|
static const char *defdemoname;
|
|
|
|
void G_DeferedPlayDemo (const char* name)
|
|
{
|
|
defdemoname = name;
|
|
gameaction = ga_playdemo;
|
|
}
|
|
|
|
static int demolumpnum = -1;
|
|
|
|
static int G_GetOriginalDoomCompatLevel(int ver)
|
|
{
|
|
{
|
|
int lev;
|
|
int i = M_CheckParm("-complevel");
|
|
if (i && (i+1 < myargc))
|
|
{
|
|
lev = atoi(myargv[i+1]);
|
|
if (lev>=0)
|
|
return lev;
|
|
}
|
|
}
|
|
if (ver < 107) return doom_1666_compatibility;
|
|
if (gamemode == retail) return ultdoom_compatibility;
|
|
if (gamemission >= pack_tnt) return finaldoom_compatibility;
|
|
return doom2_19_compatibility;
|
|
}
|
|
|
|
//e6y: Check for overrun
|
|
static boolean CheckForOverrun(const byte *start_p, const byte *current_p, size_t maxsize, size_t size, boolean failonerror)
|
|
{
|
|
size_t pos = current_p - start_p;
|
|
if (pos + size > maxsize)
|
|
{
|
|
if (failonerror)
|
|
I_Error("G_ReadDemoHeader: wrong demo header\n");
|
|
else
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static const byte* G_ReadDemoHeader(const byte *demo_p, size_t size, boolean failonerror)
|
|
{
|
|
skill_t skill;
|
|
int i, episode, map;
|
|
|
|
// e6y
|
|
// The local variable should be used instead of demobuffer,
|
|
// because demobuffer can be uninitialized
|
|
const byte *header_p = demo_p;
|
|
|
|
const byte *option_p = NULL; /* killough 11/98 */
|
|
|
|
basetic = gametic; // killough 9/29/98
|
|
|
|
// killough 2/22/98, 2/28/98: autodetect old demos and act accordingly.
|
|
// Old demos turn on demo_compatibility => compatibility; new demos load
|
|
// compatibility flag, and other flags as well, as a part of the demo.
|
|
|
|
//e6y: check for overrun
|
|
if (CheckForOverrun(header_p, demo_p, size, 1, failonerror))
|
|
return NULL;
|
|
|
|
demover = *demo_p++;
|
|
longtics = 0;
|
|
|
|
// e6y
|
|
// Handling of unrecognized demo formats
|
|
// Versions up to 1.2 use a 7-byte header - first byte is a skill level.
|
|
// Versions after 1.2 use a 13-byte header - first byte is a demoversion.
|
|
// BOOM's demoversion starts from 200
|
|
if (!((demover >= 0 && demover <= 4) ||
|
|
(demover >= 104 && demover <= 111) ||
|
|
(demover >= 200 && demover <= 214)))
|
|
{
|
|
I_Error("G_ReadDemoHeader: Unknown demo format %d.", demover);
|
|
}
|
|
|
|
if (demover < 200) // Autodetect old demos
|
|
{
|
|
if (demover >= 111) longtics = 1;
|
|
|
|
// killough 3/2/98: force these variables to be 0 in demo_compatibility
|
|
|
|
variable_friction = 0;
|
|
|
|
weapon_recoil = 0;
|
|
|
|
allow_pushers = 0;
|
|
|
|
monster_infighting = 1; // killough 7/19/98
|
|
|
|
#ifdef DOGS
|
|
dogs = 0; // killough 7/19/98
|
|
dog_jumping = 0; // killough 10/98
|
|
#endif
|
|
|
|
monster_backing = 0; // killough 9/8/98
|
|
|
|
monster_avoid_hazards = 0; // killough 9/9/98
|
|
|
|
monster_friction = 0; // killough 10/98
|
|
help_friends = 0; // killough 9/9/98
|
|
monkeys = 0;
|
|
|
|
// killough 3/6/98: rearrange to fix savegame bugs (moved fastparm,
|
|
// respawnparm, nomonsters flags to G_LoadOptions()/G_SaveOptions())
|
|
|
|
if ((skill=demover) >= 100) // For demos from versions >= 1.4
|
|
{
|
|
//e6y: check for overrun
|
|
if (CheckForOverrun(header_p, demo_p, size, 8, failonerror))
|
|
return NULL;
|
|
|
|
compatibility_level = G_GetOriginalDoomCompatLevel(demover);
|
|
skill = *demo_p++;
|
|
episode = *demo_p++;
|
|
map = *demo_p++;
|
|
deathmatch = *demo_p++;
|
|
respawnparm = *demo_p++;
|
|
fastparm = *demo_p++;
|
|
nomonsters = *demo_p++;
|
|
consoleplayer = *demo_p++;
|
|
}
|
|
else
|
|
{
|
|
//e6y: check for overrun
|
|
if (CheckForOverrun(header_p, demo_p, size, 2, failonerror))
|
|
return NULL;
|
|
|
|
compatibility_level = doom_12_compatibility;
|
|
episode = *demo_p++;
|
|
map = *demo_p++;
|
|
deathmatch = respawnparm = fastparm =
|
|
nomonsters = consoleplayer = 0;
|
|
}
|
|
G_Compatibility();
|
|
}
|
|
else // new versions of demos
|
|
{
|
|
demo_p += 6; // skip signature;
|
|
switch (demover) {
|
|
case 200: /* BOOM */
|
|
case 201:
|
|
//e6y: check for overrun
|
|
if (CheckForOverrun(header_p, demo_p, size, 1, failonerror))
|
|
return NULL;
|
|
|
|
if (!*demo_p++)
|
|
compatibility_level = boom_201_compatibility;
|
|
else
|
|
compatibility_level = boom_compatibility_compatibility;
|
|
break;
|
|
case 202:
|
|
//e6y: check for overrun
|
|
if (CheckForOverrun(header_p, demo_p, size, 1, failonerror))
|
|
return NULL;
|
|
|
|
if (!*demo_p++)
|
|
compatibility_level = boom_202_compatibility;
|
|
else
|
|
compatibility_level = boom_compatibility_compatibility;
|
|
break;
|
|
case 203:
|
|
/* LxDoom or MBF - determine from signature
|
|
* cph - load compatibility level */
|
|
switch (*(header_p + 2)) {
|
|
case 'B': /* LxDoom */
|
|
/* cph - DEMOSYNC - LxDoom demos recorded in compatibility modes support dropped */
|
|
compatibility_level = lxdoom_1_compatibility;
|
|
break;
|
|
case 'M':
|
|
compatibility_level = mbf_compatibility;
|
|
demo_p++;
|
|
break;
|
|
}
|
|
break;
|
|
case 210:
|
|
compatibility_level = prboom_2_compatibility;
|
|
demo_p++;
|
|
break;
|
|
case 211:
|
|
compatibility_level = prboom_3_compatibility;
|
|
demo_p++;
|
|
break;
|
|
case 212:
|
|
compatibility_level = prboom_4_compatibility;
|
|
demo_p++;
|
|
break;
|
|
case 213:
|
|
compatibility_level = prboom_5_compatibility;
|
|
demo_p++;
|
|
break;
|
|
case 214:
|
|
compatibility_level = prboom_6_compatibility;
|
|
longtics = 1;
|
|
demo_p++;
|
|
break;
|
|
}
|
|
//e6y: check for overrun
|
|
if (CheckForOverrun(header_p, demo_p, size, 5, failonerror))
|
|
return NULL;
|
|
|
|
skill = *demo_p++;
|
|
episode = *demo_p++;
|
|
map = *demo_p++;
|
|
deathmatch = *demo_p++;
|
|
consoleplayer = *demo_p++;
|
|
|
|
/* killough 11/98: save option pointer for below */
|
|
if (mbf_features)
|
|
option_p = demo_p;
|
|
|
|
//e6y: check for overrun
|
|
if (CheckForOverrun(header_p, demo_p, size, GAME_OPTION_SIZE, failonerror))
|
|
return NULL;
|
|
|
|
demo_p = G_ReadOptions(demo_p); // killough 3/1/98: Read game options
|
|
|
|
if (demover == 200) // killough 6/3/98: partially fix v2.00 demos
|
|
demo_p += 256-GAME_OPTION_SIZE;
|
|
}
|
|
|
|
if (sizeof(comp_lev_str)/sizeof(comp_lev_str[0]) != MAX_COMPATIBILITY_LEVEL)
|
|
I_Error("G_ReadDemoHeader: compatibility level strings incomplete");
|
|
lprintf(LO_INFO, "G_DoPlayDemo: playing demo with %s compatibility\n",
|
|
comp_lev_str[compatibility_level]);
|
|
|
|
if (demo_compatibility) // only 4 players can exist in old demos
|
|
{
|
|
//e6y: check for overrun
|
|
if (CheckForOverrun(header_p, demo_p, size, 4, failonerror))
|
|
return NULL;
|
|
|
|
for (i=0; i<4; i++) // intentionally hard-coded 4 -- killough
|
|
playeringame[i] = *demo_p++;
|
|
for (;i < MAXPLAYERS; i++)
|
|
playeringame[i] = 0;
|
|
}
|
|
else
|
|
{
|
|
//e6y: check for overrun
|
|
if (CheckForOverrun(header_p, demo_p, size, MAXPLAYERS, failonerror))
|
|
return NULL;
|
|
|
|
for (i=0 ; i < MAXPLAYERS; i++)
|
|
playeringame[i] = *demo_p++;
|
|
demo_p += MIN_MAXPLAYERS - MAXPLAYERS;
|
|
}
|
|
|
|
if (playeringame[1])
|
|
{
|
|
netgame = true;
|
|
netdemo = true;
|
|
}
|
|
|
|
if (gameaction != ga_loadgame) { /* killough 12/98: support -loadgame */
|
|
G_InitNew(skill, episode, map);
|
|
}
|
|
|
|
for (i=0; i<MAXPLAYERS;i++) // killough 4/24/98
|
|
players[i].cheats = 0;
|
|
|
|
return demo_p;
|
|
}
|
|
|
|
void G_DoPlayDemo(void)
|
|
{
|
|
char basename[9];
|
|
|
|
ExtractFileBase(defdemoname,basename); // killough
|
|
basename[8] = 0;
|
|
|
|
/* cph - store lump number for unlocking later */
|
|
demolumpnum = W_GetNumForName(basename);
|
|
demobuffer = W_CacheLumpNum(demolumpnum);
|
|
demolength = W_LumpLength(demolumpnum);
|
|
|
|
demo_p = G_ReadDemoHeader(demobuffer, demolength, true);
|
|
|
|
gameaction = ga_nothing;
|
|
usergame = false;
|
|
|
|
demoplayback = true;
|
|
R_SmoothPlaying_Reset(NULL); // e6y
|
|
|
|
starttime = I_GetTime_RealTime ();
|
|
}
|
|
|
|
/* 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)
|
|
{
|
|
P_ChecksumFinal();
|
|
|
|
if (demorecording)
|
|
{
|
|
demorecording = false;
|
|
fputc(DEMOMARKER, demofp);
|
|
I_Error("G_CheckDemoStatus: Demo recorded");
|
|
return false; // killough
|
|
}
|
|
|
|
if (timingdemo)
|
|
{
|
|
int endtime = I_GetTime_RealTime ();
|
|
// killough -- added fps information and made it work for longer demos:
|
|
unsigned realtics = endtime-starttime;
|
|
I_Error ("Timed %u gametics in %u realtics = %-.1f frames per second",
|
|
(unsigned) gametic,realtics,
|
|
(unsigned) gametic * (double) TICRATE / realtics);
|
|
}
|
|
|
|
if (demoplayback)
|
|
{
|
|
if (singledemo)
|
|
exit(0); // killough
|
|
|
|
if (demolumpnum != -1) {
|
|
// cph - unlock the demo lump
|
|
W_UnlockLumpNum(demolumpnum);
|
|
demolumpnum = -1;
|
|
}
|
|
G_ReloadDefaults(); // killough 3/1/98
|
|
netgame = false; // killough 3/29/98
|
|
deathmatch = false;
|
|
D_AdvanceDemo ();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// killough 1/22/98: this is a "Doom printf" for messages. I've gotten
|
|
// tired of using players->message=... and so I've added this dprintf.
|
|
//
|
|
// killough 3/6/98: Made limit static to allow z_zone functions to call
|
|
// this function, without calling realloc(), which seems to cause problems.
|
|
|
|
#define MAX_MESSAGE_SIZE 1024
|
|
|
|
// CPhipps - renamed to doom_printf to avoid name collision with glibc
|
|
void doom_printf(const char *s, ...)
|
|
{
|
|
static char msg[MAX_MESSAGE_SIZE];
|
|
va_list v;
|
|
va_start(v,s);
|
|
#ifdef HAVE_VSNPRINTF
|
|
vsnprintf(msg,sizeof(msg),s,v); /* print message in buffer */
|
|
#else
|
|
vsprintf(msg,s,v);
|
|
#endif
|
|
va_end(v);
|
|
players[consoleplayer].message = msg; // set new message
|
|
}
|