1 // Emacs style mode select	 -*- C++ -*-
2 //-----------------------------------------------------------------------------
3 //
4 // $Id:$
5 //
6 // Copyright (C) 1993-1996 by id Software, Inc.
7 //
8 // This source is available for distribution and/or modification
9 // only under the terms of the DOOM Source Code License as
10 // published by id Software. All rights reserved.
11 //
12 // The source is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License
15 // for more details.
16 //
17 // $Log:$
18 //
19 // DESCRIPTION:  none
20 //
21 //-----------------------------------------------------------------------------
22 
23 
24 
25 #include <string.h>
26 #include <stdlib.h>
27 #include <stdio.h>
28 #include <stddef.h>
29 #include <time.h>
30 #ifdef __APPLE__
31 #include <CoreServices/CoreServices.h>
32 #endif
33 
34 #include "templates.h"
35 #include "version.h"
36 #include "doomdef.h"
37 #include "doomstat.h"
38 #include "d_protocol.h"
39 #include "d_netinf.h"
40 #include "intermission/intermission.h"
41 #include "m_argv.h"
42 #include "m_misc.h"
43 #include "menu/menu.h"
44 #include "m_random.h"
45 #include "m_crc32.h"
46 #include "i_system.h"
47 #include "i_input.h"
48 #include "p_saveg.h"
49 #include "p_tick.h"
50 #include "d_main.h"
51 #include "wi_stuff.h"
52 #include "hu_stuff.h"
53 #include "st_stuff.h"
54 #include "am_map.h"
55 #include "c_console.h"
56 #include "c_cvars.h"
57 #include "c_bind.h"
58 #include "c_dispatch.h"
59 #include "v_video.h"
60 #include "w_wad.h"
61 #include "p_local.h"
62 #include "s_sound.h"
63 #include "gstrings.h"
64 #include "r_sky.h"
65 #include "g_game.h"
66 #include "g_level.h"
67 #include "b_bot.h"			//Added by MC:
68 #include "sbar.h"
69 #include "m_swap.h"
70 #include "m_png.h"
71 #include "gi.h"
72 #include "a_keys.h"
73 #include "a_artifacts.h"
74 #include "r_data/r_translate.h"
75 #include "cmdlib.h"
76 #include "d_net.h"
77 #include "d_event.h"
78 #include "p_acs.h"
79 #include "p_effect.h"
80 #include "m_joy.h"
81 #include "farchive.h"
82 #include "r_renderer.h"
83 #include "r_data/colormaps.h"
84 
85 #include <zlib.h>
86 
87 #include "g_hub.h"
88 
89 
90 static FRandom pr_dmspawn ("DMSpawn");
91 static FRandom pr_pspawn ("PlayerSpawn");
92 
93 const int SAVEPICWIDTH = 216;
94 const int SAVEPICHEIGHT = 162;
95 
96 bool	G_CheckDemoStatus (void);
97 void	G_ReadDemoTiccmd (ticcmd_t *cmd, int player);
98 void	G_WriteDemoTiccmd (ticcmd_t *cmd, int player, int buf);
99 void	G_PlayerReborn (int player);
100 
101 void	G_DoNewGame (void);
102 void	G_DoLoadGame (void);
103 void	G_DoPlayDemo (void);
104 void	G_DoCompleted (void);
105 void	G_DoVictory (void);
106 void	G_DoWorldDone (void);
107 void	G_DoSaveGame (bool okForQuicksave, FString filename, const char *description);
108 void	G_DoAutoSave ();
109 
110 void STAT_Write(FILE *file);
111 void STAT_Read(PNGHandle *png);
112 
113 FIntCVar gameskill ("skill", 2, CVAR_SERVERINFO|CVAR_LATCH);
114 CVAR (Int, deathmatch, 0, CVAR_SERVERINFO|CVAR_LATCH);
115 CVAR (Bool, chasedemo, false, 0);
116 CVAR (Bool, storesavepic, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
117 CVAR (Bool, longsavemessages, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
118 CVAR (String, save_dir, "", CVAR_ARCHIVE|CVAR_GLOBALCONFIG);
119 CVAR (Bool, cl_waitforsave, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG);
120 EXTERN_CVAR (Float, con_midtime);
121 
122 //==========================================================================
123 //
124 // CVAR displaynametags
125 //
126 // Selects whether to display name tags or not when changing weapons/items
127 //
128 //==========================================================================
129 
130 CUSTOM_CVAR (Int, displaynametags, 0, CVAR_ARCHIVE)
131 {
132 	if (self < 0 || self > 3)
133 	{
134 		self = 0;
135 	}
136 }
137 
138 CVAR(Int, nametagcolor, CR_GOLD, CVAR_ARCHIVE)
139 
140 
141 gameaction_t	gameaction;
142 gamestate_t 	gamestate = GS_STARTUP;
143 
144 int 			paused;
145 bool			pauseext;
146 bool 			sendpause;				// send a pause event next tic
147 bool			sendsave;				// send a save event next tic
148 bool			sendturn180;			// [RH] send a 180 degree turn next tic
149 bool 			usergame;				// ok to save / end game
150 bool			insave;					// Game is saving - used to block exit commands
151 
152 bool			timingdemo; 			// if true, exit with report on completion
153 bool 			nodrawers;				// for comparative timing purposes
154 bool 			noblit; 				// for comparative timing purposes
155 
156 bool	 		viewactive;
157 
158 bool 			netgame;				// only true if packets are broadcast
159 bool			multiplayer;
160 player_t		players[MAXPLAYERS];
161 bool			playeringame[MAXPLAYERS];
162 
163 int 			consoleplayer;			// player taking events
164 int 			gametic;
165 
166 CVAR(Bool, demo_compress, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG);
167 FString			newdemoname;
168 FString			newdemomap;
169 FString			demoname;
170 bool 			demorecording;
171 bool 			demoplayback;
172 bool			demonew;				// [RH] Only used around G_InitNew for demos
173 int				demover;
174 BYTE*			demobuffer;
175 BYTE*			demo_p;
176 BYTE*			democompspot;
177 BYTE*			demobodyspot;
178 size_t			maxdemosize;
179 BYTE*			zdemformend;			// end of FORM ZDEM chunk
180 BYTE*			zdembodyend;			// end of ZDEM BODY chunk
181 bool 			singledemo; 			// quit after playing a demo from cmdline
182 
183 bool 			precache = true;		// if true, load all graphics at start
184 
185 wbstartstruct_t wminfo; 				// parms for world map / intermission
186 
187 short			consistancy[MAXPLAYERS][BACKUPTICS];
188 
189 
190 #define MAXPLMOVE				(forwardmove[1])
191 
192 #define TURBOTHRESHOLD	12800
193 
194 float	 		normforwardmove[2] = {0x19, 0x32};		// [RH] For setting turbo from console
195 float	 		normsidemove[2] = {0x18, 0x28};			// [RH] Ditto
196 
197 fixed_t			forwardmove[2], sidemove[2];
198 fixed_t 		angleturn[4] = {640, 1280, 320, 320};		// + slow turn
199 fixed_t			flyspeed[2] = {1*256, 3*256};
200 int				lookspeed[2] = {450, 512};
201 
202 #define SLOWTURNTICS	6
203 
204 CVAR (Bool,		cl_run,			false,	CVAR_GLOBALCONFIG|CVAR_ARCHIVE)		// Always run?
205 CVAR (Bool,		invertmouse,	false,	CVAR_GLOBALCONFIG|CVAR_ARCHIVE)		// Invert mouse look down/up?
206 CVAR (Bool,		freelook,		false,	CVAR_GLOBALCONFIG|CVAR_ARCHIVE)		// Always mlook?
207 CVAR (Bool,		lookstrafe,		false,	CVAR_GLOBALCONFIG|CVAR_ARCHIVE)		// Always strafe with mouse?
208 CVAR (Float,	m_pitch,		1.f,	CVAR_GLOBALCONFIG|CVAR_ARCHIVE)		// Mouse speeds
209 CVAR (Float,	m_yaw,			1.f,	CVAR_GLOBALCONFIG|CVAR_ARCHIVE)
210 CVAR (Float,	m_forward,		1.f,	CVAR_GLOBALCONFIG|CVAR_ARCHIVE)
211 CVAR (Float,	m_side,			2.f,	CVAR_GLOBALCONFIG|CVAR_ARCHIVE)
212 
213 int 			turnheld;								// for accelerative turning
214 
215 // mouse values are used once
216 int 			mousex;
217 int 			mousey;
218 
219 FString			savegamefile;
220 char			savedescription[SAVESTRINGSIZE];
221 
222 // [RH] Name of screenshot file to generate (usually NULL)
223 FString			shotfile;
224 
225 AActor* 		bodyque[BODYQUESIZE];
226 int 			bodyqueslot;
227 
228 void R_ExecuteSetViewSize (void);
229 
230 FString savename;
231 FString BackupSaveName;
232 
233 bool SendLand;
234 const AInventory *SendItemUse, *SendItemDrop;
235 
EXTERN_CVAR(Int,team)236 EXTERN_CVAR (Int, team)
237 
238 CVAR (Bool, teamplay, false, CVAR_SERVERINFO)
239 
240 // [RH] Allow turbo setting anytime during game
241 CUSTOM_CVAR (Float, turbo, 100.f, 0)
242 {
243 	if (self < 10.f)
244 	{
245 		self = 10.f;
246 	}
247 	else if (self > 255.f)
248 	{
249 		self = 255.f;
250 	}
251 	else
252 	{
253 		double scale = self * 0.01;
254 
255 		forwardmove[0] = (int)(normforwardmove[0]*scale);
256 		forwardmove[1] = (int)(normforwardmove[1]*scale);
257 		sidemove[0] = (int)(normsidemove[0]*scale);
258 		sidemove[1] = (int)(normsidemove[1]*scale);
259 	}
260 }
261 
CCMD(turnspeeds)262 CCMD (turnspeeds)
263 {
264 	if (argv.argc() == 1)
265 	{
266 		Printf ("Current turn speeds: %d %d %d %d\n", angleturn[0],
267 			angleturn[1], angleturn[2], angleturn[3]);
268 	}
269 	else
270 	{
271 		int i;
272 
273 		for (i = 1; i <= 4 && i < argv.argc(); ++i)
274 		{
275 			angleturn[i-1] = atoi (argv[i]);
276 		}
277 		if (i <= 2)
278 		{
279 			angleturn[1] = angleturn[0] * 2;
280 		}
281 		if (i <= 3)
282 		{
283 			angleturn[2] = angleturn[0] / 2;
284 		}
285 		if (i <= 4)
286 		{
287 			angleturn[3] = angleturn[2];
288 		}
289 	}
290 }
291 
CCMD(slot)292 CCMD (slot)
293 {
294 	if (argv.argc() > 1)
295 	{
296 		int slot = atoi (argv[1]);
297 
298 		if (slot < NUM_WEAPON_SLOTS)
299 		{
300 			SendItemUse = players[consoleplayer].weapons.Slots[slot].PickWeapon (&players[consoleplayer],
301 				!(dmflags2 & DF2_DONTCHECKAMMO));
302 		}
303 	}
304 }
305 
CCMD(centerview)306 CCMD (centerview)
307 {
308 	Net_WriteByte (DEM_CENTERVIEW);
309 }
310 
CCMD(crouch)311 CCMD(crouch)
312 {
313 	Net_WriteByte(DEM_CROUCH);
314 }
315 
CCMD(land)316 CCMD (land)
317 {
318 	SendLand = true;
319 }
320 
CCMD(pause)321 CCMD (pause)
322 {
323 	sendpause = true;
324 }
325 
CCMD(turn180)326 CCMD (turn180)
327 {
328 	sendturn180 = true;
329 }
330 
CCMD(weapnext)331 CCMD (weapnext)
332 {
333 	SendItemUse = players[consoleplayer].weapons.PickNextWeapon (&players[consoleplayer]);
334 	// [BC] Option to display the name of the weapon being cycled to.
335 	if ((displaynametags & 2) && StatusBar && SmallFont && SendItemUse)
336 	{
337 		StatusBar->AttachMessage(new DHUDMessageFadeOut(SmallFont, SendItemUse->GetTag(),
338 			1.5f, 0.90f, 0, 0, (EColorRange)*nametagcolor, 2.f, 0.35f), MAKE_ID( 'W', 'E', 'P', 'N' ));
339 	}
340 }
341 
CCMD(weapprev)342 CCMD (weapprev)
343 {
344 	SendItemUse = players[consoleplayer].weapons.PickPrevWeapon (&players[consoleplayer]);
345 	// [BC] Option to display the name of the weapon being cycled to.
346 	if ((displaynametags & 2) && StatusBar && SmallFont && SendItemUse)
347 	{
348 		StatusBar->AttachMessage(new DHUDMessageFadeOut(SmallFont, SendItemUse->GetTag(),
349 			1.5f, 0.90f, 0, 0, (EColorRange)*nametagcolor, 2.f, 0.35f), MAKE_ID( 'W', 'E', 'P', 'N' ));
350 	}
351 }
352 
CCMD(invnext)353 CCMD (invnext)
354 {
355 	AInventory *next;
356 
357 	if (who == NULL)
358 		return;
359 
360 	if (who->InvSel != NULL)
361 	{
362 		if ((next = who->InvSel->NextInv()) != NULL)
363 		{
364 			who->InvSel = next;
365 		}
366 		else
367 		{
368 			// Select the first item in the inventory
369 			if (!(who->Inventory->ItemFlags & IF_INVBAR))
370 			{
371 				who->InvSel = who->Inventory->NextInv();
372 			}
373 			else
374 			{
375 				who->InvSel = who->Inventory;
376 			}
377 		}
378 		if ((displaynametags & 1) && StatusBar && SmallFont && who->InvSel)
379 			StatusBar->AttachMessage (new DHUDMessageFadeOut (SmallFont, who->InvSel->GetTag(),
380 			1.5f, 0.80f, 0, 0, (EColorRange)*nametagcolor, 2.f, 0.35f), MAKE_ID('S','I','N','V'));
381 	}
382 	who->player->inventorytics = 5*TICRATE;
383 }
384 
CCMD(invprev)385 CCMD (invprev)
386 {
387 	AInventory *item, *newitem;
388 
389 	if (who == NULL)
390 		return;
391 
392 	if (who->InvSel != NULL)
393 	{
394 		if ((item = who->InvSel->PrevInv()) != NULL)
395 		{
396 			who->InvSel = item;
397 		}
398 		else
399 		{
400 			// Select the last item in the inventory
401 			item = who->InvSel;
402 			while ((newitem = item->NextInv()) != NULL)
403 			{
404 				item = newitem;
405 			}
406 			who->InvSel = item;
407 		}
408 		if ((displaynametags & 1) && StatusBar && SmallFont && who->InvSel)
409 			StatusBar->AttachMessage (new DHUDMessageFadeOut (SmallFont, who->InvSel->GetTag(),
410 			1.5f, 0.80f, 0, 0, (EColorRange)*nametagcolor, 2.f, 0.35f), MAKE_ID('S','I','N','V'));
411 	}
412 	who->player->inventorytics = 5*TICRATE;
413 }
414 
CCMD(invuseall)415 CCMD (invuseall)
416 {
417 	SendItemUse = (const AInventory *)1;
418 }
419 
CCMD(invuse)420 CCMD (invuse)
421 {
422 	if (players[consoleplayer].inventorytics == 0)
423 	{
424 		if (players[consoleplayer].mo) SendItemUse = players[consoleplayer].mo->InvSel;
425 	}
426 	players[consoleplayer].inventorytics = 0;
427 }
428 
CCMD(invquery)429 CCMD(invquery)
430 {
431 	AInventory *inv = players[consoleplayer].mo->InvSel;
432 	if (inv != NULL)
433 	{
434 		Printf(PRINT_HIGH, "%s (%dx)\n", inv->GetTag(), inv->Amount);
435 	}
436 }
437 
CCMD(use)438 CCMD (use)
439 {
440 	if (argv.argc() > 1 && who != NULL)
441 	{
442 		SendItemUse = who->FindInventory (PClass::FindClass (argv[1]));
443 	}
444 }
445 
CCMD(invdrop)446 CCMD (invdrop)
447 {
448 	if (players[consoleplayer].mo) SendItemDrop = players[consoleplayer].mo->InvSel;
449 }
450 
CCMD(weapdrop)451 CCMD (weapdrop)
452 {
453 	SendItemDrop = players[consoleplayer].ReadyWeapon;
454 }
455 
CCMD(drop)456 CCMD (drop)
457 {
458 	if (argv.argc() > 1 && who != NULL)
459 	{
460 		SendItemDrop = who->FindInventory (PClass::FindClass (argv[1]));
461 	}
462 }
463 
464 const PClass *GetFlechetteType(AActor *other);
465 
CCMD(useflechette)466 CCMD (useflechette)
467 { // Select from one of arti_poisonbag1-3, whichever the player has
468 	static const ENamedName bagnames[3] =
469 	{
470 		NAME_ArtiPoisonBag1,
471 		NAME_ArtiPoisonBag2,
472 		NAME_ArtiPoisonBag3
473 	};
474 
475 	if (who == NULL)
476 		return;
477 
478 	const PClass *type = GetFlechetteType(who);
479 	if (type != NULL)
480 	{
481 		AInventory *item;
482 		if ( (item = who->FindInventory (type) ))
483 		{
484 			SendItemUse = item;
485 			return;
486 		}
487 	}
488 
489 	// The default flechette could not be found. Try all 3 types then.
490 	for (int j = 0; j < 3; ++j)
491 	{
492 		AInventory *item;
493 		if ( (item = who->FindInventory (bagnames[j])) )
494 		{
495 			SendItemUse = item;
496 			break;
497 		}
498 	}
499 }
500 
CCMD(select)501 CCMD (select)
502 {
503 	if (argv.argc() > 1)
504 	{
505 		AInventory *item = who->FindInventory (PClass::FindClass (argv[1]));
506 		if (item != NULL)
507 		{
508 			who->InvSel = item;
509 		}
510 	}
511 	who->player->inventorytics = 5*TICRATE;
512 }
513 
joyint(double val)514 static inline int joyint(double val)
515 {
516 	if (val >= 0)
517 	{
518 		return int(ceil(val));
519 	}
520 	else
521 	{
522 		return int(floor(val));
523 	}
524 }
525 
526 //
527 // G_BuildTiccmd
528 // Builds a ticcmd from all of the available inputs
529 // or reads it from the demo buffer.
530 // If recording a demo, write it out
531 //
G_BuildTiccmd(ticcmd_t * cmd)532 void G_BuildTiccmd (ticcmd_t *cmd)
533 {
534 	int 		strafe;
535 	int 		speed;
536 	int 		forward;
537 	int 		side;
538 	int			fly;
539 
540 	ticcmd_t	*base;
541 
542 	base = I_BaseTiccmd (); 			// empty, or external driver
543 	*cmd = *base;
544 
545 	cmd->consistancy = consistancy[consoleplayer][(maketic/ticdup)%BACKUPTICS];
546 
547 	strafe = Button_Strafe.bDown;
548 	speed = Button_Speed.bDown ^ (int)cl_run;
549 
550 	forward = side = fly = 0;
551 
552 	// [RH] only use two stage accelerative turning on the keyboard
553 	//		and not the joystick, since we treat the joystick as
554 	//		the analog device it is.
555 	if (Button_Left.bDown || Button_Right.bDown)
556 		turnheld += ticdup;
557 	else
558 		turnheld = 0;
559 
560 	// let movement keys cancel each other out
561 	if (strafe)
562 	{
563 		if (Button_Right.bDown)
564 			side += sidemove[speed];
565 		if (Button_Left.bDown)
566 			side -= sidemove[speed];
567 	}
568 	else
569 	{
570 		int tspeed = speed;
571 
572 		if (turnheld < SLOWTURNTICS)
573 			tspeed += 2;		// slow turn
574 
575 		if (Button_Right.bDown)
576 		{
577 			G_AddViewAngle (angleturn[tspeed]);
578 			LocalKeyboardTurner = true;
579 		}
580 		if (Button_Left.bDown)
581 		{
582 			G_AddViewAngle (-angleturn[tspeed]);
583 			LocalKeyboardTurner = true;
584 		}
585 	}
586 
587 	if (Button_LookUp.bDown)
588 	{
589 		G_AddViewPitch (lookspeed[speed]);
590 		LocalKeyboardTurner = true;
591 	}
592 	if (Button_LookDown.bDown)
593 	{
594 		G_AddViewPitch (-lookspeed[speed]);
595 		LocalKeyboardTurner = true;
596 	}
597 
598 	if (Button_MoveUp.bDown)
599 		fly += flyspeed[speed];
600 	if (Button_MoveDown.bDown)
601 		fly -= flyspeed[speed];
602 
603 	if (Button_Klook.bDown)
604 	{
605 		if (Button_Forward.bDown)
606 			G_AddViewPitch (lookspeed[speed]);
607 		if (Button_Back.bDown)
608 			G_AddViewPitch (-lookspeed[speed]);
609 	}
610 	else
611 	{
612 		if (Button_Forward.bDown)
613 			forward += forwardmove[speed];
614 		if (Button_Back.bDown)
615 			forward -= forwardmove[speed];
616 	}
617 
618 	if (Button_MoveRight.bDown)
619 		side += sidemove[speed];
620 	if (Button_MoveLeft.bDown)
621 		side -= sidemove[speed];
622 
623 	// buttons
624 	if (Button_Attack.bDown)		cmd->ucmd.buttons |= BT_ATTACK;
625 	if (Button_AltAttack.bDown)		cmd->ucmd.buttons |= BT_ALTATTACK;
626 	if (Button_Use.bDown)			cmd->ucmd.buttons |= BT_USE;
627 	if (Button_Jump.bDown)			cmd->ucmd.buttons |= BT_JUMP;
628 	if (Button_Crouch.bDown)		cmd->ucmd.buttons |= BT_CROUCH;
629 	if (Button_Zoom.bDown)			cmd->ucmd.buttons |= BT_ZOOM;
630 	if (Button_Reload.bDown)		cmd->ucmd.buttons |= BT_RELOAD;
631 
632 	if (Button_User1.bDown)			cmd->ucmd.buttons |= BT_USER1;
633 	if (Button_User2.bDown)			cmd->ucmd.buttons |= BT_USER2;
634 	if (Button_User3.bDown)			cmd->ucmd.buttons |= BT_USER3;
635 	if (Button_User4.bDown)			cmd->ucmd.buttons |= BT_USER4;
636 
637 	if (Button_Speed.bDown)			cmd->ucmd.buttons |= BT_SPEED;
638 	if (Button_Strafe.bDown)		cmd->ucmd.buttons |= BT_STRAFE;
639 	if (Button_MoveRight.bDown)		cmd->ucmd.buttons |= BT_MOVERIGHT;
640 	if (Button_MoveLeft.bDown)		cmd->ucmd.buttons |= BT_MOVELEFT;
641 	if (Button_LookDown.bDown)		cmd->ucmd.buttons |= BT_LOOKDOWN;
642 	if (Button_LookUp.bDown)		cmd->ucmd.buttons |= BT_LOOKUP;
643 	if (Button_Back.bDown)			cmd->ucmd.buttons |= BT_BACK;
644 	if (Button_Forward.bDown)		cmd->ucmd.buttons |= BT_FORWARD;
645 	if (Button_Right.bDown)			cmd->ucmd.buttons |= BT_RIGHT;
646 	if (Button_Left.bDown)			cmd->ucmd.buttons |= BT_LEFT;
647 	if (Button_MoveDown.bDown)		cmd->ucmd.buttons |= BT_MOVEDOWN;
648 	if (Button_MoveUp.bDown)		cmd->ucmd.buttons |= BT_MOVEUP;
649 	if (Button_ShowScores.bDown)	cmd->ucmd.buttons |= BT_SHOWSCORES;
650 
651 	// Handle joysticks/game controllers.
652 	float joyaxes[NUM_JOYAXIS];
653 
654 	I_GetAxes(joyaxes);
655 
656 	// Remap some axes depending on button state.
657 	if (Button_Strafe.bDown || (Button_Mlook.bDown && lookstrafe))
658 	{
659 		joyaxes[JOYAXIS_Side] = joyaxes[JOYAXIS_Yaw];
660 		joyaxes[JOYAXIS_Yaw] = 0;
661 	}
662 	if (Button_Mlook.bDown)
663 	{
664 		joyaxes[JOYAXIS_Pitch] = joyaxes[JOYAXIS_Forward];
665 		joyaxes[JOYAXIS_Forward] = 0;
666 	}
667 
668 	if (joyaxes[JOYAXIS_Pitch] != 0)
669 	{
670 		G_AddViewPitch(joyint(joyaxes[JOYAXIS_Pitch] * 2048));
671 		LocalKeyboardTurner = true;
672 	}
673 	if (joyaxes[JOYAXIS_Yaw] != 0)
674 	{
675 		G_AddViewAngle(joyint(-1280 * joyaxes[JOYAXIS_Yaw]));
676 		LocalKeyboardTurner = true;
677 	}
678 
679 	side -= joyint(sidemove[speed] * joyaxes[JOYAXIS_Side]);
680 	forward += joyint(joyaxes[JOYAXIS_Forward] * forwardmove[speed]);
681 	fly += joyint(joyaxes[JOYAXIS_Up] * 2048);
682 
683 	// Handle mice.
684 	if (!Button_Mlook.bDown && !freelook)
685 	{
686 		forward += (int)((float)mousey * m_forward);
687 	}
688 
689 	cmd->ucmd.pitch = LocalViewPitch >> 16;
690 
691 	if (SendLand)
692 	{
693 		SendLand = false;
694 		fly = -32768;
695 	}
696 
697 	if (strafe || lookstrafe)
698 		side += (int)((float)mousex * m_side);
699 
700 	mousex = mousey = 0;
701 
702 	// Build command.
703 	if (forward > MAXPLMOVE)
704 		forward = MAXPLMOVE;
705 	else if (forward < -MAXPLMOVE)
706 		forward = -MAXPLMOVE;
707 	if (side > MAXPLMOVE)
708 		side = MAXPLMOVE;
709 	else if (side < -MAXPLMOVE)
710 		side = -MAXPLMOVE;
711 
712 	cmd->ucmd.forwardmove += forward;
713 	cmd->ucmd.sidemove += side;
714 	cmd->ucmd.yaw = LocalViewAngle >> 16;
715 	cmd->ucmd.upmove = fly;
716 	LocalViewAngle = 0;
717 	LocalViewPitch = 0;
718 
719 	// special buttons
720 	if (sendturn180)
721 	{
722 		sendturn180 = false;
723 		cmd->ucmd.buttons |= BT_TURN180;
724 	}
725 	if (sendpause)
726 	{
727 		sendpause = false;
728 		Net_WriteByte (DEM_PAUSE);
729 	}
730 	if (sendsave)
731 	{
732 		sendsave = false;
733 		Net_WriteByte (DEM_SAVEGAME);
734 		Net_WriteString (savegamefile);
735 		Net_WriteString (savedescription);
736 		savegamefile = "";
737 	}
738 	if (SendItemUse == (const AInventory *)1)
739 	{
740 		Net_WriteByte (DEM_INVUSEALL);
741 		SendItemUse = NULL;
742 	}
743 	else if (SendItemUse != NULL)
744 	{
745 		Net_WriteByte (DEM_INVUSE);
746 		Net_WriteLong (SendItemUse->InventoryID);
747 		SendItemUse = NULL;
748 	}
749 	if (SendItemDrop != NULL)
750 	{
751 		Net_WriteByte (DEM_INVDROP);
752 		Net_WriteLong (SendItemDrop->InventoryID);
753 		SendItemDrop = NULL;
754 	}
755 
756 	cmd->ucmd.forwardmove <<= 8;
757 	cmd->ucmd.sidemove <<= 8;
758 }
759 
760 //[Graf Zahl] This really helps if the mouse update rate can't be increased!
761 CVAR (Bool,		smooth_mouse,	false,	CVAR_GLOBALCONFIG|CVAR_ARCHIVE)
762 
G_AddViewPitch(int look)763 void G_AddViewPitch (int look)
764 {
765 	if (gamestate == GS_TITLELEVEL)
766 	{
767 		return;
768 	}
769 	look <<= 16;
770 	if (players[consoleplayer].playerstate != PST_DEAD &&		// No adjustment while dead.
771 		players[consoleplayer].ReadyWeapon != NULL &&			// No adjustment if no weapon.
772 		players[consoleplayer].ReadyWeapon->FOVScale > 0)		// No adjustment if it is non-positive.
773 	{
774 		look = int(look * players[consoleplayer].ReadyWeapon->FOVScale);
775 	}
776 	if (!level.IsFreelookAllowed())
777 	{
778 		LocalViewPitch = 0;
779 	}
780 	else if (look > 0)
781 	{
782 		// Avoid overflowing
783 		if (LocalViewPitch > INT_MAX - look)
784 		{
785 			LocalViewPitch = 0x78000000;
786 		}
787 		else
788 		{
789 			LocalViewPitch = MIN(LocalViewPitch + look, 0x78000000);
790 		}
791 	}
792 	else if (look < 0)
793 	{
794 		// Avoid overflowing
795 		if (LocalViewPitch < INT_MIN - look)
796 		{
797 			LocalViewPitch = -0x78000000;
798 		}
799 		else
800 		{
801 			LocalViewPitch = MAX(LocalViewPitch + look, -0x78000000);
802 		}
803 	}
804 	if (look != 0)
805 	{
806 		LocalKeyboardTurner = smooth_mouse;
807 	}
808 }
809 
G_AddViewAngle(int yaw)810 void G_AddViewAngle (int yaw)
811 {
812 	if (gamestate == GS_TITLELEVEL)
813 	{
814 		return;
815 	}
816 	yaw <<= 16;
817 	if (players[consoleplayer].playerstate != PST_DEAD &&	// No adjustment while dead.
818 		players[consoleplayer].ReadyWeapon != NULL &&		// No adjustment if no weapon.
819 		players[consoleplayer].ReadyWeapon->FOVScale > 0)	// No adjustment if it is non-positive.
820 	{
821 		yaw = int(yaw * players[consoleplayer].ReadyWeapon->FOVScale);
822 	}
823 	LocalViewAngle -= yaw;
824 	if (yaw != 0)
825 	{
826 		LocalKeyboardTurner = smooth_mouse;
827 	}
828 }
829 
830 CVAR (Bool, bot_allowspy, false, 0)
831 
832 
833 enum {
834 	SPY_CANCEL = 0,
835 	SPY_NEXT,
836 	SPY_PREV,
837 };
838 
839 // [RH] Spy mode has been separated into two console commands.
840 //		One goes forward; the other goes backward.
ChangeSpy(int changespy)841 static void ChangeSpy (int changespy)
842 {
843 	// If you're not in a level, then you can't spy.
844 	if (gamestate != GS_LEVEL)
845 	{
846 		return;
847 	}
848 
849 	// If not viewing through a player, return your eyes to your own head.
850 	if (players[consoleplayer].camera->player == NULL)
851 	{
852 		// When watching demos, you will just have to wait until your player
853 		// has done this for you, since it could desync otherwise.
854 		if (!demoplayback)
855 		{
856 			Net_WriteByte(DEM_REVERTCAMERA);
857 		}
858 		return;
859 	}
860 
861 	// We may not be allowed to spy on anyone.
862 	if (dmflags2 & DF2_DISALLOW_SPYING)
863 		return;
864 
865 	// Otherwise, cycle to the next player.
866 	bool checkTeam = !demoplayback && deathmatch;
867 	int pnum = consoleplayer;
868 	if (changespy != SPY_CANCEL)
869 	{
870 		player_t *player = players[consoleplayer].camera->player;
871 		// only use the camera as starting index if it's a valid player.
872 		if (player != NULL) pnum = int(players[consoleplayer].camera->player - players);
873 
874 		int step = (changespy == SPY_NEXT) ? 1 : -1;
875 
876 		do
877 		{
878 			pnum += step;
879 			pnum &= MAXPLAYERS-1;
880 			if (playeringame[pnum] &&
881 				(!checkTeam || players[pnum].mo->IsTeammate (players[consoleplayer].mo) ||
882 				(bot_allowspy && players[pnum].Bot != NULL)))
883 			{
884 				break;
885 			}
886 		} while (pnum != consoleplayer);
887 	}
888 
889 	players[consoleplayer].camera = players[pnum].mo;
890 	S_UpdateSounds(players[consoleplayer].camera);
891 	StatusBar->AttachToPlayer (&players[pnum]);
892 	if (demoplayback || multiplayer)
893 	{
894 		StatusBar->ShowPlayerName ();
895 	}
896 }
897 
CCMD(spynext)898 CCMD (spynext)
899 {
900 	// allow spy mode changes even during the demo
901 	ChangeSpy (SPY_NEXT);
902 }
903 
CCMD(spyprev)904 CCMD (spyprev)
905 {
906 	// allow spy mode changes even during the demo
907 	ChangeSpy (SPY_PREV);
908 }
909 
CCMD(spycancel)910 CCMD (spycancel)
911 {
912 	// allow spy mode changes even during the demo
913 	ChangeSpy (SPY_CANCEL);
914 }
915 
916 //
917 // G_Responder
918 // Get info needed to make ticcmd_ts for the players.
919 //
G_Responder(event_t * ev)920 bool G_Responder (event_t *ev)
921 {
922 	// any other key pops up menu if in demos
923 	// [RH] But only if the key isn't bound to a "special" command
924 	if (gameaction == ga_nothing &&
925 		(demoplayback || gamestate == GS_DEMOSCREEN || gamestate == GS_TITLELEVEL))
926 	{
927 		const char *cmd = Bindings.GetBind (ev->data1);
928 
929 		if (ev->type == EV_KeyDown)
930 		{
931 
932 			if (!cmd || (
933 				strnicmp (cmd, "menu_", 5) &&
934 				stricmp (cmd, "toggleconsole") &&
935 				stricmp (cmd, "sizeup") &&
936 				stricmp (cmd, "sizedown") &&
937 				stricmp (cmd, "togglemap") &&
938 				stricmp (cmd, "spynext") &&
939 				stricmp (cmd, "spyprev") &&
940 				stricmp (cmd, "chase") &&
941 				stricmp (cmd, "+showscores") &&
942 				stricmp (cmd, "bumpgamma") &&
943 				stricmp (cmd, "screenshot")))
944 			{
945 				M_StartControlPanel(true);
946 				M_SetMenu(NAME_Mainmenu, -1);
947 				return true;
948 			}
949 			else
950 			{
951 				return C_DoKey (ev, &Bindings, &DoubleBindings);
952 			}
953 		}
954 		if (cmd && cmd[0] == '+')
955 			return C_DoKey (ev, &Bindings, &DoubleBindings);
956 
957 		return false;
958 	}
959 
960 	if (CT_Responder (ev))
961 		return true;			// chat ate the event
962 
963 	if (gamestate == GS_LEVEL)
964 	{
965 		if (ST_Responder (ev))
966 			return true;		// status window ate it
967 		if (!viewactive && AM_Responder (ev, false))
968 			return true;		// automap ate it
969 	}
970 	else if (gamestate == GS_FINALE)
971 	{
972 		if (F_Responder (ev))
973 			return true;		// finale ate the event
974 	}
975 
976 	switch (ev->type)
977 	{
978 	case EV_KeyDown:
979 		if (C_DoKey (ev, &Bindings, &DoubleBindings))
980 			return true;
981 		break;
982 
983 	case EV_KeyUp:
984 		C_DoKey (ev, &Bindings, &DoubleBindings);
985 		break;
986 
987 	// [RH] mouse buttons are sent as key up/down events
988 	case EV_Mouse:
989 		mousex = (int)(ev->x * mouse_sensitivity);
990 		mousey = (int)(ev->y * mouse_sensitivity);
991 		break;
992 	}
993 
994 	// [RH] If the view is active, give the automap a chance at
995 	// the events *last* so that any bound keys get precedence.
996 
997 	if (gamestate == GS_LEVEL && viewactive)
998 		return AM_Responder (ev, true);
999 
1000 	return (ev->type == EV_KeyDown ||
1001 			ev->type == EV_Mouse);
1002 }
1003 
1004 
1005 
1006 //
1007 // G_Ticker
1008 // Make ticcmd_ts for the players.
1009 //
1010 extern FTexture *Page;
1011 
1012 
G_Ticker()1013 void G_Ticker ()
1014 {
1015 	int i;
1016 	gamestate_t	oldgamestate;
1017 
1018 	// do player reborns if needed
1019 	for (i = 0; i < MAXPLAYERS; i++)
1020 	{
1021 		if (playeringame[i])
1022 		{
1023 			if (players[i].playerstate == PST_GONE)
1024 			{
1025 				G_DoPlayerPop(i);
1026 			}
1027 			if (players[i].playerstate == PST_REBORN || players[i].playerstate == PST_ENTER)
1028 			{
1029 				G_DoReborn(i, false);
1030 			}
1031 		}
1032 	}
1033 
1034 	if (ToggleFullscreen)
1035 	{
1036 		static char toggle_fullscreen[] = "toggle fullscreen";
1037 		ToggleFullscreen = false;
1038 		AddCommandString (toggle_fullscreen);
1039 	}
1040 
1041 	// do things to change the game state
1042 	oldgamestate = gamestate;
1043 	while (gameaction != ga_nothing)
1044 	{
1045 		if (gameaction == ga_newgame2)
1046 		{
1047 			gameaction = ga_newgame;
1048 			break;
1049 		}
1050 		switch (gameaction)
1051 		{
1052 		case ga_loadlevel:
1053 			G_DoLoadLevel (-1, false);
1054 			break;
1055 		case ga_recordgame:
1056 			G_CheckDemoStatus();
1057 			G_RecordDemo(newdemoname);
1058 			G_BeginRecording(newdemomap);
1059 		case ga_newgame2:	// Silence GCC (see above)
1060 		case ga_newgame:
1061 			G_DoNewGame ();
1062 			break;
1063 		case ga_loadgame:
1064 		case ga_loadgamehidecon:
1065 		case ga_autoloadgame:
1066 			G_DoLoadGame ();
1067 			break;
1068 		case ga_savegame:
1069 			G_DoSaveGame (true, savegamefile, savedescription);
1070 			gameaction = ga_nothing;
1071 			savegamefile = "";
1072 			savedescription[0] = '\0';
1073 			break;
1074 		case ga_autosave:
1075 			G_DoAutoSave ();
1076 			gameaction = ga_nothing;
1077 			break;
1078 		case ga_loadgameplaydemo:
1079 			G_DoLoadGame ();
1080 			// fallthrough
1081 		case ga_playdemo:
1082 			G_DoPlayDemo ();
1083 			break;
1084 		case ga_completed:
1085 			G_DoCompleted ();
1086 			break;
1087 		case ga_slideshow:
1088 			if (gamestate == GS_LEVEL) F_StartIntermission(level.info->slideshow, FSTATE_InLevel);
1089 			break;
1090 		case ga_worlddone:
1091 			G_DoWorldDone ();
1092 			break;
1093 		case ga_screenshot:
1094 			M_ScreenShot (shotfile);
1095 			shotfile = "";
1096 			gameaction = ga_nothing;
1097 			break;
1098 		case ga_fullconsole:
1099 			C_FullConsole ();
1100 			gameaction = ga_nothing;
1101 			break;
1102 		case ga_togglemap:
1103 			AM_ToggleMap ();
1104 			gameaction = ga_nothing;
1105 			break;
1106 		case ga_nothing:
1107 			break;
1108 		}
1109 		C_AdjustBottom ();
1110 	}
1111 
1112 	if (oldgamestate != gamestate)
1113 	{
1114 		if (oldgamestate == GS_DEMOSCREEN && Page != NULL)
1115 		{
1116 			Page->Unload();
1117 			Page = NULL;
1118 		}
1119 		else if (oldgamestate == GS_FINALE)
1120 		{
1121 			F_EndFinale ();
1122 		}
1123 	}
1124 
1125 	// get commands, check consistancy, and build new consistancy check
1126 	int buf = (gametic/ticdup)%BACKUPTICS;
1127 
1128 	// [RH] Include some random seeds and player stuff in the consistancy
1129 	// check, not just the player's x position like BOOM.
1130 	DWORD rngsum = FRandom::StaticSumSeeds ();
1131 
1132 	//Added by MC: For some of that bot stuff. The main bot function.
1133 	bglobal.Main ();
1134 
1135 	for (i = 0; i < MAXPLAYERS; i++)
1136 	{
1137 		if (playeringame[i])
1138 		{
1139 			ticcmd_t *cmd = &players[i].cmd;
1140 			ticcmd_t *newcmd = &netcmds[i][buf];
1141 
1142 			if ((gametic % ticdup) == 0)
1143 			{
1144 				RunNetSpecs (i, buf);
1145 			}
1146 			if (demorecording)
1147 			{
1148 				G_WriteDemoTiccmd (newcmd, i, buf);
1149 			}
1150 			players[i].oldbuttons = cmd->ucmd.buttons;
1151 			// If the user alt-tabbed away, paused gets set to -1. In this case,
1152 			// we do not want to read more demo commands until paused is no
1153 			// longer negative.
1154 			if (demoplayback)
1155 			{
1156 				G_ReadDemoTiccmd (cmd, i);
1157 			}
1158 			else
1159 			{
1160 				memcpy(cmd, newcmd, sizeof(ticcmd_t));
1161 			}
1162 
1163 			// check for turbo cheats
1164 			if (cmd->ucmd.forwardmove > TURBOTHRESHOLD &&
1165 				!(gametic&31) && ((gametic>>5)&(MAXPLAYERS-1)) == i )
1166 			{
1167 				Printf ("%s is turbo!\n", players[i].userinfo.GetName());
1168 			}
1169 
1170 			if (netgame && players[i].Bot == NULL && !demoplayback && (gametic%ticdup) == 0)
1171 			{
1172 				//players[i].inconsistant = 0;
1173 				if (gametic > BACKUPTICS*ticdup && consistancy[i][buf] != cmd->consistancy)
1174 				{
1175 					players[i].inconsistant = gametic - BACKUPTICS*ticdup;
1176 				}
1177 				if (players[i].mo)
1178 				{
1179 					DWORD sum = rngsum + players[i].mo->X() + players[i].mo->Y() + players[i].mo->Z()
1180 						+ players[i].mo->angle + players[i].mo->pitch;
1181 					sum ^= players[i].health;
1182 					consistancy[i][buf] = sum;
1183 				}
1184 				else
1185 				{
1186 					consistancy[i][buf] = rngsum;
1187 				}
1188 			}
1189 		}
1190 	}
1191 
1192 	// do main actions
1193 	switch (gamestate)
1194 	{
1195 	case GS_LEVEL:
1196 		P_Ticker ();
1197 		AM_Ticker ();
1198 		break;
1199 
1200 	case GS_TITLELEVEL:
1201 		P_Ticker ();
1202 		break;
1203 
1204 	case GS_INTERMISSION:
1205 		WI_Ticker ();
1206 		break;
1207 
1208 	case GS_FINALE:
1209 		F_Ticker ();
1210 		break;
1211 
1212 	case GS_DEMOSCREEN:
1213 		D_PageTicker ();
1214 		break;
1215 
1216 	case GS_STARTUP:
1217 		if (gameaction == ga_nothing)
1218 		{
1219 			gamestate = GS_FULLCONSOLE;
1220 			gameaction = ga_fullconsole;
1221 		}
1222 		break;
1223 
1224 	default:
1225 		break;
1226 	}
1227 }
1228 
1229 
1230 //
1231 // PLAYER STRUCTURE FUNCTIONS
1232 // also see P_SpawnPlayer in P_Mobj
1233 //
1234 
1235 //
1236 // G_PlayerFinishLevel
1237 // Called when a player completes a level.
1238 //
1239 // flags is checked for RESETINVENTORY and RESETHEALTH only.
1240 
G_PlayerFinishLevel(int player,EFinishLevelType mode,int flags)1241 void G_PlayerFinishLevel (int player, EFinishLevelType mode, int flags)
1242 {
1243 	AInventory *item, *next;
1244 	player_t *p;
1245 
1246 	p = &players[player];
1247 
1248 	if (p->morphTics != 0)
1249 	{ // Undo morph
1250 		P_UndoPlayerMorph (p, p, 0, true);
1251 	}
1252 
1253 	// Strip all current powers, unless moving in a hub and the power is okay to keep.
1254 	item = p->mo->Inventory;
1255 	while (item != NULL)
1256 	{
1257 		next = item->Inventory;
1258 		if (item->IsKindOf (RUNTIME_CLASS(APowerup)))
1259 		{
1260 			if (deathmatch || ((mode != FINISH_SameHub || !(item->ItemFlags & IF_HUBPOWER))
1261 				&& !(item->ItemFlags & IF_PERSISTENTPOWER))) // Keep persistent powers in non-deathmatch games
1262 			{
1263 				item->Destroy ();
1264 			}
1265 		}
1266 		item = next;
1267 	}
1268 	if (p->ReadyWeapon != NULL &&
1269 		p->ReadyWeapon->WeaponFlags&WIF_POWERED_UP &&
1270 		p->PendingWeapon == p->ReadyWeapon->SisterWeapon)
1271 	{
1272 		// Unselect powered up weapons if the unpowered counterpart is pending
1273 		p->ReadyWeapon=p->PendingWeapon;
1274 	}
1275 	// reset invisibility to default
1276 	if (p->mo->GetDefault()->flags & MF_SHADOW)
1277 	{
1278 		p->mo->flags |= MF_SHADOW;
1279 	}
1280 	else
1281 	{
1282 		p->mo->flags &= ~MF_SHADOW;
1283 	}
1284 	p->mo->RenderStyle = p->mo->GetDefault()->RenderStyle;
1285 	p->mo->alpha = p->mo->GetDefault()->alpha;
1286 	p->extralight = 0;					// cancel gun flashes
1287 	p->fixedcolormap = NOFIXEDCOLORMAP;	// cancel ir goggles
1288 	p->fixedlightlevel = -1;
1289 	p->damagecount = 0; 				// no palette changes
1290 	p->bonuscount = 0;
1291 	p->poisoncount = 0;
1292 	p->inventorytics = 0;
1293 
1294 	if (mode != FINISH_SameHub)
1295 	{
1296 		// Take away flight and keys (and anything else with IF_INTERHUBSTRIP set)
1297 		item = p->mo->Inventory;
1298 		while (item != NULL)
1299 		{
1300 			next = item->Inventory;
1301 			if (item->InterHubAmount < 1)
1302 			{
1303 				item->Destroy ();
1304 			}
1305 			item = next;
1306 		}
1307 	}
1308 
1309 	if (mode == FINISH_NoHub && !(level.flags2 & LEVEL2_KEEPFULLINVENTORY))
1310 	{ // Reduce all owned (visible) inventory to defined maximum interhub amount
1311 		for (item = p->mo->Inventory; item != NULL; item = item->Inventory)
1312 		{
1313 			// If the player is carrying more samples of an item than allowed, reduce amount accordingly
1314 			if (item->ItemFlags & IF_INVBAR && item->Amount > item->InterHubAmount)
1315 			{
1316 				item->Amount = item->InterHubAmount;
1317 			}
1318 		}
1319 	}
1320 
1321 	// Resets player health to default if not dead.
1322 	if ((flags & CHANGELEVEL_RESETHEALTH) && p->playerstate != PST_DEAD)
1323 	{
1324 		p->health = p->mo->health = p->mo->SpawnHealth();
1325 	}
1326 
1327 	// Clears the entire inventory and gives back the defaults for starting a game
1328 	if ((flags & CHANGELEVEL_RESETINVENTORY) && p->playerstate != PST_DEAD)
1329 	{
1330 		p->mo->ClearInventory();
1331 		p->mo->GiveDefaultInventory();
1332 	}
1333 }
1334 
1335 
1336 //
1337 // G_PlayerReborn
1338 // Called after a player dies
1339 // almost everything is cleared and initialized
1340 //
G_PlayerReborn(int player)1341 void G_PlayerReborn (int player)
1342 {
1343 	player_t*	p;
1344 	int 		frags[MAXPLAYERS];
1345 	int			fragcount;	// [RH] Cumulative frags
1346 	int 		killcount;
1347 	int 		itemcount;
1348 	int 		secretcount;
1349 	int			chasecam;
1350 	BYTE		currclass;
1351 	userinfo_t  userinfo;	// [RH] Save userinfo
1352 	APlayerPawn *actor;
1353 	const PClass *cls;
1354 	FString		log;
1355 	DBot		*Bot;		//Added by MC:
1356 
1357 	p = &players[player];
1358 
1359 	memcpy (frags, p->frags, sizeof(frags));
1360 	fragcount = p->fragcount;
1361 	killcount = p->killcount;
1362 	itemcount = p->itemcount;
1363 	secretcount = p->secretcount;
1364 	currclass = p->CurrentPlayerClass;
1365 	userinfo.TransferFrom(p->userinfo);
1366 	actor = p->mo;
1367 	cls = p->cls;
1368 	log = p->LogText;
1369 	chasecam = p->cheats & CF_CHASECAM;
1370 	Bot = p->Bot;			//Added by MC:
1371 
1372 	// Reset player structure to its defaults
1373 	p->~player_t();
1374 	::new(p) player_t;
1375 
1376 	memcpy (p->frags, frags, sizeof(p->frags));
1377 	p->health = actor->health;
1378 	p->fragcount = fragcount;
1379 	p->killcount = killcount;
1380 	p->itemcount = itemcount;
1381 	p->secretcount = secretcount;
1382 	p->CurrentPlayerClass = currclass;
1383 	p->userinfo.TransferFrom(userinfo);
1384 	p->mo = actor;
1385 	p->cls = cls;
1386 	p->LogText = log;
1387 	p->cheats |= chasecam;
1388 	p->Bot = Bot;			//Added by MC:
1389 
1390 	p->oldbuttons = ~0, p->attackdown = true; p->usedown = true;	// don't do anything immediately
1391 	p->original_oldbuttons = ~0;
1392 	p->playerstate = PST_LIVE;
1393 
1394 	if (gamestate != GS_TITLELEVEL)
1395 	{
1396 		// [GRB] Give inventory specified in DECORATE
1397 		actor->GiveDefaultInventory ();
1398 		p->ReadyWeapon = p->PendingWeapon;
1399 	}
1400 
1401 	//Added by MC: Init bot structure.
1402 	if (p->Bot != NULL)
1403 	{
1404 		botskill_t skill = p->Bot->skill;
1405 		p->Bot->Clear ();
1406 		p->Bot->player = p;
1407 		p->Bot->skill = skill;
1408 	}
1409 }
1410 
1411 //
1412 // G_CheckSpot
1413 // Returns false if the player cannot be respawned
1414 // at the given mapthing spot
1415 // because something is occupying it
1416 //
1417 
G_CheckSpot(int playernum,FPlayerStart * mthing)1418 bool G_CheckSpot (int playernum, FPlayerStart *mthing)
1419 {
1420 	fixed_t x;
1421 	fixed_t y;
1422 	fixed_t z, oldz;
1423 	int i;
1424 
1425 	if (mthing->type == 0) return false;
1426 
1427 	x = mthing->x;
1428 	y = mthing->y;
1429 	z = mthing->z;
1430 
1431 	if (!(level.flags & LEVEL_USEPLAYERSTARTZ))
1432 	{
1433 		z = 0;
1434 	}
1435 	z += P_PointInSector (x, y)->floorplane.ZatPoint (x, y);
1436 
1437 	if (!players[playernum].mo)
1438 	{ // first spawn of level, before corpses
1439 		for (i = 0; i < playernum; i++)
1440 			if (players[i].mo && players[i].mo->X() == x && players[i].mo->Y() == y)
1441 				return false;
1442 		return true;
1443 	}
1444 
1445 	oldz = players[playernum].mo->Z();	// [RH] Need to save corpse's z-height
1446 	players[playernum].mo->SetZ(z);		// [RH] Checks are now full 3-D
1447 
1448 	// killough 4/2/98: fix bug where P_CheckPosition() uses a non-solid
1449 	// corpse to detect collisions with other players in DM starts
1450 	//
1451 	// Old code:
1452 	// if (!P_CheckPosition (players[playernum].mo, x, y))
1453 	//    return false;
1454 
1455 	players[playernum].mo->flags |=  MF_SOLID;
1456 	i = P_CheckPosition(players[playernum].mo, x, y);
1457 	players[playernum].mo->flags &= ~MF_SOLID;
1458 	players[playernum].mo->SetZ(oldz);	// [RH] Restore corpse's height
1459 	if (!i)
1460 		return false;
1461 
1462 	return true;
1463 }
1464 
1465 
1466 //
1467 // G_DeathMatchSpawnPlayer
1468 // Spawns a player at one of the random death match spots
1469 // called at level load and each death
1470 //
1471 
1472 // [RH] Returns the distance of the closest player to the given mapthing
PlayersRangeFromSpot(FPlayerStart * spot)1473 static fixed_t PlayersRangeFromSpot (FPlayerStart *spot)
1474 {
1475 	fixed_t closest = INT_MAX;
1476 	fixed_t distance;
1477 	int i;
1478 
1479 	for (i = 0; i < MAXPLAYERS; i++)
1480 	{
1481 		if (!playeringame[i] || !players[i].mo || players[i].health <= 0)
1482 			continue;
1483 
1484 		distance = players[i].mo->AproxDistance (spot->x, spot->y);
1485 
1486 		if (distance < closest)
1487 			closest = distance;
1488 	}
1489 
1490 	return closest;
1491 }
1492 
1493 // [RH] Select the deathmatch spawn spot farthest from everyone.
SelectFarthestDeathmatchSpot(size_t selections)1494 static FPlayerStart *SelectFarthestDeathmatchSpot (size_t selections)
1495 {
1496 	fixed_t bestdistance = 0;
1497 	FPlayerStart *bestspot = NULL;
1498 	unsigned int i;
1499 
1500 	for (i = 0; i < selections; i++)
1501 	{
1502 		fixed_t distance = PlayersRangeFromSpot (&deathmatchstarts[i]);
1503 
1504 		if (distance > bestdistance)
1505 		{
1506 			bestdistance = distance;
1507 			bestspot = &deathmatchstarts[i];
1508 		}
1509 	}
1510 
1511 	return bestspot;
1512 }
1513 
1514 // [RH] Select a deathmatch spawn spot at random (original mechanism)
SelectRandomDeathmatchSpot(int playernum,unsigned int selections)1515 static FPlayerStart *SelectRandomDeathmatchSpot (int playernum, unsigned int selections)
1516 {
1517 	unsigned int i, j;
1518 
1519 	for (j = 0; j < 20; j++)
1520 	{
1521 		i = pr_dmspawn() % selections;
1522 		if (G_CheckSpot (playernum, &deathmatchstarts[i]) )
1523 		{
1524 			return &deathmatchstarts[i];
1525 		}
1526 	}
1527 
1528 	// [RH] return a spot anyway, since we allow telefragging when a player spawns
1529 	return &deathmatchstarts[i];
1530 }
1531 
G_DeathMatchSpawnPlayer(int playernum)1532 void G_DeathMatchSpawnPlayer (int playernum)
1533 {
1534 	unsigned int selections;
1535 	FPlayerStart *spot;
1536 
1537 	selections = deathmatchstarts.Size ();
1538 	// [RH] We can get by with just 1 deathmatch start
1539 	if (selections < 1)
1540 		I_Error ("No deathmatch starts");
1541 
1542 	// At level start, none of the players have mobjs attached to them,
1543 	// so we always use the random deathmatch spawn. During the game,
1544 	// though, we use whatever dmflags specifies.
1545 	if ((dmflags & DF_SPAWN_FARTHEST) && players[playernum].mo)
1546 		spot = SelectFarthestDeathmatchSpot (selections);
1547 	else
1548 		spot = SelectRandomDeathmatchSpot (playernum, selections);
1549 
1550 	if (spot == NULL)
1551 	{ // No good spot, so the player will probably get stuck.
1552 	  // We were probably using select farthest above, and all
1553 	  // the spots were taken.
1554 		spot = G_PickPlayerStart(playernum, PPS_FORCERANDOM);
1555 		if (!G_CheckSpot(playernum, spot))
1556 		{ // This map doesn't have enough coop spots for this player
1557 		  // to use one.
1558 			spot = SelectRandomDeathmatchSpot(playernum, selections);
1559 			if (spot == NULL)
1560 			{ // We have a player 1 start, right?
1561 				spot = &playerstarts[0];
1562 				if (spot->type == 0)
1563 				{ // Fine, whatever.
1564 					spot = &deathmatchstarts[0];
1565 				}
1566 			}
1567 		}
1568 	}
1569 	AActor *mo = P_SpawnPlayer(spot, playernum);
1570 	if (mo != NULL) P_PlayerStartStomp(mo);
1571 }
1572 
1573 //
1574 // G_PickPlayerStart
1575 //
G_PickPlayerStart(int playernum,int flags)1576 FPlayerStart *G_PickPlayerStart(int playernum, int flags)
1577 {
1578 	if (AllPlayerStarts.Size() == 0) // No starts to pick
1579 	{
1580 		return NULL;
1581 	}
1582 
1583 	if ((level.flags2 & LEVEL2_RANDOMPLAYERSTARTS) || (flags & PPS_FORCERANDOM) ||
1584 		playerstarts[playernum].type == 0)
1585 	{
1586 		if (!(flags & PPS_NOBLOCKINGCHECK))
1587 		{
1588 			TArray<FPlayerStart *> good_starts;
1589 			unsigned int i;
1590 
1591 			// Find all unblocked player starts.
1592 			for (i = 0; i < AllPlayerStarts.Size(); ++i)
1593 			{
1594 				if (G_CheckSpot(playernum, &AllPlayerStarts[i]))
1595 				{
1596 					good_starts.Push(&AllPlayerStarts[i]);
1597 				}
1598 			}
1599 			if (good_starts.Size() > 0)
1600 			{ // Pick an open spot at random.
1601 				return good_starts[pr_pspawn(good_starts.Size())];
1602 			}
1603 		}
1604 		// Pick a spot at random, whether it's open or not.
1605 		return &AllPlayerStarts[pr_pspawn(AllPlayerStarts.Size())];
1606 	}
1607 	return &playerstarts[playernum];
1608 }
1609 
1610 //
1611 // G_QueueBody
1612 //
G_QueueBody(AActor * body)1613 static void G_QueueBody (AActor *body)
1614 {
1615 	// flush an old corpse if needed
1616 	int modslot = bodyqueslot%BODYQUESIZE;
1617 
1618 	if (bodyqueslot >= BODYQUESIZE && bodyque[modslot] != NULL)
1619 	{
1620 		bodyque[modslot]->Destroy ();
1621 	}
1622 	bodyque[modslot] = body;
1623 
1624 	// Copy the player's translation, so that if they change their color later, only
1625 	// their current body will change and not all their old corpses.
1626 	if (GetTranslationType(body->Translation) == TRANSLATION_Players ||
1627 		GetTranslationType(body->Translation) == TRANSLATION_PlayersExtra)
1628 	{
1629 		*translationtables[TRANSLATION_PlayerCorpses][modslot] = *TranslationToTable(body->Translation);
1630 		body->Translation = TRANSLATION(TRANSLATION_PlayerCorpses,modslot);
1631 		translationtables[TRANSLATION_PlayerCorpses][modslot]->UpdateNative();
1632 	}
1633 
1634 	const int skinidx = body->player->userinfo.GetSkin();
1635 
1636 	if (0 != skinidx && !(body->flags4 & MF4_NOSKIN))
1637 	{
1638 		// Apply skin's scale to actor's scale, it will be lost otherwise
1639 		const AActor *const defaultActor = body->GetDefault();
1640 		const FPlayerSkin &skin = skins[skinidx];
1641 
1642 		body->scaleX = Scale(body->scaleX, skin.ScaleX, defaultActor->scaleX);
1643 		body->scaleY = Scale(body->scaleY, skin.ScaleY, defaultActor->scaleY);
1644 	}
1645 
1646 	bodyqueslot++;
1647 }
1648 
1649 //
1650 // G_DoReborn
1651 //
G_DoReborn(int playernum,bool freshbot)1652 void G_DoReborn (int playernum, bool freshbot)
1653 {
1654 	if (!multiplayer && !(level.flags2 & LEVEL2_ALLOWRESPAWN))
1655 	{
1656 		if (BackupSaveName.Len() > 0 && FileExists (BackupSaveName.GetChars()))
1657 		{ // Load game from the last point it was saved
1658 			savename = BackupSaveName;
1659 			gameaction = ga_autoloadgame;
1660 		}
1661 		else
1662 		{ // Reload the level from scratch
1663 			bool indemo = demoplayback;
1664 			BackupSaveName = "";
1665 			G_InitNew (level.MapName, false);
1666 			demoplayback = indemo;
1667 //			gameaction = ga_loadlevel;
1668 		}
1669 	}
1670 	else
1671 	{
1672 		// respawn at the start
1673 		// first disassociate the corpse
1674 		if (players[playernum].mo)
1675 		{
1676 			G_QueueBody (players[playernum].mo);
1677 			players[playernum].mo->player = NULL;
1678 		}
1679 
1680 		// spawn at random spot if in deathmatch
1681 		if (deathmatch)
1682 		{
1683 			G_DeathMatchSpawnPlayer (playernum);
1684 			return;
1685 		}
1686 
1687 		if (!(level.flags2 & LEVEL2_RANDOMPLAYERSTARTS) &&
1688 			playerstarts[playernum].type != 0 &&
1689 			G_CheckSpot (playernum, &playerstarts[playernum]))
1690 		{
1691 			AActor *mo = P_SpawnPlayer(&playerstarts[playernum], playernum);
1692 			if (mo != NULL) P_PlayerStartStomp(mo, true);
1693 		}
1694 		else
1695 		{ // try to spawn at any random player's spot
1696 			FPlayerStart *start = G_PickPlayerStart(playernum, PPS_FORCERANDOM);
1697 			AActor *mo = P_SpawnPlayer(start, playernum);
1698 			if (mo != NULL) P_PlayerStartStomp(mo, true);
1699 		}
1700 	}
1701 }
1702 
1703 //
1704 // G_DoReborn
1705 //
G_DoPlayerPop(int playernum)1706 void G_DoPlayerPop(int playernum)
1707 {
1708 	playeringame[playernum] = false;
1709 
1710 	if (deathmatch)
1711 	{
1712 		Printf("%s left the game with %d frags\n",
1713 			players[playernum].userinfo.GetName(),
1714 			players[playernum].fragcount);
1715 	}
1716 	else
1717 	{
1718 		Printf("%s left the game\n", players[playernum].userinfo.GetName());
1719 	}
1720 
1721 	// [RH] Revert each player to their own view if spying through the player who left
1722 	for (int ii = 0; ii < MAXPLAYERS; ++ii)
1723 	{
1724 		if (playeringame[ii] && players[ii].camera == players[playernum].mo)
1725 		{
1726 			players[ii].camera = players[ii].mo;
1727 			if (ii == consoleplayer && StatusBar != NULL)
1728 			{
1729 				StatusBar->AttachToPlayer(&players[ii]);
1730 			}
1731 		}
1732 	}
1733 
1734 	// [RH] Make the player disappear
1735 	FBehavior::StaticStopMyScripts(players[playernum].mo);
1736 	// [RH] Let the scripts know the player left
1737 	FBehavior::StaticStartTypedScripts(SCRIPT_Disconnect, players[playernum].mo, true, playernum, true);
1738 	if (players[playernum].mo != NULL)
1739 	{
1740 		P_DisconnectEffect(players[playernum].mo);
1741 		players[playernum].mo->player = NULL;
1742 		players[playernum].mo->Destroy();
1743 		if (!(players[playernum].mo->ObjectFlags & OF_EuthanizeMe))
1744 		{ // We just destroyed a morphed player, so now the original player
1745 			// has taken their place. Destroy that one too.
1746 			players[playernum].mo->Destroy();
1747 		}
1748 		players[playernum].mo = NULL;
1749 		players[playernum].camera = NULL;
1750 	}
1751 }
1752 
G_ScreenShot(char * filename)1753 void G_ScreenShot (char *filename)
1754 {
1755 	shotfile = filename;
1756 	gameaction = ga_screenshot;
1757 }
1758 
1759 
1760 
1761 //
1762 // G_InitFromSavegame
1763 // Can be called by the startup code or the menu task.
1764 //
G_LoadGame(const char * name,bool hidecon)1765 void G_LoadGame (const char* name, bool hidecon)
1766 {
1767 	if (name != NULL)
1768 	{
1769 		savename = name;
1770 		gameaction = !hidecon ? ga_loadgame : ga_loadgamehidecon;
1771 	}
1772 }
1773 
CheckSingleWad(char * name,bool & printRequires,bool printwarn)1774 static bool CheckSingleWad (char *name, bool &printRequires, bool printwarn)
1775 {
1776 	if (name == NULL)
1777 	{
1778 		return true;
1779 	}
1780 	if (Wads.CheckIfWadLoaded (name) < 0)
1781 	{
1782 		if (printwarn)
1783 		{
1784 			if (!printRequires)
1785 			{
1786 				Printf ("This savegame needs these wads:\n%s", name);
1787 			}
1788 			else
1789 			{
1790 				Printf (", %s", name);
1791 			}
1792 		}
1793 		printRequires = true;
1794 		delete[] name;
1795 		return false;
1796 	}
1797 	delete[] name;
1798 	return true;
1799 }
1800 
1801 // Return false if not all the needed wads have been loaded.
G_CheckSaveGameWads(PNGHandle * png,bool printwarn)1802 bool G_CheckSaveGameWads (PNGHandle *png, bool printwarn)
1803 {
1804 	char *text;
1805 	bool printRequires = false;
1806 
1807 	text = M_GetPNGText (png, "Game WAD");
1808 	CheckSingleWad (text, printRequires, printwarn);
1809 	text = M_GetPNGText (png, "Map WAD");
1810 	CheckSingleWad (text, printRequires, printwarn);
1811 
1812 	if (printRequires)
1813 	{
1814 		if (printwarn)
1815 		{
1816 			Printf ("\n");
1817 		}
1818 		return false;
1819 	}
1820 
1821 	return true;
1822 }
1823 
1824 
G_DoLoadGame()1825 void G_DoLoadGame ()
1826 {
1827 	char sigcheck[20];
1828 	char *text = NULL;
1829 	char *map;
1830 	bool hidecon;
1831 
1832 	if (gameaction != ga_autoloadgame)
1833 	{
1834 		demoplayback = false;
1835 	}
1836 	hidecon = gameaction == ga_loadgamehidecon;
1837 	gameaction = ga_nothing;
1838 
1839 	FILE *stdfile = fopen (savename.GetChars(), "rb");
1840 	if (stdfile == NULL)
1841 	{
1842 		Printf ("Could not read savegame '%s'\n", savename.GetChars());
1843 		return;
1844 	}
1845 
1846 	PNGHandle *png = M_VerifyPNG (stdfile);
1847 	if (png == NULL)
1848 	{
1849 		fclose (stdfile);
1850 		Printf ("'%s' is not a valid (PNG) savegame\n", savename.GetChars());
1851 		return;
1852 	}
1853 
1854 	SaveVersion = 0;
1855 
1856 	// Check whether this savegame actually has been created by a compatible engine.
1857 	// Since there are ZDoom derivates using the exact same savegame format but
1858 	// with mutual incompatibilities this check simplifies things significantly.
1859 	char *engine = M_GetPNGText (png, "Engine");
1860 	if (engine == NULL || 0 != strcmp (engine, GAMESIG))
1861 	{
1862 		// Make a special case for the message printed for old savegames that don't
1863 		// have this information.
1864 		if (engine == NULL)
1865 		{
1866 			Printf ("Savegame is from an incompatible version\n");
1867 		}
1868 		else
1869 		{
1870 			Printf ("Savegame is from another ZDoom-based engine: %s\n", engine);
1871 			delete[] engine;
1872 		}
1873 		delete png;
1874 		fclose (stdfile);
1875 		return;
1876 	}
1877 	if (engine != NULL)
1878 	{
1879 		delete[] engine;
1880 	}
1881 
1882 	SaveVersion = 0;
1883 	if (!M_GetPNGText (png, "ZDoom Save Version", sigcheck, 20) ||
1884 		0 != strncmp (sigcheck, SAVESIG, 9) ||		// ZDOOMSAVE is the first 9 chars
1885 		(SaveVersion = atoi (sigcheck+9)) < MINSAVEVER)
1886 	{
1887 		delete png;
1888 		fclose (stdfile);
1889 		Printf ("Savegame is from an incompatible version");
1890 		if (SaveVersion != 0)
1891 		{
1892 			Printf(": %d (%d is the oldest supported)", SaveVersion, MINSAVEVER);
1893 		}
1894 		Printf("\n");
1895 		return;
1896 	}
1897 
1898 	if (!G_CheckSaveGameWads (png, true))
1899 	{
1900 		fclose (stdfile);
1901 		return;
1902 	}
1903 
1904 	map = M_GetPNGText (png, "Current Map");
1905 	if (map == NULL)
1906 	{
1907 		Printf ("Savegame is missing the current map\n");
1908 		fclose (stdfile);
1909 		return;
1910 	}
1911 
1912 	// Now that it looks like we can load this save, hide the fullscreen console if it was up
1913 	// when the game was selected from the menu.
1914 	if (hidecon && gamestate == GS_FULLCONSOLE)
1915 	{
1916 		gamestate = GS_HIDECONSOLE;
1917 	}
1918 
1919 	// Read intermission data for hubs
1920 	G_ReadHubInfo(png);
1921 
1922 	bglobal.RemoveAllBots (true);
1923 
1924 	text = M_GetPNGText (png, "Important CVARs");
1925 	if (text != NULL)
1926 	{
1927 		BYTE *vars_p = (BYTE *)text;
1928 		C_ReadCVars (&vars_p);
1929 		delete[] text;
1930 		if (SaveVersion <= 4509)
1931 		{
1932 			// account for the flag shuffling for making freelook a 3-state option
1933 			INTBOOL flag = dmflags & DF_YES_FREELOOK;
1934 			dmflags = dmflags & ~DF_YES_FREELOOK;
1935 			if (flag) dmflags2 = dmflags2 | DF2_RESPAWN_SUPER;
1936 		}
1937 	}
1938 
1939 	// dearchive all the modifications
1940 	if (M_FindPNGChunk (png, MAKE_ID('p','t','I','c')) == 8)
1941 	{
1942 		DWORD time[2];
1943 		fread (&time, 8, 1, stdfile);
1944 		time[0] = BigLong((unsigned int)time[0]);
1945 		time[1] = BigLong((unsigned int)time[1]);
1946 		level.time = Scale (time[1], TICRATE, time[0]);
1947 	}
1948 	else
1949 	{ // No ptIc chunk so we don't know how long the user was playing
1950 		level.time = 0;
1951 	}
1952 
1953 	G_ReadSnapshots (png);
1954 
1955 	// load a base level
1956 	savegamerestore = true;		// Use the player actors in the savegame
1957 	bool demoplaybacksave = demoplayback;
1958 	G_InitNew (map, false);
1959 	demoplayback = demoplaybacksave;
1960 	delete[] map;
1961 	savegamerestore = false;
1962 
1963 	STAT_Read(png);
1964 	FRandom::StaticReadRNGState(png);
1965 	P_ReadACSDefereds(png);
1966 	P_ReadACSVars(png);
1967 
1968 	NextSkill = -1;
1969 	if (M_FindPNGChunk (png, MAKE_ID('s','n','X','t')) == 1)
1970 	{
1971 		BYTE next;
1972 		fread (&next, 1, 1, stdfile);
1973 		NextSkill = next;
1974 	}
1975 
1976 	if (level.info->snapshot != NULL)
1977 	{
1978 		delete level.info->snapshot;
1979 		level.info->snapshot = NULL;
1980 	}
1981 
1982 	BackupSaveName = savename;
1983 
1984 	delete png;
1985 	fclose (stdfile);
1986 
1987 	// At this point, the GC threshold is likely a lot higher than the
1988 	// amount of memory in use, so bring it down now by starting a
1989 	// collection.
1990 	GC::StartCollection();
1991 }
1992 
1993 
1994 //
1995 // G_SaveGame
1996 // Called by the menu task.
1997 // Description is a 24 byte text string
1998 //
G_SaveGame(const char * filename,const char * description)1999 void G_SaveGame (const char *filename, const char *description)
2000 {
2001 	if (sendsave || gameaction == ga_savegame)
2002 	{
2003 		Printf ("A game save is still pending.\n");
2004 		return;
2005 	}
2006     else if (!usergame)
2007 	{
2008         Printf ("not in a saveable game\n");
2009     }
2010     else if (gamestate != GS_LEVEL)
2011 	{
2012         Printf ("not in a level\n");
2013     }
2014     else if (players[consoleplayer].health <= 0 && !multiplayer)
2015     {
2016         Printf ("player is dead in a single-player game\n");
2017     }
2018 	else
2019 	{
2020 		savegamefile = filename;
2021 		strncpy (savedescription, description, sizeof(savedescription)-1);
2022 		savedescription[sizeof(savedescription)-1] = '\0';
2023 		sendsave = true;
2024 	}
2025 }
2026 
G_BuildSaveName(const char * prefix,int slot)2027 FString G_BuildSaveName (const char *prefix, int slot)
2028 {
2029 	FString name;
2030 	FString leader;
2031 	const char *slash = "";
2032 
2033 	leader = Args->CheckValue ("-savedir");
2034 	if (leader.IsEmpty())
2035 	{
2036 		leader = save_dir;
2037 		if (leader.IsEmpty())
2038 		{
2039 			leader = M_GetSavegamesPath();
2040 		}
2041 	}
2042 	size_t len = leader.Len();
2043 	if (leader[0] != '\0' && leader[len-1] != '\\' && leader[len-1] != '/')
2044 	{
2045 		slash = "/";
2046 	}
2047 	name << leader << slash;
2048 	name = NicePath(name);
2049 	CreatePath(name);
2050 	name << prefix;
2051 	if (slot >= 0)
2052 	{
2053 		name.AppendFormat("%d.zds", slot);
2054 	}
2055 	return name;
2056 }
2057 
2058 CVAR (Int, autosavenum, 0, CVAR_NOSET|CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
2059 static int nextautosave = -1;
2060 CVAR (Int, disableautosave, 0, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
2061 CUSTOM_CVAR (Int, autosavecount, 4, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
2062 {
2063 	if (self < 0)
2064 		self = 0;
2065 	if (self > 20)
2066 		self = 20;
2067 }
2068 
2069 extern void P_CalcHeight (player_t *);
2070 
G_DoAutoSave()2071 void G_DoAutoSave ()
2072 {
2073 	char description[SAVESTRINGSIZE];
2074 	FString file;
2075 	// Keep up to four autosaves at a time
2076 	UCVarValue num;
2077 	const char *readableTime;
2078 	int count = autosavecount != 0 ? autosavecount : 1;
2079 
2080 	if (nextautosave == -1)
2081 	{
2082 		nextautosave = (autosavenum + 1) % count;
2083 	}
2084 
2085 	num.Int = nextautosave;
2086 	autosavenum.ForceSet (num, CVAR_Int);
2087 
2088 	file = G_BuildSaveName ("auto", nextautosave);
2089 
2090 	if (!(level.flags2 & LEVEL2_NOAUTOSAVEHINT))
2091 	{
2092 		nextautosave = (nextautosave + 1) % count;
2093 	}
2094 	else
2095 	{
2096 		// This flag can only be used once per level
2097 		level.flags2 &= ~LEVEL2_NOAUTOSAVEHINT;
2098 	}
2099 
2100 	readableTime = myasctime ();
2101 	strcpy (description, "Autosave ");
2102 	strncpy (description+9, readableTime+4, 12);
2103 	description[9+12] = 0;
2104 
2105 	G_DoSaveGame (false, file, description);
2106 }
2107 
2108 
PutSaveWads(FILE * file)2109 static void PutSaveWads (FILE *file)
2110 {
2111 	const char *name;
2112 
2113 	// Name of IWAD
2114 	name = Wads.GetWadName (FWadCollection::IWAD_FILENUM);
2115 	M_AppendPNGText (file, "Game WAD", name);
2116 
2117 	// Name of wad the map resides in
2118 	if (Wads.GetLumpFile (level.lumpnum) > 1)
2119 	{
2120 		name = Wads.GetWadName (Wads.GetLumpFile (level.lumpnum));
2121 		M_AppendPNGText (file, "Map WAD", name);
2122 	}
2123 }
2124 
PutSaveComment(FILE * file)2125 static void PutSaveComment (FILE *file)
2126 {
2127 	char comment[256];
2128 	const char *readableTime;
2129 	WORD len;
2130 	int levelTime;
2131 
2132 	// Get the current date and time
2133 	readableTime = myasctime ();
2134 
2135 	strncpy (comment, readableTime, 10);
2136 	strncpy (comment+10, readableTime+19, 5);
2137 	strncpy (comment+15, readableTime+10, 9);
2138 	comment[24] = 0;
2139 
2140 	M_AppendPNGText (file, "Creation Time", comment);
2141 
2142 	// Get level name
2143 	//strcpy (comment, level.level_name);
2144 	mysnprintf(comment, countof(comment), "%s - %s", level.MapName.GetChars(), level.LevelName.GetChars());
2145 	len = (WORD)strlen (comment);
2146 	comment[len] = '\n';
2147 
2148 	// Append elapsed time
2149 	levelTime = level.time / TICRATE;
2150 	mysnprintf (comment + len + 1, countof(comment) - len - 1, "time: %02d:%02d:%02d",
2151 		levelTime/3600, (levelTime%3600)/60, levelTime%60);
2152 	comment[len+16] = 0;
2153 
2154 	// Write out the comment
2155 	M_AppendPNGText (file, "Comment", comment);
2156 }
2157 
PutSavePic(FILE * file,int width,int height)2158 static void PutSavePic (FILE *file, int width, int height)
2159 {
2160 	if (width <= 0 || height <= 0 || !storesavepic)
2161 	{
2162 		M_CreateDummyPNG (file);
2163 	}
2164 	else
2165 	{
2166 		Renderer->WriteSavePic(&players[consoleplayer], file, width, height);
2167 	}
2168 }
2169 
G_DoSaveGame(bool okForQuicksave,FString filename,const char * description)2170 void G_DoSaveGame (bool okForQuicksave, FString filename, const char *description)
2171 {
2172 	char buf[100];
2173 
2174 	// Do not even try, if we're not in a level. (Can happen after
2175 	// a demo finishes playback.)
2176 	if (lines == NULL || sectors == NULL || gamestate != GS_LEVEL)
2177 	{
2178 		return;
2179 	}
2180 
2181 	if (demoplayback)
2182 	{
2183 		filename = G_BuildSaveName ("demosave.zds", -1);
2184 	}
2185 
2186 	if (cl_waitforsave)
2187 		I_FreezeTime(true);
2188 
2189 	insave = true;
2190 	G_SnapshotLevel ();
2191 
2192 	FILE *stdfile = fopen (filename, "wb");
2193 
2194 	if (stdfile == NULL)
2195 	{
2196 		Printf ("Could not create savegame '%s'\n", filename.GetChars());
2197 		insave = false;
2198 		I_FreezeTime(false);
2199 		return;
2200 	}
2201 
2202 	SaveVersion = SAVEVER;
2203 	PutSavePic (stdfile, SAVEPICWIDTH, SAVEPICHEIGHT);
2204 	mysnprintf(buf, countof(buf), GAMENAME " %s", GetVersionString());
2205 	M_AppendPNGText (stdfile, "Software", buf);
2206 	M_AppendPNGText (stdfile, "Engine", GAMESIG);
2207 	M_AppendPNGText (stdfile, "ZDoom Save Version", SAVESIG);
2208 	M_AppendPNGText (stdfile, "Title", description);
2209 	M_AppendPNGText (stdfile, "Current Map", level.MapName);
2210 	PutSaveWads (stdfile);
2211 	PutSaveComment (stdfile);
2212 
2213 	// Intermission stats for hubs
2214 	G_WriteHubInfo(stdfile);
2215 
2216 	{
2217 		FString vars = C_GetMassCVarString(CVAR_SERVERINFO);
2218 		M_AppendPNGText (stdfile, "Important CVARs", vars.GetChars());
2219 	}
2220 
2221 	if (level.time != 0 || level.maptime != 0)
2222 	{
2223 		DWORD time[2] = { DWORD(BigLong(TICRATE)), DWORD(BigLong(level.time)) };
2224 		M_AppendPNGChunk (stdfile, MAKE_ID('p','t','I','c'), (BYTE *)&time, 8);
2225 	}
2226 
2227 	G_WriteSnapshots (stdfile);
2228 	STAT_Write(stdfile);
2229 	FRandom::StaticWriteRNGState (stdfile);
2230 	P_WriteACSDefereds (stdfile);
2231 
2232 	P_WriteACSVars(stdfile);
2233 
2234 	if (NextSkill != -1)
2235 	{
2236 		BYTE next = NextSkill;
2237 		M_AppendPNGChunk (stdfile, MAKE_ID('s','n','X','t'), &next, 1);
2238 	}
2239 
2240 	M_FinishPNG (stdfile);
2241 	fclose (stdfile);
2242 
2243 	M_NotifyNewSave (filename.GetChars(), description, okForQuicksave);
2244 
2245 	// Check whether the file is ok.
2246 	bool success = false;
2247 	stdfile = fopen (filename.GetChars(), "rb");
2248 	if (stdfile != NULL)
2249 	{
2250 		PNGHandle *pngh = M_VerifyPNG(stdfile);
2251 		if (pngh != NULL)
2252 		{
2253 			success = true;
2254 			delete pngh;
2255 		}
2256 		fclose(stdfile);
2257 	}
2258 	if (success)
2259 	{
2260 		if (longsavemessages) Printf ("%s (%s)\n", GStrings("GGSAVED"), filename.GetChars());
2261 		else Printf ("%s\n", GStrings("GGSAVED"));
2262 	}
2263 	else Printf(PRINT_HIGH, "Save failed\n");
2264 
2265 	BackupSaveName = filename;
2266 
2267 	// We don't need the snapshot any longer.
2268 	if (level.info->snapshot != NULL)
2269 	{
2270 		delete level.info->snapshot;
2271 		level.info->snapshot = NULL;
2272 	}
2273 
2274 	insave = false;
2275 	I_FreezeTime(false);
2276 }
2277 
2278 
2279 
2280 
2281 //
2282 // DEMO RECORDING
2283 //
2284 
G_ReadDemoTiccmd(ticcmd_t * cmd,int player)2285 void G_ReadDemoTiccmd (ticcmd_t *cmd, int player)
2286 {
2287 	int id = DEM_BAD;
2288 
2289 	while (id != DEM_USERCMD && id != DEM_EMPTYUSERCMD)
2290 	{
2291 		if (!demorecording && demo_p >= zdembodyend)
2292 		{
2293 			// nothing left in the BODY chunk, so end playback.
2294 			G_CheckDemoStatus ();
2295 			break;
2296 		}
2297 
2298 		id = ReadByte (&demo_p);
2299 
2300 		switch (id)
2301 		{
2302 		case DEM_STOP:
2303 			// end of demo stream
2304 			G_CheckDemoStatus ();
2305 			break;
2306 
2307 		case DEM_USERCMD:
2308 			UnpackUserCmd (&cmd->ucmd, &cmd->ucmd, &demo_p);
2309 			break;
2310 
2311 		case DEM_EMPTYUSERCMD:
2312 			// leave cmd->ucmd unchanged
2313 			break;
2314 
2315 		case DEM_DROPPLAYER:
2316 			{
2317 				BYTE i = ReadByte (&demo_p);
2318 				if (i < MAXPLAYERS)
2319 				{
2320 					playeringame[i] = false;
2321 				}
2322 			}
2323 			break;
2324 
2325 		default:
2326 			Net_DoCommand (id, &demo_p, player);
2327 			break;
2328 		}
2329 	}
2330 }
2331 
2332 bool stoprecording;
2333 
CCMD(stop)2334 CCMD (stop)
2335 {
2336 	stoprecording = true;
2337 }
2338 
2339 extern BYTE *lenspot;
2340 
G_WriteDemoTiccmd(ticcmd_t * cmd,int player,int buf)2341 void G_WriteDemoTiccmd (ticcmd_t *cmd, int player, int buf)
2342 {
2343 	BYTE *specdata;
2344 	int speclen;
2345 
2346 	if (stoprecording)
2347 	{ // use "stop" console command to end demo recording
2348 		G_CheckDemoStatus ();
2349 		if (!netgame)
2350 		{
2351 			gameaction = ga_fullconsole;
2352 		}
2353 		return;
2354 	}
2355 
2356 	// [RH] Write any special "ticcmds" for this player to the demo
2357 	if ((specdata = NetSpecs[player][buf].GetData (&speclen)) && gametic % ticdup == 0)
2358 	{
2359 		memcpy (demo_p, specdata, speclen);
2360 		demo_p += speclen;
2361 		NetSpecs[player][buf].SetData (NULL, 0);
2362 	}
2363 
2364 	// [RH] Now write out a "normal" ticcmd.
2365 	WriteUserCmdMessage (&cmd->ucmd, &players[player].cmd.ucmd, &demo_p);
2366 
2367 	// [RH] Bigger safety margin
2368 	if (demo_p > demobuffer + maxdemosize - 64)
2369 	{
2370 		ptrdiff_t pos = demo_p - demobuffer;
2371 		ptrdiff_t spot = lenspot - demobuffer;
2372 		ptrdiff_t comp = democompspot - demobuffer;
2373 		ptrdiff_t body = demobodyspot - demobuffer;
2374 		// [RH] Allocate more space for the demo
2375 		maxdemosize += 0x20000;
2376 		demobuffer = (BYTE *)M_Realloc (demobuffer, maxdemosize);
2377 		demo_p = demobuffer + pos;
2378 		lenspot = demobuffer + spot;
2379 		democompspot = demobuffer + comp;
2380 		demobodyspot = demobuffer + body;
2381 	}
2382 }
2383 
2384 
2385 
2386 //
2387 // G_RecordDemo
2388 //
G_RecordDemo(const char * name)2389 void G_RecordDemo (const char* name)
2390 {
2391 	usergame = false;
2392 	demoname = name;
2393 	FixPathSeperator (demoname);
2394 	DefaultExtension (demoname, ".lmp");
2395 	maxdemosize = 0x20000;
2396 	demobuffer = (BYTE *)M_Malloc (maxdemosize);
2397 	demorecording = true;
2398 }
2399 
2400 
2401 // [RH] Demos are now saved as IFF FORMs. I've also removed support
2402 //		for earlier ZDEMs since I didn't want to bother supporting
2403 //		something that probably wasn't used much (if at all).
2404 
G_BeginRecording(const char * startmap)2405 void G_BeginRecording (const char *startmap)
2406 {
2407 	int i;
2408 
2409 	if (startmap == NULL)
2410 	{
2411 		startmap = level.MapName;
2412 	}
2413 	demo_p = demobuffer;
2414 
2415 	WriteLong (FORM_ID, &demo_p);			// Write FORM ID
2416 	demo_p += 4;							// Leave space for len
2417 	WriteLong (ZDEM_ID, &demo_p);			// Write ZDEM ID
2418 
2419 	// Write header chunk
2420 	StartChunk (ZDHD_ID, &demo_p);
2421 	WriteWord (DEMOGAMEVERSION, &demo_p);	// Write ZDoom version
2422 	*demo_p++ = 2;							// Write minimum version needed to use this demo.
2423 	*demo_p++ = 3;							// (Useful?)
2424 
2425 	strcpy((char*)demo_p, startmap);		// Write name of map demo was recorded on.
2426 	demo_p += strlen(startmap) + 1;
2427 	WriteLong(rngseed, &demo_p);			// Write RNG seed
2428 	*demo_p++ = consoleplayer;
2429 	FinishChunk (&demo_p);
2430 
2431 	// Write player info chunks
2432 	for (i = 0; i < MAXPLAYERS; i++)
2433 	{
2434 		if (playeringame[i])
2435 		{
2436 			StartChunk (UINF_ID, &demo_p);
2437 			WriteByte ((BYTE)i, &demo_p);
2438 			D_WriteUserInfoStrings (i, &demo_p);
2439 			FinishChunk (&demo_p);
2440 		}
2441 	}
2442 
2443 	// It is possible to start a "multiplayer" game with only one player,
2444 	// so checking the number of players when playing back the demo is not
2445 	// enough.
2446 	if (multiplayer)
2447 	{
2448 		StartChunk (NETD_ID, &demo_p);
2449 		FinishChunk (&demo_p);
2450 	}
2451 
2452 	// Write cvars chunk
2453 	StartChunk (VARS_ID, &demo_p);
2454 	C_WriteCVars (&demo_p, CVAR_SERVERINFO|CVAR_DEMOSAVE);
2455 	FinishChunk (&demo_p);
2456 
2457 	// Write weapon ordering chunk
2458 	StartChunk (WEAP_ID, &demo_p);
2459 	P_WriteDemoWeaponsChunk(&demo_p);
2460 	FinishChunk (&demo_p);
2461 
2462 	// Indicate body is compressed
2463 	StartChunk (COMP_ID, &demo_p);
2464 	democompspot = demo_p;
2465 	WriteLong (0, &demo_p);
2466 	FinishChunk (&demo_p);
2467 
2468 	// Begin BODY chunk
2469 	StartChunk (BODY_ID, &demo_p);
2470 	demobodyspot = demo_p;
2471 }
2472 
2473 
2474 //
2475 // G_PlayDemo
2476 //
2477 
2478 FString defdemoname;
2479 
G_DeferedPlayDemo(const char * name)2480 void G_DeferedPlayDemo (const char *name)
2481 {
2482 	defdemoname = name;
2483 	gameaction = (gameaction == ga_loadgame) ? ga_loadgameplaydemo : ga_playdemo;
2484 }
2485 
CCMD(playdemo)2486 CCMD (playdemo)
2487 {
2488 	if (netgame)
2489 	{
2490 		Printf("End your current netgame first!");
2491 		return;
2492 	}
2493 	if (demorecording)
2494 	{
2495 		Printf("End your current demo first!");
2496 		return;
2497 	}
2498 	if (argv.argc() > 1)
2499 	{
2500 		G_DeferedPlayDemo (argv[1]);
2501 		singledemo = true;
2502 	}
2503 }
2504 
CCMD(timedemo)2505 CCMD (timedemo)
2506 {
2507 	if (argv.argc() > 1)
2508 	{
2509 		G_TimeDemo (argv[1]);
2510 		singledemo = true;
2511 	}
2512 }
2513 
2514 // [RH] Process all the information in a FORM ZDEM
2515 //		until a BODY chunk is entered.
G_ProcessIFFDemo(FString & mapname)2516 bool G_ProcessIFFDemo (FString &mapname)
2517 {
2518 	bool headerHit = false;
2519 	bool bodyHit = false;
2520 	int numPlayers = 0;
2521 	int id, len, i;
2522 	uLong uncompSize = 0;
2523 	BYTE *nextchunk;
2524 
2525 	demoplayback = true;
2526 
2527 	for (i = 0; i < MAXPLAYERS; i++)
2528 		playeringame[i] = 0;
2529 
2530 	len = ReadLong (&demo_p);
2531 	zdemformend = demo_p + len + (len & 1);
2532 
2533 	// Check to make sure this is a ZDEM chunk file.
2534 	// TODO: Support multiple FORM ZDEMs in a CAT. Might be useful.
2535 
2536 	id = ReadLong (&demo_p);
2537 	if (id != ZDEM_ID)
2538 	{
2539 		Printf ("Not a " GAMENAME " demo file!\n");
2540 		return true;
2541 	}
2542 
2543 	// Process all chunks until a BODY chunk is encountered.
2544 
2545 	while (demo_p < zdemformend && !bodyHit)
2546 	{
2547 		id = ReadLong (&demo_p);
2548 		len = ReadLong (&demo_p);
2549 		nextchunk = demo_p + len + (len & 1);
2550 		if (nextchunk > zdemformend)
2551 		{
2552 			Printf ("Demo is mangled!\n");
2553 			return true;
2554 		}
2555 
2556 		switch (id)
2557 		{
2558 		case ZDHD_ID:
2559 			headerHit = true;
2560 
2561 			demover = ReadWord (&demo_p);	// ZDoom version demo was created with
2562 			if (demover < MINDEMOVERSION)
2563 			{
2564 				Printf ("Demo requires an older version of " GAMENAME "!\n");
2565 				//return true;
2566 			}
2567 			if (ReadWord (&demo_p) > DEMOGAMEVERSION)	// Minimum ZDoom version
2568 			{
2569 				Printf ("Demo requires a newer version of " GAMENAME "!\n");
2570 				return true;
2571 			}
2572 			if (demover >= 0x21a)
2573 			{
2574 				mapname = (char*)demo_p;
2575 				demo_p += mapname.Len() + 1;
2576 			}
2577 			else
2578 			{
2579 				mapname = FString((char*)demo_p, 8);
2580 				demo_p += 8;
2581 			}
2582 			rngseed = ReadLong (&demo_p);
2583 			// Only reset the RNG if this demo is not in conjunction with a savegame.
2584 			if (mapname[0] != 0)
2585 			{
2586 				FRandom::StaticClearRandom ();
2587 			}
2588 			consoleplayer = *demo_p++;
2589 			break;
2590 
2591 		case VARS_ID:
2592 			C_ReadCVars (&demo_p);
2593 			break;
2594 
2595 		case UINF_ID:
2596 			i = ReadByte (&demo_p);
2597 			if (!playeringame[i])
2598 			{
2599 				playeringame[i] = 1;
2600 				numPlayers++;
2601 			}
2602 			D_ReadUserInfoStrings (i, &demo_p, false);
2603 			break;
2604 
2605 		case NETD_ID:
2606 			multiplayer = true;
2607 			break;
2608 
2609 		case WEAP_ID:
2610 			P_ReadDemoWeaponsChunk(&demo_p);
2611 			break;
2612 
2613 		case BODY_ID:
2614 			bodyHit = true;
2615 			zdembodyend = demo_p + len;
2616 			break;
2617 
2618 		case COMP_ID:
2619 			uncompSize = ReadLong (&demo_p);
2620 			break;
2621 		}
2622 
2623 		if (!bodyHit)
2624 			demo_p = nextchunk;
2625 	}
2626 
2627 	if (!headerHit)
2628 	{
2629 		Printf ("Demo has no header!\n");
2630 		return true;
2631 	}
2632 
2633 	if (!numPlayers)
2634 	{
2635 		Printf ("Demo has no players!\n");
2636 		return true;
2637 	}
2638 
2639 	if (!bodyHit)
2640 	{
2641 		zdembodyend = NULL;
2642 		Printf ("Demo has no BODY chunk!\n");
2643 		return true;
2644 	}
2645 
2646 	if (numPlayers > 1)
2647 		multiplayer = netgame = true;
2648 
2649 	if (uncompSize > 0)
2650 	{
2651 		BYTE *uncompressed = (BYTE*)M_Malloc(uncompSize);
2652 		int r = uncompress (uncompressed, &uncompSize, demo_p, uLong(zdembodyend - demo_p));
2653 		if (r != Z_OK)
2654 		{
2655 			Printf ("Could not decompress demo! %s\n", M_ZLibError(r).GetChars());
2656 			M_Free(uncompressed);
2657 			return true;
2658 		}
2659 		M_Free (demobuffer);
2660 		zdembodyend = uncompressed + uncompSize;
2661 		demobuffer = demo_p = uncompressed;
2662 	}
2663 
2664 	return false;
2665 }
2666 
G_DoPlayDemo(void)2667 void G_DoPlayDemo (void)
2668 {
2669 	FString mapname;
2670 	int demolump;
2671 
2672 	gameaction = ga_nothing;
2673 
2674 	// [RH] Allow for demos not loaded as lumps
2675 	demolump = Wads.CheckNumForFullName (defdemoname, true);
2676 	if (demolump >= 0)
2677 	{
2678 		int demolen = Wads.LumpLength (demolump);
2679 		demobuffer = (BYTE *)M_Malloc(demolen);
2680 		Wads.ReadLump (demolump, demobuffer);
2681 	}
2682 	else
2683 	{
2684 		FixPathSeperator (defdemoname);
2685 		DefaultExtension (defdemoname, ".lmp");
2686 		M_ReadFileMalloc (defdemoname, &demobuffer);
2687 	}
2688 	demo_p = demobuffer;
2689 
2690 	Printf ("Playing demo %s\n", defdemoname.GetChars());
2691 
2692 	C_BackupCVars ();		// [RH] Save cvars that might be affected by demo
2693 
2694 	if (ReadLong (&demo_p) != FORM_ID)
2695 	{
2696 		const char *eek = "Cannot play non-" GAMENAME " demos.\n";
2697 
2698 		C_ForgetCVars();
2699 		M_Free(demobuffer);
2700 		demo_p = demobuffer = NULL;
2701 		if (singledemo)
2702 		{
2703 			I_Error ("%s", eek);
2704 		}
2705 		else
2706 		{
2707 			Printf (PRINT_BOLD, "%s", eek);
2708 			gameaction = ga_nothing;
2709 		}
2710 	}
2711 	else if (G_ProcessIFFDemo (mapname))
2712 	{
2713 		C_RestoreCVars();
2714 		gameaction = ga_nothing;
2715 		demoplayback = false;
2716 	}
2717 	else
2718 	{
2719 		// don't spend a lot of time in loadlevel
2720 		precache = false;
2721 		demonew = true;
2722 		if (mapname.Len() != 0)
2723 		{
2724 			G_InitNew (mapname, false);
2725 		}
2726 		else if (numsectors == 0)
2727 		{
2728 			I_Error("Cannot play demo without its savegame\n");
2729 		}
2730 		C_HideConsole ();
2731 		demonew = false;
2732 		precache = true;
2733 
2734 		usergame = false;
2735 		demoplayback = true;
2736 	}
2737 }
2738 
2739 //
2740 // G_TimeDemo
2741 //
G_TimeDemo(const char * name)2742 void G_TimeDemo (const char* name)
2743 {
2744 	nodrawers = !!Args->CheckParm ("-nodraw");
2745 	noblit = !!Args->CheckParm ("-noblit");
2746 	timingdemo = true;
2747 	singletics = true;
2748 
2749 	defdemoname = name;
2750 	gameaction = (gameaction == ga_loadgame) ? ga_loadgameplaydemo : ga_playdemo;
2751 }
2752 
2753 
2754 /*
2755 ===================
2756 =
2757 = G_CheckDemoStatus
2758 =
2759 = Called after a death or level completion to allow demos to be cleaned up
2760 = Returns true if a new demo loop action will take place
2761 ===================
2762 */
2763 
G_CheckDemoStatus(void)2764 bool G_CheckDemoStatus (void)
2765 {
2766 	if (!demorecording)
2767 	{ // [RH] Restore the player's userinfo settings.
2768 		D_SetupUserInfo();
2769 	}
2770 
2771 	if (demoplayback)
2772 	{
2773 		extern int starttime;
2774 		int endtime = 0;
2775 
2776 		if (timingdemo)
2777 			endtime = I_GetTime (false) - starttime;
2778 
2779 		C_RestoreCVars ();		// [RH] Restore cvars demo might have changed
2780 		M_Free (demobuffer);
2781 		demobuffer = NULL;
2782 
2783 		P_SetupWeapons_ntohton();
2784 		demoplayback = false;
2785 		netgame = false;
2786 		multiplayer = false;
2787 		singletics = false;
2788 		for (int i = 1; i < MAXPLAYERS; i++)
2789 			playeringame[i] = 0;
2790 		consoleplayer = 0;
2791 		players[0].camera = NULL;
2792 		if (StatusBar != NULL)
2793 		{
2794 			StatusBar->AttachToPlayer (&players[0]);
2795 		}
2796 		if (singledemo || timingdemo)
2797 		{
2798 			if (timingdemo)
2799 			{
2800 				// Trying to get back to a stable state after timing a demo
2801 				// seems to cause problems. I don't feel like fixing that
2802 				// right now.
2803 				I_FatalError ("timed %i gametics in %i realtics (%.1f fps)\n"
2804 							  "(This is not really an error.)", gametic,
2805 							  endtime, (float)gametic/(float)endtime*(float)TICRATE);
2806 			}
2807 			else
2808 			{
2809 				Printf ("Demo ended.\n");
2810 			}
2811 			gameaction = ga_fullconsole;
2812 			timingdemo = false;
2813 			return false;
2814 		}
2815 		else
2816 		{
2817 			D_AdvanceDemo ();
2818 		}
2819 
2820 		return true;
2821 	}
2822 
2823 	if (demorecording)
2824 	{
2825 		BYTE *formlen;
2826 
2827 		WriteByte (DEM_STOP, &demo_p);
2828 
2829 		if (demo_compress)
2830 		{
2831 			// Now that the entire BODY chunk has been created, replace it with
2832 			// a compressed version. If the BODY successfully compresses, the
2833 			// contents of the COMP chunk will be changed to indicate the
2834 			// uncompressed size of the BODY.
2835 			uLong len = uLong(demo_p - demobodyspot);
2836 			uLong outlen = (len + len/100 + 12);
2837 			Byte *compressed = new Byte[outlen];
2838 			int r = compress2 (compressed, &outlen, demobodyspot, len, 9);
2839 			if (r == Z_OK && outlen < len)
2840 			{
2841 				formlen = democompspot;
2842 				WriteLong (len, &democompspot);
2843 				memcpy (demobodyspot, compressed, outlen);
2844 				demo_p = demobodyspot + outlen;
2845 			}
2846 			delete[] compressed;
2847 		}
2848 		FinishChunk (&demo_p);
2849 		formlen = demobuffer + 4;
2850 		WriteLong (int(demo_p - demobuffer - 8), &formlen);
2851 
2852 		bool saved = M_WriteFile (demoname, demobuffer, int(demo_p - demobuffer));
2853 		M_Free (demobuffer);
2854 		demorecording = false;
2855 		stoprecording = false;
2856 		if (saved)
2857 		{
2858 			Printf ("Demo %s recorded\n", demoname.GetChars());
2859 		}
2860 		else
2861 		{
2862 			Printf ("Demo %s could not be saved\n", demoname.GetChars());
2863 		}
2864 	}
2865 
2866 	return false;
2867 }
2868