1 // SONIC ROBO BLAST 2
2 //-----------------------------------------------------------------------------
3 // Copyright (C) 1993-1996 by id Software, Inc.
4 // Copyright (C) 1998-2000 by DooM Legacy Team.
5 // Copyright (C) 1999-2020 by Sonic Team Junior.
6 //
7 // This program is free software distributed under the
8 // terms of the GNU General Public License, version 2.
9 // See the 'LICENSE' file for more details.
10 //-----------------------------------------------------------------------------
11 /// \file m_cheat.c
12 /// \brief Cheat sequence checking
13
14 #include "doomdef.h"
15 #include "g_input.h"
16 #include "g_game.h"
17 #include "s_sound.h"
18
19 #include "r_local.h"
20 #include "p_local.h"
21 #include "p_setup.h"
22 #include "d_net.h"
23
24 #include "m_cheat.h"
25 #include "m_menu.h"
26 #include "m_random.h"
27 #include "m_misc.h"
28
29 #include "hu_stuff.h"
30 #include "m_cond.h" // secrets unlocked?
31
32 #include "v_video.h"
33 #include "z_zone.h"
34 #include "p_slopes.h"
35
36 #include "lua_script.h"
37 #include "lua_hook.h"
38
39 //
40 // CHEAT SEQUENCE PACKAGE
41 //
42
43 #define SCRAMBLE(a) \
44 ((((a)&1)<<7) + (((a)&2)<<5) + ((a)&4) + (((a)&8)<<1) \
45 + (((a)&16)>>1) + ((a)&32) + (((a)&64)>>5) + (((a)&128)>>7))
46
47 typedef struct
48 {
49 UINT8 *p;
50 UINT8 (*func)(void); // called when cheat confirmed.
51 UINT8 sequence[];
52 } cheatseq_t;
53
54 // ==========================================================================
55 // CHEAT Structures
56 // ==========================================================================
57
58 // Cheat responders
cheatf_ultimate(void)59 static UINT8 cheatf_ultimate(void)
60 {
61 if (menuactive && (currentMenu != &MainDef && currentMenu != &SP_LoadDef))
62 return 0; // Only on the main menu, or the save select!
63
64 BwehHehHe();
65 ultimate_selectable = (!ultimate_selectable);
66
67 // If on the save select, move to what is now Ultimate Mode!
68 if (currentMenu == &SP_LoadDef)
69 M_ForceSaveSlotSelected(NOSAVESLOT);
70 return 1;
71 }
72
cheatf_warp(void)73 static UINT8 cheatf_warp(void)
74 {
75 if (modifiedgame)
76 return 0;
77
78 if (menuactive && currentMenu != &MainDef)
79 return 0; // Only on the main menu!
80
81 S_StartSound(0, sfx_itemup);
82
83 // Temporarily unlock stuff.
84 G_SetGameModified(false);
85 unlockables[31].unlocked = true; // credits
86 unlockables[30].unlocked = true; // sound test
87 unlockables[28].unlocked = true; // level select
88
89 // Refresh secrets menu existing.
90 M_ClearMenus(true);
91 M_StartControlPanel();
92 return 1;
93 }
94
95 #ifdef DEVELOP
cheatf_devmode(void)96 static UINT8 cheatf_devmode(void)
97 {
98 UINT8 i;
99
100 if (modifiedgame)
101 return 0;
102
103 if (menuactive && currentMenu != &MainDef)
104 return 0; // Only on the main menu!
105
106 S_StartSound(0, sfx_itemup);
107
108 // Just unlock all the things and turn on -debug and console devmode.
109 G_SetGameModified(false);
110 for (i = 0; i < MAXUNLOCKABLES; i++)
111 unlockables[i].unlocked = true;
112 devparm = true;
113 cv_debug |= 0x8000;
114
115 // Refresh secrets menu existing.
116 M_ClearMenus(true);
117 M_StartControlPanel();
118 return 1;
119 }
120 #endif
121
122 static cheatseq_t cheat_ultimate = {
123 0, cheatf_ultimate,
124 { SCRAMBLE('u'), SCRAMBLE('l'), SCRAMBLE('t'), SCRAMBLE('i'), SCRAMBLE('m'), SCRAMBLE('a'), SCRAMBLE('t'), SCRAMBLE('e'), 0xff }
125 };
126
127 static cheatseq_t cheat_ultimate_joy = {
128 0, cheatf_ultimate,
129 { SCRAMBLE(KEY_UPARROW), SCRAMBLE(KEY_UPARROW), SCRAMBLE(KEY_DOWNARROW), SCRAMBLE(KEY_DOWNARROW),
130 SCRAMBLE(KEY_LEFTARROW), SCRAMBLE(KEY_RIGHTARROW), SCRAMBLE(KEY_LEFTARROW), SCRAMBLE(KEY_RIGHTARROW),
131 SCRAMBLE(KEY_ENTER), 0xff }
132 };
133
134 static cheatseq_t cheat_warp = {
135 0, cheatf_warp,
136 { SCRAMBLE('c'), SCRAMBLE('a'), SCRAMBLE('s'), SCRAMBLE('h'), SCRAMBLE('r'), SCRAMBLE('i'), SCRAMBLE('d'), SCRAMBLE('a'), 0xff }
137 };
138
139 static cheatseq_t cheat_warp_joy = {
140 0, cheatf_warp,
141 { SCRAMBLE(KEY_RIGHTARROW), SCRAMBLE(KEY_RIGHTARROW), SCRAMBLE(KEY_DOWNARROW),
142 SCRAMBLE(KEY_LEFTARROW), SCRAMBLE(KEY_LEFTARROW), SCRAMBLE(KEY_DOWNARROW),
143 SCRAMBLE(KEY_RIGHTARROW), SCRAMBLE(KEY_DOWNARROW),
144 SCRAMBLE(KEY_ENTER), 0xff }
145 };
146
147 #ifdef DEVELOP
148 static cheatseq_t cheat_devmode = {
149 0, cheatf_devmode,
150 { SCRAMBLE('d'), SCRAMBLE('e'), SCRAMBLE('v'), SCRAMBLE('m'), SCRAMBLE('o'), SCRAMBLE('d'), SCRAMBLE('e'), 0xff }
151 };
152 #endif
153
154 // ==========================================================================
155 // CHEAT SEQUENCE PACKAGE
156 // ==========================================================================
157
158 static UINT8 cheat_xlate_table[256];
159
cht_Init(void)160 void cht_Init(void)
161 {
162 size_t i = 0;
163 INT16 pi = 0;
164 for (; i < 256; i++, pi++)
165 {
166 const INT32 cc = SCRAMBLE(pi);
167 cheat_xlate_table[i] = (UINT8)cc;
168 }
169 }
170
171 //
172 // Called in st_stuff module, which handles the input.
173 // Returns a 1 if the cheat was successful, 0 if failed.
174 //
cht_CheckCheat(cheatseq_t * cht,char key)175 static UINT8 cht_CheckCheat(cheatseq_t *cht, char key)
176 {
177 UINT8 rc = 0;
178
179 if (!cht->p)
180 cht->p = cht->sequence; // initialize if first time
181
182 if (*cht->p == 0)
183 *(cht->p++) = key;
184 else if (cheat_xlate_table[(UINT8)key] == *cht->p)
185 cht->p++;
186 else
187 cht->p = cht->sequence;
188
189 if (*cht->p == 1)
190 cht->p++;
191 else if (*cht->p == 0xff) // end of sequence character
192 {
193 cht->p = cht->sequence;
194 rc = cht->func();
195 }
196
197 return rc;
198 }
199
cht_Responder(event_t * ev)200 boolean cht_Responder(event_t *ev)
201 {
202 UINT8 ret = 0, ch = 0;
203 if (ev->type != ev_keydown)
204 return false;
205
206 if (ev->data1 > 0xFF)
207 {
208 // map some fake (joy) inputs into keys
209 // map joy inputs into keys
210 switch (ev->data1)
211 {
212 case KEY_JOY1:
213 case KEY_JOY1 + 2:
214 ch = KEY_ENTER;
215 break;
216 case KEY_HAT1:
217 ch = KEY_UPARROW;
218 break;
219 case KEY_HAT1 + 1:
220 ch = KEY_DOWNARROW;
221 break;
222 case KEY_HAT1 + 2:
223 ch = KEY_LEFTARROW;
224 break;
225 case KEY_HAT1 + 3:
226 ch = KEY_RIGHTARROW;
227 break;
228 default:
229 // no mapping
230 return false;
231 }
232 }
233 else
234 ch = (UINT8)ev->data1;
235
236 ret += cht_CheckCheat(&cheat_ultimate, (char)ch);
237 ret += cht_CheckCheat(&cheat_ultimate_joy, (char)ch);
238 ret += cht_CheckCheat(&cheat_warp, (char)ch);
239 ret += cht_CheckCheat(&cheat_warp_joy, (char)ch);
240 #ifdef DEVELOP
241 ret += cht_CheckCheat(&cheat_devmode, (char)ch);
242 #endif
243 return (ret != 0);
244 }
245
246 // Console cheat commands rely on these a lot...
247 #define REQUIRE_PANDORA if (!M_SecretUnlocked(SECRET_PANDORA) && !cv_debug)\
248 { CONS_Printf(M_GetText("You haven't earned this yet.\n")); return; }
249
250 #define REQUIRE_DEVMODE if (!cv_debug)\
251 { CONS_Printf(M_GetText("DEVMODE must be enabled.\n")); return; }
252
253 #define REQUIRE_OBJECTPLACE if (!objectplacing)\
254 { CONS_Printf(M_GetText("OBJECTPLACE must be enabled.\n")); return; }
255
256 #define REQUIRE_INLEVEL if (gamestate != GS_LEVEL || demoplayback)\
257 { CONS_Printf(M_GetText("You must be in a level to use this.\n")); return; }
258
259 #define REQUIRE_SINGLEPLAYER if (netgame || multiplayer)\
260 { CONS_Printf(M_GetText("This only works in single player.\n")); return; }
261
262 #define REQUIRE_NOULTIMATE if (ultimatemode)\
263 { CONS_Printf(M_GetText("You're too good to be cheating!\n")); return; }
264
265 // command that can be typed at the console!
Command_CheatNoClip_f(void)266 void Command_CheatNoClip_f(void)
267 {
268 player_t *plyr;
269
270 REQUIRE_INLEVEL;
271 REQUIRE_SINGLEPLAYER;
272 REQUIRE_NOULTIMATE;
273
274 plyr = &players[consoleplayer];
275 plyr->pflags ^= PF_NOCLIP;
276 CONS_Printf(M_GetText("No Clipping %s\n"), plyr->pflags & PF_NOCLIP ? M_GetText("On") : M_GetText("Off"));
277
278 G_SetGameModified(multiplayer);
279 }
280
Command_CheatGod_f(void)281 void Command_CheatGod_f(void)
282 {
283 player_t *plyr;
284
285 REQUIRE_INLEVEL;
286 REQUIRE_SINGLEPLAYER;
287 REQUIRE_NOULTIMATE;
288
289 plyr = &players[consoleplayer];
290 plyr->pflags ^= PF_GODMODE;
291 CONS_Printf(M_GetText("Cheese Mode %s\n"), plyr->pflags & PF_GODMODE ? M_GetText("On") : M_GetText("Off"));
292
293 G_SetGameModified(multiplayer);
294 }
295
Command_CheatNoTarget_f(void)296 void Command_CheatNoTarget_f(void)
297 {
298 player_t *plyr;
299
300 REQUIRE_INLEVEL;
301 REQUIRE_SINGLEPLAYER;
302 REQUIRE_NOULTIMATE;
303
304 plyr = &players[consoleplayer];
305 plyr->pflags ^= PF_INVIS;
306 CONS_Printf(M_GetText("SEP Field %s\n"), plyr->pflags & PF_INVIS ? M_GetText("On") : M_GetText("Off"));
307
308 G_SetGameModified(multiplayer);
309 }
310
Command_Scale_f(void)311 void Command_Scale_f(void)
312 {
313 const double scaled = atof(COM_Argv(1));
314 fixed_t scale = FLOAT_TO_FIXED(scaled);
315
316 REQUIRE_DEVMODE;
317 REQUIRE_INLEVEL;
318 REQUIRE_SINGLEPLAYER;
319
320 if (scale < FRACUNIT/100 || scale > 100*FRACUNIT) //COM_Argv(1) will return a null string if they did not give a paramater, so...
321 {
322 CONS_Printf(M_GetText("scale <value> (0.01-100.0): set player scale size\n"));
323 return;
324 }
325
326 if (!players[consoleplayer].mo)
327 return;
328
329 players[consoleplayer].mo->destscale = scale;
330
331 CONS_Printf(M_GetText("Scale set to %s\n"), COM_Argv(1));
332 }
333
Command_Gravflip_f(void)334 void Command_Gravflip_f(void)
335 {
336 REQUIRE_DEVMODE;
337 REQUIRE_INLEVEL;
338 REQUIRE_SINGLEPLAYER;
339
340 if (players[consoleplayer].mo)
341 players[consoleplayer].mo->flags2 ^= MF2_OBJECTFLIP;
342 }
343
Command_Hurtme_f(void)344 void Command_Hurtme_f(void)
345 {
346 REQUIRE_DEVMODE;
347 REQUIRE_INLEVEL;
348 REQUIRE_SINGLEPLAYER;
349
350 if (COM_Argc() < 2)
351 {
352 CONS_Printf(M_GetText("hurtme <damage>: Damage yourself by a specific amount\n"));
353 return;
354 }
355
356 P_DamageMobj(players[consoleplayer].mo, NULL, NULL, atoi(COM_Argv(1)), 0);
357 }
358
359 // Moves the NiGHTS player to another axis within the current mare
Command_JumpToAxis_f(void)360 void Command_JumpToAxis_f(void)
361 {
362 REQUIRE_DEVMODE;
363 REQUIRE_INLEVEL;
364 REQUIRE_SINGLEPLAYER;
365
366 if (COM_Argc() != 2)
367 {
368 CONS_Printf(M_GetText("jumptoaxis <axisnum>: Jump to axis within current mare.\n"));
369 return;
370 }
371
372 P_TransferToAxis(&players[consoleplayer], atoi(COM_Argv(1)));
373 }
374
Command_Charability_f(void)375 void Command_Charability_f(void)
376 {
377 REQUIRE_DEVMODE;
378 REQUIRE_INLEVEL;
379 REQUIRE_SINGLEPLAYER;
380
381 if (COM_Argc() < 3)
382 {
383 CONS_Printf(M_GetText("charability <1/2> <value>: change character abilities\n"));
384 return;
385 }
386
387 if (atoi(COM_Argv(1)) == 1)
388 players[consoleplayer].charability = (UINT8)atoi(COM_Argv(2));
389 else if (atoi(COM_Argv(1)) == 2)
390 players[consoleplayer].charability2 = (UINT8)atoi(COM_Argv(2));
391 else
392 CONS_Printf(M_GetText("charability <1/2> <value>: change character abilities\n"));
393 }
394
Command_Charspeed_f(void)395 void Command_Charspeed_f(void)
396 {
397 REQUIRE_DEVMODE;
398 REQUIRE_INLEVEL;
399 REQUIRE_SINGLEPLAYER;
400
401 if (COM_Argc() < 3)
402 {
403 CONS_Printf(M_GetText("charspeed <normalspeed/runspeed/thrustfactor/accelstart/acceleration/actionspd> <value>: set character speed\n"));
404 return;
405 }
406
407 if (!strcasecmp(COM_Argv(1), "normalspeed"))
408 players[consoleplayer].normalspeed = atoi(COM_Argv(2))<<FRACBITS;
409 else if (!strcasecmp(COM_Argv(1), "runspeed"))
410 players[consoleplayer].runspeed = atoi(COM_Argv(2))<<FRACBITS;
411 else if (!strcasecmp(COM_Argv(1), "thrustfactor"))
412 players[consoleplayer].thrustfactor = atoi(COM_Argv(2));
413 else if (!strcasecmp(COM_Argv(1), "accelstart"))
414 players[consoleplayer].accelstart = atoi(COM_Argv(2));
415 else if (!strcasecmp(COM_Argv(1), "acceleration"))
416 players[consoleplayer].acceleration = atoi(COM_Argv(2));
417 else if (!strcasecmp(COM_Argv(1), "actionspd"))
418 players[consoleplayer].actionspd = atoi(COM_Argv(2))<<FRACBITS;
419 else
420 CONS_Printf(M_GetText("charspeed <normalspeed/runspeed/thrustfactor/accelstart/acceleration/actionspd> <value>: set character speed\n"));
421 }
422
Command_RTeleport_f(void)423 void Command_RTeleport_f(void)
424 {
425 fixed_t intx, inty, intz;
426 size_t i;
427 player_t *p = &players[consoleplayer];
428 subsector_t *ss;
429
430 REQUIRE_DEVMODE;
431 REQUIRE_INLEVEL;
432 REQUIRE_SINGLEPLAYER;
433
434 if (COM_Argc() < 3 || COM_Argc() > 7)
435 {
436 CONS_Printf(M_GetText("rteleport -x <value> -y <value> -z <value>: relative teleport to a location\n"));
437 return;
438 }
439
440 if (!p->mo)
441 return;
442
443 i = COM_CheckParm("-x");
444 if (i)
445 intx = atoi(COM_Argv(i + 1));
446 else
447 intx = 0;
448
449 i = COM_CheckParm("-y");
450 if (i)
451 inty = atoi(COM_Argv(i + 1));
452 else
453 inty = 0;
454
455 ss = R_PointInSubsectorOrNull(p->mo->x + intx*FRACUNIT, p->mo->y + inty*FRACUNIT);
456 if (!ss || ss->sector->ceilingheight - ss->sector->floorheight < p->mo->height)
457 {
458 CONS_Alert(CONS_NOTICE, M_GetText("Not a valid location.\n"));
459 return;
460 }
461 i = COM_CheckParm("-z");
462 if (i)
463 {
464 intz = atoi(COM_Argv(i + 1));
465 intz <<= FRACBITS;
466 intz += p->mo->z;
467 if (intz < ss->sector->floorheight)
468 intz = ss->sector->floorheight;
469 if (intz > ss->sector->ceilingheight - p->mo->height)
470 intz = ss->sector->ceilingheight - p->mo->height;
471 }
472 else
473 intz = p->mo->z;
474
475 CONS_Printf(M_GetText("Teleporting by %d, %d, %d...\n"), intx, inty, FixedInt((intz-p->mo->z)));
476
477 P_MapStart();
478 if (!P_TeleportMove(p->mo, p->mo->x+intx*FRACUNIT, p->mo->y+inty*FRACUNIT, intz))
479 CONS_Alert(CONS_WARNING, M_GetText("Unable to teleport to that spot!\n"));
480 else
481 S_StartSound(p->mo, sfx_mixup);
482 P_MapEnd();
483 }
484
Command_Teleport_f(void)485 void Command_Teleport_f(void)
486 {
487 fixed_t intx, inty, intz;
488 size_t i;
489 player_t *p = &players[consoleplayer];
490 subsector_t *ss;
491
492 REQUIRE_DEVMODE;
493 REQUIRE_INLEVEL;
494 REQUIRE_SINGLEPLAYER;
495
496 if (COM_Argc() < 3 || COM_Argc() > 11)
497 {
498 CONS_Printf(M_GetText("teleport -x <value> -y <value> -z <value> -ang <value> -aim <value>: teleport to a location\nteleport -sp <sequence> <placement>: teleport to specified checkpoint\n"));
499 return;
500 }
501
502 if (!p->mo)
503 return;
504
505 i = COM_CheckParm("-sp");
506 if (i)
507 {
508 INT32 starpostnum = atoi(COM_Argv(i + 1)); // starpost number
509 INT32 starpostpath = atoi(COM_Argv(i + 2)); // quick, dirty way to distinguish between paths
510
511 if (starpostnum < 0 || starpostpath < 0)
512 {
513 CONS_Alert(CONS_NOTICE, M_GetText("Negative starpost indexing is not valid.\n"));
514 return;
515 }
516
517 if (!starpostnum) // spawnpoints...
518 {
519 mapthing_t *mt;
520 fixed_t offset;
521
522 if (starpostpath >= numcoopstarts)
523 {
524 CONS_Alert(CONS_NOTICE, M_GetText("Player %d spawnpoint not found (%d max).\n"), starpostpath+1, numcoopstarts-1);
525 return;
526 }
527
528 mt = playerstarts[starpostpath]; // Given above check, should never be NULL.
529 intx = mt->x<<FRACBITS;
530 inty = mt->y<<FRACBITS;
531 offset = mt->z<<FRACBITS;
532
533 ss = R_PointInSubsectorOrNull(intx, inty);
534 if (!ss || ss->sector->ceilingheight - ss->sector->floorheight < p->mo->height)
535 {
536 CONS_Alert(CONS_NOTICE, M_GetText("Spawnpoint not in a valid location.\n"));
537 return;
538 }
539
540 // Flagging a player's ambush will make them start on the ceiling
541 // Objectflip inverts
542 if (!!(mt->options & MTF_AMBUSH) ^ !!(mt->options & MTF_OBJECTFLIP))
543 intz = ss->sector->ceilingheight - p->mo->height - offset;
544 else
545 intz = ss->sector->floorheight + offset;
546
547 if (mt->options & MTF_OBJECTFLIP) // flip the player!
548 {
549 p->mo->eflags |= MFE_VERTICALFLIP;
550 p->mo->flags2 |= MF2_OBJECTFLIP;
551 }
552 else
553 {
554 p->mo->eflags &= ~MFE_VERTICALFLIP;
555 p->mo->flags2 &= ~MF2_OBJECTFLIP;
556 }
557
558 p->mo->angle = p->drawangle = FixedAngle(mt->angle<<FRACBITS);
559 P_SetPlayerAngle(p, p->mo->angle);
560 }
561 else // scan the thinkers to find starposts...
562 {
563 mobj_t *mo2 = NULL;
564 thinker_t *th;
565
566 INT32 starpostmax = 0;
567 intz = starpostpath; // variable reuse - counting down for selection purposes
568
569 for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
570 {
571 if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
572 continue;
573
574 mo2 = (mobj_t *)th;
575
576 if (mo2->type != MT_STARPOST)
577 continue;
578
579 if (mo2->health != starpostnum)
580 {
581 if (mo2->health > starpostmax)
582 starpostmax = mo2->health;
583 continue;
584 }
585
586 if (intz--)
587 continue;
588
589 break;
590 }
591
592 if (th == &thlist[THINK_MOBJ])
593 {
594 if (intz == starpostpath)
595 CONS_Alert(CONS_NOTICE, M_GetText("No starpost of position %d found (%d max).\n"), starpostnum, starpostmax);
596 else
597 CONS_Alert(CONS_NOTICE, M_GetText("Starpost of position %d, %d not found (%d, %d max).\n"), starpostnum, starpostpath, starpostmax, (starpostpath-intz)-1);
598 return;
599 }
600
601 ss = R_PointInSubsectorOrNull(mo2->x, mo2->y);
602 if (!ss || ss->sector->ceilingheight - ss->sector->floorheight < p->mo->height)
603 {
604 CONS_Alert(CONS_NOTICE, M_GetText("Starpost not in a valid location.\n"));
605 return;
606 }
607
608 intx = mo2->x;
609 inty = mo2->y;
610 intz = mo2->z;
611
612 if (mo2->flags2 & MF2_OBJECTFLIP) // flip the player!
613 {
614 p->mo->eflags |= MFE_VERTICALFLIP;
615 p->mo->flags2 |= MF2_OBJECTFLIP;
616 }
617 else
618 {
619 p->mo->eflags &= ~MFE_VERTICALFLIP;
620 p->mo->flags2 &= ~MF2_OBJECTFLIP;
621 }
622
623 p->mo->angle = p->drawangle = mo2->angle;
624 P_SetPlayerAngle(p, p->mo->angle);
625 }
626
627 CONS_Printf(M_GetText("Teleporting to checkpoint %d, %d...\n"), starpostnum, starpostpath);
628 }
629 else
630 {
631 i = COM_CheckParm("-nop"); // undocumented stupid addition to allow pivoting on the spot with -ang and -aim
632 if (i)
633 {
634 intx = p->mo->x;
635 inty = p->mo->y;
636 }
637 else
638 {
639 i = COM_CheckParm("-x");
640 if (i)
641 intx = atoi(COM_Argv(i + 1))<<FRACBITS;
642 else
643 {
644 CONS_Alert(CONS_NOTICE, M_GetText("%s value not specified.\n"), "X");
645 return;
646 }
647
648 i = COM_CheckParm("-y");
649 if (i)
650 inty = atoi(COM_Argv(i + 1))<<FRACBITS;
651 else
652 {
653 CONS_Alert(CONS_NOTICE, M_GetText("%s value not specified.\n"), "Y");
654 return;
655 }
656 }
657
658 ss = R_PointInSubsectorOrNull(intx, inty);
659 if (!ss || ss->sector->ceilingheight - ss->sector->floorheight < p->mo->height)
660 {
661 CONS_Alert(CONS_NOTICE, M_GetText("Not a valid location.\n"));
662 return;
663 }
664 i = COM_CheckParm("-z");
665 if (i)
666 {
667 intz = atoi(COM_Argv(i + 1))<<FRACBITS;
668 if (intz < ss->sector->floorheight)
669 intz = ss->sector->floorheight;
670 if (intz > ss->sector->ceilingheight - p->mo->height)
671 intz = ss->sector->ceilingheight - p->mo->height;
672 }
673 else
674 intz = ((p->mo->eflags & MFE_VERTICALFLIP) ? ss->sector->ceilingheight : ss->sector->floorheight);
675
676 i = COM_CheckParm("-ang");
677 if (i)
678 {
679 p->drawangle = p->mo->angle = FixedAngle(atoi(COM_Argv(i + 1))<<FRACBITS);
680 P_SetPlayerAngle(p, p->mo->angle);
681 }
682
683 i = COM_CheckParm("-aim");
684 if (i)
685 {
686 angle_t aim = FixedAngle(atoi(COM_Argv(i + 1))<<FRACBITS);
687 if (aim >= ANGLE_90 && aim <= ANGLE_270)
688 {
689 CONS_Alert(CONS_NOTICE, M_GetText("Not a valid aiming angle (between +/-90).\n"));
690 return;
691 }
692 localaiming = p->aiming = aim;
693 }
694
695 CONS_Printf(M_GetText("Teleporting to %d, %d, %d...\n"), FixedInt(intx), FixedInt(inty), FixedInt(intz));
696 }
697
698 P_MapStart();
699 if (!P_TeleportMove(p->mo, intx, inty, intz))
700 CONS_Alert(CONS_WARNING, M_GetText("Unable to teleport to that spot!\n"));
701 else
702 S_StartSound(p->mo, sfx_mixup);
703 P_MapEnd();
704 }
705
Command_Skynum_f(void)706 void Command_Skynum_f(void)
707 {
708 REQUIRE_DEVMODE;
709 REQUIRE_INLEVEL;
710 REQUIRE_SINGLEPLAYER;
711
712 if (COM_Argc() != 2)
713 {
714 CONS_Printf(M_GetText("skynum <sky#>: change the sky\n"));
715 CONS_Printf(M_GetText("Current sky is %d\n"), levelskynum);
716 return;
717 }
718
719 CONS_Printf(M_GetText("Previewing sky %s...\n"), COM_Argv(1));
720
721 P_SetupLevelSky(atoi(COM_Argv(1)), false);
722 }
723
Command_Weather_f(void)724 void Command_Weather_f(void)
725 {
726 REQUIRE_DEVMODE;
727 REQUIRE_INLEVEL;
728 REQUIRE_SINGLEPLAYER;
729
730 if (COM_Argc() != 2)
731 {
732 CONS_Printf(M_GetText("weather <weather#>: change the weather\n"));
733 CONS_Printf(M_GetText("Current weather is %d\n"), curWeather);
734 return;
735 }
736
737 CONS_Printf(M_GetText("Previewing weather %s...\n"), COM_Argv(1));
738
739 P_SwitchWeather(atoi(COM_Argv(1)));
740 }
741
Command_Toggletwod_f(void)742 void Command_Toggletwod_f(void)
743 {
744 player_t *p = &players[consoleplayer];
745
746 REQUIRE_DEVMODE;
747 REQUIRE_INLEVEL;
748 REQUIRE_SINGLEPLAYER;
749
750 if (p->mo)
751 p->mo->flags2 ^= MF2_TWOD;
752 }
753
754 #ifdef _DEBUG
755 // You never thought you needed this, did you? >=D
756 // Yes, this has the specific purpose of completely screwing you up
757 // to see if the consistency restoration code can fix you.
758 // Don't enable this for normal builds...
Command_CauseCfail_f(void)759 void Command_CauseCfail_f(void)
760 {
761 if (consoleplayer == serverplayer)
762 {
763 CONS_Printf(M_GetText("Only remote players can use this command.\n"));
764 return;
765 }
766
767 P_UnsetThingPosition(players[consoleplayer].mo);
768 P_RandomFixed();
769 P_RandomByte();
770 P_RandomFixed();
771 players[consoleplayer].mo->x = 0;
772 players[consoleplayer].mo->y = 123311; //cfail cansuled kthxbye
773 players[consoleplayer].mo->z = 123311;
774 players[consoleplayer].score = 1337;
775 players[consoleplayer].rings = 1337;
776 players[consoleplayer].mo->destscale = 25;
777 P_SetThingPosition(players[consoleplayer].mo);
778
779 // CTF consistency test
780 if (gametyperules & GTR_TEAMFLAGS)
781 {
782 if (blueflag) {
783 P_RemoveMobj(blueflag);
784 blueflag = NULL;
785 }
786 if (redflag)
787 {
788 redflag->x = 423423;
789 redflag->y = 666;
790 redflag->z = 123311;
791 }
792 }
793 }
794 #endif
795
796 #ifdef LUA_ALLOW_BYTECODE
Command_Dumplua_f(void)797 void Command_Dumplua_f(void)
798 {
799 if (modifiedgame)
800 {
801 CONS_Printf(M_GetText("This command has been disabled in modified games, to prevent scripted attacks.\n"));
802 return;
803 }
804
805 if (COM_Argc() < 2)
806 {
807 CONS_Printf(M_GetText("dumplua <filename>: Compile a plain text lua script (not a wad!) into bytecode.\n"));
808 CONS_Alert(CONS_WARNING, M_GetText("This command makes irreversible file changes, please use with caution!\n"));
809 return;
810 }
811
812 LUA_DumpFile(COM_Argv(1));
813 }
814 #endif
815
Command_Savecheckpoint_f(void)816 void Command_Savecheckpoint_f(void)
817 {
818 REQUIRE_DEVMODE;
819 REQUIRE_INLEVEL;
820 REQUIRE_SINGLEPLAYER;
821
822 players[consoleplayer].starposttime = players[consoleplayer].realtime;
823 players[consoleplayer].starpostx = players[consoleplayer].mo->x>>FRACBITS;
824 players[consoleplayer].starposty = players[consoleplayer].mo->y>>FRACBITS;
825 players[consoleplayer].starpostz = players[consoleplayer].mo->floorz>>FRACBITS;
826 players[consoleplayer].starpostangle = players[consoleplayer].mo->angle;
827 players[consoleplayer].starpostscale = players[consoleplayer].mo->destscale;
828 if (players[consoleplayer].mo->flags2 & MF2_OBJECTFLIP)
829 {
830 players[consoleplayer].starpostscale *= -1;
831 players[consoleplayer].starpostz += players[consoleplayer].mo->height;
832 }
833
834 CONS_Printf(M_GetText("Temporary checkpoint created at %d, %d, %d\n"), players[consoleplayer].starpostx, players[consoleplayer].starposty, players[consoleplayer].starpostz);
835 }
836
837 // Like M_GetAllEmeralds() but for console devmode junkies.
Command_Getallemeralds_f(void)838 void Command_Getallemeralds_f(void)
839 {
840 REQUIRE_SINGLEPLAYER;
841 REQUIRE_NOULTIMATE;
842 REQUIRE_PANDORA;
843
844 emeralds = ((EMERALD7)*2)-1;
845
846 CONS_Printf(M_GetText("You now have all 7 emeralds.\n"));
847 }
848
Command_Resetemeralds_f(void)849 void Command_Resetemeralds_f(void)
850 {
851 REQUIRE_SINGLEPLAYER;
852 REQUIRE_PANDORA;
853
854 emeralds = 0;
855
856 CONS_Printf(M_GetText("Emeralds reset to zero.\n"));
857 }
858
Command_Devmode_f(void)859 void Command_Devmode_f(void)
860 {
861 #ifndef _DEBUG
862 REQUIRE_SINGLEPLAYER;
863 #endif
864 REQUIRE_NOULTIMATE;
865
866 if (COM_Argc() > 1)
867 {
868 const char *arg = COM_Argv(1);
869
870 if (arg[0] && arg[0] == '0' &&
871 arg[1] && arg[1] == 'x') // Use hexadecimal!
872 cv_debug = axtoi(arg+2);
873 else
874 cv_debug = atoi(arg);
875 }
876 else
877 {
878 CONS_Printf(M_GetText("devmode <flags>: enable debugging tools and info, prepend with 0x to use hexadecimal\n"));
879 return;
880 }
881
882 G_SetGameModified(multiplayer);
883 }
884
Command_Setrings_f(void)885 void Command_Setrings_f(void)
886 {
887 REQUIRE_INLEVEL;
888 REQUIRE_SINGLEPLAYER;
889 REQUIRE_NOULTIMATE;
890 REQUIRE_PANDORA;
891
892 if (COM_Argc() > 1)
893 {
894 if (!(maptol & TOL_NIGHTS))
895 {
896 // P_GivePlayerRings does value clamping
897 players[consoleplayer].rings = 0;
898 P_GivePlayerRings(&players[consoleplayer], atoi(COM_Argv(1)));
899 players[consoleplayer].totalring -= atoi(COM_Argv(1)); //undo totalring addition done in P_GivePlayerRings
900 }
901 else
902 {
903 players[consoleplayer].spheres = 0;
904 P_GivePlayerSpheres(&players[consoleplayer], atoi(COM_Argv(1)));
905 // no totalsphere addition to revert
906 }
907
908 G_SetGameModified(multiplayer);
909 }
910 }
911
Command_Setlives_f(void)912 void Command_Setlives_f(void)
913 {
914 REQUIRE_INLEVEL;
915 REQUIRE_SINGLEPLAYER;
916 REQUIRE_NOULTIMATE;
917 REQUIRE_PANDORA;
918
919 if (COM_Argc() > 1)
920 {
921 SINT8 lives = atoi(COM_Argv(1));
922 if (lives == -1)
923 players[consoleplayer].lives = INFLIVES; // infinity!
924 else
925 {
926 // P_GivePlayerLives does value clamping
927 players[consoleplayer].lives = 0;
928 P_GivePlayerLives(&players[consoleplayer], atoi(COM_Argv(1)));
929 }
930
931 G_SetGameModified(multiplayer);
932 }
933 }
934
Command_Setcontinues_f(void)935 void Command_Setcontinues_f(void)
936 {
937 REQUIRE_INLEVEL;
938 REQUIRE_SINGLEPLAYER;
939 REQUIRE_NOULTIMATE;
940 REQUIRE_PANDORA;
941
942 if (!continuesInSession)
943 {
944 CONS_Printf(M_GetText("This session does not use continues.\n"));
945 return;
946 }
947
948 if (COM_Argc() > 1)
949 {
950 INT32 numcontinues = atoi(COM_Argv(1));
951 if (numcontinues > 99)
952 numcontinues = 99;
953 else if (numcontinues < 0)
954 numcontinues = 0;
955
956 players[consoleplayer].continues = numcontinues;
957
958 G_SetGameModified(multiplayer);
959 }
960 }
961
962 //
963 // OBJECTPLACE (and related variables)
964 //
965 static CV_PossibleValue_t op_mapthing_t[] = {{0, "MIN"}, {4095, "MAX"}, {0, NULL}};
966 static CV_PossibleValue_t op_speed_t[] = {{1, "MIN"}, {128, "MAX"}, {0, NULL}};
967 static CV_PossibleValue_t op_flags_t[] = {{0, "MIN"}, {15, "MAX"}, {0, NULL}};
968 static CV_PossibleValue_t op_hoopflags_t[] = {{0, "MIN"}, {15, "MAX"}, {0, NULL}};
969
970 consvar_t cv_mapthingnum = CVAR_INIT ("op_mapthingnum", "0", CV_NOTINNET, op_mapthing_t, NULL);
971 consvar_t cv_speed = CVAR_INIT ("op_speed", "16", CV_NOTINNET, op_speed_t, NULL);
972 consvar_t cv_opflags = CVAR_INIT ("op_flags", "0", CV_NOTINNET, op_flags_t, NULL);
973 consvar_t cv_ophoopflags = CVAR_INIT ("op_hoopflags", "4", CV_NOTINNET, op_hoopflags_t, NULL);
974
975 boolean objectplacing = false;
976 mobjtype_t op_currentthing = 0; // For the object placement mode
977 UINT16 op_currentdoomednum = 0; // For display, etc
978 UINT32 op_displayflags = 0; // for display in ST_stuff
979
980 static pflags_t op_oldpflags = 0;
981 static mobjflag_t op_oldflags1 = 0;
982 static mobjflag2_t op_oldflags2 = 0;
983 static UINT32 op_oldeflags = 0;
984 static fixed_t op_oldmomx = 0, op_oldmomy = 0, op_oldmomz = 0, op_oldheight = 0;
985 static statenum_t op_oldstate = 0;
986 static UINT16 op_oldcolor = 0;
987
988 //
989 // Static calculation / common output help
990 //
OP_CycleThings(INT32 amt)991 static void OP_CycleThings(INT32 amt)
992 {
993 INT32 add = (amt > 0 ? 1 : -1);
994
995 while (amt)
996 {
997 do
998 {
999 op_currentthing += add;
1000 if (op_currentthing <= 0)
1001 op_currentthing = NUMMOBJTYPES-1;
1002 if (op_currentthing >= NUMMOBJTYPES)
1003 op_currentthing = 0;
1004 } while
1005 (mobjinfo[op_currentthing].doomednum == -1
1006 || op_currentthing == MT_NIGHTSDRONE
1007 || mobjinfo[op_currentthing].flags & (MF_AMBIENT|MF_NOSECTOR)
1008 || (states[mobjinfo[op_currentthing].spawnstate].sprite == SPR_NULL
1009 && states[mobjinfo[op_currentthing].seestate].sprite == SPR_NULL)
1010 );
1011 amt -= add;
1012 }
1013
1014 // HACK, minus has SPR_NULL sprite
1015 if (states[mobjinfo[op_currentthing].spawnstate].sprite == SPR_NULL)
1016 {
1017 states[S_OBJPLACE_DUMMY].sprite = states[mobjinfo[op_currentthing].seestate].sprite;
1018 states[S_OBJPLACE_DUMMY].frame = states[mobjinfo[op_currentthing].seestate].frame;
1019 }
1020 else
1021 {
1022 states[S_OBJPLACE_DUMMY].sprite = states[mobjinfo[op_currentthing].spawnstate].sprite;
1023 states[S_OBJPLACE_DUMMY].frame = states[mobjinfo[op_currentthing].spawnstate].frame;
1024 }
1025 if (players[0].mo->eflags & MFE_VERTICALFLIP) // correct z when flipped
1026 players[0].mo->z += players[0].mo->height - FixedMul(mobjinfo[op_currentthing].height, players[0].mo->scale);
1027 players[0].mo->height = FixedMul(mobjinfo[op_currentthing].height, players[0].mo->scale);
1028 P_SetPlayerMobjState(players[0].mo, S_OBJPLACE_DUMMY);
1029
1030 op_currentdoomednum = mobjinfo[op_currentthing].doomednum;
1031 }
1032
OP_HeightOkay(player_t * player,UINT8 ceiling)1033 static boolean OP_HeightOkay(player_t *player, UINT8 ceiling)
1034 {
1035 sector_t *sec = player->mo->subsector->sector;
1036
1037 if (ceiling)
1038 {
1039 // Truncate position to match where mapthing would be when spawned
1040 // (this applies to every further P_GetSlopeZAt call as well)
1041 fixed_t cheight = P_GetSectorCeilingZAt(sec, player->mo->x & 0xFFFF0000, player->mo->y & 0xFFFF0000);
1042
1043 if (((cheight - player->mo->z - player->mo->height)>>FRACBITS) >= (1 << (16-ZSHIFT)))
1044 {
1045 CONS_Printf(M_GetText("Sorry, you're too %s to place this object (max: %d %s).\n"), M_GetText("low"),
1046 (1 << (16-ZSHIFT)), M_GetText("below top ceiling"));
1047 return false;
1048 }
1049 }
1050 else
1051 {
1052 fixed_t fheight = P_GetSectorFloorZAt(sec, player->mo->x & 0xFFFF0000, player->mo->y & 0xFFFF0000);
1053 if (((player->mo->z - fheight)>>FRACBITS) >= (1 << (16-ZSHIFT)))
1054 {
1055 CONS_Printf(M_GetText("Sorry, you're too %s to place this object (max: %d %s).\n"), M_GetText("high"),
1056 (1 << (16-ZSHIFT)), M_GetText("above bottom floor"));
1057 return false;
1058 }
1059 }
1060 return true;
1061 }
1062
OP_CreateNewMapThing(player_t * player,UINT16 type,boolean ceiling)1063 static mapthing_t *OP_CreateNewMapThing(player_t *player, UINT16 type, boolean ceiling)
1064 {
1065 mapthing_t *mt = mapthings;
1066 sector_t *sec = player->mo->subsector->sector;
1067
1068 LUA_InvalidateMapthings();
1069
1070 mapthings = Z_Realloc(mapthings, ++nummapthings * sizeof (*mapthings), PU_LEVEL, NULL);
1071
1072 // as Z_Realloc can relocate mapthings, quickly go through thinker list and correct
1073 // the spawnpoints of any objects that have them to the new location
1074 if (mt != mapthings)
1075 {
1076 thinker_t *th;
1077 mobj_t *mo;
1078
1079 for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
1080 {
1081 if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
1082 continue;
1083
1084 mo = (mobj_t *)th;
1085 // get offset from mt, which points to old mapthings, then add new location
1086 if (!mo->spawnpoint)
1087 continue;
1088 mo->spawnpoint = (mo->spawnpoint - mt) + mapthings;
1089 }
1090 }
1091
1092 mt = (mapthings+nummapthings-1);
1093
1094 mt->type = type;
1095 mt->x = (INT16)(player->mo->x>>FRACBITS);
1096 mt->y = (INT16)(player->mo->y>>FRACBITS);
1097 if (ceiling)
1098 {
1099 fixed_t cheight = P_GetSectorCeilingZAt(sec, mt->x << FRACBITS, mt->y << FRACBITS);
1100 mt->z = (UINT16)((cheight - player->mo->z - player->mo->height)>>FRACBITS);
1101 }
1102 else
1103 {
1104 fixed_t fheight = P_GetSectorFloorZAt(sec, mt->x << FRACBITS, mt->y << FRACBITS);
1105 mt->z = (UINT16)((player->mo->z - fheight)>>FRACBITS);
1106 }
1107 mt->angle = (INT16)(FixedInt(AngleFixed(player->mo->angle)));
1108
1109 mt->options = (mt->z << ZSHIFT) | (UINT16)cv_opflags.value;
1110 mt->scale = player->mo->scale;
1111 memset(mt->args, 0, NUMMAPTHINGARGS*sizeof(*mt->args));
1112 memset(mt->stringargs, 0x00, NUMMAPTHINGSTRINGARGS*sizeof(*mt->stringargs));
1113 mt->pitch = mt->roll = 0;
1114 return mt;
1115 }
1116
1117 //
1118 // Helper functions
1119 //
OP_FreezeObjectplace(void)1120 boolean OP_FreezeObjectplace(void)
1121 {
1122 if (!objectplacing)
1123 return false;
1124
1125 if ((maptol & TOL_NIGHTS) && (players[consoleplayer].powers[pw_carry] == CR_NIGHTSMODE))
1126 return false;
1127
1128 return true;
1129 }
1130
OP_ResetObjectplace(void)1131 void OP_ResetObjectplace(void)
1132 {
1133 objectplacing = false;
1134 op_currentthing = 0;
1135 }
1136
1137 //
1138 // Main meat of objectplace: handling functions
1139 //
OP_NightsObjectplace(player_t * player)1140 void OP_NightsObjectplace(player_t *player)
1141 {
1142 ticcmd_t *cmd = &player->cmd;
1143 mapthing_t *mt;
1144
1145 player->nightstime = 3*TICRATE;
1146 player->drillmeter = TICRATE;
1147
1148 if (player->pflags & PF_ATTACKDOWN)
1149 {
1150 // Are ANY objectplace buttons pressed? If no, remove flag.
1151 if (!(cmd->buttons & (BT_ATTACK|BT_TOSSFLAG|BT_SPIN|BT_WEAPONNEXT|BT_WEAPONPREV)))
1152 player->pflags &= ~PF_ATTACKDOWN;
1153
1154 // Do nothing.
1155 return;
1156 }
1157
1158 // This places a hoop!
1159 if (cmd->buttons & BT_ATTACK)
1160 {
1161 UINT16 angle = (UINT16)(player->anotherflyangle % 360);
1162 INT16 temp = (INT16)FixedInt(AngleFixed(player->mo->angle)); // Traditional 2D Angle
1163
1164 player->pflags |= PF_ATTACKDOWN;
1165
1166 mt = OP_CreateNewMapThing(player, 1713, false);
1167
1168 // Tilt
1169 mt->angle = (INT16)FixedInt(FixedDiv(angle*FRACUNIT, 360*(FRACUNIT/256)));
1170
1171 if (player->anotherflyangle < 90 || player->anotherflyangle > 270)
1172 temp -= 90;
1173 else
1174 temp += 90;
1175 temp %= 360;
1176
1177 mt->options = (mt->options & ~(UINT16)cv_opflags.value) | (UINT16)cv_ophoopflags.value;
1178 mt->angle = (INT16)(mt->angle+(INT16)((FixedInt(FixedDiv(temp*FRACUNIT, 360*(FRACUNIT/256))))<<8));
1179
1180 P_SpawnHoop(mt);
1181 }
1182
1183 // This places a bumper!
1184 if (cmd->buttons & BT_TOSSFLAG)
1185 {
1186 UINT16 vertangle = (UINT16)(player->anotherflyangle % 360);
1187 UINT16 newflags;
1188
1189 player->pflags |= PF_ATTACKDOWN;
1190 if (!OP_HeightOkay(player, false))
1191 return;
1192
1193 mt = OP_CreateNewMapThing(player, (UINT16)mobjinfo[MT_NIGHTSBUMPER].doomednum, false);
1194 mt->z = min(mt->z - (mobjinfo[MT_NIGHTSBUMPER].height/4), 0);
1195 // height offset: from P_TouchSpecialThing case MT_NIGHTSBUMPER
1196
1197 // clockwise
1198 if (vertangle >= 75 && vertangle < 105) // up
1199 newflags = 3;
1200 else if (vertangle >= 105 && vertangle < 135) // 60 upward tilt
1201 newflags = 2;
1202 else if (vertangle >= 135 && vertangle < 165) // 30 upward tilt
1203 newflags = 1;
1204 //else if (vertangle >= 165 && vertangle < 195) // forward, see else case
1205 // newflags = 0;
1206 else if (vertangle >= 195 && vertangle < 225) // 30 downward tilt
1207 newflags = 11;
1208 else if (vertangle >= 225 && vertangle < 255) // 60 downward tilt
1209 newflags = 10;
1210 else if (vertangle >= 255 && vertangle < 285) // down
1211 newflags = 9;
1212 else if (vertangle >= 285 && vertangle < 315) // 60 downward tilt backwards
1213 newflags = 8;
1214 else if (vertangle >= 315 && vertangle < 345) // 30 downward tilt backwards
1215 newflags = 7;
1216 else if (vertangle >= 345 || vertangle < 15) // backwards
1217 newflags = 6;
1218 else if (vertangle >= 15 && vertangle < 45) // 30 upward tilt backwards
1219 newflags = 5;
1220 else if (vertangle >= 45 && vertangle < 75) // 60 upward tilt backwards
1221 newflags = 4;
1222 else // forward
1223 newflags = 0;
1224
1225 mt->options = (mt->z << ZSHIFT) | newflags;
1226
1227 // if NiGHTS is facing backwards, orient the Thing angle forwards so that the sprite angle
1228 // displays correctly. Backwards movement via the Thing flags is unaffected.
1229 if (vertangle < 90 || vertangle > 270)
1230 mt->angle = (mt->angle + 180) % 360;
1231
1232 P_SpawnMapThing(mt);
1233 }
1234
1235 // This places a sphere!
1236 if (cmd->buttons & BT_WEAPONNEXT)
1237 {
1238 player->pflags |= PF_ATTACKDOWN;
1239 if (!OP_HeightOkay(player, false))
1240 return;
1241
1242 mt = OP_CreateNewMapThing(player, (UINT16)mobjinfo[MT_BLUESPHERE].doomednum, false);
1243 P_SpawnMapThing(mt);
1244 }
1245
1246 // This places a ring!
1247 if (cmd->buttons & BT_WEAPONPREV)
1248 {
1249 player->pflags |= PF_ATTACKDOWN;
1250 if (!OP_HeightOkay(player, false))
1251 return;
1252
1253 mt = OP_CreateNewMapThing(player, (UINT16)mobjinfo[MT_RING].doomednum, false);
1254 P_SpawnMapThing(mt);
1255 }
1256
1257 // This places a custom object as defined in the console cv_mapthingnum.
1258 if (cmd->buttons & BT_SPIN)
1259 {
1260 UINT16 angle;
1261
1262 player->pflags |= PF_ATTACKDOWN;
1263 if (!cv_mapthingnum.value)
1264 {
1265 CONS_Alert(CONS_WARNING, "Set op_mapthingnum first!\n");
1266 return;
1267 }
1268 if (!OP_HeightOkay(player, false))
1269 return;
1270
1271 if (player->mo->target->flags2 & MF2_AMBUSH)
1272 angle = (UINT16)player->anotherflyangle;
1273 else
1274 {
1275 angle = (UINT16)((360-player->anotherflyangle) % 360);
1276 if (angle > 90 && angle < 270)
1277 {
1278 angle += 180;
1279 angle %= 360;
1280 }
1281 }
1282
1283 mt = OP_CreateNewMapThing(player, (UINT16)cv_mapthingnum.value, false);
1284 mt->angle = angle;
1285
1286 if (mt->type >= 600 && mt->type <= 609) // Placement patterns
1287 P_SpawnItemPattern(mt, false);
1288 else if (mt->type == 1705 || mt->type == 1713) // NiGHTS Hoops
1289 P_SpawnHoop(mt);
1290 else
1291 P_SpawnMapThing(mt);
1292 }
1293 }
1294
1295 //
1296 // OP_ObjectplaceMovement
1297 //
1298 // Control code for Objectplace mode
1299 //
OP_ObjectplaceMovement(player_t * player)1300 void OP_ObjectplaceMovement(player_t *player)
1301 {
1302 ticcmd_t *cmd = &player->cmd;
1303
1304 player->drawangle = player->mo->angle = (cmd->angleturn<<16 /* not FRACBITS */);
1305
1306 ticruned++;
1307 if (!(cmd->angleturn & TICCMD_RECEIVED))
1308 ticmiss++;
1309
1310 if (cmd->buttons & BT_JUMP)
1311 player->mo->z += player->mo->scale*cv_speed.value;
1312 else if (cmd->buttons & BT_SPIN)
1313 player->mo->z -= player->mo->scale*cv_speed.value;
1314
1315 if (cmd->forwardmove != 0)
1316 {
1317 P_Thrust(player->mo, player->mo->angle, (cmd->forwardmove*player->mo->scale/MAXPLMOVE)*cv_speed.value);
1318 P_TeleportMove(player->mo, player->mo->x+player->mo->momx, player->mo->y+player->mo->momy, player->mo->z);
1319 player->mo->momx = player->mo->momy = 0;
1320 }
1321 if (cmd->sidemove != 0)
1322 {
1323 P_Thrust(player->mo, player->mo->angle-ANGLE_90, (cmd->sidemove*player->mo->scale/MAXPLMOVE)*cv_speed.value);
1324 P_TeleportMove(player->mo, player->mo->x+player->mo->momx, player->mo->y+player->mo->momy, player->mo->z);
1325 player->mo->momx = player->mo->momy = 0;
1326 }
1327
1328 if (player->mo->z > player->mo->ceilingz - player->mo->height)
1329 player->mo->z = player->mo->ceilingz - player->mo->height;
1330 if (player->mo->z < player->mo->floorz)
1331 player->mo->z = player->mo->floorz;
1332
1333 if (cv_opflags.value & MTF_OBJECTFLIP)
1334 player->mo->eflags |= MFE_VERTICALFLIP;
1335 else
1336 player->mo->eflags &= ~MFE_VERTICALFLIP;
1337
1338 // make sure viewz follows player if in 1st person mode
1339 player->deltaviewheight = 0;
1340 player->viewheight = FixedMul(41*player->height/48, player->mo->scale);
1341 if (player->mo->eflags & MFE_VERTICALFLIP)
1342 player->viewz = player->mo->z + player->mo->height - player->viewheight;
1343 else
1344 player->viewz = player->mo->z + player->viewheight;
1345
1346 // Display flag information
1347 // Moved up so it always updates.
1348 {
1349 sector_t *sec = player->mo->subsector->sector;
1350
1351 if (!!(mobjinfo[op_currentthing].flags & MF_SPAWNCEILING) ^ !!(cv_opflags.value & MTF_OBJECTFLIP))
1352 {
1353 fixed_t cheight = P_GetSectorCeilingZAt(sec, player->mo->x & 0xFFFF0000, player->mo->y & 0xFFFF0000);
1354 op_displayflags = (UINT16)((cheight - player->mo->z - mobjinfo[op_currentthing].height)>>FRACBITS);
1355 }
1356 else
1357 {
1358 fixed_t fheight = P_GetSectorFloorZAt(sec, player->mo->x & 0xFFFF0000, player->mo->y & 0xFFFF0000);
1359 op_displayflags = (UINT16)((player->mo->z - fheight)>>FRACBITS);
1360 }
1361 op_displayflags <<= ZSHIFT;
1362 op_displayflags |= (UINT16)cv_opflags.value;
1363 }
1364
1365 if (player->pflags & PF_ATTACKDOWN)
1366 {
1367 // Are ANY objectplace buttons pressed? If no, remove flag.
1368 if (!(cmd->buttons & (BT_ATTACK|BT_TOSSFLAG|BT_WEAPONNEXT|BT_WEAPONPREV)))
1369 player->pflags &= ~PF_ATTACKDOWN;
1370
1371 // Do nothing.
1372 return;
1373 }
1374
1375 if (cmd->buttons & BT_WEAPONPREV)
1376 {
1377 OP_CycleThings(-1);
1378 player->pflags |= PF_ATTACKDOWN;
1379 }
1380
1381 if (cmd->buttons & BT_WEAPONNEXT)
1382 {
1383 OP_CycleThings(1);
1384 player->pflags |= PF_ATTACKDOWN;
1385 }
1386
1387 // Place an object and add it to the maplist
1388 if (cmd->buttons & BT_ATTACK)
1389 {
1390 mapthing_t *mt;
1391 mobjtype_t spawnmid = op_currentthing;
1392 mobjtype_t spawnthing = op_currentdoomednum;
1393 boolean ceiling;
1394
1395 player->pflags |= PF_ATTACKDOWN;
1396
1397 if (cv_mapthingnum.value > 0 && cv_mapthingnum.value < 4096)
1398 {
1399 // find which type to spawn
1400 for (spawnmid = 0; spawnmid < NUMMOBJTYPES; ++spawnmid)
1401 if (cv_mapthingnum.value == mobjinfo[spawnmid].doomednum)
1402 break;
1403
1404 if (spawnmid == NUMMOBJTYPES)
1405 {
1406 CONS_Alert(CONS_ERROR, M_GetText("Can't place an object with mapthingnum %d.\n"), cv_mapthingnum.value);
1407 return;
1408 }
1409 spawnthing = mobjinfo[spawnmid].doomednum;
1410 }
1411
1412 ceiling = !!(mobjinfo[spawnmid].flags & MF_SPAWNCEILING) ^ !!(cv_opflags.value & MTF_OBJECTFLIP);
1413 if (!OP_HeightOkay(player, ceiling))
1414 return;
1415
1416 mt = OP_CreateNewMapThing(player, (UINT16)spawnthing, ceiling);
1417 if (mt->type >= 600 && mt->type <= 609) // Placement patterns
1418 P_SpawnItemPattern(mt, false);
1419 else if (mt->type == 1705 || mt->type == 1713) // NiGHTS Hoops
1420 P_SpawnHoop(mt);
1421 else
1422 P_SpawnMapThing(mt);
1423
1424 CONS_Printf(M_GetText("Placed object type %d at %d, %d, %d, %d\n"), mt->type, mt->x, mt->y, mt->z, mt->angle);
1425 }
1426 }
1427
1428 //
1429 // Objectplace related commands.
1430 //
Command_Writethings_f(void)1431 void Command_Writethings_f(void)
1432 {
1433 REQUIRE_INLEVEL;
1434 REQUIRE_SINGLEPLAYER;
1435 REQUIRE_OBJECTPLACE;
1436
1437 P_WriteThings();
1438 }
1439
Command_ObjectPlace_f(void)1440 void Command_ObjectPlace_f(void)
1441 {
1442 size_t thingarg;
1443 size_t silent;
1444
1445 REQUIRE_INLEVEL;
1446 REQUIRE_SINGLEPLAYER;
1447 REQUIRE_NOULTIMATE;
1448
1449 G_SetGameModified(multiplayer);
1450
1451 silent = COM_CheckParm("-silent");
1452
1453 thingarg = 2 - ( silent != 1 );
1454
1455 // Entering objectplace?
1456 if (!objectplacing || thingarg < COM_Argc())
1457 {
1458 if (!objectplacing)
1459 {
1460 objectplacing = true;
1461
1462 if (players[0].powers[pw_carry] == CR_NIGHTSMODE)
1463 return;
1464
1465 if (! silent)
1466 {
1467 HU_SetCEchoFlags(V_RETURN8|V_MONOSPACE|V_AUTOFADEOUT);
1468 HU_SetCEchoDuration(10);
1469 HU_DoCEcho(va(M_GetText(
1470 "\\\\\\\\\\\\\\\\\\\\\\\\\x82"
1471 " Objectplace Controls: \x80\\\\"
1472 "Weapon Next/Prev: Cycle mapthings\\"
1473 " Jump: Float up \\"
1474 " Spin: Float down \\"
1475 " Fire Ring: Place object \\")));
1476 }
1477
1478 // Save all the player's data.
1479 op_oldflags1 = players[0].mo->flags;
1480 op_oldflags2 = players[0].mo->flags2;
1481 op_oldeflags = players[0].mo->eflags;
1482 op_oldpflags = players[0].pflags;
1483 op_oldmomx = players[0].mo->momx;
1484 op_oldmomy = players[0].mo->momy;
1485 op_oldmomz = players[0].mo->momz;
1486 op_oldheight = players[0].mo->height;
1487 op_oldstate = S_PLAY_STND;
1488 op_oldcolor = players[0].mo->color; // save color too in case of super/fireflower
1489
1490 // Remove ALL flags and motion.
1491 P_UnsetThingPosition(players[0].mo);
1492 players[0].pflags = 0;
1493 players[0].mo->flags2 = 0;
1494 players[0].mo->eflags = 0;
1495 players[0].mo->flags = (MF_NOCLIP|MF_NOGRAVITY|MF_NOBLOCKMAP);
1496 players[0].mo->momx = players[0].mo->momy = players[0].mo->momz = 0;
1497 P_SetThingPosition(players[0].mo);
1498
1499 // Take away color so things display properly
1500 players[0].mo->color = 0;
1501
1502 // Like the classics, recover from death by entering objectplace
1503 if (players[0].mo->health <= 0)
1504 {
1505 players[0].mo->health = 1;
1506 players[0].deadtimer = 0;
1507 op_oldflags1 = mobjinfo[MT_PLAYER].flags;
1508 ++players[0].lives;
1509 players[0].playerstate = PST_LIVE;
1510 P_RestoreMusic(&players[0]);
1511 }
1512 else
1513 op_oldstate = (statenum_t)(players[0].mo->state-states);
1514 }
1515
1516 if (thingarg < COM_Argc())
1517 {
1518 UINT16 mapthingnum = atoi(COM_Argv(thingarg));
1519 mobjtype_t type = P_GetMobjtype(mapthingnum);
1520 if (type == MT_UNKNOWN)
1521 CONS_Printf(M_GetText("No mobj type delegated to thing type %d.\n"), mapthingnum);
1522 else
1523 op_currentthing = type;
1524 }
1525
1526 // If no thing set, then cycle a little
1527 if (!op_currentthing)
1528 {
1529 op_currentthing = 1;
1530 OP_CycleThings(1);
1531 }
1532 else
1533 OP_CycleThings(0); // sets all necessary height values without cycling op_currentthing
1534
1535 P_SetPlayerMobjState(players[0].mo, S_OBJPLACE_DUMMY);
1536 }
1537 // Or are we leaving it instead?
1538 else
1539 {
1540 objectplacing = false;
1541
1542 // Don't touch the NiGHTS Objectplace stuff.
1543 // ... or if the mo mysteriously vanished.
1544 if (!players[0].mo || (players[0].powers[pw_carry] == CR_NIGHTSMODE))
1545 return;
1546
1547 // If still in dummy state, get out of it.
1548 if (players[0].mo->state == &states[S_OBJPLACE_DUMMY])
1549 P_SetPlayerMobjState(players[0].mo, op_oldstate);
1550
1551 // Reset everything back to how it was before we entered objectplace.
1552 P_UnsetThingPosition(players[0].mo);
1553 players[0].mo->flags = op_oldflags1;
1554 players[0].mo->flags2 = op_oldflags2;
1555 players[0].mo->eflags = op_oldeflags;
1556 players[0].pflags = op_oldpflags;
1557 players[0].mo->momx = op_oldmomx;
1558 players[0].mo->momy = op_oldmomy;
1559 players[0].mo->momz = op_oldmomz;
1560 players[0].mo->height = op_oldheight;
1561 P_SetThingPosition(players[0].mo);
1562
1563 // Return their color to normal.
1564 players[0].mo->color = op_oldcolor;
1565
1566 // This is necessary for recovery of dying players.
1567 if (players[0].powers[pw_flashing] >= flashingtics)
1568 players[0].powers[pw_flashing] = flashingtics - 1;
1569 }
1570 }
1571