1 /*
2  * Portions of this file are copyright Rebirth contributors and licensed as
3  * described in COPYING.txt.
4  * Portions of this file are copyright Parallax Software and licensed
5  * according to the Parallax license below.
6  * See COPYING.txt for license details.
7 
8 THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX
9 SOFTWARE CORPORATION ("PARALLAX").  PARALLAX, IN DISTRIBUTING THE CODE TO
10 END-USERS, AND SUBJECT TO ALL OF THE TERMS AND CONDITIONS HEREIN, GRANTS A
11 ROYALTY-FREE, PERPETUAL LICENSE TO SUCH END-USERS FOR USE BY SUCH END-USERS
12 IN USING, DISPLAYING,  AND CREATING DERIVATIVE WORKS THEREOF, SO LONG AS
13 SUCH USE, DISPLAY OR CREATION IS FOR NON-COMMERCIAL, ROYALTY OR REVENUE
14 FREE PURPOSES.  IN NO EVENT SHALL THE END-USER USE THE COMPUTER CODE
15 CONTAINED HEREIN FOR REVENUE-BEARING PURPOSES.  THE END-USER UNDERSTANDS
16 AND AGREES TO THE TERMS HEREIN AND ACCEPTS THE SAME BY USE OF THIS FILE.
17 COPYRIGHT 1993-1999 PARALLAX SOFTWARE CORPORATION.  ALL RIGHTS RESERVED.
18 */
19 
20 /*
21  *
22  * Game loop for Inferno
23  *
24  */
25 
26 #include "dxxsconf.h"
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <stdarg.h>
31 #include <SDL.h>
32 #include <ctime>
33 #if DXX_USE_SCREENSHOT_FORMAT_PNG
34 #include <png.h>
35 #include "vers_id.h"
36 #endif
37 
38 #if DXX_USE_OGL
39 #include "ogl_init.h"
40 #endif
41 
42 #include "pstypes.h"
43 #include "console.h"
44 #include "gr.h"
45 #include "inferno.h"
46 #include "game.h"
47 #include "key.h"
48 #include "config.h"
49 #include "object.h"
50 #include "dxxerror.h"
51 #include "joy.h"
52 #include "pcx.h"
53 #include "timer.h"
54 #include "render.h"
55 #include "laser.h"
56 #include "screens.h"
57 #include "textures.h"
58 #include "gauges.h"
59 #include "3d.h"
60 #include "effects.h"
61 #include "menu.h"
62 #include "player.h"
63 #include "gameseg.h"
64 #include "wall.h"
65 #include "ai.h"
66 #include "fuelcen.h"
67 #include "digi.h"
68 #include "u_mem.h"
69 #include "palette.h"
70 #include "morph.h"
71 #include "lighting.h"
72 #include "newdemo.h"
73 #include "collide.h"
74 #include "weapon.h"
75 #include "sounds.h"
76 #include "args.h"
77 #include "gameseq.h"
78 #include "automap.h"
79 #include "text.h"
80 #include "powerup.h"
81 #include "fireball.h"
82 #include "newmenu.h"
83 #include "gamefont.h"
84 #include "endlevel.h"
85 #include "kconfig.h"
86 #include "mouse.h"
87 #include "switch.h"
88 #include "controls.h"
89 #include "songs.h"
90 
91 #include "multi.h"
92 #include "cntrlcen.h"
93 #include "pcx.h"
94 #include "state.h"
95 #include "piggy.h"
96 #include "ai.h"
97 #include "robot.h"
98 #include "playsave.h"
99 #include "maths.h"
100 #include "hudmsg.h"
101 #if defined(DXX_BUILD_DESCENT_II)
102 #include <climits>
103 #include "gamepal.h"
104 #include "movie.h"
105 #endif
106 #include "event.h"
107 #include "window.h"
108 
109 #if DXX_USE_EDITOR
110 #include "editor/editor.h"
111 #include "editor/esegment.h"
112 #endif
113 
114 #include "d_enumerate.h"
115 #include "d_levelstate.h"
116 #include "d_range.h"
117 #include "compiler-range_for.h"
118 #include "partial_range.h"
119 #include "segiter.h"
120 
121 static fix64 last_timer_value=0;
122 static fix64 sync_timer_value=0;
123 d_time_fix ThisLevelTime;
124 
125 namespace dcx {
126 grs_subcanvas Screen_3d_window;							// The rectangle for rendering the mine to
127 int	force_cockpit_redraw=0;
128 int	PaletteRedAdd, PaletteGreenAdd, PaletteBlueAdd;
129 
130 int	Game_suspended=0; //if non-zero, nothing moves but player
131 game_mode_flags Game_mode;
132 int	Global_missile_firing_count = 0;
133 }
134 
135 //	Function prototypes for GAME.C exclusively.
136 
137 namespace dsx {
138 
139 game_window *Game_wind;
140 
141 namespace {
142 
143 static window_event_result GameProcessFrame(void);
144 static bool FireLaser(player_info &, const control_info &Controls);
145 static void powerup_grab_cheat_all();
146 
147 }
148 
149 #if defined(DXX_BUILD_DESCENT_II)
150 d_flickering_light_state Flickering_light_state;
151 namespace {
152 static void slide_textures(void);
153 static void flicker_lights(const d_level_shared_destructible_light_state &LevelSharedDestructibleLightState, d_flickering_light_state &fls, fvmsegptridx &vmsegptridx);
154 }
155 #endif
156 
157 // Cheats
158 game_cheats cheats;
159 
160 //	==============================================================================================
161 
162 //this is called once per game
init_game()163 void init_game()
164 {
165 	init_objects();
166 
167 	init_special_effects();
168 	Clear_window = 2;		//	do portal only window clear.
169 }
170 
171 }
172 
173 namespace dcx {
174 
reset_palette_add()175 void reset_palette_add()
176 {
177 	PaletteRedAdd 		= 0;
178 	PaletteGreenAdd	= 0;
179 	PaletteBlueAdd		= 0;
180 }
181 
182 #if !DXX_USE_OGL
183 constexpr screen_mode initial_small_game_screen_mode{320, 200};
184 #endif
185 constexpr screen_mode initial_large_game_screen_mode{1024, 768};
186 screen_mode Game_screen_mode = initial_large_game_screen_mode;
187 
188 StereoFormat VR_stereo;
189 fix  VR_eye_width = F1_0;
190 int  VR_eye_offset = 0;
191 int  VR_sync_width = 20;
192 grs_subcanvas VR_hud_left;
193 grs_subcanvas VR_hud_right;
194 }
195 
196 namespace dsx {
197 
init_stereo()198 void init_stereo()
199 {
200 #if DXX_USE_OGL
201 	// init stereo options
202 	if (CGameArg.OglStereo || CGameArg.OglStereoView) {
203 		if (VR_stereo == StereoFormat::None && !VR_eye_offset)
204 			VR_stereo = (CGameArg.OglStereoView) ? static_cast<StereoFormat>(CGameArg.OglStereoView % (static_cast<unsigned>(StereoFormat::HighestFormat) + 1)) : StereoFormat::AboveBelow;
205 		constexpr int half_width_eye_offset = -6;
206 		constexpr int full_width_eye_offset = -12;
207 		switch (VR_stereo)
208 		{
209 			case StereoFormat::None:
210 			case StereoFormat::AboveBelow:
211 			case StereoFormat::AboveBelowSync:
212 				VR_eye_offset = full_width_eye_offset;
213 				break;
214 			case StereoFormat::SideBySideFullHeight:
215 			case StereoFormat::SideBySideHalfHeight:
216 				VR_eye_offset = half_width_eye_offset;
217 				break;
218 		}
219 		VR_eye_width = (F1_0 * 7) / 10;	// Descent 1.5 defaults
220 		VR_sync_width = (20 * SHEIGHT) / 480;
221 		PlayerCfg.CockpitMode[1] = CM_FULL_SCREEN;
222 	}
223 	else {
224 		VR_stereo = StereoFormat::None;
225 	}
226 #endif
227 }
228 
229 //initialize the various canvases on the game screen
230 //called every time the screen mode or cockpit changes
init_cockpit()231 void init_cockpit()
232 {
233 	//Initialize the on-screen canvases
234 
235 	if (Screen_mode != SCREEN_GAME)
236 		return;
237 
238 	if ( Screen_mode == SCREEN_EDITOR )
239 		PlayerCfg.CockpitMode[1] = CM_FULL_SCREEN;
240 
241 #if !DXX_USE_OGL
242 	if (PlayerCfg.CockpitMode[1] != CM_LETTERBOX)
243 	{
244 #if defined(DXX_BUILD_DESCENT_II)
245 		int HiresGFXAvailable = !GameArg.GfxSkipHiresGFX;
246 #endif
247 		auto full_screen_mode = HiresGFXAvailable ? initial_large_game_screen_mode : initial_small_game_screen_mode;
248 		if (Game_screen_mode != full_screen_mode) {
249 			PlayerCfg.CockpitMode[1] = CM_FULL_SCREEN;
250 		}
251 	}
252 #endif
253 
254 	gr_set_default_canvas();
255 	auto &canvas = *grd_curcanv;
256 
257 	switch( PlayerCfg.CockpitMode[1] ) {
258 		case CM_FULL_COCKPIT:
259 			game_init_render_sub_buffers(canvas, 0, 0, SWIDTH, (SHEIGHT*2)/3);
260 			break;
261 
262 		case CM_REAR_VIEW:
263 		{
264 			unsigned x1 = 0, y1 = 0, x2 = SWIDTH, y2 = (SHEIGHT*2)/3;
265 			int mode = PlayerCfg.CockpitMode[1];
266 #if defined(DXX_BUILD_DESCENT_II)
267 			mode += (HIRESMODE?(Num_cockpits/2):0);
268 #endif
269 
270 			PIGGY_PAGE_IN(cockpit_bitmap[mode]);
271 			auto &bm = GameBitmaps[cockpit_bitmap[mode].index];
272 			gr_bitblt_find_transparent_area(bm, x1, y1, x2, y2);
273 			game_init_render_sub_buffers(canvas, x1 * (static_cast<float>(SWIDTH) / bm.bm_w), y1 * (static_cast<float>(SHEIGHT) / bm.bm_h), (x2 - x1 + 1) * (static_cast<float>(SWIDTH) / bm.bm_w), (y2 - y1 + 2) * (static_cast<float>(SHEIGHT) / bm.bm_h));
274 			break;
275 		}
276 
277 		case CM_FULL_SCREEN:
278 			{
279 				unsigned w = SWIDTH;
280 				unsigned h = SHEIGHT;
281 				switch (VR_stereo)
282 				{
283 					case StereoFormat::None:
284 						/* Preserve width */
285 						/* Preserve height */
286 						break;
287 					case StereoFormat::AboveBelow:
288 					case StereoFormat::AboveBelowSync:
289 						/* Preserve width */
290 						/* Change height */
291 						h /= 2;
292 						break;
293 					case StereoFormat::SideBySideHalfHeight:
294 						/* Change width */
295 						/* Change height */
296 						h /= 2;
297 						DXX_BOOST_FALLTHROUGH;
298 					case StereoFormat::SideBySideFullHeight:
299 						/* Change width */
300 						/* Preserve height */
301 						w /= 2;
302 						break;
303 				}
304 				game_init_render_sub_buffers(canvas, 0, 0, w, h);
305 			}
306 			break;
307 
308 		case CM_STATUS_BAR:
309 			game_init_render_sub_buffers(canvas, 0, 0, SWIDTH, (HIRESMODE?(SHEIGHT*2)/2.6:(SHEIGHT*2)/2.72));
310 			break;
311 
312 		case CM_LETTERBOX:	{
313 			const unsigned gsm_height = grd_curscreen->get_screen_height();
314 			const unsigned w = grd_curscreen->get_screen_width();
315 			const unsigned h = (gsm_height * 3) / 4; // true letterbox size (16:9)
316 			const unsigned x = 0;
317 			const unsigned y = (gsm_height - h) / 2;
318 
319 			const uint8_t color = 0;
320 			auto &canvas = *grd_curcanv;
321 			gr_rect(canvas, x, 0, w, gsm_height - h, color);
322 			gr_rect(canvas, x, gsm_height - h, w, gsm_height, color);
323 
324 			game_init_render_sub_buffers(canvas, x, y, w, h);
325 			break;
326 		}
327 	}
328 
329 	gr_set_default_canvas();
330 }
331 
332 }
333 
334 //selects a given cockpit (or lack of one).  See types in game.h
select_cockpit(cockpit_mode_t mode)335 void select_cockpit(cockpit_mode_t mode)
336 {
337 	// skip switching cockpit views while stereo viewport active
338 	if (VR_stereo != StereoFormat::None && mode != CM_FULL_SCREEN)
339 		return;
340 
341 	if (mode != PlayerCfg.CockpitMode[1]) {		//new mode
342 		PlayerCfg.CockpitMode[1]=mode;
343 		init_cockpit();
344 	}
345 }
346 
347 namespace dcx {
348 
349 //force cockpit redraw next time. call this if you've trashed the screen
reset_cockpit()350 void reset_cockpit()
351 {
352 	force_cockpit_redraw=1;
353 	last_drawn_cockpit = -1;
354 }
355 
game_init_render_sub_buffers(grs_canvas & canvas,const int x,const int y,const int w,const int h)356 void game_init_render_sub_buffers(grs_canvas &canvas, const int x, const int y, const int w, const int h)
357 {
358 	gr_clear_canvas(canvas, 0);
359 	gr_init_sub_canvas(Screen_3d_window, canvas, x, y, w, h);
360 
361 	if (VR_stereo != StereoFormat::None)
362 	{
363 		// offset HUD screen rects to force out-of-screen parallax on HUD overlays
364 		const int dx = (VR_eye_offset < 0) ? -VR_eye_offset : 0;
365 		const int dy = VR_sync_width / 2;
366 		struct {
367 			uint16_t x;
368 			uint16_t y;
369 			uint16_t w;
370 			uint16_t h;
371 		} l, r;
372 		switch (VR_stereo) {
373 			case StereoFormat::None:
374 			default:
375 				return;
376 			case StereoFormat::AboveBelow:
377 				l.x = x + dx;
378 				l.y = y;
379 				l.w = r.w = w - dx;
380 				l.h = r.h = h;
381 				r.x = x;
382 				r.y = y + h;
383 				break;
384 			case StereoFormat::AboveBelowSync:
385 				l.x = x + dx;
386 				l.y = y;
387 				l.w = r.w = w - dx;
388 				l.h = r.h = h - dy;
389 				r.x = x;
390 				r.y = y + h + dy;
391 				break;
392 			case StereoFormat::SideBySideFullHeight:
393 				l.x = x + dx;
394 				l.y = r.y = y;
395 				l.w = r.w = w - dx;
396 				l.h = r.h = h;
397 				r.x = x + w;
398 				break;
399 			case StereoFormat::SideBySideHalfHeight:
400 				l.x = x + dx;
401 				l.y = r.y = y + (h / 2);
402 				l.w = r.w = w - dx;
403 				l.h = r.h = h;
404 				r.x = x + w;
405 				break;
406 		}
407 		gr_init_sub_canvas(VR_hud_left,  grd_curscreen->sc_canvas, l.x, l.y, l.w, l.h);
408 		gr_init_sub_canvas(VR_hud_right, grd_curscreen->sc_canvas, r.x, r.y, r.w, r.h);
409 	}
410 }
411 
412 }
413 
414 namespace dsx {
415 
416 //called to change the screen mode. Parameter sm is the new mode, one of
417 //SMODE_GAME or SMODE_EDITOR. returns mode acutally set (could be other
418 //mode if cannot init requested mode)
set_screen_mode(int sm)419 int set_screen_mode(int sm)
420 {
421 	if ( (Screen_mode == sm) && !((sm==SCREEN_GAME) && (grd_curscreen->get_screen_mode() != Game_screen_mode)) && !(sm==SCREEN_MENU) )
422 	{
423 		return 1;
424 	}
425 
426 #if DXX_USE_EDITOR
427 	Canv_editor = NULL;
428 #endif
429 
430 	Screen_mode = sm;
431 
432 #if SDL_MAJOR_VERSION == 1
433 	switch( Screen_mode )
434 	{
435 		case SCREEN_MENU:
436 			if  (grd_curscreen->get_screen_mode() != Game_screen_mode)
437 				if (gr_set_mode(Game_screen_mode))
438 					Error("Cannot set screen mode.");
439 			break;
440 
441 		case SCREEN_GAME:
442 			if  (grd_curscreen->get_screen_mode() != Game_screen_mode)
443 				if (gr_set_mode(Game_screen_mode))
444 					Error("Cannot set screen mode.");
445 			break;
446 #if DXX_USE_EDITOR
447 		case SCREEN_EDITOR:
448 		{
449 			const screen_mode editor_mode{800, 600};
450 			if (grd_curscreen->get_screen_mode() != editor_mode)
451 			{
452 				int gr_error;
453 				if ((gr_error = gr_set_mode(editor_mode)) != 0) { //force into game scrren
454 					Warning("Cannot init editor screen (error=%d)",gr_error);
455 					return 0;
456 				}
457 			}
458 		}
459 			break;
460 #endif
461 #if defined(DXX_BUILD_DESCENT_II)
462 		case SCREEN_MOVIE:
463 		{
464 			const screen_mode movie_mode{MOVIE_WIDTH, MOVIE_HEIGHT};
465 			if (grd_curscreen->get_screen_mode() != movie_mode)
466 			{
467 				if (gr_set_mode(movie_mode))
468 					Error("Cannot set screen mode for game!");
469 				gr_palette_load( gr_palette );
470 			}
471 		}
472 			break;
473 #endif
474 		default:
475 			Error("Invalid screen mode %d",sm);
476 	}
477 #endif
478 	return 1;
479 }
480 
481 }
482 
483 namespace dcx {
484 
485 namespace {
486 
487 class game_world_time_paused
488 {
489 	unsigned time_paused;
490 public:
operator bool() const491 	explicit operator bool() const
492 	{
493 		return time_paused;
494 	}
495 	void increase_pause_count();
496 	void decrease_pause_count();
497 };
498 
499 static game_world_time_paused time_paused;
500 
501 }
502 
increase_pause_count()503 void game_world_time_paused::increase_pause_count()
504 {
505 	if (time_paused==0) {
506 		const fix64 time = timer_update();
507 		last_timer_value = time - last_timer_value;
508 		if (last_timer_value < 0) {
509 			last_timer_value = 0;
510 		}
511 	}
512 	time_paused++;
513 }
514 
decrease_pause_count()515 void game_world_time_paused::decrease_pause_count()
516 {
517 	Assert(time_paused > 0);
518 	--time_paused;
519 	if (time_paused==0) {
520 		const fix64 time = timer_update();
521 		last_timer_value = time - last_timer_value;
522 	}
523 }
524 
start_time()525 void start_time()
526 {
527 	time_paused.decrease_pause_count();
528 }
529 
stop_time()530 void stop_time()
531 {
532 	time_paused.increase_pause_count();
533 }
534 
pause_game_world_time()535 pause_game_world_time::pause_game_world_time()
536 {
537 	stop_time();
538 }
539 
~pause_game_world_time()540 pause_game_world_time::~pause_game_world_time()
541 {
542 	start_time();
543 }
544 
545 namespace {
546 
game_flush_common_inputs()547 static void game_flush_common_inputs()
548 {
549 	event_flush();
550 	key_flush();
551 	joy_flush();
552 	mouse_flush();
553 }
554 
555 }
556 
557 }
558 
559 namespace dsx {
560 
game_flush_inputs(control_info & Controls)561 void game_flush_inputs(control_info &Controls)
562 {
563 	Controls = {};
564 	game_flush_common_inputs();
565 }
566 
game_flush_respawn_inputs(control_info & Controls)567 void game_flush_respawn_inputs(control_info &Controls)
568 {
569 	static_cast<control_info::fire_controls_t &>(Controls.state) = {};
570 }
571 
572 }
573 
574 namespace dcx {
575 
576 /*
577  * timer that every sets d_tick_step true and increments d_tick_count every 1000/DESIGNATED_GAME_FPS ms.
578  */
calc_d_tick()579 void calc_d_tick()
580 {
581 	static fix timer = 0;
582 	auto t = timer + FrameTime;
583 
584 	d_tick_step = t >= DESIGNATED_GAME_FRAMETIME;
585 	if (d_tick_step)
586 	{
587 		d_tick_count++;
588 		if (d_tick_count > 1000000)
589 			d_tick_count = 0;
590 		t -= DESIGNATED_GAME_FRAMETIME;
591 	}
592 	timer = t;
593 }
594 
reset_time()595 void reset_time()
596 {
597 	last_timer_value = timer_update();
598 }
599 
600 }
601 
calc_frame_time()602 void calc_frame_time()
603 {
604 	fix last_frametime = FrameTime;
605 
606 	const auto vsync = CGameCfg.VSync;
607 	const auto bound = f1_0 / (likely(vsync) ? MAXIMUM_FPS : CGameArg.SysMaxFPS);
608 	const auto may_sleep = !CGameArg.SysNoNiceFPS && !vsync;
609 	for (;;)
610 	{
611 		const auto timer_value = timer_update();
612 		FrameTime = timer_value - last_timer_value;
613 		if (FrameTime > 0 && timer_value - sync_timer_value >= bound)
614 		{
615 			last_timer_value = timer_value;
616 
617 			sync_timer_value += bound;
618 			if (sync_timer_value + bound < timer_value) {
619 				sync_timer_value = timer_value;
620 			}
621 			break;
622 		}
623 		if (Game_mode & GM_MULTI)
624 			multi_do_frame(); // during long wait, keep packets flowing
625 		if (may_sleep)
626 			timer_delay_ms(1);
627 	}
628 
629 	if ( cheats.turbo )
630 		FrameTime *= 2;
631 
632 	if (FrameTime < 0)				//if bogus frametime...
633 		FrameTime = (last_frametime==0?1:last_frametime);		//...then use time from last frame
634 
635 	GameTime64 += FrameTime;
636 
637 	calc_d_tick();
638 #ifdef NEWHOMER
639         calc_d_homer_tick();
640 #endif
641 }
642 
643 namespace dsx {
644 
645 #if DXX_USE_EDITOR
move_player_2_segment(const vmsegptridx_t seg,const unsigned side)646 void move_player_2_segment(const vmsegptridx_t seg, const unsigned side)
647 {
648 	auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
649 	auto &Objects = LevelUniqueObjectState.Objects;
650 	auto &Vertices = LevelSharedVertexState.get_vertices();
651 	auto &vmobjptr = Objects.vmptr;
652 	auto &vmobjptridx = Objects.vmptridx;
653 	const auto &&console = vmobjptridx(ConsoleObject);
654 	auto &vcvertptr = Vertices.vcptr;
655 	compute_segment_center(vcvertptr, console->pos, seg);
656 	auto vp = compute_center_point_on_side(vcvertptr, seg, side);
657 	vm_vec_sub2(vp, console->pos);
658 	vm_vector_2_matrix(console->orient, vp, nullptr, nullptr);
659 	obj_relink(vmobjptr, vmsegptr, console, seg);
660 }
661 #endif
662 
663 }
664 
665 namespace dcx {
666 
667 namespace {
668 
669 #if DXX_USE_SCREENSHOT_FORMAT_PNG
670 struct RAIIpng_struct
671 {
672 	png_struct *png_ptr;
673 	png_info *info_ptr = nullptr;
RAIIpng_structdcx::__anon6f31e2830611::RAIIpng_struct674 	RAIIpng_struct(png_struct *const p) :
675 		png_ptr(p)
676 	{
677 	}
~RAIIpng_structdcx::__anon6f31e2830611::RAIIpng_struct678 	~RAIIpng_struct()
679 	{
680 		png_destroy_write_struct(&png_ptr, &info_ptr);
681 	}
682 };
683 
684 struct d_screenshot : RAIIpng_struct
685 {
686 	DXX_INHERIT_CONSTRUCTORS(d_screenshot,RAIIpng_struct);
687 	/* error handling callbacks */
688 	[[noreturn]]
689 	static void png_error_cb(png_struct *png, const char *str);
690 	static void png_warn_cb(png_struct *png, const char *str);
691 	/* output callbacks */
692 	static void png_write_cb(png_struct *png, uint8_t *buf, png_size_t);
693 	static void png_flush_cb(png_struct *png);
694 	class png_exception : std::exception
695 	{
696 	};
697 };
698 
png_error_cb(png_struct * const png,const char * const str)699 void d_screenshot::png_error_cb(png_struct *const png, const char *const str)
700 {
701 	/* libpng requires that this function not return to its caller, and
702 	 * will abort the program if this requirement is violated.  However,
703 	 * throwing an exception that unwinds out past libpng is permitted.
704 	 */
705 	(void)png;
706 	con_printf(CON_URGENT, "libpng error: %s", str);
707 	throw png_exception();
708 }
709 
png_warn_cb(png_struct * const png,const char * const str)710 void d_screenshot::png_warn_cb(png_struct *const png, const char *const str)
711 {
712 	(void)png;
713 	con_printf(CON_URGENT, "libpng warning: %s", str);
714 }
715 
png_write_cb(png_struct * const png,uint8_t * const buf,const png_size_t size)716 void d_screenshot::png_write_cb(png_struct *const png, uint8_t *const buf, const png_size_t size)
717 {
718 	const auto file = reinterpret_cast<PHYSFS_File *>(png_get_io_ptr(png));
719 	PHYSFS_write(file, buf, size, 1);
720 }
721 
png_flush_cb(png_struct * const png)722 void d_screenshot::png_flush_cb(png_struct *const png)
723 {
724 	(void)png;
725 }
726 
record_screenshot_time(const struct tm & tm,png_struct * const png_ptr,png_info * const info_ptr)727 void record_screenshot_time(const struct tm &tm, png_struct *const png_ptr, png_info *const info_ptr)
728 {
729 #ifdef PNG_tIME_SUPPORTED
730 	png_time pt{};
731 	pt.year = tm.tm_year + 1900;
732 	pt.month = tm.tm_mon + 1;
733 	pt.day = tm.tm_mday;
734 	pt.hour = tm.tm_hour;
735 	pt.minute = tm.tm_min;
736 	pt.second = tm.tm_sec;
737 	png_set_tIME(png_ptr, info_ptr, &pt);
738 #else
739 	(void)png_ptr;
740 	(void)info_ptr;
741 	con_printf(CON_NORMAL, "libpng configured without support for time chunk: screenshot will lack time record.");
742 #endif
743 }
744 
745 #ifdef PNG_TEXT_SUPPORTED
record_screenshot_text_metadata(png_struct * const png_ptr,png_info * const info_ptr)746 void record_screenshot_text_metadata(png_struct *const png_ptr, png_info *const info_ptr)
747 {
748 	std::array<png_text, 6> text_fields{};
749 	char descent_version[80];
750 	char descent_build_datetime[21];
751 	std::string current_mission_path;
752 	ntstring<MISSION_NAME_LEN> current_mission_name;
753 	char current_level_number[4];
754 	char viewer_segment[sizeof("65536")];
755 	unsigned idx = 0;
756 	char key_descent_version[] = "Rebirth.version";
757 	{
758 		auto &t = text_fields[idx++];
759 		auto &text = descent_version;
760 		t.key = key_descent_version;
761 		text[sizeof(text) - 1] = 0;
762 		strncpy(text, g_descent_version, sizeof(text) - 1);
763 		t.text = text;
764 		t.compression = PNG_TEXT_COMPRESSION_NONE;
765 	}
766 	char key_descent_build_datetime[] = "Rebirth.build_datetime";
767 	{
768 		auto &t = text_fields[idx++];
769 		auto &text = descent_build_datetime;
770 		t.key = key_descent_build_datetime;
771 		text[sizeof(text) - 1] = 0;
772 		strncpy(text, g_descent_build_datetime, sizeof(text) - 1);
773 		t.text = text;
774 		t.compression = PNG_TEXT_COMPRESSION_NONE;
775 	}
776 	char key_current_mission_path[] = "Rebirth.mission.pathname";
777 	char key_current_mission_name[] = "Rebirth.mission.textname";
778 	char key_viewer_segment[] = "Rebirth.viewer_segment";
779 	char key_current_level_number[] = "Rebirth.current_level_number";
780 	if (const auto current_mission = Current_mission.get())
781 	{
782 		{
783 			auto &t = text_fields[idx++];
784 			t.key = key_current_mission_path;
785 			current_mission_path = current_mission->path;
786 			t.text = &current_mission_path[0];
787 			t.compression = PNG_TEXT_COMPRESSION_NONE;
788 		}
789 		{
790 			auto &t = text_fields[idx++];
791 			t.key = key_current_mission_name;
792 			current_mission_name = current_mission->mission_name;
793 			t.text = current_mission_name.data();
794 			t.compression = PNG_TEXT_COMPRESSION_NONE;
795 		}
796 		{
797 			auto &t = text_fields[idx++];
798 			t.key = key_current_level_number;
799 			t.text = current_level_number;
800 			t.compression = PNG_TEXT_COMPRESSION_NONE;
801 			snprintf(current_level_number, sizeof(current_level_number), "%i", Current_level_num);
802 		}
803 		if (const auto viewer = Viewer)
804 		{
805 			auto &t = text_fields[idx++];
806 			t.key = key_viewer_segment;
807 			t.text = viewer_segment;
808 			t.compression = PNG_TEXT_COMPRESSION_NONE;
809 			snprintf(viewer_segment, sizeof(viewer_segment), "%hu", viewer->segnum);
810 		}
811 	}
812 	png_set_text(png_ptr, info_ptr, text_fields.data(), idx);
813 }
814 #endif
815 
816 #if DXX_USE_OGL
817 #define write_screenshot_png(F,T,B,P)	write_screenshot_png(F,T,B)
818 #endif
write_screenshot_png(PHYSFS_File * const file,const struct tm * const tm,const grs_bitmap & bitmap,const palette_array_t & pal)819 unsigned write_screenshot_png(PHYSFS_File *const file, const struct tm *const tm, const grs_bitmap &bitmap, const palette_array_t &pal)
820 {
821 	const unsigned bm_w = ((bitmap.bm_w + 3) & ~3);
822 	const unsigned bm_h = ((bitmap.bm_h + 3) & ~3);
823 #if DXX_USE_OGL
824 	const unsigned bufsize = bm_w * bm_h * 3;
825 	const auto buf = std::make_unique<uint8_t[]>(bufsize);
826 	const auto begin_byte_buffer = buf.get();
827 	glReadPixels(0, 0, bm_w, bm_h, GL_RGB, GL_UNSIGNED_BYTE, begin_byte_buffer);
828 #else
829 	const unsigned bufsize = bitmap.bm_rowsize * bm_h;
830 	const auto begin_byte_buffer = bitmap.bm_mdata;
831 #endif
832 	d_screenshot ss(png_create_write_struct(PNG_LIBPNG_VER_STRING, &ss, &d_screenshot::png_error_cb, &d_screenshot::png_warn_cb));
833 	if (!ss.png_ptr)
834 	{
835 		con_puts(CON_URGENT, "Cannot save screenshot: libpng png_create_write_struct failed");
836 		return 1;
837 	}
838 	/* Assert that Rebirth type rgb_t is layout compatible with
839 	 * libpng type png_color, so that the Rebirth palette_array_t
840 	 * can be safely reinterpret_cast to an array of png_color.
841 	 * Without this, it would be necessary to copy each rgb_t
842 	 * palette entry into a libpng png_color.
843 	 */
844 	static_assert(sizeof(png_color) == sizeof(rgb_t), "size mismatch");
845 	static_assert(offsetof(png_color, red) == offsetof(rgb_t, r), "red offsetof mismatch");
846 	static_assert(offsetof(png_color, green) == offsetof(rgb_t, g), "green offsetof mismatch");
847 	static_assert(offsetof(png_color, blue) == offsetof(rgb_t, b), "blue offsetof mismatch");
848 	try {
849 		ss.info_ptr = png_create_info_struct(ss.png_ptr);
850 		if (tm)
851 			record_screenshot_time(*tm, ss.png_ptr, ss.info_ptr);
852 		png_set_write_fn(ss.png_ptr, file, &d_screenshot::png_write_cb, &d_screenshot::png_flush_cb);
853 #if DXX_USE_OGL
854 		const auto color_type = PNG_COLOR_TYPE_RGB;
855 #else
856 		png_set_PLTE(ss.png_ptr, ss.info_ptr, reinterpret_cast<const png_color *>(pal.data()), pal.size());
857 		const auto color_type = PNG_COLOR_TYPE_PALETTE;
858 #endif
859 		png_set_IHDR(ss.png_ptr, ss.info_ptr, bm_w, bm_h, 8 /* always 256 colors */, color_type, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
860 #ifdef PNG_TEXT_SUPPORTED
861 		record_screenshot_text_metadata(ss.png_ptr, ss.info_ptr);
862 #endif
863 		png_write_info(ss.png_ptr, ss.info_ptr);
864 		std::array<png_byte *, 1024> row_pointers;
865 		const auto rpb = row_pointers.begin();
866 		auto o = rpb;
867 		const auto end_byte_buffer = begin_byte_buffer + bufsize;
868 #if DXX_USE_OGL
869 		/* OpenGL glReadPixels returns an image with origin in bottom
870 		 * left.  Write rows from end back to beginning, since PNG
871 		 * expects origin in top left.  If rows were written in memory
872 		 * order, the image would be vertically flipped.
873 		 */
874 		const uint_fast32_t stride = bm_w * 3;	/* Without palette, written data is 3-byte-sized RGB tuples of color */
875 		for (auto p = end_byte_buffer; p != begin_byte_buffer;)
876 #else
877 		const uint_fast32_t stride = bm_w;	/* With palette, written data is byte-sized indices into a color table */
878 		/* SDL canvas uses an image with origin in top left.  Write rows
879 		 * in memory order, since this matches the PNG layout.
880 		 */
881 		for (auto p = begin_byte_buffer; p != end_byte_buffer;)
882 #endif
883 		{
884 #if DXX_USE_OGL
885 			p -= stride;
886 #else
887 			p += stride;
888 #endif
889 			*o++ = p;
890 			if (o == row_pointers.end())
891 			{
892 				/* Internal capacity exhausted.  Flush rows and rewind
893 				 * to the beginning of the array.
894 				 */
895 				o = rpb;
896 				png_write_rows(ss.png_ptr, o, row_pointers.size());
897 			}
898 		}
899 		/* Flush any trailing rows */
900 		if (const auto len = o - rpb)
901 			png_write_rows(ss.png_ptr, rpb, len);
902 		png_write_end(ss.png_ptr, ss.info_ptr);
903 		return 0;
904 	} catch (const d_screenshot::png_exception &) {
905 		/* Destructor unwind will handle the exception.  This catch is
906 		 * only required to prevent further propagation.
907 		 *
908 		 * Return nonzero to instruct the caller to delete the failed
909 		 * file.
910 		 */
911 		return 1;
912 	}
913 }
914 #endif
915 
916 }
917 
918 #if DXX_USE_SCREENSHOT
save_screen_shot(int automap_flag)919 void save_screen_shot(int automap_flag)
920 {
921 #if DXX_USE_OGL
922 	if (!CGameArg.DbgGlReadPixelsOk)
923 	{
924 		if (!automap_flag)
925 			HUD_init_message_literal(HM_DEFAULT, "glReadPixels not supported on your configuration");
926 		return;
927 	}
928 #endif
929 #if DXX_USE_SCREENSHOT_FORMAT_PNG
930 #define DXX_SCREENSHOT_FILE_EXTENSION	"png"
931 #elif DXX_USE_SCREENSHOT_FORMAT_LEGACY
932 #if DXX_USE_OGL
933 #define DXX_SCREENSHOT_FILE_EXTENSION	"tga"
934 #else
935 #define DXX_SCREENSHOT_FILE_EXTENSION	"pcx"
936 #endif
937 #endif
938 
939 	if (!PHYSFSX_exists(SCRNS_DIR,0))
940 		PHYSFS_mkdir(SCRNS_DIR); //try making directory
941 
942 	pause_game_world_time p;
943 	unsigned tm_sec;
944 	unsigned tm_min;
945 	unsigned tm_hour;
946 	unsigned tm_mday;
947 	unsigned tm_mon;
948 	unsigned tm_year;
949 	const auto t = time(nullptr);
950 	struct tm *tm = nullptr;
951 	if (t == static_cast<time_t>(-1) || !(tm = gmtime(&t)))
952 		tm_year = tm_mon = tm_mday = tm_hour = tm_min = tm_sec = 0;
953 	else
954 	{
955 		tm_sec = tm->tm_sec;
956 		tm_min = tm->tm_min;
957 		tm_hour = tm->tm_hour;
958 		tm_mday = tm->tm_mday;
959 		tm_mon = tm->tm_mon + 1;
960 		tm_year = tm->tm_year + 1900;
961 	}
962 	/* Colon is not legal in Windows filenames, so use - to separate
963 	 * hour:minute:second.
964 	 */
965 #define DXX_SCREENSHOT_TIME_FORMAT_STRING	SCRNS_DIR "%04u-%02u-%02u.%02u-%02u-%02u"
966 #define DXX_SCREENSHOT_TIME_FORMAT_VALUES	tm_year, tm_mon, tm_mday, tm_hour, tm_min, tm_sec
967 	/* Reserve extra space for the trailing -NN disambiguation.  This
968 	 * is only used if multiple screenshots get the same time, so it is
969 	 * unlikely to be seen in practice and very unlikely to be exhausted
970 	 * (unless the clock is frozen or returns invalid times).
971 	 */
972 	char savename[sizeof(SCRNS_DIR) + sizeof("2000-01-01.00-00-00.NN.ext")];
973 	snprintf(savename, sizeof(savename), DXX_SCREENSHOT_TIME_FORMAT_STRING "." DXX_SCREENSHOT_FILE_EXTENSION, DXX_SCREENSHOT_TIME_FORMAT_VALUES);
974 	for (unsigned savenum = 0; PHYSFS_exists(savename) && savenum != 100; ++savenum)
975 	{
976 		snprintf(savename, sizeof(savename), DXX_SCREENSHOT_TIME_FORMAT_STRING ".%02u." DXX_SCREENSHOT_FILE_EXTENSION, DXX_SCREENSHOT_TIME_FORMAT_VALUES, savenum);
977 #undef DXX_SCREENSHOT_TIME_FORMAT_VALUES
978 #undef DXX_SCREENSHOT_TIME_FORMAT_STRING
979 #undef DXX_SCREENSHOT_FILE_EXTENSION
980 	}
981 	if (const auto &&[file, physfserr] = PHYSFSX_openWriteBuffered(savename); file)
982 	{
983 	if (!automap_flag)
984 		HUD_init_message(HM_DEFAULT, "%s '%s'", TXT_DUMPING_SCREEN, &savename[sizeof(SCRNS_DIR) - 1]);
985 
986 #if DXX_USE_OGL
987 #if !DXX_USE_OGLES
988 	glReadBuffer(GL_FRONT);
989 #endif
990 #if DXX_USE_SCREENSHOT_FORMAT_PNG
991 	auto write_error = write_screenshot_png(file, tm, grd_curscreen->sc_canvas.cv_bitmap, void /* unused */);
992 #elif DXX_USE_SCREENSHOT_FORMAT_LEGACY
993 	write_bmp(file, grd_curscreen->get_screen_width(), grd_curscreen->get_screen_height());
994 	/* write_bmp never fails */
995 	std::false_type write_error;
996 #endif
997 #else
998 	grs_canvas &screen_canv = grd_curscreen->sc_canvas;
999 	palette_array_t pal;
1000 
1001 	const auto &&temp_canv = gr_create_canvas(screen_canv.cv_bitmap.bm_w, screen_canv.cv_bitmap.bm_h);
1002 	gr_ubitmap(*temp_canv, screen_canv.cv_bitmap);
1003 
1004 	gr_palette_read(pal);		//get actual palette from the hardware
1005 	// Correct palette colors
1006 	range_for (auto &i, pal)
1007 	{
1008 		i.r <<= 2;
1009 		i.g <<= 2;
1010 		i.b <<= 2;
1011 	}
1012 #if DXX_USE_SCREENSHOT_FORMAT_PNG
1013 	auto write_error = write_screenshot_png(file, tm, grd_curscreen->sc_canvas.cv_bitmap, pal);
1014 #elif DXX_USE_SCREENSHOT_FORMAT_LEGACY
1015 	auto write_error = pcx_write_bitmap(file, &temp_canv->cv_bitmap, pal);
1016 #endif
1017 #endif
1018 	if (write_error)
1019 		PHYSFS_delete(savename);
1020 	}
1021 	else
1022 	{
1023 		const auto e = PHYSFS_getErrorByCode(physfserr);
1024 		if (!automap_flag)
1025 			HUD_init_message(HM_DEFAULT, "Failed to open screenshot file for writing: %s: %s", &savename[sizeof(SCRNS_DIR) - 1], e);
1026 		else
1027 			con_printf(CON_URGENT, "Failed to open screenshot file for writing: %s: %s", savename, e);
1028 		return;
1029 	}
1030 }
1031 #endif
1032 
1033 //initialize flying
fly_init(object_base & obj)1034 void fly_init(object_base &obj)
1035 {
1036 	obj.control_source = object::control_type::flying;
1037 	obj.movement_source = object::movement_type::physics;
1038 
1039 	obj.mtype.phys_info.velocity = {};
1040 	obj.mtype.phys_info.thrust = {};
1041 	obj.mtype.phys_info.rotvel = {};
1042 	obj.mtype.phys_info.rotthrust = {};
1043 }
1044 
1045 }
1046 
1047 namespace dsx {
1048 
1049 namespace {
1050 
1051 //	------------------------------------------------------------------------------------
do_cloak_stuff(void)1052 static void do_cloak_stuff(void)
1053 {
1054 	auto &Objects = LevelUniqueObjectState.Objects;
1055 	auto &vmobjptr = Objects.vmptr;
1056 	for (auto &&[i, value] : enumerate(partial_range(Players, N_players)))
1057 	{
1058 		auto &plobj = *vmobjptr(value.objnum);
1059 		auto &player_info = plobj.ctype.player_info;
1060 		auto &pl_flags = player_info.powerup_flags;
1061 		if (pl_flags & PLAYER_FLAGS_CLOAKED)
1062 		{
1063 			if (GameTime64 > player_info.cloak_time+CLOAK_TIME_MAX)
1064 			{
1065 				pl_flags &= ~PLAYER_FLAGS_CLOAKED;
1066 				if (i == Player_num) {
1067 					multi_digi_play_sample(SOUND_CLOAK_OFF, F1_0);
1068 					maybe_drop_net_powerup(POW_CLOAK, 1, 0);
1069 					if ( Newdemo_state != ND_STATE_PLAYBACK )
1070 						multi_send_decloak(); // For demo recording
1071 				}
1072 			}
1073 		}
1074 	}
1075 }
1076 
1077 //	------------------------------------------------------------------------------------
do_invulnerable_stuff(player_info & player_info)1078 static void do_invulnerable_stuff(player_info &player_info)
1079 {
1080 	auto &pl_flags = player_info.powerup_flags;
1081 	if (pl_flags & PLAYER_FLAGS_INVULNERABLE)
1082 	{
1083 		if (GameTime64 > player_info.invulnerable_time + INVULNERABLE_TIME_MAX)
1084 		{
1085 			pl_flags &= ~PLAYER_FLAGS_INVULNERABLE;
1086 			if (auto &FakingInvul = player_info.FakingInvul)
1087 			{
1088 				FakingInvul = 0;
1089 				return;
1090 			}
1091 				multi_digi_play_sample(SOUND_INVULNERABILITY_OFF, F1_0);
1092 				if (Game_mode & GM_MULTI)
1093 				{
1094 					maybe_drop_net_powerup(POW_INVULNERABILITY, 1, 0);
1095 				}
1096 		}
1097 	}
1098 }
1099 
1100 }
1101 
1102 #if defined(DXX_BUILD_DESCENT_I)
do_afterburner_stuff(object_array &)1103 static inline void do_afterburner_stuff(object_array &)
1104 {
1105 }
1106 #elif defined(DXX_BUILD_DESCENT_II)
1107 ubyte	Last_afterburner_state = 0;
1108 static fix Last_afterburner_charge;
1109 fix64	Time_flash_last_played;
1110 
1111 #define AFTERBURNER_LOOP_START	((GameArg.SndDigiSampleRate==SAMPLE_RATE_22K)?32027:(32027/2))		//20098
1112 #define AFTERBURNER_LOOP_END		((GameArg.SndDigiSampleRate==SAMPLE_RATE_22K)?48452:(48452/2))		//25776
1113 
1114 namespace {
1115 
do_afterburner_stuff(object_array & Objects)1116 static void do_afterburner_stuff(object_array &Objects)
1117 {
1118 	auto &vmobjptr = Objects.vmptr;
1119 	auto &vcobjptridx = Objects.vcptridx;
1120 	static sbyte func_play = 0;
1121 
1122 	auto &player_info = get_local_plrobj().ctype.player_info;
1123 	const auto have_afterburner = player_info.powerup_flags & PLAYER_FLAGS_AFTERBURNER;
1124 	if (!have_afterburner)
1125 		Afterburner_charge = 0;
1126 
1127 	const auto plobj = vcobjptridx(get_local_player().objnum);
1128 	if (Endlevel_sequence || Player_dead_state != player_dead_state::no)
1129 	{
1130 		digi_kill_sound_linked_to_object(plobj);
1131 		if (Game_mode & GM_MULTI && func_play)
1132 		{
1133 			multi_send_sound_function (0,0);
1134 			func_play = 0;
1135 		}
1136 	}
1137 
1138 	if ((Controls.state.afterburner != Last_afterburner_state && Last_afterburner_charge) || (Last_afterburner_state && Last_afterburner_charge && !Afterburner_charge)) {
1139 		if (Afterburner_charge && Controls.state.afterburner && have_afterburner) {
1140 			digi_link_sound_to_object3(SOUND_AFTERBURNER_IGNITE, plobj, 1, F1_0, sound_stack::allow_stacking, vm_distance{i2f(256)}, AFTERBURNER_LOOP_START, AFTERBURNER_LOOP_END);
1141 			if (Game_mode & GM_MULTI)
1142 			{
1143 				multi_send_sound_function (3,SOUND_AFTERBURNER_IGNITE);
1144 				func_play = 1;
1145 			}
1146 		} else {
1147 			digi_kill_sound_linked_to_object(plobj);
1148 			digi_link_sound_to_object2(SOUND_AFTERBURNER_PLAY, plobj, 0, F1_0, sound_stack::allow_stacking, vm_distance{i2f(256)});
1149 			if (Game_mode & GM_MULTI)
1150 			{
1151 			 	multi_send_sound_function (0,0);
1152 				func_play = 0;
1153 			}
1154 		}
1155 	}
1156 
1157 	//@@if (Controls.state.afterburner && Afterburner_charge)
1158 	//@@	afterburner_shake();
1159 
1160 	Last_afterburner_state = Controls.state.afterburner;
1161 	Last_afterburner_charge = Afterburner_charge;
1162 }
1163 
1164 }
1165 #endif
1166 
1167 //	Amount to diminish guns towards normal, per second.
1168 #define	DIMINISH_RATE 16 // gots to be a power of 2, else change the code in diminish_palette_towards_normal
1169 
1170  //adds to rgb values for palette flash
PALETTE_FLASH_ADD(int _dr,int _dg,int _db)1171 void PALETTE_FLASH_ADD(int _dr, int _dg, int _db)
1172 {
1173 	int	maxval;
1174 
1175 	PaletteRedAdd += _dr;
1176 	PaletteGreenAdd += _dg;
1177 	PaletteBlueAdd += _db;
1178 
1179 #if defined(DXX_BUILD_DESCENT_II)
1180 	if (Flash_effect)
1181 		maxval = 60;
1182 	else
1183 #endif
1184 		maxval = MAX_PALETTE_ADD;
1185 
1186 	if (PaletteRedAdd > maxval)
1187 		PaletteRedAdd = maxval;
1188 
1189 	if (PaletteGreenAdd > maxval)
1190 		PaletteGreenAdd = maxval;
1191 
1192 	if (PaletteBlueAdd > maxval)
1193 		PaletteBlueAdd = maxval;
1194 
1195 	if (PaletteRedAdd < -maxval)
1196 		PaletteRedAdd = -maxval;
1197 
1198 	if (PaletteGreenAdd < -maxval)
1199 		PaletteGreenAdd = -maxval;
1200 
1201 	if (PaletteBlueAdd < -maxval)
1202 		PaletteBlueAdd = -maxval;
1203 }
1204 
1205 }
1206 
diminish_palette_color_toward_zero(int & palette_color_add,const int & dec_amount)1207 static void diminish_palette_color_toward_zero(int& palette_color_add, const int& dec_amount)
1208 {
1209 	if (palette_color_add > 0 ) {
1210 		if (palette_color_add < dec_amount)
1211 			palette_color_add = 0;
1212 		else
1213 			palette_color_add -= dec_amount;
1214 	} else if (palette_color_add < 0 ) {
1215 		if (palette_color_add > -dec_amount )
1216 			palette_color_add = 0;
1217 		else
1218 			palette_color_add += dec_amount;
1219 	}
1220 }
1221 
1222 namespace dsx {
1223 
1224 namespace {
1225 
1226 //	------------------------------------------------------------------------------------
1227 //	Diminish palette effects towards normal.
diminish_palette_towards_normal(void)1228 static void diminish_palette_towards_normal(void)
1229 {
1230 	int	dec_amount = 0;
1231 	float brightness_correction = 1-(static_cast<float>(gr_palette_get_gamma())/64); // to compensate for brightness setting of the game
1232 
1233 	// Diminish at DIMINISH_RATE units/second.
1234 	if (FrameTime < (F1_0/DIMINISH_RATE))
1235 	{
1236 		static fix diminish_timer = 0;
1237 		diminish_timer += FrameTime;
1238 		if (diminish_timer >= (F1_0/DIMINISH_RATE))
1239 		{
1240 			diminish_timer -= (F1_0/DIMINISH_RATE);
1241 			dec_amount = 1;
1242 		}
1243 	}
1244 	else
1245 	{
1246 		dec_amount = f2i(FrameTime*DIMINISH_RATE); // one second = DIMINISH_RATE counts
1247 		if (dec_amount == 0)
1248 			dec_amount++; // make sure we decrement by something
1249 	}
1250 
1251 #if defined(DXX_BUILD_DESCENT_II)
1252 	if (Flash_effect) {
1253 		int	force_do = 0;
1254 		static fix Flash_step_up_timer = 0;
1255 
1256 		// Part of hack system to force update of palette after exiting a menu.
1257 		if (Time_flash_last_played) {
1258 			force_do = 1;
1259 			PaletteRedAdd ^= 1; // Very Tricky! In gr_palette_step_up, if all stepups same as last time, won't do anything!
1260 		}
1261 
1262 		if (Time_flash_last_played + F1_0/8 < GameTime64) {
1263 			digi_play_sample( SOUND_CLOAK_OFF, Flash_effect/4);
1264 			Time_flash_last_played = GameTime64;
1265 		}
1266 
1267 		Flash_effect -= FrameTime;
1268 		Flash_step_up_timer += FrameTime;
1269 		if (Flash_effect < 0)
1270 			Flash_effect = 0;
1271 
1272 		if (force_do || (Flash_step_up_timer >= F1_0/26)) // originally time interval based on (d_rand() > 4096)
1273 		{
1274 			Flash_step_up_timer -= (F1_0/26);
1275 			if ( (Newdemo_state==ND_STATE_RECORDING) && (PaletteRedAdd || PaletteGreenAdd || PaletteBlueAdd) )
1276 				newdemo_record_palette_effect(PaletteRedAdd, PaletteGreenAdd, PaletteBlueAdd);
1277 
1278 			gr_palette_step_up( PaletteRedAdd*brightness_correction, PaletteGreenAdd*brightness_correction, PaletteBlueAdd*brightness_correction );
1279 
1280 			return;
1281 		}
1282 
1283 	}
1284 #endif
1285 
1286 	diminish_palette_color_toward_zero(PaletteRedAdd, dec_amount);
1287 	diminish_palette_color_toward_zero(PaletteGreenAdd, dec_amount);
1288 	diminish_palette_color_toward_zero(PaletteBlueAdd, dec_amount);
1289 
1290 	if ( (Newdemo_state==ND_STATE_RECORDING) && (PaletteRedAdd || PaletteGreenAdd || PaletteBlueAdd) )
1291 		newdemo_record_palette_effect(PaletteRedAdd, PaletteGreenAdd, PaletteBlueAdd);
1292 
1293 	gr_palette_step_up( PaletteRedAdd*brightness_correction, PaletteGreenAdd*brightness_correction, PaletteBlueAdd*brightness_correction );
1294 }
1295 
1296 }
1297 
1298 }
1299 
1300 namespace {
1301 
1302 int	Redsave, Bluesave, Greensave;
1303 
1304 }
1305 
1306 #if defined(DXX_BUILD_DESCENT_II)
1307 static
1308 #endif
palette_save(void)1309 void palette_save(void)
1310 {
1311 	Redsave = PaletteRedAdd; Bluesave = PaletteBlueAdd; Greensave = PaletteGreenAdd;
1312 }
1313 
1314 namespace dsx {
palette_restore(void)1315 void palette_restore(void)
1316 {
1317 	float brightness_correction = 1-(static_cast<float>(gr_palette_get_gamma())/64);
1318 
1319 	PaletteRedAdd = Redsave; PaletteBlueAdd = Bluesave; PaletteGreenAdd = Greensave;
1320 	gr_palette_step_up( PaletteRedAdd*brightness_correction, PaletteGreenAdd*brightness_correction, PaletteBlueAdd*brightness_correction );
1321 
1322 #if defined(DXX_BUILD_DESCENT_II)
1323 	//	Forces flash effect to fixup palette next frame.
1324 	Time_flash_last_played = 0;
1325 #endif
1326 }
1327 
1328 //	--------------------------------------------------------------------------------------------------
allowed_to_fire_laser(const player_info & player_info)1329 bool allowed_to_fire_laser(const player_info &player_info)
1330 {
1331 	if (Player_dead_state != player_dead_state::no)
1332 	{
1333 		Global_missile_firing_count = 0;
1334 		return 0;
1335 	}
1336 
1337 	auto &Next_laser_fire_time = player_info.Next_laser_fire_time;
1338 	//	Make sure enough time has elapsed to fire laser
1339 	if (Next_laser_fire_time > GameTime64)
1340 		return 0;
1341 
1342 	return 1;
1343 }
1344 
allowed_to_fire_missile(const player_info & player_info)1345 int allowed_to_fire_missile(const player_info &player_info)
1346 {
1347 	auto &Next_missile_fire_time = player_info.Next_missile_fire_time;
1348 	//	Make sure enough time has elapsed to fire missile
1349 	if (Next_missile_fire_time > GameTime64)
1350 		return 0;
1351 
1352 	return 1;
1353 }
1354 
1355 #if defined(DXX_BUILD_DESCENT_II)
full_palette_save(void)1356 void full_palette_save(void)
1357 {
1358 	palette_save();
1359 	reset_palette_add();
1360 	gr_palette_load( gr_palette );
1361 }
1362 #endif
1363 
1364 namespace {
1365 
1366 #if DXX_USE_SDLMIXER
1367 #define EXT_MUSIC_TEXT "Jukebox/Audio CD"
1368 #else
1369 #define EXT_MUSIC_TEXT "Audio CD"
1370 #endif
1371 
1372 #if (defined(__APPLE__) || defined(macintosh))
1373 #define _DXX_HELP_MENU_SAVE_LOAD(VERB)	\
1374 	DXX_MENUITEM(VERB, TEXT, "Alt-F2/F3 (\x85-SHIFT-s/o)\t  SAVE/LOAD GAME", HELP_AF2_3)	\
1375 	DXX_MENUITEM(VERB, TEXT, "Alt-Shift-F2/F3 (\x85-s/o)\t  Quick Save/Load", HELP_ASF2_3)
1376 #define _DXX_HELP_MENU_PAUSE(VERB) DXX_MENUITEM(VERB, TEXT, "Pause (\x85-P)\t  Pause", HELP_PAUSE)
1377 
1378 #if DXX_USE_SDL_REDBOOK_AUDIO
1379 #define _DXX_HELP_MENU_AUDIO_REDBOOK(VERB)	\
1380 	DXX_MENUITEM(VERB, TEXT, "\x85-E\t  Eject Audio CD", HELP_ASF9)	\
1381 
1382 #else
1383 #define _DXX_HELP_MENU_AUDIO_REDBOOK(VERB)
1384 #endif
1385 
1386 #define _DXX_HELP_MENU_AUDIO(VERB)	\
1387 	_DXX_HELP_MENU_AUDIO_REDBOOK(VERB)	\
1388 	DXX_MENUITEM(VERB, TEXT, "\x85-Up/Down\t  Play/Pause " EXT_MUSIC_TEXT, HELP_ASF10)	\
1389 	DXX_MENUITEM(VERB, TEXT, "\x85-Left/Right\t  Previous/Next Song", HELP_ASF11_12)
1390 #define _DXX_HELP_MENU_HINT_CMD_KEY(VERB, PREFIX)	\
1391 	DXX_MENUITEM(VERB, TEXT, "", PREFIX##_SEP_HINT_CMD)	\
1392 	DXX_MENUITEM(VERB, TEXT, "(Use \x85-# for F#. e.g. \x85-1 for F1)", PREFIX##_HINT_CMD)
1393 #define _DXX_NETHELP_SAVELOAD_GAME(VERB)	\
1394 	DXX_MENUITEM(VERB, TEXT, "Alt-F2/F3 (\x85-SHIFT-s/\x85-o)\t  SAVE/LOAD COOP GAME", NETHELP_SAVELOAD)
1395 #else
1396 #define _DXX_HELP_MENU_SAVE_LOAD(VERB)	\
1397 	DXX_MENUITEM(VERB, TEXT, "Alt-F2/F3\t  SAVE/LOAD GAME", HELP_AF2_3)	\
1398 	DXX_MENUITEM(VERB, TEXT, "Alt-Shift-F2/F3\t  Fast Save", HELP_ASF2_3)
1399 #define _DXX_HELP_MENU_PAUSE(VERB)	DXX_MENUITEM(VERB, TEXT, TXT_HELP_PAUSE, HELP_PAUSE)
1400 
1401 #if DXX_USE_SDL_REDBOOK_AUDIO
1402 #define _DXX_HELP_MENU_AUDIO_REDBOOK(VERB)	\
1403 	DXX_MENUITEM(VERB, TEXT, "Alt-Shift-F9\t  Eject Audio CD", HELP_ASF9)	\
1404 
1405 #else
1406 #define _DXX_HELP_MENU_AUDIO_REDBOOK(VERB)
1407 #endif
1408 
1409 #define _DXX_HELP_MENU_AUDIO(VERB)	\
1410 	_DXX_HELP_MENU_AUDIO_REDBOOK(VERB)	\
1411 	DXX_MENUITEM(VERB, TEXT, "Alt-Shift-F10\t  Play/Pause " EXT_MUSIC_TEXT, HELP_ASF10)	\
1412 	DXX_MENUITEM(VERB, TEXT, "Alt-Shift-F11/F12\t  Previous/Next Song", HELP_ASF11_12)
1413 #define _DXX_HELP_MENU_HINT_CMD_KEY(VERB, PREFIX)
1414 #define _DXX_NETHELP_SAVELOAD_GAME(VERB)	\
1415 	DXX_MENUITEM(VERB, TEXT, "Alt-F2/F3\t  SAVE/LOAD COOP GAME", NETHELP_SAVELOAD)
1416 #endif
1417 
1418 #if defined(DXX_BUILD_DESCENT_II)
1419 #define _DXX_HELP_MENU_D2_DXX_F4(VERB)	DXX_MENUITEM(VERB, TEXT, TXT_HELP_F4, HELP_F4)
1420 #define _DXX_HELP_MENU_D2_DXX_FEATURES(VERB)	\
1421 	DXX_MENUITEM(VERB, TEXT, "Shift-F1/F2\t  Cycle left/right window", HELP_SF1_2)	\
1422 	DXX_MENUITEM(VERB, TEXT, "Shift-F4\t  GuideBot menu", HELP_SF4)	\
1423 	DXX_MENUITEM(VERB, TEXT, "Alt-Shift-F4\t  Rename GuideBot", HELP_ASF4)	\
1424 	DXX_MENUITEM(VERB, TEXT, "Shift-F5/F6\t  Drop primary/secondary", HELP_SF5_6)	\
1425 	DXX_MENUITEM(VERB, TEXT, "Shift-number\t  GuideBot commands", HELP_GUIDEBOT_COMMANDS)
1426 #define DSX_NETHELP_DROPFLAG(VERB)	\
1427 	DXX_MENUITEM(VERB, TEXT, "ALT-0\t  DROP FLAG", NETHELP_DROPFLAG)
1428 #else
1429 #define _DXX_HELP_MENU_D2_DXX_F4(VERB)
1430 #define _DXX_HELP_MENU_D2_DXX_FEATURES(VERB)
1431 #define DSX_NETHELP_DROPFLAG(VERB)
1432 #endif
1433 
1434 #define DXX_HELP_MENU(VERB)	\
1435 	DXX_MENUITEM(VERB, TEXT, TXT_HELP_ESC, HELP_ESC)	\
1436 	DXX_MENUITEM(VERB, TEXT, "SHIFT-ESC\t  SHOW GAME LOG", HELP_LOG)	\
1437 	DXX_MENUITEM(VERB, TEXT, "F1\t  THIS SCREEN", HELP_HELP)	\
1438 	DXX_MENUITEM(VERB, TEXT, TXT_HELP_F2, HELP_F2)	\
1439 	_DXX_HELP_MENU_SAVE_LOAD(VERB)	\
1440 	DXX_MENUITEM(VERB, TEXT, "F3\t  SWITCH COCKPIT MODES", HELP_F3)	\
1441 	_DXX_HELP_MENU_D2_DXX_F4(VERB)	\
1442 	DXX_MENUITEM(VERB, TEXT, TXT_HELP_F5, HELP_F5)	\
1443 	DXX_MENUITEM(VERB, TEXT, "ALT-F7\t  SWITCH HUD MODES", HELP_AF7)	\
1444 	_DXX_HELP_MENU_PAUSE(VERB)	\
1445 	DXX_MENUITEM(VERB, TEXT, TXT_HELP_PRTSCN, HELP_PRTSCN)	\
1446 	DXX_MENUITEM(VERB, TEXT, TXT_HELP_1TO5, HELP_1TO5)	\
1447 	DXX_MENUITEM(VERB, TEXT, TXT_HELP_6TO10, HELP_6TO10)	\
1448 	_DXX_HELP_MENU_D2_DXX_FEATURES(VERB)	\
1449 	_DXX_HELP_MENU_AUDIO(VERB)	\
1450 	_DXX_HELP_MENU_HINT_CMD_KEY(VERB, HELP)	\
1451 
1452 }
1453 
show_help()1454 void show_help()
1455 {
1456 	struct help_menu_items
1457 	{
1458 		enum {
1459 			DXX_HELP_MENU(ENUM)
1460 		};
1461 		std::array<newmenu_item, DXX_HELP_MENU(COUNT)> m;
1462 		help_menu_items()
1463 		{
1464 			DXX_HELP_MENU(ADD);
1465 		}
1466 	};
1467 	struct help_menu : help_menu_items, passive_newmenu
1468 	{
1469 		help_menu(grs_canvas &src) :
1470 			passive_newmenu(menu_title{nullptr}, menu_subtitle{TXT_KEYS}, menu_filename{nullptr}, tiny_mode_flag::tiny, tab_processing_flag::ignore, adjusted_citem::create(m, 0), src)
1471 		{
1472 		}
1473 	};
1474 	auto menu = window_create<help_menu>(grd_curscreen->sc_canvas);
1475 	(void)menu;
1476 }
1477 
1478 #undef DXX_HELP_MENU
1479 
1480 #define DSX_NETHELP_MENU(VERB)	\
1481 	DXX_MENUITEM(VERB, TEXT, "F1\t  THIS SCREEN", NETHELP_HELP)	\
1482 	DSX_NETHELP_DROPFLAG(VERB)	\
1483 	_DXX_NETHELP_SAVELOAD_GAME(VERB)	\
1484 	DXX_MENUITEM(VERB, TEXT, "ALT-F4\t  SHOW PLAYER NAMES ON HUD", NETHELP_HUDNAMES)	\
1485 	DXX_MENUITEM(VERB, TEXT, "F7\t  TOGGLE KILL LIST", NETHELP_TOGGLE_KILL_LIST)	\
1486 	DXX_MENUITEM(VERB, TEXT, "F8\t  SEND MESSAGE", NETHELP_SENDMSG)	\
1487 	DXX_MENUITEM(VERB, TEXT, "(SHIFT-)F9 to F12\t  (DEFINE)SEND MACRO", NETHELP_MACRO)	\
1488 	DXX_MENUITEM(VERB, TEXT, "PAUSE\t  SHOW NETGAME INFORMATION", NETHELP_GAME_INFO)	\
1489 	DXX_MENUITEM(VERB, TEXT, "SHIFT-PAUSE\t  SHOW NETGAME INFO & RULES", NETHELP_GAME_INFORULES)	\
1490 	_DXX_HELP_MENU_HINT_CMD_KEY(VERB, NETHELP)	\
1491 	DXX_MENUITEM(VERB, TEXT, "", NETHELP_SEP1)	\
1492 	DXX_MENUITEM(VERB, TEXT, "MULTIPLAYER MESSAGE COMMANDS:", NETHELP_COMMAND_HEADER)	\
1493 	DXX_MENUITEM(VERB, TEXT, "(*): TEXT\t  SEND TEXT TO PLAYER/TEAM (*)", NETHELP_DIRECT_MESSAGE)	\
1494 	DXX_MENUITEM(VERB, TEXT, "/Handicap: (*)\t  SET YOUR STARTING SHIELDS TO (*) [10-100]", NETHELP_COMMAND_HANDICAP)	\
1495 	DXX_MENUITEM(VERB, TEXT, "/move: (*)\t  MOVE PLAYER (*) TO OTHER TEAM (Host-only)", NETHELP_COMMAND_MOVE)	\
1496 	DXX_MENUITEM(VERB, TEXT, "/kick: (*)\t  KICK PLAYER (*) FROM GAME (Host-only)", NETHELP_COMMAND_KICK)	\
1497 	DXX_MENUITEM(VERB, TEXT, "/KillReactor\t  BLOW UP THE MINE (Host-only)", NETHELP_COMMAND_KILL_REACTOR)	\
1498 
1499 enum {
1500 	DSX_NETHELP_MENU(ENUM)
1501 };
1502 
show_netgame_help()1503 void show_netgame_help()
1504 {
1505 	struct help_menu_items
1506 	{
1507 		enum {
1508 			DSX_NETHELP_MENU(ENUM)
1509 		};
1510 		std::array<newmenu_item, DSX_NETHELP_MENU(COUNT)> m;
1511 		help_menu_items()
1512 		{
1513 			DSX_NETHELP_MENU(ADD);
1514 		}
1515 	};
1516 	struct help_menu : help_menu_items, passive_newmenu
1517 	{
1518 		help_menu(grs_canvas &src) :
1519 			passive_newmenu(menu_title{nullptr}, menu_subtitle{TXT_KEYS}, menu_filename{nullptr}, tiny_mode_flag::tiny, tab_processing_flag::ignore, adjusted_citem::create(m, 0), src)
1520 		{
1521 		}
1522 	};
1523 	auto menu = window_create<help_menu>(grd_curscreen->sc_canvas);
1524 	(void)menu;
1525 }
1526 
1527 #undef DSX_NETHELP_MENU
1528 
1529 #define DXX_NEWDEMO_HELP_MENU(VERB)	\
1530 	DXX_MENUITEM(VERB, TEXT, "ESC\t  QUIT DEMO PLAYBACK", DEMOHELP_QUIT)	\
1531 	DXX_MENUITEM(VERB, TEXT, "F1\t  THIS SCREEN", DEMOHELP_HELP)	\
1532 	DXX_MENUITEM(VERB, TEXT, TXT_HELP_F2, DEMOHELP_F2)	\
1533 	DXX_MENUITEM(VERB, TEXT, "F3\t  SWITCH COCKPIT MODES", DEMOHELP_F3)	\
1534 	DXX_MENUITEM(VERB, TEXT, "F4\t  TOGGLE PERCENTAGE DISPLAY", DEMOHELP_F4)	\
1535 	DXX_MENUITEM(VERB, TEXT, "UP\t  PLAY", DEMOHELP_PLAY)	\
1536 	DXX_MENUITEM(VERB, TEXT, "DOWN\t  PAUSE", DEMOHELP_PAUSE)	\
1537 	DXX_MENUITEM(VERB, TEXT, "RIGHT\t  ONE FRAME FORWARD", DEMOHELP_FRAME_FORWARD)	\
1538 	DXX_MENUITEM(VERB, TEXT, "LEFT\t  ONE FRAME BACKWARD", DEMOHELP_FRAME_BACKWARD)	\
1539 	DXX_MENUITEM(VERB, TEXT, "SHIFT-RIGHT\t  FAST FORWARD", DEMOHELP_FAST_FORWARD)	\
1540 	DXX_MENUITEM(VERB, TEXT, "SHIFT-LEFT\t  FAST BACKWARD", DEMOHELP_FAST_BACKWARD)	\
1541 	DXX_MENUITEM(VERB, TEXT, "CTRL-RIGHT\t  JUMP TO END", DEMOHELP_JUMP_END)	\
1542 	DXX_MENUITEM(VERB, TEXT, "CTRL-LEFT\t  JUMP TO START", DEMOHELP_JUMP_START)	\
1543 	_DXX_HELP_MENU_HINT_CMD_KEY(VERB, DEMOHELP)	\
1544 
1545 enum {
1546 	DXX_NEWDEMO_HELP_MENU(ENUM)
1547 };
1548 
show_newdemo_help()1549 void show_newdemo_help()
1550 {
1551 	struct help_menu_items
1552 	{
1553 		enum {
1554 			DXX_NEWDEMO_HELP_MENU(ENUM)
1555 		};
1556 		std::array<newmenu_item, DXX_NEWDEMO_HELP_MENU(COUNT)> m;
1557 		help_menu_items()
1558 		{
1559 			DXX_NEWDEMO_HELP_MENU(ADD);
1560 		}
1561 	};
1562 	struct help_menu : help_menu_items, passive_newmenu
1563 	{
1564 		help_menu(grs_canvas &src) :
1565 			passive_newmenu(menu_title{nullptr}, menu_subtitle{"DEMO PLAYBACK CONTROLS"}, menu_filename{nullptr}, tiny_mode_flag::tiny, tab_processing_flag::ignore, adjusted_citem::create(m, 0), src)
1566 		{
1567 		}
1568 	};
1569 	auto menu = window_create<help_menu>(grd_curscreen->sc_canvas);
1570 	(void)menu;
1571 }
1572 
1573 }
1574 
1575 namespace {
1576 
1577 #undef DXX_NEWDEMO_HELP_MENU
1578 
1579 #define LEAVE_TIME 0x4000		//how long until we decide key is down	(Used to be 0x4000)
1580 
1581 enum class leave_type : uint_fast8_t
1582 {
1583 	none,
1584 	maybe_on_release,
1585 	wait_for_release,
1586 	on_press,
1587 };
1588 
1589 static leave_type leave_mode;
1590 
end_rear_view()1591 static void end_rear_view()
1592 {
1593 	Rear_view = 0;
1594 	if (PlayerCfg.CockpitMode[1] == CM_REAR_VIEW)
1595 		select_cockpit(PlayerCfg.CockpitMode[0]);
1596 	if (Newdemo_state == ND_STATE_RECORDING)
1597 		newdemo_record_restore_rearview();
1598 }
1599 
check_end_rear_view()1600 static void check_end_rear_view()
1601 {
1602 	leave_mode = leave_type::none;
1603 	if (Rear_view)
1604 		end_rear_view();
1605 }
1606 
1607 }
1608 
1609 namespace dsx {
1610 
1611 //deal with rear view - switch it on, or off, or whatever
check_rear_view(control_info & Controls)1612 void check_rear_view(control_info &Controls)
1613 {
1614 	static fix64 entry_time;
1615 
1616 	if (Newdemo_state == ND_STATE_PLAYBACK)
1617 		return;
1618 
1619 	const auto rear_view = Controls.state.rear_view;
1620 	switch (leave_mode)
1621 	{
1622 		case leave_type::none:
1623 			if (!rear_view)
1624 				return;
1625 			if (Rear_view)
1626 				end_rear_view();
1627 			else
1628 			{
1629 				Rear_view = 1;
1630 				leave_mode = leave_type::maybe_on_release;		//means wait for another key
1631 				entry_time = timer_query();
1632 				if (PlayerCfg.CockpitMode[1] == CM_FULL_COCKPIT)
1633 					select_cockpit(CM_REAR_VIEW);
1634 				if (Newdemo_state == ND_STATE_RECORDING)
1635 					newdemo_record_rearview();
1636 			}
1637 			return;
1638 		case leave_type::maybe_on_release:
1639 			if (rear_view)
1640 			{
1641 				if (timer_query() - entry_time > LEAVE_TIME)
1642 					leave_mode = leave_type::wait_for_release;
1643 			}
1644 			else
1645 				leave_mode = leave_type::on_press;
1646 			return;
1647 		case leave_type::wait_for_release:
1648 			if (!rear_view)
1649 				check_end_rear_view();
1650 			return;
1651 		case leave_type::on_press:
1652 			if (rear_view)
1653 			{
1654 				Controls.state.rear_view = 0;
1655 				check_end_rear_view();
1656 			}
1657 			return;
1658 		default:
1659 			break;
1660 	}
1661 }
1662 
1663 }
1664 
reset_rear_view(void)1665 void reset_rear_view(void)
1666 {
1667 	if (Rear_view) {
1668 		if (Newdemo_state == ND_STATE_RECORDING)
1669 			newdemo_record_restore_rearview();
1670 	}
1671 
1672 	Rear_view = 0;
1673 	select_cockpit(PlayerCfg.CockpitMode[0]);
1674 }
1675 
cheats_enabled()1676 int cheats_enabled()
1677 {
1678 	return cheats.enabled;
1679 }
1680 
1681 //turns off all cheats & resets cheater flag
game_disable_cheats()1682 void game_disable_cheats()
1683 {
1684 #if defined(DXX_BUILD_DESCENT_II)
1685 	if (cheats.homingfire)
1686 		weapons_homing_all_reset();
1687 #endif
1688 
1689 	cheats = {};
1690 }
1691 
1692 //	game_setup()
1693 // ----------------------------------------------------------------------------
1694 
1695 namespace dsx {
1696 
game_setup()1697 game_window *game_setup()
1698 {
1699 
1700 	PlayerCfg.CockpitMode[1] = PlayerCfg.CockpitMode[0];
1701 	last_drawn_cockpit = -1;	// Force cockpit to redraw next time a frame renders.
1702 	Endlevel_sequence = 0;
1703 
1704 	auto game_wind = window_create<game_window>(grd_curscreen->sc_canvas, 0, 0, SWIDTH, SHEIGHT);
1705 	reset_palette_add();
1706 	init_stereo();
1707 	init_cockpit();
1708 	init_gauges();
1709 	netplayerinfo_on = 0;
1710 
1711 #if DXX_USE_EDITOR
1712 	if (!Cursegp)
1713 	{
1714 		Cursegp = imsegptridx(segment_first);
1715 		Curside = 0;
1716 	}
1717 #endif
1718 
1719 	Viewer = ConsoleObject;
1720 	fly_init(*ConsoleObject);
1721 	Game_suspended = 0;
1722 	reset_time();
1723 	FrameTime = 0;			//make first frame zero
1724 
1725 	fix_object_segs();
1726 	if (CGameArg.SysAutoRecordDemo && Newdemo_state == ND_STATE_NORMAL)
1727 		newdemo_start_recording();
1728 	return game_wind;
1729 }
1730 
1731 // Event handler for the game
event_handler(const d_event & event)1732 window_event_result game_window::event_handler(const d_event &event)
1733 {
1734 	auto result = window_event_result::ignored;
1735 
1736 	switch (event.type)
1737 	{
1738 		case EVENT_WINDOW_ACTIVATED:
1739 			set_screen_mode(SCREEN_GAME);
1740 
1741 			event_toggle_focus(1);
1742 			key_toggle_repeat(0);
1743 			game_flush_inputs(Controls);
1744 
1745 			if (time_paused)
1746 				start_time();
1747 
1748 			if (!((Game_mode & GM_MULTI) && (Newdemo_state != ND_STATE_PLAYBACK)))
1749 				digi_resume_digi_sounds();
1750 
1751 			if (!((Game_mode & GM_MULTI) && (Newdemo_state != ND_STATE_PLAYBACK)))
1752 				palette_restore();
1753 
1754 			reset_cockpit();
1755 			break;
1756 
1757 		case EVENT_WINDOW_DEACTIVATED:
1758 			if (!(((Game_mode & GM_MULTI) && (Newdemo_state != ND_STATE_PLAYBACK)) && (!Endlevel_sequence)) )
1759 				stop_time();
1760 
1761 			if (!((Game_mode & GM_MULTI) && (Newdemo_state != ND_STATE_PLAYBACK)))
1762 				digi_pause_digi_sounds();
1763 
1764 			if (!((Game_mode & GM_MULTI) && (Newdemo_state != ND_STATE_PLAYBACK)))
1765 				full_palette_save();
1766 
1767 			event_toggle_focus(0);
1768 			key_toggle_repeat(1);
1769 			break;
1770 
1771 #if DXX_MAX_BUTTONS_PER_JOYSTICK
1772 		case EVENT_JOYSTICK_BUTTON_UP:
1773 		case EVENT_JOYSTICK_BUTTON_DOWN:
1774 #endif
1775 #if DXX_MAX_AXES_PER_JOYSTICK
1776 		case EVENT_JOYSTICK_MOVED:
1777 #endif
1778 		case EVENT_MOUSE_BUTTON_UP:
1779 		case EVENT_MOUSE_BUTTON_DOWN:
1780 		case EVENT_MOUSE_MOVED:
1781 		case EVENT_KEY_COMMAND:
1782 		case EVENT_KEY_RELEASE:
1783 		case EVENT_IDLE:
1784 			return ReadControls(event, Controls);
1785 
1786 		case EVENT_WINDOW_DRAW:
1787 			if (!time_paused)
1788 			{
1789 				calc_frame_time();
1790 				result = GameProcessFrame();
1791 			}
1792 
1793 			if (!Automap_active)		// efficiency hack
1794 			{
1795 				if (force_cockpit_redraw) {			//screen need redrawing?
1796 					init_cockpit();
1797 					force_cockpit_redraw=0;
1798 				}
1799 				game_render_frame(Controls);
1800 			}
1801 			break;
1802 
1803 		case EVENT_WINDOW_CLOSE:
1804 			digi_stop_digi_sounds();
1805 
1806 			if ( (Newdemo_state == ND_STATE_RECORDING) || (Newdemo_state == ND_STATE_PAUSED) )
1807 				newdemo_stop_recording();
1808 
1809 			multi_leave_game();
1810 
1811 			if ( Newdemo_state == ND_STATE_PLAYBACK )
1812 				newdemo_stop_playback();
1813 
1814 			songs_play_song( SONG_TITLE, 1 );
1815 
1816 			game_disable_cheats();
1817 			Game_mode = {};
1818 #if DXX_USE_EDITOR
1819 			if (!EditorWindow)		// have to do it this way because of the necessary longjmp. Yuck.
1820 #endif
1821 				show_menus();
1822 			event_toggle_focus(0);
1823 			key_toggle_repeat(1);
1824 			Game_wind = nullptr;
1825 			return window_event_result::ignored;
1826 
1827 		case EVENT_LOOP_BEGIN_LOOP:
1828 			kconfig_begin_loop(Controls);
1829 			break;
1830 
1831 		default:
1832 			break;
1833 	}
1834 
1835 	return result;
1836 }
1837 
1838 // Initialise game, actually runs in main event loop
game()1839 void game()
1840 {
1841 	hide_menus();
1842 	Game_wind = game_setup();
1843 }
1844 
1845 }
1846 
1847 //called at the end of the program
close_game()1848 void close_game()
1849 {
1850 	close_gauges();
1851 	restore_effect_bitmap_icons();
1852 }
1853 
1854 #if defined(DXX_BUILD_DESCENT_II)
1855 namespace dsx {
1856 object *Missile_viewer=NULL;
1857 object_signature_t Missile_viewer_sig;
1858 
1859 enumerated_array<game_marker_index, 2, gauge_inset_window_view> Marker_viewer_num{
1860 	{{
1861 		game_marker_index::None,
1862 		game_marker_index::None,
1863 	}}
1864 };
1865 enumerated_array<unsigned, 2, gauge_inset_window_view> Coop_view_player{
1866 	{{
1867 		 UINT_MAX,
1868 		 UINT_MAX
1869 	}}
1870 };
1871 
1872 //returns ptr to escort robot, or NULL
find_escort(fvmobjptridx & vmobjptridx,const d_level_shared_robot_info_state::d_robot_info_array & Robot_info)1873 imobjptridx_t find_escort(fvmobjptridx &vmobjptridx, const d_level_shared_robot_info_state::d_robot_info_array &Robot_info)
1874 {
1875 	range_for (const auto &&o, vmobjptridx)
1876 	{
1877 		if (o->type == OBJ_ROBOT && Robot_info[get_robot_id(o)].companion)
1878 			return imobjptridx_t(o);
1879 	}
1880 	return object_none;
1881 }
1882 
1883 namespace {
1884 //if water or fire level, make occasional sound
do_ambient_sounds(const uint8_t s2_flags)1885 static void do_ambient_sounds(const uint8_t s2_flags)
1886 {
1887 	const auto has_water = (s2_flags & S2F_AMBIENT_WATER);
1888 	sound_effect sound;
1889 	if (s2_flags & S2F_AMBIENT_LAVA)
1890 	{							//has lava
1891 		sound = SOUND_AMBIENT_LAVA;
1892 		if (has_water && (d_rand() & 1))	//both, pick one
1893 			sound = SOUND_AMBIENT_WATER;
1894 	}
1895 	else if (has_water)						//just water
1896 		sound = SOUND_AMBIENT_WATER;
1897 	else
1898 		return;
1899 
1900 	if (((d_rand() << 3) < FrameTime)) {						//play the sound
1901 		fix volume = d_rand() + f1_0/2;
1902 		digi_play_sample(sound,volume);
1903 	}
1904 }
1905 }
1906 }
1907 #endif
1908 
game_leave_menus(void)1909 void game_leave_menus(void)
1910 {
1911 	if (!Game_wind)
1912 		return;
1913 	for (;;) // go through all windows and actually close them if they want to
1914 	{
1915 		const auto wind = window_get_front();
1916 		if (!wind)
1917 			break;
1918 		if (wind == Game_wind)
1919 			break;
1920 		if (!window_close(wind))
1921 			break;
1922 	}
1923 }
1924 
1925 namespace dsx {
1926 
1927 namespace {
1928 
GameProcessFrame()1929 window_event_result GameProcessFrame()
1930 {
1931 	auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
1932 	auto &Objects = LevelUniqueObjectState.Objects;
1933 	auto &vmobjptr = Objects.vmptr;
1934 	auto &plrobj = get_local_plrobj();
1935 	auto &player_info = plrobj.ctype.player_info;
1936 	auto &local_player_shields_ref = plrobj.shields;
1937 	fix player_shields = local_player_shields_ref;
1938 	const auto player_was_dead = Player_dead_state;
1939 	auto result = window_event_result::ignored;
1940 
1941 	state_poll_autosave_game(GameUniqueState, LevelUniqueObjectState);
1942 	update_player_stats();
1943 	diminish_palette_towards_normal();		//	Should leave palette effect up for as long as possible by putting right before render.
1944 	do_afterburner_stuff(Objects);
1945 	do_cloak_stuff();
1946 	do_invulnerable_stuff(player_info);
1947 #if defined(DXX_BUILD_DESCENT_II)
1948 	init_ai_frame(player_info.powerup_flags, Controls);
1949 	result = do_final_boss_frame();
1950 
1951 	auto &pl_flags = player_info.powerup_flags;
1952 	if (pl_flags & PLAYER_FLAGS_HEADLIGHT_ON)
1953 	{
1954 		static int turned_off=0;
1955 		auto &energy = player_info.energy;
1956 		energy -= (FrameTime*3/8);
1957 		if (energy < i2f(10)) {
1958 			if (!turned_off) {
1959 				pl_flags &= ~PLAYER_FLAGS_HEADLIGHT_ON;
1960 				turned_off = 1;
1961 				if (Game_mode & GM_MULTI)
1962 					multi_send_flags(Player_num);
1963 			}
1964 		}
1965 		else
1966 			turned_off = 0;
1967 
1968 		if (energy <= 0)
1969 		{
1970 			energy = 0;
1971 			pl_flags &= ~PLAYER_FLAGS_HEADLIGHT_ON;
1972 			if (Game_mode & GM_MULTI)
1973 				multi_send_flags(Player_num);
1974 		}
1975 	}
1976 #endif
1977 
1978 #if DXX_USE_EDITOR
1979 	check_create_player_path();
1980 	player_follow_path(vmobjptr(ConsoleObject));
1981 #endif
1982 
1983 	if (Game_mode & GM_MULTI)
1984 	{
1985 		result = std::max(multi_do_frame(), result);
1986 		if (Netgame.PlayTimeAllowed.count())
1987 		{
1988 			if (ThisLevelTime >= Netgame.PlayTimeAllowed)
1989 				multi_check_for_killgoal_winner();
1990 			ThisLevelTime += d_time_fix(FrameTime);
1991 		}
1992 	}
1993 
1994 	result = std::max(dead_player_frame(), result);
1995 	if (Newdemo_state != ND_STATE_PLAYBACK)
1996 		result = std::max(do_controlcen_dead_frame(), result);
1997 	if (result == window_event_result::close)
1998 		return result;	// skip everything else - don't set Player_dead_state again
1999 
2000 #if defined(DXX_BUILD_DESCENT_II)
2001 	process_super_mines_frame();
2002 	do_seismic_stuff();
2003 	do_ambient_sounds(vcsegptr(ConsoleObject->segnum)->s2_flags);
2004 #endif
2005 
2006 	digi_sync_sounds();
2007 
2008 	if (Endlevel_sequence) {
2009 		result = std::max(do_endlevel_frame(), result);
2010 		powerup_grab_cheat_all();
2011 		do_special_effects();
2012 		return result;					//skip everything else
2013 	}
2014 
2015 	if ((Newdemo_state != ND_STATE_PLAYBACK) || (Newdemo_vcr_state != ND_STATE_PAUSED)) {
2016 		do_special_effects();
2017 		wall_frame_process();
2018 	}
2019 
2020 	if (LevelUniqueControlCenterState.Control_center_destroyed)
2021 	{
2022 		if (Newdemo_state==ND_STATE_RECORDING )
2023 			newdemo_record_control_center_destroyed();
2024 	}
2025 
2026 	flash_frame();
2027 
2028 	if ( Newdemo_state == ND_STATE_PLAYBACK )
2029 	{
2030 		result = std::max(newdemo_playback_one_frame(), result);
2031 		if ( Newdemo_state != ND_STATE_PLAYBACK )
2032 		{
2033 			Assert(result == window_event_result::close);
2034 			return window_event_result::close;	// Go back to menu
2035 		}
2036 	}
2037 	else
2038 	{ // Note the link to above!
2039 #ifndef NEWHOMER
2040 		player_info.homing_object_dist = -1; // Assume not being tracked.  Laser_do_weapon_sequence modifies this.
2041 #endif
2042 		result = std::max(game_move_all_objects(), result);
2043 		powerup_grab_cheat_all();
2044 
2045 		if (Endlevel_sequence)	//might have been started during move
2046 			return result;
2047 
2048 		fuelcen_update_all();
2049 
2050 		do_ai_frame_all();
2051 
2052 		auto laser_firing_count = FireLaser(player_info, Controls);
2053 		if (auto &Auto_fire_fusion_cannon_time = player_info.Auto_fire_fusion_cannon_time)
2054 		{
2055 			if (player_info.Primary_weapon != primary_weapon_index_t::FUSION_INDEX)
2056 				Auto_fire_fusion_cannon_time = 0;
2057 			else if ((laser_firing_count = (GameTime64 + FrameTime/2 >= Auto_fire_fusion_cannon_time)))
2058 			{
2059 				Auto_fire_fusion_cannon_time = 0;
2060 			} else if (d_tick_step) {
2061 				const auto rx = (d_rand() - 16384) / 8;
2062 				const auto rz = (d_rand() - 16384) / 8;
2063 				const auto &&console = vmobjptr(ConsoleObject);
2064 				auto &rotvel = console->mtype.phys_info.rotvel;
2065 				rotvel.x += rx;
2066 				rotvel.z += rz;
2067 
2068 				const auto bump_amount = player_info.Fusion_charge > F1_0*2 ? player_info.Fusion_charge * 4 : F1_0 * 4;
2069 				bump_one_object(console, make_random_vector(), bump_amount);
2070 			}
2071 		}
2072 
2073 		if (laser_firing_count)
2074 			do_laser_firing_player(plrobj);
2075 		delayed_autoselect(player_info, Controls);
2076 	}
2077 
2078 	if (Do_appearance_effect) {
2079 		Do_appearance_effect = 0;
2080 		create_player_appearance_effect(Vclip, *ConsoleObject);
2081 	}
2082 
2083 #if defined(DXX_BUILD_DESCENT_II)
2084 	omega_charge_frame(player_info);
2085 	slide_textures();
2086 	auto &LevelSharedDestructibleLightState = LevelSharedSegmentState.DestructibleLights;
2087 	flicker_lights(LevelSharedDestructibleLightState, Flickering_light_state, vmsegptridx);
2088 
2089 	//if the player is taking damage, give up guided missile control
2090 	if (local_player_shields_ref != player_shields)
2091 		release_guided_missile(LevelUniqueObjectState, Player_num);
2092 #endif
2093 
2094 	// Check if we have to close in-game menus for multiplayer
2095 	if ((Game_mode & GM_MULTI) && (get_local_player().connected == CONNECT_PLAYING))
2096 	{
2097 		if (Endlevel_sequence || Player_dead_state != player_was_dead || local_player_shields_ref < player_shields || (LevelUniqueControlCenterState.Control_center_destroyed && LevelUniqueControlCenterState.Countdown_seconds_left < 10))
2098                         game_leave_menus();
2099 	}
2100 
2101 	return result;
2102 }
2103 
2104 }
2105 
2106 #if defined(DXX_BUILD_DESCENT_II)
compute_slide_segs()2107 void compute_slide_segs()
2108 {
2109 	auto &TmapInfo = LevelUniqueTmapInfoState.TmapInfo;
2110 	for (const csmusegment suseg : vmsegptr)
2111 	{
2112 		uint8_t slide_textures = 0;
2113 		range_for (const int sidenum, xrange(6u)) {
2114 			const auto &uside = suseg.u.sides[sidenum];
2115 			const auto &ti = TmapInfo[get_texture_index(uside.tmap_num)];
2116 			if (!(ti.slide_u || ti.slide_v))
2117 				continue;
2118 			const auto &sside = suseg.s.sides[sidenum];
2119 			if (IS_CHILD(suseg.s.children[sidenum]) && sside.wall_num == wall_none)
2120 				/* If a wall exists, it could be visible at start or
2121 				 * become visible later, so always enable sliding for
2122 				 * walls.
2123 				 */
2124 				continue;
2125 			slide_textures |= 1 << sidenum;
2126 		}
2127 		suseg.u.slide_textures = slide_textures;
2128 	}
2129 }
2130 
2131 namespace {
2132 
2133 template <fix uvl::*p>
update_uv(std::array<uvl,4> & uvls,uvl & i,fix a)2134 static void update_uv(std::array<uvl, 4> &uvls, uvl &i, fix a)
2135 {
2136 	if (!a)
2137 		return;
2138 	const auto ip = (i.*p += a);
2139 	if (ip > f2_0)
2140 		range_for (auto &j, uvls)
2141 			j.*p -= f1_0;
2142 	else if (ip < -f2_0)
2143 		range_for (auto &j, uvls)
2144 			j.*p += f1_0;
2145 }
2146 
2147 //	-----------------------------------------------------------------------------
slide_textures(void)2148 static void slide_textures(void)
2149 {
2150 	auto &TmapInfo = LevelUniqueTmapInfoState.TmapInfo;
2151 	for (unique_segment &useg : vmsegptr)
2152 	{
2153 		if (const auto slide_seg = useg.slide_textures)
2154 		{
2155 			range_for (const int sidenum, xrange(6u)) {
2156 				if (slide_seg & (1 << sidenum))
2157 				{
2158 					auto &side = useg.sides[sidenum];
2159 					const auto &ti = TmapInfo[get_texture_index(side.tmap_num)];
2160 					const auto tiu = ti.slide_u;
2161 					const auto tiv = ti.slide_v;
2162 					if (tiu || tiv)
2163 					{
2164 						const auto frametime = FrameTime;
2165 						const auto ua = fixmul(frametime, tiu << 8);
2166 						const auto va = fixmul(frametime, tiv << 8);
2167 						auto &uvls = side.uvls;
2168 						range_for (auto &i, uvls)
2169 						{
2170 							update_uv<&uvl::u>(uvls, i, ua);
2171 							update_uv<&uvl::v>(uvls, i, va);
2172 						}
2173 					}
2174 				}
2175 			}
2176 		}
2177 	}
2178 }
2179 
2180 constexpr std::integral_constant<fix, INT32_MIN> flicker_timer_disabled{};
2181 
flicker_lights(const d_level_shared_destructible_light_state & LevelSharedDestructibleLightState,d_flickering_light_state & fls,fvmsegptridx & vmsegptridx)2182 static void flicker_lights(const d_level_shared_destructible_light_state &LevelSharedDestructibleLightState, d_flickering_light_state &fls, fvmsegptridx &vmsegptridx)
2183 {
2184 	auto &TmapInfo = LevelUniqueTmapInfoState.TmapInfo;
2185 	auto &Walls = LevelUniqueWallSubsystemState.Walls;
2186 	auto &vcwallptr = Walls.vcptr;
2187 	range_for (auto &f, partial_range(fls.Flickering_lights, fls.Num_flickering_lights))
2188 	{
2189 		if (f.timer == flicker_timer_disabled)		//disabled
2190 			continue;
2191 		const auto &&segp = vmsegptridx(f.segnum);
2192 		const auto sidenum = f.sidenum;
2193 		{
2194 			auto &side = segp->unique_segment::sides[sidenum];
2195 			if (!(TmapInfo[get_texture_index(side.tmap_num)].lighting || TmapInfo[get_texture_index(side.tmap_num2)].lighting))
2196 				continue;
2197 		}
2198 
2199 		//make sure this is actually a light
2200 		if (! (WALL_IS_DOORWAY(GameBitmaps, Textures, vcwallptr, segp, sidenum) & WALL_IS_DOORWAY_FLAG::render))
2201 			continue;
2202 
2203 		if ((f.timer -= FrameTime) < 0)
2204 		{
2205 			while (f.timer < 0)
2206 				f.timer += f.delay;
2207 			f.mask = ((f.mask & 0x80000000) ? 1 : 0) + (f.mask << 1);
2208 			if (f.mask & 1)
2209 				add_light(LevelSharedDestructibleLightState, segp, sidenum);
2210 			else
2211 				subtract_light(LevelSharedDestructibleLightState, segp, sidenum);
2212 		}
2213 	}
2214 }
2215 
2216 //returns ptr to flickering light structure, or NULL if can't find
find_flicker(d_flickering_light_state & fls,const vmsegidx_t segnum,const unsigned sidenum)2217 static std::pair<d_flickering_light_state::Flickering_light_array_t::iterator, d_flickering_light_state::Flickering_light_array_t::iterator> find_flicker(d_flickering_light_state &fls, const vmsegidx_t segnum, const unsigned sidenum)
2218 {
2219 	//see if there's already an entry for this seg/side
2220 	const auto &&pr = partial_range(fls.Flickering_lights, fls.Num_flickering_lights);
2221 	const auto &&predicate = [segnum, sidenum](const flickering_light &f) {
2222 		return f.segnum == segnum && f.sidenum == sidenum;	//found it!
2223 	};
2224 	const auto &&pe = pr.end();
2225 	return {std::find_if(pr.begin(), pe, predicate), pe};
2226 }
2227 
update_flicker(d_flickering_light_state & fls,const vmsegidx_t segnum,const unsigned sidenum,const fix timer)2228 static void update_flicker(d_flickering_light_state &fls, const vmsegidx_t segnum, const unsigned sidenum, const fix timer)
2229 {
2230 	const auto &&i = find_flicker(fls, segnum, sidenum);
2231 	if (i.first != i.second)
2232 		i.first->timer = timer;
2233 }
2234 
2235 }
2236 
2237 //turn flickering off (because light has been turned off)
disable_flicker(d_flickering_light_state & fls,const vmsegidx_t segnum,const unsigned sidenum)2238 void disable_flicker(d_flickering_light_state &fls, const vmsegidx_t segnum, const unsigned sidenum)
2239 {
2240 	update_flicker(fls, segnum, sidenum, flicker_timer_disabled);
2241 }
2242 
2243 //turn flickering off (because light has been turned on)
enable_flicker(d_flickering_light_state & fls,const vmsegidx_t segnum,const unsigned sidenum)2244 void enable_flicker(d_flickering_light_state &fls, const vmsegidx_t segnum, const unsigned sidenum)
2245 {
2246 	update_flicker(fls, segnum, sidenum, 0);
2247 }
2248 #endif
2249 
2250 namespace {
2251 
2252 //	-----------------------------------------------------------------------------
2253 //	Fire Laser:  Registers a laser fire, and performs special stuff for the fusion
2254 //				    cannon.
FireLaser(player_info & player_info,const control_info & Controls)2255 bool FireLaser(player_info &player_info, const control_info &Controls)
2256 {
2257 	auto &Objects = LevelUniqueObjectState.Objects;
2258 	auto &vmobjptr = Objects.vmptr;
2259 	auto &vmobjptridx = Objects.vmptridx;
2260 	if (!Controls.state.fire_primary)
2261 		return false;
2262 	if (!allowed_to_fire_laser(player_info))
2263 		return false;
2264 	auto &Primary_weapon = player_info.Primary_weapon;
2265 	if (!Weapon_info[Primary_weapon_to_weapon_info[Primary_weapon]].fire_count)
2266 		/* Retail data sets fire_count=1 for all primary weapons */
2267 		return false;
2268 
2269 	if (Primary_weapon == primary_weapon_index_t::FUSION_INDEX)
2270 	{
2271 		auto &energy = player_info.energy;
2272 		auto &Auto_fire_fusion_cannon_time = player_info.Auto_fire_fusion_cannon_time;
2273 		if (energy < F1_0 * 2 && Auto_fire_fusion_cannon_time == 0)
2274 		{
2275 			return false;
2276 		} else {
2277 			static fix64 Fusion_next_sound_time = 0;
2278 
2279 			if (player_info.Fusion_charge == 0)
2280 				energy -= F1_0*2;
2281 
2282 			const auto Fusion_charge = (player_info.Fusion_charge += FrameTime);
2283 			energy -= FrameTime;
2284 
2285 			if (energy <= 0)
2286 			{
2287 				energy = 0;
2288 				Auto_fire_fusion_cannon_time = GameTime64 -1;	//	Fire now!
2289 			} else
2290 				Auto_fire_fusion_cannon_time = GameTime64 + FrameTime/2 + 1;		//	Fire the fusion cannon at this time in the future.
2291 
2292 			{
2293 				int dg, db;
2294 				const int dr = Fusion_charge >> 11;
2295 				if (Fusion_charge < F1_0*2)
2296 					dg = 0, db = dr;
2297 				else
2298 					dg = dr, db = 0;
2299 				PALETTE_FLASH_ADD(dr, dg, db);
2300 			}
2301 
2302 			if (Fusion_next_sound_time > GameTime64 + F1_0/8 + D_RAND_MAX/4) // GameTime64 is smaller than max delay - player in new level?
2303 				Fusion_next_sound_time = GameTime64 - 1;
2304 
2305 			if (Fusion_next_sound_time < GameTime64) {
2306 				if (Fusion_charge > F1_0*2) {
2307 					digi_play_sample( 11, F1_0 );
2308 #if defined(DXX_BUILD_DESCENT_I)
2309 					if(Game_mode & GM_MULTI)
2310 						multi_send_play_sound(11, F1_0, sound_stack::allow_stacking);
2311 #endif
2312 					const auto cobjp = vmobjptridx(ConsoleObject);
2313 					apply_damage_to_player(cobjp, cobjp, d_rand() * 4, 0);
2314 				} else {
2315 					create_awareness_event(vmobjptr(ConsoleObject), player_awareness_type_t::PA_WEAPON_ROBOT_COLLISION, LevelUniqueRobotAwarenessState);
2316 					multi_digi_play_sample(SOUND_FUSION_WARMUP, F1_0);
2317 				}
2318 				Fusion_next_sound_time = GameTime64 + F1_0/8 + d_rand()/4;
2319 			}
2320 		}
2321 	}
2322 	return true;
2323 }
2324 
2325 
2326 //	-------------------------------------------------------------------------------------------------------
2327 //	If player is close enough to objnum, which ought to be a powerup, pick it up!
2328 //	This could easily be made difficulty level dependent.
powerup_grab_cheat(object & player,const vmobjptridx_t powerup)2329 static void powerup_grab_cheat(object &player, const vmobjptridx_t powerup)
2330 {
2331 	fix	powerup_size;
2332 	fix	player_size;
2333 
2334 	Assert(powerup->type == OBJ_POWERUP);
2335 
2336 	powerup_size = powerup->size;
2337 	player_size = player.size;
2338 
2339 	const auto dist = vm_vec_dist_quick(powerup->pos, player.pos);
2340 
2341 	if ((dist < 2*(powerup_size + player_size)) && !(powerup->flags & OF_SHOULD_BE_DEAD)) {
2342 		collide_live_local_player_and_powerup(powerup);
2343 	}
2344 }
2345 
2346 //	-------------------------------------------------------------------------------------------------------
2347 //	Make it easier to pick up powerups.
2348 //	For all powerups in this segment, pick them up at up to twice pickuppable distance based on dot product
2349 //	from player to powerup and player's forward vector.
2350 //	This has the effect of picking them up more easily left/right and up/down, but not making them disappear
2351 //	way before the player gets there.
powerup_grab_cheat_all(void)2352 void powerup_grab_cheat_all(void)
2353 {
2354 	auto &Objects = LevelUniqueObjectState.Objects;
2355 	auto &vmobjptr = Objects.vmptr;
2356 	auto &vmobjptridx = Objects.vmptridx;
2357 	if (Endlevel_sequence)
2358 		return;
2359 	if (Player_dead_state != player_dead_state::no)
2360 		return;
2361 	const auto &&console = vmobjptr(ConsoleObject);
2362 	range_for (const auto objnum, objects_in(vmsegptr(console->segnum), vmobjptridx, vmsegptr))
2363 		if (objnum->type == OBJ_POWERUP)
2364 			powerup_grab_cheat(console, objnum);
2365 }
2366 
2367 }
2368 
2369 }
2370 
2371 #ifdef SHOW_EXIT_PATH
2372 namespace dsx {
2373 namespace {
2374 
2375 //	------------------------------------------------------------------------------------------------------------------
2376 //	Create path for player from current segment to goal segment.
2377 //	Return true if path created, else return false.
mark_player_path_to_segment(const d_vclip_array & Vclip,fvmobjptridx & vmobjptridx,fvmsegptridx & vmsegptridx,segnum_t segnum)2378 static int mark_player_path_to_segment(const d_vclip_array &Vclip, fvmobjptridx &vmobjptridx, fvmsegptridx &vmsegptridx, segnum_t segnum)
2379 {
2380 	int		player_hide_index=-1;
2381 
2382 	if (LevelUniqueObjectState.Level_path_created)
2383 		return 0;
2384 	LevelUniqueObjectState.Level_path_created = 1;
2385 
2386 	auto objp = vmobjptridx(ConsoleObject);
2387 	const auto &&cr = create_path_points(objp, objp->segnum, segnum, Point_segs_free_ptr, 100, create_path_random_flag::nonrandom, create_path_safety_flag::unsafe, segment_none);
2388 	const unsigned player_path_length = cr.second;
2389 	if (cr.first == create_path_result::early)
2390 		return 0;
2391 
2392 	player_hide_index = Point_segs_free_ptr - Point_segs;
2393 	Point_segs_free_ptr += player_path_length;
2394 
2395 	if (Point_segs_free_ptr - Point_segs + MAX_PATH_LENGTH*2 > MAX_POINT_SEGS) {
2396 		ai_reset_all_paths();
2397 		return 0;
2398 	}
2399 
2400 	for (int i=1; i<player_path_length; i++) {
2401 		vms_vector	seg_center;
2402 
2403 		seg_center = Point_segs[player_hide_index+i].point;
2404 
2405 		const auto &&obj = obj_create(OBJ_POWERUP, POW_ENERGY, vmsegptridx(Point_segs[player_hide_index+i].segnum), seg_center, &vmd_identity_matrix, Powerup_info[POW_ENERGY].size, object::control_type::powerup, object::movement_type::None, RT_POWERUP);
2406 		if (obj == object_none) {
2407 			Int3();		//	Unable to drop energy powerup for path
2408 			return 1;
2409 		}
2410 
2411 		obj->rtype.vclip_info.vclip_num = Powerup_info[get_powerup_id(obj)].vclip_num;
2412 		obj->rtype.vclip_info.frametime = Vclip[obj->rtype.vclip_info.vclip_num].frame_time;
2413 		obj->rtype.vclip_info.framenum = 0;
2414 		obj->lifeleft = F1_0*100 + d_rand() * 4;
2415 	}
2416 
2417 	return 1;
2418 }
2419 
2420 }
2421 
2422 //	Return true if it happened, else return false.
create_special_path(void)2423 int create_special_path(void)
2424 {
2425 	auto &Objects = LevelUniqueObjectState.Objects;
2426 	auto &vmobjptridx = Objects.vmptridx;
2427 	//	---------- Find exit doors ----------
2428 	range_for (const auto &&segp, vcsegptridx)
2429 	{
2430 		for (const auto child_segnum : segp->shared_segment::children)
2431 			if (child_segnum == segment_exit)
2432 			{
2433 				return mark_player_path_to_segment(Vclip, vmobjptridx, vmsegptridx, segp);
2434 			}
2435 	}
2436 
2437 	return 0;
2438 }
2439 
2440 }
2441 #endif
2442 
2443 
2444 #if defined(DXX_BUILD_DESCENT_II)
2445 namespace dsx {
2446 /*
2447  * reads a flickering_light structure from a PHYSFS_File
2448  */
flickering_light_read(flickering_light & fl,PHYSFS_File * fp)2449 void flickering_light_read(flickering_light &fl, PHYSFS_File *fp)
2450 {
2451 	fl.segnum = PHYSFSX_readShort(fp);
2452 	fl.sidenum = PHYSFSX_readShort(fp);
2453 	fl.mask = PHYSFSX_readInt(fp);
2454 	fl.timer = PHYSFSX_readFix(fp);
2455 	fl.delay = PHYSFSX_readFix(fp);
2456 }
2457 
flickering_light_write(const flickering_light & fl,PHYSFS_File * fp)2458 void flickering_light_write(const flickering_light &fl, PHYSFS_File *fp)
2459 {
2460 	PHYSFS_writeSLE16(fp, fl.segnum);
2461 	PHYSFS_writeSLE16(fp, fl.sidenum);
2462 	PHYSFS_writeULE32(fp, fl.mask);
2463 	PHYSFSX_writeFix(fp, fl.timer);
2464 	PHYSFSX_writeFix(fp, fl.delay);
2465 }
2466 }
2467 #endif
2468