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
 * Code for rendering external scenes
23
 *
24
 */
25
 
26
//#define _MARK_ON
27
 
28
#include <algorithm>
29
#include <stdlib.h>
30
 
31
 
32
#include <stdio.h>
33
#include <string.h>
34
#include <ctype.h> // for isspace
35
 
36
#include "maths.h"
37
#include "vecmat.h"
38
#include "gr.h"
39
#include "3d.h"
40
#include "dxxerror.h"
41
#include "palette.h"
42
#include "iff.h"
43
#include "console.h"
44
#include "texmap.h"
45
#include "fvi.h"
46
#include "u_mem.h"
47
#include "sounds.h"
48
#include "playsave.h"
49
#include "inferno.h"
50
#include "endlevel.h"
51
#include "object.h"
52
#include "game.h"
53
#include "gamepal.h"
54
#include "screens.h"
55
#include "gauges.h"
56
#include "terrain.h"
57
#include "robot.h"
58
#include "player.h"
59
#include "physfsx.h"
60
#include "bm.h"
61
#include "gameseg.h"
62
#include "gameseq.h"
63
#include "newdemo.h"
64
#include "gamepal.h"
65
#include "multi.h"
66
#include "vclip.h"
67
#include "fireball.h"
68
#include "text.h"
69
#include "digi.h"
70
#include "songs.h"
71
#if defined(DXX_BUILD_DESCENT_II)
72
#include "movie.h"
73
#endif
74
#include "render.h"
75
#include "titles.h"
76
#include "hudmsg.h"
77
#if DXX_USE_OGL
78
#include "ogl_init.h"
79
#endif
80
 
81
#if DXX_USE_EDITOR
82
#include "editor/editor.h"
83
#endif
84
 
85
#include "compiler-range_for.h"
86
#include "d_enumerate.h"
87
#include "d_range.h"
88
#include <iterator>
89
 
90
using std::min;
91
using std::max;
92
 
93
namespace {
94
 
95
struct flythrough_data
96
{
97
        object          *obj;
98
        vms_angvec      angles;                 //orientation in angles
99
        vms_vector      step;                           //how far in a second
100
        vms_vector      angstep;                        //rotation per second
101
        fix                     speed;                  //how fast object is moving
102
        vms_vector      headvec;                        //where we want to be pointing
103
        int                     first_time;             //flag for if first time through
104
        fix                     offset_frac;    //how far off-center as portion of way
105
        fix                     offset_dist;    //how far currently off-center
106
};
107
 
108
#define MAX_FLY_OBJECTS 2
109
 
110
d_unique_endlevel_state UniqueEndlevelState;
111
 
112
}
113
 
114
static std::array<flythrough_data, MAX_FLY_OBJECTS> fly_objects;
115
 
116
//endlevel sequence states
117
 
118
#define EL_OFF                          0               //not in endlevel
119
#define EL_FLYTHROUGH   1               //auto-flythrough in tunnel
120
#define EL_LOOKBACK             2               //looking back at player
121
#define EL_OUTSIDE              3               //flying outside for a while
122
#define EL_STOPPED              4               //stopped, watching explosion
123
#define EL_PANNING              5               //panning around, watching player
124
#define EL_CHASING              6               //chasing player to station
125
 
126
#define SHORT_SEQUENCE  1               //if defined, end sequnce when panning starts
127
 
128
int Endlevel_sequence = 0;
129
 
130
static object *endlevel_camera;
131
 
132
#define FLY_SPEED i2f(50)
133
 
134
static void do_endlevel_flythrough(flythrough_data *flydata);
135
static void draw_stars(grs_canvas &, const d_unique_endlevel_state::starfield_type &stars);
136
static int find_exit_side(const object_base &obj);
137
static void generate_starfield(d_unique_endlevel_state::starfield_type &stars);
138
static void start_endlevel_flythrough(flythrough_data *flydata,const vmobjptr_t obj,fix speed);
139
 
140
#if defined(DXX_BUILD_DESCENT_II)
141
constexpr std::array<const char, 24> movie_table{{
142
        'A','B','C','A',
143
        'D','F','D','F',
144
        'G','H','I','G',
145
        'J','K','L','J',
146
        'M','O','M','O',
147
        'P','Q','P','Q'
148
}};
149
static int endlevel_movie_played = MOVIE_NOT_PLAYED;
150
#endif
151
 
152
 
153
#define FLY_ACCEL i2f(5)
154
 
155
static fix cur_fly_speed,desired_fly_speed;
156
 
157
static grs_bitmap *satellite_bitmap;
158
grs_bitmap *terrain_bitmap;     //!!*exit_bitmap,
159
vms_vector satellite_pos,satellite_upvec;
160
//!!grs_bitmap **exit_bitmap_list[1];
161
unsigned exit_modelnum,destroyed_exit_modelnum;
162
 
163
static vms_vector station_pos{0xf8c4<<10,0x3c1c<<12,0x372<<10};
164
 
165
static vms_vector mine_exit_point;
166
static vms_vector mine_ground_exit_point;
167
static vms_vector mine_side_exit_point;
168
static vms_matrix mine_exit_orient;
169
 
170
static int outside_mine;
171
 
172
static grs_main_bitmap terrain_bm_instance, satellite_bm_instance;
173
 
174
//find delta between two angles
175
static fixang delta_ang(fixang a,fixang b)
176
{
177
        fixang delta0,delta1;
178
 
179
        return (abs(delta0 = a - b) < abs(delta1 = b - a)) ? delta0 : delta1;
180
 
181
}
182
 
183
//return though which side of seg0 is seg1
184
static size_t matt_find_connect_side(const shared_segment &seg0, const vcsegidx_t seg1)
185
{
186
        auto &children = seg0.children;
187
        return std::distance(children.begin(), std::find(children.begin(), children.end(), seg1));
188
}
189
 
190
#if defined(DXX_BUILD_DESCENT_II)
191
#define MOVIE_REQUIRED 1
192
 
193
//returns movie played status.  see movie.h
194
static int start_endlevel_movie()
195
{
196
        int r;
197
        palette_array_t save_pal;
198
 
199
        //Assert(PLAYING_BUILTIN_MISSION); //only play movie for built-in mission
200
 
201
        //Assert(N_MOVIES >= Last_level);
202
        //Assert(N_MOVIES_SECRET >= -Last_secret_level);
203
 
204
        const auto current_level_num = Current_level_num;
205
        if (is_SHAREWARE)
206
                return 0;
207
        if (!is_D2_OEM)
208
                if (current_level_num == Last_level)
209
                        return 1;   //don't play movie
210
 
211
        if (!(current_level_num > 0))
212
                return 0;       //no escapes for secret level
213
        char movie_name[] = "ESA.MVE";
214
        movie_name[2] = movie_table[Current_level_num-1];
215
 
216
        save_pal = gr_palette;
217
 
218
        r=PlayMovie(NULL, movie_name,(Game_mode & GM_MULTI)?0:MOVIE_REQUIRED);
219
 
220
        if (Newdemo_state == ND_STATE_PLAYBACK) {
221
                set_screen_mode(SCREEN_GAME);
222
                gr_palette = save_pal;
223
        }
224
 
225
        return (r);
226
 
227
}
228
#endif
229
 
230
void free_endlevel_data()
231
{
232
        terrain_bm_instance.reset();
233
        satellite_bm_instance.reset();
234
 
235
        free_light_table();
236
        free_height_array();
237
}
238
 
239
static object *external_explosion;
240
static int ext_expl_playing,mine_destroyed;
241
 
242
static vms_angvec exit_angles={-0xa00,0,0};
243
 
244
vms_matrix surface_orient;
245
 
246
static int endlevel_data_loaded;
247
 
