Subversion Repositories Games.Descent

Rev

Details | Last modification | View Log | RSS feed

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