mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-07 08:15:59 -10:00
* avoid undefined behavior from UNREACHABLE on reachable code paths
UNREACHABLE expands to __assume(false) / __builtin_unreachable() in
release builds, which is undefined behavior if the code is ever actually
reached. Many UNREACHABLE sites only guard against unexpected-but-
possible runtime states (values from tables, mission/save files, network
packets, scripting, or user input), so reaching them should degrade
gracefully rather than invoke UB.
Replace UNREACHABLE with Assertion(false, ...) at all such sites. It
still pops an error in debug builds, but in release it is a harmless
no-op that simply falls through to the following code. Sites that are
genuinely unreachable by local invariants (e.g. a switch over a bounded
random number) are left as UNREACHABLE.
A few converted sites had no safe fall-through -- an uninitialized read,
null dereference, or null call would have followed -- so they also get a
minimal recovery: initialize the value, guard the dereference, return
early, or skip the offending item.
Where the converted site was a simple guard of the form
if (x) { Assertion(false, ...); return; }
hoist the check onto the invariant instead:
Assertion(!x, ...);
if (x) { return; }
This states the expectation directly and shows the offending value in
the debug popup, while keeping the same graceful runtime degradation.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
* addendum
After consultation with Lafiel, use UNREACHABLE in situations that are effectively impossible even if not provably impossible. UNREACHABLE = "this shouldn't ever happen"; Assertion = "if this ever happens, it's a coding error". Also fix a few other sites and change a couple to Warnings.
---------
Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
4243 lines
132 KiB
C++
4243 lines
132 KiB
C++
/*
|
|
* Copyright (C) Volition, Inc. 1999. All rights reserved.
|
|
*
|
|
* All source code herein is the property of Volition, Inc. You may not sell
|
|
* or otherwise commercially exploit the source or things you created based on the
|
|
* source.
|
|
*
|
|
*/
|
|
|
|
#include "ship/shipfx.h"
|
|
|
|
#include "globalincs/linklist.h"
|
|
|
|
#include "asteroid/asteroid.h"
|
|
#include "bmpman/bmpman.h"
|
|
#include "cmdline/cmdline.h"
|
|
#include "debris/debris.h"
|
|
#include "debugconsole/console.h"
|
|
#include "fireball/fireballs.h"
|
|
#include "gamesequence/gamesequence.h"
|
|
#include "gamesnd/gamesnd.h"
|
|
#include "graphics/shadows.h"
|
|
#include "hud/hudmessage.h"
|
|
#include "io/timer.h"
|
|
#include "lighting/lighting.h"
|
|
#include "math/fvi.h"
|
|
#include "mod_table/mod_table.h"
|
|
#include "model/model.h"
|
|
#include "network/multi.h"
|
|
#include "network/multimsgs.h"
|
|
#include "network/multiutil.h"
|
|
#include "object/object.h"
|
|
#include "object/objectdock.h"
|
|
#include "object/objectsnd.h"
|
|
#include "parse/parselo.h"
|
|
#include "playerman/player.h"
|
|
#include "prop/prop.h"
|
|
#include "render/3d.h" // needed for View_position, which is used when playing a 3D sound
|
|
#include "render/batching.h"
|
|
#include "scripting/hook_api.h"
|
|
#include "ship/ship.h"
|
|
#include "ship/shiphit.h"
|
|
#include "utils/Random.h"
|
|
#include "weapon/muzzleflash.h"
|
|
#include "weapon/shockwave.h"
|
|
#include "weapon/weapon.h"
|
|
|
|
#ifndef NDEBUG
|
|
extern float flFrametime;
|
|
extern int Framecount;
|
|
#endif
|
|
|
|
extern int Cmdline_tbp;
|
|
|
|
#define SHIP_CANNON_BITMAP "argh"
|
|
int Ship_cannon_bitmap = -1;
|
|
|
|
sound_handle Player_engine_wash_loop = sound_handle::invalid();
|
|
|
|
extern float splode_level;
|
|
|
|
const auto OnWarpOutCompleteHook = scripting::OverridableHook<scripting::hooks::ShipSourceConditions>::Factory(
|
|
"On Warp Out Complete", "Called when a ship has completed the warp out animation",
|
|
{{"Self", "ship", "The object that is warping out."}});
|
|
|
|
const auto OnWarpOutHook = scripting::OverridableHook<scripting::hooks::ShipSourceConditions>::Factory(
|
|
"On Warp Out", "Called when a ship warps out.", {{"Self", "ship", "The object that is warping out."}});
|
|
|
|
const auto OnWarpInHook = scripting::OverridableHook<scripting::hooks::ShipSourceConditions>::Factory(
|
|
"On Warp In", "Called when a ship warps in.", {{"Self", "ship", "The object that is warping in."}});
|
|
|
|
static void shipfx_remove_submodel_ship_sparks(ship* shipp, int submodel_num)
|
|
{
|
|
Assert(submodel_num != -1);
|
|
|
|
// maybe no active sparks on submodel
|
|
if (shipp->num_sparks == 0) {
|
|
return;
|
|
}
|
|
|
|
for (int spark_num = 0; spark_num < shipp->num_sparks; spark_num++) {
|
|
if (shipp->sparks[spark_num].submodel_num == submodel_num) {
|
|
shipp->sparks[spark_num].end_time = timestamp(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
void model_get_rotating_submodel_axis(vec3d *model_axis, vec3d *world_axis, const polymodel *pm, const polymodel_instance *pmi, int submodel_num, const matrix *objorient);
|
|
|
|
/**
|
|
* Check if subsystem has live debris and create
|
|
*
|
|
* DKA: 5/26/99 make velocity of debris scale according to size of debris subobject (at least for large subobjects)
|
|
*/
|
|
static void shipfx_subsystem_maybe_create_live_debris(object *ship_objp, const ship *ship_p, const ship_subsys *subsys, const vec3d *exp_center, float exp_mag, bool no_fireballs = false)
|
|
{
|
|
// initializations
|
|
ship *shipp = &Ships[ship_objp->instance];
|
|
polymodel *pm = model_get(Ship_info[ship_p->ship_info_index].model_num);
|
|
polymodel_instance *pmi = model_get_instance(shipp->model_instance_num);
|
|
int submodel_num = subsys->system_info->subobj_num;
|
|
submodel_instance *smi = &pmi->submodel[submodel_num];
|
|
|
|
object *live_debris_obj;
|
|
int i, num_live_debris, live_debris_submodel;
|
|
|
|
// get number of live debris objects to create
|
|
num_live_debris = pm->submodel[submodel_num].num_live_debris;
|
|
if ((num_live_debris <= 0) || (subsys->flags[Ship::Subsystem_Flags::No_live_debris])) {
|
|
return;
|
|
}
|
|
|
|
// make sure the axis point is set
|
|
vec3d model_axis, world_axis, rotvel, world_axis_pt;
|
|
matrix m_rot; // rotation for debris orient about axis
|
|
|
|
if (pm->submodel[submodel_num].rotation_type == MOVEMENT_TYPE_REGULAR || pm->submodel[submodel_num].rotation_type == MOVEMENT_TYPE_INTRINSIC || pm->submodel[submodel_num].rotation_type == MOVEMENT_TYPE_TRIGGERED){
|
|
// get the rotvel
|
|
model_get_rotating_submodel_axis(&model_axis, &world_axis, pm, pmi, submodel_num, &ship_objp->orient);
|
|
vm_vec_copy_scale(&rotvel, &world_axis, smi->current_turn_rate);
|
|
|
|
model_instance_local_to_global_point(&world_axis_pt, &smi->canonical_offset, pm, pmi, submodel_num, &ship_objp->orient, &ship_objp->pos);
|
|
|
|
vm_quaternion_rotate(&m_rot, smi->cur_angle, &model_axis);
|
|
} else {
|
|
//fix to allow non rotating submodels to use live debris
|
|
vm_vec_zero(&rotvel);
|
|
vm_set_identity(&m_rot);
|
|
vm_vec_zero(&world_axis_pt);
|
|
}
|
|
|
|
// create live debris pieces
|
|
for (i=0; i<num_live_debris; i++) {
|
|
live_debris_submodel = pm->submodel[submodel_num].live_debris[i];
|
|
vec3d start_world_pos, start_model_pos, end_world_pos;
|
|
|
|
// get start world pos
|
|
vm_vec_zero(&start_world_pos);
|
|
model_instance_local_to_global_point(&start_world_pos, &pm->submodel[live_debris_submodel].offset, pm, pmi, live_debris_submodel, &ship_objp->orient, &ship_objp->pos );
|
|
|
|
// convert to model coord of underlying submodel
|
|
model_instance_global_to_local_point(&start_model_pos, &start_world_pos, pm, pmi, submodel_num, &ship_objp->orient, &ship_objp->pos);
|
|
|
|
// rotate from submodel coord to world coords
|
|
model_instance_local_to_global_point(&end_world_pos, &start_model_pos, pm, pmi, submodel_num, &ship_objp->orient, &ship_objp->pos);
|
|
|
|
int fireball_type = fireball_ship_explosion_type(&Ship_info[ship_p->ship_info_index]);
|
|
if(fireball_type < 0) {
|
|
fireball_type = FIREBALL_EXPLOSION_MEDIUM;
|
|
}
|
|
|
|
if (!no_fireballs) {
|
|
// create fireball here.
|
|
fireball_create(&end_world_pos, fireball_type, FIREBALL_MEDIUM_EXPLOSION, OBJ_INDEX(ship_objp), pm->submodel[live_debris_submodel].rad);
|
|
}
|
|
|
|
// create debris
|
|
live_debris_obj = debris_create(ship_objp, pm->id, live_debris_submodel, &end_world_pos, exp_center, 1, exp_mag, subsys);
|
|
|
|
// only do if debris is created
|
|
if (live_debris_obj) {
|
|
// get radial velocity of debris
|
|
vec3d delta_x, radial_vel;
|
|
vm_vec_sub(&delta_x, &end_world_pos, &world_axis_pt);
|
|
vm_vec_cross(&radial_vel, &rotvel, &delta_x);
|
|
|
|
if (Ship_info[ship_p->ship_info_index].flags[Ship::Info_Flags::Knossos_device]) {
|
|
// set velocity to cross center of knossos device
|
|
vec3d rand_vec, vec_to_center;
|
|
|
|
float vel_mag = vm_vec_mag_quick(&radial_vel) * 1.3f * (0.9f + 0.2f*frand());
|
|
vm_vec_normalized_dir(&vec_to_center, &world_axis_pt, &end_world_pos);
|
|
vm_vec_rand_vec_quick(&rand_vec);
|
|
vm_vec_scale_add2(&vec_to_center, &rand_vec, 0.2f);
|
|
vm_vec_scale_add2(&live_debris_obj->phys_info.vel, &vec_to_center, vel_mag);
|
|
|
|
} else {
|
|
// Get rotation of debris object
|
|
matrix copy = live_debris_obj->orient;
|
|
vm_matrix_x_matrix(&live_debris_obj->orient, ©, &m_rot);
|
|
|
|
// Add radial velocity (at least as large as exp velocity)
|
|
vec3d temp_vel; // explosion velocity with ship_obj velocity removed
|
|
vm_vec_sub(&temp_vel, &live_debris_obj->phys_info.vel, &ship_objp->phys_info.vel);
|
|
|
|
// find magnitudes of radial and temp velocity
|
|
float vel_mag = vm_vec_mag(&temp_vel);
|
|
float rotvel_mag = vm_vec_mag(&radial_vel);
|
|
|
|
if (rotvel_mag > 0.1) {
|
|
float scale = (1.2f + 0.2f * frand()) * vel_mag / rotvel_mag;
|
|
// always add *at least* rotvel
|
|
if (scale < 1) {
|
|
scale = 1.0f;
|
|
}
|
|
|
|
if (exp_mag > 1) { // whole ship going down
|
|
scale = exp_mag;
|
|
}
|
|
|
|
if (Ship_info[ship_p->ship_info_index].flags[Ship::Info_Flags::Knossos_device]) {
|
|
scale = 1.0f;
|
|
}
|
|
|
|
vm_vec_scale_add2(&live_debris_obj->phys_info.vel, &radial_vel, scale);
|
|
}
|
|
|
|
// scale up speed of debris if ship_obj > 125, but not for knossos
|
|
if (ship_objp->radius > 250 && !(Ship_info[ship_p->ship_info_index].flags[Ship::Info_Flags::Knossos_device])) {
|
|
vm_vec_scale(&live_debris_obj->phys_info.vel, ship_objp->radius/250.0f);
|
|
}
|
|
}
|
|
|
|
shipfx_debris_limit_speed(&Debris[live_debris_obj->instance], ship_p);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create debris for ship submodel which has live debris (at ship death)
|
|
* when ship submodel has not already been blown off (and hence liberated live debris)
|
|
*/
|
|
static void shipfx_maybe_create_live_debris_at_ship_death( object *ship_objp )
|
|
{
|
|
// if ship has live debris, detonate that subsystem now
|
|
// search for any submodels which have live debris
|
|
|
|
ship *shipp = &Ships[ship_objp->instance];
|
|
polymodel *pm = model_get(Ship_info[shipp->ship_info_index].model_num);
|
|
polymodel_instance *pmi = model_get_instance(shipp->model_instance_num);
|
|
|
|
// no subsystems -> no live debris.
|
|
if (Ship_info[shipp->ship_info_index].n_subsystems == 0) {
|
|
return;
|
|
}
|
|
|
|
for (auto pss: list_range(&shipp->subsys_list)) {
|
|
if (pss->system_info != nullptr) {
|
|
int submodel_num = pss->system_info->subobj_num;
|
|
|
|
// Subsystems without a valid submodel cannot produce live debris
|
|
if (submodel_num < 0 || submodel_num >= pm->n_models) {
|
|
continue;
|
|
}
|
|
|
|
// find the submodels which aren't already blown up and have live debris
|
|
if (!pmi->submodel[submodel_num].blown_off && pm->submodel[submodel_num].num_live_debris > 0) {
|
|
vec3d exp_center, tmp = ZERO_VECTOR;
|
|
model_instance_local_to_global_point(&exp_center, &tmp, pm, pmi, submodel_num, &ship_objp->orient, &ship_objp->pos);
|
|
|
|
// create its debris
|
|
shipfx_subsystem_maybe_create_live_debris(ship_objp, shipp, pss, &exp_center, 3.0f);
|
|
|
|
// now set subsystem as blown off, so we only get one copy
|
|
pmi->submodel[submodel_num].blown_off = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void shipfx_blow_off_subsystem(object *ship_objp, ship *ship_p, const ship_subsys *subsys, const vec3d *exp_center, bool no_explosion, bool no_fireballs)
|
|
{
|
|
vec3d subobj_pos;
|
|
|
|
model_subsystem *psub = subsys->system_info;
|
|
|
|
get_subsystem_world_pos(ship_objp, subsys, &subobj_pos);
|
|
|
|
// get rid of sparks on submodel that is destroyed
|
|
shipfx_remove_submodel_ship_sparks(ship_p, psub->subobj_num);
|
|
|
|
// create debris shards
|
|
if (!(subsys->flags[Ship::Subsystem_Flags::Vanished]) && !no_explosion) {
|
|
// default debris shard creation is flag dependent --wookieejedi
|
|
int num_shards;
|
|
if (Disable_all_noncustom_generic_debris ||
|
|
(Ship_info[ship_p->ship_info_index].flags[Ship::Info_Flags::Disable_all_generic_explosion_debris]) ||
|
|
(subsys->system_info->flags[Model::Subsystem_Flags::Disable_all_generic_explosion_debris])) {
|
|
num_shards = 0;
|
|
} else {
|
|
num_shards = 50;
|
|
}
|
|
shipfx_blow_up_model(ship_objp, psub->subobj_num, num_shards, &subobj_pos, subsys);
|
|
|
|
// create live debris objects, if any
|
|
// TODO: some MULTIPLAYER implications here!!
|
|
shipfx_subsystem_maybe_create_live_debris(ship_objp, ship_p, subsys, exp_center, 1.0f, no_fireballs);
|
|
|
|
if (!no_fireballs) {
|
|
int fireball_type = fireball_ship_explosion_type(&Ship_info[ship_p->ship_info_index]);
|
|
if(fireball_type < 0) {
|
|
fireball_type = FIREBALL_EXPLOSION_MEDIUM;
|
|
}
|
|
// create first fireball
|
|
fireball_create( &subobj_pos, fireball_type, FIREBALL_MEDIUM_EXPLOSION, OBJ_INDEX(ship_objp), psub->radius );
|
|
}
|
|
}
|
|
}
|
|
|
|
static void shipfx_blow_up_hull(object *obj, const polymodel *pm, const polymodel_instance *pmi, const vec3d *exp_center)
|
|
{
|
|
int i;
|
|
ushort sig_save;
|
|
|
|
if (!pm) return;
|
|
|
|
// in multiplayer, send a debris_hull_create packet. Save/restore the debris signature
|
|
// when in misison only (since we can create debris pieces before mission starts)
|
|
sig_save = 0;
|
|
|
|
if ( (Game_mode & GM_MULTIPLAYER) && (Game_mode & GM_IN_MISSION) ) {
|
|
sig_save = multi_get_next_network_signature( MULTI_SIG_DEBRIS );
|
|
multi_set_network_signature( Ships[obj->instance].debris_net_sig, MULTI_SIG_DEBRIS );
|
|
}
|
|
|
|
bool try_live_debris = true;
|
|
for (i=0; i<pm->num_debris_objects; i++ ) {
|
|
if (! pm->submodel[pm->debris_objects[i]].flags[Model::Submodel_flags::Is_live_debris]) {
|
|
vec3d tmp = ZERO_VECTOR;
|
|
model_instance_local_to_global_point(&tmp, &pm->submodel[pm->debris_objects[i]].offset, pm, pmi, 0, &obj->orient, &obj->pos );
|
|
debris_create( obj, pm->id, pm->debris_objects[i], &tmp, exp_center, 1, 3.0f );
|
|
} else {
|
|
if ( try_live_debris ) {
|
|
// only create live debris once
|
|
// this creates *all* the live debris for *all* the currently live subsystems.
|
|
try_live_debris = false;
|
|
shipfx_maybe_create_live_debris_at_ship_death(obj);
|
|
}
|
|
}
|
|
// in multiplayer we need to increment the network signature for each piece of debris we create
|
|
if ( (Game_mode & GM_MULTIPLAYER) && (Game_mode & GM_IN_MISSION) ) {
|
|
multi_assign_network_signature(MULTI_SIG_DEBRIS);
|
|
}
|
|
}
|
|
|
|
// restore the debris signature to it's original value.
|
|
if ( (Game_mode & GM_MULTIPLAYER) && (Game_mode & GM_IN_MISSION) ) {
|
|
multi_set_network_signature( sig_save, MULTI_SIG_DEBRIS );
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Creates "ndebris" pieces of debris on random verts of the the "submodel" in the ship's model.
|
|
*/
|
|
void shipfx_blow_up_model(object *obj, int submodel, int ndebris, const vec3d *exp_center, const ship_subsys *subsys)
|
|
{
|
|
int i;
|
|
|
|
auto pmi = model_get_instance(Ships[obj->instance].model_instance_num);
|
|
auto pm = model_get(pmi->model_num);
|
|
|
|
// if in a multiplayer game -- seed the random number generator with a value that will be the same
|
|
// on all clients in the game -- the net_signature of the object works nicely -- since doing so should
|
|
// ensure that all pieces of debris will get scattered in same direction on all machines
|
|
if ( Game_mode & GM_MULTIPLAYER )
|
|
Random::seed( obj->net_signature );
|
|
|
|
// made a change to allow anyone but multiplayer client to blow up hull. Clients will do it when
|
|
// they get the create packet
|
|
bool use_ship_debris = false;
|
|
if ( submodel == 0 ) {
|
|
shipfx_blow_up_hull(obj, pm, pmi, exp_center);
|
|
use_ship_debris = true;
|
|
}
|
|
|
|
for (i=0; i<ndebris; i++ ) {
|
|
// Gets two random points on the surface of a submodel
|
|
vec3d pnt1 = submodel_get_random_point(pm->id, submodel);
|
|
vec3d pnt2 = submodel_get_random_point(pm->id, submodel);
|
|
|
|
vec3d tmp, outpnt;
|
|
|
|
vm_vec_avg( &tmp, &pnt1, &pnt2 );
|
|
model_instance_local_to_global_point(&outpnt, &tmp, pm, pmi, submodel, &obj->orient, &obj->pos );
|
|
|
|
debris_create( obj, use_ship_debris ? Ship_info[Ships[obj->instance].ship_info_index].generic_debris_model_num : -1, -1, &outpnt, exp_center, false, 1.0f, subsys );
|
|
}
|
|
}
|
|
|
|
|
|
// =================================================
|
|
// SHIP WARP IN EFFECT CODE
|
|
// =================================================
|
|
|
|
|
|
/**
|
|
* Given an ship, find the radius of it as viewed from the front.
|
|
*/
|
|
static float shipfx_calculate_effect_radius( object *objp, WarpDirection warp_dir )
|
|
{
|
|
float rad;
|
|
ship* shipp = &Ships[objp->instance];
|
|
|
|
// if docked, we need to calculate the overall cross-sectional radius around the z-axis (longitudinal axis)
|
|
if (object_is_docked(objp) && !( (warp_dir == WarpDirection::WARP_IN && shipp->flags[Ship::Ship_Flags::Same_arrival_warp_when_docked])
|
|
|| (warp_dir == WarpDirection::WARP_OUT && shipp->flags[Ship::Ship_Flags::Same_departure_warp_when_docked]) ))
|
|
{
|
|
rad = dock_calc_max_cross_sectional_radius_perpendicular_to_axis(objp, Z_AXIS);
|
|
}
|
|
// if it's not docked, we can save a lot of work by just using width and height
|
|
else
|
|
{
|
|
//WMC - see if a radius was specified
|
|
float warp_radius = Warp_params[warp_dir == WarpDirection::WARP_IN ? shipp->warpin_params_index : shipp->warpout_params_index].radius;
|
|
if (warp_radius > 0.0f)
|
|
return warp_radius;
|
|
|
|
float w, h;
|
|
polymodel *pm = model_get(Ship_info[Ships[objp->instance].ship_info_index].model_num);
|
|
|
|
w = pm->maxs.xyz.x - pm->mins.xyz.x;
|
|
h = pm->maxs.xyz.y - pm->mins.xyz.y;
|
|
|
|
if ( w > h )
|
|
rad = w / 2.0f;
|
|
else
|
|
rad = h / 2.0f;
|
|
}
|
|
|
|
return rad*3.0f;
|
|
}
|
|
|
|
// How long the stage 1 & stage 2 of warp in effect lasts.
|
|
// There are different times for small, medium, and large ships.
|
|
// The appropriate values are picked depending on the ship's
|
|
// radius.
|
|
#define SHIPFX_WARP_DELAY (2.0f) // time for warp effect to ramp up before ship moves into it.
|
|
|
|
// Give object objp, calculate how long it should take the
|
|
// ship to go through the warp effect and how fast the ship
|
|
// should go. For reference, capital ship of 2780m
|
|
// should take 7 seconds to fly through. Fighters of 30,
|
|
// should take 1.5 seconds to fly through.
|
|
|
|
#define LARGEST_RAD 1390.0f
|
|
#define LARGEST_RAD_TIME 7.0f
|
|
|
|
#define SMALLEST_RAD 15.0f
|
|
#define SMALLEST_RAD_TIME 1.5f
|
|
|
|
float shipfx_calculate_warp_time(object *objp, WarpDirection warp_dir, float half_length, float warping_dist)
|
|
{
|
|
WarpParams *params = &Warp_params[warp_dir == WarpDirection::WARP_IN ? Ships[objp->instance].warpin_params_index : Ships[objp->instance].warpout_params_index];
|
|
|
|
// warpin or warpout time defined
|
|
if (params->time > 0.0f) {
|
|
return (float)params->time / 1000.0f;
|
|
}
|
|
// warpin or warpout speed defined
|
|
else if (params->speed > 0.0f) {
|
|
return warping_dist / params->speed;
|
|
}
|
|
// Player warpout
|
|
else if ((warp_dir == WarpDirection::WARP_OUT) && (objp == Player_obj)) {
|
|
if (params->warpout_player_speed > 0.0f) {
|
|
return warping_dist / params->warpout_player_speed;
|
|
} else {
|
|
return warping_dist / Player_warpout_speed;
|
|
}
|
|
}
|
|
|
|
// Find rad_percent from 0 to 1, 0 being smallest ship, 1 being largest
|
|
float rad_percent = (half_length-SMALLEST_RAD) / (LARGEST_RAD-SMALLEST_RAD);
|
|
CLAMP(rad_percent, 0.0f, 1.0f);
|
|
|
|
float rad_time = rad_percent*(LARGEST_RAD_TIME-SMALLEST_RAD_TIME) + SMALLEST_RAD_TIME;
|
|
|
|
return rad_time;
|
|
}
|
|
|
|
// This is called to actually warp this object in
|
|
// after all the flashy fx are done, or if the flashy
|
|
// fx don't work for some reason.
|
|
void shipfx_actually_warpin(ship *shipp, object *objp)
|
|
{
|
|
shipp->flags.remove(Ship::Ship_Flags::Arriving_stage_1);
|
|
shipp->flags.remove(Ship::Ship_Flags::Arriving_stage_2);
|
|
|
|
// dock leader needs to handle dockees
|
|
if (object_is_docked(objp)) {
|
|
Assertion(shipp->flags[Ship::Ship_Flags::Dock_leader], "The docked ship warping in (%s) should be the dock leader at this point!\n", shipp->ship_name);
|
|
dock_function_info dfi;
|
|
dock_evaluate_all_docked_objects(objp, &dfi, object_remove_arriving_stage1_ndl_flag_helper);
|
|
dock_evaluate_all_docked_objects(objp, &dfi, object_remove_arriving_stage2_ndl_flag_helper);
|
|
shipp->flags.remove(Ship::Ship_Flags::Dock_leader); // the dock leader flag is only used for arrival and could interfere with future scripted warpIn() calls
|
|
}
|
|
|
|
// let physics in on it too.
|
|
objp->phys_info.flags &= (~PF_WARP_IN);
|
|
}
|
|
|
|
// Validate reference_objnum
|
|
static int shipfx_special_warp_objnum_valid(int objnum)
|
|
{
|
|
object *special_objp;
|
|
|
|
// must be a valid object
|
|
if ((objnum < 0) || (objnum >= MAX_OBJECTS))
|
|
return 0;
|
|
|
|
special_objp = &Objects[objnum];
|
|
|
|
// must be a ship
|
|
if (special_objp->type != OBJ_SHIP)
|
|
return 0;
|
|
|
|
// must be a knossos
|
|
if (!(Ship_info[Ships[special_objp->instance].ship_info_index].flags[Ship::Info_Flags::Knossos_device]))
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
// JAS - code to start the ship doing the warp in effect
|
|
// This also starts the animating 3d effect playing.
|
|
// There are two modes, stage 1 and stage 2. Stage 1 is
|
|
// when the ship just invisibly waits for a certain time
|
|
// period after the effect starts, and then stage 2 begins,
|
|
// where the ships flies through the effect at a set
|
|
// velocity so it gets through in a certain amount of
|
|
// time.
|
|
void shipfx_warpin_start( object *objp )
|
|
{
|
|
ship *shipp = &Ships[objp->instance];
|
|
|
|
if (shipp->is_arriving())
|
|
{
|
|
mprintf(( "Ship '%s' is already arriving!\n", shipp->ship_name ));
|
|
return;
|
|
}
|
|
|
|
// docked ships who are not dock leaders don't use the warp effect code
|
|
// (the dock leader takes care of the whole group)
|
|
if (object_is_docked(objp) && !(shipp->flags[Ship::Ship_Flags::Dock_leader]))
|
|
{
|
|
return;
|
|
}
|
|
|
|
//WMC - Check if scripting handles this.
|
|
if (OnWarpInHook->isActive())
|
|
{
|
|
auto params = scripting::hook_param_list(scripting::hook_param("Self", 'o', objp));
|
|
auto conditions = scripting::hooks::ShipSourceConditions{ shipp };
|
|
if (OnWarpInHook->isOverride(conditions, params)) {
|
|
OnWarpInHook->run(conditions, std::move(params));
|
|
return;
|
|
}
|
|
}
|
|
|
|
// if there is no arrival warp, set the flag but don't create the effect
|
|
if (shipp->flags[Ship::Ship_Flags::No_arrival_warp])
|
|
{
|
|
shipp->flags.set(Ship::Ship_Flags::Arriving_stage_1);
|
|
// we don't actually need to set the flag on docked ships, since the flag will be immediately removed
|
|
return;
|
|
}
|
|
|
|
Assertion(shipp->warpin_effect != nullptr, "shipfx_warpin_start() was fed a ship with an uninitialized warpin_effect.");
|
|
shipp->warpin_effect->warpStart();
|
|
|
|
if (OnWarpInHook->isActive())
|
|
{
|
|
auto params = scripting::hook_param_list(scripting::hook_param("Self", 'o', objp));
|
|
auto conditions = scripting::hooks::ShipSourceConditions{ shipp };
|
|
OnWarpInHook->run(conditions, std::move(params));
|
|
}
|
|
}
|
|
|
|
void shipfx_warpin_frame( object *objp, float frametime )
|
|
{
|
|
ship *shipp;
|
|
|
|
shipp = &Ships[objp->instance];
|
|
|
|
if ( shipp->flags[Ship::Ship_Flags::Dying] ) return;
|
|
|
|
// for ships with no warp effect, skip to the end
|
|
if (shipp->flags[Ship::Ship_Flags::No_arrival_warp])
|
|
shipfx_actually_warpin(shipp, objp);
|
|
else
|
|
shipp->warpin_effect->warpFrame(frametime);
|
|
}
|
|
|
|
// This is called to actually warp this object out
|
|
// after all the flashy fx are done, or if the flashy fx
|
|
// don't work for some reason. OR to skip the flashy fx.
|
|
static void shipfx_actually_warpout(ship *shipp, object *objp)
|
|
{
|
|
shipp->flags.remove(Ship::Ship_Flags::Depart_warp);
|
|
|
|
// let physics in on it too.
|
|
objp->phys_info.flags &= (~PF_WARP_OUT);
|
|
|
|
if (OnWarpOutCompleteHook->isActive()) {
|
|
auto params = scripting::hook_param_list(scripting::hook_param("Self", 'o', objp));
|
|
auto conditions = scripting::hooks::ShipSourceConditions{ shipp };
|
|
if (OnWarpOutCompleteHook->isOverride(conditions, params)) {
|
|
OnWarpOutCompleteHook->run(conditions, std::move(params));
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Once we get through effect, make the ship go away
|
|
ship_actually_depart(objp->instance);
|
|
|
|
if (OnWarpOutCompleteHook->isActive()) {
|
|
auto params = scripting::hook_param_list(scripting::hook_param("Self", 'o', objp));
|
|
auto conditions = scripting::hooks::ShipSourceConditions{shipp};
|
|
OnWarpOutCompleteHook->run(conditions, std::move(params));
|
|
}
|
|
}
|
|
|
|
void WE_Default::compute_warpout_stuff(float *ship_move_time, vec3d *warp_pos)
|
|
{
|
|
Assertion(isValid(), "Warp effect must be valid when compute_warpout_stuff() is called!");
|
|
|
|
float warp_dist(0.0f), dist_to_plane, ship_move_dist;
|
|
vec3d facing_normal, vec_to_knossos, center_pos;
|
|
|
|
auto objp = &Objects[m_objnum];
|
|
auto sip = &Ship_info[m_ship_info_index];
|
|
|
|
// find world position of the center of the ship assembly
|
|
vm_vec_unrotate(¢er_pos, &actual_local_center, &objp->orient);
|
|
vm_vec_add2(¢er_pos, &objp->pos);
|
|
|
|
// If we're warping through the knossos, do something different.
|
|
if (m_portal_objnum >= 0)
|
|
{
|
|
auto portal_objp = &Objects[m_portal_objnum];
|
|
|
|
// get facing normal from knossos
|
|
vm_vec_sub(&vec_to_knossos, &portal_objp->pos, ¢er_pos);
|
|
facing_normal = portal_objp->orient.vec.fvec;
|
|
if (vm_vec_dot(&vec_to_knossos, &portal_objp->orient.vec.fvec) > 0.0f) {
|
|
vm_vec_negate(&facing_normal);
|
|
}
|
|
|
|
// find position to play the warp ani..
|
|
dist_to_plane = fvi_ray_plane(warp_pos, &portal_objp->pos, &facing_normal, ¢er_pos, &objp->orient.vec.fvec, 0.0f);
|
|
|
|
// calculate distance to warpout point.
|
|
dist_to_plane -= half_length;
|
|
|
|
if (dist_to_plane < 0.0f) {
|
|
mprintf(("special warpout started too late\n"));
|
|
dist_to_plane = 0.0f;
|
|
}
|
|
|
|
// validate angle
|
|
float max_warpout_angle = 0.707f; // 45 degree half-angle cone for small ships
|
|
if (Ship_info[Ships[objp->instance].ship_info_index].is_big_or_huge()) {
|
|
max_warpout_angle = 0.866f; // 30 degree half-angle cone for BIG or HUGE
|
|
}
|
|
|
|
if (-vm_vec_dot(&objp->orient.vec.fvec, &facing_normal) < max_warpout_angle) { // within allowed angle
|
|
mprintf(("special warpout angle exceeded\n"));
|
|
}
|
|
|
|
ship_move_dist = dist_to_plane;
|
|
}
|
|
// normal warp
|
|
else
|
|
{
|
|
// If this is a huge ship, set the distance to the length of the ship
|
|
if (sip->is_huge_ship())
|
|
{
|
|
warp_dist = half_length * 2.0f;
|
|
}
|
|
else
|
|
{
|
|
// Now we know our speed. Figure out how far the warp effect will be from here.
|
|
warp_dist = (warping_speed * SHIPFX_WARP_DELAY) + half_length * 1.5f; // We want to get to 1.5R away from effect
|
|
}
|
|
|
|
ship_move_dist = warp_dist - half_length;
|
|
}
|
|
|
|
// Calculate time to get to warp effect, before we actually go through it.
|
|
*ship_move_time = ship_move_dist / warping_speed;
|
|
|
|
if (m_portal_objnum < 0)
|
|
{
|
|
// project the warp portal in front of us
|
|
vm_vec_scale_add(warp_pos, ¢er_pos, &objp->orient.vec.fvec, warp_dist);
|
|
}
|
|
}
|
|
|
|
// JAS - code to start the ship doing the warp out effect
|
|
// This puts the ship into a mode specified by SF_DEPARTING
|
|
// where it flies forward for a set time period at a set
|
|
// velocity, then disappears when that time is reached. This
|
|
// also starts the animating 3d effect playing.
|
|
void shipfx_warpout_start( object *objp )
|
|
{
|
|
ship* shipp;
|
|
shipp = &Ships[objp->instance];
|
|
|
|
if (shipp->flags[Ship::Ship_Flags::Depart_warp]) {
|
|
mprintf(("Ship is already departing!\n"));
|
|
return;
|
|
}
|
|
|
|
if (OnWarpOutHook->isActive()) {
|
|
auto params = scripting::hook_param_list(scripting::hook_param("Self", 'o', objp));
|
|
auto conditions = scripting::hooks::ShipSourceConditions{ shipp };
|
|
if (OnWarpOutHook->isOverride(conditions, params)) {
|
|
OnWarpOutHook->run(conditions, std::move(params));
|
|
return;
|
|
}
|
|
}
|
|
|
|
// if we're dying return
|
|
if (shipp->flags[Ship::Ship_Flags::Dying]) {
|
|
return;
|
|
}
|
|
|
|
// return if disabled
|
|
if (shipp->flags[Ship::Ship_Flags::Disabled]) {
|
|
return;
|
|
}
|
|
|
|
// if we're HUGE, keep alive - set guardian
|
|
if (Ship_info[shipp->ship_info_index].is_huge_ship()) {
|
|
shipp->ship_guardian_threshold = SHIP_GUARDIAN_THRESHOLD_DEFAULT;
|
|
}
|
|
|
|
// don't send ship depart packets for player ships
|
|
if ((MULTIPLAYER_MASTER) && !(objp->flags[Object::Object_Flags::Player_ship])) {
|
|
send_ship_depart_packet(objp);
|
|
}
|
|
|
|
// don't do departure wormhole if ship flag is set which indicates no effect
|
|
if (shipp->flags[Ship::Ship_Flags::No_departure_warp]) {
|
|
// DKA 5/25/99 If he's going to warpout, set it.
|
|
// Next line fixes assert in wing cleanup code when no warp effect.
|
|
// NOTE: It also causes shipfx_warpout_frame to be run in ship_process_post
|
|
// which will make the ship actually depart
|
|
shipp->flags.set(Ship::Ship_Flags::Depart_warp);
|
|
return;
|
|
}
|
|
|
|
Assertion(shipp->warpout_effect != nullptr,
|
|
"shipfx_warpout_start() was fed a ship with an uninitialized warpout_effect.");
|
|
shipp->warpout_effect->warpStart();
|
|
|
|
if (OnWarpOutHook->isActive()) {
|
|
auto params = scripting::hook_param_list(scripting::hook_param("Self", 'o', objp));
|
|
auto conditions = scripting::hooks::ShipSourceConditions{ shipp };
|
|
OnWarpOutHook->run(conditions, std::move(params));
|
|
}
|
|
}
|
|
|
|
void shipfx_warpout_frame( object *objp, float frametime )
|
|
{
|
|
ship *shipp;
|
|
shipp = &Ships[objp->instance];
|
|
|
|
if ( shipp->flags[Ship::Ship_Flags::Dying] ) return;
|
|
|
|
//disabled ships should stay on the battlefield if they were disabled during warpout
|
|
//phreak 5/22/03
|
|
if (shipp->flags[Ship::Ship_Flags::Disabled]){
|
|
shipp->flags.remove(Ship::Ship_Flags::Depart_dockbay);
|
|
shipp->flags.remove(Ship::Ship_Flags::Depart_warp);
|
|
return;
|
|
}
|
|
|
|
// for ships with no warp effect, skip to the end
|
|
if (shipp->flags[Ship::Ship_Flags::No_departure_warp])
|
|
shipfx_actually_warpout(shipp, objp);
|
|
else
|
|
shipp->warpout_effect->warpFrame(frametime);
|
|
}
|
|
|
|
|
|
//==================================================
|
|
// Stuff for keeping track of which ships are in
|
|
// whose shadows.
|
|
|
|
|
|
#define w(p) (*((int *) (p)))
|
|
/**
|
|
* Given world point see if it is in a shadow.
|
|
*/
|
|
bool shipfx_eye_in_shadow( vec3d *eye_pos, object * src_obj, int light_n )
|
|
{
|
|
object *objp;
|
|
ship_obj *so;
|
|
|
|
vec3d rp0, rp1;
|
|
vec3d light_dir;
|
|
|
|
rp0 = *eye_pos;
|
|
|
|
// get the light dir
|
|
if(!light_get_global_dir(&light_dir, light_n)){
|
|
return false;
|
|
}
|
|
|
|
// Find rp1
|
|
for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) {
|
|
objp = &Objects[so->objnum];
|
|
if (objp->flags[Object::Object_Flags::Should_be_dead])
|
|
continue;
|
|
|
|
if ( src_obj != objp ) {
|
|
mc_info mc;
|
|
|
|
vm_vec_scale_add( &rp1, &rp0, &light_dir, objp->radius*10.0f );
|
|
|
|
mc.model_instance_num = Ships[objp->instance].model_instance_num;
|
|
mc.model_num = Ship_info[Ships[objp->instance].ship_info_index].model_num;
|
|
mc.orient = &objp->orient;
|
|
mc.pos = &objp->pos;
|
|
mc.p0 = &rp0;
|
|
mc.p1 = &rp1;
|
|
mc.flags = MC_CHECK_MODEL | MC_RESPECT_DETAIL_BOX_SPHERE;
|
|
|
|
if (model_collide(&mc)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const auto& p : Props) {
|
|
if (p.has_value()) {
|
|
objp = &Objects[p->objnum];
|
|
if (objp->flags[Object::Object_Flags::Should_be_dead])
|
|
continue;
|
|
|
|
if (src_obj != objp) {
|
|
mc_info mc;
|
|
|
|
vm_vec_scale_add(&rp1, &rp0, &light_dir, objp->radius * 10.0f);
|
|
|
|
mc.model_instance_num = p->model_instance_num;
|
|
mc.model_num = Prop_info[p->prop_info_index].model_num;
|
|
mc.orient = &objp->orient;
|
|
mc.pos = &objp->pos;
|
|
mc.p0 = &rp0;
|
|
mc.p1 = &rp1;
|
|
mc.flags = MC_CHECK_MODEL;
|
|
|
|
if (model_collide(&mc)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check all the big hull debris pieces.
|
|
for (auto &db: Debris) {
|
|
if ( !(db.flags[Debris_Flags::Used]) || !db.is_hull ){
|
|
continue;
|
|
}
|
|
|
|
objp = &Objects[db.objnum];
|
|
|
|
vm_vec_scale_add( &rp1, &rp0, &light_dir, objp->radius*10.0f );
|
|
|
|
mc_info mc;
|
|
|
|
mc.model_instance_num = -1;
|
|
mc.model_num = db.model_num; // Fill in the model to check
|
|
mc.submodel_num = db.submodel_num;
|
|
mc.orient = &objp->orient; // The object's orient
|
|
mc.pos = &objp->pos; // The object's position
|
|
mc.p0 = &rp0; // Point 1 of ray to check
|
|
mc.p1 = &rp1; // Point 2 of ray to check
|
|
mc.flags = (MC_CHECK_MODEL | MC_SUBMODEL);
|
|
|
|
if (model_collide(&mc)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// check cockpit model
|
|
if( Viewer_obj != NULL && Viewer_mode != VM_TOPDOWN ) {
|
|
if ( Viewer_obj->type == OBJ_SHIP && Viewer_obj->instance >= 0 ) {
|
|
ship *shipp = &Ships[Viewer_obj->instance];
|
|
ship_info *sip = &Ship_info[shipp->ship_info_index];
|
|
|
|
if(sip->cockpit_model_num > 0) {
|
|
vm_vec_scale_add( &rp1, &rp0, &light_dir, Viewer_obj->radius*2.0f );
|
|
vec3d pos,eye_posi;
|
|
matrix eye_ori;
|
|
object_get_eye(&eye_posi, &eye_ori, Viewer_obj, false);
|
|
vm_vec_unrotate(&pos, &sip->cockpit_offset, &eye_ori);
|
|
vm_vec_add2(&pos, &eye_posi);
|
|
|
|
mc_info mc;
|
|
|
|
mc.model_instance_num = -1;
|
|
mc.model_num = sip->cockpit_model_num;
|
|
mc.submodel_num = -1;
|
|
mc.orient = &eye_ori;
|
|
mc.pos = &pos;
|
|
mc.p0 = &rp0;
|
|
mc.p1 = &rp1;
|
|
mc.flags = MC_CHECK_MODEL | MC_RESPECT_DETAIL_BOX_SPHERE;
|
|
|
|
int mc_result = model_collide(&mc);
|
|
mc.pos = NULL;
|
|
|
|
if( mc_result ) {
|
|
if ( mc.t_poly ) {
|
|
polymodel *pm = model_get(sip->cockpit_model_num);
|
|
int tmap_num = w(mc.t_poly+40);
|
|
|
|
Assertion (tmap_num < MAX_MODEL_TEXTURES, "Texture map index (%i) exceeded max", tmap_num);
|
|
if (tmap_num >= MAX_MODEL_TEXTURES) { return 0; }
|
|
if( !(pm->maps[tmap_num].is_transparent) && strcmp(bm_get_filename(mc.hit_bitmap), "glass.dds") != 0 ) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if ( mc.f_poly ) {
|
|
return true;
|
|
}
|
|
|
|
if ( mc.bsp_leaf ) {
|
|
if ( mc.bsp_leaf->tmap_num < 255 ) {
|
|
polymodel *pm = model_get(sip->cockpit_model_num);
|
|
int tmap_num = mc.bsp_leaf->tmap_num;
|
|
|
|
Assertion (tmap_num < MAX_MODEL_TEXTURES, "Texture map index (%i) exceeded max", tmap_num);
|
|
if (tmap_num >= MAX_MODEL_TEXTURES) { return 0; }
|
|
if ( !(pm->maps[tmap_num].is_transparent) && strcmp(bm_get_filename(mc.hit_bitmap), "glass.dds") != 0 ) {
|
|
return true;
|
|
}
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( sip->flags[Ship::Info_Flags::Show_ship_model]
|
|
&& (!Show_ship_only_if_cockpits_enabled || Cockpit_active) ) {
|
|
vm_vec_scale_add( &rp1, &rp0, &light_dir, Viewer_obj->radius*10.0f );
|
|
|
|
mc_info mc;
|
|
|
|
mc.model_instance_num = -1;
|
|
mc.model_num = sip->model_num;
|
|
mc.submodel_num = -1;
|
|
mc.orient = &Viewer_obj->orient;
|
|
mc.pos = &Viewer_obj->pos;
|
|
mc.p0 = &rp0;
|
|
mc.p1 = &rp1;
|
|
mc.flags = MC_CHECK_MODEL | MC_RESPECT_DETAIL_BOX_SPHERE;
|
|
|
|
if( model_collide(&mc) ) {
|
|
if ( mc.t_poly ) {
|
|
polymodel *pm = model_get(sip->model_num);
|
|
int tmap_num = w(mc.t_poly+40);
|
|
|
|
Assertion (tmap_num < MAX_MODEL_TEXTURES, "Texture map index (%i) exceeded max", tmap_num);
|
|
if (tmap_num >= MAX_MODEL_TEXTURES) { return 0; }
|
|
if ( !(pm->maps[tmap_num].is_transparent) && strcmp(bm_get_filename(mc.hit_bitmap),"glass.dds") != 0 ) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if ( mc.f_poly ) {
|
|
return true;
|
|
}
|
|
|
|
if ( mc.bsp_leaf ) {
|
|
if ( mc.bsp_leaf->tmap_num < 255 ) {
|
|
polymodel *pm = model_get(sip->model_num);
|
|
int tmap_num = mc.bsp_leaf->tmap_num;
|
|
|
|
Assertion (tmap_num < MAX_MODEL_TEXTURES, "Texture map index (%i) exceeded max", tmap_num);
|
|
if (tmap_num >= MAX_MODEL_TEXTURES) { return 0; }
|
|
if ( !(pm->maps[tmap_num].is_transparent) && strcmp(bm_get_filename(mc.hit_bitmap), "glass.dds") != 0 ) {
|
|
return true;
|
|
}
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// check asteroids
|
|
asteroid *ast = Asteroids;
|
|
|
|
if (Asteroid_field.num_initial_asteroids <= 0 )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
for (int i = 0 ; i < MAX_ASTEROIDS; i++, ast++)
|
|
{
|
|
if (!(ast->flags & AF_USED))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
objp = &Objects[ast->objnum];
|
|
|
|
vm_vec_scale_add( &rp1, &rp0, &light_dir, objp->radius*10.0f );
|
|
|
|
mc_info mc;
|
|
|
|
mc.model_instance_num = -1;
|
|
mc.model_num = Asteroid_info[ast->asteroid_type].subtypes[ast->asteroid_subtype].model_number; // Fill in the model to check
|
|
mc.submodel_num = -1;
|
|
mc.orient = &objp->orient; // The object's orient
|
|
mc.pos = &objp->pos; // The object's position
|
|
mc.p0 = &rp0; // Point 1 of ray to check
|
|
mc.p1 = &rp1; // Point 2 of ray to check
|
|
mc.flags = MC_CHECK_MODEL;
|
|
|
|
if (model_collide(&mc)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// not in shadow
|
|
return false;
|
|
}
|
|
|
|
//=====================================================================================
|
|
// STUFF FOR DOING SHIP GUN FLASHES
|
|
//=====================================================================================
|
|
|
|
#define MAX_FLASHES 128 // How many flashes total
|
|
#define FLASH_LIFE_PRIMARY 0.25f // How long flash lives
|
|
#define FLASH_LIFE_SECONDARY 0.50f // How long flash lives
|
|
|
|
|
|
typedef struct ship_flash {
|
|
int objnum; // object number of parent ship
|
|
int obj_signature; // signature of that object
|
|
int light_num; // which light in the model this uses
|
|
float life; // how long this should be around
|
|
float max_life; // how long this has been around.
|
|
} ship_flash;
|
|
|
|
int Ship_flash_inited = 0;
|
|
int Ship_flash_highest = -1;
|
|
ship_flash Ship_flash[MAX_FLASHES];
|
|
|
|
/**
|
|
* Resets the ship flash stuff. Call before each level.
|
|
*/
|
|
void shipfx_flash_init()
|
|
{
|
|
int i;
|
|
|
|
for (i=0; i<MAX_FLASHES; i++ ) {
|
|
Ship_flash[i].objnum = -1; // mark as unused
|
|
}
|
|
Ship_flash_highest = -1;
|
|
Ship_flash_inited = 1;
|
|
}
|
|
|
|
|
|
/**
|
|
* Given that a ship fired a weapon, light up the model accordingly.
|
|
*/
|
|
void shipfx_flash_create(object *objp, int model_num, vec3d *gun_pos, vec3d *gun_dir, int is_primary, int weapon_info_index, int weapon_objnum)
|
|
{
|
|
int i;
|
|
int objnum = OBJ_INDEX(objp);
|
|
|
|
Assert(Ship_flash_inited);
|
|
|
|
polymodel *pm = model_get(model_num);
|
|
int closest_light = -1;
|
|
float d, closest_dist = 0.0f;
|
|
|
|
// ALWAYS do this - since this is called once per firing
|
|
// if this is a cannon type weapon, create a muzzle flash
|
|
// HACK - let the flak guns do this on their own since they fire so quickly
|
|
// Also don't create if its the player in the cockpit unless he's also got show_ship_model, provided render_player_mflash isnt on
|
|
bool in_cockpit_view = (Viewer_mode & (VM_EXTERNAL | VM_CHASE | VM_OTHER_SHIP | VM_WARP_CHASE)) == 0;
|
|
bool player_show_ship_model = (
|
|
objp == Player_obj
|
|
&& Ship_info[Ships[objp->instance].ship_info_index].flags[Ship::Info_Flags::Show_ship_model]
|
|
&& (!Show_ship_only_if_cockpits_enabled || Cockpit_active));
|
|
|
|
auto* wip = &Weapon_info[weapon_info_index];
|
|
|
|
if (!(wip->wi_flags[Weapon::Info_Flags::Flak]) &&
|
|
(objp != Player_obj || Render_player_mflash || (!in_cockpit_view || player_show_ship_model))) {
|
|
// if there's a muzzle effect entry, we use that
|
|
if (Weapon_info[weapon_info_index].muzzle_effect.isValid()) {
|
|
matrix gunOrient;
|
|
vm_vector_2_matrix_norm(&gunOrient, gun_dir);
|
|
|
|
// spawn particle effect
|
|
auto particleSource = particle::ParticleManager::get()->createSource(Weapon_info[weapon_info_index].muzzle_effect);
|
|
//This should probably end up attached to the subobject, not the object, but it's not that much of a problem since primaries / secondaries rarely move.
|
|
particleSource->setHost(std::make_unique<EffectHostObject>(objp, *gun_pos, gunOrient, true));
|
|
|
|
auto *weapon_objp = &Objects[weapon_objnum];
|
|
auto *wp = &Weapons[weapon_objp->instance];
|
|
|
|
float radius_mult = 1.f;
|
|
|
|
if (wip->render_type == WRT_LASER) {
|
|
radius_mult = wip->weapon_curves.get_output(weapon_info::WeaponCurveOutputs::LASER_RADIUS_MULT, *wp, &wp->modular_curves_instance);
|
|
}
|
|
|
|
particleSource->setTriggerRadius(weapon_objp->radius * radius_mult);
|
|
particleSource->setTriggerVelocity(vm_vec_mag_quick(&weapon_objp->phys_info.vel));
|
|
|
|
particleSource->finishCreation();
|
|
}
|
|
}
|
|
|
|
if ( pm->num_lights < 1 ) return;
|
|
|
|
for (i=0; i<pm->num_lights; i++ ) {
|
|
d = vm_vec_dist( &pm->lights[i].pos, gun_pos );
|
|
|
|
if ( pm->lights[i].type == BSP_LIGHT_TYPE_WEAPON ) {
|
|
if ( (closest_light==-1) || (d<closest_dist) ) {
|
|
closest_light = i;
|
|
closest_dist = d;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( closest_light == -1 ) return;
|
|
|
|
int first_slot = -1;
|
|
|
|
for (i=0; i<=Ship_flash_highest; i++ ) {
|
|
if ( (first_slot==-1) && (Ship_flash[i].objnum < 0) ) {
|
|
first_slot = i;
|
|
}
|
|
|
|
if ( (Ship_flash[i].objnum == objnum) && (Ship_flash[i].obj_signature==objp->signature) ) {
|
|
if ( Ship_flash[i].light_num == closest_light ) {
|
|
// This is already flashing!
|
|
Ship_flash[i].life = 0.0f;
|
|
if ( is_primary ) {
|
|
Ship_flash[i].max_life = FLASH_LIFE_PRIMARY;
|
|
} else {
|
|
Ship_flash[i].max_life = FLASH_LIFE_SECONDARY;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( first_slot == -1 ) {
|
|
if ( Ship_flash_highest < MAX_FLASHES-1 ) {
|
|
Ship_flash_highest++;
|
|
first_slot = Ship_flash_highest;
|
|
} else {
|
|
return; // out of flash slots
|
|
}
|
|
}
|
|
|
|
Assert( Ship_flash[first_slot].objnum == -1 );
|
|
|
|
Ship_flash[first_slot].objnum = objnum;
|
|
Ship_flash[first_slot].obj_signature = objp->signature;
|
|
Ship_flash[first_slot].life = 0.0f; // Start it up
|
|
if ( is_primary ) {
|
|
Ship_flash[first_slot].max_life = FLASH_LIFE_PRIMARY;
|
|
} else {
|
|
Ship_flash[first_slot].max_life = FLASH_LIFE_SECONDARY;
|
|
}
|
|
Ship_flash[first_slot].light_num = closest_light;
|
|
}
|
|
|
|
/**
|
|
* Does whatever processing needs to be done each frame.
|
|
*/
|
|
void shipfx_flash_do_frame(float frametime)
|
|
{
|
|
ship_flash *sf;
|
|
int kill_it = 0;
|
|
int i;
|
|
|
|
for (i=0, sf = &Ship_flash[0]; i<=Ship_flash_highest; i++, sf++ ) {
|
|
if ( sf->objnum > -1 ) {
|
|
if ( Objects[sf->objnum].signature != sf->obj_signature ) {
|
|
kill_it = 1;
|
|
}
|
|
sf->life += frametime;
|
|
if ( sf->life >= sf->max_life ) kill_it = 1;
|
|
|
|
if (kill_it) {
|
|
sf->objnum = -1;
|
|
if ( i == Ship_flash_highest ) {
|
|
while( (Ship_flash_highest>0) && (Ship_flash[Ship_flash_highest].objnum == -1) ) {
|
|
Ship_flash_highest--;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// Make sparks fly off of ship n.
|
|
// sn = spark number to spark, corrosponding to element in
|
|
// ship->hitpos array. If this isn't -1, it is a just
|
|
// got hit by weapon spark, otherwise pick one randomally.
|
|
void shipfx_emit_spark( int n, int sn )
|
|
{
|
|
int create_spark = 1;
|
|
object * obj;
|
|
vec3d outpnt;
|
|
ship *shipp = &Ships[n];
|
|
|
|
ship_info *sip = &Ship_info[shipp->ship_info_index];
|
|
if(sn > -1 && !sip->impact_spew.isValid())
|
|
return;
|
|
|
|
if(sn < 0 && !sip->damage_spew.isValid())
|
|
return;
|
|
|
|
if ( shipp->num_sparks <= 0 )
|
|
return;
|
|
|
|
obj = &Objects[shipp->objnum];
|
|
|
|
float hull_percent = get_hull_pct(obj);
|
|
if (hull_percent < 0.001) {
|
|
hull_percent = 0.001f;
|
|
}
|
|
float fraction = 0.1f * obj->radius / hull_percent;
|
|
if (fraction > 1.0f) {
|
|
fraction = 1.0f;
|
|
}
|
|
|
|
int spark_num;
|
|
if ( sn == -1 ) {
|
|
spark_num = Random::next(shipp->num_sparks);
|
|
} else {
|
|
spark_num = sn;
|
|
}
|
|
|
|
// don't display sparks that have expired
|
|
if ( timestamp_elapsed(shipp->sparks[spark_num].end_time) ) {
|
|
return;
|
|
}
|
|
|
|
auto pmi = model_get_instance(shipp->model_instance_num);
|
|
auto pm = model_get(pmi->model_num);
|
|
|
|
// get spark position
|
|
if (shipp->sparks[spark_num].submodel_num != -1) {
|
|
model_instance_local_to_global_point(&outpnt, &shipp->sparks[spark_num].pos, pm, pmi, shipp->sparks[spark_num].submodel_num, &obj->orient, &obj->pos);
|
|
} else {
|
|
// rotate sparks correctly with current ship orient
|
|
vm_vec_unrotate(&outpnt, &shipp->sparks[spark_num].pos, &obj->orient);
|
|
vm_vec_add2(&outpnt,&obj->pos);
|
|
}
|
|
|
|
// phreak: Mantis 1676 - Re-enable warpout clipping.
|
|
WarpEffect* warp_effect = nullptr;
|
|
|
|
if ((shipp->is_arriving()) && (shipp->warpin_effect))
|
|
warp_effect = shipp->warpin_effect;
|
|
else if ((shipp->flags[Ship::Ship_Flags::Depart_warp]) && (shipp->warpout_effect))
|
|
warp_effect = shipp->warpout_effect;
|
|
|
|
if (warp_effect != nullptr && point_is_clipped_by_warp(&outpnt, warp_effect))
|
|
return;
|
|
|
|
if ( create_spark ) {
|
|
// TODO: add velocity from rotation if submodel is rotating
|
|
// v_rot = w x r
|
|
|
|
// r = outpnt - model_local_to_global_point(0)
|
|
|
|
// w = model_local_to_global_dir(
|
|
// model_local_to_global_dir(&out_dir, &in_dir, model_num, submodel_num, &objorient, &objpos);
|
|
|
|
vec3d tmp_norm, tmp_vel;
|
|
vm_vec_sub( &tmp_norm, &outpnt, &obj->pos );
|
|
vm_vec_normalize_safe(&tmp_norm);
|
|
|
|
tmp_vel = obj->phys_info.vel;
|
|
if ( vm_vec_normalize_safe(&tmp_vel) > 1.0f ) {
|
|
vm_vec_scale_add2(&tmp_norm,&tmp_vel, -2.0f);
|
|
vm_vec_normalize_safe(&tmp_norm);
|
|
}
|
|
|
|
vec3d local_norm;
|
|
model_instance_global_to_local_dir(&local_norm, &tmp_norm, pm, pmi, shipp->sparks[spark_num].submodel_num, &obj->orient);
|
|
|
|
matrix orient;
|
|
vm_vector_2_matrix_norm(&orient, &local_norm);
|
|
|
|
std::unique_ptr<EffectHost> particleHost;
|
|
if (shipp->sparks[spark_num].submodel_num != -1) {
|
|
particleHost = std::make_unique<EffectHostSubmodel>(obj, shipp->sparks[spark_num].submodel_num, shipp->sparks[spark_num].pos, orient);
|
|
}
|
|
else {
|
|
particleHost = std::make_unique<EffectHostObject>(obj, shipp->sparks[spark_num].pos, orient);
|
|
}
|
|
|
|
// first time through - set up end time and make heavier initially
|
|
if ( sn > -1 ) {
|
|
// Sparks for first time at this spot
|
|
if (sip->flags[Ship::Info_Flags::Fighter]) {
|
|
if (hull_percent > 0.6f) {
|
|
// sparks only once when hull > 60%
|
|
float spark_duration = (float)pow(2.0f, -5.0f*(hull_percent-1.3f)) * (1.0f + 0.6f*(frand()-0.5f)); // +- 30%
|
|
shipp->sparks[spark_num].end_time = timestamp( (int) (1000.0f * spark_duration) );
|
|
} else {
|
|
// spark never ends when hull < 60% (~277 hr)
|
|
shipp->sparks[spark_num].end_time = timestamp( 100000000 );
|
|
}
|
|
}
|
|
|
|
auto source = particle::ParticleManager::get()->createSource(sip->impact_spew);
|
|
source->setHost(std::move(particleHost));
|
|
source->finishCreation();
|
|
} else {
|
|
auto source = particle::ParticleManager::get()->createSource(sip->damage_spew);
|
|
source->setHost(std::move(particleHost));
|
|
source->finishCreation();
|
|
}
|
|
}
|
|
|
|
// Select time to do next spark
|
|
shipp->next_hit_spark = timestamp_rand(50,100);
|
|
}
|
|
|
|
|
|
|
|
//=====================================================================================
|
|
// STUFF FOR DOING LARGE SHIP EXPLOSIONS
|
|
//=====================================================================================
|
|
|
|
int Bs_exp_fire_low = 1;
|
|
float Bs_exp_fire_time_mult = 1.0f;
|
|
|
|
DCF_BOOL(bs_exp_fire_low, Bs_exp_fire_low)
|
|
DCF(bs_exp_fire_time_mult, "Sets multiplier time between fireball in big ship explosion")
|
|
{
|
|
float value;
|
|
|
|
if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) {
|
|
dc_printf("Bs_exp_fire_time_mult : %f\n", Bs_exp_fire_time_mult);
|
|
return;
|
|
}
|
|
|
|
dc_stuff_float(&value);
|
|
|
|
CLAMP(value, 0.1f, 5.0f);
|
|
Bs_exp_fire_time_mult = value;
|
|
dc_printf("Bs_exp_fire_time_mult set to %f\n", Bs_exp_fire_time_mult);
|
|
}
|
|
|
|
|
|
#define DEBRIS_NONE 0
|
|
#define DEBRIS_DRAW 1
|
|
#define DEBRIS_FREE 2
|
|
|
|
typedef struct clip_ship {
|
|
object* parent_obj;
|
|
float length_left; // uncomsumed length
|
|
matrix orient;
|
|
physics_info phys_info;
|
|
vec3d local_pivot; // world center of mass position of half ship
|
|
vec3d model_center_disp_to_orig_center; // displacement from half ship center to original model center
|
|
vec3d clip_plane_norm; // clip plane normal (local [0,0,1] or [0,0,-1])
|
|
float cur_clip_plane_pt; // displacement from half ship clip plane to original model center
|
|
float explosion_vel;
|
|
ubyte draw_debris[MAX_DEBRIS_OBJECTS];
|
|
int next_fireball;
|
|
} clip_ship;
|
|
|
|
typedef struct split_ship {
|
|
int used = 0; // 0 if not used, 1 if used
|
|
clip_ship front_ship;
|
|
clip_ship back_ship;
|
|
int explosion_flash_timestamp = 0;
|
|
int explosion_flash_started = 0;
|
|
std::array<sound_handle, NUM_SUB_EXPL_HANDLES> sound_handles;
|
|
|
|
split_ship()
|
|
{
|
|
memset(&front_ship, 0, sizeof(front_ship));
|
|
memset(&back_ship, 0, sizeof(back_ship));
|
|
sound_handles.fill(sound_handle::invalid());
|
|
}
|
|
} split_ship;
|
|
|
|
|
|
static SCP_vector<split_ship> Split_ships;
|
|
|
|
static int get_split_ship()
|
|
{
|
|
int i;
|
|
|
|
// check for an existing free slot
|
|
int max_size = (int)Split_ships.size();
|
|
for (i = 0; i < max_size; i++) {
|
|
if (!Split_ships[i].used)
|
|
return i;
|
|
}
|
|
|
|
// Construct default constructed object at the end
|
|
Split_ships.emplace_back();
|
|
|
|
return (int)(Split_ships.size() - 1);
|
|
}
|
|
|
|
static void maybe_fireball_wipe(clip_ship* half_ship, sound_handle* sound_handle);
|
|
static void split_ship_init( ship* shipp, split_ship* split_shipp )
|
|
{
|
|
object* parent_ship_obj = &Objects[shipp->objnum];
|
|
matrix* orient = &parent_ship_obj->orient;
|
|
for (int ii=0; ii<NUM_SUB_EXPL_HANDLES; ++ii) {
|
|
split_shipp->sound_handles[ii] = shipp->sub_expl_sound_handle[ii];
|
|
}
|
|
|
|
// play 3d sound for shockwave explosion
|
|
snd_play_3d( gamesnd_get_game_sound(GameSounds::SHOCKWAVE_EXPLODE), &parent_ship_obj->pos, &View_position, 0.0f, NULL, 0, 1.0f, SND_PRIORITY_SINGLE_INSTANCE, NULL, 3.0f );
|
|
|
|
// initialize both ships
|
|
split_shipp->front_ship.parent_obj = parent_ship_obj;
|
|
split_shipp->back_ship.parent_obj = parent_ship_obj;
|
|
split_shipp->explosion_flash_timestamp = timestamp((int)(0.00075f*parent_ship_obj->radius));
|
|
split_shipp->explosion_flash_started = 0;
|
|
split_shipp->front_ship.orient = *orient;
|
|
split_shipp->back_ship.orient = *orient;
|
|
split_shipp->front_ship.next_fireball = timestamp_rand(0, 100);
|
|
split_shipp->back_ship.next_fireball = timestamp_rand(0, 100);
|
|
|
|
split_shipp->front_ship.clip_plane_norm = vmd_z_vector;
|
|
vm_vec_copy_scale(&split_shipp->back_ship.clip_plane_norm, &vmd_z_vector, -1.0f);
|
|
|
|
// find the point at which the ship splits (relative to its pivot)
|
|
polymodel* pm = model_get(Ship_info[shipp->ship_info_index].model_num);
|
|
float init_clip_plane_dist;
|
|
if (pm->num_split_plane > 0) {
|
|
int index = Random::next(pm->num_split_plane);
|
|
init_clip_plane_dist = pm->split_plane[index];
|
|
} else {
|
|
init_clip_plane_dist = 0.5f * (0.5f - frand())*pm->core_radius;
|
|
}
|
|
|
|
split_shipp->back_ship.cur_clip_plane_pt = init_clip_plane_dist;
|
|
split_shipp->front_ship.cur_clip_plane_pt = init_clip_plane_dist;
|
|
|
|
float dist;
|
|
dist = (split_shipp->front_ship.cur_clip_plane_pt+pm->maxs.xyz.z)/2.0f;
|
|
vm_vec_copy_scale(&split_shipp->front_ship.local_pivot, &orient->vec.fvec, dist);
|
|
vm_vec_make(&split_shipp->front_ship.model_center_disp_to_orig_center, 0.0f, 0.0f, -dist);
|
|
dist = (split_shipp->back_ship.cur_clip_plane_pt +pm->mins.xyz.z)/2.0f;
|
|
vm_vec_copy_scale(&split_shipp->back_ship.local_pivot, &orient->vec.fvec, dist);
|
|
vm_vec_make(&split_shipp->back_ship.model_center_disp_to_orig_center, 0.0f, 0.0f, -dist);
|
|
vm_vec_add2(&split_shipp->front_ship.local_pivot, &parent_ship_obj->pos );
|
|
vm_vec_add2(&split_shipp->back_ship.local_pivot, &parent_ship_obj->pos );
|
|
|
|
// find which debris pieces are in the front and back split ships
|
|
for (int i=0; i<pm->num_debris_objects; i++ ) {
|
|
vec3d temp_pos = ZERO_VECTOR;
|
|
vec3d tmp = ZERO_VECTOR;
|
|
vec3d tmp1 = pm->submodel[pm->debris_objects[i]].offset;
|
|
// tmp is world position, temp_pos is world_pivot, tmp1 is offset from world_pivot (in ship local coord)
|
|
// we don't need to use a model instance here because we're not working with any submodels
|
|
model_local_to_global_point(&tmp, &tmp1, pm, -1, &vmd_identity_matrix, &temp_pos );
|
|
if (tmp.xyz.z > init_clip_plane_dist) {
|
|
split_shipp->front_ship.draw_debris[i] = DEBRIS_DRAW;
|
|
split_shipp->back_ship.draw_debris[i] = DEBRIS_NONE;
|
|
} else {
|
|
split_shipp->front_ship.draw_debris[i] = DEBRIS_NONE;
|
|
split_shipp->back_ship.draw_debris[i] = DEBRIS_DRAW;
|
|
}
|
|
}
|
|
|
|
// set up physics
|
|
physics_init( &split_shipp->front_ship.phys_info );
|
|
physics_init( &split_shipp->back_ship.phys_info );
|
|
split_shipp->front_ship.phys_info.flags |= PF_BALLISTIC;
|
|
split_shipp->back_ship.phys_info.flags |= PF_BALLISTIC;
|
|
split_shipp->front_ship.phys_info.gravity_const = parent_ship_obj->phys_info.gravity_const;
|
|
split_shipp->back_ship.phys_info.gravity_const = parent_ship_obj->phys_info.gravity_const;
|
|
split_shipp->front_ship.phys_info.rotdamp = 10000.0f;
|
|
split_shipp->back_ship.phys_info.rotdamp = 10000.0f;
|
|
|
|
// set up explosion vel and relative velocities (assuming mass depends on length)
|
|
float front_length = pm->maxs.xyz.z - split_shipp->front_ship.cur_clip_plane_pt;
|
|
float back_length = split_shipp->back_ship.cur_clip_plane_pt - pm->mins.xyz.z;
|
|
float ship_length = front_length + back_length;
|
|
split_shipp->front_ship.length_left = front_length;
|
|
split_shipp->back_ship.length_left = back_length;
|
|
|
|
float expl_length_scale = (ship_length - 200.0f) / 2000.0f;
|
|
// s_r_f effects speed of "wipe" and rotvel
|
|
float speed_reduction_factor = (1.0f + 0.001f*parent_ship_obj->radius);
|
|
float explosion_time = (3.0f + expl_length_scale + (frand()-0.5f)) * speed_reduction_factor;
|
|
float long_length = MAX(front_length, back_length);
|
|
float expl_vel = long_length / explosion_time;
|
|
split_shipp->front_ship.explosion_vel = expl_vel;
|
|
split_shipp->back_ship.explosion_vel = -expl_vel;
|
|
|
|
float rel_vel = (0.6f + 0.2f*frand()) * expl_vel * speed_reduction_factor;
|
|
float front_vel = rel_vel * back_length / ship_length;
|
|
float back_vel = -rel_vel * front_length / ship_length;
|
|
|
|
// set up rotational vel
|
|
vec3d rotvel;
|
|
vm_vec_rand_vec_quick(&rotvel);
|
|
rotvel.xyz.z = 0.0f;
|
|
vm_vec_normalize(&rotvel);
|
|
vm_vec_scale(&rotvel, 0.15f / speed_reduction_factor);
|
|
split_shipp->front_ship.phys_info.rotvel = rotvel;
|
|
vm_vec_copy_scale(&split_shipp->back_ship.phys_info.rotvel, &rotvel, -(front_length*front_length)/(back_length*back_length));
|
|
split_shipp->front_ship.phys_info.rotvel.xyz.z = parent_ship_obj->phys_info.rotvel.xyz.z;
|
|
split_shipp->back_ship.phys_info.rotvel.xyz.z = parent_ship_obj->phys_info.rotvel.xyz.z;
|
|
|
|
|
|
// modify vel of each split ship based on rotvel of parent ship obj
|
|
vec3d temp_rotvel = parent_ship_obj->phys_info.rotvel;
|
|
temp_rotvel.xyz.z = 0.0f;
|
|
vec3d vel_from_rotvel;
|
|
vm_vec_cross(&vel_from_rotvel, &temp_rotvel, &split_shipp->front_ship.local_pivot);
|
|
vm_vec_cross(&vel_from_rotvel, &temp_rotvel, &split_shipp->back_ship.local_pivot);
|
|
|
|
// set up velocity and make initial fireballs and particles
|
|
split_shipp->front_ship.phys_info.vel = parent_ship_obj->phys_info.vel;
|
|
split_shipp->back_ship.phys_info.vel = parent_ship_obj->phys_info.vel;
|
|
maybe_fireball_wipe(&split_shipp->front_ship, split_shipp->sound_handles.data());
|
|
maybe_fireball_wipe(&split_shipp->back_ship, split_shipp->sound_handles.data());
|
|
vm_vec_scale_add2(&split_shipp->front_ship.phys_info.vel, &orient->vec.fvec, front_vel);
|
|
vm_vec_scale_add2(&split_shipp->back_ship.phys_info.vel, &orient->vec.fvec, back_vel);
|
|
|
|
// HANDLE LIVE DEBRIS - blow off if not already gone
|
|
shipfx_maybe_create_live_debris_at_ship_death( parent_ship_obj );
|
|
}
|
|
|
|
void shipfx_queue_render_ship_halves_and_debris(model_draw_list *scene, clip_ship* half_ship, ship *shipp)
|
|
{
|
|
polymodel_instance *pmi = model_get_instance(shipp->model_instance_num);
|
|
polymodel *pm = model_get(pmi->model_num);
|
|
|
|
// get rotated clip plane normal and world coord of original ship center
|
|
vec3d orig_ship_world_center, clip_plane_norm, model_clip_plane_pt, debris_clip_plane_pt;
|
|
vm_vec_unrotate(&clip_plane_norm, &half_ship->clip_plane_norm, &half_ship->orient);
|
|
vm_vec_unrotate(&orig_ship_world_center, &half_ship->model_center_disp_to_orig_center, &half_ship->orient);
|
|
vm_vec_add2(&orig_ship_world_center, &half_ship->local_pivot);
|
|
|
|
// get debris clip plane pt and draw debris
|
|
vm_vec_unrotate(&debris_clip_plane_pt, &half_ship->model_center_disp_to_orig_center, &half_ship->orient);
|
|
vm_vec_add2(&debris_clip_plane_pt, &half_ship->local_pivot);
|
|
|
|
// set up render flags
|
|
uint64_t render_flags = MR_NORMAL;
|
|
|
|
if (shipp->flags[Ship::Ship_Flags::Glowmaps_disabled]) {
|
|
render_flags |= MR_NO_GLOWMAPS;
|
|
}
|
|
|
|
for (int i = 0; i < pm->num_debris_objects; i++ ) {
|
|
// draw DEBRIS_FREE in test only
|
|
if (half_ship->draw_debris[i] == DEBRIS_DRAW) {
|
|
vec3d temp_pos = orig_ship_world_center;
|
|
vec3d tmp = ZERO_VECTOR;
|
|
vec3d tmp1 = pm->submodel[pm->debris_objects[i]].offset;
|
|
|
|
// determine if explosion front has past debris piece
|
|
// 67 ~ dist expl moves in 2 frames -- maybe fraction works better
|
|
bool is_live_debris = pm->submodel[pm->debris_objects[i]].flags[Model::Submodel_flags::Is_live_debris];
|
|
int create_debris = 0;
|
|
// front ship
|
|
if (half_ship->explosion_vel > 0) {
|
|
if (half_ship->cur_clip_plane_pt > tmp1.xyz.z + pm->submodel[pm->debris_objects[i]].max.xyz.z - 0.1f*half_ship->explosion_vel) {
|
|
create_debris = 1;
|
|
}
|
|
// back ship
|
|
} else {
|
|
if (half_ship->cur_clip_plane_pt < tmp1.xyz.z + pm->submodel[pm->debris_objects[i]].min.xyz.z - 0.1f*half_ship->explosion_vel) {
|
|
create_debris = 1;
|
|
}
|
|
}
|
|
|
|
// Draw debris, but not live debris
|
|
if ( !is_live_debris ) {
|
|
// we don't need to use a model instance here because we're not working with any submodels
|
|
model_local_to_global_point(&tmp, &tmp1, pm, -1, &half_ship->orient, &temp_pos);
|
|
model_render_params render_info;
|
|
|
|
render_info.set_clip_plane(debris_clip_plane_pt, clip_plane_norm);
|
|
render_info.set_replacement_textures(pmi->texture_replace);
|
|
render_info.set_flags(render_flags);
|
|
|
|
submodel_render_queue(&render_info, scene, pm, pmi, pm->debris_objects[i], &half_ship->orient, &tmp);
|
|
}
|
|
|
|
// make free piece of debris
|
|
if ( create_debris ) {
|
|
half_ship->draw_debris[i] = DEBRIS_FREE; // mark debris to not render with model
|
|
vec3d center_to_debris, debris_vel, radial_vel;
|
|
// do debris create here, but not for live debris
|
|
// debris vel (1) split ship vel (2) split ship rotvel (3) random
|
|
if ( !is_live_debris ) {
|
|
object* debris_obj;
|
|
debris_obj = debris_create(half_ship->parent_obj, pm->id, pm->debris_objects[i], &tmp, &half_ship->local_pivot, 1, 1.0f);
|
|
// AL: make sure debris_obj isn't NULL!
|
|
if ( debris_obj ) {
|
|
vm_vec_scale(&debris_obj->phys_info.rotvel, 4.0f);
|
|
debris_obj->orient = half_ship->orient;
|
|
|
|
vm_vec_sub(¢er_to_debris, &tmp, &half_ship->local_pivot);
|
|
vm_vec_cross(&debris_vel, ¢er_to_debris, &half_ship->phys_info.rotvel);
|
|
vm_vec_add2(&debris_vel, &half_ship->phys_info.vel);
|
|
vm_vec_copy_normalize(&radial_vel, ¢er_to_debris);
|
|
float radial_mag = 10.0f + 30.0f*frand();
|
|
vm_vec_scale_add2(&debris_vel, &radial_vel, radial_mag);
|
|
debris_obj->phys_info.vel = debris_vel;
|
|
shipfx_debris_limit_speed(&Debris[debris_obj->instance], shipp);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// get model clip plane pt and draw model
|
|
vec3d temp;
|
|
vm_vec_make(&temp, 0.0f, 0.0f, half_ship->cur_clip_plane_pt);
|
|
vm_vec_unrotate(&model_clip_plane_pt, &temp, &half_ship->orient);
|
|
vm_vec_add2(&model_clip_plane_pt, &orig_ship_world_center);
|
|
|
|
model_render_params render_info;
|
|
|
|
render_info.set_flags(render_flags);
|
|
render_info.set_clip_plane(model_clip_plane_pt, clip_plane_norm);
|
|
render_info.set_replacement_textures(pmi->texture_replace);
|
|
render_info.set_object_number(shipp->objnum);
|
|
|
|
if (Ship_info[shipp->ship_info_index].uses_team_colors && !shipp->flags[Ship::Ship_Flags::Render_without_miscmap]) {
|
|
team_color model_team_color;
|
|
|
|
bool team_color_set = model_get_team_color(&model_team_color, shipp->team_name, shipp->secondary_team_name, shipp->team_change_timestamp, shipp->team_change_time);
|
|
|
|
if (team_color_set) {
|
|
render_info.set_team_color(model_team_color);
|
|
}
|
|
}
|
|
|
|
model_render_queue(&render_info, scene, pm->id, &half_ship->orient, &orig_ship_world_center);
|
|
}
|
|
|
|
void shipfx_large_blowup_level_init()
|
|
{
|
|
Split_ships.clear();
|
|
|
|
if(Ship_cannon_bitmap != -1){
|
|
bm_release(Ship_cannon_bitmap);
|
|
Ship_cannon_bitmap = bm_load(SHIP_CANNON_BITMAP);
|
|
}
|
|
}
|
|
|
|
void shipfx_large_blowup_init(ship *shipp)
|
|
{
|
|
int i;
|
|
|
|
i = get_split_ship();
|
|
|
|
Split_ships[i].used = 1;
|
|
shipp->large_ship_blowup_index = i;
|
|
|
|
split_ship_init(shipp, &Split_ships[i] );
|
|
}
|
|
|
|
void shipfx_debris_limit_speed(const debris *db, const ship *shipp)
|
|
{
|
|
if(db == NULL || shipp == NULL)
|
|
return;
|
|
|
|
object *ship_objp = &Objects[shipp->objnum];
|
|
physics_info *pi = &Objects[db->objnum].phys_info;
|
|
ship_info *sip = &Ship_info[shipp->ship_info_index];
|
|
|
|
float curspeed = vm_vec_mag(&pi->vel);
|
|
if(sip->debris_min_speed >= 0.0f && sip->debris_max_speed >= 0.0f)
|
|
{
|
|
float debris_speed = (( sip->debris_max_speed - sip->debris_min_speed ) * frand()) + sip->debris_min_speed;
|
|
if(fabs(curspeed) >= 0.001f)
|
|
{
|
|
float scale = debris_speed / curspeed;
|
|
vm_vec_scale(&pi->vel, scale);
|
|
}
|
|
else
|
|
{
|
|
vm_vec_copy_scale(&pi->vel, &ship_objp->orient.vec.fvec, debris_speed);
|
|
}
|
|
}
|
|
else if(sip->debris_min_speed >= 0.0f)
|
|
{
|
|
if(curspeed < sip->debris_min_speed)
|
|
{
|
|
if(fabs(curspeed) >= 0.001f)
|
|
{
|
|
float scale = sip->debris_min_speed / curspeed;
|
|
vm_vec_scale(&pi->vel, scale);
|
|
}
|
|
else
|
|
{
|
|
vm_vec_copy_scale(&pi->vel, &ship_objp->orient.vec.fvec, sip->debris_min_speed);
|
|
}
|
|
}
|
|
}
|
|
else if(sip->debris_max_speed >= 0.0f)
|
|
{
|
|
if(curspeed > sip->debris_max_speed)
|
|
{
|
|
if(fabs(curspeed) >= 0.001f)
|
|
{
|
|
float scale = sip->debris_max_speed / curspeed;
|
|
vm_vec_scale(&pi->vel, scale);
|
|
}
|
|
else
|
|
{
|
|
vm_vec_copy_scale(&pi->vel, &ship_objp->orient.vec.fvec, sip->debris_max_speed);
|
|
}
|
|
}
|
|
}
|
|
|
|
//WMC - Rotational velocity user cap
|
|
float currotvel = vm_vec_mag(&pi->rotvel);
|
|
if(sip->debris_min_rotspeed >= 0.0f && sip->debris_max_rotspeed >= 0.0f)
|
|
{
|
|
float debris_rotspeed = (( sip->debris_max_rotspeed - sip->debris_min_rotspeed ) * frand()) + sip->debris_min_rotspeed;
|
|
if(fabs(currotvel) >= 0.001f)
|
|
{
|
|
float scale = debris_rotspeed / currotvel;
|
|
vm_vec_scale(&pi->rotvel, scale);
|
|
}
|
|
else
|
|
{
|
|
vm_vec_copy_scale(&pi->rotvel, &ship_objp->orient.vec.uvec, debris_rotspeed);
|
|
}
|
|
}
|
|
else if(sip->debris_min_rotspeed >= 0.0f)
|
|
{
|
|
if(curspeed < sip->debris_min_rotspeed)
|
|
{
|
|
if(fabs(currotvel) >= 0.001f)
|
|
{
|
|
float scale = sip->debris_min_rotspeed / currotvel;
|
|
vm_vec_scale(&pi->rotvel, scale);
|
|
}
|
|
else
|
|
{
|
|
vm_vec_copy_scale(&pi->rotvel, &ship_objp->orient.vec.uvec, sip->debris_min_rotspeed);
|
|
}
|
|
}
|
|
}
|
|
else if(sip->debris_max_rotspeed >= 0.0f)
|
|
{
|
|
curspeed = vm_vec_mag(&pi->rotvel);
|
|
if(curspeed > sip->debris_max_rotspeed)
|
|
{
|
|
if(fabs(currotvel) >= 0.001f)
|
|
{
|
|
float scale = sip->debris_max_rotspeed / currotvel;
|
|
vm_vec_scale(&pi->rotvel, scale);
|
|
}
|
|
else
|
|
{
|
|
vm_vec_copy_scale(&pi->rotvel, &ship_objp->orient.vec.uvec, sip->debris_max_rotspeed);
|
|
}
|
|
}
|
|
}
|
|
|
|
int ship_type = sip->class_type;
|
|
if(ship_type > -1)
|
|
{
|
|
if(vm_vec_mag(&pi->vel) > Ship_types[ship_type].debris_max_speed) {
|
|
float scale = Ship_types[ship_type].debris_max_speed / vm_vec_mag(&pi->vel);
|
|
vm_vec_scale(&pi->vel, scale);
|
|
}
|
|
}
|
|
|
|
Assert(is_valid_vec(&pi->vel));
|
|
Assert(is_valid_vec(&pi->rotvel));
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// uses list of model z values with constant increment to find the radius of the
|
|
// cross section at the current model z value
|
|
static float get_model_cross_section_at_z(float z, polymodel* pm)
|
|
{
|
|
if (pm->num_xc < 2) {
|
|
return 0.0f;
|
|
}
|
|
|
|
float index, increment;
|
|
increment = (pm->xc[pm->num_xc-1].z - pm->xc[0].z) / (float)(pm->num_xc - 1);
|
|
index = (z - pm->xc[0].z) / increment;
|
|
|
|
if (index < 0.5f) {
|
|
return pm->xc[0].radius;
|
|
} else if (index > (pm->num_xc - 1.0f - 0.5f)) {
|
|
return pm->xc[pm->num_xc-1].radius;
|
|
} else {
|
|
int floor_index = (int)floor(index);
|
|
int ceil_index = (int)ceil(index);
|
|
return MAX(pm->xc[ceil_index].radius, pm->xc[floor_index].radius);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns how long sound has been playing
|
|
*/
|
|
static int get_sound_time_played(sound_load_id snd_id, sound_handle handle)
|
|
{
|
|
if (!handle.isValid() || !snd_id.isValid()) {
|
|
return 100000;
|
|
}
|
|
|
|
int time_left = snd_time_remaining(handle);
|
|
int duration = snd_get_duration(snd_id);
|
|
|
|
return (duration - time_left);
|
|
}
|
|
|
|
/**
|
|
* Sound manager for big ship sub explosions sounds.
|
|
*
|
|
* Forces playing of sub-explosion sounds. Keeps track of active sounds, plays them for >= 750 ms
|
|
* when sound has played >= 750, sound is stopped and new instance is started
|
|
*/
|
|
void do_sub_expl_sound(float radius, vec3d* sound_pos, sound_handle* handle_array)
|
|
{
|
|
// multiplier for range (near and far distances) to apply attenuation
|
|
float sound_range = 1.0f + 0.0043f*radius;
|
|
|
|
int handle_index = Random::next(NUM_SUB_EXPL_HANDLES);
|
|
|
|
auto sound_index = GameSounds::SHIP_EXPLODE_1;
|
|
auto handle = handle_array[handle_index];
|
|
|
|
if (!handle.isValid()) {
|
|
// if no handle, get one
|
|
handle_array[handle_index] = snd_play_3d(gamesnd_get_game_sound(sound_index), sound_pos, &View_position, 0.0f,
|
|
nullptr, 0, 0.6f, SND_PRIORITY_MUST_PLAY, nullptr, sound_range);
|
|
} else if (!snd_is_playing(handle)) {
|
|
// if sound not playing and old, get new one
|
|
// I don't think will happen with SND_PRIORITY_MUST_PLAY
|
|
if (get_sound_time_played(snd_get_sound_id(handle), handle) > 400) {
|
|
snd_stop(handle_array[handle_index]);
|
|
handle_array[handle_index] =
|
|
snd_play_3d(gamesnd_get_game_sound(sound_index), sound_pos, &View_position, 0.0f, nullptr, 0, 0.6f,
|
|
SND_PRIORITY_MUST_PLAY, nullptr, sound_range);
|
|
}
|
|
} else if (get_sound_time_played(snd_get_sound_id(handle), handle) > 750) {
|
|
handle_array[handle_index] = snd_play_3d(gamesnd_get_game_sound(sound_index), sound_pos, &View_position, 0.0f,
|
|
nullptr, 0, 0.6f, SND_PRIORITY_MUST_PLAY, nullptr, sound_range);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Maybe create a fireball along model clip plane also maybe plays explosion sound
|
|
*/
|
|
static void maybe_fireball_wipe(clip_ship* half_ship, sound_handle* handle_array)
|
|
{
|
|
// maybe make fireball to cover wipe.
|
|
if ( timestamp_elapsed(half_ship->next_fireball) ) {
|
|
if ( half_ship->length_left > 0.2f*fl_abs(half_ship->explosion_vel) ) {
|
|
ship_info *sip = &Ship_info[Ships[half_ship->parent_obj->instance].ship_info_index];
|
|
|
|
if ( !sip->split_particles.isValid() ) {
|
|
// time out forever
|
|
half_ship->next_fireball = timestamp(-1);
|
|
return;
|
|
}
|
|
|
|
polymodel* pm = model_get(sip->model_num);
|
|
|
|
vec3d model_clip_plane_pt, orig_ship_world_center, temp;
|
|
|
|
vm_vec_unrotate(&orig_ship_world_center, &half_ship->model_center_disp_to_orig_center, &half_ship->orient);
|
|
vm_vec_add2(&orig_ship_world_center, &half_ship->local_pivot);
|
|
|
|
vm_vec_make(&temp, 0.0f, 0.0f, half_ship->cur_clip_plane_pt);
|
|
vm_vec_unrotate(&model_clip_plane_pt, &temp, &half_ship->orient);
|
|
vm_vec_add2(&model_clip_plane_pt, &orig_ship_world_center);
|
|
vm_vec_rand_vec_quick(&temp);
|
|
vm_vec_scale(&temp, 0.1f*frand());
|
|
vm_vec_add2(&model_clip_plane_pt, &temp);
|
|
|
|
float cross_section_rad = get_model_cross_section_at_z(half_ship->cur_clip_plane_pt, pm);
|
|
float rad = cross_section_rad;
|
|
if (cross_section_rad < 1) {
|
|
// changed from 0.4 & 0.6 to 0.6 & 0.9 as later 1.5 multiplier was removed
|
|
rad = half_ship->parent_obj->radius * frand_range(0.6f, 0.9f);
|
|
} else {
|
|
// make fireball radius (1.5 +/- .1) * model_cross_section value
|
|
// changed from 1.4 & 1.6 to 2.1 & 2.4 as later 1.5 multiplier was removed
|
|
rad *= frand_range(2.1f, 2.4f);
|
|
}
|
|
|
|
rad = MIN(rad, half_ship->parent_obj->radius);
|
|
|
|
if (sip->propagating_exp_particles.isValid()) {
|
|
auto source = particle::ParticleManager::get()->createSource(sip->propagating_exp_particles);
|
|
auto host = std::make_unique<EffectHostVector>(model_clip_plane_pt, half_ship->orient, half_ship->phys_info.vel);
|
|
// for particle effects, we'll ignore all the special logic applied to rad and just use the raw radius; the modder can handle it using curves
|
|
host->setRadius(cross_section_rad);
|
|
source->setHost(std::move(host));
|
|
source->setNormal(half_ship->orient.vec.uvec);
|
|
source->finishCreation();
|
|
} else {
|
|
//defaults to 1.0 now that multiplier was applied to the static values above
|
|
rad *= sip->prop_exp_rad_mult;
|
|
|
|
int fireball_type = fireball_ship_explosion_type(sip);
|
|
if(fireball_type < 0) {
|
|
fireball_type = FIREBALL_EXPLOSION_LARGE1 + Random::next(FIREBALL_NUM_LARGE_EXPLOSIONS);
|
|
}
|
|
int low_res_fireballs = Bs_exp_fire_low;
|
|
fireball_create(&model_clip_plane_pt, fireball_type, FIREBALL_LARGE_EXPLOSION, OBJ_INDEX(half_ship->parent_obj), rad, false, &half_ship->parent_obj->phys_info.vel, 0.0f, -1, nullptr, low_res_fireballs);
|
|
}
|
|
|
|
// start the next fireball up (3-4 per frame) + 30%
|
|
int time_low, time_high;
|
|
time_low = int(650 * Bs_exp_fire_time_mult);
|
|
time_high = int(900 * Bs_exp_fire_time_mult);
|
|
half_ship->next_fireball = timestamp_rand(time_low, time_high);
|
|
|
|
// do sound
|
|
do_sub_expl_sound(half_ship->parent_obj->radius, &model_clip_plane_pt, handle_array);
|
|
|
|
// do particles
|
|
|
|
//This does not seem right, but is current particle behaviour
|
|
vec3d normal = vmd_x_vector;
|
|
|
|
auto source = particle::ParticleManager::get()->createSource(sip->split_particles);
|
|
|
|
matrix orient;
|
|
vm_vector_2_matrix_norm(&orient, &normal);
|
|
|
|
auto host = std::make_unique<EffectHostVector>(model_clip_plane_pt, orient, half_ship->phys_info.vel, vmd_identity_matrix, true);
|
|
host->setRadius(half_ship->parent_obj->radius);
|
|
source->setHost(std::move(host));
|
|
source->setTriggerVelocity(half_ship->explosion_vel);
|
|
source->finishCreation();
|
|
|
|
if (sip->generic_debris_model_num >= 0) {
|
|
// spawn a bunch of debris pieces, first determine the cross sectional average position to be the force explosion center
|
|
vec3d local_xc_rand, local_xc_avg, xc_rand, xc_avg;
|
|
submodel_get_cross_sectional_avg_pos(sip->model_num, -1, half_ship->cur_clip_plane_pt, &local_xc_avg);
|
|
vm_vec_unrotate(&xc_avg, &local_xc_avg, &half_ship->orient);
|
|
vm_vec_add2(&xc_avg, &orig_ship_world_center);
|
|
float num_debris = sip->generic_debris_spew_num * (Detail.num_small_debris / 4.0f);
|
|
for (int i = 0; i < num_debris; i++) {
|
|
// then get random positions on the cross section and spawn the pieces there
|
|
submodel_get_cross_sectional_random_pos(sip->model_num, -1, half_ship->cur_clip_plane_pt, &local_xc_rand);
|
|
vm_vec_unrotate(&xc_rand, &local_xc_rand, &half_ship->orient);
|
|
vm_vec_add2(&xc_rand, &orig_ship_world_center);
|
|
debris_create(half_ship->parent_obj, sip->generic_debris_model_num, -1, &xc_rand, &xc_avg, 0, frand() * half_ship->parent_obj->radius / 100);
|
|
}
|
|
}
|
|
|
|
} else {
|
|
// time out forever
|
|
half_ship->next_fireball = timestamp(-1);
|
|
}
|
|
}
|
|
}
|
|
|
|
void big_explosion_flash(float);
|
|
|
|
/**
|
|
* Returns 1 when explosion is done
|
|
*/
|
|
int shipfx_large_blowup_do_frame(ship *shipp, float frametime)
|
|
{
|
|
Assert( shipp->large_ship_blowup_index > -1 );
|
|
Assert( shipp->large_ship_blowup_index < (int)Split_ships.size() );
|
|
|
|
split_ship *the_split_ship = &Split_ships[shipp->large_ship_blowup_index];
|
|
Assert( the_split_ship->used ); // Get John
|
|
|
|
// Do fireballs, particles, shockwave here
|
|
// Note parent ship is still valid, vel and pos updated in obj_move_all
|
|
|
|
if ( timestamp_elapsed(the_split_ship->explosion_flash_timestamp) ) {
|
|
if ( !the_split_ship->explosion_flash_started ) {
|
|
object* objp = &Objects[shipp->objnum];
|
|
if (objp->flags[Object::Object_Flags::Was_rendered]) {
|
|
float excess_dist = (Player_obj == nullptr) ? 0.0f : vm_vec_dist(&Player_obj->pos, &objp->pos) - 2.0f*objp->radius - Player_obj->radius;
|
|
float intensity = 1.0f - 0.1f*excess_dist / objp->radius;
|
|
|
|
if (intensity > 1) {
|
|
intensity = 1.0f;
|
|
}
|
|
|
|
if (intensity > 0.1f && Ship_info[shipp->ship_info_index].flags[Ship::Info_Flags::Flash]) {
|
|
big_explosion_flash(intensity);
|
|
}
|
|
}
|
|
the_split_ship->explosion_flash_started = 1;
|
|
}
|
|
}
|
|
physics_sim(&the_split_ship->front_ship.local_pivot, &the_split_ship->front_ship.orient, &the_split_ship->front_ship.phys_info, &The_mission.gravity, frametime);
|
|
physics_sim(&the_split_ship->back_ship.local_pivot, &the_split_ship->back_ship.orient, &the_split_ship->back_ship.phys_info, &The_mission.gravity, frametime);
|
|
the_split_ship->front_ship.length_left -= the_split_ship->front_ship.explosion_vel*frametime;
|
|
the_split_ship->back_ship.length_left += the_split_ship->back_ship.explosion_vel *frametime;
|
|
the_split_ship->front_ship.cur_clip_plane_pt += the_split_ship->front_ship.explosion_vel*frametime;
|
|
the_split_ship->back_ship.cur_clip_plane_pt += the_split_ship->back_ship.explosion_vel *frametime;
|
|
|
|
float length_left = MAX( the_split_ship->front_ship.length_left, the_split_ship->back_ship.length_left );
|
|
|
|
if ( length_left < 0 ) {
|
|
the_split_ship->used = 0;
|
|
return 1;
|
|
}
|
|
|
|
maybe_fireball_wipe(&the_split_ship->front_ship, the_split_ship->sound_handles.data());
|
|
maybe_fireball_wipe(&the_split_ship->back_ship, the_split_ship->sound_handles.data());
|
|
return 0;
|
|
}
|
|
|
|
void shipfx_large_blowup_queue_render(model_draw_list *scene, ship* shipp)
|
|
{
|
|
Assert( shipp->large_ship_blowup_index > -1 );
|
|
Assert( shipp->large_ship_blowup_index < (int)Split_ships.size() );
|
|
|
|
split_ship *the_split_ship = &Split_ships[shipp->large_ship_blowup_index];
|
|
Assert( the_split_ship->used ); // Get John
|
|
|
|
if (the_split_ship->front_ship.length_left > 0) {
|
|
shipfx_queue_render_ship_halves_and_debris(scene, &the_split_ship->front_ship,shipp);
|
|
}
|
|
|
|
if (the_split_ship->back_ship.length_left > 0) {
|
|
shipfx_queue_render_ship_halves_and_debris(scene, &the_split_ship->back_ship,shipp);
|
|
}
|
|
}
|
|
|
|
void shipfx_shadow_render_blowup(shadow_render_list* shadow_list, ship* shipp)
|
|
{
|
|
Assert(shipp->large_ship_blowup_index > -1);
|
|
Assert(shipp->large_ship_blowup_index < (int)Split_ships.size());
|
|
|
|
split_ship* the_split_ship = &Split_ships[shipp->large_ship_blowup_index];
|
|
Assert(the_split_ship->used);
|
|
|
|
auto pmi = model_get_instance(shipp->model_instance_num);
|
|
auto pm = model_get(pmi->model_num);
|
|
|
|
for (int half_idx = 0; half_idx < 2; half_idx++) {
|
|
clip_ship* half = (half_idx == 0) ? &the_split_ship->front_ship : &the_split_ship->back_ship;
|
|
if (half->length_left <= 0) continue;
|
|
|
|
vec3d clip_plane_norm, orig_ship_world_center, model_clip_plane_pt;
|
|
vm_vec_unrotate(&clip_plane_norm, &half->clip_plane_norm, &half->orient);
|
|
vm_vec_unrotate(&orig_ship_world_center, &half->model_center_disp_to_orig_center, &half->orient);
|
|
vm_vec_add2(&orig_ship_world_center, &half->local_pivot);
|
|
|
|
vec3d temp;
|
|
vm_vec_make(&temp, 0.0f, 0.0f, half->cur_clip_plane_pt);
|
|
vm_vec_unrotate(&model_clip_plane_pt, &temp, &half->orient);
|
|
vm_vec_add2(&model_clip_plane_pt, &orig_ship_world_center);
|
|
|
|
shadow_render_list::clip_plane_info clip;
|
|
clip.normal = clip_plane_norm;
|
|
clip.position = model_clip_plane_pt;
|
|
|
|
shadow_render_list::add_model_draws(shadow_list, pm, pmi, shipp->objnum, &half->local_pivot, &half->orient, &clip);
|
|
}
|
|
}
|
|
|
|
// ================== DO THE ELECTRIC ARCING STUFF =====================
|
|
// Creates any new ones, moves old ones.
|
|
|
|
const float MAX_ARC_LENGTH_PERCENTAGE = 0.25f;
|
|
|
|
const float MAX_EMP_ARC_TIMESTAMP = 150.0f;
|
|
|
|
void shipfx_do_lightning_arcs_frame( ship *shipp )
|
|
{
|
|
object *obj = &Objects[shipp->objnum];
|
|
ship_info* sip = &Ship_info[shipp->ship_info_index];
|
|
int model_num = sip->model_num;
|
|
polymodel* pm = model_get(model_num);
|
|
|
|
// first do any passive ship arcs, separate from damage or emp arcs
|
|
for (int passive_arc_info_idx = 0; passive_arc_info_idx < (int)sip->ship_passive_arcs.size(); passive_arc_info_idx++) {
|
|
if (!shipp->flags[Ship::Ship_Flags::No_passive_lightning] && timestamp_elapsed(shipp->passive_arc_next_times[passive_arc_info_idx])) {
|
|
|
|
ship_passive_arc_info* arc_info = &sip->ship_passive_arcs[passive_arc_info_idx];
|
|
|
|
// find the specified submodels involved, if necessary
|
|
if (arc_info->submodels.first < 0 || arc_info->submodels.second < 0) {
|
|
for (int i = 0; i < pm->n_models; i++) {
|
|
if (!stricmp(pm->submodel[i].name, arc_info->submodel_strings.first.c_str()))
|
|
arc_info->submodels.first = i;
|
|
if (!stricmp(pm->submodel[i].name, arc_info->submodel_strings.second.c_str()))
|
|
arc_info->submodels.second = i;
|
|
}
|
|
}
|
|
int submodel_1 = arc_info->submodels.first;
|
|
int submodel_2 = arc_info->submodels.second;
|
|
|
|
// see if these submodels are also subsystems, and dont draw if its destroyed
|
|
bool skip = false;
|
|
for (ship_subsys* pss = GET_FIRST(&shipp->subsys_list); pss != END_OF_LIST(&shipp->subsys_list); pss = GET_NEXT(pss)) {
|
|
if (pss->system_info->subobj_num == submodel_1 && pss->max_hits > 0 && pss->current_hits <= 0) {
|
|
skip = true;
|
|
break;
|
|
} else if (pss->system_info->subobj_num == submodel_2 && pss->max_hits > 0 && pss->current_hits <= 0) {
|
|
skip = true;
|
|
break;
|
|
}
|
|
}
|
|
if (skip) continue;
|
|
|
|
if (submodel_1 >= 0 && submodel_2 >= 0) {
|
|
// spawn the arc in the first unused slot, or in a new slot if there are no unused ones
|
|
auto arc = ship_find_or_create_electrical_arc_slot(shipp, shipp->electrical_arcs.size() >= MAX_ARC_EFFECTS);
|
|
if (arc) {
|
|
arc->timestamp = _timestamp(fl2i(arc_info->duration * MILLISECONDS_PER_SECOND));
|
|
|
|
vec3d v1, v2, offset;
|
|
// subtract away the submodel's offset, since these positions were in frame of ref of the whole ship
|
|
model_find_submodel_offset(&offset, pm, submodel_1);
|
|
v1 = arc_info->pos.first - offset;
|
|
model_find_submodel_offset(&offset, pm, submodel_2);
|
|
v2 = arc_info->pos.second - offset;
|
|
|
|
model_instance_local_to_global_point(&v1, &v1, shipp->model_instance_num, submodel_1, &vmd_identity_matrix, &vmd_zero_vector);
|
|
arc->endpoint_1 = v1;
|
|
model_instance_local_to_global_point(&v2, &v2, shipp->model_instance_num, submodel_2, &vmd_identity_matrix, &vmd_zero_vector);
|
|
arc->endpoint_2 = v2;
|
|
|
|
//Set the arc colors
|
|
arc->primary_color_1 = arc_info->primary_color_1;
|
|
arc->primary_color_2 = arc_info->primary_color_2;
|
|
arc->secondary_color = arc_info->secondary_color;
|
|
|
|
arc->type = MARC_TYPE_SHIP;
|
|
|
|
if (arc_info->width > 0.0f) {
|
|
arc->width = arc_info->width;
|
|
} else {
|
|
// same width as other arc types in model_render_add_lightning
|
|
// try and scale the size a bit so that it looks equally well on smaller vessels
|
|
arc->width = Arc_width_default_damage;
|
|
if (pm->rad < Arc_width_no_multiply_over_radius_damage) {
|
|
arc->width *= (pm->rad * Arc_width_radius_multiplier_damage);
|
|
|
|
if (arc->width < Arc_width_minimum_damage) {
|
|
arc->width = Arc_width_minimum_damage;
|
|
}
|
|
}
|
|
}
|
|
|
|
arc->segment_depth = 4; // previously hard-coded in interp_generate_arc_segment()
|
|
|
|
arc->persistent_arc_points.reset(); // by default, no persistent points
|
|
|
|
shipp->passive_arc_next_times[passive_arc_info_idx] = _timestamp(fl2i(arc_info->frequency * MILLISECONDS_PER_SECOND));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// now handle damage/emp arcs
|
|
int should_arc = 1;
|
|
int disrupted_arc=0;
|
|
|
|
float damage = get_hull_pct(obj);
|
|
|
|
if (damage < 0) {
|
|
damage = 0.0f;
|
|
}
|
|
|
|
// don't draw an arc based on damage
|
|
if ( damage > 0.30f ) {
|
|
// Don't do spark.
|
|
should_arc = 0;
|
|
}
|
|
|
|
// SUSHI: If the lightning type is NONE, we can skip this
|
|
if (Ship_info[shipp->ship_info_index].damage_lightning_type == SLT_NONE)
|
|
should_arc = 0;
|
|
|
|
// we should draw an arc
|
|
if( shipp->emp_intensity > 0.0f){
|
|
should_arc = 1;
|
|
}
|
|
|
|
if ((ship_subsys_disrupted(shipp,SUBSYSTEM_ENGINE)) ||
|
|
(ship_subsys_disrupted(shipp,SUBSYSTEM_WEAPONS)) ||
|
|
(ship_subsys_disrupted(shipp,SUBSYSTEM_SENSORS)) )
|
|
{
|
|
disrupted_arc=1;
|
|
should_arc=1;
|
|
}
|
|
|
|
// Kill off old sparks
|
|
for (auto &arc: shipp->electrical_arcs) {
|
|
if (arc.timestamp.isValid() && timestamp_elapsed(arc.timestamp)) {
|
|
arc.timestamp = TIMESTAMP::invalid();
|
|
}
|
|
}
|
|
|
|
// if we shouldn't draw an arc, return
|
|
if(!should_arc){
|
|
return;
|
|
}
|
|
|
|
if (!shipp->arc_next_time.isValid()) {
|
|
// start the next fireball up in the next 10 seconds or so...
|
|
int freq;
|
|
|
|
// if the emp effect is active or its disrupted
|
|
if((shipp->emp_intensity > 0.0f) || (disrupted_arc)){
|
|
freq = fl2i(MAX_EMP_ARC_TIMESTAMP);
|
|
}
|
|
// otherwise if we're arcing based upon damage
|
|
else {
|
|
freq = fl2i((damage+0.1f)*5000.0f);
|
|
}
|
|
|
|
// set the next arc time
|
|
shipp->arc_next_time = _timestamp_rand(freq*2,freq*4);
|
|
}
|
|
|
|
if ( timestamp_elapsed(shipp->arc_next_time) ) {
|
|
|
|
shipp->arc_next_time = TIMESTAMP::invalid(); // invalid, so it gets restarted next frame
|
|
|
|
int n_arcs = Random::next(1, 3);
|
|
|
|
vec3d v1 = submodel_get_random_point(model_num, -1);
|
|
vec3d v2 = submodel_get_random_point(model_num, -1);
|
|
vec3d v3 = submodel_get_random_point(model_num, -1);
|
|
vec3d v4 = submodel_get_random_point(model_num, -1);
|
|
|
|
// For large ships, cap the length to be 25% of max radius
|
|
if ( obj->radius > 200.0f ) {
|
|
float max_dist = obj->radius * MAX_ARC_LENGTH_PERCENTAGE;
|
|
|
|
vec3d tmp;
|
|
float d;
|
|
|
|
// Cap arc 2->1
|
|
vm_vec_sub( &tmp, &v1, &v2 );
|
|
d = vm_vec_mag_quick( &tmp );
|
|
if ( d > max_dist ) {
|
|
vm_vec_scale_add( &v1, &v2, &tmp, max_dist / d );
|
|
}
|
|
|
|
// Cap arc 2->3
|
|
vm_vec_sub( &tmp, &v3, &v2 );
|
|
d = vm_vec_mag_quick( &tmp );
|
|
if ( d > max_dist ) {
|
|
vm_vec_scale_add( &v3, &v2, &tmp, max_dist / d );
|
|
}
|
|
|
|
|
|
// Cap arc 2->4
|
|
vm_vec_sub( &tmp, &v4, &v2 );
|
|
d = vm_vec_mag_quick( &tmp );
|
|
if ( d > max_dist ) {
|
|
vm_vec_scale_add( &v4, &v2, &tmp, max_dist / d );
|
|
}
|
|
|
|
}
|
|
|
|
float factor = 1.0f + 0.0025f*obj->radius;
|
|
int a = (int) (factor*100.0f);
|
|
int b = (int) (factor*1000.0f);
|
|
int lifetime = Random::next(a, b);
|
|
|
|
// Create the arc effects
|
|
auto num_damage_arcs = std::count_if(shipp->electrical_arcs.begin(), shipp->electrical_arcs.end(), [](const ship_electrical_arc &arc) {
|
|
return arc.timestamp.isValid() && (arc.type == MARC_TYPE_DAMAGED || arc.type == MARC_TYPE_EMP);
|
|
});
|
|
for (int n = 0; n < n_arcs && num_damage_arcs < MAX_SHIP_DAMAGE_ARCS; n++) {
|
|
auto arc = ship_find_or_create_electrical_arc_slot(shipp, false);
|
|
if (arc) {
|
|
arc->timestamp = _timestamp(lifetime); // live up to a second
|
|
|
|
switch( n ) {
|
|
case 0:
|
|
arc->endpoint_1 = v1;
|
|
arc->endpoint_2 = v2;
|
|
break;
|
|
case 1:
|
|
arc->endpoint_1 = v2;
|
|
arc->endpoint_2 = v3;
|
|
break;
|
|
case 2:
|
|
arc->endpoint_1 = v2;
|
|
arc->endpoint_2 = v4;
|
|
break;
|
|
|
|
default:
|
|
Assertion(false, "Unhandled case %d for electrical arc creation in shipfx_do_lightning_arcs_frame()!", n);
|
|
}
|
|
|
|
// determine what kind of arc to create
|
|
if((shipp->emp_intensity > 0.0f) || (disrupted_arc)){
|
|
arc->type = MARC_TYPE_EMP;
|
|
} else {
|
|
arc->type = MARC_TYPE_DAMAGED;
|
|
}
|
|
|
|
arc->segment_depth = 4; // previously hard-coded in interp_generate_arc_segment()
|
|
|
|
arc->persistent_arc_points.reset(); // by default, no persistent points
|
|
|
|
num_damage_arcs++;
|
|
}
|
|
}
|
|
|
|
// rotate v2 out of local coordinates into world.
|
|
// Use v2 since it is used in every bolt. See above switch().
|
|
vec3d snd_pos;
|
|
vm_vec_unrotate(&snd_pos, &v2, &obj->orient);
|
|
vm_vec_add2(&snd_pos, &obj->pos );
|
|
|
|
//Play a sound effect
|
|
if ( lifetime > 750 ) {
|
|
// 1.00 second effect
|
|
snd_play_3d( gamesnd_get_game_sound(GameSounds::DEBRIS_ARC_05), &snd_pos, &View_position, obj->radius );
|
|
} else if ( lifetime > 500 ) {
|
|
// 0.75 second effect
|
|
snd_play_3d( gamesnd_get_game_sound(GameSounds::DEBRIS_ARC_04), &snd_pos, &View_position, obj->radius );
|
|
} else if ( lifetime > 250 ) {
|
|
// 0.50 second effect
|
|
snd_play_3d( gamesnd_get_game_sound(GameSounds::DEBRIS_ARC_03), &snd_pos, &View_position, obj->radius );
|
|
} else if ( lifetime > 100 ) {
|
|
// 0.25 second effect
|
|
snd_play_3d( gamesnd_get_game_sound(GameSounds::DEBRIS_ARC_02), &snd_pos, &View_position, obj->radius );
|
|
} else {
|
|
// 0.10 second effect
|
|
snd_play_3d( gamesnd_get_game_sound(GameSounds::DEBRIS_ARC_01), &snd_pos, &View_position, obj->radius );
|
|
}
|
|
}
|
|
|
|
// maybe move arc points around
|
|
for (auto &arc: shipp->electrical_arcs) {
|
|
//Only move arc points around for Damaged or EMP type arcs
|
|
if (((arc.type == MARC_TYPE_DAMAGED) || (arc.type == MARC_TYPE_EMP)) && arc.timestamp.isValid()) {
|
|
if (!timestamp_elapsed(arc.timestamp)) {
|
|
// Maybe move a vertex.... 20% of the time maybe?
|
|
int mr = Random::next();
|
|
if ( mr < Random::MAX_VALUE/5 ) {
|
|
vec3d v1 = submodel_get_random_point(model_num, -1);
|
|
|
|
vec3d static_one;
|
|
|
|
if ( mr % 2 ) {
|
|
static_one = arc.endpoint_1;
|
|
} else {
|
|
static_one = arc.endpoint_2;
|
|
}
|
|
|
|
// For large ships, cap the length to be 25% of max radius
|
|
if ( obj->radius > 200.0f ) {
|
|
float max_dist = obj->radius * MAX_ARC_LENGTH_PERCENTAGE;
|
|
|
|
vec3d tmp;
|
|
float d;
|
|
|
|
// Cap arc 2->1
|
|
vm_vec_sub( &tmp, &v1, &static_one );
|
|
d = vm_vec_mag_quick( &tmp );
|
|
if ( d > max_dist ) {
|
|
vm_vec_scale_add( &v1, &static_one, &tmp, max_dist / d );
|
|
}
|
|
}
|
|
|
|
if (mr % 2 == 0)
|
|
arc.endpoint_1 = v1;
|
|
else
|
|
arc.endpoint_2 = v1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int l_cruiser_count = 1;
|
|
int l_big_count = 2;
|
|
int l_huge_count = 3;
|
|
float l_max_radius = 3000.0f;
|
|
void shipfx_do_lightning_frame( ship * /*shipp*/ )
|
|
{
|
|
/*
|
|
ship_info *sip;
|
|
object *objp;
|
|
int stamp, count;
|
|
vec3d v1, v2, n1, n2, temp, temp2;
|
|
bolt_info binfo;
|
|
|
|
// sanity checks
|
|
Assert(shipp != NULL);
|
|
if(shipp == NULL){
|
|
return;
|
|
}
|
|
Assert(shipp->ship_info_index >= 0);
|
|
if(shipp->ship_info_index < 0){
|
|
return;
|
|
}
|
|
Assert(shipp->objnum >= 0);
|
|
if(shipp->objnum < 0){
|
|
return;
|
|
}
|
|
|
|
// get some pointers
|
|
sip = &Ship_info[shipp->ship_info_index];
|
|
objp = &Objects[shipp->objnum];
|
|
|
|
// if this is not a nebula mission, don't do anything
|
|
if(!(The_mission.flags[Mission::Mission_Flags::Fullneb])){
|
|
shipp->lightning_stamp = -1;
|
|
return;
|
|
}
|
|
|
|
// if this not a cruiser or big ship
|
|
if(!((sip->flags[Ship::Info_Flags::Cruiser]) || (sip->is_big_ship()) || (sip->is_huge_ship()))){
|
|
shipp->lightning_stamp = -1;
|
|
return;
|
|
}
|
|
|
|
// determine stamp and count values
|
|
if(sip->flags[Ship::Info_Flags::Cruiser]){
|
|
stamp = (int)((float)(Nebl_cruiser_min + ((Nebl_cruiser_max - Nebl_cruiser_min) * Nebl_intensity)) * frand_range(0.8f, 1.1f));
|
|
count = l_cruiser_count;
|
|
}
|
|
else {
|
|
if(sip->is_huge_ship()){
|
|
stamp = (int)((float)(Nebl_supercap_min + ((Nebl_supercap_max - Nebl_supercap_min) * Nebl_intensity)) * frand_range(0.8f, 1.1f));
|
|
count = l_huge_count;
|
|
} else {
|
|
stamp = (int)((float)(Nebl_cap_min + ((Nebl_cap_max - Nebl_cap_min) * Nebl_intensity)) * frand_range(0.8f, 1.1f));
|
|
count = l_big_count;
|
|
}
|
|
}
|
|
|
|
// if his timestamp is unset
|
|
if(shipp->lightning_stamp == -1){
|
|
shipp->lightning_stamp = timestamp(stamp);
|
|
return;
|
|
}
|
|
// if his timestamp is currently unelapsed
|
|
if(!timestamp_elapsed(shipp->lightning_stamp)){
|
|
return;
|
|
}
|
|
|
|
mprintf(("SHIP BOLT\n"));
|
|
|
|
// restamp him first
|
|
shipp->lightning_stamp = timestamp(stamp);
|
|
|
|
// ah, now we can create some lightning bolts
|
|
count = Random::next(count+1);
|
|
while(count > 0){
|
|
// get 2 points on the hull of the ship
|
|
v1 = submodel_get_random_point(shipp->modelnum, 0);
|
|
v2 = submodel_get_random_point(shipp->modelnum, 0);
|
|
|
|
// make up to 2 bolts
|
|
if(objp->radius > l_max_radius){
|
|
vm_vec_scale_add(&temp2, &v1, &n1, l_max_radius);
|
|
} else {
|
|
vm_vec_scale_add(&temp2, &v1, &n1, objp->radius);
|
|
}
|
|
vm_vec_unrotate(&temp, &temp2, &objp->orient);
|
|
vm_vec_add2(&temp, &objp->pos);
|
|
vm_vec_unrotate(&temp2, &v1, &objp->orient);
|
|
vm_vec_add2(&temp2, &objp->pos);
|
|
|
|
// create the bolt
|
|
binfo.start = temp;
|
|
binfo.strike = temp2;
|
|
binfo.num_strikes = 3;
|
|
binfo.noise = 0.045f;
|
|
binfo.life = 375;
|
|
binfo.delay = Random::next(1600+1);
|
|
nebl_bolt(&binfo);
|
|
count--;
|
|
|
|
// done
|
|
if(count <= 0){
|
|
break;
|
|
}
|
|
|
|
// one more
|
|
if(objp->radius > l_max_radius){
|
|
vm_vec_scale_add(&temp2, &v2, &n2, l_max_radius);
|
|
} else {
|
|
vm_vec_scale_add(&temp2, &v2, &n2, objp->radius);
|
|
}
|
|
vm_vec_unrotate(&temp, &temp2, &objp->orient);
|
|
vm_vec_add2(&temp, &objp->pos);
|
|
vm_vec_unrotate(&temp2, &v2, &objp->orient);
|
|
vm_vec_add2(&temp2, &objp->pos);
|
|
|
|
// create the bolt
|
|
binfo.start = temp;
|
|
binfo.strike = temp2;
|
|
binfo.num_strikes = 3;
|
|
binfo.noise = 0.045f;
|
|
binfo.life = 375;
|
|
binfo.delay = Random::next(1600+1);
|
|
nebl_bolt(&binfo);
|
|
count--;
|
|
}
|
|
*/
|
|
}
|
|
|
|
/**
|
|
* Do all shockwaves for a ship blowing up
|
|
*/
|
|
void shipfx_do_shockwave_stuff(ship *shipp, shockwave_create_info *sci)
|
|
{
|
|
ship_info *sip;
|
|
object *objp;
|
|
polymodel *pm;
|
|
vec3d temp, dir, shockwave_pos;
|
|
vec3d head = vmd_zero_vector;
|
|
vec3d tail = vmd_zero_vector;
|
|
float step, cur;
|
|
int idx;
|
|
|
|
// sanity checks
|
|
Assert(shipp != NULL);
|
|
if(shipp == NULL){
|
|
return;
|
|
}
|
|
Assert(shipp->ship_info_index >= 0);
|
|
if(shipp->ship_info_index < 0){
|
|
return;
|
|
}
|
|
Assert(shipp->objnum >= 0);
|
|
if(shipp->objnum < 0){
|
|
return;
|
|
}
|
|
Assert(sci != NULL);
|
|
if (sci == NULL) {
|
|
return;
|
|
}
|
|
|
|
// get some pointers
|
|
sip = &Ship_info[shipp->ship_info_index];
|
|
objp = &Objects[shipp->objnum];
|
|
|
|
if(sip->shockwave_count <= 0){
|
|
return;
|
|
}
|
|
|
|
// get vectors at the head and tail of the object, dead center
|
|
pm = model_get(sip->model_num);
|
|
if(pm == NULL){
|
|
return;
|
|
}
|
|
head.xyz.x = pm->submodel[0].offset.xyz.x;
|
|
head.xyz.y = pm->submodel[0].offset.xyz.y;
|
|
head.xyz.z = pm->maxs.xyz.z;
|
|
|
|
tail.xyz.x = pm->submodel[0].offset.xyz.x;
|
|
tail.xyz.y = pm->submodel[0].offset.xyz.y;
|
|
tail.xyz.z = pm->mins.xyz.z;
|
|
|
|
// transform the vectors into world coords
|
|
vm_vec_unrotate(&temp, &head, &objp->orient);
|
|
vm_vec_add(&head, &temp, &objp->pos);
|
|
vm_vec_unrotate(&temp, &tail, &objp->orient);
|
|
vm_vec_add(&tail, &temp, &objp->pos);
|
|
|
|
// now create as many shockwaves as needed
|
|
vm_vec_sub(&dir, &head, &tail);
|
|
step = 1.0f / ((float)sip->shockwave_count + 1.0f);
|
|
cur = step;
|
|
for(idx=0; idx<sip->shockwave_count; idx++){
|
|
// get the shockwave position
|
|
temp = dir;
|
|
vm_vec_scale(&temp, cur);
|
|
vm_vec_add(&shockwave_pos, &tail, &temp);
|
|
|
|
// if knossos device, make shockwave in center
|
|
if (Ship_info[shipp->ship_info_index].flags[Ship::Info_Flags::Knossos_device]) {
|
|
shockwave_pos = Objects[shipp->objnum].pos;
|
|
}
|
|
|
|
// create the shockwave
|
|
shockwave_create_info sci2 = *sci;
|
|
sci2.blast = (sci->blast / (float)sip->shockwave_count) * frand_range(0.75f, 1.25f);
|
|
sci2.damage = (sci->damage / (float)sip->shockwave_count) * frand_range(0.75f, 1.25f);
|
|
sci2.speed = sci->speed * frand_range(0.75f, 1.25f);
|
|
sci2.rot_angles.p = frand_range(0.0f, 1.99f*PI);
|
|
sci2.rot_angles.b = frand_range(0.0f, 1.99f*PI);
|
|
sci2.rot_angles.h = frand_range(0.0f, 1.99f*PI);
|
|
|
|
shockwave_create(shipp->objnum, &shockwave_pos, &sci2, SW_SHIP_DEATH, Random::next(350+1));
|
|
|
|
// next shockwave
|
|
cur += step;
|
|
}
|
|
}
|
|
|
|
extern int model_should_render_engine_glow(int objnum, int bank_obj);
|
|
int Wash_on = 1;
|
|
DCF_BOOL(engine_wash, Wash_on)
|
|
#define ENGINE_WASH_CHECK_INTERVAL 250 // (4x sec)
|
|
|
|
/**
|
|
* Do engine wash effect for ship
|
|
*
|
|
* Assumes length of engine wash is greater than radius of engine wash hemisphere
|
|
*/
|
|
void engine_wash_ship_process(ship *shipp)
|
|
{
|
|
int idx, j;
|
|
object *objp, *max_ship_intensity_objp;
|
|
int started_with_no_wash = shipp->wash_intensity <= 0 ? 1 : 0;
|
|
|
|
if (!Wash_on) {
|
|
return;
|
|
}
|
|
|
|
Assert(shipp != NULL);
|
|
Assert(shipp->objnum >= 0);
|
|
objp = &Objects[shipp->objnum];
|
|
ship_obj *so;
|
|
|
|
float dist_sqr, inset_depth, dot_to_ship, max_ship_intensity;
|
|
float max_wash_dist, half_angle, radius_mult;
|
|
|
|
// if this is not a fighter or bomber, we don't care
|
|
if ((objp->type != OBJ_SHIP) || !(Ship_info[shipp->ship_info_index].is_fighter_bomber()) ) {
|
|
return;
|
|
}
|
|
|
|
// is it time to check for engine wash
|
|
int time_to_next_hit = timestamp_until(shipp->wash_timestamp);
|
|
if (time_to_next_hit <= 0) {
|
|
if (time_to_next_hit < -ENGINE_WASH_CHECK_INTERVAL) {
|
|
time_to_next_hit = 0;
|
|
}
|
|
|
|
// keep interval constant independent of framerate
|
|
shipp->wash_timestamp = timestamp(ENGINE_WASH_CHECK_INTERVAL + time_to_next_hit);
|
|
|
|
// initialize wash params
|
|
shipp->wash_intensity = 0.0f;
|
|
vm_vec_zero(&shipp->wash_rot_axis);
|
|
max_ship_intensity_objp = NULL;
|
|
max_ship_intensity = 0;
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
// only do damage if we're within half of the max wash distance
|
|
int do_damage = 0;
|
|
|
|
// go thru Ship_used_list and check if we're in wash from CAP or SUPERCAP (SIF_HUGE)
|
|
for (so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so)) {
|
|
if (so->objnum < 0) {
|
|
continue;
|
|
}
|
|
|
|
object *wash_objp = &Objects[so->objnum];
|
|
if (wash_objp->flags[Object::Object_Flags::Should_be_dead])
|
|
continue;
|
|
|
|
if (!wash_objp || (wash_objp->instance < 0) || (wash_objp->type != OBJ_SHIP)) {
|
|
continue;
|
|
}
|
|
|
|
ship *wash_shipp = &Ships[wash_objp->instance];
|
|
ship_info *wash_sip = &Ship_info[wash_shipp->ship_info_index];
|
|
|
|
// don't do small ships
|
|
if (wash_sip->is_small_ship()) {
|
|
continue;
|
|
}
|
|
|
|
auto pm = model_get(wash_sip->model_num);
|
|
auto pmi = model_get_instance(wash_shipp->model_instance_num);
|
|
float ship_intensity = 0;
|
|
|
|
// if engines disabled, no engine wash
|
|
if (ship_subsystems_blown(wash_shipp, SUBSYSTEM_ENGINE)) {
|
|
continue;
|
|
}
|
|
|
|
float speed_scale;
|
|
if (wash_objp->phys_info.speed > 20.0f)
|
|
speed_scale = 1.0f;
|
|
else
|
|
speed_scale = wash_objp->phys_info.speed/20.0f;
|
|
|
|
for (idx = 0; idx < pm->n_thrusters; idx++) {
|
|
thruster_bank *bank = &pm->thrusters[idx];
|
|
vec3d submodel_static_offset; // The associated submodel's static offset in the ship's frame of reference
|
|
bool submodel_rotation = false;
|
|
|
|
// make sure this engine is functional before we try to process a wash from it
|
|
if ( !model_should_render_engine_glow(OBJ_INDEX(wash_objp), bank->obj_num) ) {
|
|
continue;
|
|
}
|
|
|
|
// check if thruster bank has engine wash
|
|
if (bank->wash_info_pointer == NULL) {
|
|
// if huge, give default engine wash
|
|
if ((wash_sip->is_huge_ship()) && !Engine_wash_info.empty()) {
|
|
bank->wash_info_pointer = &Engine_wash_info[0];
|
|
nprintf(("wash", "Adding default engine wash to ship %s", wash_sip->name));
|
|
} else {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
engine_wash_info *ewp = bank->wash_info_pointer;
|
|
half_angle = ewp->angle;
|
|
radius_mult = ewp->radius_mult;
|
|
float wash_intensity;
|
|
if (Use_engine_wash_intensity) {
|
|
wash_intensity = ewp->intensity;
|
|
} else {
|
|
wash_intensity = 1.0f;
|
|
}
|
|
|
|
|
|
|
|
// If bank is attached to a submodel, prepare to account for rotations
|
|
//
|
|
// TODO: This won't work in the ship lab, because the lab code doesn't
|
|
// set the the necessary submodel instance info needed here. The second
|
|
// condition is thus a hack to disable the feature while in the lab, and
|
|
// can be removed if the lab is re-structured accordingly. -zookeeper
|
|
if ( bank->submodel_num >= 0 && pm->submodel[bank->submodel_num].flags[Model::Submodel_flags::Can_move] && (gameseq_get_state_idx(GS_STATE_LAB) == -1) ) {
|
|
model_find_submodel_offset(&submodel_static_offset, pm, bank->submodel_num);
|
|
|
|
submodel_rotation = true;
|
|
}
|
|
|
|
for (j=0; j<bank->num_points; j++) {
|
|
vec3d world_thruster_pos, world_thruster_norm, apex, thruster_to_ship, apex_to_ship, temp;
|
|
vec3d loc_pos = bank->points[j].pnt;
|
|
vec3d loc_norm = bank->points[j].norm;
|
|
|
|
if ( submodel_rotation ) {
|
|
vm_vec_sub(&loc_pos, &bank->points[j].pnt, &submodel_static_offset);
|
|
|
|
// Gets the final offset and normal in the ship's frame of reference
|
|
temp = loc_pos;
|
|
model_instance_local_to_global_point_dir(&loc_pos, &loc_norm, &temp, &loc_norm, pm, pmi, bank->submodel_num);
|
|
}
|
|
|
|
// get world pos of thruster
|
|
vm_vec_unrotate(&world_thruster_pos, &loc_pos, &wash_objp->orient);
|
|
vm_vec_add2(&world_thruster_pos, &wash_objp->pos);
|
|
|
|
// get world norm of thruster;
|
|
vm_vec_unrotate(&world_thruster_norm, &loc_norm, &wash_objp->orient);
|
|
|
|
// get vector from thruster to ship
|
|
vm_vec_sub(&thruster_to_ship, &objp->pos, &world_thruster_pos);
|
|
|
|
// check if on back side of thruster
|
|
dot_to_ship = vm_vec_dot(&thruster_to_ship, &world_thruster_norm);
|
|
if (dot_to_ship > 0) {
|
|
|
|
// get max wash distance
|
|
max_wash_dist = MAX(ewp->length, bank->points[j].radius * ewp->radius_mult);
|
|
|
|
// check if within dist range
|
|
dist_sqr = vm_vec_mag_squared(&thruster_to_ship);
|
|
if (dist_sqr < max_wash_dist*max_wash_dist) {
|
|
|
|
// check if inside the sphere
|
|
if ( dist_sqr < ((radius_mult * radius_mult) * (bank->points[j].radius * bank->points[j].radius)) ) {
|
|
vm_vec_cross(&temp, &world_thruster_norm, &thruster_to_ship);
|
|
vm_vec_scale_add2(&shipp->wash_rot_axis, &temp, dot_to_ship / dist_sqr);
|
|
ship_intensity += (1.0f - dist_sqr / (max_wash_dist*max_wash_dist)) * wash_intensity;
|
|
if (!do_damage) {
|
|
if (dist_sqr < 0.25 * max_wash_dist * max_wash_dist) {
|
|
do_damage = 1;
|
|
}
|
|
}
|
|
} else {
|
|
// check if inside cone - first fine apex of cone
|
|
inset_depth = (bank->points[j].radius / fl_tan(half_angle));
|
|
vm_vec_scale_add(&apex, &world_thruster_pos, &world_thruster_norm, -inset_depth);
|
|
vm_vec_sub(&apex_to_ship, &objp->pos, &apex);
|
|
vm_vec_normalize(&apex_to_ship);
|
|
|
|
// check if inside cone angle
|
|
if (vm_vec_dot(&apex_to_ship, &world_thruster_norm) > cosf(half_angle)) {
|
|
vm_vec_cross(&temp, &world_thruster_norm, &thruster_to_ship);
|
|
vm_vec_scale_add2(&shipp->wash_rot_axis, &temp, dot_to_ship / dist_sqr);
|
|
ship_intensity += (1.0f - dist_sqr / (max_wash_dist*max_wash_dist)) * wash_intensity;
|
|
if (!do_damage) {
|
|
if (dist_sqr < 0.25 * max_wash_dist * max_wash_dist) {
|
|
do_damage = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
shipp->wash_intensity += ship_intensity * speed_scale;
|
|
|
|
if (ship_intensity > max_ship_intensity) {
|
|
max_ship_intensity = ship_intensity;
|
|
max_ship_intensity_objp = wash_objp;
|
|
}
|
|
}
|
|
|
|
// apply damage at rate of 1%/sec
|
|
if (shipp->wash_intensity > 0) {
|
|
Assert(max_ship_intensity_objp != NULL);
|
|
|
|
nprintf(("wash", "Wash intensity %.2f\n", shipp->wash_intensity));
|
|
|
|
float damage;
|
|
if (!do_damage) {
|
|
damage = 0;
|
|
} else {
|
|
damage = (0.001f * 0.003f * ENGINE_WASH_CHECK_INTERVAL * shipp->ship_max_hull_strength * shipp->wash_intensity);
|
|
}
|
|
|
|
ship_apply_wash_damage(objp, max_ship_intensity_objp, damage);
|
|
|
|
// if we had no wash before now, add the wash object sound
|
|
if(started_with_no_wash){
|
|
if(shipp != Player_ship){
|
|
obj_snd_assign(shipp->objnum, GameSounds::ENGINE_WASH, &vmd_zero_vector, OS_MAIN);
|
|
} else {
|
|
Player_engine_wash_loop = snd_play_looping( gamesnd_get_game_sound(GameSounds::ENGINE_WASH), 0.0f , -1, -1, 1.0f);
|
|
}
|
|
}
|
|
}
|
|
// if we've got no wash, kill any wash object sounds from this guy
|
|
else {
|
|
if(shipp != Player_ship){
|
|
obj_snd_delete_type(shipp->objnum, GameSounds::ENGINE_WASH);
|
|
} else {
|
|
snd_stop(Player_engine_wash_loop);
|
|
Player_engine_wash_loop = sound_handle::invalid();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Engine wash level init
|
|
*/
|
|
void shipfx_engine_wash_level_init() { Player_engine_wash_loop = sound_handle::invalid(); }
|
|
|
|
/**
|
|
* Pause engine wash sounds
|
|
*/
|
|
void shipfx_stop_engine_wash_sound()
|
|
{
|
|
if (Player_engine_wash_loop.isValid()) {
|
|
snd_stop(Player_engine_wash_loop);
|
|
Player_engine_wash_loop = sound_handle::invalid();
|
|
}
|
|
}
|
|
|
|
|
|
WarpParams::WarpParams()
|
|
{
|
|
anim[0] = '\0';
|
|
}
|
|
|
|
bool WarpParams::operator==(const WarpParams &other)
|
|
{
|
|
return direction == other.direction
|
|
&& strcmp(anim, other.anim) == 0
|
|
&& radius == other.radius
|
|
&& snd_start == other.snd_start
|
|
&& snd_end == other.snd_end
|
|
&& speed == other.speed
|
|
&& time == other.time
|
|
&& accel_exp == other.accel_exp
|
|
&& warp_type == other.warp_type
|
|
&& supercap_warp_physics == other.supercap_warp_physics
|
|
&& warpout_engage_time == other.warpout_engage_time
|
|
&& warpout_player_speed == other.warpout_player_speed;
|
|
}
|
|
|
|
bool WarpParams::operator!=(const WarpParams &other)
|
|
{
|
|
return !(operator==(other));
|
|
}
|
|
|
|
SCP_vector<WarpParams> Warp_params;
|
|
|
|
int find_or_add_warp_params(const WarpParams ¶ms)
|
|
{
|
|
// see if these parameters already exist
|
|
auto ii = std::find(Warp_params.begin(), Warp_params.end(), params);
|
|
if (ii != Warp_params.end())
|
|
return (int) (ii - Warp_params.begin());
|
|
|
|
// these are unique, so add them
|
|
Warp_params.push_back(params);
|
|
return (int) (Warp_params.size() - 1);
|
|
}
|
|
|
|
|
|
const char *Warp_types[] = {
|
|
"Default",
|
|
"Knossos",
|
|
"Babylon5",
|
|
"Galactica",
|
|
"Homeworld",
|
|
"Hyperspace",
|
|
};
|
|
|
|
int Num_warp_types = sizeof(Warp_types) / sizeof(char*);
|
|
|
|
int warptype_match(const char *p)
|
|
{
|
|
int i;
|
|
for (i = 0; i < Num_warp_types; i++)
|
|
{
|
|
if (!stricmp(Warp_types[i], p))
|
|
return i;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
void ship_set_warp_effects(object *objp)
|
|
{
|
|
int objnum = OBJ_INDEX(objp);
|
|
ship *shipp = &Ships[objp->instance];
|
|
int warpin_type = Warp_params[shipp->warpin_params_index].warp_type;
|
|
int warpout_type = Warp_params[shipp->warpout_params_index].warp_type;
|
|
|
|
if (warpin_type & WT_DEFAULT_WITH_FIREBALL)
|
|
warpin_type = WT_DEFAULT;
|
|
if (warpout_type & WT_DEFAULT_WITH_FIREBALL)
|
|
warpout_type = WT_DEFAULT;
|
|
|
|
if (shipp->warpin_effect != nullptr)
|
|
delete shipp->warpin_effect;
|
|
|
|
switch (warpin_type)
|
|
{
|
|
case WT_DEFAULT:
|
|
case WT_KNOSSOS:
|
|
case WT_DEFAULT_THEN_KNOSSOS:
|
|
shipp->warpin_effect = new WE_Default(objnum, WarpDirection::WARP_IN);
|
|
break;
|
|
case WT_IN_PLACE_ANIM:
|
|
shipp->warpin_effect = new WE_BSG(objnum, WarpDirection::WARP_IN);
|
|
break;
|
|
case WT_SWEEPER:
|
|
shipp->warpin_effect = new WE_Homeworld(objnum, WarpDirection::WARP_IN);
|
|
break;
|
|
case WT_HYPERSPACE:
|
|
shipp->warpin_effect = new WE_Hyperspace(objnum, WarpDirection::WARP_IN);
|
|
break;
|
|
default:
|
|
shipp->warpin_effect = new WarpEffect();
|
|
}
|
|
|
|
if (shipp->warpout_effect != nullptr)
|
|
delete shipp->warpout_effect;
|
|
|
|
switch (warpout_type)
|
|
{
|
|
case WT_DEFAULT:
|
|
case WT_KNOSSOS:
|
|
case WT_DEFAULT_THEN_KNOSSOS:
|
|
shipp->warpout_effect = new WE_Default(objnum, WarpDirection::WARP_OUT);
|
|
break;
|
|
case WT_IN_PLACE_ANIM:
|
|
shipp->warpout_effect = new WE_BSG(objnum, WarpDirection::WARP_OUT);
|
|
break;
|
|
case WT_SWEEPER:
|
|
shipp->warpout_effect = new WE_Homeworld(objnum, WarpDirection::WARP_OUT);
|
|
break;
|
|
case WT_HYPERSPACE:
|
|
shipp->warpout_effect = new WE_Hyperspace(objnum, WarpDirection::WARP_OUT);
|
|
break;
|
|
default:
|
|
shipp->warpout_effect = new WarpEffect();
|
|
}
|
|
}
|
|
|
|
// returns true if a point is on the "wrong side" of a portal effect i.e. the unrendered side
|
|
bool point_is_clipped_by_warp(const vec3d* point, WarpEffect* warp_effect) {
|
|
if (warp_effect == nullptr)
|
|
return false;
|
|
|
|
vec3d warp_pnt, relative_direction;
|
|
matrix warp_orient;
|
|
|
|
warp_effect->getWarpPosition(&warp_pnt);
|
|
warp_effect->getWarpOrientation(&warp_orient);
|
|
|
|
vm_vec_sub(&relative_direction, point, &warp_pnt);
|
|
return vm_vec_dot(&relative_direction, &warp_orient.vec.fvec) < 0.0f;
|
|
}
|
|
|
|
|
|
//********************-----CLASS: WarpEffect-----********************//
|
|
WarpEffect::WarpEffect(int objnum, WarpDirection direction)
|
|
{
|
|
m_objnum = objnum;
|
|
m_direction = direction;
|
|
|
|
if (m_objnum < 0)
|
|
return;
|
|
auto objp = &Objects[m_objnum];
|
|
|
|
if (objp->type != OBJ_SHIP || objp->instance < 0)
|
|
return;
|
|
auto shipp = &Ships[objp->instance];
|
|
|
|
if (shipp->ship_info_index >= 0)
|
|
{
|
|
//Setup courtesy variables
|
|
m_shipnum = objp->instance;
|
|
m_ship_info_index = shipp->ship_info_index;
|
|
m_warp_params_index = (direction == WarpDirection::WARP_IN) ? shipp->warpin_params_index : shipp->warpout_params_index;
|
|
}
|
|
}
|
|
|
|
bool WarpEffect::isValid() const
|
|
{
|
|
if (m_objnum < 0 || m_shipnum < 0 || m_ship_info_index < 0)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
void WarpEffect::pageIn()
|
|
{
|
|
}
|
|
void WarpEffect::pageOut()
|
|
{
|
|
}
|
|
|
|
int WarpEffect::warpStart()
|
|
{
|
|
if(!this->isValid())
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
int WarpEffect::warpFrame(float /*frametime*/)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
int WarpEffect::warpShipClip(model_render_params * /*render_info*/)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
int WarpEffect::warpShipRender()
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
int WarpEffect::warpEnd()
|
|
{
|
|
if(!this->isValid())
|
|
return 0;
|
|
|
|
if (m_direction == WarpDirection::WARP_IN)
|
|
shipfx_actually_warpin(&Ships[m_shipnum], &Objects[m_objnum]);
|
|
else if (m_direction == WarpDirection::WARP_OUT)
|
|
shipfx_actually_warpout(&Ships[m_shipnum], &Objects[m_objnum]);
|
|
|
|
return 1;
|
|
}
|
|
|
|
int WarpEffect::getWarpPosition(vec3d *output) const
|
|
{
|
|
if(!this->isValid())
|
|
return 0;
|
|
|
|
*output = Objects[m_objnum].pos;
|
|
return 1;
|
|
}
|
|
|
|
int WarpEffect::getWarpOrientation(matrix* output) const
|
|
{
|
|
if(!this->isValid())
|
|
return 0;
|
|
|
|
*output = Objects[m_objnum].orient;
|
|
return 1;
|
|
}
|
|
|
|
//********************-----CLASS: WE_Default-----********************//
|
|
WE_Default::WE_Default(int objnum, WarpDirection direction)
|
|
: WarpEffect(objnum, direction)
|
|
{}
|
|
|
|
int WE_Default::warpStart()
|
|
{
|
|
if (!this->isValid())
|
|
return 0;
|
|
|
|
if (m_direction == WarpDirection::WARP_OUT && m_objnum == OBJ_INDEX(Player_obj))
|
|
{
|
|
HUD_printf(NOX("Subspace drive engaged"));
|
|
}
|
|
|
|
auto objp = &Objects[m_objnum];
|
|
auto shipp = &Ships[objp->instance];
|
|
|
|
// see if we have a valid Knossos device
|
|
if ((m_direction == WarpDirection::WARP_IN) && shipfx_special_warp_objnum_valid(shipp->special_warpin_objnum))
|
|
{
|
|
m_portal_objnum = shipp->special_warpin_objnum;
|
|
}
|
|
else if ((m_direction == WarpDirection::WARP_OUT) && shipfx_special_warp_objnum_valid(shipp->special_warpout_objnum))
|
|
{
|
|
m_portal_objnum = shipp->special_warpout_objnum;
|
|
}
|
|
|
|
// knossos warpout only valid in single player
|
|
if (m_portal_objnum >= 0 && Game_mode & GM_MULTIPLAYER)
|
|
{
|
|
mprintf(("special warpout only for single player\n"));
|
|
m_portal_objnum = -1;
|
|
}
|
|
|
|
auto portal_objp = (m_portal_objnum < 0) ? nullptr : &Objects[m_portal_objnum];
|
|
auto sip = &Ship_info[m_ship_info_index];
|
|
const auto params = &Warp_params[m_warp_params_index];
|
|
|
|
// determine the correct center of the model (which may not be the model's origin)
|
|
if (object_is_docked(objp))
|
|
dock_calc_docked_actual_center(&actual_local_center, objp);
|
|
else
|
|
ship_class_get_actual_center(sip, &actual_local_center);
|
|
|
|
// determine the half-length and the warping distance (which is actually the full length)
|
|
if (object_is_docked(objp))
|
|
{
|
|
// we need to get the longitudinal radius of our ship, so find the semilatus rectum along the Z-axis
|
|
half_length = dock_calc_max_semilatus_rectum_parallel_to_axis(objp, Z_AXIS);
|
|
warping_dist = 2.0f * half_length;
|
|
}
|
|
else
|
|
{
|
|
warping_dist = ship_class_get_length(sip);
|
|
half_length = 0.5f * warping_dist;
|
|
}
|
|
|
|
// determine the warping time
|
|
warping_time = shipfx_calculate_warp_time(objp, m_direction, half_length, warping_dist);
|
|
|
|
// determine the warping speed
|
|
if (m_direction == WarpDirection::WARP_OUT)
|
|
warping_speed = ship_get_warpout_speed(objp, sip, half_length, warping_dist);
|
|
else
|
|
warping_speed = warping_dist / warping_time;
|
|
|
|
// done with initial computation; now set up the warp effect
|
|
|
|
float open_time, close_time;
|
|
if (m_direction == WarpDirection::WARP_IN)
|
|
{
|
|
// first determine the world center in relation to its position
|
|
vm_vec_unrotate(&pos, &actual_local_center, &objp->orient);
|
|
vm_vec_add2(&pos, &objp->pos);
|
|
|
|
// now project the warp effect forward
|
|
vm_vec_scale_add( &pos, &pos, &objp->orient.vec.fvec, half_length );
|
|
|
|
// for arrivals, opening and closing times are both SHIPFX_WARP_DELAY
|
|
open_time = close_time = SHIPFX_WARP_DELAY;
|
|
}
|
|
else
|
|
{
|
|
// for departures, the opening time is the time it takes for the ship to actually approach the warp effect
|
|
// (even if Volition likely intended or assumed that the opening time is always SHIPFX_WARP_DELAY, this is a consequence of the warp effect sequence)
|
|
// and the closing time is SHIPFX_WARP_DELAY as normal
|
|
compute_warpout_stuff(&open_time, &pos);
|
|
close_time = SHIPFX_WARP_DELAY;
|
|
|
|
// turn off warpin physics in case we're jumping out immediately
|
|
objp->phys_info.flags &= ~PF_SUPERCAP_WARP_IN;
|
|
|
|
// maybe turn on warpout physics
|
|
if (params->supercap_warp_physics) {
|
|
objp->phys_info.flags |= PF_SUPERCAP_WARP_OUT;
|
|
}
|
|
}
|
|
|
|
// Effect time is the time to start, warping_time for ship to go thru, and the time to go away.
|
|
float effect_time = open_time + warping_time + close_time;
|
|
|
|
radius = shipfx_calculate_effect_radius(objp, m_direction);
|
|
// cap radius to size of knossos
|
|
if (portal_objp != nullptr)
|
|
{
|
|
// cap radius to size of knossos
|
|
radius = MIN(radius, 0.8f*portal_objp->radius);
|
|
}
|
|
|
|
// select the fireball we use
|
|
int fireball_type = FIREBALL_WARP;
|
|
if ((portal_objp != nullptr) || (params->warp_type == WT_KNOSSOS) || (m_direction == WarpDirection::WARP_OUT && params->warp_type == WT_DEFAULT_THEN_KNOSSOS))
|
|
fireball_type = FIREBALL_KNOSSOS;
|
|
else if (params->warp_type & WT_DEFAULT_WITH_FIREBALL)
|
|
fireball_type = params->warp_type & WT_FLAG_MASK;
|
|
|
|
// create fireball
|
|
int warp_objnum = fireball_create(&pos, fireball_type, FIREBALL_WARP_EFFECT, m_objnum, radius, (m_direction == WarpDirection::WARP_OUT), nullptr, effect_time, m_ship_info_index, nullptr, 0, 0, params->snd_start, params->snd_end, open_time, close_time);
|
|
|
|
//WMC - bail
|
|
// JAS: This must always be created, if not, just warp the ship in/out
|
|
if (warp_objnum < 0)
|
|
{
|
|
this->warpEnd();
|
|
return 0;
|
|
}
|
|
|
|
fvec = Objects[warp_objnum].orient.vec.fvec;
|
|
|
|
stage_time_start = total_time_start = timestamp();
|
|
if (m_direction == WarpDirection::WARP_IN)
|
|
{
|
|
stage_duration[1] = fl2i(open_time*1000.0f);
|
|
stage_duration[2] = fl2i(warping_time*1000.0f);
|
|
stage_time_end = timestamp(stage_duration[1]);
|
|
total_time_end = stage_duration[1] + stage_duration[2];
|
|
shipp->flags.set(Ship::Ship_Flags::Arriving_stage_1);
|
|
// dock leader needs to handle dockees
|
|
if (object_is_docked(objp)) {
|
|
Assertion(shipp->flags[Ship::Ship_Flags::Dock_leader], "The docked ship warping in (%s) should only be the dock leader at this point!\n", shipp->ship_name);
|
|
dock_function_info dfi;
|
|
dock_evaluate_all_docked_objects(objp, &dfi, object_set_arriving_stage1_ndl_flag_helper);
|
|
}
|
|
}
|
|
else if (m_direction == WarpDirection::WARP_OUT)
|
|
{
|
|
shipp->flags.set(Ship::Ship_Flags::Depart_warp);
|
|
|
|
// calculate the time when ship is completely warped out or warped in
|
|
if ( objp == Player_obj ) {
|
|
total_time_end = timestamp(fl2i(effect_time*1000.0f));
|
|
} else {
|
|
total_time_end = timestamp(fl2i((open_time+warping_time)*1000.0f));
|
|
}
|
|
|
|
// This is a hack to make the ship go at the right speed to go from it's current position to the warp_effect_pos;
|
|
|
|
// Set ship's velocity to 'speed'
|
|
// This should actually be an AI that flies from the current
|
|
// position through 'shipp->warp_effect_pos' in 'warp_time'
|
|
// and keeps going
|
|
if ( objp != Player_obj || Player_use_ai ) {
|
|
vec3d vel;
|
|
vel = objp->orient.vec.fvec;
|
|
vm_vec_scale( &vel, warping_speed );
|
|
objp->phys_info.vel = vel;
|
|
objp->phys_info.desired_vel = vel;
|
|
objp->phys_info.prev_ramp_vel.xyz.x = 0.0f;
|
|
objp->phys_info.prev_ramp_vel.xyz.y = 0.0f;
|
|
objp->phys_info.prev_ramp_vel.xyz.z = warping_speed;
|
|
objp->phys_info.linear_thrust.xyz.z = 1.0f; // How much the forward thruster is applied. -1 - 1.
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
int WE_Default::warpFrame(float frametime)
|
|
{
|
|
auto objp = &Objects[m_objnum];
|
|
auto shipp = &Ships[m_shipnum];
|
|
const auto params = &Warp_params[m_warp_params_index];
|
|
|
|
if (m_direction == WarpDirection::WARP_IN)
|
|
{
|
|
if ((shipp->flags[Ship::Ship_Flags::Arriving_stage_1]) && timestamp_elapsed(stage_time_end))
|
|
{
|
|
// let physics know the ship is going to warp in.
|
|
objp->phys_info.flags |= PF_WARP_IN;
|
|
|
|
// done doing stage 1 of warp, so go on to stage 2
|
|
shipp->flags.remove(Ship::Ship_Flags::Arriving_stage_1);
|
|
shipp->flags.set(Ship::Ship_Flags::Arriving_stage_2);
|
|
// if the ship is a dock leader; handle all the dockees
|
|
if (object_is_docked(objp)) {
|
|
Assertion(shipp->flags[Ship::Ship_Flags::Dock_leader], "The docked ship warping in (%s) should only be the dock leader at this point!\n", shipp->ship_name);
|
|
dock_function_info dfi;
|
|
dock_evaluate_all_docked_objects(objp, &dfi, object_remove_arriving_stage1_ndl_flag_helper);
|
|
dock_evaluate_all_docked_objects(objp, &dfi, object_set_arriving_stage2_ndl_flag_helper);
|
|
}
|
|
// Make ship move at velocity so that it moves two radii in warp_time seconds.
|
|
vec3d vel;
|
|
vel = objp->orient.vec.fvec;
|
|
vm_vec_scale( &vel, warping_speed );
|
|
objp->phys_info.vel = vel;
|
|
objp->phys_info.desired_vel = vel;
|
|
objp->phys_info.prev_ramp_vel.xyz.x = 0.0f;
|
|
objp->phys_info.prev_ramp_vel.xyz.y = 0.0f;
|
|
objp->phys_info.prev_ramp_vel.xyz.z = warping_speed;
|
|
objp->phys_info.linear_thrust.xyz.z = 0.0f; // How much the forward thruster is applied. -1 - 1.
|
|
|
|
stage_time_end = timestamp(fl2i(warping_time*1000.0f));
|
|
}
|
|
else if ( (shipp->flags[Ship::Ship_Flags::Arriving_stage_2]) && timestamp_elapsed(stage_time_end) )
|
|
{
|
|
// done doing stage 2 of warp, so turn off arriving flag
|
|
this->warpEnd();
|
|
|
|
// notify physics to slow down
|
|
if (params->supercap_warp_physics) {
|
|
// let physics know this is a special warp in
|
|
objp->phys_info.flags |= PF_SUPERCAP_WARP_IN;
|
|
}
|
|
}
|
|
}
|
|
else if (m_direction == WarpDirection::WARP_OUT)
|
|
{
|
|
vec3d tempv;
|
|
float warp_pos; // position of warp effect in object's frame of reference
|
|
|
|
vm_vec_sub( &tempv, &objp->pos, &pos );
|
|
warp_pos = -vm_vec_dot( &tempv, &fvec );
|
|
|
|
|
|
// Find the closest point on line from center of wormhole
|
|
vec3d cpos;
|
|
|
|
fvi_ray_plane(&cpos, &objp->pos, &fvec, &pos, &fvec, 0.0f );
|
|
|
|
if ( objp == Player_obj ) {
|
|
// Code for player warpout frame
|
|
|
|
if ( (Player->control_mode==PCM_WARPOUT_STAGE2) && (warp_pos > objp->radius) ) {
|
|
gameseq_post_event( GS_EVENT_PLAYER_WARPOUT_DONE_STAGE2 );
|
|
}
|
|
|
|
if ( timestamp_elapsed(total_time_end) ) {
|
|
|
|
// Something went wrong... oh well, warp him out anyway.
|
|
if ( Player->control_mode != PCM_WARPOUT_STAGE3 ) {
|
|
mprintf(( "Hmmm... player ship warpout time elapsed, but he wasn't in warp stage 3.\n" ));
|
|
}
|
|
|
|
this->warpEnd();
|
|
}
|
|
|
|
} else {
|
|
// Code for all non-player ships warpout frame
|
|
|
|
bool timed_out = timestamp_elapsed(total_time_end);
|
|
if ( timed_out ) {
|
|
int delta_ms = timestamp_since(total_time_end);
|
|
if (delta_ms > 1000.0f * frametime ) {
|
|
nprintf(("AI", "Frame %i: Ship %s missed departure cue by %7.3f seconds.\n", Framecount, shipp->ship_name, (float) delta_ms/1000.0f));
|
|
}
|
|
}
|
|
|
|
// MWA 10/21/97 -- added shipp->flags[Ship::Ship_Flags::No_departure_warp] part of next if statement. For ships
|
|
// that don't get a wormhole effect, I wanted to drop into this code immediately.
|
|
if ( (warp_pos > objp->radius) || (shipp->flags[Ship::Ship_Flags::No_departure_warp]) || timed_out ) {
|
|
this->warpEnd();
|
|
}
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
int WE_Default::warpShipClip(model_render_params *render_info)
|
|
{
|
|
if(!this->isValid())
|
|
return 0;
|
|
|
|
render_info->set_clip_plane(pos, fvec);
|
|
|
|
return 1;
|
|
}
|
|
|
|
int WE_Default::warpShipRender()
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
int WE_Default::getWarpPosition(vec3d *output) const
|
|
{
|
|
if(!this->isValid())
|
|
return 0;
|
|
|
|
*output = pos;
|
|
return 1;
|
|
}
|
|
|
|
int WE_Default::getWarpOrientation(matrix* output) const
|
|
{
|
|
if (!this->isValid())
|
|
return 0;
|
|
|
|
auto objp = &Objects[m_objnum];
|
|
|
|
if (this->m_direction == WarpDirection::WARP_IN)
|
|
vm_vector_2_matrix_norm(output, &objp->orient.vec.fvec, nullptr, nullptr);
|
|
else {
|
|
vec3d backwards = objp->orient.vec.fvec;
|
|
vm_vec_negate(&backwards);
|
|
vm_vector_2_matrix_norm(output, &backwards, nullptr, nullptr);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
float shipfx_calculate_arrival_warp_distance(object *objp)
|
|
{
|
|
Assertion(objp != nullptr && objp->type == OBJ_SHIP, "Object parameter to shipfx_calculate_arrival_warp_distance must be a ship!");
|
|
auto shipp = &Ships[objp->instance];
|
|
|
|
// c.f. WE_Default::warpStart()
|
|
float half_length, warping_dist;
|
|
if (object_is_docked(objp))
|
|
{
|
|
half_length = dock_calc_max_semilatus_rectum_parallel_to_axis(objp, Z_AXIS);
|
|
warping_dist = 2.0f * half_length;
|
|
}
|
|
else
|
|
{
|
|
warping_dist = ship_class_get_length(&Ship_info[shipp->ship_info_index]);
|
|
half_length = 0.5f * warping_dist;
|
|
}
|
|
float warping_time = shipfx_calculate_warp_time(objp, WarpDirection::WARP_IN, half_length, warping_dist);
|
|
float warping_speed = warping_dist / warping_time;
|
|
|
|
auto warpin_params = &Warp_params[shipp->warpin_params_index];
|
|
|
|
// the total distance is a full length from its current position, plus the distance it takes to slow down from its warping speed
|
|
float decel_time_const = objp->phys_info.forward_decel_time_const;
|
|
if (warpin_params->supercap_warp_physics) {
|
|
// super cap style warpins are annoying, one time constant while above their max speed, a different one while slowing down from there
|
|
float supercap_slowdown_dist = (warping_speed - (objp->phys_info.max_vel.xyz.z + SUPERCAP_WARP_EXCESS_SPD_THRESHOLD)) * SUPERCAP_WARP_T_CONST;
|
|
float regular_slowdown_dist = (objp->phys_info.max_vel.xyz.z + SUPERCAP_WARP_EXCESS_SPD_THRESHOLD) * decel_time_const;
|
|
|
|
return warping_dist + supercap_slowdown_dist + regular_slowdown_dist;
|
|
} else {
|
|
return warping_dist + warping_speed * decel_time_const;
|
|
}
|
|
}
|
|
|
|
//********************-----CLASS: WE_BSG-----********************//
|
|
WE_BSG::WE_BSG(int objnum, WarpDirection direction)
|
|
: WarpEffect(objnum, direction)
|
|
{
|
|
//Zero animation and such
|
|
anim = shockwave = -1;
|
|
anim_fps = shockwave_fps = 0;
|
|
anim_nframes = shockwave_nframes = 0;
|
|
anim_total_time = shockwave_total_time = 0;
|
|
|
|
if (!isValid())
|
|
return;
|
|
auto objp = &Objects[m_objnum];
|
|
auto sip = &Ship_info[m_ship_info_index];
|
|
const auto params = &Warp_params[m_warp_params_index];
|
|
|
|
//Setup anim name
|
|
char tmp_name[MAX_FILENAME_LEN];
|
|
memset(tmp_name, 0, MAX_FILENAME_LEN);
|
|
strcpy_s(tmp_name, params->anim);
|
|
strlwr(tmp_name);
|
|
|
|
if(strlen(tmp_name))
|
|
{
|
|
//Load anim
|
|
anim = bm_load_either(tmp_name, &anim_nframes, &anim_fps, NULL, true);
|
|
if(anim > -1)
|
|
{
|
|
anim_total_time = fl2i(((float)anim_nframes / (float)anim_fps) * 1000.0f);
|
|
}
|
|
|
|
//Setup shockwave name
|
|
strncat(tmp_name, "-shockwave", MAX_FILENAME_LEN-1);
|
|
|
|
//Load shockwave
|
|
shockwave = bm_load_either(tmp_name, &shockwave_nframes, &shockwave_fps, NULL, true);
|
|
if(shockwave > -1)
|
|
{
|
|
shockwave_total_time = fl2i(((float)shockwave_nframes / (float)shockwave_fps) * 1000.0f);
|
|
}
|
|
}
|
|
//Set radius
|
|
tube_radius = 0.0f;
|
|
shockwave_radius = 0.0f;
|
|
|
|
//Use the warp radius for shockwave radius, not tube radius
|
|
shockwave_radius = params->radius;
|
|
|
|
polymodel *pm = model_get(sip->model_num);
|
|
if(pm == NULL)
|
|
{
|
|
autocenter = vmd_zero_vector;
|
|
z_offset_max = objp->radius;
|
|
z_offset_min = -objp->radius;
|
|
|
|
if(tube_radius <= 0.0f)
|
|
tube_radius = objp->radius;
|
|
|
|
if(shockwave_radius <= 0.0f)
|
|
shockwave_radius = objp->radius;
|
|
}
|
|
else
|
|
{
|
|
//Autogenerate everything from ship dimensions
|
|
if(tube_radius <= 0.0f)
|
|
tube_radius = MAX((pm->maxs.xyz.y - pm->mins.xyz.y), (pm->maxs.xyz.x - pm->mins.xyz.x))/2.0f;
|
|
autocenter = pm->autocenter;
|
|
z_offset_max = pm->maxs.xyz.z - pm->autocenter.xyz.z;
|
|
z_offset_min = pm->mins.xyz.z - pm->autocenter.xyz.z;
|
|
|
|
if (shockwave_radius <= 0.0f)
|
|
shockwave_radius = z_offset_max - z_offset_min;
|
|
}
|
|
|
|
//*****Timing
|
|
stage = -1;
|
|
|
|
stage_duration[0] = params->time;
|
|
stage_duration[1] = MAX(anim_total_time - params->time, shockwave_total_time);
|
|
|
|
stage_time_start = stage_time_end = total_time_start = total_time_end = timestamp();
|
|
|
|
//*****Sound
|
|
snd_range_factor = 1.0f;
|
|
snd_start = snd_end = sound_handle::invalid();
|
|
snd_start_gs = snd_end_gs = NULL;
|
|
|
|
//*****Instance
|
|
pos = vmd_zero_vector;
|
|
}
|
|
|
|
WE_BSG::~WE_BSG()
|
|
{
|
|
if(anim > -1)
|
|
bm_unload(anim);
|
|
if(shockwave > -1)
|
|
bm_unload(shockwave);
|
|
}
|
|
|
|
void WE_BSG::pageIn()
|
|
{
|
|
if(anim > -1)
|
|
bm_page_in_texture(anim);
|
|
if(shockwave > -1)
|
|
bm_page_in_texture(shockwave);
|
|
}
|
|
|
|
int WE_BSG::warpStart()
|
|
{
|
|
if(!WarpEffect::warpStart())
|
|
return 0;
|
|
|
|
//WMC - bail
|
|
if (anim < 0 && shockwave < 0)
|
|
{
|
|
this->warpEnd();
|
|
return 0;
|
|
}
|
|
|
|
auto objp = &Objects[m_objnum];
|
|
auto shipp = &Ships[m_shipnum];
|
|
const auto params = &Warp_params[m_warp_params_index];
|
|
|
|
//WMC - If object is docked now, update data:
|
|
if(object_is_docked(objp))
|
|
{
|
|
z_offset_max = dock_calc_max_semilatus_rectum_parallel_to_axis(objp, Z_AXIS);
|
|
z_offset_min = -z_offset_max;
|
|
if(tube_radius <= 0.0f)
|
|
{
|
|
float x_radius = dock_calc_max_semilatus_rectum_parallel_to_axis(objp, X_AXIS);
|
|
float y_radius = dock_calc_max_semilatus_rectum_parallel_to_axis(objp, Y_AXIS);
|
|
tube_radius = MAX(x_radius, y_radius);
|
|
}
|
|
|
|
shockwave_radius = z_offset_max - z_offset_min;
|
|
|
|
dock_calc_docked_actual_center(&autocenter, objp);
|
|
}
|
|
|
|
if (m_direction == WarpDirection::WARP_IN)
|
|
{
|
|
shipp->flags.set(Ship::Ship_Flags::Arriving_stage_1);
|
|
// dock leader needs to handle dockees
|
|
if (object_is_docked(objp)) {
|
|
Assertion(shipp->flags[Ship::Ship_Flags::Dock_leader], "The ship warping in (%s) must be the dock leader at this point!\n", shipp->ship_name);
|
|
dock_function_info dfi;
|
|
dock_evaluate_all_docked_objects(objp, &dfi, object_set_arriving_stage1_ndl_flag_helper);
|
|
}
|
|
}
|
|
else
|
|
shipp->flags.set(Ship::Ship_Flags::Depart_warp);
|
|
|
|
//*****Sound
|
|
if(params->snd_start.isValid())
|
|
{
|
|
snd_start_gs = gamesnd_get_game_sound(params->snd_start);
|
|
snd_start = snd_play_3d(snd_start_gs, &objp->pos, &View_position, 0.0f, NULL, 0, 1, SND_PRIORITY_SINGLE_INSTANCE, NULL, snd_range_factor);
|
|
}
|
|
if(params->snd_end.isValid())
|
|
{
|
|
snd_end_gs = gamesnd_get_game_sound(params->snd_end);
|
|
snd_end = sound_handle::invalid();
|
|
}
|
|
|
|
stage = 0;
|
|
int total_duration = 0;
|
|
for(int i = 0; i < WE_BSG_NUM_STAGES; i++)
|
|
total_duration += stage_duration[i];
|
|
|
|
total_time_start = timestamp();
|
|
total_time_end = timestamp(total_duration);
|
|
|
|
stage_time_start = total_time_start;
|
|
stage_time_end = timestamp(stage_duration[stage]);
|
|
|
|
return 1;
|
|
}
|
|
|
|
int WE_BSG::warpFrame(float /*frametime*/)
|
|
{
|
|
if(!this->isValid())
|
|
return 0;
|
|
|
|
auto objp = &Objects[m_objnum];
|
|
auto shipp = &Ships[m_shipnum];
|
|
|
|
while( timestamp_elapsed(stage_time_end ))
|
|
{
|
|
stage++;
|
|
if(stage < WE_BSG_NUM_STAGES)
|
|
{
|
|
stage_time_start = timestamp();
|
|
stage_time_end = timestamp(stage_duration[stage]);
|
|
}
|
|
switch(stage)
|
|
{
|
|
case 1:
|
|
if (m_direction == WarpDirection::WARP_IN)
|
|
{
|
|
shipp->flags.remove(Ship::Ship_Flags::Arriving_stage_1);
|
|
shipp->flags.set(Ship::Ship_Flags::Arriving_stage_2);
|
|
// dock leader needs to handle dockees
|
|
if (object_is_docked(objp)) {
|
|
Assertion(shipp->flags[Ship::Ship_Flags::Dock_leader], "The ship warping in (%s) must be the dock leader at this point!\n", shipp->ship_name);
|
|
dock_function_info dfi;
|
|
dock_evaluate_all_docked_objects(objp, &dfi, object_remove_arriving_stage1_ndl_flag_helper);
|
|
dock_evaluate_all_docked_objects(objp, &dfi, object_set_arriving_stage2_ndl_flag_helper);
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
this->warpEnd();
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
switch(stage)
|
|
{
|
|
case 0:
|
|
case 1:
|
|
vm_vec_unrotate(&pos, &autocenter, &objp->orient);
|
|
vm_vec_add2(&pos, &objp->pos);
|
|
break;
|
|
default:
|
|
this->warpEnd();
|
|
return 0;
|
|
}
|
|
|
|
if (snd_start.isValid())
|
|
snd_update_3d_pos(snd_start, snd_start_gs, &objp->pos, 0.0f, snd_range_factor);
|
|
|
|
return 1;
|
|
}
|
|
|
|
int WE_BSG::warpShipClip(model_render_params *render_info)
|
|
{
|
|
if(!this->isValid())
|
|
return 0;
|
|
|
|
auto objp = &Objects[m_objnum];
|
|
|
|
if (m_direction == WarpDirection::WARP_OUT && stage > 0)
|
|
{
|
|
vec3d position;
|
|
vm_vec_scale_add(&position, &objp->pos, &objp->orient.vec.fvec, objp->radius);
|
|
|
|
render_info->set_clip_plane(position, objp->orient.vec.fvec);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
int WE_BSG::warpShipRender()
|
|
{
|
|
if(!this->isValid())
|
|
return 0;
|
|
|
|
if(anim < 0 && shockwave < 0)
|
|
return 0;
|
|
|
|
auto objp = &Objects[m_objnum];
|
|
|
|
// SUSHI: Turning off Zbuffering results in the FTL effect showing up through ship hulls.
|
|
// The effect is slightly degraded by leaving it on, but ATM it's worth the tradeoff.
|
|
// turn off zbuffering
|
|
//int saved_zbuffer_mode = gr_zbuffer_get();
|
|
//gr_zbuffer_set(GR_ZBUFF_NONE);
|
|
|
|
if(anim > -1)
|
|
{
|
|
//Figure out which frame we're on
|
|
int anim_frame = fl2i( ((float)(timestamp() - total_time_start)/1000.0f) * (float)anim_fps);
|
|
|
|
if ( anim_frame < anim_nframes )
|
|
{
|
|
//Set the correct frame
|
|
//gr_set_bitmap(anim + anim_frame, GR_ALPHABLEND_FILTER, GR_BITBLT_MODE_NORMAL, 1.0f);
|
|
|
|
//Do warpout geometry
|
|
vec3d start, end;
|
|
vm_vec_scale_add(&start, &pos, &objp->orient.vec.fvec, z_offset_min);
|
|
vm_vec_scale_add(&end, &pos, &objp->orient.vec.fvec, z_offset_max);
|
|
|
|
//Render the warpout effect
|
|
//batch_add_beam(anim + anim_frame, TMAP_FLAG_GOURAUD | TMAP_FLAG_RGB | TMAP_FLAG_TEXTURED | TMAP_FLAG_CORRECT | TMAP_HTL_3D_UNLIT, &start, &end, tube_radius*2.0f, 1.0f);
|
|
batching_add_beam(anim + anim_frame, &start, &end, tube_radius * 2.0f, 1.0f);
|
|
}
|
|
}
|
|
|
|
if(stage == 1 && shockwave > -1)
|
|
{
|
|
int shockwave_frame = fl2i( ((float)(timestamp() - stage_time_start)/1000.0f) * (float)shockwave_fps);
|
|
|
|
if(shockwave_frame < shockwave_nframes)
|
|
{
|
|
vertex p;
|
|
|
|
memset(&p, 0, sizeof(p));
|
|
|
|
g3_transfer_vertex(&p, &pos);
|
|
|
|
batching_add_volume_bitmap(shockwave + shockwave_frame, &p, 0, shockwave_radius, 1.0f);
|
|
}
|
|
}
|
|
|
|
// restore zbuffer mode
|
|
//gr_zbuffer_set(saved_zbuffer_mode);
|
|
return 1;
|
|
}
|
|
|
|
int WE_BSG::warpEnd()
|
|
{
|
|
if (snd_start.isValid())
|
|
snd_stop(snd_start);
|
|
if (snd_end_gs != nullptr && m_objnum >= 0)
|
|
snd_end = snd_play_3d(snd_end_gs, &Objects[m_objnum].pos, &View_position, 0.0f, nullptr, 0, 1.0f, SND_PRIORITY_SINGLE_INSTANCE, nullptr, snd_range_factor);
|
|
|
|
return WarpEffect::warpEnd();
|
|
}
|
|
|
|
//WMC - These two functions are used to fool collision detection code
|
|
//And do player warpout
|
|
int WE_BSG::getWarpPosition(vec3d *output) const
|
|
{
|
|
if (!this->isValid())
|
|
return 0;
|
|
|
|
auto objp = &Objects[m_objnum];
|
|
|
|
vec3d position;
|
|
vm_vec_scale_add(&position, &objp->pos, &objp->orient.vec.fvec, objp->radius);
|
|
|
|
*output = position;
|
|
return 1;
|
|
}
|
|
|
|
int WE_BSG::getWarpOrientation(matrix* output) const
|
|
{
|
|
if (!this->isValid())
|
|
return 0;
|
|
|
|
auto objp = &Objects[m_objnum];
|
|
|
|
vm_vector_2_matrix_norm(output, &objp->orient.vec.fvec, nullptr, nullptr);
|
|
return 1;
|
|
}
|
|
|
|
//********************-----CLASS: WE_Homeworld-----********************//
|
|
const float HOMEWORLD_SWEEPER_LINE_THICKNESS = 0.002f; // How tall the initial and final hyperspace "lines" are, as a factor of height
|
|
WE_Homeworld::WE_Homeworld(int objnum, WarpDirection direction)
|
|
: WarpEffect(objnum, direction)
|
|
{
|
|
if(!this->isValid())
|
|
return;
|
|
|
|
auto objp = &Objects[m_objnum];
|
|
auto sip = &Ship_info[m_ship_info_index];
|
|
const auto params = &Warp_params[m_warp_params_index];
|
|
|
|
//Stage and time
|
|
stage = 0;
|
|
stage_time_start = stage_time_end = timestamp();
|
|
|
|
//Stage duration presets
|
|
stage_duration[0] = 0;
|
|
stage_duration[1] = 1200;
|
|
stage_duration[2] = 1600;
|
|
stage_duration[3] = -1;
|
|
stage_duration[4] = 1600;
|
|
stage_duration[5] = 1200;
|
|
|
|
//Configure stage duration 3
|
|
stage_duration[3] = params->time - (stage_duration[1] + stage_duration[2] + stage_duration[4] + stage_duration[5]);
|
|
if (stage_duration[3] <= 0)
|
|
stage_duration[3] = 3400; // set for a 9 second total time
|
|
|
|
//Anim
|
|
anim = bm_load_either(params->anim, &anim_nframes, &anim_fps, nullptr, true);
|
|
|
|
pos = vmd_zero_vector;
|
|
fvec = vmd_zero_vector;
|
|
|
|
polymodel *pm = model_get(sip->model_num);
|
|
width_full = params->radius;
|
|
if(pm != nullptr)
|
|
{
|
|
if(width_full <= 0.0f)
|
|
{
|
|
width_full = pm->maxs.xyz.x - pm->mins.xyz.x;
|
|
height_full = pm->maxs.xyz.y - pm->mins.xyz.y;
|
|
z_offset_max = pm->maxs.xyz.z;
|
|
z_offset_min = pm->mins.xyz.z;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(width_full <= 0.0f)
|
|
{
|
|
width_full = 2.0f*objp->radius;
|
|
}
|
|
height_full = width_full;
|
|
z_offset_max = objp->radius;
|
|
z_offset_min = -objp->radius;
|
|
}
|
|
//WMC - This scales up or down the sound depending on ship size, with ~100m diameter ship as base
|
|
//REMEMBER: Radius != diameter
|
|
snd_range_factor = sqrt(width_full*width_full+height_full*height_full)/141.421356f;
|
|
|
|
if(width_full <= 0.0f)
|
|
width_full = 1.0f;
|
|
if(height_full <= 0.0f)
|
|
height_full = 1.0f;
|
|
width = width_full;
|
|
height = 0.0f;
|
|
|
|
//Sound
|
|
snd = sound_handle::invalid();
|
|
snd_gs = NULL;
|
|
}
|
|
|
|
WE_Homeworld::~WE_Homeworld()
|
|
{
|
|
if(anim > -1)
|
|
bm_unload(anim);
|
|
}
|
|
|
|
int WE_Homeworld::warpStart()
|
|
{
|
|
if(!this->isValid())
|
|
return 0;
|
|
|
|
if(anim < 0)
|
|
{
|
|
this->warpEnd();
|
|
return 0;
|
|
}
|
|
|
|
auto objp = &Objects[m_objnum];
|
|
auto shipp = &Ships[m_shipnum];
|
|
const auto params = &Warp_params[m_warp_params_index];
|
|
|
|
stage = 1;
|
|
total_time_start = timestamp();
|
|
total_time_end = 0;
|
|
for(int i = 0; i < WE_HOMEWORLD_NUM_STAGES; i++)
|
|
{
|
|
total_time_end += stage_duration[i];
|
|
}
|
|
stage_time_start = total_time_start;
|
|
stage_time_end = timestamp(stage_duration[stage]);
|
|
|
|
//Position
|
|
vm_vec_scale_add(&pos, &objp->pos, &objp->orient.vec.fvec, z_offset_max);
|
|
fvec = objp->orient.vec.fvec;
|
|
if (m_direction == WarpDirection::WARP_OUT)
|
|
vm_vec_negate(&fvec);
|
|
|
|
width = 0.f;
|
|
height = height_full * HOMEWORLD_SWEEPER_LINE_THICKNESS;
|
|
|
|
if (m_direction == WarpDirection::WARP_IN)
|
|
{
|
|
shipp->flags.set(Ship::Ship_Flags::Arriving_stage_1);
|
|
// dock leader needs to handle dockees
|
|
if (object_is_docked(objp)) {
|
|
Assertion(shipp->flags[Ship::Ship_Flags::Dock_leader], "The ship warping in (%s) must be the dock leader at this point!\n", shipp->ship_name);
|
|
dock_function_info dfi;
|
|
dock_evaluate_all_docked_objects(objp, &dfi, object_set_arriving_stage1_ndl_flag_helper);
|
|
}
|
|
}
|
|
else if (m_direction == WarpDirection::WARP_OUT)
|
|
{
|
|
shipp->flags.set(Ship::Ship_Flags::Depart_warp);
|
|
}
|
|
else
|
|
{
|
|
this->warpEnd();
|
|
return 0;
|
|
}
|
|
|
|
if(params->snd_start.isValid())
|
|
{
|
|
snd_gs = gamesnd_get_game_sound(params->snd_start);
|
|
snd = snd_play_3d(snd_gs, &pos, &View_position, 0.0f, NULL, 0, 1, SND_PRIORITY_SINGLE_INSTANCE, NULL, snd_range_factor);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
int WE_Homeworld::warpFrame(float /*frametime*/)
|
|
{
|
|
if(!this->isValid())
|
|
return 0;
|
|
|
|
auto objp = &Objects[m_objnum];
|
|
auto shipp = &Ships[m_shipnum];
|
|
|
|
//Setup stage
|
|
while( timestamp_elapsed(stage_time_end ))
|
|
{
|
|
stage++;
|
|
if(stage < WE_HOMEWORLD_NUM_STAGES)
|
|
{
|
|
stage_time_start = timestamp();
|
|
stage_time_end = timestamp(stage_duration[stage]);
|
|
}
|
|
switch(stage)
|
|
{
|
|
case 2:
|
|
break;
|
|
case 3:
|
|
if (m_direction == WarpDirection::WARP_IN)
|
|
{
|
|
objp->phys_info.flags |= PF_WARP_IN;
|
|
shipp->flags.remove(Ship::Ship_Flags::Arriving_stage_1);
|
|
shipp->flags.set(Ship::Ship_Flags::Arriving_stage_2);
|
|
// dock leader needs to handle dockees
|
|
if (object_is_docked(objp)) {
|
|
Assertion(shipp->flags[Ship::Ship_Flags::Dock_leader], "The ship warping in (%s) must be the dock leader at this point!\n", shipp->ship_name);
|
|
dock_function_info dfi;
|
|
dock_evaluate_all_docked_objects(objp, &dfi, object_remove_arriving_stage1_ndl_flag_helper);
|
|
dock_evaluate_all_docked_objects(objp, &dfi, object_set_arriving_stage2_ndl_flag_helper);
|
|
}
|
|
}
|
|
|
|
break;
|
|
case 4:
|
|
break;
|
|
case 5:
|
|
break;
|
|
default:
|
|
this->warpEnd();
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
//Process stage
|
|
float sweeper_z_position;
|
|
|
|
float progress = ((float)timestamp() - (float)stage_time_start) / ((float)stage_time_end - (float)stage_time_start);
|
|
float adjusted_progress = progress * 1.2f - 0.1f; // we'll let the progress "overflow" a bit and then clamp it, so the sweeper
|
|
CLAMP(adjusted_progress, 0.f, 1.f); // lingers for a moment at the beginning and end of the grow and shrink stages
|
|
float visual_progress = ((3.f - 2 * adjusted_progress) * adjusted_progress * adjusted_progress); // good ol' cubic easing function
|
|
|
|
switch(stage)
|
|
{
|
|
case 1:
|
|
sweeper_z_position = z_offset_max;
|
|
width = width_full * visual_progress;
|
|
break;
|
|
case 2:
|
|
sweeper_z_position = z_offset_max;
|
|
height = (height_full * visual_progress) + height_full * HOMEWORLD_SWEEPER_LINE_THICKNESS;
|
|
break;
|
|
case 3:
|
|
sweeper_z_position = z_offset_max - adjusted_progress * (z_offset_max - z_offset_min);
|
|
break;
|
|
case 4:
|
|
sweeper_z_position = z_offset_min;
|
|
height = (height_full * (1.f - visual_progress)) + height_full * HOMEWORLD_SWEEPER_LINE_THICKNESS;
|
|
break;
|
|
case 5:
|
|
sweeper_z_position = z_offset_min;
|
|
width = width_full * (1.f - visual_progress);
|
|
break;
|
|
default:
|
|
this->warpEnd();
|
|
return 0;
|
|
}
|
|
|
|
//update sweeper position
|
|
vm_vec_scale_add(&pos, &objp->pos, &objp->orient.vec.fvec, sweeper_z_position);
|
|
|
|
//Update sound
|
|
if (snd.isValid())
|
|
snd_update_3d_pos(snd, snd_gs, &pos, 0.0f, snd_range_factor);
|
|
|
|
return 1;
|
|
}
|
|
|
|
int WE_Homeworld::warpShipClip(model_render_params *render_info)
|
|
{
|
|
if(!this->isValid())
|
|
return 0;
|
|
|
|
render_info->set_clip_plane(pos, fvec);
|
|
return 1;
|
|
}
|
|
|
|
int WE_Homeworld::warpShipRender()
|
|
{
|
|
if(!this->isValid())
|
|
return 0;
|
|
|
|
int frame = 0;
|
|
if(anim_fps > 0)
|
|
frame = fl2i( (int)(((float)(timestamp() - (float)total_time_start)/1000.0f) * (float)anim_fps) % anim_nframes);
|
|
|
|
//Set the correct frame
|
|
batching_add_polygon(anim + frame, &pos, &Objects[m_objnum].orient, width, height);
|
|
|
|
return 1;
|
|
}
|
|
|
|
int WE_Homeworld::warpEnd()
|
|
{
|
|
if (snd.isValid())
|
|
snd_stop(snd);
|
|
return WarpEffect::warpEnd();
|
|
}
|
|
|
|
int WE_Homeworld::getWarpPosition(vec3d *output) const
|
|
{
|
|
if(!this->isValid())
|
|
return 0;
|
|
|
|
*output = pos;
|
|
return 1;
|
|
}
|
|
|
|
int WE_Homeworld::getWarpOrientation(matrix* output) const
|
|
{
|
|
if (!this->isValid())
|
|
return 0;
|
|
|
|
auto objp = &Objects[m_objnum];
|
|
|
|
if (this->m_direction == WarpDirection::WARP_IN)
|
|
*output = objp->orient;
|
|
else {
|
|
vec3d backwards = objp->orient.vec.fvec;
|
|
vm_vec_negate(&backwards);
|
|
vm_vector_2_matrix_norm(output, &backwards, &objp->orient.vec.uvec, nullptr);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
//********************-----CLASS: WE_Hyperspace----********************//
|
|
WE_Hyperspace::WE_Hyperspace(int objnum, WarpDirection direction)
|
|
: WarpEffect(objnum, direction)
|
|
{
|
|
if (!isValid())
|
|
return;
|
|
auto objp = &Objects[m_objnum];
|
|
const auto params = &Warp_params[m_warp_params_index];
|
|
|
|
total_duration = params->time;
|
|
if (total_duration <= 0)
|
|
total_duration = 1000;
|
|
|
|
accel_or_decel_exp = params->accel_exp;
|
|
|
|
total_time_start = total_time_end = timestamp();
|
|
pos_final = vmd_zero_vector;
|
|
scale_factor = 750.0f * objp->radius;
|
|
|
|
initial_velocity = 1.0f;
|
|
|
|
//*****Sound
|
|
snd_range_factor = 1.0f * objp->radius;
|
|
snd_start = snd_end = sound_handle::invalid();
|
|
snd_start_gs = snd_end_gs = nullptr;
|
|
}
|
|
|
|
int WE_Hyperspace::warpStart()
|
|
{
|
|
if(!this->isValid())
|
|
return 0;
|
|
|
|
auto objp = &Objects[m_objnum];
|
|
auto shipp = &Ships[m_shipnum];
|
|
auto sip = &Ship_info[m_ship_info_index];
|
|
const auto params = &Warp_params[m_warp_params_index];
|
|
|
|
total_time_start = timestamp();
|
|
total_time_end = timestamp(total_duration);
|
|
|
|
if (m_direction == WarpDirection::WARP_IN)
|
|
{
|
|
auto ship_entry = ship_registry_get(shipp->ship_name);
|
|
if (ship_entry && ship_entry->has_p_objp()) {
|
|
initial_velocity = ship_entry->p_objp()->initial_velocity * sip->max_speed / 100.0f;
|
|
}
|
|
|
|
shipp->flags.set(Ship::Ship_Flags::Arriving_stage_1);
|
|
// dock leader needs to handle dockees
|
|
if (object_is_docked(objp)) {
|
|
Assertion(shipp->flags[Ship::Ship_Flags::Dock_leader], "The ship warping in (%s) must be the dock leader at this point!\n", shipp->ship_name);
|
|
dock_function_info dfi;
|
|
dock_evaluate_all_docked_objects(objp, &dfi, object_set_arriving_stage1_ndl_flag_helper);
|
|
// docked objects use speed to find the object that controls movement; therefore the warping in dock leader must have the highest speed!
|
|
objp->phys_info.speed = (scale_factor / params->time)*1000.0f;
|
|
}
|
|
objp->phys_info.flags |= PF_WARP_IN;
|
|
objp->flags.remove(Object::Object_Flags::Physics);
|
|
}
|
|
else if (m_direction == WarpDirection::WARP_OUT)
|
|
{
|
|
// wookieejedi - if the ship is already in the mission the initial_velocity for warpout should be the ship's current speed
|
|
initial_velocity = objp->phys_info.fspeed;
|
|
shipp->flags.set(Ship::Ship_Flags::Depart_warp);
|
|
}
|
|
else
|
|
{
|
|
this->warpEnd();
|
|
}
|
|
|
|
pos_final = objp->pos;
|
|
|
|
// Cyborg17 - After setting pos_final, we should move the ship to the actual starting position.
|
|
vm_vec_scale_add(&objp->pos, &pos_final, &objp->orient.vec.fvec, -scale_factor);
|
|
|
|
if (params->snd_start.isValid())
|
|
{
|
|
snd_start_gs = gamesnd_get_game_sound(params->snd_start);
|
|
snd_start = snd_play_3d(snd_start_gs, &pos_final, &View_position, 0.0f, nullptr, 0, 1, SND_PRIORITY_SINGLE_INSTANCE, nullptr, snd_range_factor);
|
|
}
|
|
if (params->snd_end.isValid())
|
|
{
|
|
snd_end_gs = gamesnd_get_game_sound(params->snd_end);
|
|
snd_end = sound_handle::invalid();
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
int WE_Hyperspace::warpFrame(float /*frametime*/)
|
|
{
|
|
if(!this->isValid())
|
|
return 0;
|
|
|
|
auto objp = &Objects[m_objnum];
|
|
|
|
if(timestamp_elapsed(total_time_end))
|
|
{
|
|
objp->pos = pos_final;
|
|
objp->flags.set(Object::Object_Flags::Physics);
|
|
this->warpEnd();
|
|
}
|
|
else
|
|
{
|
|
// How far along in the effect we are, in range of 0.0..1.0.
|
|
float progress = ((float)timestamp() - (float)total_time_start)/(float)total_duration;
|
|
float scale = 0.0f;
|
|
if (m_direction == WarpDirection::WARP_IN)
|
|
{
|
|
scale = scale_factor*(1.0f-pow((1.0f-progress), accel_or_decel_exp))-scale_factor;
|
|
|
|
// Makes sure that the velocity won't drop below the ship's initial
|
|
// velocity during the warpin. Ideally it should be done more
|
|
// smoothly than this.
|
|
scale = MIN(scale, (initial_velocity * (total_duration / 1000) * -(1.0f - progress)));
|
|
}
|
|
else
|
|
{
|
|
scale = scale_factor*pow(progress, accel_or_decel_exp);
|
|
|
|
// Makes sure the warpout velocity won't drop below the ship's last
|
|
// known real velocity.
|
|
scale += initial_velocity * (total_duration / 1000) * progress;
|
|
}
|
|
vm_vec_scale_add(&objp->pos, &pos_final, &objp->orient.vec.fvec, scale);
|
|
}
|
|
|
|
if (snd_start.isValid())
|
|
snd_update_3d_pos(snd_start, snd_start_gs, &pos_final, 0.0f, snd_range_factor);
|
|
|
|
return 1;
|
|
}
|
|
int WE_Hyperspace::warpEnd()
|
|
{
|
|
if (snd_start.isValid())
|
|
snd_stop(snd_start);
|
|
if(snd_end_gs != nullptr && m_objnum >= 0)
|
|
snd_end = snd_play_3d(snd_end_gs, &Objects[m_objnum].pos, &View_position, 0.0f, nullptr, 0, 1.0f, SND_PRIORITY_SINGLE_INSTANCE, nullptr, snd_range_factor);
|
|
|
|
return WarpEffect::warpEnd();
|
|
}
|