dxx-rebirth/similar/main/endlevel.cpp
Kp b737524415 Use enum class for next_level_request_secret_flag
Instead of passing a bare `int` named `secret_flag`, define it as an
`enum class : uint8_t` to name the two special values.

Rework the passing of this value, to deal with some confusing
inconsistencies when reading the code.

Before this change:
In D1:
- Multiplayer will always go to the secret level, regardless of which
  exit door the player used.
In D2:
- Flying through a D1 secret exit in multiplayer shows the on-HUD error
  "Secret Level Teleporter disabled in multiplayer!", and does not exit
  the level.  This is at best confusing, and at worst dangerous, since
  D1 secret exits are only available during the countdown, so the player
  has little time to realize that the normal exit must be used instead.
- Like D1, multiplayer will request to go to the secret level regardless
  of the exit used.  Unlike D1, the caller ignores the flag and always
  advances to the next regular level.

After this change:
- No observable differences for the player in-game.  The questionable D2
  secret exit handling for D1 is retained.
- The code makes clearer that secret exits do not work in D2
  multiplayer, by way of `#if defined(DXX_BUILD_DESCENT_I)` guarding the
  existence of the parameter and all updates to it.
2022-07-16 15:26:12 +00:00

1520 lines
43 KiB
C++

/*
* Portions of this file are copyright Rebirth contributors and licensed as
* described in COPYING.txt.
* Portions of this file are copyright Parallax Software and licensed
* according to the Parallax license below.
* See COPYING.txt for license details.
THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX
SOFTWARE CORPORATION ("PARALLAX"). PARALLAX, IN DISTRIBUTING THE CODE TO
END-USERS, AND SUBJECT TO ALL OF THE TERMS AND CONDITIONS HEREIN, GRANTS A
ROYALTY-FREE, PERPETUAL LICENSE TO SUCH END-USERS FOR USE BY SUCH END-USERS
IN USING, DISPLAYING, AND CREATING DERIVATIVE WORKS THEREOF, SO LONG AS
SUCH USE, DISPLAY OR CREATION IS FOR NON-COMMERCIAL, ROYALTY OR REVENUE
FREE PURPOSES. IN NO EVENT SHALL THE END-USER USE THE COMPUTER CODE
CONTAINED HEREIN FOR REVENUE-BEARING PURPOSES. THE END-USER UNDERSTANDS
AND AGREES TO THE TERMS HEREIN AND ACCEPTS THE SAME BY USE OF THIS FILE.
COPYRIGHT 1993-1999 PARALLAX SOFTWARE CORPORATION. ALL RIGHTS RESERVED.
*/
/*
*
* Code for rendering external scenes
*
*/
//#define _MARK_ON
#include <algorithm>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h> // for isspace
#include "maths.h"
#include "vecmat.h"
#include "gr.h"
#include "3d.h"
#include "dxxerror.h"
#include "palette.h"
#include "iff.h"
#include "console.h"
#include "texmap.h"
#include "fvi.h"
#include "u_mem.h"
#include "sounds.h"
#include "playsave.h"
#include "inferno.h"
#include "endlevel.h"
#include "object.h"
#include "game.h"
#include "gamepal.h"
#include "screens.h"
#include "terrain.h"
#include "robot.h"
#include "player.h"
#include "physfsx.h"
#include "bm.h"
#include "gameseg.h"
#include "gameseq.h"
#include "newdemo.h"
#include "gamepal.h"
#include "net_udp.h"
#include "vclip.h"
#include "fireball.h"
#include "text.h"
#include "digi.h"
#include "songs.h"
#if defined(DXX_BUILD_DESCENT_II)
#include "movie.h"
#endif
#include "render.h"
#include "hudmsg.h"
#if DXX_USE_OGL
#include "ogl_init.h"
#endif
#include "joy.h"
#if DXX_USE_EDITOR
#include "editor/editor.h"
#endif
#include "compiler-range_for.h"
#include "d_enumerate.h"
#include "d_levelstate.h"
#include "d_range.h"
#include <iterator>
using std::min;
using std::max;
#define SHORT_SEQUENCE 1 //if defined, end sequence when panning starts
namespace dcx {
int Endlevel_sequence;
grs_bitmap *terrain_bitmap; //!!*exit_bitmap,
unsigned exit_modelnum, destroyed_exit_modelnum;
vms_matrix surface_orient;
namespace {
#define FLY_ACCEL i2f(5)
#define MAX_FLY_OBJECTS 2
d_unique_endlevel_state UniqueEndlevelState;
static void generate_starfield(d_unique_endlevel_state::starfield_type &stars);
static void draw_stars(grs_canvas &, const d_unique_endlevel_state::starfield_type &stars);
static sidenum_t find_exit_side(const d_level_shared_segment_state &LevelSharedSegmentState, const d_level_shared_vertex_state &LevelSharedVertexState, const vms_vector &last_console_player_position, const object_base &obj);
static fix cur_fly_speed, desired_fly_speed;
static grs_bitmap *satellite_bitmap;
vms_vector satellite_pos,satellite_upvec;
static vms_vector station_pos{0xf8c4<<10,0x3c1c<<12,0x372<<10};
static vms_vector mine_exit_point;
static vms_vector mine_ground_exit_point;
static vms_vector mine_side_exit_point;
static vms_matrix mine_exit_orient;
static int outside_mine;
static grs_main_bitmap terrain_bm_instance, satellite_bm_instance;
static int ext_expl_playing,mine_destroyed;
static vms_angvec exit_angles = {-0xa00, 0, 0};
static int endlevel_data_loaded;
static vms_angvec player_angles,player_dest_angles;
#ifndef SHORT_SEQUENCE
static vms_angvec camera_desired_angles,camera_cur_angles;
#endif
static int exit_point_bmx,exit_point_bmy;
static fix satellite_size = i2f(400);
//find delta between two angles
static fixang delta_ang(fixang a,fixang b)
{
fixang delta0,delta1;
return (abs(delta0 = a - b) < abs(delta1 = b - a)) ? delta0 : delta1;
}
//return though which side of seg0 is seg1
static sidenum_t matt_find_connect_side(const shared_segment &seg0, const vcsegidx_t seg1)
{
auto &children = seg0.children;
return static_cast<sidenum_t>(std::distance(children.begin(), std::find(children.begin(), children.end(), seg1)));
}
static unsigned get_tunnel_length(fvcsegptridx &vcsegptridx, const vcsegptridx_t console_seg, const sidenum_t exit_console_side)
{
auto seg = console_seg;
auto exit_side = exit_console_side;
unsigned tunnel_length = 0;
for (;;)
{
const auto child = seg->shared_segment::children[exit_side];
if (child == segment_none)
return 0;
++tunnel_length;
if (child == segment_exit)
{
assert(seg == PlayerUniqueEndlevelState.exit_segnum);
return tunnel_length;
}
const vcsegidx_t old_segidx = seg;
seg = vcsegptridx(child);
const auto entry_side = matt_find_connect_side(seg, old_segidx);
if (!Side_opposite.valid_index(entry_side))
return 0;
exit_side = Side_opposite[entry_side];
}
}
static vcsegidx_t get_tunnel_transition_segment(const unsigned tunnel_length, const vcsegptridx_t console_seg, const sidenum_t exit_console_side)
{
auto seg = console_seg;
auto exit_side = exit_console_side;
for (auto i = tunnel_length / 3;; --i)
{
/*
* If the tunnel ended with segment_none, the caller would have
* returned immediately after the prior loop. If the tunnel
* ended with segment_exit, then tunnel_length is the count of
* segments to reach the exit. The termination condition on
* this loop quits at (tunnel_length / 3), so the loop will quit
* before it reaches segment_exit.
*/
const auto child = seg->shared_segment::children[exit_side];
if (!IS_CHILD(child))
/* This is only a sanity check. It should never execute
* unless there is a bug elsewhere.
*/
return seg;
if (!i)
return child;
const vcsegidx_t old_segidx = seg;
seg = vcsegptridx(child);
const auto entry_side = matt_find_connect_side(seg, old_segidx);
exit_side = Side_opposite[entry_side];
}
}
#define CHASE_TURN_RATE (0x4000/4) //max turn per second
//returns bitmask of which angles are at dest. bits 0,1,2 = p,b,h
static int chase_angles(vms_angvec *cur_angles,vms_angvec *desired_angles)
{
vms_angvec delta_angs,alt_angles,alt_delta_angs;
fix total_delta,alt_total_delta;
fix frame_turn;
int mask=0;
delta_angs.p = desired_angles->p - cur_angles->p;
delta_angs.h = desired_angles->h - cur_angles->h;
delta_angs.b = desired_angles->b - cur_angles->b;
total_delta = abs(delta_angs.p) + abs(delta_angs.b) + abs(delta_angs.h);
alt_angles.p = f1_0/2 - cur_angles->p;
alt_angles.b = cur_angles->b + f1_0/2;
alt_angles.h = cur_angles->h + f1_0/2;
alt_delta_angs.p = desired_angles->p - alt_angles.p;
alt_delta_angs.h = desired_angles->h - alt_angles.h;
alt_delta_angs.b = desired_angles->b - alt_angles.b;
alt_total_delta = abs(alt_delta_angs.p) + abs(alt_delta_angs.b) + abs(alt_delta_angs.h);
if (alt_total_delta < total_delta) {
*cur_angles = alt_angles;
delta_angs = alt_delta_angs;
}
frame_turn = fixmul(FrameTime,CHASE_TURN_RATE);
if (abs(delta_angs.p) < frame_turn) {
cur_angles->p = desired_angles->p;
mask |= 1;
}
else
if (delta_angs.p > 0)
cur_angles->p += frame_turn;
else
cur_angles->p -= frame_turn;
if (abs(delta_angs.b) < frame_turn) {
cur_angles->b = desired_angles->b;
mask |= 2;
}
else
if (delta_angs.b > 0)
cur_angles->b += frame_turn;
else
cur_angles->b -= frame_turn;
//cur_angles->b = 0;
if (abs(delta_angs.h) < frame_turn) {
cur_angles->h = desired_angles->h;
mask |= 4;
}
else
if (delta_angs.h > 0)
cur_angles->h += frame_turn;
else
cur_angles->h -= frame_turn;
return mask;
}
//find the angle between the player's heading & the station
static void get_angs_to_object(vms_angvec &av,const vms_vector &targ_pos,const vms_vector &cur_pos)
{
const auto tv = vm_vec_sub(targ_pos,cur_pos);
vm_extract_angles_vector(av,tv);
}
#define MIN_D 0x100
//find which side to fly out of
static sidenum_t find_exit_side(const d_level_shared_segment_state &LevelSharedSegmentState, const d_level_shared_vertex_state &LevelSharedVertexState, const vms_vector &last_console_player_position, const object_base &obj)
{
auto &Vertices = LevelSharedVertexState.get_vertices();
vms_vector prefvec;
fix best_val=-f2_0;
//find exit side
vm_vec_normalized_dir_quick(prefvec, obj.pos, last_console_player_position);
const shared_segment &pseg = LevelSharedSegmentState.get_segments().vcptr(obj.segnum);
auto &vcvertptr = Vertices.vcptr;
const auto segcenter = compute_segment_center(vcvertptr, pseg);
sidenum_t best_side = side_none;
for (const auto &&[i, child] : enumerate(pseg.children))
{
fix d;
if (child != segment_none)
{
auto sidevec = compute_center_point_on_side(vcvertptr, pseg, i);
vm_vec_normalized_dir_quick(sidevec,sidevec,segcenter);
d = vm_vec_dot(sidevec,prefvec);
if (labs(d) < MIN_D) d=0;
if (d > best_val)
{
best_val = d;
best_side = static_cast<sidenum_t>(i);
}
}
}
return best_side;
}
static void draw_mine_exit_cover(grs_canvas &canvas)
{
const int of = 10;
const fix u = i2f(6), d = i2f(9), ur = i2f(14), dr = i2f(17);
const uint8_t color = BM_XRGB(0, 0, 0);
vms_vector v;
g3s_point p0, p1, p2, p3;
vm_vec_scale_add(v,mine_exit_point,mine_exit_orient.fvec,i2f(of));
auto mrd = mine_exit_orient.rvec;
{
vms_vector vu;
vm_vec_scale_add(vu, v, mine_exit_orient.uvec, u);
auto mru = mrd;
vm_vec_scale(mru, ur);
vms_vector p;
g3_rotate_point(p0, (vm_vec_add(p, vu, mru), p));
g3_rotate_point(p1, (vm_vec_sub(p, vu, mru), p));
}
{
vms_vector vd;
vm_vec_scale_add(vd, v, mine_exit_orient.uvec, -d);
vm_vec_scale(mrd, dr);
vms_vector p;
g3_rotate_point(p2, (vm_vec_sub(p, vd, mrd), p));
g3_rotate_point(p3, (vm_vec_add(p, vd, mrd), p));
}
const std::array<cg3s_point *, 4> pointlist{{
&p0,
&p1,
&p2,
&p3,
}};
g3_draw_poly(canvas, pointlist.size(), pointlist, color);
}
static void generate_starfield(d_unique_endlevel_state::starfield_type &stars)
{
range_for (auto &i, stars)
{
i.x = (d_rand() - D_RAND_MAX / 2) << 14;
i.z = (d_rand() - D_RAND_MAX / 2) << 14;
i.y = (d_rand() / 2) << 14;
}
}
void draw_stars(grs_canvas &canvas, const d_unique_endlevel_state::starfield_type &stars)
{
int intensity=31;
g3s_point p;
uint8_t color = 0;
for (const auto &&[i, si] : enumerate(stars))
{
if ((i&63) == 0) {
color = BM_XRGB(intensity,intensity,intensity);
intensity-=3;
}
g3_rotate_delta_vec(p.p3_vec, si);
g3_code_point(p);
if (p.p3_codes == 0) {
p.p3_flags &= ~PF_PROJECTED;
g3_project_point(p);
#if !DXX_USE_OGL
gr_pixel(canvas.cv_bitmap, f2i(p.p3_sx), f2i(p.p3_sy), color);
#else
g3_draw_sphere(canvas, p, F1_0 * 3, color);
#endif
}
}
//@@ {
//@@ vms_vector delta;
//@@ g3s_point top_pnt;
//@@
//@@ g3_rotate_point(&p,&satellite_pos);
//@@ g3_rotate_delta_vec(&delta,&satellite_upvec);
//@@
//@@ g3_add_delta_vec(&top_pnt,&p,&delta);
//@@
//@@ if (! (p.p3_codes & CC_BEHIND)) {
//@@ int save_im = Interpolation_method;
//@@ Interpolation_method = 0;
//@@ //p.p3_flags &= ~PF_PROJECTED;
//@@ g3_project_point(&p);
//@@ if (! (p.p3_flags & PF_OVERFLOW))
//@@ //gr_bitmapm(f2i(p.p3_sx)-32,f2i(p.p3_sy)-32,satellite_bitmap);
//@@ g3_draw_rod_tmap(satellite_bitmap,&p,SATELLITE_WIDTH,&top_pnt,SATELLITE_WIDTH,f1_0);
//@@ Interpolation_method = save_im;
//@@ }
//@@ }
}
static void angvec_add2_scale(vms_angvec &dest,const vms_vector &src,fix s)
{
dest.p += fixmul(src.x,s);
dest.b += fixmul(src.z,s);
dest.h += fixmul(src.y,s);
}
static int convert_ext(d_fname &dest, const char (&ext)[4])
{
auto b = std::begin(dest);
auto e = std::end(dest);
auto t = std::find(b, e, '.');
if (t != e && std::distance(b, t) <= 8)
{
std::copy(std::begin(ext), std::end(ext), std::next(t));
return 1;
}
else
return 0;
}
static std::pair<icsegidx_t, sidenum_t> find_exit_segment_side(fvcsegptridx &vcsegptridx)
{
range_for (const auto &&segp, vcsegptridx)
{
for (const auto &&[sidenum, child_segnum] : enumerate(segp->shared_segment::children))
{
if (child_segnum == segment_exit)
{
return {segp, static_cast<sidenum_t>(sidenum)};
}
}
}
return {segment_none, side_none};
}
}
void free_endlevel_data()
{
terrain_bm_instance.reset();
satellite_bm_instance.reset();
free_light_table();
free_height_array();
}
}
namespace dsx {
namespace {
struct flythrough_data
{
object *obj;
vms_angvec angles; //orientation in angles
vms_vector step; //how far in a second
vms_vector angstep; //rotation per second
fix speed; //how fast object is moving
vms_vector headvec; //where we want to be pointing
int first_time; //flag for if first time through
fix offset_frac; //how far off-center as portion of way
fix offset_dist; //how far currently off-center
};
static std::array<flythrough_data, MAX_FLY_OBJECTS> fly_objects;
//endlevel sequence states
#define EL_OFF 0 //not in endlevel
#define EL_FLYTHROUGH 1 //auto-flythrough in tunnel
#define EL_LOOKBACK 2 //looking back at player
#define EL_OUTSIDE 3 //flying outside for a while
#define EL_STOPPED 4 //stopped, watching explosion
#define EL_PANNING 5 //panning around, watching player
#define EL_CHASING 6 //chasing player to station
static object *endlevel_camera;
static object *external_explosion;
#define FLY_SPEED i2f(50)
static void do_endlevel_flythrough(d_level_unique_object_state &LevelUniqueObjectState, const d_level_shared_segment_state &LevelSharedSegmentState, d_level_unique_segment_state &LevelUniqueSegmentState, flythrough_data *flydata);
#define DEFAULT_SPEED i2f(16)
#if defined(DXX_BUILD_DESCENT_II)
constexpr std::array<const char, 24> movie_table{{
'A','B','C','A',
'D','F','D','F',
'G','H','I','G',
'J','K','L','J',
'M','O','M','O',
'P','Q','P','Q'
}};
static auto endlevel_movie_played = movie_play_status::skipped;
#define MOVIE_REQUIRED 1
//returns movie played status. see movie.h
static movie_play_status start_endlevel_movie()
{
palette_array_t save_pal;
//Assert(PLAYING_BUILTIN_MISSION); //only play movie for built-in mission
const auto current_level_num = Current_level_num;
if (is_SHAREWARE)
return movie_play_status::skipped;
if (!is_D2_OEM)
if (current_level_num == Current_mission->last_level)
return movie_play_status::started; //don't play movie
if (!(current_level_num > 0))
return movie_play_status::skipped; //no escapes for secret level
char movie_name[] = "ESA.MVE";
movie_name[2] = movie_table[Current_level_num-1];
save_pal = gr_palette;
const auto r = PlayMovie(NULL, movie_name,(Game_mode & GM_MULTI)?0:MOVIE_REQUIRED);
if (Newdemo_state == ND_STATE_PLAYBACK) {
set_screen_mode(SCREEN_GAME);
gr_palette = save_pal;
}
return (r);
}
#endif
//if speed is zero, use default speed
void start_endlevel_flythrough(flythrough_data &flydata, object &obj, const fix speed)
{
flydata.obj = &obj;
flydata.first_time = 1;
flydata.speed = speed?speed:DEFAULT_SPEED;
flydata.offset_frac = 0;
}
void draw_exit_model(grs_canvas &canvas)
{
int f=15,u=0; //21;
g3s_lrgb lrgb = { f1_0, f1_0, f1_0 };
if (mine_destroyed)
{
// draw black shape to mask out terrain in exit hatch
draw_mine_exit_cover(canvas);
}
auto model_pos = vm_vec_scale_add(mine_exit_point,mine_exit_orient.fvec,i2f(f));
vm_vec_scale_add2(model_pos,mine_exit_orient.uvec,i2f(u));
draw_polygon_model(canvas, model_pos, mine_exit_orient, nullptr, mine_destroyed ? destroyed_exit_modelnum : exit_modelnum, 0, lrgb, nullptr, nullptr);
}
#define SATELLITE_DIST i2f(1024)
#define SATELLITE_WIDTH satellite_size
#define SATELLITE_HEIGHT ((satellite_size*9)/4) //((satellite_size*5)/2)
static void render_external_scene(fvcobjptridx &vcobjptridx, grs_canvas &canvas, const d_level_unique_light_state &LevelUniqueLightState, const fix eye_offset)
{
auto &Objects = LevelUniqueObjectState.Objects;
auto &vmobjptridx = Objects.vmptridx;
#if DXX_USE_OGL
int orig_Render_depth = Render_depth;
#endif
g3s_lrgb lrgb = { f1_0, f1_0, f1_0 };
auto Viewer_eye = Viewer->pos;
if (eye_offset)
vm_vec_scale_add2(Viewer_eye,Viewer->orient.rvec,eye_offset);
g3_set_view_matrix(Viewer->pos,Viewer->orient,Render_zoom);
//g3_draw_horizon(BM_XRGB(0,0,0),BM_XRGB(16,16,16)); //,-1);
gr_clear_canvas(canvas, BM_XRGB(0,0,0));
{
auto &&ctx = g3_start_instance_matrix(vmd_zero_vector, surface_orient);
draw_stars(canvas, UniqueEndlevelState.stars);
g3_done_instance(ctx);
}
{ //draw satellite
vms_vector delta;
g3s_point top_pnt;
const auto p = g3_rotate_point(satellite_pos);
g3_rotate_delta_vec(delta,satellite_upvec);
g3_add_delta_vec(top_pnt,p,delta);
if (! (p.p3_codes & CC_BEHIND)) {
//p.p3_flags &= ~PF_PROJECTED;
//g3_project_point(&p);
if (! (p.p3_flags & PF_OVERFLOW)) {
push_interpolation_method save_im(0);
//gr_bitmapm(f2i(p.p3_sx)-32,f2i(p.p3_sy)-32,satellite_bitmap);
g3_draw_rod_tmap(canvas, *satellite_bitmap, p, SATELLITE_WIDTH, top_pnt, SATELLITE_WIDTH, lrgb);
}
}
}
#if DXX_USE_OGL
ogl_toggle_depth_test(0);
Render_depth = (200-(vm_vec_dist_quick(mine_ground_exit_point, Viewer_eye)/F1_0))/36;
#endif
render_terrain(canvas, Viewer_eye, mine_ground_exit_point, exit_point_bmx, exit_point_bmy);
#if DXX_USE_OGL
Render_depth = orig_Render_depth;
ogl_toggle_depth_test(1);
#endif
draw_exit_model(canvas);
if (ext_expl_playing)
{
const auto alpha = PlayerCfg.AlphaBlendMineExplosion;
if (alpha) // set nice transparency/blending for the big explosion
gr_settransblend(canvas, GR_FADE_OFF, gr_blend::additive_c);
draw_fireball(Vclip, canvas, vcobjptridx(external_explosion));
#if DXX_USE_OGL
/* If !OGL, the third argument is discarded, so this call
* becomes the same as the one above.
*/
if (alpha)
gr_settransblend(canvas, GR_FADE_OFF, gr_blend::normal); // revert any transparency/blending setting back to normal
#endif
}
#if !DXX_USE_OGL
Lighting_on=0;
#endif
render_object(canvas, LevelUniqueLightState, vmobjptridx(ConsoleObject));
#if !DXX_USE_OGL
Lighting_on=1;
#endif
}
}
window_event_result start_endlevel_sequence()
{
auto &Objects = LevelUniqueObjectState.Objects;
auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
auto &vmobjptr = Objects.vmptr;
const auto dead = Player_dead_state != player_dead_state::no || (ConsoleObject->flags & OF_SHOULD_BE_DEAD);
if (!dead)
reset_rear_view(); //turn off rear view if set - NOTE: make sure this happens before we pause demo recording!!
if (Newdemo_state == ND_STATE_RECORDING) // stop demo recording
Newdemo_state = ND_STATE_PAUSED;
if (Newdemo_state == ND_STATE_PLAYBACK) { // don't do this if in playback mode
#if defined(DXX_BUILD_DESCENT_II)
if (PLAYING_BUILTIN_MISSION) // only play movie for built-in mission
{
const auto g = Game_wind;
g->set_visible(0); // suspend the game, including drawing
start_endlevel_movie();
g->set_visible(1);
}
strcpy(last_palette_loaded,""); //force palette load next time
#endif
return window_event_result::ignored;
}
if (dead)
return window_event_result::ignored; //don't start if dead!
con_puts(CON_NORMAL, "You have escaped the mine!");
#if defined(DXX_BUILD_DESCENT_II)
auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
// Dematerialize Buddy!
range_for (const auto &&objp, vmobjptr)
{
if (objp->type == OBJ_ROBOT)
if (Robot_info[get_robot_id(objp)].companion) {
object_create_explosion(vmsegptridx(objp->segnum), objp->pos, F1_0*7/2, VCLIP_POWERUP_DISAPPEARANCE );
objp->flags |= OF_SHOULD_BE_DEAD;
}
}
#endif
get_local_plrobj().ctype.player_info.homing_object_dist = -F1_0; // Turn off homing sound.
if (Game_mode & GM_MULTI) {
multi_send_endlevel_start(multi_endlevel_type::normal);
multi::dispatch->do_protocol_frame(1, 1);
}
#if defined(DXX_BUILD_DESCENT_I)
if (!endlevel_data_loaded)
#elif defined(DXX_BUILD_DESCENT_II)
if (PLAYING_BUILTIN_MISSION) // only play movie for built-in mission
if (!(Game_mode & GM_MULTI))
{
const auto g = Game_wind;
g->set_visible(0); // suspend the game, including drawing
endlevel_movie_played = start_endlevel_movie();
g->set_visible(1);
}
if (!(!(Game_mode & GM_MULTI) && (endlevel_movie_played == movie_play_status::skipped) && endlevel_data_loaded))
#endif
{
return PlayerFinishedLevel(next_level_request_secret_flag::only_normal_level); //done with level
}
#if defined(DXX_BUILD_DESCENT_II)
int exit_models_loaded = 0;
if (Piggy_hamfile_version < 3)
exit_models_loaded = 1; // built-in for PC shareware
else
exit_models_loaded = load_exit_models();
if (!exit_models_loaded)
return window_event_result::ignored;
#endif
{
//count segments in exit tunnel
const object_base &console = vmobjptr(ConsoleObject);
const auto exit_console_side = find_exit_side(LevelSharedSegmentState, LevelSharedVertexState, LevelUniqueObjectState.last_console_player_position, console);
const auto &&console_seg = vcsegptridx(console.segnum);
const auto tunnel_length = get_tunnel_length(vcsegptridx, console_seg, exit_console_side);
if (!tunnel_length)
{
return PlayerFinishedLevel(next_level_request_secret_flag::only_normal_level); //don't do special sequence
}
//now pick transition segnum 1/3 of the way in
PlayerUniqueEndlevelState.transition_segnum = get_tunnel_transition_segment(tunnel_length, console_seg, exit_console_side);
}
if (Game_mode & GM_MULTI) {
multi_send_endlevel_start(multi_endlevel_type::normal);
multi::dispatch->do_protocol_frame(1, 1);
}
songs_play_song( SONG_ENDLEVEL, 0 );
Endlevel_sequence = EL_FLYTHROUGH;
ConsoleObject->movement_source = object::movement_type::None; //movement handled by flythrough
ConsoleObject->control_source = object::control_type::None;
Game_suspended |= SUSP_ROBOTS; //robots don't move
cur_fly_speed = desired_fly_speed = FLY_SPEED;
start_endlevel_flythrough(fly_objects[0], vmobjptr(ConsoleObject), cur_fly_speed); //initialize
HUD_init_message_literal(HM_DEFAULT, TXT_EXIT_SEQUENCE );
outside_mine = ext_expl_playing = 0;
flash_scale = f1_0;
generate_starfield(UniqueEndlevelState.stars);
mine_destroyed=0;
return window_event_result::handled;
}
window_event_result stop_endlevel_sequence()
{
#if !DXX_USE_OGL
Interpolation_method = 0;
#endif
select_cockpit(PlayerCfg.CockpitMode[0]);
Endlevel_sequence = EL_OFF;
return PlayerFinishedLevel(next_level_request_secret_flag::only_normal_level);
}
#define VCLIP_BIG_PLAYER_EXPLOSION 58
//--unused-- vms_vector upvec = {0,f1_0,0};
window_event_result do_endlevel_frame(const d_level_shared_robot_info_state &LevelSharedRobotInfoState)
{
auto &Objects = LevelUniqueObjectState.Objects;
auto &vmobjptr = Objects.vmptr;
static fix timer;
static fix bank_rate;
static fix explosion_wait1=0;
static fix explosion_wait2=0;
static fix ext_expl_halflife;
auto result = endlevel_move_all_objects(LevelSharedRobotInfoState);
if (ext_expl_playing) {
do_explosion_sequence(LevelSharedRobotInfoState.Robot_info, vmobjptr(external_explosion));
if (external_explosion->lifeleft < ext_expl_halflife)
mine_destroyed = 1;
if (external_explosion->flags & OF_SHOULD_BE_DEAD)
ext_expl_playing = 0;
}
if (cur_fly_speed != desired_fly_speed) {
fix delta = desired_fly_speed - cur_fly_speed;
fix frame_accel = fixmul(FrameTime,FLY_ACCEL);
if (abs(delta) < frame_accel)
cur_fly_speed = desired_fly_speed;
else
if (delta > 0)
cur_fly_speed += frame_accel;
else
cur_fly_speed -= frame_accel;
}
//do big explosions
if (!outside_mine) {
if (Endlevel_sequence==EL_OUTSIDE) {
const auto tvec = vm_vec_sub(ConsoleObject->pos,mine_side_exit_point);
if (vm_vec_dot(tvec,mine_exit_orient.fvec) > 0) {
vms_vector mov_vec;
outside_mine = 1;
const auto &&exit_segp = vmsegptridx(PlayerUniqueEndlevelState.exit_segnum);
const auto &&tobj = object_create_explosion(exit_segp, mine_side_exit_point, i2f(50), VCLIP_BIG_PLAYER_EXPLOSION);
if (tobj) {
// Move explosion to Viewer to draw it in front of mine exit model
vm_vec_normalized_dir_quick(mov_vec,Viewer->pos,tobj->pos);
vm_vec_scale_add2(tobj->pos,mov_vec,i2f(30));
external_explosion = tobj;
flash_scale = 0; //kill lights in mine
ext_expl_halflife = tobj->lifeleft;
ext_expl_playing = 1;
}
digi_link_sound_to_pos(SOUND_BIG_ENDLEVEL_EXPLOSION, exit_segp, sidenum_t::WLEFT, mine_side_exit_point, 0, i2f(3)/4);
}
}
//do explosions chasing player
if ((explosion_wait1-=FrameTime) < 0) {
static int sound_count;
auto tpnt = vm_vec_scale_add(ConsoleObject->pos,ConsoleObject->orient.fvec,-ConsoleObject->size*5);
vm_vec_scale_add2(tpnt,ConsoleObject->orient.rvec,(d_rand()-D_RAND_MAX/2)*15);
vm_vec_scale_add2(tpnt,ConsoleObject->orient.uvec,(d_rand()-D_RAND_MAX/2)*15);
const auto &&segnum = find_point_seg(LevelSharedSegmentState, LevelUniqueSegmentState, tpnt, Segments.vmptridx(ConsoleObject->segnum));
if (segnum != segment_none) {
object_create_explosion(segnum,tpnt,i2f(20),VCLIP_BIG_PLAYER_EXPLOSION);
if (d_rand()<10000 || ++sound_count==7) { //pseudo-random
digi_link_sound_to_pos( SOUND_TUNNEL_EXPLOSION, segnum, sidenum_t::WLEFT, tpnt, 0, F1_0 );
sound_count=0;
}
}
explosion_wait1 = 0x2000 + d_rand()/4;
}
}
//do little explosions on walls
if (Endlevel_sequence >= EL_FLYTHROUGH && Endlevel_sequence < EL_OUTSIDE)
if ((explosion_wait2-=FrameTime) < 0) {
fvi_info hit_data;
//create little explosion on wall
auto tpnt = vm_vec_copy_scale(ConsoleObject->orient.rvec,(d_rand()-D_RAND_MAX/2)*100);
vm_vec_scale_add2(tpnt,ConsoleObject->orient.uvec,(d_rand()-D_RAND_MAX/2)*100);
vm_vec_add2(tpnt,ConsoleObject->pos);
if (Endlevel_sequence == EL_FLYTHROUGH)
vm_vec_scale_add2(tpnt,ConsoleObject->orient.fvec,d_rand()*200);
else
vm_vec_scale_add2(tpnt,ConsoleObject->orient.fvec,d_rand()*60);
//find hit point on wall
const auto hit_type = find_vector_intersection(fvi_query{
ConsoleObject->pos,
tpnt,
fvi_query::unused_ignore_obj_list,
fvi_query::unused_LevelUniqueObjectState,
fvi_query::unused_Robot_info,
0,
Objects.icptridx(object_first),
}, ConsoleObject->segnum, 0, hit_data);
if (hit_type == fvi_hit_type::Wall && hit_data.hit_seg != segment_none)
object_create_explosion(vmsegptridx(hit_data.hit_seg), hit_data.hit_pnt, i2f(3) + d_rand() * 6, VCLIP_SMALL_EXPLOSION);
explosion_wait2 = (0xa00 + d_rand()/8)/2;
}
switch (Endlevel_sequence) {
case EL_OFF: return result;
case EL_FLYTHROUGH: {
do_endlevel_flythrough(LevelUniqueObjectState, LevelSharedSegmentState, LevelUniqueSegmentState, &fly_objects[0]);
if (ConsoleObject->segnum == PlayerUniqueEndlevelState.transition_segnum)
{
#if defined(DXX_BUILD_DESCENT_II)
if (PLAYING_BUILTIN_MISSION && endlevel_movie_played != movie_play_status::skipped)
result = std::max(stop_endlevel_sequence(), result);
else
#endif
{
//songs_play_song( SONG_ENDLEVEL, 0 );
Endlevel_sequence = EL_LOOKBACK;
const auto &&objnum = obj_create(LevelUniqueObjectState, LevelSharedSegmentState, LevelUniqueSegmentState, OBJ_CAMERA, 0,
vmsegptridx(ConsoleObject->segnum), ConsoleObject->pos, &ConsoleObject->orient, 0,
object::control_type::None, object::movement_type::None, RT_NONE);
if (objnum == object_none) { //can't get object, so abort
return std::max(stop_endlevel_sequence(), result);
}
Viewer = endlevel_camera = objnum;
select_cockpit(cockpit_mode_t::letterbox);
fly_objects[1] = fly_objects[0];
fly_objects[1].obj = endlevel_camera;
fly_objects[1].speed = (5*cur_fly_speed)/4;
fly_objects[1].offset_frac = 0x4000;
vm_vec_scale_add2(endlevel_camera->pos,endlevel_camera->orient.fvec,i2f(7));
timer=0x20000;
}
}
break;
}
case EL_LOOKBACK: {
do_endlevel_flythrough(LevelUniqueObjectState, LevelSharedSegmentState, LevelUniqueSegmentState, &fly_objects[0]);
do_endlevel_flythrough(LevelUniqueObjectState, LevelSharedSegmentState, LevelUniqueSegmentState, &fly_objects[1]);
if (timer>0) {
timer -= FrameTime;
if (timer < 0) //reduce speed
fly_objects[1].speed = fly_objects[0].speed;
}
if (endlevel_camera->segnum == PlayerUniqueEndlevelState.exit_segnum)
{
Endlevel_sequence = EL_OUTSIDE;
timer = i2f(2);
vm_vec_negate(endlevel_camera->orient.fvec);
vm_vec_negate(endlevel_camera->orient.rvec);
const auto cam_angles = vm_extract_angles_matrix(endlevel_camera->orient);
const auto exit_seg_angles = vm_extract_angles_matrix(mine_exit_orient);
bank_rate = (-exit_seg_angles.b - cam_angles.b)/2;
ConsoleObject->control_source = endlevel_camera->control_source = object::control_type::None;
}
break;
}
case EL_OUTSIDE: {
vm_vec_scale_add2(ConsoleObject->pos,ConsoleObject->orient.fvec,fixmul(FrameTime,cur_fly_speed));
vm_vec_scale_add2(endlevel_camera->pos,endlevel_camera->orient.fvec,fixmul(FrameTime,-2*cur_fly_speed));
vm_vec_scale_add2(endlevel_camera->pos,endlevel_camera->orient.uvec,fixmul(FrameTime,-cur_fly_speed/10));
auto cam_angles = vm_extract_angles_matrix(endlevel_camera->orient);
cam_angles.b += fixmul(bank_rate,FrameTime);
vm_angles_2_matrix(endlevel_camera->orient,cam_angles);
timer -= FrameTime;
if (timer < 0) {
Endlevel_sequence = EL_STOPPED;
vm_extract_angles_matrix(player_angles,ConsoleObject->orient);
timer = i2f(3);
}
break;
}
case EL_STOPPED: {
get_angs_to_object(player_dest_angles,station_pos,ConsoleObject->pos);
chase_angles(&player_angles,&player_dest_angles);
vm_angles_2_matrix(ConsoleObject->orient,player_angles);
vm_vec_scale_add2(ConsoleObject->pos,ConsoleObject->orient.fvec,fixmul(FrameTime,cur_fly_speed));
timer -= FrameTime;
if (timer < 0) {
#ifdef SHORT_SEQUENCE
result = std::max(stop_endlevel_sequence(), result);
#else
Endlevel_sequence = EL_PANNING;
vm_extract_angles_matrix(camera_cur_angles,endlevel_camera->orient);
timer = i2f(3);
if (Game_mode & GM_MULTI) { // try to skip part of the seq if multiplayer
result = std::max(stop_endlevel_sequence(), result);
return result;
}
#endif //SHORT_SEQUENCE
}
break;
}
#ifndef SHORT_SEQUENCE
case EL_PANNING: {
int mask;
get_angs_to_object(player_dest_angles,station_pos,ConsoleObject->pos);
chase_angles(&player_angles,&player_dest_angles);
vm_angles_2_matrix(ConsoleObject->orient,player_angles);
vm_vec_scale_add2(ConsoleObject->pos,ConsoleObject->orient.fvec,fixmul(FrameTime,cur_fly_speed));
get_angs_to_object(camera_desired_angles,ConsoleObject->pos,endlevel_camera->pos);
mask = chase_angles(&camera_cur_angles,&camera_desired_angles);
vm_angles_2_matrix(endlevel_camera->orient,camera_cur_angles);
if ((mask&5) == 5) {
vms_vector tvec;
Endlevel_sequence = EL_CHASING;
vm_vec_normalized_dir_quick(tvec,station_pos,ConsoleObject->pos);
vm_vector_2_matrix(ConsoleObject->orient,tvec,&surface_orient.uvec,nullptr);
desired_fly_speed *= 2;
}
break;
}
case EL_CHASING: {
fix d,speed_scale;
get_angs_to_object(camera_desired_angles,ConsoleObject->pos,endlevel_camera->pos);
chase_angles(&camera_cur_angles,&camera_desired_angles);
vm_angles_2_matrix(endlevel_camera->orient,camera_cur_angles);
d = vm_vec_dist_quick(ConsoleObject->pos,endlevel_camera->pos);
speed_scale = fixdiv(d,i2f(0x20));
if (d<f1_0) d=f1_0;
get_angs_to_object(player_dest_angles,station_pos,ConsoleObject->pos);
chase_angles(&player_angles,&player_dest_angles);
vm_angles_2_matrix(ConsoleObject->orient,player_angles);
vm_vec_scale_add2(ConsoleObject->pos,ConsoleObject->orient.fvec,fixmul(FrameTime,cur_fly_speed));
vm_vec_scale_add2(endlevel_camera->pos,endlevel_camera->orient.fvec,fixmul(FrameTime,fixmul(speed_scale,cur_fly_speed)));
if (vm_vec_dist(ConsoleObject->pos,station_pos) < i2f(10))
result = std::max(stop_endlevel_sequence(), result);
break;
}
#endif //ifdef SHORT_SEQUENCE
}
return result;
}
namespace {
static void endlevel_render_mine(const d_level_shared_segment_state &LevelSharedSegmentState, grs_canvas &canvas, fix eye_offset)
{
auto Viewer_eye = Viewer->pos;
if (Viewer->type == OBJ_PLAYER )
vm_vec_scale_add2(Viewer_eye,Viewer->orient.fvec,(Viewer->size*3)/4);
if (eye_offset)
vm_vec_scale_add2(Viewer_eye,Viewer->orient.rvec,eye_offset);
#if DXX_USE_EDITOR
if (EditorWindow)
Viewer_eye = Viewer->pos;
#endif
segnum_t start_seg_num;
if (Endlevel_sequence >= EL_OUTSIDE) {
start_seg_num = PlayerUniqueEndlevelState.exit_segnum;
}
else {
start_seg_num = find_point_seg(LevelSharedSegmentState, Viewer_eye, Segments.vcptridx(Viewer->segnum));
if (start_seg_num==segment_none)
start_seg_num = Viewer->segnum;
}
g3_set_view_matrix(Viewer_eye, Endlevel_sequence == EL_LOOKBACK
? vm_matrix_x_matrix(Viewer->orient, vm_angles_2_matrix(vms_angvec{0, 0, INT16_MAX}))
: Viewer->orient, Render_zoom);
window_rendered_data window;
render_mine(canvas, Viewer_eye, start_seg_num, eye_offset, window);
}
}
void render_endlevel_frame(grs_canvas &canvas, fix eye_offset)
{
auto &Objects = LevelUniqueObjectState.Objects;
auto &vcobjptridx = Objects.vcptridx;
g3_start_frame(canvas);
if (Endlevel_sequence < EL_OUTSIDE)
endlevel_render_mine(LevelSharedSegmentState, canvas, eye_offset);
else
render_external_scene(vcobjptridx, canvas, LevelUniqueLightState, eye_offset);
g3_end_frame();
}
///////////////////////// copy of flythrough code for endlevel
#define MAX_ANGSTEP 0x4000 //max turn per second
#define MAX_SLIDE_PER_SEGMENT 0x10000
namespace {
void do_endlevel_flythrough(d_level_unique_object_state &LevelUniqueObjectState, const d_level_shared_segment_state &LevelSharedSegmentState, d_level_unique_segment_state &LevelUniqueSegmentState, flythrough_data *const flydata)
{
auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
auto &Objects = LevelUniqueObjectState.Objects;
auto &Vertices = LevelSharedVertexState.get_vertices();
const auto &&obj = Objects.vmptridx(flydata->obj);
vcsegidx_t old_player_seg = obj->segnum;
//move the player for this frame
if (!flydata->first_time) {
vm_vec_scale_add2(obj->pos,flydata->step,FrameTime);
angvec_add2_scale(flydata->angles,flydata->angstep,FrameTime);
vm_angles_2_matrix(obj->orient,flydata->angles);
}
//check new player seg
update_object_seg(Objects.vmptr, LevelSharedSegmentState, LevelUniqueSegmentState, obj);
const shared_segment &pseg = *vcsegptr(obj->segnum);
if (flydata->first_time || obj->segnum != old_player_seg) { //moved into new seg
fix seg_time;
sidenum_t up_side{};
sidenum_t exit_side; //what side we leave through
sidenum_t entry_side{}; //what side we enter through
//find new exit side
if (!flydata->first_time) {
entry_side = matt_find_connect_side(vcsegptr(obj->segnum), old_player_seg);
exit_side = Side_opposite[entry_side];
}
if (flydata->first_time || entry_side == side_none || pseg.children[exit_side] == segment_none)
exit_side = find_exit_side(LevelSharedSegmentState, LevelSharedVertexState, LevelUniqueObjectState.last_console_player_position, obj);
{ //find closest side to align to
fix d,largest_d=-f1_0;
for (const auto i : MAX_SIDES_PER_SEGMENT)
{
d = vm_vec_dot(pseg.sides[i].normals[0], flydata->obj->orient.uvec);
if (d > largest_d) {largest_d = d; up_side=i;}
}
}
//update target point & angles
//where we are heading (center of exit_side)
auto &vcvertptr = Vertices.vcptr;
auto dest_point = compute_center_point_on_side(vcvertptr, pseg, exit_side);
const vms_vector nextcenter = (pseg.children[exit_side] == segment_exit)
? dest_point
: compute_segment_center(vcvertptr, vcsegptr(pseg.children[exit_side]));
//update target point and movement points
//offset object sideways
if (flydata->offset_frac) {
std::array<vms_vector, 2> sp;
auto isp = sp.begin();
for (const auto i : MAX_SIDES_PER_SEGMENT)
if (i!=entry_side && i!=exit_side && i!=up_side && i!=Side_opposite[up_side])
{
compute_center_point_on_side(vcvertptr, *isp, pseg, i);
++ isp;
if (isp == sp.end())
break;
}
const fix dist = std::min(fixmul(vm_vec_dist(sp[0], sp[1]), flydata->offset_frac), flydata->offset_dist + MAX_SLIDE_PER_SEGMENT);
flydata->offset_dist = dist;
vm_vec_scale_add2(dest_point,obj->orient.rvec,dist);
}
vm_vec_sub(flydata->step,dest_point,obj->pos);
auto step_size = vm_vec_normalize_quick(flydata->step);
vm_vec_scale(flydata->step,flydata->speed);
const auto curcenter = compute_segment_center(vcvertptr, pseg);
vm_vec_sub(flydata->headvec,nextcenter,curcenter);
const auto dest_orient = vm_vector_2_matrix(flydata->headvec,&pseg.sides[up_side].normals[0],nullptr);
//where we want to be pointing
const auto dest_angles = vm_extract_angles_matrix(dest_orient);
if (flydata->first_time)
vm_extract_angles_matrix(flydata->angles,obj->orient);
seg_time = fixdiv(step_size,flydata->speed); //how long through seg
if (seg_time) {
flydata->angstep.x = max(-MAX_ANGSTEP,min(MAX_ANGSTEP,fixdiv(delta_ang(flydata->angles.p,dest_angles.p),seg_time)));
flydata->angstep.z = max(-MAX_ANGSTEP,min(MAX_ANGSTEP,fixdiv(delta_ang(flydata->angles.b,dest_angles.b),seg_time)));
flydata->angstep.y = max(-MAX_ANGSTEP,min(MAX_ANGSTEP,fixdiv(delta_ang(flydata->angles.h,dest_angles.h),seg_time)));
}
else {
flydata->angles = dest_angles;
flydata->angstep.x = flydata->angstep.y = flydata->angstep.z = 0;
}
}
flydata->first_time=0;
}
}
#define LINE_LEN 80
#define NUM_VARS 8
#define STATION_DIST i2f(1024)
//called for each level to load & setup the exit sequence
void load_endlevel_data(int level_num)
{
auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
auto &Vertices = LevelSharedVertexState.get_vertices();
d_fname filename;
char *p;
int var;
int have_binary = 0;
endlevel_data_loaded = 0; //not loaded yet
try_again:
;
if (level_num<0) //secret level
filename = Current_mission->secret_level_names[-level_num - 1];
else //normal level
filename = Current_mission->level_names[level_num - 1];
#if defined(DXX_BUILD_DESCENT_I)
if (!convert_ext(filename,"end"))
return;
#elif defined(DXX_BUILD_DESCENT_II)
if (!convert_ext(filename,"END"))
Error("Error converting filename <%s> for endlevel data\n",static_cast<const char *>(filename));
#endif
auto ifile = PHYSFSX_openReadBuffered(filename).first;
if (!ifile) {
convert_ext(filename,"txb");
if (!strcmp(filename, Current_mission->briefing_text_filename) ||
!strcmp(filename, Current_mission->ending_text_filename))
return; // Don't want to interpret the briefing as an end level sequence!
ifile = PHYSFSX_openReadBuffered(filename).first;
if (!ifile) {
if (level_num==1) {
#if defined(DXX_BUILD_DESCENT_II)
con_printf(CON_DEBUG, "Cannot load file text of binary version of <%s>",static_cast<const char *>(filename));
endlevel_data_loaded = 0; // won't be able to play endlevel sequence
#endif
return;
}
else {
level_num = 1;
goto try_again;
}
}
have_binary = 1;
}
//ok...this parser is pretty simple. It ignores comments, but
//everything else must be in the right place
var = 0;
PHYSFSX_gets_line_t<LINE_LEN> line;
while (PHYSFSX_fgets(line,ifile)) {
if (have_binary)
decode_text_line (line);
if ((p=strchr(line,';'))!=NULL)
*p = 0; //cut off comment
for (p = line; isspace(static_cast<unsigned>(*p)); ++p)
;
if (!*p) //empty line
continue;
auto ns = p;
for (auto p2 = p; *p2; ++p2)
if (!isspace(static_cast<unsigned>(*p2)))
ns = p2;
*++ns = 0;
switch (var) {
case 0: { //ground terrain
int iff_error;
palette_array_t pal;
terrain_bm_instance.reset();
iff_error = iff_read_bitmap(p, terrain_bm_instance, &pal);
if (iff_error != IFF_NO_ERROR) {
con_printf(CON_DEBUG, "Can't load exit terrain from file %s: IFF error: %s",
p, iff_errormsg(iff_error));
endlevel_data_loaded = 0; // won't be able to play endlevel sequence
return;
}
terrain_bitmap = &terrain_bm_instance;
gr_remap_bitmap_good(terrain_bm_instance, pal, iff_transparent_color, -1);
break;
}
case 1: //height map
load_terrain(p);
break;
case 2:
sscanf(p,"%d,%d",&exit_point_bmx,&exit_point_bmy);
break;
case 3: //exit heading
exit_angles.h = i2f(atoi(p))/360;
break;
case 4: { //planet bitmap
int iff_error;
palette_array_t pal;
satellite_bm_instance.reset();
iff_error = iff_read_bitmap(p, satellite_bm_instance, &pal);
if (iff_error != IFF_NO_ERROR) {
con_printf(CON_DEBUG, "Can't load exit satellite from file %s: IFF error: %s",
p, iff_errormsg(iff_error));
endlevel_data_loaded = 0; // won't be able to play endlevel sequence
return;
}
satellite_bitmap = &satellite_bm_instance;
gr_remap_bitmap_good(satellite_bm_instance, pal, iff_transparent_color, -1);
break;
}
case 5: //earth pos
case 7: { //station pos
vms_angvec ta;
int pitch,head;
sscanf(p,"%d,%d",&head,&pitch);
ta.h = i2f(head)/360;
ta.p = -i2f(pitch)/360;
ta.b = 0;
const auto &&tm = vm_angles_2_matrix(ta);
if (var==5)
satellite_pos = tm.fvec;
//vm_vec_copy_scale(&satellite_pos,&tm.fvec,SATELLITE_DIST);
else
station_pos = tm.fvec;
break;
}
case 6: //planet size
satellite_size = i2f(atoi(p));
break;
}
var++;
}
Assert(var == NUM_VARS);
// OK, now the data is loaded. Initialize everything
//find the exit sequence by searching all segments for a side with
//children == -2
const auto &&exit_segside = find_exit_segment_side(vcsegptridx);
const icsegidx_t &exit_segnum = exit_segside.first;
const auto &exit_side = exit_segside.second;
PlayerUniqueEndlevelState.exit_segnum = exit_segnum;
if (exit_segnum == segment_none)
return;
const auto &&exit_seg = vmsegptr(exit_segnum);
auto &vcvertptr = Vertices.vcptr;
compute_segment_center(vcvertptr, mine_exit_point, exit_seg);
extract_orient_from_segment(vcvertptr, mine_exit_orient, exit_seg);
compute_center_point_on_side(vcvertptr, mine_side_exit_point, exit_seg, exit_side);
vm_vec_scale_add(mine_ground_exit_point,mine_exit_point,mine_exit_orient.uvec,-i2f(20));
//compute orientation of surface
{
auto &&exit_orient = vm_angles_2_matrix(exit_angles);
vm_transpose_matrix(exit_orient);
vm_matrix_x_matrix(surface_orient,mine_exit_orient,exit_orient);
vms_matrix tm = vm_transposed_matrix(surface_orient);
const auto tv0 = vm_vec_rotate(station_pos,tm);
vm_vec_scale_add(station_pos,mine_exit_point,tv0,STATION_DIST);
const auto tv = vm_vec_rotate(satellite_pos,tm);
vm_vec_scale_add(satellite_pos,mine_exit_point,tv,SATELLITE_DIST);
const auto tm2 = vm_vector_2_matrix(tv,&surface_orient.uvec,nullptr);
vm_vec_copy_scale(satellite_upvec,tm2.uvec,SATELLITE_HEIGHT);
}
endlevel_data_loaded = 1;
}
}