Subversion Repositories Games.Descent

Rev

Blame | Last modification | View Log | Download | RSS feed

  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. }
  1565.