248
static unsigned get_tunnel_length(fvcsegptridx &vcsegptridx, const vcsegptridx_t console_seg, const unsigned exit_console_side)
249
{
250
        auto seg = console_seg;
251
        auto exit_side = exit_console_side;
252
        unsigned tunnel_length = 0;
253
        for (;;)
254
        {
255
                const auto child = seg->children[exit_side];
256
                if (child == segment_none)
257
                        return 0;
258
                ++tunnel_length;
259
                if (child == segment_exit)
260
                {
261
                        assert(seg == PlayerUniqueEndlevelState.exit_segnum);
262
                        return tunnel_length;
263
                }
264
                const vcsegidx_t old_segidx = seg;
265
                seg = vcsegptridx(child);
266
                const auto entry_side = matt_find_connect_side(seg, old_segidx);
267
                if (entry_side >= Side_opposite.size())
268
                        return 0;
269
                exit_side = Side_opposite[entry_side];
270
        }
271
}
272
 
273
static vcsegidx_t get_tunnel_transition_segment(const unsigned tunnel_length, const vcsegptridx_t console_seg, const unsigned exit_console_side)
274
{
275
        auto seg = console_seg;
276
        auto exit_side = exit_console_side;
277
        for (auto i = tunnel_length / 3;; --i)
278
        {
279
                /*
280
                 * If the tunnel ended with segment_none, the caller would have
281
                 * returned immediately after the prior loop.  If the tunnel
282
                 * ended with segment_exit, then tunnel_length is the count of
283
                 * segments to reach the exit.  The termination condition on
284
                 * this loop quits at (tunnel_length / 3), so the loop will quit
285
                 * before it reaches segment_exit.
286
                 */
287
                const auto child = seg->children[exit_side];
288
                if (!IS_CHILD(child))
289
                        /* This is only a sanity check.  It should never execute
290
                         * unless there is a bug elsewhere.
291
                         */
292
                        return seg;
293
                if (!i)
294
                        return child;
295
                const vcsegidx_t old_segidx = seg;
296
                seg = vcsegptridx(child);
297
                const auto entry_side = matt_find_connect_side(seg, old_segidx);
298
                exit_side = Side_opposite[entry_side];
299
        }
300
}
301
 
302
namespace dsx {
303
window_event_result start_endlevel_sequence()
304
{
305
        auto &Objects = LevelUniqueObjectState.Objects;
306
        auto &vmobjptr = Objects.vmptr;
307
        reset_rear_view(); //turn off rear view if set - NOTE: make sure this happens before we pause demo recording!!
308
 
309
        if (Newdemo_state == ND_STATE_RECORDING)                // stop demo recording
310
                Newdemo_state = ND_STATE_PAUSED;
311
 
312
        if (Newdemo_state == ND_STATE_PLAYBACK) {               // don't do this if in playback mode
313
#if defined(DXX_BUILD_DESCENT_II)
314
                if (PLAYING_BUILTIN_MISSION) // only play movie for built-in mission
315
                {
316
                        window_set_visible(Game_wind, 0);       // suspend the game, including drawing
317
                        start_endlevel_movie();
318
                        window_set_visible(Game_wind, 1);
319
                }
320
                strcpy(last_palette_loaded,"");         //force palette load next time
321
#endif
322
                return window_event_result::ignored;
323
        }
324
 
325
        if (Player_dead_state != player_dead_state::no ||
326
                (ConsoleObject->flags & OF_SHOULD_BE_DEAD))
327
                return window_event_result::ignored;                            //don't start if dead!
328
        con_puts(CON_NORMAL, "You have escaped the mine!");
329
 
330
#if defined(DXX_BUILD_DESCENT_II)
331
        auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
332
        //      Dematerialize Buddy!
333
        range_for (const auto &&objp, vmobjptr)
334
        {
335
                if (objp->type == OBJ_ROBOT)
336
                        if (Robot_info[get_robot_id(objp)].companion) {
337
                                object_create_explosion(vmsegptridx(objp->segnum), objp->pos, F1_0*7/2, VCLIP_POWERUP_DISAPPEARANCE );
338
                                objp->flags |= OF_SHOULD_BE_DEAD;
339
                        }
340
        }
341
#endif
342
 
343
        get_local_plrobj().ctype.player_info.homing_object_dist = -F1_0; // Turn off homing sound.
344
 
345
        if (Game_mode & GM_MULTI) {
346
                multi_send_endlevel_start(multi_endlevel_type::normal);
347
                multi_do_protocol_frame(1, 1);
348
        }
349
 
350
#if defined(DXX_BUILD_DESCENT_I)
351
        if (!endlevel_data_loaded)
352
#elif defined(DXX_BUILD_DESCENT_II)
353
 
354
        if (PLAYING_BUILTIN_MISSION) // only play movie for built-in mission
355
                if (!(Game_mode & GM_MULTI))
356
                {
357
                        window_set_visible(Game_wind, 0);       // suspend the game, including drawing
358
                        endlevel_movie_played = start_endlevel_movie();
359
                        window_set_visible(Game_wind, 1);
360
                }
361
 
362
        if (!(!(Game_mode & GM_MULTI) && (endlevel_movie_played == MOVIE_NOT_PLAYED) && endlevel_data_loaded))
363
#endif
364
        {
365
 
366
                return PlayerFinishedLevel(0);          //done with level
367
        }
368
#if defined(DXX_BUILD_DESCENT_II)
369
        int exit_models_loaded = 0;
370
 
371
        if (Piggy_hamfile_version < 3)
372
                exit_models_loaded = 1; // built-in for PC shareware
373
 
374
        else
375
                exit_models_loaded = load_exit_models();
376
 
377
        if (!exit_models_loaded)
378
                return window_event_result::ignored;
379
#endif
380
        {
381
                //count segments in exit tunnel
382
 
383
                const object_base &console = vmobjptr(ConsoleObject);
384
                const auto exit_console_side = find_exit_side(console);
385
                const auto &&console_seg = vcsegptridx(console.segnum);
386
                const auto tunnel_length = get_tunnel_length(vcsegptridx, console_seg, exit_console_side);
387
                if (!tunnel_length)
388
                {
389
                                return PlayerFinishedLevel(0);          //don't do special sequence
390
                }
391
                //now pick transition segnum 1/3 of the way in
392
 
393
                PlayerUniqueEndlevelState.transition_segnum = get_tunnel_transition_segment(tunnel_length, console_seg, exit_console_side);
394
        }
395
 
396
        if (Game_mode & GM_MULTI) {
397
                multi_send_endlevel_start(multi_endlevel_type::normal);
398
                multi_do_protocol_frame(1, 1);
399
        }
400
        songs_play_song( SONG_ENDLEVEL, 0 );
401
 
402
        Endlevel_sequence = EL_FLYTHROUGH;
403
 
404
        ConsoleObject->movement_type = MT_NONE;                 //movement handled by flythrough
405
        ConsoleObject->control_type = CT_NONE;
406
 
407
        Game_suspended |= SUSP_ROBOTS;          //robots don't move
408
 
409
        cur_fly_speed = desired_fly_speed = FLY_SPEED;
410
 
411
        start_endlevel_flythrough(&fly_objects[0], vmobjptr(ConsoleObject), cur_fly_speed);             //initialize
412
 
413
        HUD_init_message_literal(HM_DEFAULT, TXT_EXIT_SEQUENCE );
414
 
415
        outside_mine = ext_expl_playing = 0;
416
 
417
        flash_scale = f1_0;
418
 
419
        generate_starfield(UniqueEndlevelState.stars);
420
 
421
        mine_destroyed=0;
422
 
423
        return window_event_result::handled;
424
}
425
}
426
 
427
static vms_angvec player_angles,player_dest_angles;
428
#ifndef SHORT_SEQUENCE
429
static vms_angvec camera_desired_angles,camera_cur_angles;
430
#endif
431
 
432
#define CHASE_TURN_RATE (0x4000/4)              //max turn per second
433
 
