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