08e4a6e620
clang becomes confused trying to determine which vm_distance_squared
constructor to use for a literal input of 0x7fffffffffffffff, even
though the size of the input requires it to be `long` and only one
constructor can take a `long`. Switch from an explicit
0x7fffffffffffffff to the symbolic constant INT64_MAX, which has the
same value, but a platform-appropriate suffix to force the compiler to
pick the right type.
For general clarity, switch some other instances of integer maximum
literals to symbolic constants of the same value.
This commit has no effect on the generated code (except for changes to
line numbers).
Reported-by: kreatordxx <https://github.com/dxx-rebirth/dxx-rebirth/pull/324>
Fixes: 17208cca79
("Disallow int for vm_distance_squared")
1583 lines
41 KiB
C++
1583 lines
41 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 SLEW_ON 1
|
|
|
|
//#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 "gauges.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 "multi.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 "titles.h"
|
|
#include "hudmsg.h"
|
|
#if DXX_USE_OGL
|
|
#include "ogl_init.h"
|
|
#endif
|
|
|
|
#if DXX_USE_EDITOR
|
|
#include "editor/editor.h"
|
|
#endif
|
|
|
|
#include "compiler-begin.h"
|
|
#include "compiler-range_for.h"
|
|
|
|
using std::min;
|
|
using std::max;
|
|
|
|
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
|
|
};
|
|
|
|
#define MAX_FLY_OBJECTS 2
|
|
|
|
}
|
|
|
|
static 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
|
|
|
|
#define SHORT_SEQUENCE 1 //if defined, end sequnce when panning starts
|
|
//#define STATION_ENABLED 1 //if defined, load & use space station model
|
|
|
|
int Endlevel_sequence = 0;
|
|
|
|
static segnum_t transition_segnum;
|
|
segnum_t exit_segnum;
|
|
|
|
static object *endlevel_camera;
|
|
|
|
#define FLY_SPEED i2f(50)
|
|
|
|
static void do_endlevel_flythrough(flythrough_data *flydata);
|
|
static void draw_stars();
|
|
static int find_exit_side(const object_base &obj);
|
|
static void generate_starfield();
|
|
static void start_endlevel_flythrough(flythrough_data *flydata,const vobjptr_t obj,fix speed);
|
|
|
|
#if defined(DXX_BUILD_DESCENT_II)
|
|
constexpr char 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 int endlevel_movie_played = MOVIE_NOT_PLAYED;
|
|
#endif
|
|
|
|
|
|
#define FLY_ACCEL i2f(5)
|
|
|
|
static fix cur_fly_speed,desired_fly_speed;
|
|
|
|
static grs_bitmap *satellite_bitmap;
|
|
grs_bitmap *terrain_bitmap; //!!*exit_bitmap,
|
|
vms_vector satellite_pos,satellite_upvec;
|
|
//!!grs_bitmap **exit_bitmap_list[1];
|
|
unsigned exit_modelnum,destroyed_exit_modelnum;
|
|
|
|
static vms_vector station_pos{0xf8c4<<10,0x3c1c<<12,0x372<<10};
|
|
|
|
#ifdef STATION_ENABLED
|
|
static grs_bitmap *station_bitmap;
|
|
grs_bitmap **station_bitmap_list[1];
|
|
static unsigned station_modelnum;
|
|
#endif
|
|
|
|
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;
|
|
|
|
//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 size_t matt_find_connect_side(const segment &seg0, const vcsegidx_t seg1)
|
|
{
|
|
auto &children = seg0.children;
|
|
return std::distance(children.begin(), std::find(children.begin(), children.end(), seg1));
|
|
}
|
|
|
|
#if defined(DXX_BUILD_DESCENT_II)
|
|
#define MOVIE_REQUIRED 1
|
|
|
|
//returns movie played status. see movie.h
|
|
static int start_endlevel_movie()
|
|
{
|
|
char movie_name[] = "esa.mve";
|
|
int r;
|
|
palette_array_t save_pal;
|
|
|
|
//Assert(PLAYING_BUILTIN_MISSION); //only play movie for built-in mission
|
|
|
|
//Assert(N_MOVIES >= Last_level);
|
|
//Assert(N_MOVIES_SECRET >= -Last_secret_level);
|
|
|
|
if (is_SHAREWARE)
|
|
return 0;
|
|
if (!is_D2_OEM)
|
|
if (Current_level_num == Last_level)
|
|
return 1; //don't play movie
|
|
|
|
if (Current_level_num > 0)
|
|
movie_name[2] = movie_table[Current_level_num-1];
|
|
else {
|
|
return 0; //no escapes for secret level
|
|
}
|
|
|
|
save_pal = gr_palette;
|
|
|
|
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
|
|
|
|
void free_endlevel_data()
|
|
{
|
|
terrain_bm_instance.reset();
|
|
satellite_bm_instance.reset();
|
|
|
|
free_light_table();
|
|
free_height_array();
|
|
}
|
|
|
|
void init_endlevel()
|
|
{
|
|
//##satellite_bitmap = bm_load("earth.bbm");
|
|
//##terrain_bitmap = bm_load("moon.bbm");
|
|
//##
|
|
//##load_terrain("matt5b.bbm"); //load bitmap as height array
|
|
//##//load_terrain("ttest2.bbm"); //load bitmap as height array
|
|
|
|
#ifdef STATION_ENABLED
|
|
station_bitmap = bm_load("steel3.bbm");
|
|
station_bitmap_list[0] = &station_bitmap;
|
|
|
|
station_modelnum = load_polygon_model("station.pof",1,station_bitmap_list,NULL);
|
|
#endif
|
|
|
|
//!! exit_bitmap = bm_load("steel1.bbm");
|
|
//!! exit_bitmap_list[0] = &exit_bitmap;
|
|
|
|
//!! exit_modelnum = load_polygon_model("exit01.pof",1,exit_bitmap_list,NULL);
|
|
//!! destroyed_exit_modelnum = load_polygon_model("exit01d.pof",1,exit_bitmap_list,NULL);
|
|
|
|
generate_starfield();
|
|
}
|
|
|
|
static object *external_explosion;
|
|
static int ext_expl_playing,mine_destroyed;
|
|
|
|
static vms_angvec exit_angles={-0xa00,0,0};
|
|
|
|
vms_matrix surface_orient;
|
|
|
|
static int endlevel_data_loaded;
|
|
|
|
namespace dsx {
|
|
window_event_result start_endlevel_sequence()
|
|
{
|
|
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
|
|
{
|
|
window_set_visible(Game_wind, 0); // suspend the game, including drawing
|
|
start_endlevel_movie();
|
|
window_set_visible(Game_wind, 1);
|
|
}
|
|
strcpy(last_palette_loaded,""); //force palette load next time
|
|
#endif
|
|
return window_event_result::ignored;
|
|
}
|
|
|
|
if (Player_dead_state != player_dead_state::no ||
|
|
(ConsoleObject->flags & OF_SHOULD_BE_DEAD))
|
|
return window_event_result::ignored; //don't start if dead!
|
|
con_printf(CON_NORMAL, "You have escaped the mine!");
|
|
|
|
#if defined(DXX_BUILD_DESCENT_II)
|
|
// Dematerialize Buddy!
|
|
range_for (const auto &&objp, vobjptr)
|
|
{
|
|
if (objp->type == OBJ_ROBOT)
|
|
if (Robot_info[get_robot_id(objp)].companion) {
|
|
object_create_explosion(vsegptridx(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_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))
|
|
{
|
|
window_set_visible(Game_wind, 0); // suspend the game, including drawing
|
|
endlevel_movie_played = start_endlevel_movie();
|
|
window_set_visible(Game_wind, 1);
|
|
}
|
|
|
|
if (!(!(Game_mode & GM_MULTI) && (endlevel_movie_played == MOVIE_NOT_PLAYED) && endlevel_data_loaded))
|
|
#endif
|
|
{
|
|
|
|
return PlayerFinishedLevel(0); //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
|
|
#ifndef NDEBUG
|
|
segnum_t last_segnum;
|
|
#endif
|
|
{
|
|
//count segments in exit tunnel
|
|
|
|
const object_base &console = vobjptr(ConsoleObject);
|
|
const auto exit_console_side = find_exit_side(console);
|
|
auto old_segnum = vcsegptridx(console.segnum);
|
|
auto child = old_segnum->children[exit_console_side];
|
|
unsigned tunnel_length = 0;
|
|
for (;;)
|
|
{
|
|
if (child == segment_none)
|
|
{
|
|
return PlayerFinishedLevel(0); //don't do special sequence
|
|
}
|
|
tunnel_length++;
|
|
if (child == segment_exit)
|
|
break;
|
|
const auto segnum = vcsegptridx(child);
|
|
const auto entry_side = matt_find_connect_side(segnum, old_segnum);
|
|
const auto exit_side = Side_opposite[entry_side];
|
|
old_segnum = segnum;
|
|
child = segnum->children[exit_side];
|
|
}
|
|
#ifndef NDEBUG
|
|
last_segnum = old_segnum;
|
|
#endif
|
|
//now pick transition segnum 1/3 of the way in
|
|
|
|
old_segnum = vcsegptridx(console.segnum);
|
|
child = old_segnum->children[exit_console_side];
|
|
for (auto i = tunnel_length / 3; i; --i)
|
|
{
|
|
/*
|
|
* No sanity checks here. If the tunnel ended with
|
|
* segment_none, the function would have returned from 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.
|
|
*/
|
|
auto segnum = vcsegptridx(child);
|
|
const auto entry_side = matt_find_connect_side(segnum, old_segnum);
|
|
const auto exit_side = Side_opposite[entry_side];
|
|
old_segnum = segnum;
|
|
child = segnum->children[exit_side];
|
|
}
|
|
transition_segnum = child;
|
|
}
|
|
|
|
if (Game_mode & GM_MULTI) {
|
|
multi_send_endlevel_start(multi_endlevel_type::normal);
|
|
multi_do_protocol_frame(1, 1);
|
|
}
|
|
#ifndef NDEBUG
|
|
Assert(last_segnum == exit_segnum);
|
|
#endif
|
|
songs_play_song( SONG_ENDLEVEL, 0 );
|
|
|
|
Endlevel_sequence = EL_FLYTHROUGH;
|
|
|
|
ConsoleObject->movement_type = MT_NONE; //movement handled by flythrough
|
|
ConsoleObject->control_type = CT_NONE;
|
|
|
|
Game_suspended |= SUSP_ROBOTS; //robots don't move
|
|
|
|
cur_fly_speed = desired_fly_speed = FLY_SPEED;
|
|
|
|
start_endlevel_flythrough(&fly_objects[0], vobjptr(ConsoleObject), cur_fly_speed); //initialize
|
|
|
|
HUD_init_message_literal(HM_DEFAULT, TXT_EXIT_SEQUENCE );
|
|
|
|
outside_mine = ext_expl_playing = 0;
|
|
|
|
flash_scale = f1_0;
|
|
|
|
//init_endlevel();
|
|
|
|
mine_destroyed=0;
|
|
|
|
return window_event_result::handled;
|
|
}
|
|
}
|
|
|
|
static vms_angvec player_angles,player_dest_angles;
|
|
#ifndef SHORT_SEQUENCE
|
|
static vms_angvec camera_desired_angles,camera_cur_angles;
|
|
#endif
|
|
|
|
#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;
|
|
}
|
|
|
|
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(0);
|
|
}
|
|
|
|
#define VCLIP_BIG_PLAYER_EXPLOSION 58
|
|
|
|
//--unused-- vms_vector upvec = {0,f1_0,0};
|
|
|
|
//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);
|
|
}
|
|
|
|
namespace dsx {
|
|
window_event_result do_endlevel_frame()
|
|
{
|
|
static fix timer;
|
|
static fix bank_rate;
|
|
vms_vector save_last_pos;
|
|
static fix explosion_wait1=0;
|
|
static fix explosion_wait2=0;
|
|
static fix ext_expl_halflife;
|
|
|
|
save_last_pos = ConsoleObject->last_pos; //don't let move code change this
|
|
auto result = object_move_all();
|
|
ConsoleObject->last_pos = save_last_pos;
|
|
|
|
if (ext_expl_playing) {
|
|
|
|
do_explosion_sequence(vobjptr(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 = vsegptridx(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, 0, 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(tpnt, vsegptridx(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, 0, 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_query fq;
|
|
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
|
|
|
|
fq.p0 = &ConsoleObject->pos;
|
|
fq.p1 = &tpnt;
|
|
fq.startseg = ConsoleObject->segnum;
|
|
fq.rad = 0;
|
|
fq.thisobjnum = object_first;
|
|
fq.ignore_obj_list.first = nullptr;
|
|
fq.flags = 0;
|
|
|
|
find_vector_intersection(fq, hit_data);
|
|
|
|
if (hit_data.hit_type==HIT_WALL && hit_data.hit_seg!=segment_none)
|
|
object_create_explosion(vsegptridx(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(&fly_objects[0]);
|
|
|
|
if (ConsoleObject->segnum == transition_segnum) {
|
|
|
|
#if defined(DXX_BUILD_DESCENT_II)
|
|
if (PLAYING_BUILTIN_MISSION && endlevel_movie_played != MOVIE_NOT_PLAYED)
|
|
result = std::max(stop_endlevel_sequence(), result);
|
|
else
|
|
#endif
|
|
{
|
|
|
|
//songs_play_song( SONG_ENDLEVEL, 0 );
|
|
|
|
Endlevel_sequence = EL_LOOKBACK;
|
|
|
|
auto objnum = obj_create(OBJ_CAMERA, 0,
|
|
vsegptridx(ConsoleObject->segnum), ConsoleObject->pos, &ConsoleObject->orient, 0,
|
|
CT_NONE,MT_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(CM_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(&fly_objects[0]);
|
|
do_endlevel_flythrough(&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 == 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_type = endlevel_camera->control_type = CT_NONE;
|
|
|
|
#ifdef SLEW_ON
|
|
slew_obj = endlevel_camera;
|
|
#endif
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case EL_OUTSIDE: {
|
|
vm_vec_scale_add2(ConsoleObject->pos,ConsoleObject->orient.fvec,fixmul(FrameTime,cur_fly_speed));
|
|
#ifndef SLEW_ON
|
|
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);
|
|
#endif
|
|
|
|
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 SLEW_ON
|
|
slew_obj = endlevel_camera;
|
|
_do_slew_movement(endlevel_camera,1);
|
|
timer += FrameTime; //make time stop
|
|
break;
|
|
#else
|
|
|
|
#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
|
|
#endif //SLEW_ON
|
|
|
|
}
|
|
break;
|
|
}
|
|
|
|
#ifndef SHORT_SEQUENCE
|
|
case EL_PANNING: {
|
|
#ifndef SLEW_ON
|
|
int mask;
|
|
#endif
|
|
|
|
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));
|
|
|
|
#ifdef SLEW_ON
|
|
_do_slew_movement(endlevel_camera,1);
|
|
#else
|
|
|
|
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;
|
|
}
|
|
#endif
|
|
|
|
break;
|
|
}
|
|
|
|
case EL_CHASING: {
|
|
fix d,speed_scale;
|
|
|
|
#ifdef SLEW_ON
|
|
_do_slew_movement(endlevel_camera,1);
|
|
#endif
|
|
|
|
get_angs_to_object(camera_desired_angles,ConsoleObject->pos,endlevel_camera->pos);
|
|
chase_angles(&camera_cur_angles,&camera_desired_angles);
|
|
|
|
#ifndef SLEW_ON
|
|
vm_angles_2_matrix(endlevel_camera->orient,camera_cur_angles);
|
|
#endif
|
|
|
|
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));
|
|
#ifndef SLEW_ON
|
|
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);
|
|
#endif
|
|
|
|
break;
|
|
|
|
}
|
|
#endif //ifdef SHORT_SEQUENCE
|
|
|
|
}
|
|
|
|
return result;
|
|
}
|
|
}
|
|
|
|
|
|
#define MIN_D 0x100
|
|
|
|
//find which side to fly out of
|
|
static int find_exit_side(const object_base &obj)
|
|
{
|
|
vms_vector prefvec;
|
|
fix best_val=-f2_0;
|
|
int best_side;
|
|
|
|
//find exit side
|
|
|
|
vm_vec_normalized_dir_quick(prefvec, obj.pos, obj.last_pos);
|
|
|
|
const auto &&pseg = vcsegptr(obj.segnum);
|
|
const auto segcenter = compute_segment_center(pseg);
|
|
|
|
best_side=-1;
|
|
for (int i=MAX_SIDES_PER_SEGMENT;--i >= 0;) {
|
|
fix d;
|
|
|
|
if (pseg->children[i]!=segment_none) {
|
|
auto sidevec = compute_center_point_on_side(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=i;}
|
|
|
|
}
|
|
}
|
|
|
|
Assert(best_side!=-1);
|
|
|
|
return best_side;
|
|
}
|
|
|
|
void draw_exit_model()
|
|
{
|
|
int f=15,u=0; //21;
|
|
g3s_lrgb lrgb = { f1_0, f1_0, f1_0 };
|
|
|
|
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(*grd_curcanv, model_pos, &mine_exit_orient, nullptr, (mine_destroyed)?destroyed_exit_modelnum:exit_modelnum, 0, lrgb, nullptr, nullptr);
|
|
}
|
|
|
|
static int exit_point_bmx,exit_point_bmy;
|
|
|
|
static fix satellite_size = i2f(400);
|
|
|
|
#define SATELLITE_DIST i2f(1024)
|
|
#define SATELLITE_WIDTH satellite_size
|
|
#define SATELLITE_HEIGHT ((satellite_size*9)/4) //((satellite_size*5)/2)
|
|
|
|
constexpr vms_vector vmd_zero_vector{};
|
|
static void render_external_scene(fix eye_offset)
|
|
{
|
|
#if DXX_USE_OGL
|
|
int orig_Render_depth = Render_depth;
|
|
#endif
|
|
g3s_lrgb lrgb = { f1_0, f1_0, f1_0 };
|
|
|
|
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(*grd_curcanv, BM_XRGB(0,0,0));
|
|
|
|
g3_start_instance_matrix(vmd_zero_vector,&surface_orient);
|
|
draw_stars();
|
|
g3_done_instance();
|
|
|
|
{ //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(*grd_curcanv, *satellite_bitmap, p, SATELLITE_WIDTH, top_pnt, SATELLITE_WIDTH, lrgb);
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef STATION_ENABLED
|
|
draw_polygon_model(station_pos,&vmd_identity_matrix,NULL,station_modelnum,0,lrgb,NULL,NULL);
|
|
#endif
|
|
|
|
#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(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();
|
|
if (ext_expl_playing)
|
|
{
|
|
const auto alpha = PlayerCfg.AlphaBlendMineExplosion;
|
|
if (alpha) // set nice transparency/blending for the big explosion
|
|
gr_settransblend(*grd_curcanv, GR_FADE_OFF, GR_BLEND_ADDITIVE_C);
|
|
draw_fireball(vobjptridx(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(*grd_curcanv, GR_FADE_OFF, GR_BLEND_NORMAL); // revert any transparency/blending setting back to normal
|
|
#endif
|
|
}
|
|
|
|
Lighting_on=0;
|
|
render_object(vobjptridx(ConsoleObject));
|
|
Lighting_on=1;
|
|
}
|
|
|
|
#define MAX_STARS 500
|
|
|
|
static array<vms_vector, MAX_STARS> stars;
|
|
|
|
static void generate_starfield()
|
|
{
|
|
for (int i=0;i<MAX_STARS;i++) {
|
|
|
|
stars[i].x = (d_rand() - D_RAND_MAX/2) << 14;
|
|
stars[i].z = (d_rand() - D_RAND_MAX/2) << 14;
|
|
stars[i].y = (d_rand()/2) << 14;
|
|
|
|
}
|
|
}
|
|
|
|
void draw_stars()
|
|
{
|
|
int intensity=31;
|
|
g3s_point p;
|
|
|
|
uint8_t color = 0;
|
|
for (int i=0;i<MAX_STARS;i++) {
|
|
|
|
if ((i&63) == 0) {
|
|
color = BM_XRGB(intensity,intensity,intensity);
|
|
intensity-=3;
|
|
}
|
|
|
|
//g3_rotate_point(&p,&stars[i]);
|
|
g3_rotate_delta_vec(p.p3_vec,stars[i]);
|
|
g3_code_point(p);
|
|
|
|
if (p.p3_codes == 0) {
|
|
|
|
p.p3_flags &= ~PF_PROJECTED;
|
|
|
|
g3_project_point(p);
|
|
#if !DXX_USE_OGL
|
|
gr_pixel(*grd_curcanv, f2i(p.p3_sx), f2i(p.p3_sy), color);
|
|
#else
|
|
g3_draw_sphere(*grd_curcanv, 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 endlevel_render_mine(fix eye_offset)
|
|
{
|
|
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 = exit_segnum;
|
|
}
|
|
else {
|
|
start_seg_num = find_point_seg(Viewer_eye, vsegptridx(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(start_seg_num, eye_offset, window);
|
|
}
|
|
|
|
void render_endlevel_frame(fix eye_offset)
|
|
{
|
|
|
|
g3_start_frame(*grd_curcanv);
|
|
|
|
if (Endlevel_sequence < EL_OUTSIDE)
|
|
endlevel_render_mine(eye_offset);
|
|
else
|
|
render_external_scene(eye_offset);
|
|
|
|
g3_end_frame();
|
|
|
|
}
|
|
|
|
|
|
///////////////////////// copy of flythrough code for endlevel
|
|
|
|
|
|
#define DEFAULT_SPEED i2f(16)
|
|
|
|
#define MIN_D 0x100
|
|
|
|
//if speed is zero, use default speed
|
|
void start_endlevel_flythrough(flythrough_data *flydata,const vobjptr_t obj,fix speed)
|
|
{
|
|
flydata->obj = obj;
|
|
|
|
flydata->first_time = 1;
|
|
|
|
flydata->speed = speed?speed:DEFAULT_SPEED;
|
|
|
|
flydata->offset_frac = 0;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
#define MAX_ANGSTEP 0x4000 //max turn per second
|
|
|
|
#define MAX_SLIDE_PER_SEGMENT 0x10000
|
|
|
|
void do_endlevel_flythrough(flythrough_data *flydata)
|
|
{
|
|
const auto &&obj = vobjptridx(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(obj);
|
|
const auto &&pseg = vsegptr(obj->segnum);
|
|
|
|
if (flydata->first_time || obj->segnum != old_player_seg) { //moved into new seg
|
|
fix seg_time;
|
|
short entry_side,exit_side = -1;//what sides we entry and leave through
|
|
int up_side=0;
|
|
|
|
entry_side=0;
|
|
|
|
//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(obj);
|
|
|
|
{ //find closest side to align to
|
|
fix d,largest_d=-f1_0;
|
|
for (int i=0;i<6;i++) {
|
|
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 dest_point = compute_center_point_on_side(pseg,exit_side);
|
|
const vms_vector nextcenter = (pseg->children[exit_side] == segment_exit)
|
|
? dest_point
|
|
: compute_segment_center(vcsegptr(pseg->children[exit_side]));
|
|
|
|
//update target point and movement points
|
|
|
|
//offset object sideways
|
|
if (flydata->offset_frac) {
|
|
int s0=-1,s1=0;
|
|
fix dist;
|
|
|
|
for (int i=0;i<6;i++)
|
|
if (i!=entry_side && i!=exit_side && i!=up_side && i!=Side_opposite[up_side])
|
|
{
|
|
if (s0==-1)
|
|
s0 = i;
|
|
else
|
|
s1 = i;
|
|
}
|
|
|
|
const auto s0p = compute_center_point_on_side(pseg,s0);
|
|
const auto s1p = compute_center_point_on_side(pseg,s1);
|
|
dist = fixmul(vm_vec_dist(s0p,s1p),flydata->offset_frac);
|
|
|
|
if (dist-flydata->offset_dist > MAX_SLIDE_PER_SEGMENT)
|
|
dist = 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(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 JOY_NULL 15
|
|
#define ROT_SPEED 8 //rate of rotation while key held down
|
|
#define VEL_SPEED (15) //rate of acceleration while key held down
|
|
|
|
#include "key.h"
|
|
#include "joy.h"
|
|
|
|
#ifdef SLEW_ON //this is a special routine for slewing around external scene
|
|
int _do_slew_movement(const vobjptr_t obj, int check_keys )
|
|
{
|
|
int moved = 0;
|
|
vms_vector svel; //scaled velocity (per this frame)
|
|
vms_angvec rotang;
|
|
|
|
if (keyd_pressed[KEY_PAD5])
|
|
vm_vec_zero(obj->mtype.phys_info.velocity);
|
|
|
|
if (check_keys) {
|
|
obj->mtype.phys_info.velocity.x += VEL_SPEED * keyd_pressed[KEY_PAD9] * FrameTime;
|
|
obj->mtype.phys_info.velocity.x -= VEL_SPEED * keyd_pressed[KEY_PAD7] * FrameTime;
|
|
obj->mtype.phys_info.velocity.y += VEL_SPEED * keyd_pressed[KEY_PADMINUS] * FrameTime;
|
|
obj->mtype.phys_info.velocity.y -= VEL_SPEED * keyd_pressed[KEY_PADPLUS] * FrameTime;
|
|
obj->mtype.phys_info.velocity.z += VEL_SPEED * keyd_pressed[KEY_PAD8] * FrameTime;
|
|
obj->mtype.phys_info.velocity.z -= VEL_SPEED * keyd_pressed[KEY_PAD2] * FrameTime;
|
|
|
|
rotang.pitch = rotang.bank = rotang.head = 0;
|
|
rotang.pitch += keyd_pressed[KEY_LBRACKET] * FrameTime / ROT_SPEED;
|
|
rotang.pitch -= keyd_pressed[KEY_RBRACKET] * FrameTime / ROT_SPEED;
|
|
rotang.bank += keyd_pressed[KEY_PAD1] * FrameTime / ROT_SPEED;
|
|
rotang.bank -= keyd_pressed[KEY_PAD3] * FrameTime / ROT_SPEED;
|
|
rotang.head += keyd_pressed[KEY_PAD6] * FrameTime / ROT_SPEED;
|
|
rotang.head -= keyd_pressed[KEY_PAD4] * FrameTime / ROT_SPEED;
|
|
}
|
|
else
|
|
rotang = {};
|
|
|
|
moved = rotang.p | rotang.b | rotang.h;
|
|
|
|
const auto &&rotmat = vm_angles_2_matrix(rotang);
|
|
const auto new_pm = vm_transposed_matrix(obj->orient = vm_matrix_x_matrix(obj->orient,rotmat));
|
|
//make those columns rows
|
|
|
|
moved |= obj->mtype.phys_info.velocity.x | obj->mtype.phys_info.velocity.y | obj->mtype.phys_info.velocity.z;
|
|
|
|
svel = obj->mtype.phys_info.velocity;
|
|
vm_vec_scale(svel,FrameTime); //movement in this frame
|
|
const auto movement = vm_vec_rotate(svel,new_pm);
|
|
|
|
vm_vec_add2(obj->pos,movement);
|
|
|
|
moved |= (movement.x || movement.y || movement.z);
|
|
|
|
return moved;
|
|
}
|
|
#endif
|
|
|
|
#define LINE_LEN 80
|
|
#define NUM_VARS 8
|
|
|
|
#define STATION_DIST i2f(1024)
|
|
|
|
static int convert_ext(d_fname &dest, const char (&ext)[4])
|
|
{
|
|
auto b = begin(dest);
|
|
auto e = end(dest);
|
|
auto t = std::find(b, e, '.');
|
|
if (t != e && std::distance(b, t) <= 8)
|
|
{
|
|
std::copy(begin(ext), end(ext), std::next(t));
|
|
return 1;
|
|
}
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
//called for each level to load & setup the exit sequence
|
|
namespace dsx {
|
|
void load_endlevel_data(int level_num)
|
|
{
|
|
d_fname filename;
|
|
char *p;
|
|
int var;
|
|
int exit_side = 0;
|
|
int have_binary = 0;
|
|
|
|
endlevel_data_loaded = 0; //not loaded yet
|
|
|
|
try_again:
|
|
;
|
|
|
|
if (level_num<0) //secret level
|
|
filename = Secret_level_names[-level_num-1];
|
|
else //normal level
|
|
filename = 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);
|
|
|
|
if (!ifile) {
|
|
|
|
convert_ext(filename,"txb");
|
|
if (!strcmp(filename, Briefing_text_filename) ||
|
|
!strcmp(filename, Ending_text_filename))
|
|
return; // Don't want to interpret the briefing as an end level sequence!
|
|
|
|
ifile = PHYSFSX_openReadBuffered(filename);
|
|
|
|
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
|
|
|
|
exit_segnum = segment_none;
|
|
range_for (const auto &&segp, vcsegptridx)
|
|
{
|
|
for (int sidenum=0;sidenum<6;sidenum++)
|
|
if (segp->children[sidenum] == segment_exit)
|
|
{
|
|
exit_segnum = segp;
|
|
exit_side = sidenum;
|
|
break;
|
|
}
|
|
if (exit_segnum != segment_none)
|
|
break;
|
|
}
|
|
|
|
Assert(exit_segnum!=segment_none);
|
|
|
|
const auto &&exit_seg = vsegptr(exit_segnum);
|
|
compute_segment_center(mine_exit_point, exit_seg);
|
|
extract_orient_from_segment(&mine_exit_orient, exit_seg);
|
|
compute_center_point_on_side(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;
|
|
}
|
|
}
|