434
//returns bitmask of which angles are at dest. bits 0,1,2 = p,b,h
435
static int chase_angles(vms_angvec *cur_angles,vms_angvec *desired_angles)
436
{
437
        vms_angvec delta_angs,alt_angles,alt_delta_angs;
438
        fix total_delta,alt_total_delta;
439
        fix frame_turn;
440
        int mask=0;
441
 
442
        delta_angs.p = desired_angles->p - cur_angles->p;
443
        delta_angs.h = desired_angles->h - cur_angles->h;
444
        delta_angs.b = desired_angles->b - cur_angles->b;
445
 
446
        total_delta = abs(delta_angs.p) + abs(delta_angs.b) + abs(delta_angs.h);
447
 
448
        alt_angles.p = f1_0/2 - cur_angles->p;
449
        alt_angles.b = cur_angles->b + f1_0/2;
450
        alt_angles.h = cur_angles->h + f1_0/2;
451
 
452
        alt_delta_angs.p = desired_angles->p - alt_angles.p;
453
        alt_delta_angs.h = desired_angles->h - alt_angles.h;
454
        alt_delta_angs.b = desired_angles->b - alt_angles.b;
455
 
456
        alt_total_delta = abs(alt_delta_angs.p) + abs(alt_delta_angs.b) + abs(alt_delta_angs.h);
457
 
458
        if (alt_total_delta < total_delta) {
459
                *cur_angles = alt_angles;
460
                delta_angs = alt_delta_angs;
461
        }
462
 
463
        frame_turn = fixmul(FrameTime,CHASE_TURN_RATE);
464
 
465
        if (abs(delta_angs.p) < frame_turn) {
466
                cur_angles->p = desired_angles->p;
467
                mask |= 1;
468
        }
469
        else
470
                if (delta_angs.p > 0)
471
                        cur_angles->p += frame_turn;
472
                else
473
                        cur_angles->p -= frame_turn;
474
 
475
        if (abs(delta_angs.b) < frame_turn) {
476
                cur_angles->b = desired_angles->b;
477
                mask |= 2;
478
        }
479
        else
480
                if (delta_angs.b > 0)
481
                        cur_angles->b += frame_turn;
482
                else
483
                        cur_angles->b -= frame_turn;
484
//cur_angles->b = 0;
485
 
486
        if (abs(delta_angs.h) < frame_turn) {
487
                cur_angles->h = desired_angles->h;
488
                mask |= 4;
489
        }
490
        else
491
                if (delta_angs.h > 0)
492
                        cur_angles->h += frame_turn;
493
                else
494
                        cur_angles->h -= frame_turn;
495
 
496
        return mask;
497
}
498
 
499
window_event_result stop_endlevel_sequence()
500
{
501
#if !DXX_USE_OGL
502
        Interpolation_method = 0;
503
#endif
504
 
505
        select_cockpit(PlayerCfg.CockpitMode[0]);
506
 
507
        Endlevel_sequence = EL_OFF;
508
 
509
        return PlayerFinishedLevel(0);
510
}
511
 
512
#define VCLIP_BIG_PLAYER_EXPLOSION      58
513
 
514
//--unused-- vms_vector upvec = {0,f1_0,0};
515
 
516
//find the angle between the player's heading & the station
517
static void get_angs_to_object(vms_angvec &av,const vms_vector &targ_pos,const vms_vector &cur_pos)
518
{
519
        const auto tv = vm_vec_sub(targ_pos,cur_pos);
520
        vm_extract_angles_vector(av,tv);
521
}
522
 
