2016-03-01 15:47:10 +00:00
//-----------------------------------------------------------------------------
//
2017-04-17 11:33:19 +00:00
// Copyright 1993-1996 id Software
// Copyright 1999-2016 Randy Heit
// Copyright 2002-2016 Christoph Oelckers
2016-03-01 15:47:10 +00:00
//
2017-04-17 11:33:19 +00:00
// 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 3 of the License, or
// (at your option) any later version.
2016-03-01 15:47:10 +00:00
//
2017-04-17 11:33:19 +00:00
// This program is distributed in the hope that it will be useful,
2016-03-01 15:47:10 +00:00
// but WITHOUT ANY WARRANTY; without even the implied warranty of
2017-04-17 11:33:19 +00:00
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
2016-03-01 15:47:10 +00:00
//
2017-04-17 11:33:19 +00:00
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses/
2016-03-01 15:47:10 +00:00
//
//-----------------------------------------------------------------------------
2017-04-17 11:33:19 +00:00
//
2016-03-01 15:47:10 +00:00
# include <string.h>
# include <stdlib.h>
# include <stdio.h>
# include <stddef.h>
- removed the sequential processing of JSON objects because the benefit is too small.
After testing with a savegame on ZDCMP2 which is probably the largest map in existence, timing both methods resulted in a speed difference of less than 40 ms (70 vs 110 ms for reading all sectory, linedefs, sidedefs and objects).
This compares to an overall restoration time, including reloading the level, precaching all textures and setting everything up, of approx. 1.2 s, meaning an increase of 3% of the entire reloading time.
That's simply not worth all the negative side effects that may happen with a method that highly depends on proper code construction.
On the other hand, using random access means that a savegame version change is only needed now when the semantics of a field change, but not if some get added or deleted.
- do not I_Error out in the serializer unless caused by a programming error.
It is better to let the serializer finish, collect all the errors and I_Error out when the game is known to be in a stable enough state to allow unwinding.
2016-09-23 12:04:05 +00:00
# include <memory>
2016-03-01 15:47:10 +00:00
2017-11-12 08:06:40 +00:00
# include "i_time.h"
2021-10-30 08:49:12 +00:00
2016-03-01 15:47:10 +00:00
# include "version.h"
# include "doomdef.h"
# include "doomstat.h"
# include "d_protocol.h"
# include "d_netinf.h"
# include "intermission/intermission.h"
# include "m_argv.h"
# include "m_misc.h"
2020-06-13 22:27:32 +00:00
# include "menu.h"
2016-03-01 15:47:10 +00:00
# include "m_crc32.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"
# include "c_console.h"
# include "c_bind.h"
# include "c_dispatch.h"
2020-04-11 11:36:23 +00:00
# include "filesystem.h"
2016-03-01 15:47:10 +00:00
# include "p_local.h"
# include "gstrings.h"
# include "r_sky.h"
# include "g_game.h"
# include "sbar.h"
# include "m_png.h"
# include "a_keys.h"
# include "cmdlib.h"
# include "d_net.h"
# include "d_event.h"
# include "p_acs.h"
# include "p_effect.h"
# include "m_joy.h"
# include "r_utility.h"
# include "a_morph.h"
# include "p_spec.h"
2020-04-11 17:27:11 +00:00
# include "serializer_doom.h"
2017-04-12 23:12:04 +00:00
# include "vm.h"
2018-11-19 17:13:23 +00:00
# include "dobjgc.h"
2019-01-05 07:48:57 +00:00
# include "gi.h"
2019-01-29 00:09:02 +00:00
# include "a_dynlight.h"
2019-01-31 18:38:04 +00:00
# include "i_system.h"
2019-04-04 22:59:32 +00:00
# include "p_conversation.h"
2020-04-11 17:49:09 +00:00
# include "v_palette.h"
2020-10-25 08:16:37 +00:00
# include "s_music.h"
# include "p_setup.h"
2021-05-22 10:58:07 +00:00
# include "d_event.h"
2022-07-01 05:23:55 +00:00
# include "model.h"
2016-03-01 15:47:10 +00:00
2020-04-11 10:25:57 +00:00
# include "v_video.h"
2016-03-01 15:47:10 +00:00
# include "g_hub.h"
2017-01-08 17:45:30 +00:00
# include "g_levellocals.h"
2017-02-02 18:46:10 +00:00
# include "events.h"
2020-04-11 16:00:10 +00:00
# include "c_buttons.h"
2020-04-11 16:09:51 +00:00
# include "d_buttons.h"
2020-04-25 11:18:57 +00:00
# include "hwrenderer/scene/hw_drawinfo.h"
2020-06-13 20:43:35 +00:00
# include "doommenu.h"
2022-04-22 11:39:44 +00:00
# include "screenjob.h"
2022-10-02 13:14:11 +00:00
# include "i_interface.h"
2023-08-19 13:30:40 +00:00
# include "fs_findfile.h"
2016-03-01 15:47:10 +00:00
static FRandom pr_dmspawn ( " DMSpawn " ) ;
static FRandom pr_pspawn ( " PlayerSpawn " ) ;
bool G_CheckDemoStatus ( void ) ;
void G_ReadDemoTiccmd ( ticcmd_t * cmd , int player ) ;
void G_WriteDemoTiccmd ( ticcmd_t * cmd , int player , int buf ) ;
void G_PlayerReborn ( int player ) ;
void G_DoNewGame ( void ) ;
void G_DoLoadGame ( void ) ;
void G_DoPlayDemo ( void ) ;
void G_DoCompleted ( void ) ;
void G_DoVictory ( void ) ;
void G_DoWorldDone ( void ) ;
2019-07-13 20:38:44 +00:00
void G_DoSaveGame ( bool okForQuicksave , bool forceQuicksave , FString filename , const char * description ) ;
2016-03-01 15:47:10 +00:00
void G_DoAutoSave ( ) ;
2019-07-13 20:38:44 +00:00
void G_DoQuickSave ( ) ;
2016-03-01 15:47:10 +00:00
2016-09-21 10:19:13 +00:00
void STAT_Serialize ( FSerializer & file ) ;
2016-03-01 15:47:10 +00:00
2022-10-21 16:28:28 +00:00
CVARD_NAMED ( Int , gameskill , skill , 2 , CVAR_SERVERINFO | CVAR_LATCH , " sets the skill for the next newly started game " )
2016-09-21 19:57:24 +00:00
CVAR ( Bool , save_formatted , false , CVAR_ARCHIVE | CVAR_GLOBALCONFIG ) // use formatted JSON for saves (more readable but a larger files and a bit slower.
2016-03-01 15:47:10 +00:00
CVAR ( Int , deathmatch , 0 , CVAR_SERVERINFO | CVAR_LATCH ) ;
CVAR ( Bool , chasedemo , false , 0 ) ;
CVAR ( Bool , storesavepic , true , CVAR_ARCHIVE | CVAR_GLOBALCONFIG )
2022-07-21 17:22:30 +00:00
CVAR ( Bool , longsavemessages , false , CVAR_ARCHIVE | CVAR_GLOBALCONFIG )
2016-03-01 15:47:10 +00:00
CVAR ( Bool , cl_waitforsave , true , CVAR_ARCHIVE | CVAR_GLOBALCONFIG ) ;
2019-03-03 05:01:00 +00:00
CVAR ( Bool , enablescriptscreenshot , false , CVAR_ARCHIVE | CVAR_GLOBALCONFIG ) ;
2016-03-01 15:47:10 +00:00
EXTERN_CVAR ( Float , con_midtime ) ;
//==========================================================================
//
// CVAR displaynametags
//
// Selects whether to display name tags or not when changing weapons/items
//
//==========================================================================
CUSTOM_CVAR ( Int , displaynametags , 0 , CVAR_ARCHIVE )
{
if ( self < 0 | | self > 3 )
{
self = 0 ;
}
}
CVAR ( Int , nametagcolor , CR_GOLD , CVAR_ARCHIVE )
2021-05-17 09:41:43 +00:00
extern bool playedtitlemusic ;
2016-03-01 15:47:10 +00:00
gameaction_t gameaction ;
bool sendpause ; // send a pause event next tic
bool sendsave ; // send a save event next tic
bool sendturn180 ; // [RH] send a 180 degree turn next tic
bool usergame ; // ok to save / end game
bool insave ; // Game is saving - used to block exit commands
bool timingdemo ; // if true, exit with report on completion
bool nodrawers ; // for comparative timing purposes
bool noblit ; // for comparative timing purposes
bool viewactive ;
2016-11-01 13:47:01 +00:00
bool multiplayernext = false ; // [SP] Map coop/dm implementation
2016-03-01 15:47:10 +00:00
player_t players [ MAXPLAYERS ] ;
bool playeringame [ MAXPLAYERS ] ;
int gametic ;
CVAR ( Bool , demo_compress , true , CVAR_ARCHIVE | CVAR_GLOBALCONFIG ) ;
FString newdemoname ;
FString newdemomap ;
FString demoname ;
bool demorecording ;
bool demoplayback ;
bool demonew ; // [RH] Only used around G_InitNew for demos
int demover ;
2017-03-08 17:47:52 +00:00
uint8_t * demobuffer ;
uint8_t * demo_p ;
uint8_t * democompspot ;
uint8_t * demobodyspot ;
2016-03-01 15:47:10 +00:00
size_t maxdemosize ;
2017-03-08 17:47:52 +00:00
uint8_t * zdemformend ; // end of FORM ZDEM chunk
uint8_t * zdembodyend ; // end of ZDEM BODY chunk
2016-03-01 15:47:10 +00:00
bool singledemo ; // quit after playing a demo from cmdline
bool precache = true ; // if true, load all graphics at start
- allow the language table to supersede the title patches, if appropriate
For the Doom IWADs the provided font looks almost identical to the characters used on the title patches. So, for any level name that got replaced in some language, it will now check if the retrieved name comes from the default table, and if not, ignore the title patch and print the name with the specified font.
This also required removing the 'en' label from the default table, because with this present, the text would always be picked from 'en' instead of 'default'. Since 'en' and 'default' had the same contents, in any English locale the 'default' table was never hit, so this won't make any difference for the texts being chosen.
Last but not least, wminfo has been made a local variable in G_DoCompleted. There were two places where this was accessed from outside the summary screen or its setup code, and both were incorrect.
2019-02-14 23:29:24 +00:00
2016-03-01 15:47:10 +00:00
short consistancy [ MAXPLAYERS ] [ BACKUPTICS ] ;
# define MAXPLMOVE (forwardmove[1])
# define TURBOTHRESHOLD 12800
2019-08-08 22:57:07 +00:00
EXTERN_CVAR ( Int , turnspeedwalkfast )
EXTERN_CVAR ( Int , turnspeedsprintfast )
EXTERN_CVAR ( Int , turnspeedwalkslow )
EXTERN_CVAR ( Int , turnspeedsprintslow )
2016-03-01 15:47:10 +00:00
2016-03-26 08:28:00 +00:00
int forwardmove [ 2 ] , sidemove [ 2 ] ;
2022-10-21 16:28:28 +00:00
FIntCVarRef * angleturn [ 4 ] = { & turnspeedwalkfast , & turnspeedsprintfast , & turnspeedwalkslow , & turnspeedsprintslow } ;
2016-03-26 08:28:00 +00:00
int flyspeed [ 2 ] = { 1 * 256 , 3 * 256 } ;
2016-03-01 15:47:10 +00:00
int lookspeed [ 2 ] = { 450 , 512 } ;
# define SLOWTURNTICS 6
CVAR ( Bool , cl_run , false , CVAR_GLOBALCONFIG | CVAR_ARCHIVE ) // Always run?
2020-02-03 20:41:52 +00:00
CVAR ( Bool , freelook , true , CVAR_GLOBALCONFIG | CVAR_ARCHIVE ) // Always mlook?
2016-03-01 15:47:10 +00:00
CVAR ( Bool , lookstrafe , false , CVAR_GLOBALCONFIG | CVAR_ARCHIVE ) // Always strafe with mouse?
CVAR ( Float , m_forward , 1.f , CVAR_GLOBALCONFIG | CVAR_ARCHIVE )
CVAR ( Float , m_side , 2.f , CVAR_GLOBALCONFIG | CVAR_ARCHIVE )
int turnheld ; // for accelerative turning
// mouse values are used once
2020-09-28 20:12:44 +00:00
float mousex ;
float mousey ;
2016-03-01 15:47:10 +00:00
FString savegamefile ;
2017-02-18 15:40:32 +00:00
FString savedescription ;
2016-03-01 15:47:10 +00:00
// [RH] Name of screenshot file to generate (usually NULL)
FString shotfile ;
FString savename ;
FString BackupSaveName ;
bool SendLand ;
2018-12-04 16:00:48 +00:00
const AActor * SendItemUse , * SendItemDrop ;
2017-02-23 19:18:02 +00:00
int SendItemDropAmount ;
2016-03-01 15:47:10 +00:00
2019-01-30 00:38:18 +00:00
extern uint8_t globalfreeze ;
2016-03-01 15:47:10 +00:00
EXTERN_CVAR ( Int , team )
CVAR ( Bool , teamplay , false , CVAR_SERVERINFO )
2019-01-18 10:07:13 +00:00
// Workaround for x64 code generation bug in MSVC 2015
// Optimized targets contain illegal instructions in the function below
# if defined _M_X64 && _MSC_VER < 1910
# pragma optimize("", off)
# endif // _M_X64 && _MSC_VER < 1910
2016-03-01 15:47:10 +00:00
// [RH] Allow turbo setting anytime during game
2019-01-05 07:48:57 +00:00
CUSTOM_CVAR ( Float , turbo , 100.f , CVAR_NOINITCALL )
2016-03-01 15:47:10 +00:00
{
if ( self < 10.f )
{
self = 10.f ;
}
else if ( self > 255.f )
{
self = 255.f ;
}
else
{
double scale = self * 0.01 ;
2019-01-05 07:48:57 +00:00
forwardmove [ 0 ] = ( int ) ( gameinfo . normforwardmove [ 0 ] * scale ) ;
forwardmove [ 1 ] = ( int ) ( gameinfo . normforwardmove [ 1 ] * scale ) ;
sidemove [ 0 ] = ( int ) ( gameinfo . normsidemove [ 0 ] * scale ) ;
sidemove [ 1 ] = ( int ) ( gameinfo . normsidemove [ 1 ] * scale ) ;
2016-03-01 15:47:10 +00:00
}
}
2019-01-18 10:07:13 +00:00
# if defined _M_X64 && _MSC_VER < 1910
# pragma optimize("", on)
# endif // _M_X64 && _MSC_VER < 1910
2016-03-01 15:47:10 +00:00
CCMD ( turnspeeds )
{
if ( argv . argc ( ) = = 1 )
{
2019-08-18 16:45:18 +00:00
Printf ( " Current turn speeds: \n "
TEXTCOLOR_BLUE " turnspeedwalkfast: " TEXTCOLOR_GREEN " %d \n "
TEXTCOLOR_BLUE " turnspeedsprintfast: " TEXTCOLOR_GREEN " %d \n "
TEXTCOLOR_BLUE " turnspeedwalkslow: " TEXTCOLOR_GREEN " %d \n "
TEXTCOLOR_BLUE " turnspeedsprintslow: " TEXTCOLOR_GREEN " %d \n " , * turnspeedwalkfast ,
* turnspeedsprintfast , * turnspeedwalkslow , * turnspeedsprintslow ) ;
2016-03-01 15:47:10 +00:00
}
else
{
int i ;
2019-08-18 16:45:18 +00:00
2016-03-01 15:47:10 +00:00
for ( i = 1 ; i < = 4 & & i < argv . argc ( ) ; + + i )
{
2019-08-18 16:45:18 +00:00
* angleturn [ i - 1 ] = atoi ( argv [ i ] ) ;
2016-03-01 15:47:10 +00:00
}
if ( i < = 2 )
{
2019-08-18 16:45:18 +00:00
* angleturn [ 1 ] = * angleturn [ 0 ] * 2 ;
2016-03-01 15:47:10 +00:00
}
if ( i < = 3 )
{
2019-08-18 16:45:18 +00:00
* angleturn [ 2 ] = * angleturn [ 0 ] / 2 ;
2016-03-01 15:47:10 +00:00
}
if ( i < = 4 )
{
2019-08-18 16:45:18 +00:00
* angleturn [ 3 ] = * angleturn [ 2 ] ;
2016-03-01 15:47:10 +00:00
}
}
}
CCMD ( slot )
{
if ( argv . argc ( ) > 1 )
{
int slot = atoi ( argv [ 1 ] ) ;
2018-11-24 21:22:36 +00:00
auto mo = players [ consoleplayer ] . mo ;
if ( slot < NUM_WEAPON_SLOTS & & mo )
2016-03-01 15:47:10 +00:00
{
2018-11-24 21:03:56 +00:00
// Needs to be redone
2019-01-03 21:05:49 +00:00
IFVIRTUALPTRNAME ( mo , NAME_PlayerPawn , PickWeapon )
2018-11-24 21:22:36 +00:00
{
VMValue param [ ] = { mo , slot , ! ( dmflags2 & DF2_DONTCHECKAMMO ) } ;
VMReturn ret ( ( void * * ) & SendItemUse ) ;
VMCall ( func , param , 3 , & ret , 1 ) ;
}
2016-03-01 15:47:10 +00:00
}
2020-03-14 12:17:04 +00:00
// [Nash] Option to display the name of the weapon being switched to.
2023-08-09 12:55:18 +00:00
if ( ( paused | | pauseext ) | | players [ consoleplayer ] . playerstate ! = PST_LIVE )
return ;
2020-03-14 12:17:04 +00:00
if ( SendItemUse ! = players [ consoleplayer ] . ReadyWeapon & & ( displaynametags & 2 ) & & StatusBar & & SmallFont & & SendItemUse )
{
StatusBar - > AttachMessage ( Create < DHUDMessageFadeOut > ( nullptr , SendItemUse - > GetTag ( ) ,
1.5f , 0.90f , 0 , 0 , ( EColorRange ) * nametagcolor , 2.f , 0.35f ) , MAKE_ID ( ' W ' , ' E ' , ' P ' , ' N ' ) ) ;
}
2016-03-01 15:47:10 +00:00
}
}
CCMD ( centerview )
{
2023-08-09 12:55:18 +00:00
if ( ( players [ consoleplayer ] . cheats & CF_TOTALLYFROZEN ) )
return ;
2016-03-01 15:47:10 +00:00
Net_WriteByte ( DEM_CENTERVIEW ) ;
}
CCMD ( crouch )
{
Net_WriteByte ( DEM_CROUCH ) ;
}
CCMD ( land )
{
SendLand = true ;
}
CCMD ( pause )
{
sendpause = true ;
}
CCMD ( turn180 )
{
sendturn180 = true ;
}
CCMD ( weapnext )
{
2018-11-24 21:22:36 +00:00
auto mo = players [ consoleplayer ] . mo ;
if ( mo )
{
// Needs to be redone
2019-01-03 21:05:49 +00:00
IFVIRTUALPTRNAME ( mo , NAME_PlayerPawn , PickNextWeapon )
2018-11-24 21:22:36 +00:00
{
VMValue param [ ] = { mo } ;
VMReturn ret ( ( void * * ) & SendItemUse ) ;
VMCall ( func , param , 1 , & ret , 1 ) ;
}
}
2016-03-01 15:47:10 +00:00
// [BC] Option to display the name of the weapon being cycled to.
2021-04-26 21:42:28 +00:00
if ( ( paused | | pauseext ) | | players [ consoleplayer ] . playerstate ! = PST_LIVE ) return ;
2016-03-01 15:47:10 +00:00
if ( ( displaynametags & 2 ) & & StatusBar & & SmallFont & & SendItemUse )
{
2019-04-09 22:28:40 +00:00
StatusBar - > AttachMessage ( Create < DHUDMessageFadeOut > ( nullptr , SendItemUse - > GetTag ( ) ,
2016-03-01 15:47:10 +00:00
1.5f , 0.90f , 0 , 0 , ( EColorRange ) * nametagcolor , 2.f , 0.35f ) , MAKE_ID ( ' W ' , ' E ' , ' P ' , ' N ' ) ) ;
}
2017-02-25 19:20:43 +00:00
if ( SendItemUse ! = players [ consoleplayer ] . ReadyWeapon )
{
2019-12-16 22:52:39 +00:00
S_Sound ( CHAN_AUTO , 0 , " misc/weaponchange " , 1.0 , ATTN_NONE ) ;
2017-02-25 19:20:43 +00:00
}
2016-03-01 15:47:10 +00:00
}
CCMD ( weapprev )
{
2018-11-24 21:22:36 +00:00
auto mo = players [ consoleplayer ] . mo ;
if ( mo )
{
// Needs to be redone
2019-01-03 21:05:49 +00:00
IFVIRTUALPTRNAME ( mo , NAME_PlayerPawn , PickPrevWeapon )
2018-11-24 21:22:36 +00:00
{
VMValue param [ ] = { mo } ;
VMReturn ret ( ( void * * ) & SendItemUse ) ;
VMCall ( func , param , 1 , & ret , 1 ) ;
}
}
2016-03-01 15:47:10 +00:00
// [BC] Option to display the name of the weapon being cycled to.
2021-04-26 21:42:28 +00:00
if ( ( paused | | pauseext ) | | players [ consoleplayer ] . playerstate ! = PST_LIVE ) return ;
2016-03-01 15:47:10 +00:00
if ( ( displaynametags & 2 ) & & StatusBar & & SmallFont & & SendItemUse )
{
2019-04-09 22:28:40 +00:00
StatusBar - > AttachMessage ( Create < DHUDMessageFadeOut > ( nullptr , SendItemUse - > GetTag ( ) ,
2016-03-01 15:47:10 +00:00
1.5f , 0.90f , 0 , 0 , ( EColorRange ) * nametagcolor , 2.f , 0.35f ) , MAKE_ID ( ' W ' , ' E ' , ' P ' , ' N ' ) ) ;
}
2017-02-25 19:20:43 +00:00
if ( SendItemUse ! = players [ consoleplayer ] . ReadyWeapon )
{
2019-12-16 22:52:39 +00:00
S_Sound ( CHAN_AUTO , 0 , " misc/weaponchange " , 1.0 , ATTN_NONE ) ;
2017-02-25 19:20:43 +00:00
}
2016-03-01 15:47:10 +00:00
}
2018-12-01 21:37:12 +00:00
static void DisplayNameTag ( AActor * actor )
2018-12-01 16:09:43 +00:00
{
2018-12-01 21:37:12 +00:00
auto tag = actor - > GetTag ( ) ;
2018-12-01 16:09:43 +00:00
if ( ( displaynametags & 1 ) & & StatusBar & & SmallFont )
2019-04-09 22:28:40 +00:00
StatusBar - > AttachMessage ( Create < DHUDMessageFadeOut > ( nullptr , tag ,
2018-12-01 16:09:43 +00:00
1.5f , 0.80f , 0 , 0 , ( EColorRange ) * nametagcolor , 2.f , 0.35f ) , MAKE_ID ( ' S ' , ' I ' , ' N ' , ' V ' ) ) ;
}
2018-12-01 21:37:12 +00:00
DEFINE_ACTION_FUNCTION_NATIVE ( AActor , DisplayNameTag , DisplayNameTag )
2016-03-01 15:47:10 +00:00
{
2018-12-01 21:37:12 +00:00
PARAM_SELF_PROLOGUE ( AActor ) ;
DisplayNameTag ( self ) ;
return 0 ;
}
2016-03-01 15:47:10 +00:00
2018-12-01 21:37:12 +00:00
CCMD ( invnext )
{
2020-04-11 16:02:27 +00:00
if ( players [ consoleplayer ] . mo ! = nullptr )
2016-03-01 15:47:10 +00:00
{
2018-12-01 21:37:12 +00:00
IFVM ( PlayerPawn , InvNext )
2016-03-01 15:47:10 +00:00
{
2020-04-11 16:02:27 +00:00
VMValue param = players [ consoleplayer ] . mo ;
2018-12-01 21:37:12 +00:00
VMCall ( func , & param , 1 , nullptr , 0 ) ;
2016-03-01 15:47:10 +00:00
}
2017-02-25 19:20:43 +00:00
}
2016-03-01 15:47:10 +00:00
}
2018-12-01 21:37:12 +00:00
CCMD ( invprev )
2016-03-01 15:47:10 +00:00
{
2020-04-11 16:02:27 +00:00
if ( players [ consoleplayer ] . mo ! = nullptr )
2016-03-01 15:47:10 +00:00
{
2018-12-02 09:30:56 +00:00
IFVM ( PlayerPawn , InvPrev )
2016-03-01 15:47:10 +00:00
{
2020-04-11 16:02:27 +00:00
VMValue param = players [ consoleplayer ] . mo ;
2018-12-01 21:37:12 +00:00
VMCall ( func , & param , 1 , nullptr , 0 ) ;
2016-03-01 15:47:10 +00:00
}
2017-02-25 19:20:43 +00:00
}
2016-03-01 15:47:10 +00:00
}
CCMD ( invuseall )
{
2018-12-04 16:00:48 +00:00
SendItemUse = ( const AActor * ) 1 ;
2016-03-01 15:47:10 +00:00
}
CCMD ( invuse )
{
if ( players [ consoleplayer ] . inventorytics = = 0 )
{
2019-01-03 17:01:58 +00:00
if ( players [ consoleplayer ] . mo ) SendItemUse = players [ consoleplayer ] . mo - > PointerVar < AActor > ( NAME_InvSel ) ;
2016-03-01 15:47:10 +00:00
}
players [ consoleplayer ] . inventorytics = 0 ;
}
CCMD ( invquery )
{
2019-01-03 17:01:58 +00:00
AActor * inv = players [ consoleplayer ] . mo - > PointerVar < AActor > ( NAME_InvSel ) ;
2016-03-01 15:47:10 +00:00
if ( inv ! = NULL )
{
2018-12-02 23:43:01 +00:00
Printf ( PRINT_HIGH , " %s (%dx) \n " , inv - > GetTag ( ) , inv - > IntVar ( NAME_Amount ) ) ;
2016-03-01 15:47:10 +00:00
}
}
CCMD ( use )
{
2020-04-11 16:02:27 +00:00
if ( argv . argc ( ) > 1 & & players [ consoleplayer ] . mo ! = NULL )
2016-03-01 15:47:10 +00:00
{
2020-04-11 16:02:27 +00:00
SendItemUse = players [ consoleplayer ] . mo - > FindInventory ( argv [ 1 ] ) ;
2016-03-01 15:47:10 +00:00
}
}
CCMD ( invdrop )
{
if ( players [ consoleplayer ] . mo )
{
2019-01-03 17:01:58 +00:00
SendItemDrop = players [ consoleplayer ] . mo - > PointerVar < AActor > ( NAME_InvSel ) ;
2017-02-23 19:18:02 +00:00
SendItemDropAmount = - 1 ;
2016-03-01 15:47:10 +00:00
}
}
CCMD ( weapdrop )
{
SendItemDrop = players [ consoleplayer ] . ReadyWeapon ;
2017-02-23 19:18:02 +00:00
SendItemDropAmount = - 1 ;
2016-03-01 15:47:10 +00:00
}
CCMD ( drop )
{
2020-04-11 16:02:27 +00:00
if ( argv . argc ( ) > 1 & & players [ consoleplayer ] . mo ! = NULL )
2016-03-01 15:47:10 +00:00
{
2020-04-11 16:02:27 +00:00
SendItemDrop = players [ consoleplayer ] . mo - > FindInventory ( argv [ 1 ] ) ;
2017-02-23 19:18:02 +00:00
SendItemDropAmount = argv . argc ( ) > 2 ? atoi ( argv [ 2 ] ) : - 1 ;
2016-03-01 15:47:10 +00:00
}
}
CCMD ( useflechette )
2019-01-02 10:53:53 +00:00
{
2020-04-11 16:02:27 +00:00
if ( players [ consoleplayer ] . mo = = nullptr ) return ;
IFVIRTUALPTRNAME ( players [ consoleplayer ] . mo , NAME_PlayerPawn , GetFlechetteItem )
2016-03-01 15:47:10 +00:00
{
2020-04-11 16:02:27 +00:00
VMValue params [ ] = { players [ consoleplayer ] . mo } ;
2019-01-02 10:53:53 +00:00
AActor * cls ;
VMReturn ret ( ( void * * ) & cls ) ;
VMCall ( func , params , 1 , & ret , 1 ) ;
2016-03-01 15:47:10 +00:00
2019-01-02 10:53:53 +00:00
if ( cls ! = nullptr ) SendItemUse = cls ;
2016-03-01 15:47:10 +00:00
}
}
CCMD ( select )
{
2020-04-11 16:02:27 +00:00
if ( ! players [ consoleplayer ] . mo ) return ;
auto user = players [ consoleplayer ] . mo ;
2016-03-01 15:47:10 +00:00
if ( argv . argc ( ) > 1 )
{
2020-04-11 16:00:10 +00:00
auto item = user - > FindInventory ( argv [ 1 ] ) ;
2016-03-01 15:47:10 +00:00
if ( item ! = NULL )
{
2020-04-11 16:00:10 +00:00
user - > PointerVar < AActor > ( NAME_InvSel ) = item ;
2016-03-01 15:47:10 +00:00
}
}
2020-04-11 16:00:10 +00:00
user - > player - > inventorytics = 5 * TICRATE ;
2016-03-01 15:47:10 +00:00
}
static inline int joyint ( double val )
{
if ( val > = 0 )
{
return int ( ceil ( val ) ) ;
}
else
{
return int ( floor ( val ) ) ;
}
}
2020-04-11 11:48:55 +00:00
FBaseCVar * G_GetUserCVar ( int playernum , const char * cvarname )
{
if ( ( unsigned ) playernum > = MAXPLAYERS | | ! playeringame [ playernum ] )
{
return nullptr ;
}
FBaseCVar * * cvar_p = players [ playernum ] . userinfo . CheckKey ( FName ( cvarname , true ) ) ;
FBaseCVar * cvar ;
if ( cvar_p = = nullptr | | ( cvar = * cvar_p ) = = nullptr | | ( cvar - > GetFlags ( ) & CVAR_IGNORE ) )
{
return nullptr ;
}
return cvar ;
}
2020-04-11 16:22:21 +00:00
static ticcmd_t emptycmd ;
ticcmd_t * G_BaseTiccmd ( )
{
return & emptycmd ;
}
2020-04-11 11:48:55 +00:00
2016-03-01 15:47:10 +00:00
//
// 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 strafe ;
int speed ;
int forward ;
int side ;
int fly ;
ticcmd_t * base ;
2020-04-11 16:22:21 +00:00
base = G_BaseTiccmd ( ) ;
2016-03-01 15:47:10 +00:00
* cmd = * base ;
cmd - > consistancy = consistancy [ consoleplayer ] [ ( maketic / ticdup ) % BACKUPTICS ] ;
2020-04-11 16:09:51 +00:00
strafe = buttonMap . ButtonDown ( Button_Strafe ) ;
speed = buttonMap . ButtonDown ( Button_Speed ) ^ ( int ) cl_run ;
2016-03-01 15:47:10 +00:00
forward = side = fly = 0 ;
// [RH] only use two stage accelerative turning on the keyboard
// and not the joystick, since we treat the joystick as
// the analog device it is.
2020-04-11 16:09:51 +00:00
if ( buttonMap . ButtonDown ( Button_Left ) | | buttonMap . ButtonDown ( Button_Right ) )
2016-03-01 15:47:10 +00:00
turnheld + = ticdup ;
else
turnheld = 0 ;
// let movement keys cancel each other out
if ( strafe )
{
2020-04-11 16:09:51 +00:00
if ( buttonMap . ButtonDown ( Button_Right ) )
2016-03-01 15:47:10 +00:00
side + = sidemove [ speed ] ;
2020-04-11 16:09:51 +00:00
if ( buttonMap . ButtonDown ( Button_Left ) )
2016-03-01 15:47:10 +00:00
side - = sidemove [ speed ] ;
}
else
{
int tspeed = speed ;
if ( turnheld < SLOWTURNTICS )
tspeed + = 2 ; // slow turn
2020-04-11 16:09:51 +00:00
if ( buttonMap . ButtonDown ( Button_Right ) )
2016-03-01 15:47:10 +00:00
{
2019-08-18 16:45:18 +00:00
G_AddViewAngle ( * angleturn [ tspeed ] ) ;
2016-03-01 15:47:10 +00:00
}
2020-04-11 16:09:51 +00:00
if ( buttonMap . ButtonDown ( Button_Left ) )
2016-03-01 15:47:10 +00:00
{
2019-08-18 16:45:18 +00:00
G_AddViewAngle ( - * angleturn [ tspeed ] ) ;
2016-03-01 15:47:10 +00:00
}
}
2020-04-11 16:09:51 +00:00
if ( buttonMap . ButtonDown ( Button_LookUp ) )
2016-03-01 15:47:10 +00:00
{
G_AddViewPitch ( lookspeed [ speed ] ) ;
}
2020-04-11 16:09:51 +00:00
if ( buttonMap . ButtonDown ( Button_LookDown ) )
2016-03-01 15:47:10 +00:00
{
G_AddViewPitch ( - lookspeed [ speed ] ) ;
}
2020-04-11 16:09:51 +00:00
if ( buttonMap . ButtonDown ( Button_MoveUp ) )
2016-03-01 15:47:10 +00:00
fly + = flyspeed [ speed ] ;
2020-04-11 16:09:51 +00:00
if ( buttonMap . ButtonDown ( Button_MoveDown ) )
2016-03-01 15:47:10 +00:00
fly - = flyspeed [ speed ] ;
2020-04-11 16:09:51 +00:00
if ( buttonMap . ButtonDown ( Button_Klook ) )
2016-03-01 15:47:10 +00:00
{
2020-04-11 16:09:51 +00:00
if ( buttonMap . ButtonDown ( Button_Forward ) )
2016-03-01 15:47:10 +00:00
G_AddViewPitch ( lookspeed [ speed ] ) ;
2020-04-11 16:09:51 +00:00
if ( buttonMap . ButtonDown ( Button_Back ) )
2016-03-01 15:47:10 +00:00
G_AddViewPitch ( - lookspeed [ speed ] ) ;
}
else
{
2020-04-11 16:09:51 +00:00
if ( buttonMap . ButtonDown ( Button_Forward ) )
2016-03-01 15:47:10 +00:00
forward + = forwardmove [ speed ] ;
2020-04-11 16:09:51 +00:00
if ( buttonMap . ButtonDown ( Button_Back ) )
2016-03-01 15:47:10 +00:00
forward - = forwardmove [ speed ] ;
}
2020-04-11 16:09:51 +00:00
if ( buttonMap . ButtonDown ( Button_MoveRight ) )
2016-03-01 15:47:10 +00:00
side + = sidemove [ speed ] ;
2020-04-11 16:09:51 +00:00
if ( buttonMap . ButtonDown ( Button_MoveLeft ) )
2016-03-01 15:47:10 +00:00
side - = sidemove [ speed ] ;
// buttons
2020-04-11 16:09:51 +00:00
if ( buttonMap . ButtonDown ( Button_Attack ) ) cmd - > ucmd . buttons | = BT_ATTACK ;
if ( buttonMap . ButtonDown ( Button_AltAttack ) ) cmd - > ucmd . buttons | = BT_ALTATTACK ;
if ( buttonMap . ButtonDown ( Button_Use ) ) cmd - > ucmd . buttons | = BT_USE ;
if ( buttonMap . ButtonDown ( Button_Jump ) ) cmd - > ucmd . buttons | = BT_JUMP ;
if ( buttonMap . ButtonDown ( Button_Crouch ) ) cmd - > ucmd . buttons | = BT_CROUCH ;
if ( buttonMap . ButtonDown ( Button_Zoom ) ) cmd - > ucmd . buttons | = BT_ZOOM ;
if ( buttonMap . ButtonDown ( Button_Reload ) ) cmd - > ucmd . buttons | = BT_RELOAD ;
if ( buttonMap . ButtonDown ( Button_User1 ) ) cmd - > ucmd . buttons | = BT_USER1 ;
if ( buttonMap . ButtonDown ( Button_User2 ) ) cmd - > ucmd . buttons | = BT_USER2 ;
if ( buttonMap . ButtonDown ( Button_User3 ) ) cmd - > ucmd . buttons | = BT_USER3 ;
if ( buttonMap . ButtonDown ( Button_User4 ) ) cmd - > ucmd . buttons | = BT_USER4 ;
if ( buttonMap . ButtonDown ( Button_Speed ) ) cmd - > ucmd . buttons | = BT_SPEED ;
if ( buttonMap . ButtonDown ( Button_Strafe ) ) cmd - > ucmd . buttons | = BT_STRAFE ;
if ( buttonMap . ButtonDown ( Button_MoveRight ) ) cmd - > ucmd . buttons | = BT_MOVERIGHT ;
if ( buttonMap . ButtonDown ( Button_MoveLeft ) ) cmd - > ucmd . buttons | = BT_MOVELEFT ;
if ( buttonMap . ButtonDown ( Button_LookDown ) ) cmd - > ucmd . buttons | = BT_LOOKDOWN ;
if ( buttonMap . ButtonDown ( Button_LookUp ) ) cmd - > ucmd . buttons | = BT_LOOKUP ;
if ( buttonMap . ButtonDown ( Button_Back ) ) cmd - > ucmd . buttons | = BT_BACK ;
if ( buttonMap . ButtonDown ( Button_Forward ) ) cmd - > ucmd . buttons | = BT_FORWARD ;
if ( buttonMap . ButtonDown ( Button_Right ) ) cmd - > ucmd . buttons | = BT_RIGHT ;
if ( buttonMap . ButtonDown ( Button_Left ) ) cmd - > ucmd . buttons | = BT_LEFT ;
if ( buttonMap . ButtonDown ( Button_MoveDown ) ) cmd - > ucmd . buttons | = BT_MOVEDOWN ;
if ( buttonMap . ButtonDown ( Button_MoveUp ) ) cmd - > ucmd . buttons | = BT_MOVEUP ;
if ( buttonMap . ButtonDown ( Button_ShowScores ) ) cmd - > ucmd . buttons | = BT_SHOWSCORES ;
2020-09-26 20:26:26 +00:00
if ( speed ) cmd - > ucmd . buttons | = BT_RUN ;
2016-03-01 15:47:10 +00:00
// Handle joysticks/game controllers.
float joyaxes [ NUM_JOYAXIS ] ;
I_GetAxes ( joyaxes ) ;
// Remap some axes depending on button state.
2020-04-11 16:09:51 +00:00
if ( buttonMap . ButtonDown ( Button_Strafe ) | | ( buttonMap . ButtonDown ( Button_Mlook ) & & lookstrafe ) )
2016-03-01 15:47:10 +00:00
{
joyaxes [ JOYAXIS_Side ] = joyaxes [ JOYAXIS_Yaw ] ;
joyaxes [ JOYAXIS_Yaw ] = 0 ;
}
2020-04-11 16:09:51 +00:00
if ( buttonMap . ButtonDown ( Button_Mlook ) )
2016-03-01 15:47:10 +00:00
{
joyaxes [ JOYAXIS_Pitch ] = joyaxes [ JOYAXIS_Forward ] ;
joyaxes [ JOYAXIS_Forward ] = 0 ;
}
if ( joyaxes [ JOYAXIS_Pitch ] ! = 0 )
{
G_AddViewPitch ( joyint ( joyaxes [ JOYAXIS_Pitch ] * 2048 ) ) ;
}
if ( joyaxes [ JOYAXIS_Yaw ] ! = 0 )
{
G_AddViewAngle ( joyint ( - 1280 * joyaxes [ JOYAXIS_Yaw ] ) ) ;
}
side - = joyint ( sidemove [ speed ] * joyaxes [ JOYAXIS_Side ] ) ;
forward + = joyint ( joyaxes [ JOYAXIS_Forward ] * forwardmove [ speed ] ) ;
fly + = joyint ( joyaxes [ JOYAXIS_Up ] * 2048 ) ;
// Handle mice.
2020-04-11 16:09:51 +00:00
if ( ! buttonMap . ButtonDown ( Button_Mlook ) & & ! freelook )
2016-03-01 15:47:10 +00:00
{
2020-09-28 20:12:44 +00:00
forward + = xs_CRoundToInt ( mousey * m_forward ) ;
2016-03-01 15:47:10 +00:00
}
cmd - > ucmd . pitch = LocalViewPitch > > 16 ;
if ( SendLand )
{
SendLand = false ;
fly = - 32768 ;
}
if ( strafe | | lookstrafe )
2020-09-28 20:12:44 +00:00
side + = xs_CRoundToInt ( mousex * m_side ) ;
2016-03-01 15:47:10 +00:00
mousex = mousey = 0 ;
// Build command.
if ( forward > MAXPLMOVE )
forward = MAXPLMOVE ;
else if ( forward < - MAXPLMOVE )
forward = - MAXPLMOVE ;
if ( side > MAXPLMOVE )
side = MAXPLMOVE ;
else if ( side < - MAXPLMOVE )
side = - MAXPLMOVE ;
cmd - > ucmd . forwardmove + = forward ;
cmd - > ucmd . sidemove + = side ;
cmd - > ucmd . yaw = LocalViewAngle > > 16 ;
cmd - > ucmd . upmove = fly ;
LocalViewAngle = 0 ;
LocalViewPitch = 0 ;
// special buttons
if ( sendturn180 )
{
sendturn180 = false ;
cmd - > ucmd . buttons | = BT_TURN180 ;
}
if ( sendpause )
{
sendpause = false ;
Net_WriteByte ( DEM_PAUSE ) ;
}
if ( sendsave )
{
sendsave = false ;
Net_WriteByte ( DEM_SAVEGAME ) ;
Net_WriteString ( savegamefile ) ;
Net_WriteString ( savedescription ) ;
savegamefile = " " ;
}
2018-12-04 16:00:48 +00:00
if ( SendItemUse = = ( const AActor * ) 1 )
2016-03-01 15:47:10 +00:00
{
Net_WriteByte ( DEM_INVUSEALL ) ;
SendItemUse = NULL ;
}
else if ( SendItemUse ! = NULL )
{
Net_WriteByte ( DEM_INVUSE ) ;
Net_WriteLong ( SendItemUse - > InventoryID ) ;
SendItemUse = NULL ;
}
if ( SendItemDrop ! = NULL )
{
Net_WriteByte ( DEM_INVDROP ) ;
Net_WriteLong ( SendItemDrop - > InventoryID ) ;
2017-02-23 19:18:02 +00:00
Net_WriteLong ( SendItemDropAmount ) ;
2016-03-01 15:47:10 +00:00
SendItemDrop = NULL ;
}
cmd - > ucmd . forwardmove < < = 8 ;
cmd - > ucmd . sidemove < < = 8 ;
}
2018-11-25 00:26:19 +00:00
static int LookAdjust ( int look )
{
look < < = 16 ;
if ( players [ consoleplayer ] . playerstate ! = PST_DEAD & & // No adjustment while dead.
players [ consoleplayer ] . ReadyWeapon ! = NULL ) // No adjustment if no weapon.
{
2019-03-07 13:27:10 +00:00
auto FOVScale = players [ consoleplayer ] . ReadyWeapon - > FloatVar ( NAME_FOVScale ) ;
auto LookScale = players [ consoleplayer ] . ReadyWeapon - > FloatVar ( NAME_LookScale ) ;
if ( FOVScale > 0 ) // No adjustment if it is non-positive.
2018-11-25 00:26:19 +00:00
{
2019-03-07 13:27:10 +00:00
look = int ( look * FOVScale ) ;
}
if ( LookScale > 0 ) // No adjustment if it is non-positive.
{
look = int ( look * LookScale ) ;
2018-11-25 00:26:19 +00:00
}
}
return look ;
}
2017-10-11 15:22:22 +00:00
void G_AddViewPitch ( int look , bool mouse )
2016-03-01 15:47:10 +00:00
{
if ( gamestate = = GS_TITLELEVEL )
{
return ;
}
2018-11-25 00:26:19 +00:00
look = LookAdjust ( look ) ;
2019-02-01 23:24:43 +00:00
if ( ! primaryLevel - > IsFreelookAllowed ( ) )
2016-03-01 15:47:10 +00:00
{
LocalViewPitch = 0 ;
}
else if ( look > 0 )
{
// Avoid overflowing
if ( LocalViewPitch > INT_MAX - look )
{
LocalViewPitch = 0x78000000 ;
}
else
{
2021-10-30 08:16:52 +00:00
LocalViewPitch = min ( LocalViewPitch + look , 0x78000000 ) ;
2016-03-01 15:47:10 +00:00
}
}
else if ( look < 0 )
{
// Avoid overflowing
if ( LocalViewPitch < INT_MIN - look )
{
LocalViewPitch = - 0x78000000 ;
}
else
{
2021-10-30 08:16:52 +00:00
LocalViewPitch = max ( LocalViewPitch + look , - 0x78000000 ) ;
2016-03-01 15:47:10 +00:00
}
}
if ( look ! = 0 )
{
2020-06-10 10:54:17 +00:00
LocalKeyboardTurner = ! mouse ;
2016-03-01 15:47:10 +00:00
}
}
2017-10-11 15:22:22 +00:00
void G_AddViewAngle ( int yaw , bool mouse )
2016-03-01 15:47:10 +00:00
{
if ( gamestate = = GS_TITLELEVEL )
{
return ;
2018-11-25 00:26:19 +00:00
2016-03-01 15:47:10 +00:00
}
2018-11-25 00:26:19 +00:00
yaw = LookAdjust ( yaw ) ;
2016-03-01 15:47:10 +00:00
LocalViewAngle - = yaw ;
if ( yaw ! = 0 )
{
2020-06-10 10:54:17 +00:00
LocalKeyboardTurner = ! mouse ;
2016-03-01 15:47:10 +00:00
}
}
CVAR ( Bool , bot_allowspy , false , 0 )
enum {
SPY_CANCEL = 0 ,
SPY_NEXT ,
SPY_PREV ,
} ;
// [RH] Spy mode has been separated into two console commands.
// One goes forward; the other goes backward.
static void ChangeSpy ( int changespy )
{
// If you're not in a level, then you can't spy.
if ( gamestate ! = GS_LEVEL )
{
return ;
}
// If not viewing through a player, return your eyes to your own head.
if ( players [ consoleplayer ] . camera - > player = = NULL )
{
// When watching demos, you will just have to wait until your player
// has done this for you, since it could desync otherwise.
if ( ! demoplayback )
{
Net_WriteByte ( DEM_REVERTCAMERA ) ;
}
return ;
}
// We may not be allowed to spy on anyone.
if ( dmflags2 & DF2_DISALLOW_SPYING )
return ;
// Otherwise, cycle to the next player.
bool checkTeam = ! demoplayback & & deathmatch ;
int pnum = consoleplayer ;
if ( changespy ! = SPY_CANCEL )
{
player_t * player = players [ consoleplayer ] . camera - > player ;
// only use the camera as starting index if it's a valid player.
if ( player ! = NULL ) pnum = int ( players [ consoleplayer ] . camera - > player - players ) ;
int step = ( changespy = = SPY_NEXT ) ? 1 : - 1 ;
do
{
pnum + = step ;
pnum & = MAXPLAYERS - 1 ;
if ( playeringame [ pnum ] & &
( ! checkTeam | | players [ pnum ] . mo - > IsTeammate ( players [ consoleplayer ] . mo ) | |
( bot_allowspy & & players [ pnum ] . Bot ! = NULL ) ) )
{
break ;
}
} while ( pnum ! = consoleplayer ) ;
}
players [ consoleplayer ] . camera = players [ pnum ] . mo ;
S_UpdateSounds ( players [ consoleplayer ] . camera ) ;
2017-03-22 16:29:13 +00:00
StatusBar - > AttachToPlayer ( & players [ pnum ] ) ;
2016-03-01 15:47:10 +00:00
if ( demoplayback | | multiplayer )
{
StatusBar - > ShowPlayerName ( ) ;
}
}
CCMD ( spynext )
{
// allow spy mode changes even during the demo
ChangeSpy ( SPY_NEXT ) ;
}
CCMD ( spyprev )
{
// allow spy mode changes even during the demo
ChangeSpy ( SPY_PREV ) ;
}
CCMD ( spycancel )
{
// allow spy mode changes even during the demo
ChangeSpy ( SPY_CANCEL ) ;
}
//
// G_Responder
// Get info needed to make ticcmd_ts for the players.
//
bool G_Responder ( event_t * ev )
{
2020-06-13 18:31:57 +00:00
// check events
if ( ev - > type ! = EV_Mouse & & primaryLevel - > localEventManager - > Responder ( ev ) ) // [ZZ] ZScript ate the event // update 07.03.17: mouse events are handled directly
return true ;
2022-04-22 11:39:44 +00:00
if ( gamestate = = GS_INTRO | | gamestate = = GS_CUTSCENE )
{
return ScreenJobResponder ( ev ) ;
}
2016-03-01 15:47:10 +00:00
// any other key pops up menu if in demos
// [RH] But only if the key isn't bound to a "special" command
if ( gameaction = = ga_nothing & &
( demoplayback | | gamestate = = GS_DEMOSCREEN | | gamestate = = GS_TITLELEVEL ) )
{
const char * cmd = Bindings . GetBind ( ev - > data1 ) ;
if ( ev - > type = = EV_KeyDown )
{
if ( ! cmd | | (
strnicmp ( cmd , " menu_ " , 5 ) & &
stricmp ( cmd , " toggleconsole " ) & &
stricmp ( cmd , " sizeup " ) & &
stricmp ( cmd , " sizedown " ) & &
stricmp ( cmd , " togglemap " ) & &
stricmp ( cmd , " spynext " ) & &
stricmp ( cmd , " spyprev " ) & &
stricmp ( cmd , " chase " ) & &
stricmp ( cmd , " +showscores " ) & &
stricmp ( cmd , " bumpgamma " ) & &
stricmp ( cmd , " screenshot " ) ) )
{
M_StartControlPanel ( true ) ;
M_SetMenu ( NAME_Mainmenu , - 1 ) ;
return true ;
}
else
{
return C_DoKey ( ev , & Bindings , & DoubleBindings ) ;
}
}
if ( cmd & & cmd [ 0 ] = = ' + ' )
return C_DoKey ( ev , & Bindings , & DoubleBindings ) ;
return false ;
}
if ( CT_Responder ( ev ) )
return true ; // chat ate the event
if ( gamestate = = GS_LEVEL )
{
if ( ST_Responder ( ev ) )
return true ; // status window ate it
2021-08-06 12:02:00 +00:00
if ( ! viewactive & & primaryLevel - > automap & & primaryLevel - > automap - > Responder ( ev , false ) )
2016-03-01 15:47:10 +00:00
return true ; // automap ate it
}
switch ( ev - > type )
{
case EV_KeyDown :
if ( C_DoKey ( ev , & Bindings , & DoubleBindings ) )
return true ;
break ;
case EV_KeyUp :
C_DoKey ( ev , & Bindings , & DoubleBindings ) ;
break ;
// [RH] mouse buttons are sent as key up/down events
case EV_Mouse :
2020-09-28 19:13:34 +00:00
mousex = ev - > x ;
mousey = ev - > y ;
2016-03-01 15:47:10 +00:00
break ;
}
// [RH] If the view is active, give the automap a chance at
// the events *last* so that any bound keys get precedence.
2019-05-11 08:28:06 +00:00
if ( gamestate = = GS_LEVEL & & viewactive & & primaryLevel - > automap )
2019-02-01 23:24:43 +00:00
return primaryLevel - > automap - > Responder ( ev , true ) ;
2016-03-01 15:47:10 +00:00
return ( ev - > type = = EV_KeyDown | |
ev - > type = = EV_Mouse ) ;
}
2020-10-25 08:16:37 +00:00
static void G_FullConsole ( )
{
int oldgs = gamestate ;
if ( hud_toggled )
D_ToggleHud ( ) ;
if ( demoplayback )
G_CheckDemoStatus ( ) ;
D_QuitNetGame ( ) ;
advancedemo = false ;
C_FullConsole ( ) ;
if ( oldgs ! = GS_STARTUP )
{
primaryLevel - > Music = " " ;
S_Start ( ) ;
S_StopMusic ( true ) ;
2022-10-20 14:48:18 +00:00
P_FreeLevelData ( false ) ;
2020-10-25 08:16:37 +00:00
}
}
2020-04-11 17:33:06 +00:00
//==========================================================================
//
// FRandom :: StaticSumSeeds
//
// This function produces a uint32_t that can be used to check the consistancy
// of network games between different machines. Only a select few RNGs are
// used for the sum, because not all RNGs are important to network sync.
//
//==========================================================================
extern FRandom pr_spawnmobj ;
extern FRandom pr_acs ;
extern FRandom pr_chase ;
extern FRandom pr_damagemobj ;
2016-03-01 15:47:10 +00:00
2020-04-11 17:33:06 +00:00
static uint32_t StaticSumSeeds ( )
{
return
pr_spawnmobj . Seed ( ) +
pr_acs . Seed ( ) +
pr_chase . Seed ( ) +
pr_damagemobj . Seed ( ) ;
}
2016-03-01 15:47:10 +00:00
//
// G_Ticker
// Make ticcmd_ts for the players.
//
void G_Ticker ( )
{
int i ;
gamestate_t oldgamestate ;
// do player reborns if needed
for ( i = 0 ; i < MAXPLAYERS ; i + + )
{
if ( playeringame [ i ] )
{
if ( players [ i ] . playerstate = = PST_GONE )
{
G_DoPlayerPop ( i ) ;
}
if ( players [ i ] . playerstate = = PST_REBORN | | players [ i ] . playerstate = = PST_ENTER )
{
2019-02-01 23:24:43 +00:00
primaryLevel - > DoReborn ( i , false ) ;
2016-03-01 15:47:10 +00:00
}
}
}
if ( ToggleFullscreen )
{
ToggleFullscreen = false ;
2020-05-26 07:02:16 +00:00
AddCommandString ( " toggle vid_fullscreen " ) ;
2016-03-01 15:47:10 +00:00
}
// do things to change the game state
oldgamestate = gamestate ;
while ( gameaction ! = ga_nothing )
{
if ( gameaction = = ga_newgame2 )
{
gameaction = ga_newgame ;
break ;
}
switch ( gameaction )
{
case ga_recordgame :
G_CheckDemoStatus ( ) ;
G_RecordDemo ( newdemoname ) ;
G_BeginRecording ( newdemomap ) ;
2020-10-31 11:22:13 +00:00
[[fallthrough]] ;
2016-03-01 15:47:10 +00:00
case ga_newgame2 : // Silence GCC (see above)
case ga_newgame :
G_DoNewGame ( ) ;
break ;
case ga_loadgame :
case ga_loadgamehidecon :
case ga_autoloadgame :
G_DoLoadGame ( ) ;
break ;
case ga_savegame :
2019-07-13 20:38:44 +00:00
G_DoSaveGame ( true , false , savegamefile , savedescription ) ;
2016-03-01 15:47:10 +00:00
gameaction = ga_nothing ;
savegamefile = " " ;
2017-02-18 15:40:32 +00:00
savedescription = " " ;
2016-03-01 15:47:10 +00:00
break ;
case ga_autosave :
G_DoAutoSave ( ) ;
gameaction = ga_nothing ;
break ;
case ga_loadgameplaydemo :
G_DoLoadGame ( ) ;
// fallthrough
2021-05-17 09:41:43 +00:00
case ga_playdemo :
2016-03-01 15:47:10 +00:00
G_DoPlayDemo ( ) ;
break ;
case ga_completed :
G_DoCompleted ( ) ;
break ;
case ga_worlddone :
G_DoWorldDone ( ) ;
break ;
case ga_screenshot :
M_ScreenShot ( shotfile ) ;
shotfile = " " ;
gameaction = ga_nothing ;
break ;
case ga_fullconsole :
2020-10-25 08:16:37 +00:00
G_FullConsole ( ) ;
2016-03-01 15:47:10 +00:00
gameaction = ga_nothing ;
break ;
case ga_togglemap :
AM_ToggleMap ( ) ;
gameaction = ga_nothing ;
break ;
2019-04-04 22:59:32 +00:00
case ga_resumeconversation :
P_ResumeConversation ( ) ;
gameaction = ga_nothing ;
break ;
2022-04-11 11:12:37 +00:00
case ga_intermission :
gamestate = GS_CUTSCENE ;
2022-04-22 10:43:08 +00:00
gameaction = ga_nothing ;
2022-04-11 11:12:37 +00:00
break ;
2022-06-02 22:03:57 +00:00
case ga_titleloop :
D_StartTitle ( ) ;
break ;
2022-06-04 19:31:19 +00:00
case ga_intro :
gamestate = GS_INTRO ;
gameaction = ga_nothing ;
break ;
2022-04-11 11:12:37 +00:00
2019-01-29 01:16:36 +00:00
default :
2016-03-01 15:47:10 +00:00
case ga_nothing :
break ;
}
C_AdjustBottom ( ) ;
}
// get commands, check consistancy, and build new consistancy check
int buf = ( gametic / ticdup ) % BACKUPTICS ;
// [RH] Include some random seeds and player stuff in the consistancy
// check, not just the player's x position like BOOM.
2020-04-11 17:33:06 +00:00
uint32_t rngsum = StaticSumSeeds ( ) ;
2016-03-01 15:47:10 +00:00
//Added by MC: For some of that bot stuff. The main bot function.
2019-02-01 23:24:43 +00:00
primaryLevel - > BotInfo . Main ( primaryLevel ) ;
2016-03-01 15:47:10 +00:00
for ( i = 0 ; i < MAXPLAYERS ; i + + )
{
if ( playeringame [ i ] )
{
ticcmd_t * cmd = & players [ i ] . cmd ;
ticcmd_t * newcmd = & netcmds [ i ] [ buf ] ;
if ( ( gametic % ticdup ) = = 0 )
{
RunNetSpecs ( i , buf ) ;
}
if ( demorecording )
{
G_WriteDemoTiccmd ( newcmd , i , buf ) ;
}
players [ i ] . oldbuttons = cmd - > ucmd . buttons ;
// If the user alt-tabbed away, paused gets set to -1. In this case,
// we do not want to read more demo commands until paused is no
// longer negative.
if ( demoplayback )
{
G_ReadDemoTiccmd ( cmd , i ) ;
}
else
{
memcpy ( cmd , newcmd , sizeof ( ticcmd_t ) ) ;
}
// check for turbo cheats
2017-07-21 21:21:58 +00:00
if ( multiplayer & & turbo > 100.f & & cmd - > ucmd . forwardmove > TURBOTHRESHOLD & &
2016-03-01 15:47:10 +00:00
! ( gametic & 31 ) & & ( ( gametic > > 5 ) & ( MAXPLAYERS - 1 ) ) = = i )
{
Printf ( " %s is turbo! \n " , players [ i ] . userinfo . GetName ( ) ) ;
}
if ( netgame & & players [ i ] . Bot = = NULL & & ! demoplayback & & ( gametic % ticdup ) = = 0 )
{
//players[i].inconsistant = 0;
if ( gametic > BACKUPTICS * ticdup & & consistancy [ i ] [ buf ] ! = cmd - > consistancy )
{
players [ i ] . inconsistant = gametic - BACKUPTICS * ticdup ;
}
if ( players [ i ] . mo )
{
2017-03-08 17:47:52 +00:00
uint32_t sum = rngsum + int ( ( players [ i ] . mo - > X ( ) + players [ i ] . mo - > Y ( ) + players [ i ] . mo - > Z ( ) ) * 257 ) + players [ i ] . mo - > Angles . Yaw . BAMs ( ) + players [ i ] . mo - > Angles . Pitch . BAMs ( ) ;
2016-03-01 15:47:10 +00:00
sum ^ = players [ i ] . health ;
consistancy [ i ] [ buf ] = sum ;
}
else
{
consistancy [ i ] [ buf ] = rngsum ;
}
}
}
}
2017-03-09 12:49:18 +00:00
// [ZZ] also tick the UI part of the events
2019-02-02 15:43:11 +00:00
primaryLevel - > localEventManager - > UiTick ( ) ;
2019-01-27 09:23:47 +00:00
C_RunDelayedCommands ( ) ;
2017-03-09 12:49:18 +00:00
2016-03-01 15:47:10 +00:00
// do main actions
switch ( gamestate )
{
case GS_LEVEL :
P_Ticker ( ) ;
2019-02-01 23:24:43 +00:00
primaryLevel - > automap - > Ticker ( ) ;
2016-03-01 15:47:10 +00:00
break ;
case GS_TITLELEVEL :
P_Ticker ( ) ;
break ;
case GS_DEMOSCREEN :
D_PageTicker ( ) ;
break ;
case GS_STARTUP :
if ( gameaction = = ga_nothing )
{
gamestate = GS_FULLCONSOLE ;
gameaction = ga_fullconsole ;
}
break ;
2022-04-22 11:39:44 +00:00
case GS_CUTSCENE :
case GS_INTRO :
if ( ScreenJobTick ( ) )
{
// synchronize termination with the playsim.
Net_WriteByte ( DEM_ENDSCREENJOB ) ;
}
break ;
2016-03-01 15:47:10 +00:00
default :
break ;
}
2018-03-09 01:06:41 +00:00
// [MK] Additional ticker for UI events right after all others
2019-02-02 15:43:11 +00:00
primaryLevel - > localEventManager - > PostUiTick ( ) ;
2016-03-01 15:47:10 +00:00
}
//
// PLAYER STRUCTURE FUNCTIONS
// also see P_SpawnPlayer in P_Mobj
//
//
// G_PlayerFinishLevel
// Called when a player completes a level.
//
// flags is checked for RESETINVENTORY and RESETHEALTH only.
void G_PlayerFinishLevel ( int player , EFinishLevelType mode , int flags )
{
2018-12-02 15:26:02 +00:00
IFVM ( PlayerPawn , PlayerFinishLevel )
2016-03-01 15:47:10 +00:00
{
2018-12-02 15:26:02 +00:00
VMValue params [ ] = { players [ player ] . mo , mode , flags } ;
VMCall ( func , params , 3 , nullptr , 0 ) ;
2016-03-01 15:47:10 +00:00
}
}
//
// G_PlayerReborn
// Called after a player dies
// almost everything is cleared and initialized
//
2019-01-28 01:44:05 +00:00
void FLevelLocals : : PlayerReborn ( int player )
2016-03-01 15:47:10 +00:00
{
player_t * p ;
int frags [ MAXPLAYERS ] ;
int fragcount ; // [RH] Cumulative frags
int killcount ;
int itemcount ;
int secretcount ;
int chasecam ;
2017-03-08 17:47:52 +00:00
uint8_t currclass ;
2016-03-01 15:47:10 +00:00
userinfo_t userinfo ; // [RH] Save userinfo
2019-01-03 21:05:49 +00:00
AActor * actor ;
2017-02-08 18:42:24 +00:00
PClassActor * cls ;
2016-03-01 15:47:10 +00:00
FString log ;
DBot * Bot ; //Added by MC:
p = & players [ player ] ;
memcpy ( frags , p - > frags , sizeof ( frags ) ) ;
fragcount = p - > fragcount ;
killcount = p - > killcount ;
itemcount = p - > itemcount ;
secretcount = p - > secretcount ;
currclass = p - > CurrentPlayerClass ;
userinfo . TransferFrom ( p - > userinfo ) ;
actor = p - > mo ;
cls = p - > cls ;
log = p - > LogText ;
chasecam = p - > cheats & CF_CHASECAM ;
Bot = p - > Bot ; //Added by MC:
2018-12-10 08:36:40 +00:00
const bool settings_controller = p - > settings_controller ;
2016-03-01 15:47:10 +00:00
// Reset player structure to its defaults
p - > ~ player_t ( ) ;
: : new ( p ) player_t ;
memcpy ( p - > frags , frags , sizeof ( p - > frags ) ) ;
p - > health = actor - > health ;
p - > fragcount = fragcount ;
p - > killcount = killcount ;
p - > itemcount = itemcount ;
p - > secretcount = secretcount ;
p - > CurrentPlayerClass = currclass ;
p - > userinfo . TransferFrom ( userinfo ) ;
p - > mo = actor ;
p - > cls = cls ;
p - > LogText = log ;
p - > cheats | = chasecam ;
p - > Bot = Bot ; //Added by MC:
2018-12-10 08:36:40 +00:00
p - > settings_controller = settings_controller ;
2016-03-01 15:47:10 +00:00
p - > oldbuttons = ~ 0 , p - > attackdown = true ; p - > usedown = true ; // don't do anything immediately
p - > original_oldbuttons = ~ 0 ;
p - > playerstate = PST_LIVE ;
if ( gamestate ! = GS_TITLELEVEL )
{
// [GRB] Give inventory specified in DECORATE
2019-01-03 12:58:53 +00:00
2019-01-03 21:05:49 +00:00
IFVIRTUALPTRNAME ( actor , NAME_PlayerPawn , GiveDefaultInventory )
2019-01-03 12:58:53 +00:00
{
VMValue params [ 1 ] = { actor } ;
VMCall ( func , params , 1 , nullptr , 0 ) ;
}
2016-03-01 15:47:10 +00:00
p - > ReadyWeapon = p - > PendingWeapon ;
}
//Added by MC: Init bot structure.
if ( p - > Bot ! = NULL )
{
botskill_t skill = p - > Bot - > skill ;
p - > Bot - > Clear ( ) ;
p - > Bot - > player = p ;
p - > Bot - > skill = skill ;
}
}
//
// G_CheckSpot
// Returns false if the player cannot be respawned
// at the given mapthing spot
// because something is occupying it
//
2019-01-28 01:44:05 +00:00
bool FLevelLocals : : CheckSpot ( int playernum , FPlayerStart * mthing )
2016-03-01 15:47:10 +00:00
{
2016-03-26 08:28:00 +00:00
DVector3 spot ;
double oldz ;
2016-03-01 15:47:10 +00:00
int i ;
if ( mthing - > type = = 0 ) return false ;
2016-03-26 08:28:00 +00:00
spot = mthing - > pos ;
2016-03-01 15:47:10 +00:00
2019-01-28 01:44:05 +00:00
if ( ! ( flags & LEVEL_USEPLAYERSTARTZ ) )
2016-03-01 15:47:10 +00:00
{
2016-03-26 08:28:00 +00:00
spot . Z = 0 ;
2016-03-01 15:47:10 +00:00
}
2019-01-29 00:30:41 +00:00
spot . Z + = PointInSector ( spot ) - > floorplane . ZatPoint ( spot ) ;
2016-03-01 15:47:10 +00:00
if ( ! players [ playernum ] . mo )
{ // first spawn of level, before corpses
for ( i = 0 ; i < playernum ; i + + )
2016-03-26 08:28:00 +00:00
if ( players [ i ] . mo & & players [ i ] . mo - > X ( ) = = spot . X & & players [ i ] . mo - > Y ( ) = = spot . Y )
2016-03-01 15:47:10 +00:00
return false ;
return true ;
}
2016-03-26 08:28:00 +00:00
oldz = players [ playernum ] . mo - > Z ( ) ; // [RH] Need to save corpse's z-height
players [ playernum ] . mo - > SetZ ( spot . Z ) ; // [RH] Checks are now full 3-D
2016-03-01 15:47:10 +00:00
// 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 ;
2016-03-26 08:28:00 +00:00
i = P_CheckPosition ( players [ playernum ] . mo , spot ) ;
2016-03-01 15:47:10 +00:00
players [ playernum ] . mo - > flags & = ~ MF_SOLID ;
2016-03-26 08:28:00 +00:00
players [ playernum ] . mo - > SetZ ( oldz ) ; // [RH] Restore corpse's height
2016-03-01 15:47:10 +00:00
if ( ! i )
return false ;
return true ;
}
//
// G_DeathMatchSpawnPlayer
// Spawns a player at one of the random death match spots
// called at level load and each death
//
// [RH] Returns the distance of the closest player to the given mapthing
2019-01-28 01:44:05 +00:00
double FLevelLocals : : PlayersRangeFromSpot ( FPlayerStart * spot )
2016-03-01 15:47:10 +00:00
{
2016-03-21 23:06:58 +00:00
double closest = INT_MAX ;
double distance ;
2016-03-01 15:47:10 +00:00
int i ;
for ( i = 0 ; i < MAXPLAYERS ; i + + )
{
if ( ! playeringame [ i ] | | ! players [ i ] . mo | | players [ i ] . health < = 0 )
continue ;
2016-03-21 23:06:58 +00:00
distance = players [ i ] . mo - > Distance2D ( spot - > pos . X , spot - > pos . Y ) ;
2016-03-01 15:47:10 +00:00
if ( distance < closest )
closest = distance ;
}
return closest ;
}
// [RH] Select the deathmatch spawn spot farthest from everyone.
2019-01-28 01:44:05 +00:00
FPlayerStart * FLevelLocals : : SelectFarthestDeathmatchSpot ( size_t selections )
2016-03-01 15:47:10 +00:00
{
2016-03-21 23:06:58 +00:00
double bestdistance = 0 ;
2016-03-01 15:47:10 +00:00
FPlayerStart * bestspot = NULL ;
unsigned int i ;
for ( i = 0 ; i < selections ; i + + )
{
2019-01-28 01:44:05 +00:00
double distance = PlayersRangeFromSpot ( & deathmatchstarts [ i ] ) ;
2016-03-01 15:47:10 +00:00
if ( distance > bestdistance )
{
bestdistance = distance ;
2019-01-29 19:15:06 +00:00
bestspot = & deathmatchstarts [ i ] ;
2016-03-01 15:47:10 +00:00
}
}
return bestspot ;
}
// [RH] Select a deathmatch spawn spot at random (original mechanism)
2019-01-28 01:44:05 +00:00
FPlayerStart * FLevelLocals : : SelectRandomDeathmatchSpot ( int playernum , unsigned int selections )
2016-03-01 15:47:10 +00:00
{
unsigned int i , j ;
for ( j = 0 ; j < 20 ; j + + )
{
i = pr_dmspawn ( ) % selections ;
2019-01-29 19:15:06 +00:00
if ( CheckSpot ( playernum , & deathmatchstarts [ i ] ) )
2016-03-01 15:47:10 +00:00
{
2019-01-28 01:44:05 +00:00
return & deathmatchstarts [ i ] ;
2016-03-01 15:47:10 +00:00
}
}
// [RH] return a spot anyway, since we allow telefragging when a player spawns
2019-01-28 01:44:05 +00:00
return & deathmatchstarts [ i ] ;
2016-03-01 15:47:10 +00:00
}
2019-01-28 01:44:05 +00:00
DEFINE_ACTION_FUNCTION ( FLevelLocals , PickDeathmatchStart )
2016-11-25 18:52:35 +00:00
{
2019-01-28 01:44:05 +00:00
PARAM_SELF_STRUCT_PROLOGUE ( FLevelLocals ) ;
unsigned int selections = self - > deathmatchstarts . Size ( ) ;
2016-11-28 00:30:36 +00:00
DVector3 pos ;
int angle ;
if ( selections = = 0 )
{
angle = INT_MAX ;
pos = DVector3 ( 0 , 0 , 0 ) ;
}
else
{
unsigned int i = pr_dmspawn ( ) % selections ;
2019-01-28 01:44:05 +00:00
angle = self - > deathmatchstarts [ i ] . angle ;
pos = self - > deathmatchstarts [ i ] . pos ;
2016-11-28 00:30:36 +00:00
}
2016-11-25 18:52:35 +00:00
if ( numret > 1 )
{
2016-11-28 00:30:36 +00:00
ret [ 1 ] . SetInt ( angle ) ;
2016-11-25 18:52:35 +00:00
numret = 2 ;
}
if ( numret > 0 )
{
2016-11-28 00:30:36 +00:00
ret [ 0 ] . SetVector ( pos ) ;
2016-11-25 18:52:35 +00:00
}
return numret ;
}
2019-01-28 01:44:05 +00:00
void FLevelLocals : : DeathMatchSpawnPlayer ( int playernum )
2016-03-01 15:47:10 +00:00
{
unsigned int selections ;
FPlayerStart * spot ;
2019-01-28 01:44:05 +00:00
selections = deathmatchstarts . Size ( ) ;
2016-03-01 15:47:10 +00:00
// [RH] We can get by with just 1 deathmatch start
if ( selections < 1 )
I_Error ( " No deathmatch starts " ) ;
// At level start, none of the players have mobjs attached to them,
// so we always use the random deathmatch spawn. During the game,
// though, we use whatever dmflags specifies.
if ( ( dmflags & DF_SPAWN_FARTHEST ) & & players [ playernum ] . mo )
spot = SelectFarthestDeathmatchSpot ( selections ) ;
else
spot = SelectRandomDeathmatchSpot ( playernum , selections ) ;
if ( spot = = NULL )
{ // No good spot, so the player will probably get stuck.
// We were probably using select farthest above, and all
// the spots were taken.
2019-01-28 01:44:05 +00:00
spot = PickPlayerStart ( playernum , PPS_FORCERANDOM ) ;
if ( ! CheckSpot ( playernum , spot ) )
2016-03-01 15:47:10 +00:00
{ // This map doesn't have enough coop spots for this player
// to use one.
spot = SelectRandomDeathmatchSpot ( playernum , selections ) ;
if ( spot = = NULL )
{ // We have a player 1 start, right?
2019-01-28 01:44:05 +00:00
spot = & playerstarts [ 0 ] ;
2016-03-01 15:47:10 +00:00
if ( spot - > type = = 0 )
{ // Fine, whatever.
2019-01-28 01:44:05 +00:00
spot = & deathmatchstarts [ 0 ] ;
2016-03-01 15:47:10 +00:00
}
}
}
}
2019-01-28 02:02:25 +00:00
AActor * mo = SpawnPlayer ( spot , playernum ) ;
2016-03-01 15:47:10 +00:00
if ( mo ! = NULL ) P_PlayerStartStomp ( mo ) ;
}
2016-11-25 18:52:35 +00:00
2016-03-01 15:47:10 +00:00
//
// G_PickPlayerStart
//
2019-01-28 01:44:05 +00:00
FPlayerStart * FLevelLocals : : PickPlayerStart ( int playernum , int flags )
2016-03-01 15:47:10 +00:00
{
2019-01-28 01:44:05 +00:00
if ( AllPlayerStarts . Size ( ) = = 0 ) // No starts to pick
2016-03-01 15:47:10 +00:00
{
return NULL ;
}
2019-01-28 01:44:05 +00:00
if ( ( flags2 & LEVEL2_RANDOMPLAYERSTARTS ) | | ( flags & PPS_FORCERANDOM ) | |
playerstarts [ playernum ] . type = = 0 )
2016-03-01 15:47:10 +00:00
{
if ( ! ( flags & PPS_NOBLOCKINGCHECK ) )
{
TArray < FPlayerStart * > good_starts ;
unsigned int i ;
// Find all unblocked player starts.
2019-01-29 19:15:06 +00:00
for ( i = 0 ; i < AllPlayerStarts . Size ( ) ; + + i )
2016-03-01 15:47:10 +00:00
{
2019-01-28 01:44:05 +00:00
if ( CheckSpot ( playernum , & AllPlayerStarts [ i ] ) )
2016-03-01 15:47:10 +00:00
{
2019-01-28 01:44:05 +00:00
good_starts . Push ( & AllPlayerStarts [ i ] ) ;
2016-03-01 15:47:10 +00:00
}
}
if ( good_starts . Size ( ) > 0 )
{ // Pick an open spot at random.
return good_starts [ pr_pspawn ( good_starts . Size ( ) ) ] ;
}
}
// Pick a spot at random, whether it's open or not.
2019-01-29 19:15:06 +00:00
return & AllPlayerStarts [ pr_pspawn ( AllPlayerStarts . Size ( ) ) ] ;
2016-03-01 15:47:10 +00:00
}
2019-01-28 01:44:05 +00:00
return & playerstarts [ playernum ] ;
2016-03-01 15:47:10 +00:00
}
2019-01-28 01:44:05 +00:00
DEFINE_ACTION_FUNCTION ( FLevelLocals , PickPlayerStart )
2016-11-25 18:52:35 +00:00
{
2019-01-28 01:44:05 +00:00
PARAM_SELF_STRUCT_PROLOGUE ( FLevelLocals ) ;
2016-11-25 18:52:35 +00:00
PARAM_INT ( playernum ) ;
2018-11-17 09:03:40 +00:00
PARAM_INT ( flags ) ;
2019-01-28 01:44:05 +00:00
auto ps = self - > PickPlayerStart ( playernum , flags ) ;
2016-11-25 18:52:35 +00:00
if ( numret > 1 )
{
ret [ 1 ] . SetInt ( ps ? ps - > angle : 0 ) ;
numret = 2 ;
}
if ( numret > 0 )
{
ret [ 0 ] . SetVector ( ps ? ps - > pos : DVector3 ( 0 , 0 , 0 ) ) ;
}
return numret ;
}
2016-03-01 15:47:10 +00:00
//
// G_QueueBody
//
2019-01-28 01:44:05 +00:00
void FLevelLocals : : QueueBody ( AActor * body )
2016-03-01 15:47:10 +00:00
{
// flush an old corpse if needed
2019-01-29 19:15:06 +00:00
int modslot = bodyqueslot % BODYQUESIZE ;
2019-01-28 01:44:05 +00:00
bodyqueslot = modslot + 1 ;
2016-03-01 15:47:10 +00:00
2019-01-28 01:44:05 +00:00
if ( bodyqueslot > = BODYQUESIZE & & bodyque [ modslot ] ! = NULL )
2016-03-01 15:47:10 +00:00
{
2019-01-28 01:44:05 +00:00
bodyque [ modslot ] - > Destroy ( ) ;
2016-03-01 15:47:10 +00:00
}
2019-01-28 01:44:05 +00:00
bodyque [ modslot ] = body ;
2016-03-01 15:47:10 +00:00
// Copy the player's translation, so that if they change their color later, only
// their current body will change and not all their old corpses.
if ( GetTranslationType ( body - > Translation ) = = TRANSLATION_Players | |
GetTranslationType ( body - > Translation ) = = TRANSLATION_PlayersExtra )
{
2019-01-28 01:44:05 +00:00
// This needs to be able to handle multiple levels, in case a level with dead players is used as a secondary one later.
2020-04-11 16:19:05 +00:00
GPalette . CopyTranslation ( TRANSLATION ( TRANSLATION_PlayerCorpses , modslot ) , body - > Translation ) ;
2020-04-11 10:41:09 +00:00
body - > Translation = TRANSLATION ( TRANSLATION_PlayerCorpses , modslot ) ;
2016-03-01 15:47:10 +00:00
}
const int skinidx = body - > player - > userinfo . GetSkin ( ) ;
if ( 0 ! = skinidx & & ! ( body - > flags4 & MF4_NOSKIN ) )
{
// Apply skin's scale to actor's scale, it will be lost otherwise
const AActor * const defaultActor = body - > GetDefault ( ) ;
2017-02-17 20:51:23 +00:00
const FPlayerSkin & skin = Skins [ skinidx ] ;
2016-03-01 15:47:10 +00:00
2016-03-20 11:13:00 +00:00
body - > Scale . X * = skin . Scale . X / defaultActor - > Scale . X ;
body - > Scale . Y * = skin . Scale . Y / defaultActor - > Scale . Y ;
2016-03-01 15:47:10 +00:00
}
}
//
// G_DoReborn
//
2016-10-23 10:06:59 +00:00
EXTERN_CVAR ( Bool , sv_singleplayerrespawn )
2019-01-28 01:44:05 +00:00
void FLevelLocals : : DoReborn ( int playernum , bool freshbot )
2016-03-01 15:47:10 +00:00
{
2019-01-28 01:44:05 +00:00
if ( ! multiplayer & & ! ( flags2 & LEVEL2_ALLOWRESPAWN ) & & ! sv_singleplayerrespawn & &
2017-12-28 05:53:30 +00:00
! G_SkillProperty ( SKILLP_PlayerRespawn ) )
2016-03-01 15:47:10 +00:00
{
if ( BackupSaveName . Len ( ) > 0 & & FileExists ( BackupSaveName . GetChars ( ) ) )
{ // Load game from the last point it was saved
savename = BackupSaveName ;
gameaction = ga_autoloadgame ;
}
else
{ // Reload the level from scratch
bool indemo = demoplayback ;
BackupSaveName = " " ;
2019-01-29 19:15:06 +00:00
G_InitNew ( MapName , false ) ;
2016-03-01 15:47:10 +00:00
demoplayback = indemo ;
}
}
else
{
2020-05-02 13:20:37 +00:00
bool isUnfriendly ;
PlayerSpawnPickClass ( playernum ) ;
// this condition should never be false
assert ( players [ playernum ] . cls ! = NULL ) ;
if ( players [ playernum ] . cls ! = NULL )
{
isUnfriendly = ! ( GetDefaultByType ( players [ playernum ] . cls ) - > flags & MF_FRIENDLY ) ;
DPrintf ( DMSG_NOTIFY , " Player class IS defined: unfriendly is %i \n " , isUnfriendly ) ;
}
else
{
// we shouldn't be here, but if we are, get the player's current status
isUnfriendly = players [ playernum ] . mo & & ! ( players [ playernum ] . mo - > flags & MF_FRIENDLY ) ;
DPrintf ( DMSG_NOTIFY , " Player class NOT defined: unfriendly is %i \n " , isUnfriendly ) ;
}
2017-03-14 08:02:40 +00:00
2016-03-01 15:47:10 +00:00
// respawn at the start
// first disassociate the corpse
if ( players [ playernum ] . mo )
{
2019-01-28 01:44:05 +00:00
QueueBody ( players [ playernum ] . mo ) ;
2016-03-01 15:47:10 +00:00
players [ playernum ] . mo - > player = NULL ;
}
// spawn at random spot if in deathmatch
2019-01-28 01:44:05 +00:00
if ( ( deathmatch | | isUnfriendly ) & & ( deathmatchstarts . Size ( ) > 0 ) )
2016-03-01 15:47:10 +00:00
{
2019-01-28 01:44:05 +00:00
DeathMatchSpawnPlayer ( playernum ) ;
2016-03-01 15:47:10 +00:00
return ;
}
2019-01-28 01:44:05 +00:00
if ( ! ( flags2 & LEVEL2_RANDOMPLAYERSTARTS ) & &
playerstarts [ playernum ] . type ! = 0 & &
CheckSpot ( playernum , & playerstarts [ playernum ] ) )
2016-03-01 15:47:10 +00:00
{
2019-01-28 02:02:25 +00:00
AActor * mo = SpawnPlayer ( & playerstarts [ playernum ] , playernum ) ;
2016-03-01 15:47:10 +00:00
if ( mo ! = NULL ) P_PlayerStartStomp ( mo , true ) ;
}
else
{ // try to spawn at any random player's spot
2019-01-28 01:44:05 +00:00
FPlayerStart * start = PickPlayerStart ( playernum , PPS_FORCERANDOM ) ;
2019-01-28 02:02:25 +00:00
AActor * mo = SpawnPlayer ( start , playernum ) ;
2016-03-01 15:47:10 +00:00
if ( mo ! = NULL ) P_PlayerStartStomp ( mo , true ) ;
}
}
}
//
// G_DoReborn
//
void G_DoPlayerPop ( int playernum )
{
playeringame [ playernum ] = false ;
2019-03-18 23:37:43 +00:00
FString message = GStrings ( deathmatch ? " TXT_LEFTWITHFRAGS " : " TXT_LEFTTHEGAME " ) ;
message . Substitute ( " %s " , players [ playernum ] . userinfo . GetName ( ) ) ;
message . Substitute ( " %d " , FStringf ( " %d " , players [ playernum ] . fragcount ) ) ;
2021-02-15 09:18:06 +00:00
Printf ( " %s \n " , message . GetChars ( ) ) ;
2016-03-01 15:47:10 +00:00
// [RH] Revert each player to their own view if spying through the player who left
for ( int ii = 0 ; ii < MAXPLAYERS ; + + ii )
{
if ( playeringame [ ii ] & & players [ ii ] . camera = = players [ playernum ] . mo )
{
players [ ii ] . camera = players [ ii ] . mo ;
if ( ii = = consoleplayer & & StatusBar ! = NULL )
{
2017-03-22 16:29:13 +00:00
StatusBar - > AttachToPlayer ( & players [ ii ] ) ;
2016-03-01 15:47:10 +00:00
}
}
}
// [RH] Make the player disappear
2019-01-29 23:47:34 +00:00
auto mo = players [ playernum ] . mo ;
mo - > Level - > Behaviors . StopMyScripts ( mo ) ;
2017-02-02 18:46:10 +00:00
// [ZZ] fire player disconnect hook
2019-02-02 15:43:11 +00:00
mo - > Level - > localEventManager - > PlayerDisconnected ( playernum ) ;
2016-03-01 15:47:10 +00:00
// [RH] Let the scripts know the player left
2019-01-29 23:47:34 +00:00
mo - > Level - > Behaviors . StartTypedScripts ( SCRIPT_Disconnect , mo , true , playernum , true ) ;
if ( mo ! = NULL )
2016-03-01 15:47:10 +00:00
{
2019-01-29 23:47:34 +00:00
P_DisconnectEffect ( mo ) ;
mo - > player = NULL ;
mo - > Destroy ( ) ;
2016-03-01 15:47:10 +00:00
if ( ! ( players [ playernum ] . mo - > ObjectFlags & OF_EuthanizeMe ) )
{ // We just destroyed a morphed player, so now the original player
// has taken their place. Destroy that one too.
players [ playernum ] . mo - > Destroy ( ) ;
}
2019-01-29 23:47:34 +00:00
players [ playernum ] . mo = nullptr ;
2019-01-07 08:14:52 +00:00
players [ playernum ] . camera = nullptr ;
2016-03-01 15:47:10 +00:00
}
2016-05-09 18:03:47 +00:00
players [ playernum ] . DestroyPSprites ( ) ;
2016-03-01 15:47:10 +00:00
}
2019-03-03 05:01:00 +00:00
void G_ScreenShot ( const char * filename )
2016-03-01 15:47:10 +00:00
{
2020-08-25 15:54:57 +00:00
if ( gameaction = = ga_nothing )
{
shotfile = filename ;
gameaction = ga_screenshot ;
}
2016-03-01 15:47:10 +00:00
}
//
// G_InitFromSavegame
// Can be called by the startup code or the menu task.
//
void G_LoadGame ( const char * name , bool hidecon )
{
if ( name ! = NULL )
{
savename = name ;
gameaction = ! hidecon ? ga_loadgame : ga_loadgamehidecon ;
}
}
2016-09-21 19:57:24 +00:00
static bool CheckSingleWad ( const char * name , bool & printRequires , bool printwarn )
2016-03-01 15:47:10 +00:00
{
if ( name = = NULL )
{
return true ;
}
2020-04-11 11:26:42 +00:00
if ( fileSystem . CheckIfResourceFileLoaded ( name ) < 0 )
2016-03-01 15:47:10 +00:00
{
if ( printwarn )
{
if ( ! printRequires )
{
2019-03-18 23:37:43 +00:00
Printf ( " %s: \n %s " , GStrings ( " TXT_SAVEGAMENEEDS " ) , name ) ;
2016-03-01 15:47:10 +00:00
}
else
{
Printf ( " , %s " , name ) ;
}
}
printRequires = true ;
return false ;
}
return true ;
}
// Return false if not all the needed wads have been loaded.
2016-09-21 19:57:24 +00:00
bool G_CheckSaveGameWads ( FSerializer & arc , bool printwarn )
2016-03-01 15:47:10 +00:00
{
bool printRequires = false ;
2016-09-21 19:57:24 +00:00
FString text ;
2016-03-01 15:47:10 +00:00
2016-09-21 19:57:24 +00:00
arc ( " Game WAD " , text ) ;
2016-03-01 15:47:10 +00:00
CheckSingleWad ( text , printRequires , printwarn ) ;
2016-09-21 19:57:24 +00:00
arc ( " Map WAD " , text ) ;
2016-03-01 15:47:10 +00:00
CheckSingleWad ( text , printRequires , printwarn ) ;
if ( printRequires )
{
if ( printwarn )
{
Printf ( " \n " ) ;
}
return false ;
}
return true ;
}
2019-03-18 23:37:43 +00:00
static void LoadGameError ( const char * label , const char * append = " " )
{
FString message = GStrings ( label ) ;
message . Substitute ( " %s " , savename ) ;
Printf ( " %s %s \n " , message . GetChars ( ) , append ) ;
}
2016-03-01 15:47:10 +00:00
2020-04-11 11:48:55 +00:00
void C_SerializeCVars ( FSerializer & arc , const char * label , uint32_t filter )
{
FString dump ;
if ( arc . BeginObject ( label ) )
{
if ( arc . isWriting ( ) )
{
2022-10-21 16:28:28 +00:00
decltype ( cvarMap ) : : Iterator it ( cvarMap ) ;
decltype ( cvarMap ) : : Pair * pair ;
while ( it . NextPair ( pair ) )
2020-04-11 11:48:55 +00:00
{
2022-10-21 16:28:28 +00:00
auto cvar = pair - > Value ;
2020-04-11 11:48:55 +00:00
if ( ( cvar - > Flags & filter ) & & ! ( cvar - > Flags & ( CVAR_NOSAVE | CVAR_IGNORE | CVAR_CONFIG_ONLY ) ) )
{
UCVarValue val = cvar - > GetGenericRep ( CVAR_String ) ;
char * c = const_cast < char * > ( val . String ) ;
arc ( cvar - > GetName ( ) , c ) ;
}
}
}
else
{
2022-10-21 16:28:28 +00:00
decltype ( cvarMap ) : : Iterator it ( cvarMap ) ;
decltype ( cvarMap ) : : Pair * pair ;
while ( it . NextPair ( pair ) )
2020-04-11 11:48:55 +00:00
{
2022-10-21 16:28:28 +00:00
auto cvar = pair - > Value ;
2020-04-11 11:48:55 +00:00
if ( ( cvar - > Flags & filter ) & & ! ( cvar - > Flags & ( CVAR_NOSAVE | CVAR_IGNORE | CVAR_CONFIG_ONLY ) ) )
{
UCVarValue val ;
char * c = nullptr ;
arc ( cvar - > GetName ( ) , c ) ;
if ( c ! = nullptr )
{
val . String = c ;
cvar - > SetGenericRep ( val , CVAR_String ) ;
delete [ ] c ;
}
}
}
}
arc . EndObject ( ) ;
}
}
2016-03-01 15:47:10 +00:00
void G_DoLoadGame ( )
{
bool hidecon ;
if ( gameaction ! = ga_autoloadgame )
{
demoplayback = false ;
}
hidecon = gameaction = = ga_loadgamehidecon ;
gameaction = ga_nothing ;
2023-08-17 19:28:35 +00:00
std : : unique_ptr < FResourceFile > resfile ( FResourceFile : : OpenResourceFile ( savename . GetChars ( ) , true ) ) ;
2016-09-21 22:18:31 +00:00
if ( resfile = = nullptr )
2016-03-01 15:47:10 +00:00
{
2019-03-18 23:37:43 +00:00
LoadGameError ( " TXT_COULDNOTREAD " ) ;
2016-03-01 15:47:10 +00:00
return ;
}
2023-08-22 19:20:28 +00:00
auto info = resfile - > FindLump ( " info.json " ) ;
2017-01-22 19:04:38 +00:00
if ( info = = nullptr )
2016-03-01 15:47:10 +00:00
{
2019-03-18 23:37:43 +00:00
LoadGameError ( " TXT_NOINFOJSON " ) ;
2017-01-22 19:04:38 +00:00
return ;
}
2016-03-01 15:47:10 +00:00
2017-01-22 19:04:38 +00:00
SaveVersion = 0 ;
2016-03-01 15:47:10 +00:00
2020-04-11 11:19:59 +00:00
void * data = info - > Lock ( ) ;
2020-04-11 17:27:11 +00:00
FSerializer arc ;
2017-01-22 19:04:38 +00:00
if ( ! arc . OpenReader ( ( const char * ) data , info - > LumpSize ) )
{
2019-03-18 23:37:43 +00:00
LoadGameError ( " TXT_FAILEDTOREADSG " ) ;
2017-01-22 19:04:38 +00:00
return ;
}
2016-09-21 22:18:31 +00:00
2017-01-22 19:04:38 +00:00
// Check whether this savegame actually has been created by a compatible engine.
// Since there are ZDoom derivates using the exact same savegame format but
// with mutual incompatibilities this check simplifies things significantly.
FString savever , engine , map ;
arc ( " Save Version " , SaveVersion ) ;
arc ( " Engine " , engine ) ;
arc ( " Current Map " , map ) ;
2016-09-21 22:18:31 +00:00
2017-01-22 19:04:38 +00:00
if ( engine . CompareNoCase ( GAMESIG ) ! = 0 )
{
// Make a special case for the message printed for old savegames that don't
// have this information.
if ( engine . IsEmpty ( ) )
2016-03-01 15:47:10 +00:00
{
2019-03-18 23:37:43 +00:00
LoadGameError ( " TXT_INCOMPATIBLESG " ) ;
2016-03-01 15:47:10 +00:00
}
2017-01-22 19:04:38 +00:00
else
2016-03-01 15:47:10 +00:00
{
2023-02-19 11:47:16 +00:00
LoadGameError ( " TXT_OTHERENGINESG " , engine . GetChars ( ) ) ;
2016-03-01 15:47:10 +00:00
}
2017-01-22 19:04:38 +00:00
return ;
}
2016-03-01 15:47:10 +00:00
2017-01-22 19:04:38 +00:00
if ( SaveVersion < MINSAVEVER | | SaveVersion > SAVEVER )
{
2019-03-18 23:37:43 +00:00
FString message ;
2017-01-22 19:04:38 +00:00
if ( SaveVersion < MINSAVEVER )
2016-03-01 15:47:10 +00:00
{
2019-03-18 23:37:43 +00:00
message = GStrings ( " TXT_TOOOLDSG " ) ;
message . Substitute ( " %e " , FStringf ( " %d " , MINSAVEVER ) ) ;
2016-03-01 15:47:10 +00:00
}
2017-01-22 19:04:38 +00:00
else
2016-09-21 22:18:31 +00:00
{
2019-03-18 23:37:43 +00:00
message = GStrings ( " TXT_TOONEWSG " ) ;
message . Substitute ( " %e " , FStringf ( " %d " , SAVEVER ) ) ;
2016-09-21 22:18:31 +00:00
}
2019-03-18 23:37:43 +00:00
message . Substitute ( " %d " , FStringf ( " %d " , SaveVersion ) ) ;
LoadGameError ( message ) ;
2017-01-22 19:04:38 +00:00
return ;
}
2016-03-01 15:47:10 +00:00
2017-01-22 19:04:38 +00:00
if ( ! G_CheckSaveGameWads ( arc , true ) )
{
return ;
}
2016-09-21 22:18:31 +00:00
2017-01-22 19:04:38 +00:00
if ( map . IsEmpty ( ) )
{
2019-03-18 23:37:43 +00:00
LoadGameError ( " TXT_NOMAPSG " ) ;
2017-01-22 19:04:38 +00:00
return ;
}
2016-09-21 22:18:31 +00:00
2017-01-22 19:04:38 +00:00
// Now that it looks like we can load this save, hide the fullscreen console if it was up
// when the game was selected from the menu.
if ( hidecon & & gamestate = = GS_FULLCONSOLE )
{
gamestate = GS_HIDECONSOLE ;
}
// we are done with info.json.
arc . Close ( ) ;
2016-03-01 15:47:10 +00:00
2017-01-22 19:04:38 +00:00
info = resfile - > FindLump ( " globals.json " ) ;
if ( info = = nullptr )
{
2019-03-18 23:37:43 +00:00
LoadGameError ( " TXT_NOGLOBALSJSON " ) ;
2017-01-22 19:04:38 +00:00
return ;
}
2016-03-01 15:47:10 +00:00
2020-04-11 11:19:59 +00:00
data = info - > Lock ( ) ;
2017-01-22 19:04:38 +00:00
if ( ! arc . OpenReader ( ( const char * ) data , info - > LumpSize ) )
{
2019-03-18 23:37:43 +00:00
LoadGameError ( " TXT_SGINFOERR " ) ;
2017-01-22 19:04:38 +00:00
return ;
}
2016-03-01 15:47:10 +00:00
2017-01-22 19:04:38 +00:00
// Read intermission data for hubs
G_SerializeHub ( arc ) ;
2016-09-21 22:18:31 +00:00
2019-02-01 23:24:43 +00:00
primaryLevel - > BotInfo . RemoveAllBots ( primaryLevel , true ) ;
2016-03-01 15:47:10 +00:00
2020-10-12 07:08:59 +00:00
savegamerestore = true ; // Use the player actors in the savegame
2017-01-22 19:04:38 +00:00
FString cvar ;
arc ( " importantcvars " , cvar ) ;
if ( ! cvar . IsEmpty ( ) )
{
2017-03-08 17:47:52 +00:00
uint8_t * vars_p = ( uint8_t * ) cvar . GetChars ( ) ;
2017-01-22 19:04:38 +00:00
C_ReadCVars ( & vars_p ) ;
}
2020-01-12 11:59:08 +00:00
else
{
C_SerializeCVars ( arc , " servercvars " , CVAR_SERVERINFO ) ;
}
2016-03-01 15:47:10 +00:00
2017-03-08 17:47:52 +00:00
uint32_t time [ 2 ] = { 1 , 0 } ;
2016-03-01 15:47:10 +00:00
2017-01-22 19:04:38 +00:00
arc ( " ticrate " , time [ 0 ] )
2019-01-30 00:38:18 +00:00
( " leveltime " , time [ 1 ] )
( " globalfreeze " , globalfreeze ) ;
2017-01-22 19:04:38 +00:00
// dearchive all the modifications
level . time = Scale ( time [ 1 ] , TICRATE , time [ 0 ] ) ;
2016-03-01 15:47:10 +00:00
2017-01-22 19:04:38 +00:00
G_ReadSnapshots ( resfile . get ( ) ) ;
resfile . reset ( nullptr ) ; // we no longer need the resource file below this point
G_ReadVisited ( arc ) ;
2016-03-01 15:47:10 +00:00
2017-01-22 19:04:38 +00:00
// load a base level
bool demoplaybacksave = demoplayback ;
G_InitNew ( map , false ) ;
demoplayback = demoplaybacksave ;
savegamerestore = false ;
2016-03-01 15:47:10 +00:00
2017-01-22 19:04:38 +00:00
STAT_Serialize ( arc ) ;
FRandom : : StaticReadRNGState ( arc ) ;
P_ReadACSDefereds ( arc ) ;
P_ReadACSVars ( arc ) ;
2016-03-01 15:47:10 +00:00
2017-01-22 19:04:38 +00:00
NextSkill = - 1 ;
arc ( " nextskill " , NextSkill ) ;
2016-03-01 15:47:10 +00:00
2017-01-22 19:04:38 +00:00
if ( level . info ! = nullptr )
level . info - > Snapshot . Clean ( ) ;
BackupSaveName = savename ;
2022-07-01 05:23:55 +00:00
//Push any added models from A_ChangeModel
2022-07-23 08:10:15 +00:00
for ( auto & smf : savedModelFiles )
2022-07-01 05:23:55 +00:00
{
2022-07-23 08:10:15 +00:00
FString modelFilePath = smf . Left ( smf . LastIndexOf ( " / " ) + 1 ) ;
FString modelFileName = smf . Right ( smf . Len ( ) - smf . Left ( smf . LastIndexOf ( " / " ) + 1 ) . Len ( ) ) ;
2022-07-01 05:23:55 +00:00
FindModel ( modelFilePath , modelFileName ) ;
}
2017-01-22 19:04:38 +00:00
// At this point, the GC threshold is likely a lot higher than the
// amount of memory in use, so bring it down now by starting a
// collection.
GC : : StartCollection ( ) ;
2016-03-01 15:47:10 +00:00
}
//
// G_SaveGame
// Called by the menu task.
// Description is a 24 byte text string
//
void G_SaveGame ( const char * filename , const char * description )
{
if ( sendsave | | gameaction = = ga_savegame )
{
2019-03-18 23:37:43 +00:00
Printf ( " %s \n " , GStrings ( " TXT_SAVEPENDING " ) ) ;
2016-03-01 15:47:10 +00:00
}
else if ( ! usergame )
{
2019-03-18 23:37:43 +00:00
Printf ( " %s \n " , GStrings ( " TXT_NOTSAVEABLE " ) ) ;
2016-03-01 15:47:10 +00:00
}
else if ( gamestate ! = GS_LEVEL )
{
2019-03-18 23:37:43 +00:00
Printf ( " %s \n " , GStrings ( " TXT_NOTINLEVEL " ) ) ;
2016-03-01 15:47:10 +00:00
}
else if ( players [ consoleplayer ] . health < = 0 & & ! multiplayer )
{
2019-03-18 23:37:43 +00:00
Printf ( " %s \n " , GStrings ( " TXT_SPPLAYERDEAD " ) ) ;
2016-03-01 15:47:10 +00:00
}
else
{
savegamefile = filename ;
2017-02-18 15:40:32 +00:00
savedescription = description ;
2016-03-01 15:47:10 +00:00
sendsave = true ;
}
}
2022-08-14 19:03:20 +00:00
CCMD ( opensaves )
{
2022-10-19 23:11:47 +00:00
FString name = G_GetSavegamesFolder ( ) ;
2022-08-14 19:03:20 +00:00
CreatePath ( name ) ;
I_OpenShellFolder ( name ) ;
}
2016-03-01 15:47:10 +00:00
CVAR ( Int , autosavenum , 0 , CVAR_NOSET | CVAR_ARCHIVE | CVAR_GLOBALCONFIG )
static int nextautosave = - 1 ;
CVAR ( Int , disableautosave , 0 , CVAR_ARCHIVE | CVAR_GLOBALCONFIG )
2016-03-03 08:27:37 +00:00
CVAR ( Bool , saveloadconfirmation , true , CVAR_ARCHIVE | CVAR_GLOBALCONFIG ) // [mxd]
2016-03-01 15:47:10 +00:00
CUSTOM_CVAR ( Int , autosavecount , 4 , CVAR_ARCHIVE | CVAR_GLOBALCONFIG )
{
if ( self < 0 )
self = 0 ;
}
2019-07-13 20:38:44 +00:00
CVAR ( Int , quicksavenum , - 1 , CVAR_NOSET | CVAR_ARCHIVE | CVAR_GLOBALCONFIG )
static int lastquicksave = - 1 ;
CVAR ( Bool , quicksaverotation , false , CVAR_ARCHIVE | CVAR_GLOBALCONFIG )
CUSTOM_CVAR ( Int , quicksaverotationcount , 4 , CVAR_ARCHIVE | CVAR_GLOBALCONFIG )
{
if ( self < 1 )
self = 1 ;
}
2016-03-01 15:47:10 +00:00
void G_DoAutoSave ( )
{
2017-02-18 15:40:32 +00:00
FString description ;
2016-03-01 15:47:10 +00:00
FString file ;
// Keep up to four autosaves at a time
UCVarValue num ;
const char * readableTime ;
int count = autosavecount ! = 0 ? autosavecount : 1 ;
if ( nextautosave = = - 1 )
{
nextautosave = ( autosavenum + 1 ) % count ;
}
num . Int = nextautosave ;
2022-10-21 16:28:28 +00:00
autosavenum - > ForceSet ( num , CVAR_Int ) ;
2016-03-01 15:47:10 +00:00
2022-10-19 23:11:47 +00:00
file = G_BuildSaveName ( FStringf ( " auto%02d " , nextautosave ) ) ;
2016-03-01 15:47:10 +00:00
2019-01-29 23:47:34 +00:00
// The hint flag is only relevant on the primary level.
2019-02-01 23:24:43 +00:00
if ( ! ( primaryLevel - > flags2 & LEVEL2_NOAUTOSAVEHINT ) )
2016-03-01 15:47:10 +00:00
{
nextautosave = ( nextautosave + 1 ) % count ;
}
else
{
// This flag can only be used once per level
2019-02-01 23:24:43 +00:00
primaryLevel - > flags2 & = ~ LEVEL2_NOAUTOSAVEHINT ;
2016-03-01 15:47:10 +00:00
}
readableTime = myasctime ( ) ;
2019-03-11 20:21:37 +00:00
description . Format ( " Autosave %s " , readableTime ) ;
2019-07-13 20:38:44 +00:00
G_DoSaveGame ( false , false , file , description ) ;
}
void G_DoQuickSave ( )
{
FString description ;
FString file ;
// Keeps a rotating set of quicksaves
UCVarValue num ;
const char * readableTime ;
int count = quicksaverotationcount ! = 0 ? quicksaverotationcount : 1 ;
if ( quicksavenum < 0 )
{
lastquicksave = 0 ;
}
else
{
lastquicksave = ( quicksavenum + 1 ) % count ;
}
num . Int = lastquicksave ;
2022-10-21 16:28:28 +00:00
quicksavenum - > ForceSet ( num , CVAR_Int ) ;
2019-07-13 20:38:44 +00:00
2022-12-05 01:25:51 +00:00
file = G_BuildSaveName ( FStringf ( " quick%02d " , lastquicksave ) ) ;
2019-07-13 20:38:44 +00:00
readableTime = myasctime ( ) ;
description . Format ( " Quicksave %s " , readableTime ) ;
G_DoSaveGame ( true , true , file , description ) ;
2016-03-01 15:47:10 +00:00
}
2016-09-21 10:19:13 +00:00
static void PutSaveWads ( FSerializer & arc )
2016-03-01 15:47:10 +00:00
{
const char * name ;
// Name of IWAD
2020-04-11 11:26:42 +00:00
name = fileSystem . GetResourceFileName ( fileSystem . GetIwadNum ( ) ) ;
2016-09-21 10:19:13 +00:00
arc . AddString ( " Game WAD " , name ) ;
2016-03-01 15:47:10 +00:00
2019-01-25 18:02:30 +00:00
// Name of wad the map resides in
2020-04-11 11:27:19 +00:00
if ( fileSystem . GetFileContainer ( primaryLevel - > lumpnum ) > fileSystem . GetIwadNum ( ) )
2019-01-25 18:02:30 +00:00
{
2020-04-11 11:27:19 +00:00
name = fileSystem . GetResourceFileName ( fileSystem . GetFileContainer ( primaryLevel - > lumpnum ) ) ;
2016-09-21 10:19:13 +00:00
arc . AddString ( " Map WAD " , name ) ;
2019-01-25 18:02:30 +00:00
}
2016-03-01 15:47:10 +00:00
}
2023-09-20 23:57:21 +00:00
EXTERN_CVAR ( Bool , hud_showmonsters )
EXTERN_CVAR ( Bool , hud_showitems )
EXTERN_CVAR ( Bool , hud_showsecrets )
2016-09-21 10:19:13 +00:00
static void PutSaveComment ( FSerializer & arc )
2016-03-01 15:47:10 +00:00
{
int levelTime ;
2019-03-11 20:21:37 +00:00
FString comment = myasctime ( ) ;
2016-03-01 15:47:10 +00:00
2016-09-21 10:19:13 +00:00
arc . AddString ( " Creation Time " , comment ) ;
2016-03-01 15:47:10 +00:00
2019-01-25 18:02:30 +00:00
// Get level name
2019-02-01 23:24:43 +00:00
comment . Format ( " %s - %s \n " , primaryLevel - > MapName . GetChars ( ) , primaryLevel - > LevelName . GetChars ( ) ) ;
2016-03-01 15:47:10 +00:00
// Append elapsed time
2019-01-21 05:17:11 +00:00
const char * const time = GStrings ( " SAVECOMMENT_TIME " ) ;
2019-02-01 23:24:43 +00:00
levelTime = primaryLevel - > time / TICRATE ;
2023-02-03 13:36:55 +00:00
comment . AppendFormat ( " %s: %02d:%02d:%02d \n " , time , levelTime / 3600 , ( levelTime % 3600 ) / 60 , levelTime % 60 ) ;
2023-09-20 23:57:21 +00:00
// Append kills/items/secrets (only if visible on HUD to avoid spoiling levels)
bool hasStatOnLine = false ;
if ( hud_showmonsters ) {
comment . AppendFormat ( " K: %d/%d " , primaryLevel - > killed_monsters , primaryLevel - > total_monsters ) ;
hasStatOnLine = true ;
}
if ( hud_showitems ) {
if ( hasStatOnLine ) {
comment . AppendFormat ( " - " ) ;
}
comment . AppendFormat ( " I: %d/%d " , primaryLevel - > found_items , primaryLevel - > total_items ) ;
hasStatOnLine = true ;
}
if ( hud_showsecrets ) {
if ( hasStatOnLine ) {
comment . AppendFormat ( " - " ) ;
}
comment . AppendFormat ( " S: %d/%d " , primaryLevel - > found_secrets , primaryLevel - > total_secrets ) ;
hasStatOnLine = true ;
}
if ( hasStatOnLine ) {
comment . AppendFormat ( " \n " ) ;
}
2023-02-03 13:36:55 +00:00
// Append player health and armor
2023-09-16 09:45:00 +00:00
const char * const health = " Health " ; // GStrings("SAVECOMMENT_HEALTH");
const char * const armor = " Armor " ; // GStrings("SAVECOMMENT_ARMOR");
2023-02-03 13:36:55 +00:00
int armorAmount = 0 ;
auto basicArmorItem = primaryLevel - > Players [ consoleplayer ] - > mo - > FindInventory ( NAME_BasicArmor ) ;
if ( basicArmorItem ) {
armorAmount + = basicArmorItem - > IntVar ( NAME_Amount ) ;
}
auto hexenArmorItem = primaryLevel - > Players [ consoleplayer ] - > mo - > FindInventory ( NAME_HexenArmor ) ;
if ( hexenArmorItem ) {
double * Slots = ( double * ) hexenArmorItem - > ScriptVar ( NAME_Slots , nullptr ) ;
armorAmount + = Slots [ 0 ] + Slots [ 1 ] + Slots [ 2 ] + Slots [ 3 ] + Slots [ 4 ] ;
}
comment . AppendFormat ( " %s: %d - %s: %d " , health , primaryLevel - > Players [ consoleplayer ] - > mo - > health , armor , armorAmount ) ;
2016-03-01 15:47:10 +00:00
// Write out the comment
2016-09-21 10:19:13 +00:00
arc . AddString ( " Comment " , comment ) ;
2016-03-01 15:47:10 +00:00
}
2016-09-21 07:01:12 +00:00
static void PutSavePic ( FileWriter * file , int width , int height )
2016-03-01 15:47:10 +00:00
{
if ( width < = 0 | | height < = 0 | | ! storesavepic )
{
M_CreateDummyPNG ( file ) ;
}
else
{
2019-01-29 00:09:02 +00:00
D_Render ( [ & ] ( )
{
2020-04-25 11:18:57 +00:00
WriteSavePic ( & players [ consoleplayer ] , file , width , height ) ;
2019-01-29 00:09:02 +00:00
} , false ) ;
2016-03-01 15:47:10 +00:00
}
}
2019-07-13 20:38:44 +00:00
void G_DoSaveGame ( bool okForQuicksave , bool forceQuicksave , FString filename , const char * description )
2016-03-01 15:47:10 +00:00
{
2016-09-21 10:19:13 +00:00
TArray < FCompressedBuffer > savegame_content ;
2016-09-21 15:37:56 +00:00
TArray < FString > savegame_filenames ;
2016-09-21 10:19:13 +00:00
2016-03-01 15:47:10 +00:00
char buf [ 100 ] ;
// Do not even try, if we're not in a level. (Can happen after
// a demo finishes playback.)
2019-02-01 23:24:43 +00:00
if ( primaryLevel - > lines . Size ( ) = = 0 | | primaryLevel - > sectors . Size ( ) = = 0 | | gamestate ! = GS_LEVEL )
2016-03-01 15:47:10 +00:00
{
return ;
}
if ( demoplayback )
{
2022-10-19 23:11:47 +00:00
filename = G_BuildSaveName ( " demosave " ) ;
2016-03-01 15:47:10 +00:00
}
if ( cl_waitforsave )
I_FreezeTime ( true ) ;
insave = true ;
2017-03-03 18:19:19 +00:00
try
{
2019-01-26 20:23:19 +00:00
level . SnapshotLevel ( ) ;
2017-03-03 18:19:19 +00:00
}
catch ( CRecoverableError & err )
{
// delete the snapshot. Since the save failed it is broken.
insave = false ;
level . info - > Snapshot . Clean ( ) ;
Printf ( PRINT_HIGH , " Save failed \n " ) ;
Printf ( PRINT_HIGH , " %s \n " , err . GetMessage ( ) ) ;
// The time freeze must be reset if the save fails.
if ( cl_waitforsave )
I_FreezeTime ( false ) ;
return ;
}
catch ( . . . )
{
insave = false ;
if ( cl_waitforsave )
I_FreezeTime ( false ) ;
throw ;
}
2016-03-01 15:47:10 +00:00
2016-09-21 10:19:13 +00:00
BufferWriter savepic ;
2020-04-11 17:27:11 +00:00
FSerializer savegameinfo ; // this is for displayable info about the savegame
FSerializer savegameglobals ; // and this for non-level related info that must be saved.
2016-03-01 15:47:10 +00:00
2016-09-21 19:57:24 +00:00
savegameinfo . OpenWriter ( true ) ;
savegameglobals . OpenWriter ( save_formatted ) ;
2016-03-01 15:47:10 +00:00
SaveVersion = SAVEVER ;
2016-09-21 10:19:13 +00:00
PutSavePic ( & savepic , SAVEPICWIDTH , SAVEPICHEIGHT ) ;
2016-03-01 15:47:10 +00:00
mysnprintf ( buf , countof ( buf ) , GAMENAME " %s " , GetVersionString ( ) ) ;
2016-09-21 10:19:13 +00:00
// put some basic info into the PNG so that this isn't lost when the image gets extracted.
M_AppendPNGText ( & savepic , " Software " , buf ) ;
M_AppendPNGText ( & savepic , " Title " , description ) ;
2019-02-01 23:24:43 +00:00
M_AppendPNGText ( & savepic , " Current Map " , primaryLevel - > MapName ) ;
2016-09-21 10:19:13 +00:00
M_FinishPNG ( & savepic ) ;
2016-09-21 15:37:56 +00:00
int ver = SAVEVER ;
2016-09-21 10:19:13 +00:00
savegameinfo . AddString ( " Software " , buf )
. AddString ( " Engine " , GAMESIG )
2016-09-21 15:37:56 +00:00
( " Save Version " , ver )
2016-09-21 10:19:13 +00:00
. AddString ( " Title " , description )
2019-02-01 23:24:43 +00:00
. AddString ( " Current Map " , primaryLevel - > MapName ) ;
2016-09-21 10:19:13 +00:00
PutSaveWads ( savegameinfo ) ;
PutSaveComment ( savegameinfo ) ;
2016-03-01 15:47:10 +00:00
// Intermission stats for hubs
2016-09-21 10:19:13 +00:00
G_SerializeHub ( savegameglobals ) ;
2020-01-12 11:59:08 +00:00
C_SerializeCVars ( savegameglobals , " servercvars " , CVAR_SERVERINFO ) ;
2016-03-01 15:47:10 +00:00
if ( level . time ! = 0 | | level . maptime ! = 0 )
{
2016-09-21 10:19:13 +00:00
int tic = TICRATE ;
savegameglobals ( " ticrate " , tic ) ;
savegameglobals ( " leveltime " , level . time ) ;
2016-03-01 15:47:10 +00:00
}
2016-09-21 10:19:13 +00:00
STAT_Serialize ( savegameglobals ) ;
FRandom : : StaticWriteRNGState ( savegameglobals ) ;
P_WriteACSDefereds ( savegameglobals ) ;
P_WriteACSVars ( savegameglobals ) ;
2016-09-21 15:37:56 +00:00
G_WriteVisited ( savegameglobals ) ;
2016-03-01 15:47:10 +00:00
if ( NextSkill ! = - 1 )
{
2016-09-21 10:19:13 +00:00
savegameglobals ( " nextskill " , NextSkill ) ;
2016-03-01 15:47:10 +00:00
}
2016-09-21 15:37:56 +00:00
auto picdata = savepic . GetBuffer ( ) ;
2023-08-22 19:20:28 +00:00
FCompressedBuffer bufpng = { picdata - > Size ( ) , picdata - > Size ( ) , FileSys : : METHOD_STORED , 0 , static_cast < unsigned int > ( crc32 ( 0 , & ( * picdata ) [ 0 ] , picdata - > Size ( ) ) ) , ( char * ) & ( * picdata ) [ 0 ] } ;
2016-09-21 15:37:56 +00:00
savegame_content . Push ( bufpng ) ;
savegame_filenames . Push ( " savepic.png " ) ;
savegame_content . Push ( savegameinfo . GetCompressedOutput ( ) ) ;
savegame_filenames . Push ( " info.json " ) ;
savegame_content . Push ( savegameglobals . GetCompressedOutput ( ) ) ;
savegame_filenames . Push ( " globals.json " ) ;
G_WriteSnapshots ( savegame_filenames , savegame_content ) ;
2016-09-21 10:19:13 +00:00
2023-08-19 13:30:40 +00:00
for ( unsigned i = 0 ; i < savegame_content . Size ( ) ; i + + )
savegame_content [ i ] . filename = savegame_filenames [ i ] . GetChars ( ) ;
2016-03-01 15:47:10 +00:00
2019-08-29 11:34:02 +00:00
bool succeeded = false ;
2016-09-21 15:37:56 +00:00
2023-08-19 13:30:40 +00:00
if ( WriteZip ( filename , savegame_content . Data ( ) , savegame_content . Size ( ) ) )
2019-08-29 11:34:02 +00:00
{
// Check whether the file is ok by trying to open it.
FResourceFile * test = FResourceFile : : OpenResourceFile ( filename , true ) ;
if ( test ! = nullptr )
{
delete test ;
succeeded = true ;
}
}
2016-03-01 15:47:10 +00:00
2019-08-29 11:34:02 +00:00
if ( succeeded )
{
savegameManager . NotifyNewSave ( filename , description , okForQuicksave , forceQuicksave ) ;
BackupSaveName = filename ;
2016-09-21 15:37:56 +00:00
2019-08-29 11:34:02 +00:00
if ( longsavemessages ) Printf ( " %s (%s) \n " , GStrings ( " GGSAVED " ) , filename . GetChars ( ) ) ;
else Printf ( " %s \n " , GStrings ( " GGSAVED " ) ) ;
}
else
2016-03-01 15:47:10 +00:00
{
2019-08-29 11:34:02 +00:00
Printf ( PRINT_HIGH , " %s \n " , GStrings ( " TXT_SAVEFAILED " ) ) ;
2016-03-01 15:47:10 +00:00
}
2016-09-21 15:37:56 +00:00
2019-08-29 11:34:02 +00:00
// delete the JSON buffers we created just above. Everything else will
// either still be needed or taken care of automatically.
savegame_content [ 1 ] . Clean ( ) ;
savegame_content [ 2 ] . Clean ( ) ;
2016-03-01 15:47:10 +00:00
// We don't need the snapshot any longer.
2016-09-20 23:48:23 +00:00
level . info - > Snapshot . Clean ( ) ;
2016-03-01 15:47:10 +00:00
insave = false ;
2018-03-04 13:11:45 +00:00
if ( cl_waitforsave )
I_FreezeTime ( false ) ;
2016-03-01 15:47:10 +00:00
}
//
// DEMO RECORDING
//
void G_ReadDemoTiccmd ( ticcmd_t * cmd , int player )
{
int id = DEM_BAD ;
while ( id ! = DEM_USERCMD & & id ! = DEM_EMPTYUSERCMD )
{
if ( ! demorecording & & demo_p > = zdembodyend )
{
// nothing left in the BODY chunk, so end playback.
G_CheckDemoStatus ( ) ;
break ;
}
id = ReadByte ( & demo_p ) ;
switch ( id )
{
case DEM_STOP :
// end of demo stream
G_CheckDemoStatus ( ) ;
break ;
case DEM_USERCMD :
UnpackUserCmd ( & cmd - > ucmd , & cmd - > ucmd , & demo_p ) ;
break ;
case DEM_EMPTYUSERCMD :
// leave cmd->ucmd unchanged
break ;
case DEM_DROPPLAYER :
{
2017-03-08 17:47:52 +00:00
uint8_t i = ReadByte ( & demo_p ) ;
2016-03-01 15:47:10 +00:00
if ( i < MAXPLAYERS )
{
playeringame [ i ] = false ;
}
}
break ;
default :
Net_DoCommand ( id , & demo_p , player ) ;
break ;
}
}
}
bool stoprecording ;
CCMD ( stop )
{
stoprecording = true ;
}
2017-03-08 17:47:52 +00:00
extern uint8_t * lenspot ;
2016-03-01 15:47:10 +00:00
void G_WriteDemoTiccmd ( ticcmd_t * cmd , int player , int buf )
{
2017-03-08 17:47:52 +00:00
uint8_t * specdata ;
2016-03-01 15:47:10 +00:00
int speclen ;
if ( stoprecording )
{ // use "stop" console command to end demo recording
G_CheckDemoStatus ( ) ;
if ( ! netgame )
{
gameaction = ga_fullconsole ;
}
return ;
}
// [RH] Write any special "ticcmds" for this player to the demo
if ( ( specdata = NetSpecs [ player ] [ buf ] . GetData ( & speclen ) ) & & gametic % ticdup = = 0 )
{
memcpy ( demo_p , specdata , speclen ) ;
demo_p + = speclen ;
NetSpecs [ player ] [ buf ] . SetData ( NULL , 0 ) ;
}
// [RH] Now write out a "normal" ticcmd.
WriteUserCmdMessage ( & cmd - > ucmd , & players [ player ] . cmd . ucmd , & demo_p ) ;
// [RH] Bigger safety margin
if ( demo_p > demobuffer + maxdemosize - 64 )
{
ptrdiff_t pos = demo_p - demobuffer ;
ptrdiff_t spot = lenspot - demobuffer ;
ptrdiff_t comp = democompspot - demobuffer ;
ptrdiff_t body = demobodyspot - demobuffer ;
// [RH] Allocate more space for the demo
maxdemosize + = 0x20000 ;
2017-03-08 17:47:52 +00:00
demobuffer = ( uint8_t * ) M_Realloc ( demobuffer , maxdemosize ) ;
2016-03-01 15:47:10 +00:00
demo_p = demobuffer + pos ;
lenspot = demobuffer + spot ;
democompspot = demobuffer + comp ;
demobodyspot = demobuffer + body ;
}
}
//
// G_RecordDemo
//
void G_RecordDemo ( const char * name )
{
usergame = false ;
demoname = name ;
FixPathSeperator ( demoname ) ;
DefaultExtension ( demoname , " .lmp " ) ;
maxdemosize = 0x20000 ;
2017-03-08 17:47:52 +00:00
demobuffer = ( uint8_t * ) M_Malloc ( maxdemosize ) ;
2016-03-01 15:47:10 +00:00
demorecording = true ;
}
// [RH] Demos are now saved as IFF FORMs. I've also removed support
// for earlier ZDEMs since I didn't want to bother supporting
// something that probably wasn't used much (if at all).
void G_BeginRecording ( const char * startmap )
{
int i ;
if ( startmap = = NULL )
{
2019-02-01 23:24:43 +00:00
startmap = primaryLevel - > MapName ;
2016-03-01 15:47:10 +00:00
}
demo_p = demobuffer ;
WriteLong ( FORM_ID , & demo_p ) ; // Write FORM ID
demo_p + = 4 ; // Leave space for len
WriteLong ( ZDEM_ID , & demo_p ) ; // Write ZDEM ID
// Write header chunk
StartChunk ( ZDHD_ID , & demo_p ) ;
WriteWord ( DEMOGAMEVERSION , & demo_p ) ; // Write ZDoom version
* demo_p + + = 2 ; // Write minimum version needed to use this demo.
* demo_p + + = 3 ; // (Useful?)
strcpy ( ( char * ) demo_p , startmap ) ; // Write name of map demo was recorded on.
demo_p + = strlen ( startmap ) + 1 ;
WriteLong ( rngseed , & demo_p ) ; // Write RNG seed
* demo_p + + = consoleplayer ;
FinishChunk ( & demo_p ) ;
// Write player info chunks
for ( i = 0 ; i < MAXPLAYERS ; i + + )
{
if ( playeringame [ i ] )
{
2023-03-26 07:27:26 +00:00
StartChunk ( UINF_ID , & demo_p ) ;
WriteByte ( ( uint8_t ) i , & demo_p ) ;
auto str = D_GetUserInfoStrings ( i ) ;
memcpy ( demo_p , str . GetChars ( ) , str . Len ( ) + 1 ) ;
demo_p + = str . Len ( ) ;
FinishChunk ( & demo_p ) ;
2016-03-01 15:47:10 +00:00
}
}
// It is possible to start a "multiplayer" game with only one player,
// so checking the number of players when playing back the demo is not
// enough.
if ( multiplayer )
{
StartChunk ( NETD_ID , & demo_p ) ;
FinishChunk ( & demo_p ) ;
}
// Write cvars chunk
StartChunk ( VARS_ID , & demo_p ) ;
C_WriteCVars ( & demo_p , CVAR_SERVERINFO | CVAR_DEMOSAVE ) ;
FinishChunk ( & demo_p ) ;
// Write weapon ordering chunk
StartChunk ( WEAP_ID , & demo_p ) ;
P_WriteDemoWeaponsChunk ( & demo_p ) ;
FinishChunk ( & demo_p ) ;
// Indicate body is compressed
StartChunk ( COMP_ID , & demo_p ) ;
democompspot = demo_p ;
WriteLong ( 0 , & demo_p ) ;
FinishChunk ( & demo_p ) ;
// Begin BODY chunk
StartChunk ( BODY_ID , & demo_p ) ;
demobodyspot = demo_p ;
}
//
// G_PlayDemo
//
FString defdemoname ;
void G_DeferedPlayDemo ( const char * name )
{
defdemoname = name ;
gameaction = ( gameaction = = ga_loadgame ) ? ga_loadgameplaydemo : ga_playdemo ;
}
2018-01-29 11:30:36 +00:00
UNSAFE_CCMD ( playdemo )
2016-03-01 15:47:10 +00:00
{
if ( netgame )
{
2018-05-11 15:03:57 +00:00
Printf ( " End your current netgame first! \n " ) ;
2016-03-01 15:47:10 +00:00
return ;
}
if ( demorecording )
{
2018-05-11 15:03:57 +00:00
Printf ( " End your current demo first! \n " ) ;
2016-03-01 15:47:10 +00:00
return ;
}
if ( argv . argc ( ) > 1 )
{
G_DeferedPlayDemo ( argv [ 1 ] ) ;
singledemo = true ;
}
}
2018-01-20 08:11:28 +00:00
UNSAFE_CCMD ( timedemo )
2016-03-01 15:47:10 +00:00
{
if ( argv . argc ( ) > 1 )
{
G_TimeDemo ( argv [ 1 ] ) ;
singledemo = true ;
}
}
// [RH] Process all the information in a FORM ZDEM
// until a BODY chunk is entered.
bool G_ProcessIFFDemo ( FString & mapname )
{
bool headerHit = false ;
bool bodyHit = false ;
int numPlayers = 0 ;
int id , len , i ;
uLong uncompSize = 0 ;
2017-03-08 17:47:52 +00:00
uint8_t * nextchunk ;
2016-03-01 15:47:10 +00:00
demoplayback = true ;
for ( i = 0 ; i < MAXPLAYERS ; i + + )
playeringame [ i ] = 0 ;
len = ReadLong ( & demo_p ) ;
zdemformend = demo_p + len + ( len & 1 ) ;
// Check to make sure this is a ZDEM chunk file.
// TODO: Support multiple FORM ZDEMs in a CAT. Might be useful.
id = ReadLong ( & demo_p ) ;
if ( id ! = ZDEM_ID )
{
Printf ( " Not a " GAMENAME " demo file! \n " ) ;
return true ;
}
// Process all chunks until a BODY chunk is encountered.
while ( demo_p < zdemformend & & ! bodyHit )
{
id = ReadLong ( & demo_p ) ;
len = ReadLong ( & demo_p ) ;
nextchunk = demo_p + len + ( len & 1 ) ;
if ( nextchunk > zdemformend )
{
Printf ( " Demo is mangled! \n " ) ;
return true ;
}
switch ( id )
{
case ZDHD_ID :
headerHit = true ;
demover = ReadWord ( & demo_p ) ; // ZDoom version demo was created with
if ( demover < MINDEMOVERSION )
{
Printf ( " Demo requires an older version of " GAMENAME " ! \n " ) ;
//return true;
}
if ( ReadWord ( & demo_p ) > DEMOGAMEVERSION ) // Minimum ZDoom version
{
Printf ( " Demo requires a newer version of " GAMENAME " ! \n " ) ;
return true ;
}
if ( demover > = 0x21a )
{
mapname = ( char * ) demo_p ;
demo_p + = mapname . Len ( ) + 1 ;
}
else
{
mapname = FString ( ( char * ) demo_p , 8 ) ;
demo_p + = 8 ;
}
rngseed = ReadLong ( & demo_p ) ;
// Only reset the RNG if this demo is not in conjunction with a savegame.
if ( mapname [ 0 ] ! = 0 )
{
FRandom : : StaticClearRandom ( ) ;
}
consoleplayer = * demo_p + + ;
break ;
case VARS_ID :
C_ReadCVars ( & demo_p ) ;
break ;
case UINF_ID :
i = ReadByte ( & demo_p ) ;
if ( ! playeringame [ i ] )
{
playeringame [ i ] = 1 ;
numPlayers + + ;
}
D_ReadUserInfoStrings ( i , & demo_p , false ) ;
break ;
case NETD_ID :
multiplayer = true ;
break ;
case WEAP_ID :
P_ReadDemoWeaponsChunk ( & demo_p ) ;
break ;
case BODY_ID :
bodyHit = true ;
zdembodyend = demo_p + len ;
break ;
case COMP_ID :
uncompSize = ReadLong ( & demo_p ) ;
break ;
}
if ( ! bodyHit )
demo_p = nextchunk ;
}
if ( ! headerHit )
{
Printf ( " Demo has no header! \n " ) ;
return true ;
}
if ( ! numPlayers )
{
Printf ( " Demo has no players! \n " ) ;
return true ;
}
if ( ! bodyHit )
{
zdembodyend = NULL ;
Printf ( " Demo has no BODY chunk! \n " ) ;
return true ;
}
if ( numPlayers > 1 )
multiplayer = netgame = true ;
if ( uncompSize > 0 )
{
2017-03-08 17:47:52 +00:00
uint8_t * uncompressed = ( uint8_t * ) M_Malloc ( uncompSize ) ;
2016-03-01 15:47:10 +00:00
int r = uncompress ( uncompressed , & uncompSize , demo_p , uLong ( zdembodyend - demo_p ) ) ;
if ( r ! = Z_OK )
{
Printf ( " Could not decompress demo! %s \n " , M_ZLibError ( r ) . GetChars ( ) ) ;
M_Free ( uncompressed ) ;
return true ;
}
M_Free ( demobuffer ) ;
zdembodyend = uncompressed + uncompSize ;
demobuffer = demo_p = uncompressed ;
}
return false ;
}
void G_DoPlayDemo ( void )
{
FString mapname ;
int demolump ;
gameaction = ga_nothing ;
// [RH] Allow for demos not loaded as lumps
2020-04-11 11:24:34 +00:00
demolump = fileSystem . CheckNumForFullName ( defdemoname , true ) ;
2016-03-01 15:47:10 +00:00
if ( demolump > = 0 )
{
2020-04-11 11:26:42 +00:00
int demolen = fileSystem . FileLength ( demolump ) ;
2017-03-08 17:47:52 +00:00
demobuffer = ( uint8_t * ) M_Malloc ( demolen ) ;
2020-04-11 11:27:19 +00:00
fileSystem . ReadFile ( demolump , demobuffer ) ;
2016-03-01 15:47:10 +00:00
}
else
{
FixPathSeperator ( defdemoname ) ;
DefaultExtension ( defdemoname , " .lmp " ) ;
2018-03-11 17:32:00 +00:00
FileReader fr ;
2018-03-10 19:33:49 +00:00
if ( ! fr . OpenFile ( defdemoname ) )
2017-12-02 12:09:59 +00:00
{
I_Error ( " Unable to open demo '%s' " , defdemoname . GetChars ( ) ) ;
}
auto len = fr . GetLength ( ) ;
demobuffer = ( uint8_t * ) M_Malloc ( len ) ;
if ( fr . Read ( demobuffer , len ) ! = len )
{
I_Error ( " Unable to read demo '%s' " , defdemoname . GetChars ( ) ) ;
}
2016-03-01 15:47:10 +00:00
}
demo_p = demobuffer ;
2019-03-14 23:16:08 +00:00
if ( singledemo ) Printf ( " Playing demo %s \n " , defdemoname . GetChars ( ) ) ;
2016-03-01 15:47:10 +00:00
C_BackupCVars ( ) ; // [RH] Save cvars that might be affected by demo
if ( ReadLong ( & demo_p ) ! = FORM_ID )
{
const char * eek = " Cannot play non- " GAMENAME " demos. \n " ;
C_ForgetCVars ( ) ;
M_Free ( demobuffer ) ;
demo_p = demobuffer = NULL ;
if ( singledemo )
{
I_Error ( " %s " , eek ) ;
}
else
{
2019-03-14 23:16:08 +00:00
//Printf (PRINT_BOLD, "%s", eek);
2016-03-01 15:47:10 +00:00
gameaction = ga_nothing ;
}
}
else if ( G_ProcessIFFDemo ( mapname ) )
{
C_RestoreCVars ( ) ;
gameaction = ga_nothing ;
demoplayback = false ;
}
else
{
// don't spend a lot of time in loadlevel
precache = false ;
demonew = true ;
if ( mapname . Len ( ) ! = 0 )
{
G_InitNew ( mapname , false ) ;
}
2019-02-01 23:24:43 +00:00
else if ( primaryLevel - > sectors . Size ( ) = = 0 )
2016-03-01 15:47:10 +00:00
{
I_Error ( " Cannot play demo without its savegame \n " ) ;
}
C_HideConsole ( ) ;
demonew = false ;
precache = true ;
usergame = false ;
demoplayback = true ;
2021-05-17 09:41:43 +00:00
playedtitlemusic = false ;
2016-03-01 15:47:10 +00:00
}
}
//
// G_TimeDemo
//
void G_TimeDemo ( const char * name )
{
nodrawers = ! ! Args - > CheckParm ( " -nodraw " ) ;
noblit = ! ! Args - > CheckParm ( " -noblit " ) ;
timingdemo = true ;
singletics = true ;
defdemoname = name ;
gameaction = ( gameaction = = ga_loadgame ) ? ga_loadgameplaydemo : 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
= = = = = = = = = = = = = = = = = = =
*/
bool G_CheckDemoStatus ( void )
{
if ( ! demorecording )
{ // [RH] Restore the player's userinfo settings.
D_SetupUserInfo ( ) ;
}
if ( demoplayback )
{
extern int starttime ;
int endtime = 0 ;
if ( timingdemo )
2017-11-12 02:12:22 +00:00
endtime = I_GetTime ( ) - starttime ;
2016-03-01 15:47:10 +00:00
C_RestoreCVars ( ) ; // [RH] Restore cvars demo might have changed
M_Free ( demobuffer ) ;
demobuffer = NULL ;
P_SetupWeapons_ntohton ( ) ;
demoplayback = false ;
netgame = false ;
multiplayer = false ;
singletics = false ;
for ( int i = 1 ; i < MAXPLAYERS ; i + + )
playeringame [ i ] = 0 ;
consoleplayer = 0 ;
2019-01-07 08:14:52 +00:00
players [ 0 ] . camera = nullptr ;
2016-03-01 15:47:10 +00:00
if ( StatusBar ! = NULL )
{
2017-03-22 16:29:13 +00:00
StatusBar - > AttachToPlayer ( & players [ 0 ] ) ;
2016-03-01 15:47:10 +00:00
}
if ( singledemo | | timingdemo )
{
if ( timingdemo )
{
// Trying to get back to a stable state after timing a demo
// seems to cause problems. I don't feel like fixing that
// right now.
I_FatalError ( " timed %i gametics in %i realtics (%.1f fps) \n "
" (This is not really an error.) " , gametic ,
endtime , ( float ) gametic / ( float ) endtime * ( float ) TICRATE ) ;
}
else
{
Printf ( " Demo ended. \n " ) ;
}
gameaction = ga_fullconsole ;
timingdemo = false ;
return false ;
}
else
{
D_AdvanceDemo ( ) ;
}
return true ;
}
if ( demorecording )
{
2017-03-08 17:47:52 +00:00
uint8_t * formlen ;
2016-03-01 15:47:10 +00:00
WriteByte ( DEM_STOP , & demo_p ) ;
if ( demo_compress )
{
// Now that the entire BODY chunk has been created, replace it with
// a compressed version. If the BODY successfully compresses, the
// contents of the COMP chunk will be changed to indicate the
// uncompressed size of the BODY.
uLong len = uLong ( demo_p - demobodyspot ) ;
uLong outlen = ( len + len / 100 + 12 ) ;
2018-12-21 08:21:40 +00:00
TArray < Byte > compressed ( outlen , true ) ;
int r = compress2 ( compressed . Data ( ) , & outlen , demobodyspot , len , 9 ) ;
2016-03-01 15:47:10 +00:00
if ( r = = Z_OK & & outlen < len )
{
formlen = democompspot ;
WriteLong ( len , & democompspot ) ;
2018-12-21 08:21:40 +00:00
memcpy ( demobodyspot , compressed . Data ( ) , outlen ) ;
2016-03-01 15:47:10 +00:00
demo_p = demobodyspot + outlen ;
}
}
FinishChunk ( & demo_p ) ;
formlen = demobuffer + 4 ;
WriteLong ( int ( demo_p - demobuffer - 8 ) , & formlen ) ;
2017-12-02 12:18:20 +00:00
auto fw = FileWriter : : Open ( demoname ) ;
bool saved = false ;
if ( fw ! = nullptr )
{
2017-12-03 11:18:47 +00:00
const size_t size = demo_p - demobuffer ;
2017-12-02 12:18:20 +00:00
saved = fw - > Write ( demobuffer , size ) = = size ;
delete fw ;
if ( ! saved ) remove ( demoname ) ;
}
2016-03-01 15:47:10 +00:00
M_Free ( demobuffer ) ;
demorecording = false ;
stoprecording = false ;
if ( saved )
{
Printf ( " Demo %s recorded \n " , demoname . GetChars ( ) ) ;
}
else
{
Printf ( " Demo %s could not be saved \n " , demoname . GetChars ( ) ) ;
}
}
return false ;
}
2017-03-13 13:42:14 +00:00
2019-01-29 03:17:58 +00:00
void G_StartSlideshow ( FLevelLocals * Level , FName whichone )
2017-03-14 09:41:13 +00:00
{
2022-04-11 11:12:37 +00:00
auto SelectedSlideshow = whichone = = NAME_None ? Level - > info - > slideshow : whichone ;
auto slide = F_StartIntermission ( SelectedSlideshow ) ;
2022-06-02 22:03:57 +00:00
RunIntermission ( nullptr , nullptr , slide , nullptr , [ ] ( bool )
2022-04-11 11:12:37 +00:00
{
primaryLevel - > SetMusic ( ) ;
gamestate = GS_LEVEL ;
wipegamestate = GS_LEVEL ;
gameaction = ga_resumeconversation ;
} ) ;
2017-03-14 09:41:13 +00:00
}
DEFINE_ACTION_FUNCTION ( FLevelLocals , StartSlideshow )
{
2019-01-29 03:17:58 +00:00
PARAM_SELF_STRUCT_PROLOGUE ( FLevelLocals ) ;
2018-11-17 09:03:40 +00:00
PARAM_NAME ( whichone ) ;
2019-01-29 03:17:58 +00:00
G_StartSlideshow ( self , whichone ) ;
2017-03-14 09:41:13 +00:00
return 0 ;
}
2019-03-03 05:01:00 +00:00
DEFINE_ACTION_FUNCTION ( FLevelLocals , MakeScreenShot )
{
if ( enablescriptscreenshot )
{
G_ScreenShot ( " " ) ;
}
return 0 ;
}
void G_MakeAutoSave ( )
{
2020-08-25 15:54:57 +00:00
if ( gameaction = = ga_nothing )
{
gameaction = ga_autosave ;
}
2019-03-03 05:01:00 +00:00
}
DEFINE_ACTION_FUNCTION ( FLevelLocals , MakeAutoSave )
{
G_MakeAutoSave ( ) ;
return 0 ;
}
2017-03-13 13:42:14 +00:00
DEFINE_GLOBAL ( players )
DEFINE_GLOBAL ( playeringame )
DEFINE_GLOBAL ( PlayerClasses )
DEFINE_GLOBAL_NAMED ( Skins , PlayerSkins )
DEFINE_GLOBAL ( consoleplayer )
DEFINE_GLOBAL_NAMED ( PClassActor : : AllActorClasses , AllActorClasses )
2019-02-01 23:24:43 +00:00
DEFINE_GLOBAL_NAMED ( primaryLevel , Level )
2017-03-13 13:42:14 +00:00
DEFINE_GLOBAL ( validcount )
DEFINE_GLOBAL ( multiplayer )
DEFINE_GLOBAL ( gameaction )
DEFINE_GLOBAL ( gamestate )
DEFINE_GLOBAL ( skyflatnum )
2019-01-30 00:38:18 +00:00
DEFINE_GLOBAL ( globalfreeze )
2017-03-13 13:42:14 +00:00
DEFINE_GLOBAL ( gametic )
DEFINE_GLOBAL ( demoplayback )
2017-04-12 23:12:04 +00:00
DEFINE_GLOBAL ( automapactive ) ;
DEFINE_GLOBAL ( Net_Arbitrator ) ;
2018-12-04 16:00:48 +00:00
DEFINE_GLOBAL ( netgame ) ;
2021-12-06 13:51:19 +00:00
DEFINE_GLOBAL ( paused ) ;
2022-02-22 15:32:08 +00:00
DEFINE_GLOBAL ( Terrains ) ;