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