523
namespace dsx {
524
window_event_result do_endlevel_frame()
525
{
526
        auto &Objects = LevelUniqueObjectState.Objects;
527
        auto &vmobjptr = Objects.vmptr;
528
        static fix timer;
529
        static fix bank_rate;
530
        static fix explosion_wait1=0;
531
        static fix explosion_wait2=0;
532
        static fix ext_expl_halflife;
533
 
534
        auto result = endlevel_move_all_objects();
535
 
536
        if (ext_expl_playing) {
537
 
538
                do_explosion_sequence(vmobjptr(external_explosion));
539
 
540
                if (external_explosion->lifeleft < ext_expl_halflife)
541
                        mine_destroyed = 1;
542
 
543
                if (external_explosion->flags & OF_SHOULD_BE_DEAD)
544
                        ext_expl_playing = 0;
545
        }
546
 
547
        if (cur_fly_speed != desired_fly_speed) {
548
                fix delta = desired_fly_speed - cur_fly_speed;
549
                fix frame_accel = fixmul(FrameTime,FLY_ACCEL);
550
 
551
                if (abs(delta) < frame_accel)
552
                        cur_fly_speed = desired_fly_speed;
553
                else
554
                        if (delta > 0)
555
                                cur_fly_speed += frame_accel;
556
                        else
557
                                cur_fly_speed -= frame_accel;
558
        }
559
 
560
        //do big explosions
561
        if (!outside_mine) {
562
 
563
                if (Endlevel_sequence==EL_OUTSIDE) {
564
                        const auto tvec = vm_vec_sub(ConsoleObject->pos,mine_side_exit_point);
565
                        if (vm_vec_dot(tvec,mine_exit_orient.fvec) > 0) {
566
                                vms_vector mov_vec;
567
 
568
                                outside_mine = 1;
569
 
570
                                const auto &&exit_segp = vmsegptridx(PlayerUniqueEndlevelState.exit_segnum);
571
                                const auto &&tobj = object_create_explosion(exit_segp, mine_side_exit_point, i2f(50), VCLIP_BIG_PLAYER_EXPLOSION);
572
 
573
                                if (tobj) {
574
                                // Move explosion to Viewer to draw it in front of mine exit model
575
                                vm_vec_normalized_dir_quick(mov_vec,Viewer->pos,tobj->pos);
576
                                vm_vec_scale_add2(tobj->pos,mov_vec,i2f(30));
577
                                        external_explosion = tobj;
578
 
579
                                        flash_scale = 0;        //kill lights in mine
580
 
581
                                        ext_expl_halflife = tobj->lifeleft;
582
 
583
                                        ext_expl_playing = 1;
584
                                }
585
 
586
                                digi_link_sound_to_pos(SOUND_BIG_ENDLEVEL_EXPLOSION, exit_segp, 0, mine_side_exit_point, 0, i2f(3)/4);
587
                        }
588
                }
589
 
590
                //do explosions chasing player
591
                if ((explosion_wait1-=FrameTime) < 0) {
592
                        static int sound_count;
593
 
594
                        auto tpnt = vm_vec_scale_add(ConsoleObject->pos,ConsoleObject->orient.fvec,-ConsoleObject->size*5);
595
                        vm_vec_scale_add2(tpnt,ConsoleObject->orient.rvec,(d_rand()-D_RAND_MAX/2)*15);
596
                        vm_vec_scale_add2(tpnt,ConsoleObject->orient.uvec,(d_rand()-D_RAND_MAX/2)*15);
597
 
598
                        const auto &&segnum = find_point_seg(LevelSharedSegmentState, LevelUniqueSegmentState, tpnt, Segments.vmptridx(ConsoleObject->segnum));
599
 
600
                        if (segnum != segment_none) {
601
                                object_create_explosion(segnum,tpnt,i2f(20),VCLIP_BIG_PLAYER_EXPLOSION);
602
                                if (d_rand()<10000 || ++sound_count==7) {               //pseudo-random
603
                                        digi_link_sound_to_pos( SOUND_TUNNEL_EXPLOSION, segnum, 0, tpnt, 0, F1_0 );
604
                                        sound_count=0;
605
                                }
606
                        }
607
 
608
                        explosion_wait1 = 0x2000 + d_rand()/4;
609
 
610
                }
611
        }
612
 
613
        //do little explosions on walls
614
        if (Endlevel_sequence >= EL_FLYTHROUGH && Endlevel_sequence < EL_OUTSIDE)
615
                if ((explosion_wait2-=FrameTime) < 0) {
616
                        fvi_query fq;
617
                        fvi_info hit_data;
618
 
619
                        //create little explosion on wall
620
 
621
                        auto tpnt = vm_vec_copy_scale(ConsoleObject->orient.rvec,(d_rand()-D_RAND_MAX/2)*100);
622
                        vm_vec_scale_add2(tpnt,ConsoleObject->orient.uvec,(d_rand()-D_RAND_MAX/2)*100);
623
                        vm_vec_add2(tpnt,ConsoleObject->pos);
624
 
625
                        if (Endlevel_sequence == EL_FLYTHROUGH)
626
                                vm_vec_scale_add2(tpnt,ConsoleObject->orient.fvec,d_rand()*200);
627
                        else
628
                                vm_vec_scale_add2(tpnt,ConsoleObject->orient.fvec,d_rand()*60);
629
 
630
                        //find hit point on wall
631
 
632
                        fq.p0 = &ConsoleObject->pos;
633
                        fq.p1 = &tpnt;
634
                        fq.startseg = ConsoleObject->segnum;
635
                        fq.rad = 0;
636
                        fq.thisobjnum = object_first;
637
                        fq.ignore_obj_list.first = nullptr;
638
                        fq.flags = 0;
639
 
640
                        find_vector_intersection(fq, hit_data);
641
 
642
                        if (hit_data.hit_type==HIT_WALL && hit_data.hit_seg!=segment_none)
643
                                object_create_explosion(vmsegptridx(hit_data.hit_seg), hit_data.hit_pnt, i2f(3) + d_rand() * 6, VCLIP_SMALL_EXPLOSION);
644
 
645
                        explosion_wait2 = (0xa00 + d_rand()/8)/2;
646
                }
647
 
648
        switch (Endlevel_sequence) {
649
 
650
                case EL_OFF: return result;
651
 
652
                case EL_FLYTHROUGH: {
653
 
654
                        do_endlevel_flythrough(&fly_objects[0]);
655
 
656
                        if (ConsoleObject->segnum == PlayerUniqueEndlevelState.transition_segnum)
657
                        {
658
#if defined(DXX_BUILD_DESCENT_II)
659
                                if (PLAYING_BUILTIN_MISSION && endlevel_movie_played != MOVIE_NOT_PLAYED)
660
                                        result = std::max(stop_endlevel_sequence(), result);
661
                                else
662
#endif
663
                                {
664
 
665
                                        //songs_play_song( SONG_ENDLEVEL, 0 );
666
 
667
                                        Endlevel_sequence = EL_LOOKBACK;
668
 
669
                                        auto objnum = obj_create(OBJ_CAMERA, 0,
670
                                                            vmsegptridx(ConsoleObject->segnum), ConsoleObject->pos, &ConsoleObject->orient, 0,
671
                                                            CT_NONE,MT_NONE,RT_NONE);
672
 
673
                                        if (objnum == object_none) { //can't get object, so abort
674
                                                return std::max(stop_endlevel_sequence(), result);
675
                                        }
676
 
677
                                        Viewer = endlevel_camera = objnum;
678
 
679
                                        select_cockpit(CM_LETTERBOX);
680
 
681
                                        fly_objects[1] = fly_objects[0];
682
                                        fly_objects[1].obj = endlevel_camera;
683
                                        fly_objects[1].speed = (5*cur_fly_speed)/4;
684
                                        fly_objects[1].offset_frac = 0x4000;
685
 
686
                                        vm_vec_scale_add2(endlevel_camera->pos,endlevel_camera->orient.fvec,i2f(7));
687
 
688
                                        timer=0x20000;
689
 
690
                                }
691
                        }
692
 
693
                        break;
694
                }
695
 
696
 
697
                case EL_LOOKBACK: {
698
 
699
                        do_endlevel_flythrough(&fly_objects[0]);
700
                        do_endlevel_flythrough(&fly_objects[1]);
701
 
702
                        if (timer>0) {
703
 
704
                                timer -= FrameTime;
705
 
706
                                if (timer < 0)          //reduce speed
707
                                        fly_objects[1].speed = fly_objects[0].speed;
708
                        }
709
 
710
                        if (endlevel_camera->segnum == PlayerUniqueEndlevelState.exit_segnum)
711
                        {
712
                                Endlevel_sequence = EL_OUTSIDE;
713
 
714
                                timer = i2f(2);
715
 
716
                                vm_vec_negate(endlevel_camera->orient.fvec);
717
                                vm_vec_negate(endlevel_camera->orient.rvec);
718
 
719
                                const auto cam_angles = vm_extract_angles_matrix(endlevel_camera->orient);
720
                                const auto exit_seg_angles = vm_extract_angles_matrix(mine_exit_orient);
721
                                bank_rate = (-exit_seg_angles.b - cam_angles.b)/2;
722
 
723
                                ConsoleObject->control_type = endlevel_camera->control_type = CT_NONE;
724
 
725
                        }
726
 
727
                        break;
728
                }
729
 
730
                case EL_OUTSIDE: {
731
                        vm_vec_scale_add2(ConsoleObject->pos,ConsoleObject->orient.fvec,fixmul(FrameTime,cur_fly_speed));
732
                        vm_vec_scale_add2(endlevel_camera->pos,endlevel_camera->orient.fvec,fixmul(FrameTime,-2*cur_fly_speed));
733
                        vm_vec_scale_add2(endlevel_camera->pos,endlevel_camera->orient.uvec,fixmul(FrameTime,-cur_fly_speed/10));
734
 
735
                        auto cam_angles = vm_extract_angles_matrix(endlevel_camera->orient);
736
                        cam_angles.b += fixmul(bank_rate,FrameTime);
737
                        vm_angles_2_matrix(endlevel_camera->orient,cam_angles);
738
 
739
                        timer -= FrameTime;
740
 
741
                        if (timer < 0) {
742
 
743
                                Endlevel_sequence = EL_STOPPED;
744
 
745
                                vm_extract_angles_matrix(player_angles,ConsoleObject->orient);
746
 
747
                                timer = i2f(3);
748
 
749
                        }
750
 
751
                        break;
752
                }
753
 
754
                case EL_STOPPED: {
755
 
756
                        get_angs_to_object(player_dest_angles,station_pos,ConsoleObject->pos);
757
                        chase_angles(&player_angles,&player_dest_angles);
758
                        vm_angles_2_matrix(ConsoleObject->orient,player_angles);
759
 
760
                        vm_vec_scale_add2(ConsoleObject->pos,ConsoleObject->orient.fvec,fixmul(FrameTime,cur_fly_speed));
761
 
762
                        timer -= FrameTime;
763
 
764
                        if (timer < 0) {
765
 
766
 
767
                                #ifdef SHORT_SEQUENCE
768
 
769
                                result = std::max(stop_endlevel_sequence(), result);
770
 
771
                                #else
772
                                Endlevel_sequence = EL_PANNING;
773
 
774
                                vm_extract_angles_matrix(camera_cur_angles,endlevel_camera->orient);
775
 
776
 
777
                                timer = i2f(3);
778
 
779
                                if (Game_mode & GM_MULTI) { // try to skip part of the seq if multiplayer
780
                                        result = std::max(stop_endlevel_sequence(), result);
781
                                        return result;
782
                                }
783
 
784
                                #endif          //SHORT_SEQUENCE
785
 
786
                        }
787
                        break;
788
                }
789
 
790
                #ifndef SHORT_SEQUENCE
791
                case EL_PANNING: {
792
                        int mask;
793
 
794
                        get_angs_to_object(player_dest_angles,station_pos,ConsoleObject->pos);
795
                        chase_angles(&player_angles,&player_dest_angles);
796
                        vm_angles_2_matrix(ConsoleObject->orient,player_angles);
797
                        vm_vec_scale_add2(ConsoleObject->pos,ConsoleObject->orient.fvec,fixmul(FrameTime,cur_fly_speed));
798
 
799
 
800
                        get_angs_to_object(camera_desired_angles,ConsoleObject->pos,endlevel_camera->pos);
801
                        mask = chase_angles(&camera_cur_angles,&camera_desired_angles);
802
                        vm_angles_2_matrix(endlevel_camera->orient,camera_cur_angles);
803
 
804
                        if ((mask&5) == 5) {
805
 
806
                                vms_vector tvec;
807
 
808
                                Endlevel_sequence = EL_CHASING;
809
 
810
                                vm_vec_normalized_dir_quick(tvec,station_pos,ConsoleObject->pos);
811
                                vm_vector_2_matrix(ConsoleObject->orient,tvec,&surface_orient.uvec,nullptr);
812
 
813
                                desired_fly_speed *= 2;
814
                        }
815
 
816
                        break;
817
                }
818
 
819
                case EL_CHASING: {
820
                        fix d,speed_scale;
821
 
822
 
823
                        get_angs_to_object(camera_desired_angles,ConsoleObject->pos,endlevel_camera->pos);
824
                        chase_angles(&camera_cur_angles,&camera_desired_angles);
825
 
826
                        vm_angles_2_matrix(endlevel_camera->orient,camera_cur_angles);
827
 
828
                        d = vm_vec_dist_quick(ConsoleObject->pos,endlevel_camera->pos);
829
 
830
                        speed_scale = fixdiv(d,i2f(0x20));
831
                        if (d<f1_0) d=f1_0;
832
 
833
                        get_angs_to_object(player_dest_angles,station_pos,ConsoleObject->pos);
834
                        chase_angles(&player_angles,&player_dest_angles);
835
                        vm_angles_2_matrix(ConsoleObject->orient,player_angles);
836
 
837
                        vm_vec_scale_add2(ConsoleObject->pos,ConsoleObject->orient.fvec,fixmul(FrameTime,cur_fly_speed));
838
                        vm_vec_scale_add2(endlevel_camera->pos,endlevel_camera->orient.fvec,fixmul(FrameTime,fixmul(speed_scale,cur_fly_speed)));
839
 
840
                        if (vm_vec_dist(ConsoleObject->pos,station_pos) < i2f(10))
841
                                result = std::max(stop_endlevel_sequence(), result);
842
 
843
                        break;
844
 
845
                }
846
                #endif          //ifdef SHORT_SEQUENCE
847
 
848
        }
849
 
850
        return result;
851
}
852
}
853
 
