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 = ¤t_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