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