854
 
855
#define MIN_D 0x100
856
 
857
//find which side to fly out of
858
static int find_exit_side(const object_base &obj)
859
{
860
        auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
861
        auto &Vertices = LevelSharedVertexState.get_vertices();
862
        vms_vector prefvec;
863
        fix best_val=-f2_0;
864
        int best_side;
865
 
866
        //find exit side
867
 
868
        vm_vec_normalized_dir_quick(prefvec, obj.pos, LevelUniqueObjectState.last_console_player_position);
869
 
870
        const shared_segment &pseg = *vcsegptr(obj.segnum);
871
        auto &vcvertptr = Vertices.vcptr;
872
        const auto segcenter = compute_segment_center(vcvertptr, pseg);
873
 
874
        best_side=-1;
875
        for (int i=MAX_SIDES_PER_SEGMENT;--i >= 0;) {
876
                fix d;
877
 
878
                if (pseg.children[i] != segment_none)
879
                {
880
                        auto sidevec = compute_center_point_on_side(vcvertptr, pseg, i);
881
                        vm_vec_normalized_dir_quick(sidevec,sidevec,segcenter);
882
                        d = vm_vec_dot(sidevec,prefvec);
883
 
884
                        if (labs(d) < MIN_D) d=0;
885
 
886
                        if (d > best_val) {best_val=d; best_side=i;}
887
 
888
                }
889
        }
890
 
891
        Assert(best_side!=-1);
892
 
893
        return best_side;
894
}
895
 
896
static void draw_mine_exit_cover(grs_canvas &canvas)
897
{
898
        const int of = 10;
899
        const fix u = i2f(6), d = i2f(9), ur = i2f(14), dr = i2f(17);
900
        const uint8_t color = BM_XRGB(0, 0, 0);
901
        vms_vector v;
902
        g3s_point p0, p1, p2, p3;
903
 
904
        vm_vec_scale_add(v,mine_exit_point,mine_exit_orient.fvec,i2f(of));
905
 
906
        auto mrd = mine_exit_orient.rvec;
907
        {
908
                vms_vector vu;
909
                vm_vec_scale_add(vu, v, mine_exit_orient.uvec, u);
910
                auto mru = mrd;
911
                vm_vec_scale(mru, ur);
912
                vms_vector p;
913
                g3_rotate_point(p0, (vm_vec_add(p, vu, mru), p));
914
                g3_rotate_point(p1, (vm_vec_sub(p, vu, mru), p));
915
        }
916
        {
917
                vms_vector vd;
918
                vm_vec_scale_add(vd, v, mine_exit_orient.uvec, -d);
919
                vm_vec_scale(mrd, dr);
920
                vms_vector p;
921
                g3_rotate_point(p2, (vm_vec_sub(p, vd, mrd), p));
922
                g3_rotate_point(p3, (vm_vec_add(p, vd, mrd), p));
923
        }
924
        const std::array<cg3s_point *, 4> pointlist{{
925
                &p0,
926
                &p1,
927
                &p2,
928
                &p3,
929
        }};
930
 
931
        g3_draw_poly(canvas, pointlist.size(), pointlist, color);
932
}
933
 
934
void draw_exit_model(grs_canvas &canvas)
935
{
936
        int f=15,u=0;   //21;
937
        g3s_lrgb lrgb = { f1_0, f1_0, f1_0 };
938
 
939
        if (mine_destroyed)
940
        {
941
                // draw black shape to mask out terrain in exit hatch
942
                draw_mine_exit_cover(canvas);
943
        }
944
 
945
        auto model_pos = vm_vec_scale_add(mine_exit_point,mine_exit_orient.fvec,i2f(f));
946
        vm_vec_scale_add2(model_pos,mine_exit_orient.uvec,i2f(u));
947
        draw_polygon_model(canvas, model_pos, mine_exit_orient, nullptr, mine_destroyed ? destroyed_exit_modelnum : exit_modelnum, 0, lrgb, nullptr, nullptr);
948
}
949
 
950
static int exit_point_bmx,exit_point_bmy;
951
 
952
static fix satellite_size = i2f(400);
953
 
954
#define SATELLITE_DIST          i2f(1024)
955
#define SATELLITE_WIDTH         satellite_size
956
#define SATELLITE_HEIGHT        ((satellite_size*9)/4)          //((satellite_size*5)/2)
957
 
958
constexpr vms_vector vmd_zero_vector{};
959
 
960
namespace dsx {
961
 
962
static void render_external_scene(fvcobjptridx &vcobjptridx, grs_canvas &canvas, const d_level_unique_light_state &LevelUniqueLightState, const fix eye_offset)
963
{
964
        auto &Objects = LevelUniqueObjectState.Objects;
965
        auto &vmobjptridx = Objects.vmptridx;
966
#if DXX_USE_OGL
967
        int orig_Render_depth = Render_depth;
968
#endif
969
        g3s_lrgb lrgb = { f1_0, f1_0, f1_0 };
970
 
971
        auto Viewer_eye = Viewer->pos;
972
 
973
        if (eye_offset)
974
                vm_vec_scale_add2(Viewer_eye,Viewer->orient.rvec,eye_offset);
975
 
976
        g3_set_view_matrix(Viewer->pos,Viewer->orient,Render_zoom);
977
 
978
        //g3_draw_horizon(BM_XRGB(0,0,0),BM_XRGB(16,16,16));            //,-1);
979
        gr_clear_canvas(canvas, BM_XRGB(0,0,0));
980
 
981
        g3_start_instance_matrix(vmd_zero_vector, surface_orient);
982
        draw_stars(canvas, UniqueEndlevelState.stars);
983
        g3_done_instance();
984
 
985
        {       //draw satellite
986
 
987
                vms_vector delta;
988
                g3s_point top_pnt;
989
 
990
                const auto p = g3_rotate_point(satellite_pos);
991
                g3_rotate_delta_vec(delta,satellite_upvec);
992
 
993
                g3_add_delta_vec(top_pnt,p,delta);
994
 
995
                if (! (p.p3_codes & CC_BEHIND)) {
996
                        //p.p3_flags &= ~PF_PROJECTED;
997
                        //g3_project_point(&p);
998
                        if (! (p.p3_flags & PF_OVERFLOW)) {
999
                                push_interpolation_method save_im(0);
1000
                                //gr_bitmapm(f2i(p.p3_sx)-32,f2i(p.p3_sy)-32,satellite_bitmap);
1001
                                g3_draw_rod_tmap(canvas, *satellite_bitmap, p, SATELLITE_WIDTH, top_pnt, SATELLITE_WIDTH, lrgb);
1002
                        }
1003
                }
1004
        }
1005
 
1006
#if DXX_USE_OGL
1007
        ogl_toggle_depth_test(0);
1008
        Render_depth = (200-(vm_vec_dist_quick(mine_ground_exit_point, Viewer_eye)/F1_0))/36;
1009
#endif
1010
        render_terrain(canvas, Viewer_eye, mine_ground_exit_point, exit_point_bmx, exit_point_bmy);
1011
#if DXX_USE_OGL
1012
        Render_depth = orig_Render_depth;
1013
        ogl_toggle_depth_test(1);
1014
#endif
1015
 
1016
        draw_exit_model(canvas);
1017
        if (ext_expl_playing)
1018
        {
1019
                const auto alpha = PlayerCfg.AlphaBlendMineExplosion;
1020
                if (alpha) // set nice transparency/blending for the big explosion
1021
                        gr_settransblend(canvas, GR_FADE_OFF, gr_blend::additive_c);
1022
                draw_fireball(Vclip, canvas, vcobjptridx(external_explosion));
1023
#if DXX_USE_OGL
1024
                /* If !OGL, the third argument is discarded, so this call
1025
                 * becomes the same as the one above.
1026
                 */
1027
                if (alpha)
1028
                        gr_settransblend(canvas, GR_FADE_OFF, gr_blend::normal); // revert any transparency/blending setting back to normal
1029
#endif
1030
        }
1031
 
1032
#if !DXX_USE_OGL
1033
        Lighting_on=0;
1034
#endif
1035
        render_object(canvas, LevelUniqueLightState, vmobjptridx(ConsoleObject));
1036
#if !DXX_USE_OGL
1037
        Lighting_on=1;
1038
#endif
1039
}
1040
 
1041
}
1042
 
1043
static void generate_starfield(d_unique_endlevel_state::starfield_type &stars)
1044
{
1045
        range_for (auto &i, stars)
1046
        {
1047
                i.x = (d_rand() - D_RAND_MAX / 2) << 14;
1048
                i.z = (d_rand() - D_RAND_MAX / 2) << 14;
1049
                i.y = (d_rand() / 2) << 14;
1050
        }
1051
}
1052
 
1053
void draw_stars(grs_canvas &canvas, const d_unique_endlevel_state::starfield_type &stars)
1054
{
1055
        int intensity=31;
1056
        g3s_point p;
1057
 
1058
        uint8_t color = 0;
1059
        range_for (auto &&e, enumerate(stars))
1060
        {
1061
                const auto i = e.idx;
1062
                auto &si = e.value;
1063
 
1064
                if ((i&63) == 0) {
1065
                        color = BM_XRGB(intensity,intensity,intensity);
1066
                        intensity-=3;
1067
                }
1068
 
1069
                g3_rotate_delta_vec(p.p3_vec, si);
1070
                g3_code_point(p);
1071
 
1072
                if (p.p3_codes == 0) {
1073
 
1074
                        p.p3_flags &= ~PF_PROJECTED;
1075
 
1076
                        g3_project_point(p);
1077
#if !DXX_USE_OGL
1078
                        gr_pixel(canvas.cv_bitmap, f2i(p.p3_sx), f2i(p.p3_sy), color);
1079
#else
1080
                        g3_draw_sphere(canvas, p, F1_0 * 3, color);
1081
#endif
1082
                }
1083
        }
1084
 
1085
//@@    {
1086
//@@            vms_vector delta;
1087
//@@            g3s_point top_pnt;
1088
//@@
1089
//@@            g3_rotate_point(&p,&satellite_pos);
1090
//@@            g3_rotate_delta_vec(&delta,&satellite_upvec);
1091
//@@
1092
//@@            g3_add_delta_vec(&top_pnt,&p,&delta);
1093
//@@
1094
//@@            if (! (p.p3_codes & CC_BEHIND)) {
1095
//@@                    int save_im = Interpolation_method;
1096
//@@                    Interpolation_method = 0;
1097
//@@                    //p.p3_flags &= ~PF_PROJECTED;
1098
//@@                    g3_project_point(&p);
1099
//@@                    if (! (p.p3_flags & PF_OVERFLOW))
1100
//@@                            //gr_bitmapm(f2i(p.p3_sx)-32,f2i(p.p3_sy)-32,satellite_bitmap);
1101
//@@                            g3_draw_rod_tmap(satellite_bitmap,&p,SATELLITE_WIDTH,&top_pnt,SATELLITE_WIDTH,f1_0);
1102
//@@                    Interpolation_method = save_im;
1103
//@@            }
1104
//@@    }
1105
 
1106
}
1107
 
1108
namespace dsx {
1109
 
1110
static void endlevel_render_mine(const d_level_shared_segment_state &LevelSharedSegmentState, grs_canvas &canvas, fix eye_offset)
1111
{
1112
        auto Viewer_eye = Viewer->pos;
1113
 
1114
        if (Viewer->type == OBJ_PLAYER )
1115
                vm_vec_scale_add2(Viewer_eye,Viewer->orient.fvec,(Viewer->size*3)/4);
1116
 
1117
        if (eye_offset)
1118
                vm_vec_scale_add2(Viewer_eye,Viewer->orient.rvec,eye_offset);
1119
 
1120
#if DXX_USE_EDITOR
1121
        if (EditorWindow)
1122
                Viewer_eye = Viewer->pos;
1123
        #endif
1124
 
1125
        segnum_t start_seg_num;
1126
        if (Endlevel_sequence >= EL_OUTSIDE) {
1127
                start_seg_num = PlayerUniqueEndlevelState.exit_segnum;
1128
        }
1129
        else {
1130
                start_seg_num = find_point_seg(LevelSharedSegmentState, Viewer_eye, Segments.vcptridx(Viewer->segnum));
1131
 
1132
                if (start_seg_num==segment_none)
1133
                        start_seg_num = Viewer->segnum;
1134
        }
1135
 
1136
        g3_set_view_matrix(Viewer_eye, Endlevel_sequence == EL_LOOKBACK
1137
                ? vm_matrix_x_matrix(Viewer->orient, vm_angles_2_matrix(vms_angvec{0, 0, INT16_MAX}))
1138
                : Viewer->orient, Render_zoom);
1139
 
1140
        window_rendered_data window;
1141
        render_mine(canvas, Viewer_eye, start_seg_num, eye_offset, window);
1142
}
1143
 
1144
}
1145
 
1146
void render_endlevel_frame(grs_canvas &canvas, fix eye_offset)
1147
{
1148
        auto &Objects = LevelUniqueObjectState.Objects;
1149
        auto &vcobjptridx = Objects.vcptridx;
1150
        g3_start_frame(canvas);
1151
 
1152
        if (Endlevel_sequence < EL_OUTSIDE)
1153
                endlevel_render_mine(LevelSharedSegmentState, canvas, eye_offset);
1154
        else
1155
                render_external_scene(vcobjptridx, canvas, LevelUniqueLightState, eye_offset);
1156
 
1157
        g3_end_frame();
1158
}
1159
 
1160
///////////////////////// copy of flythrough code for endlevel
1161
 
1162
 
1163
#define DEFAULT_SPEED i2f(16)
1164
 
1165
#define MIN_D 0x100
1166
 
1167
//if speed is zero, use default speed
1168
void start_endlevel_flythrough(flythrough_data *flydata,const vmobjptr_t obj,fix speed)
1169
{
1170
        flydata->obj = obj;
1171
 
1172
        flydata->first_time = 1;
1173
 
1174
        flydata->speed = speed?speed:DEFAULT_SPEED;
1175
 
1176
        flydata->offset_frac = 0;
1177
}
1178
 
1179
static void angvec_add2_scale(vms_angvec &dest,const vms_vector &src,fix s)
1180
{
1181
        dest.p += fixmul(src.x,s);
1182
        dest.b += fixmul(src.z,s);
1183
        dest.h += fixmul(src.y,s);
1184
}
1185
 
1186
#define MAX_ANGSTEP     0x4000          //max turn per second
1187
 
1188
#define MAX_SLIDE_PER_SEGMENT 0x10000
1189
 
1190
void do_endlevel_flythrough(flythrough_data *flydata)
1191
{
1192
        auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
1193
        auto &Objects = LevelUniqueObjectState.Objects;
1194
        auto &Vertices = LevelSharedVertexState.get_vertices();
1195
        auto &vmobjptr = Objects.vmptr;
1196
        auto &vmobjptridx = Objects.vmptridx;
1197
        const auto &&obj = vmobjptridx(flydata->obj);
1198
 
1199
        vcsegidx_t old_player_seg = obj->segnum;
1200
 
1201
        //move the player for this frame
1202
 
1203
        if (!flydata->first_time) {
1204
 
1205
                vm_vec_scale_add2(obj->pos,flydata->step,FrameTime);
1206
                angvec_add2_scale(flydata->angles,flydata->angstep,FrameTime);
1207
 
1208
                vm_angles_2_matrix(obj->orient,flydata->angles);
1209
        }
1210
 
1211
        //check new player seg
1212
 
1213
        update_object_seg(vmobjptr, LevelSharedSegmentState, LevelUniqueSegmentState, obj);
1214
        const shared_segment &pseg = *vcsegptr(obj->segnum);
1215
 
1216
        if (flydata->first_time || obj->segnum != old_player_seg) {             //moved into new seg
1217
                fix seg_time;
1218
                short entry_side,exit_side = -1;//what sides we entry and leave through
1219
                int up_side=0;
1220
 
1221
                entry_side=0;
1222
 
1223
                //find new exit side
1224
 
1225
                if (!flydata->first_time) {
1226
 
1227
                        entry_side = matt_find_connect_side(vcsegptr(obj->segnum), old_player_seg);
1228
                        exit_side = Side_opposite[entry_side];
1229
                }
1230
 
1231
                if (flydata->first_time || entry_side == side_none || pseg.children[exit_side] == segment_none)
1232
                        exit_side = find_exit_side(obj);
1233
 
1234
                {                                                                               //find closest side to align to
1235
                        fix d,largest_d=-f1_0;
1236
                        range_for (const int i, xrange(6u)) {
1237
                                d = vm_vec_dot(pseg.sides[i].normals[0], flydata->obj->orient.uvec);
1238
                                if (d > largest_d) {largest_d = d; up_side=i;}
1239
                        }
1240
 
1241
                }
1242
 
1243
                //update target point & angles
1244
 
1245
                //where we are heading (center of exit_side)
1246
                auto &vcvertptr = Vertices.vcptr;
1247
                auto dest_point = compute_center_point_on_side(vcvertptr, pseg, exit_side);
1248
                const vms_vector nextcenter = (pseg.children[exit_side] == segment_exit)
1249
                        ? dest_point
1250
                        : compute_segment_center(vcvertptr, vcsegptr(pseg.children[exit_side]));
1251
 
1252
                //update target point and movement points
1253
 
1254
                //offset object sideways
1255
                if (flydata->offset_frac) {
1256
                        int s0=-1,s1=0;
1257
                        fix dist;
1258
 
1259
                        range_for (const int i, xrange(6u))
1260
                                if (i!=entry_side && i!=exit_side && i!=up_side && i!=Side_opposite[up_side])
1261
                                 {
1262
                                        if (s0==-1)
1263
                                                s0 = i;
1264
                                        else
1265
                                                s1 = i;
1266
                                 }
1267
 
1268
                        const auto &&s0p = compute_center_point_on_side(vcvertptr, pseg, s0);
1269
                        const auto &&s1p = compute_center_point_on_side(vcvertptr, pseg, s1);
1270
                        dist = fixmul(vm_vec_dist(s0p,s1p),flydata->offset_frac);
1271
 
1272
                        if (dist-flydata->offset_dist > MAX_SLIDE_PER_SEGMENT)
1273
                                dist = flydata->offset_dist + MAX_SLIDE_PER_SEGMENT;
1274
 
1275
                        flydata->offset_dist = dist;
1276
 
1277
                        vm_vec_scale_add2(dest_point,obj->orient.rvec,dist);
1278
 
1279
                }
1280
 
1281
                vm_vec_sub(flydata->step,dest_point,obj->pos);
1282
                auto step_size = vm_vec_normalize_quick(flydata->step);
1283
                vm_vec_scale(flydata->step,flydata->speed);
1284
 
1285
                const auto curcenter = compute_segment_center(vcvertptr, pseg);
1286
                vm_vec_sub(flydata->headvec,nextcenter,curcenter);
1287
 
1288
                const auto dest_orient = vm_vector_2_matrix(flydata->headvec,&pseg.sides[up_side].normals[0],nullptr);
1289
                //where we want to be pointing
1290
                const auto dest_angles = vm_extract_angles_matrix(dest_orient);
1291
 
1292
                if (flydata->first_time)
1293
                        vm_extract_angles_matrix(flydata->angles,obj->orient);
1294
 
1295
                seg_time = fixdiv(step_size,flydata->speed);    //how long through seg
1296
 
1297
                if (seg_time) {
1298
                        flydata->angstep.x = max(-MAX_ANGSTEP,min(MAX_ANGSTEP,fixdiv(delta_ang(flydata->angles.p,dest_angles.p),seg_time)));
1299
                        flydata->angstep.z = max(-MAX_ANGSTEP,min(MAX_ANGSTEP,fixdiv(delta_ang(flydata->angles.b,dest_angles.b),seg_time)));
1300
                        flydata->angstep.y = max(-MAX_ANGSTEP,min(MAX_ANGSTEP,fixdiv(delta_ang(flydata->angles.h,dest_angles.h),seg_time)));
1301
 
1302
                }
1303
                else {
1304
                        flydata->angles = dest_angles;
1305
                        flydata->angstep.x = flydata->angstep.y = flydata->angstep.z = 0;
1306
                }
1307
        }
1308
 
1309
        flydata->first_time=0;
1310
}
1311
 
1312
#include "key.h"
1313
#include "joy.h"
1314
 
1315
 
1316
#define LINE_LEN        80
1317
#define NUM_VARS        8
1318
 
1319
#define STATION_DIST    i2f(1024)
1320
 
1321
namespace dcx {
1322
 
1323
static int convert_ext(d_fname &dest, const char (&ext)[4])
1324
{
1325
        auto b = std::begin(dest);
1326
        auto e = std::end(dest);
1327
        auto t = std::find(b, e, '.');
1328
        if (t != e && std::distance(b, t) <= 8)
1329
        {
1330
                std::copy(std::begin(ext), std::end(ext), std::next(t));
1331
                return 1;
1332
        }
1333
        else
1334
                return 0;
1335
}
1336
 
1337
static std::pair<icsegidx_t, sidenum_fast_t> find_exit_segment_side(fvcsegptridx &vcsegptridx)
1338
{
1339
        range_for (const auto &&segp, vcsegptridx)
1340
        {
1341
                range_for (const auto &&e, enumerate(segp->children))
1342
                {
1343
                        const auto child_segnum = e.value;
1344
                        if (child_segnum == segment_exit)
1345
                        {
1346
                                const auto sidenum = e.idx;
1347
                                return {segp, sidenum};
1348
                        }
1349
                }
1350
        }
1351
        return {segment_none, side_none};
1352
}
1353
 
1354
}
1355
 
1356
//called for each level to load & setup the exit sequence
1357
namespace dsx {
1358
void load_endlevel_data(int level_num)
1359
{
1360
        auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
1361
        auto &Vertices = LevelSharedVertexState.get_vertices();
1362
        d_fname filename;
1363
        char *p;
1364
        int var;
1365
        int have_binary = 0;
1366
 
1367
        endlevel_data_loaded = 0;               //not loaded yet
1368
 
1369
try_again:
1370
        ;
1371
 
1372
        if (level_num<0)                //secret level
1373
                filename = Secret_level_names[-level_num-1];
1374
        else                                    //normal level
1375
                filename = Level_names[level_num-1];
1376
 
1377
#if defined(DXX_BUILD_DESCENT_I)
1378
        if (!convert_ext(filename,"end"))
1379
                return;
1380
#elif defined(DXX_BUILD_DESCENT_II)
1381
        if (!convert_ext(filename,"END"))
1382
                Error("Error converting filename <%s> for endlevel data\n",static_cast<const char *>(filename));
1383
#endif
1384
 
1385
        auto ifile = PHYSFSX_openReadBuffered(filename);
1386
 
1387
        if (!ifile) {
1388
 
1389
                convert_ext(filename,"txb");
1390
                if (!strcmp(filename, Briefing_text_filename) ||
1391
                !strcmp(filename, Ending_text_filename))
1392
                    return;     // Don't want to interpret the briefing as an end level sequence!
1393
 
1394
                ifile = PHYSFSX_openReadBuffered(filename);
1395
 
1396
                if (!ifile) {
1397
                        if (level_num==1) {
1398
#if defined(DXX_BUILD_DESCENT_II)
1399
                                con_printf(CON_DEBUG, "Cannot load file text of binary version of <%s>",static_cast<const char *>(filename));
1400
                                endlevel_data_loaded = 0; // won't be able to play endlevel sequence
1401
#endif
1402
                                return;
1403
                        }
1404
                        else {
1405
                                level_num = 1;
1406
                                goto try_again;
1407
                        }
1408
                }
1409
 
1410
                have_binary = 1;
1411
        }
1412
 
1413
        //ok...this parser is pretty simple.  It ignores comments, but
1414
        //everything else must be in the right place
1415
 
1416
        var = 0;
1417
 
1418
        PHYSFSX_gets_line_t<LINE_LEN> line;
1419
        while (PHYSFSX_fgets(line,ifile)) {
1420
 
1421
                if (have_binary)
1422
                        decode_text_line (line);
1423
 
1424
                if ((p=strchr(line,';'))!=NULL)
1425
                        *p = 0;         //cut off comment
1426
 
1427
                for (p = line; isspace(static_cast<unsigned>(*p)); ++p)
1428
                        ;
1429
                if (!*p)                //empty line
1430
                        continue;
1431
                auto ns = p;
1432
                for (auto p2 = p; *p2; ++p2)
1433
                        if (!isspace(static_cast<unsigned>(*p2)))
1434
                                ns = p2;
1435
                *++ns = 0;
1436
 
1437
                switch (var) {
1438
 
1439
                        case 0: {                                               //ground terrain
1440
                                int iff_error;
1441
                                palette_array_t pal;
1442
                                terrain_bm_instance.reset();
1443
                                iff_error = iff_read_bitmap(p, terrain_bm_instance, &pal);
1444
                                if (iff_error != IFF_NO_ERROR) {
1445
                                        con_printf(CON_DEBUG, "Can't load exit terrain from file %s: IFF error: %s",
1446
                                                p, iff_errormsg(iff_error));
1447
                                        endlevel_data_loaded = 0; // won't be able to play endlevel sequence
1448
                                        return;
1449
                                }
1450
                                terrain_bitmap = &terrain_bm_instance;
1451
                                gr_remap_bitmap_good(terrain_bm_instance, pal, iff_transparent_color, -1);
1452
                                break;
1453
                        }
1454
 
1455
                        case 1:                                                 //height map
1456
 
1457
                                load_terrain(p);
1458
                                break;
1459
 
1460
 
1461
                        case 2:
1462
 
1463
                                sscanf(p,"%d,%d",&exit_point_bmx,&exit_point_bmy);
1464
                                break;
1465
 
1466
                        case 3:                                                 //exit heading
1467
 
1468
                                exit_angles.h = i2f(atoi(p))/360;
1469
                                break;
1470
 
1471
                        case 4: {                                               //planet bitmap
1472
                                int iff_error;
1473
                                palette_array_t pal;
1474
                                satellite_bm_instance.reset();
1475
                                iff_error = iff_read_bitmap(p, satellite_bm_instance, &pal);
1476
                                if (iff_error != IFF_NO_ERROR) {
1477
                                        con_printf(CON_DEBUG, "Can't load exit satellite from file %s: IFF error: %s",
1478
                                                p, iff_errormsg(iff_error));
1479
                                        endlevel_data_loaded = 0; // won't be able to play endlevel sequence
1480
                                        return;
1481
                                }
1482
 
1483
                                satellite_bitmap = &satellite_bm_instance;
1484
                                gr_remap_bitmap_good(satellite_bm_instance, pal, iff_transparent_color, -1);
1485
 
1486
                                break;
1487
                        }
1488
 
1489
                        case 5:                                                 //earth pos
1490
                        case 7: {                                               //station pos
1491
                                vms_angvec ta;
1492
                                int pitch,head;
1493
 
1494
                                sscanf(p,"%d,%d",&head,&pitch);
1495
 
1496
                                ta.h = i2f(head)/360;
1497
                                ta.p = -i2f(pitch)/360;
1498
                                ta.b = 0;
1499
 
1500
                                const auto &&tm = vm_angles_2_matrix(ta);
1501
 
1502
                                if (var==5)
1503
                                        satellite_pos = tm.fvec;
1504
                                        //vm_vec_copy_scale(&satellite_pos,&tm.fvec,SATELLITE_DIST);
1505
                                else
1506
                                        station_pos = tm.fvec;
1507
 
1508
                                break;
1509
                        }
1510
 
1511
                        case 6:                                         //planet size
1512
                                satellite_size = i2f(atoi(p));
1513
                                break;
1514
                }
1515
 
1516
                var++;
1517
 
1518
        }
1519
 
1520
        Assert(var == NUM_VARS);
1521
 
1522
 
1523
        // OK, now the data is loaded.  Initialize everything
1524
 
1525
        //find the exit sequence by searching all segments for a side with
1526
        //children == -2
1527
 
1528
        const auto &&exit_segside = find_exit_segment_side(vcsegptridx);
1529
        const icsegidx_t &exit_segnum = exit_segside.first;
1530
        const auto &exit_side = exit_segside.second;
1531
 
1532
        PlayerUniqueEndlevelState.exit_segnum = exit_segnum;
1533
        if (exit_segnum == segment_none)
1534
                return;
1535
 
1536
        const auto &&exit_seg = vmsegptr(exit_segnum);
1537
        auto &vcvertptr = Vertices.vcptr;
1538
        compute_segment_center(vcvertptr, mine_exit_point, exit_seg);
1539
        extract_orient_from_segment(vcvertptr, mine_exit_orient, exit_seg);
1540
        compute_center_point_on_side(vcvertptr, mine_side_exit_point, exit_seg, exit_side);
1541
 
1542
        vm_vec_scale_add(mine_ground_exit_point,mine_exit_point,mine_exit_orient.uvec,-i2f(20));
1543
 
1544
        //compute orientation of surface
1545
        {
1546
                auto &&exit_orient = vm_angles_2_matrix(exit_angles);
1547
                vm_transpose_matrix(exit_orient);
1548
                vm_matrix_x_matrix(surface_orient,mine_exit_orient,exit_orient);
1549
 
1550
                vms_matrix tm = vm_transposed_matrix(surface_orient);
1551
                const auto tv0 = vm_vec_rotate(station_pos,tm);
1552
                vm_vec_scale_add(station_pos,mine_exit_point,tv0,STATION_DIST);
1553
 
1554
                const auto tv = vm_vec_rotate(satellite_pos,tm);
1555
                vm_vec_scale_add(satellite_pos,mine_exit_point,tv,SATELLITE_DIST);
1556
 
1557
                const auto tm2 = vm_vector_2_matrix(tv,&surface_orient.uvec,nullptr);
1558
                vm_vec_copy_scale(satellite_upvec,tm2.uvec,SATELLITE_HEIGHT);
1559
 
1560
 
1561
        }
1562
        endlevel_data_loaded = 1;
1563
}
1564
}