2 * Copyright (C) Volition, Inc. 1999. All rights reserved.
4 * All source code herein is the property of Volition, Inc. You may not sell
5 * or otherwise commercially exploit the source or things you created based on
10 * $Logfile: /Freespace2/code/Ship/ShipFX.cpp $
15 * Routines for ship effects (as in special)
18 * Revision 1.6 2003/05/25 02:30:44 taylor
21 * Revision 1.5 2002/09/04 01:12:11 relnev
22 * changes to screen backup/mouse drawing code. removed a few warnings.
24 * Revision 1.4 2002/06/17 06:33:11 relnev
25 * ryan's struct patch for gcc 2.95
27 * Revision 1.3 2002/06/09 04:41:26 relnev
28 * added copyright header
30 * Revision 1.2 2002/05/07 03:16:52 theoddone33
31 * The Great Newline Fix
33 * Revision 1.1.1.1 2002/05/03 03:28:10 root
37 * 51 9/13/99 4:53p Andsager
39 * 50 9/13/99 10:09a Andsager
40 * Add debug console commands to lower model render detail and fireball
41 * LOD for big ship explosiosns.
43 * 49 9/08/99 10:44p Andsager
44 * Make HUGE ships not die when warping out, after warp effect started.
46 * 48 9/06/99 10:16p Andsager
47 * Modify big ship explosions. Less frequent fireballs, fewer particles,
50 * 47 9/05/99 11:10a Andsager
51 * Fix up which split ship get debris pieces. Dont render debris pieces
52 * for split ships until they are near clip plane.
54 * 46 9/02/99 12:55a Mikek
55 * Scale engine wash by speed.
57 * 45 9/01/99 10:15a Dave
59 * 44 8/31/99 10:13p Andsager
60 * Add Knossos warp effect fireball
62 * 43 8/23/99 11:09a Andsager
63 * Round 2 of Knossos explosion
65 * 42 8/20/99 2:16p Andsager
66 * Make only supercaps slow down extra fast after warping in.
68 * 41 8/20/99 1:42p Andsager
69 * Frist pass on Knossos explosion.
71 * 40 8/19/99 4:36p Andsager
72 * Cleaned up special_warpout
74 * 39 8/16/99 10:04p Andsager
75 * Add special-warp-dist and special-warpout-name sexp for Knossos device
78 * 38 8/16/99 2:01p Andsager
79 * Knossos warp-in warp-out.
81 * 37 8/13/99 10:49a Andsager
82 * Knossos and HUGE ship warp out. HUGE ship warp in. Stealth search
83 * modes dont collide big ships.
85 * 36 8/05/99 2:06a Dave
88 * 35 7/18/99 12:32p Dave
89 * Randomly oriented shockwaves.
91 * 34 7/09/99 12:52a Andsager
92 * Modify engine wash (1) less damage (2) only at a closer range (3) no
93 * damage when engine is disabled
95 * 33 7/06/99 10:45a Andsager
96 * Modify engine wash to work on any ship that is not small. Add AWACS
99 * 32 7/02/99 9:55p Dave
100 * Player engine wash sound.
102 * 31 7/02/99 4:31p Dave
103 * Much more sophisticated lightning support.
105 * 30 7/01/99 4:23p Dave
106 * Full support for multiple linked ambient engine sounds. Added "big
109 * 29 7/01/99 11:44a Dave
110 * Updated object sound system to allow multiple obj sounds per ship.
111 * Added hit-by-beam sound. Added killed by beam sound.
113 * 28 6/17/99 12:06p Andsager
114 * Add a fireball for each live debris piece.
116 * 27 6/14/99 3:21p Andsager
117 * Allow collisions between ship and its debris. Fix up collision pairs
118 * when large ship is warping out.
120 * 26 5/27/99 12:14p Andsager
121 * Some fixes for live debris when more than one subsys on ship with live
122 * debris. Set subsys strength (when 0) blows off subsystem.
123 * sexp_hits_left_subsystem works for SUBSYSTEM_UNKNOWN.
125 * 25 5/26/99 11:46a Dave
126 * Added ship-blasting lighting and made the randomization of lighting
127 * much more customizable.
129 * 24 5/25/99 10:05a Andsager
130 * Fix assert with wing warping out with no warp effect.
132 * 23 5/24/99 5:45p Dave
133 * Added detail levels to the nebula, with a decent speedup. Split nebula
134 * lightning into its own section.
136 * 22 5/21/99 5:03p Andsager
137 * Add code to display engine wash death. Modify ship_kill_packet
139 * 21 5/19/99 11:09a Andsager
140 * Turn on engine wash. Check every 1/4 sec.
142 * 20 5/18/99 1:30p Dave
143 * Added muzzle flash table stuff.
145 * 19 5/17/99 10:33a Dave
148 * 18 5/11/99 10:16p Andsager
149 * First pass on engine wash effect. Rotation (control input), damage,
152 * 17 5/09/99 6:00p Dave
153 * Lots of cool new effects. E3 build tweaks.
155 * 16 4/28/99 11:13p Dave
156 * Temporary checkin of artillery code.
158 * 15 3/29/99 6:17p Dave
159 * More work on demo system. Got just about everything in except for
160 * blowing ships up, secondary weapons and player death/warpout.
162 * 14 3/28/99 5:58p Dave
163 * Added early demo code. Make objects move. Nice and framerate
164 * independant, but not much else. Don't use yet unless you're me :)
166 * 13 3/23/99 2:29p Andsager
167 * Fix shockwaves for kamikazi and Fred defined. Collect together
168 * shockwave_create_info struct.
170 * 12 3/20/99 3:03p Andsager
171 * Fix get_model_cross_section_at_z() from getting invalid index .
173 * 11 3/19/99 9:51a Dave
174 * Checkin to repair massive source safe crash. Also added support for
175 * pof-style nebulae, and some new weapons code.
177 * 10 2/26/99 4:14p Dave
178 * Put in the ability to have multiple shockwaves for ships.
180 * 9 1/29/99 12:47a Dave
181 * Put in sounds for beam weapon. A bunch of interface screens (tech
184 * 8 1/28/99 9:10a Andsager
185 * Particles increased in width, life, number. Max particles increased
187 * 7 1/27/99 9:56a Dave
188 * Temporary checkin of beam weapons for Dan to make cool sounds.
190 * 6 1/11/99 12:42p Andsager
191 * Add live debris - debris which is created from a destroyed subsystem,
192 * when the ship is still alive
194 * 5 11/18/98 10:29a Johnson
195 * fix assert in shipfx_remove_submodel_ship_sparks. submodel may not
196 * have spark and be destroyed.
198 * 4 11/17/98 4:27p Andsager
199 * Stop sparks from emitting from destroyed subobjects
201 * 3 10/20/98 1:39p Andsager
202 * Make so sparks follow animated ship submodels. Modify
203 * ship_weapon_do_hit_stuff() and ship_apply_local_damage() to add
204 * submodel_num. Add submodel_num to multiplayer hit packet.
206 * 2 10/07/98 10:53a Dave
209 * 1 10/07/98 10:51a Dave
216 #include "systemvars.h"
218 #include "fireballs.h"
220 #include "hudtarget.h"
225 #include "3d.h" // needed for View_position, which is used when playing a 3D sound
227 #include "multiutil.h"
230 #include "gamesequence.h"
231 #include "lighting.h"
232 #include "linklist.h"
233 #include "particle.h"
234 #include "multimsgs.h"
235 #include "multiutil.h"
237 #include "freespace.h"
238 #include "muzzleflash.h"
241 #include "neblightning.h"
242 #include "objectsnd.h"
245 extern float flFrametime;
246 extern int Framecount;
249 #define SHIP_CANNON_BITMAP "argh"
250 int Ship_cannon_bitmap = -1;
252 int Player_engine_wash_loop = -1;
254 void shipfx_remove_submodel_ship_sparks(ship *shipp, int submodel_num)
256 Assert(submodel_num != -1);
258 // maybe no active sparks on submodel
259 if (shipp->num_hits == 0) {
263 for (int spark_num=0; spark_num<shipp->num_hits; spark_num++) {
264 if (shipp->sparks[spark_num].submodel_num == submodel_num) {
265 shipp->sparks[spark_num].end_time = timestamp(1);
270 // Check if subsystem has live debris and create
271 // DKA: 5/26/99 make velocity of debris scale according to size of debris subobject (at least for large subobjects)
272 void shipfx_subsystem_mabye_create_live_debris(object *ship_obj, ship *ship_p, ship_subsys *subsys, vector *exp_center, float exp_mag)
275 polymodel *pm = model_get(ship_p->modelnum);
276 int submodel_num = subsys->system_info->subobj_num;
277 submodel_instance_info *sii = &subsys->submodel_info_1;
279 object *live_debris_obj;
280 int i, num_live_debris, live_debris_submodel;
282 // get number of live debris objects to create
283 num_live_debris = pm->submodel[submodel_num].num_live_debris;
284 if (num_live_debris <= 0) {
288 ship_model_start(ship_obj);
291 angles copy_angs = pm->submodel[submodel_num].angs;
292 angles zero_angs = {0.0f, 0.0f, 0.0f};
294 // make sure the axis point is set
295 if ( !sii->axis_set ) {
296 model_init_submodel_axis_pt(sii, ship_p->modelnum, submodel_num);
300 vector model_axis, world_axis, rotvel, world_axis_pt;
301 void model_get_rotating_submodel_axis(vector *model_axis, vector *world_axis, int modelnum, int submodel_num, object *obj);
302 model_get_rotating_submodel_axis(&model_axis, &world_axis, ship_p->modelnum, submodel_num, ship_obj);
303 vm_vec_copy_scale(&rotvel, &world_axis, sii->cur_turn_rate);
305 model_find_world_point(&world_axis_pt, &sii->pt_on_axis, ship_p->modelnum, submodel_num, &ship_obj->orient, &ship_obj->pos);
307 matrix m_rot; // rotation for debris orient about axis
308 vm_quaternion_rotate(&m_rot, vm_vec_mag((vector*)&sii->angs), &model_axis);
311 // create live debris pieces
312 for (i=0; i<num_live_debris; i++) {
313 live_debris_submodel = pm->submodel[submodel_num].live_debris[i];
314 vector start_world_pos, start_model_pos, end_world_pos;
316 // get start world pos
317 vm_vec_zero(&start_world_pos);
318 model_find_world_point(&start_world_pos, &pm->submodel[live_debris_submodel].offset, ship_p->modelnum, live_debris_submodel, &ship_obj->orient, &ship_obj->pos );
320 // convert to model coord of underlying submodel
322 pm->submodel[submodel_num].angs = zero_angs;
323 world_find_model_point(&start_model_pos, &start_world_pos, pm, submodel_num, &ship_obj->orient, &ship_obj->pos);
325 // rotate from submodel coord to world coords
326 // reset angle to current angle
327 pm->submodel[submodel_num].angs = copy_angs;
328 model_find_world_point(&end_world_pos, &start_model_pos, ship_p->modelnum, submodel_num, &ship_obj->orient, &ship_obj->pos);
330 // create fireball here.
331 fireball_create(&end_world_pos, FIREBALL_EXPLOSION_MEDIUM, OBJ_INDEX(ship_obj), pm->submodel[live_debris_submodel].rad);
334 live_debris_obj = debris_create(ship_obj, ship_p->modelnum, live_debris_submodel, &end_world_pos, exp_center, 1, exp_mag);
336 // only do if debris is created
337 if (live_debris_obj) {
338 // get radial velocity of debris
339 vector delta_x, radial_vel;
340 vm_vec_sub(&delta_x, &end_world_pos, &world_axis_pt);
341 vm_vec_crossprod(&radial_vel, &rotvel, &delta_x);
343 if (Ship_info[ship_p->ship_info_index].flags & SIF_KNOSSOS_DEVICE) {
344 // set velocity to cross center of knossos device
345 vector rand_vec, vec_to_center;
347 float vel_mag = vm_vec_mag_quick(&radial_vel) * 1.3f * (0.9f + 0.2f*frand());
348 vm_vec_normalized_dir(&vec_to_center, &world_axis_pt, &end_world_pos);
349 vm_vec_rand_vec_quick(&rand_vec);
350 vm_vec_scale_add2(&vec_to_center, &rand_vec, 0.2f);
351 vm_vec_scale_add2(&live_debris_obj->phys_info.vel, &vec_to_center, vel_mag);
354 // Get rotation of debris object
355 matrix copy = live_debris_obj->orient;
356 vm_matrix_x_matrix(&live_debris_obj->orient, ©, &m_rot);
358 // Add radial velocity (at least as large as exp velocity)
359 vector temp_vel; // explosion velocity with ship_obj velocity removed
360 vm_vec_sub(&temp_vel, &live_debris_obj->phys_info.vel, &ship_obj->phys_info.vel);
362 // find magnitudes of radial and temp velocity
363 float vel_mag = vm_vec_mag(&temp_vel);
364 float rotvel_mag = vm_vec_mag(&radial_vel);
366 if (rotvel_mag > 0.1) {
367 float scale = (1.2f + 0.2f * frand()) * vel_mag / rotvel_mag;
368 // always add *at least* rotvel
373 if (exp_mag > 1) { // whole ship going down
377 if (Ship_info[ship_p->ship_info_index].flags & SIF_KNOSSOS_DEVICE) {
381 vm_vec_scale_add2(&live_debris_obj->phys_info.vel, &radial_vel, scale);
384 // scale up speed of debris if ship_obj > 125, but not for knossos
385 if (ship_obj->radius > 250 && !(Ship_info[ship_p->ship_info_index].flags & SIF_KNOSSOS_DEVICE)) {
386 vm_vec_scale(&live_debris_obj->phys_info.vel, ship_obj->radius/250.0f);
392 ship_model_stop(ship_obj);
395 void set_ship_submodel_as_blown_off(ship *shipp, char *name)
399 // go through list of ship subsystems and find name
400 ship_subsys *pss = NULL;
401 for (pss=GET_FIRST(&shipp->subsys_list); pss!=END_OF_LIST(&shipp->subsys_list); pss=GET_NEXT(pss)) {
402 if ( stricmp(pss->system_info->name, name) == 0) {
408 // set its blown off flag to TRUE
411 pss->submodel_info_1.blown_off = 1;
416 // Create debris for ship submodel which has live debris (at ship death)
417 // when ship submodel has not already been blown off (and hence liberated live debris)
418 void shipfx_maybe_create_live_debris_at_ship_death( object *ship_obj )
420 // if ship has live debris, detonate that subsystem now
421 // search for any live debris
423 ship *shipp = &Ships[ship_obj->instance];
424 polymodel *pm = model_get(shipp->modelnum);
426 int live_debris_submodel = -1;
427 for (int idx=0; idx<pm->num_debris_objects; idx++) {
428 if (pm->submodel[pm->debris_objects[idx]].is_live_debris) {
429 live_debris_submodel = pm->debris_objects[idx];
431 // get submodel that produces live debris
432 int model_get_parent_submodel_for_live_debris( int model_num, int live_debris_model_num );
433 int parent = model_get_parent_submodel_for_live_debris( shipp->modelnum, live_debris_submodel);
434 Assert(parent != -1);
436 // set model values only once (esp blown off)
437 ship_model_start(ship_obj);
439 // check if already blown off (ship model set)
440 if ( !pm->submodel[parent].blown_off ) {
442 // get ship_subsys for live_debris
443 // Go through all subsystems and look for submodel the subsystems with "parent" submodel.
444 ship_subsys *pss = NULL;
445 for ( pss = GET_FIRST(&shipp->subsys_list); pss != END_OF_LIST(&shipp->subsys_list); pss = GET_NEXT(pss) ) {
446 if (pss->system_info->subobj_num == parent) {
451 Assert (pss != NULL);
453 vector exp_center, tmp = {0.0f, 0.0f, 0.0f};
454 model_find_world_point(&exp_center, &tmp, shipp->modelnum, parent, &ship_obj->orient, &ship_obj->pos );
456 // if not blown off, blow it off
457 shipfx_subsystem_mabye_create_live_debris(ship_obj, shipp, pss, &exp_center, 3.0f);
459 // now set subsystem as blown off, so we only get one copy
460 pm->submodel[parent].blown_off = 1;
461 set_ship_submodel_as_blown_off(&Ships[ship_obj->instance], pss->system_info->name);
468 ship_model_stop(ship_obj);
472 void shipfx_blow_off_subsystem(object *ship_obj,ship *ship_p,ship_subsys *subsys, vector *exp_center)
476 model_subsystem *psub = subsys->system_info;
478 get_subsystem_world_pos(ship_obj, subsys, &subobj_pos);
481 if ( psub->turret_gun_sobj > -1 )
482 debris_create( ship_obj, ship_p->modelnum, psub->turret_gun_sobj, &subobj_pos, exp_center, 0, 1.0f );
484 if ( psub->subobj_num > -1 )
485 debris_create( ship_obj, ship_p->modelnum, psub->subobj_num, &subobj_pos, exp_center, 0, 1.0f );
487 // get rid of sparks on submodel that is destroyed
488 shipfx_remove_submodel_ship_sparks(ship_p, psub->subobj_num);
490 // create debris shards
491 shipfx_blow_up_model(ship_obj, ship_p->modelnum, psub->subobj_num, 50, &subobj_pos );
493 // create live debris objects, if any
494 // TODO: some MULITPLAYER implcations here!!
495 shipfx_subsystem_mabye_create_live_debris(ship_obj, ship_p, subsys, exp_center, 1.0f);
497 // create first fireball
498 fireball_create( &subobj_pos, FIREBALL_EXPLOSION_MEDIUM, OBJ_INDEX(ship_obj), psub->radius );
502 void shipfx_blow_up_hull(object *obj, int model, vector *exp_center)
509 pm = model_get(model);
512 // in multiplayer, send a debris_hull_create packet. Save/restore the debris signature
513 // when in misison only (since we can create debris pieces before mission starts)
515 if ( (Game_mode & GM_MULTIPLAYER) && (Game_mode & GM_IN_MISSION) ) {
516 sig_save = multi_get_next_network_signature( MULTI_SIG_DEBRIS );
517 multi_set_network_signature( (ushort)(Ships[obj->instance].arrival_distance), MULTI_SIG_DEBRIS );
520 bool try_live_debris = true;
521 for (i=0; i<pm->num_debris_objects; i++ ) {
522 if (! pm->submodel[pm->debris_objects[i]].is_live_debris) {
523 vector tmp = {0.0f, 0.0f, 0.0f };
524 model_find_world_point(&tmp, &pm->submodel[pm->debris_objects[i]].offset, model, 0, &obj->orient, &obj->pos );
525 debris_create( obj, model, pm->debris_objects[i], &tmp, exp_center, 1, 3.0f );
527 if ( try_live_debris ) {
528 // only create live debris once
529 // this creates *all* the live debris for *all* the currently live subsystems.
530 try_live_debris = false;
531 shipfx_maybe_create_live_debris_at_ship_death(obj);
536 // restore the ship signature to it's original value.
537 if ( (Game_mode & GM_MULTIPLAYER) && (Game_mode & GM_IN_MISSION) ) {
538 multi_set_network_signature( sig_save, MULTI_SIG_DEBRIS );
543 // Creates "ndebris" pieces of debris on random verts of the the "submodel" in the
545 void shipfx_blow_up_model(object *obj,int model, int submodel, int ndebris, vector *exp_center )
549 // if in a multiplayer game -- seed the random number generator with a value that will be the same
550 // on all clients in the game -- the net_signature of the object works nicely -- since doing so should
551 // ensure that all pieces of debris will get scattered in same direction on all machines
552 if ( Game_mode & GM_MULTIPLAYER )
553 srand( obj->net_signature );
555 // made a change to allow anyone but multiplayer client to blow up hull. Clients will do it when
556 // they get the create packet
557 if ( submodel == 0 ) {
558 shipfx_blow_up_hull(obj,model, exp_center );
561 for (i=0; i<ndebris; i++ ) {
564 // Gets two random points on the surface of a submodel
565 submodel_get_two_random_points(model, submodel, &pnt1, &pnt2 );
569 vm_vec_avg( &tmp, &pnt1, &pnt2 );
570 model_find_world_point(&outpnt, &tmp, model,submodel, &obj->orient, &obj->pos );
572 debris_create( obj, -1, -1, &outpnt, exp_center, 0, 1.0f );
577 // =================================================
578 // SHIP WARP IN EFFECT CODE
579 // =================================================
582 // Given an ship, find the radius of it as viewed from the front.
583 static float shipfx_calculate_effect_radius( object *objp )
586 ship *shipp = &Ships[objp->instance];
588 polymodel *pm = model_get( shipp->modelnum );
590 w = pm->maxs.xyz.x - pm->mins.xyz.x;
591 h = pm->maxs.xyz.y - pm->mins.xyz.y;
599 object *docked_objp = ai_find_docked_object( objp );
601 // If ship is docked then center wormhold about their center and make radius large enough.
603 ship *docked_shipp = &Ships[docked_objp->instance];
605 pm = model_get( docked_shipp->modelnum );
607 w = pm->maxs.xyz.x - pm->mins.xyz.x;
608 h = pm->maxs.xyz.y - pm->mins.xyz.y;
619 // How long the stage 1 & stage 2 of warp in effect lasts.
620 // There are different times for small, medium, and large ships.
621 // The appropriate values are picked depending on the ship's
623 #define SHIPFX_WARP_DELAY (2.0f) // time for warp effect to ramp up before ship moves into it.
625 // Give object objp, calculate how long it should take the
626 // ship to go through the warp effect and how fast the ship
627 // should go. For reference, capital ship of 2780m
628 // should take 7 seconds to fly through. Fighters of 30,
629 // should take 1.5 seconds to fly through.
631 #define LARGEST_RAD 1390.0f
632 #define LARGEST_RAD_TIME 7.0f
634 #define SMALLEST_RAD 15.0f
635 #define SMALLEST_RAD_TIME 1.5f
638 static float shipfx_calculate_warp_time( object * objp )
640 // Find rad_percent from 0 to 1, 0 being smallest ship, 1 being largest
641 float rad_percent = (objp->radius-SMALLEST_RAD) / (LARGEST_RAD-SMALLEST_RAD);
642 if ( rad_percent < 0.0f ) {
644 } else if ( rad_percent > 1.0f ) {
647 float rad_time = rad_percent*(LARGEST_RAD_TIME-SMALLEST_RAD_TIME) + SMALLEST_RAD_TIME;
652 // calculate warp speed
653 float shipfx_calculate_warp_speed(object *objp)
657 Assert(objp->type == OBJ_SHIP);
658 if (objp->type != OBJ_SHIP) {
659 length = 2.0f * objp->radius;
661 length = ship_get_length(&Ships[objp->instance]);
663 return length / shipfx_calculate_warp_time(objp);
667 // This is called to actually warp this object in
668 // after all the flashy fx are done, or if the flashy
669 // fx don't work for some reason.
670 void shipfx_actually_warpin( ship *shipp, object *objp )
672 shipp->flags &= (~SF_ARRIVING_STAGE_1);
673 shipp->flags &= (~SF_ARRIVING_STAGE_2);
675 // let physics in on it too.
676 objp->phys_info.flags &= (~PF_WARP_IN);
679 // JAS - code to start the ship doing the warp in effect
680 // This also starts the animating 3d effect playing.
681 // There are two modes, stage 1 and stage 2. Stage 1 is
682 // when the ship just invisibly waits for a certain time
683 // period after the effect starts, and then stage 2 begins,
684 // where the ships flies through the effect at a set
685 // velocity so it gets through in a certain amount of
687 void shipfx_warpin_start( object *objp )
690 float effect_time, effect_radius;
692 shipp = &Ships[objp->instance];
694 if ( shipp->flags & SF_ARRIVING ) {
695 mprintf(( "Ship is already arriving!\n" ));
699 // post a warpin event
700 if(Game_mode & GM_DEMO_RECORD){
701 demo_POST_warpin(objp->signature, shipp->flags);
704 // if there is no arrival warp, then skip the whole thing
705 if ( shipp->flags & SF_NO_ARRIVAL_WARP ) {
706 shipfx_actually_warpin(shipp,objp);
710 // VALIDATE special_warp_objnum
711 if (shipp->special_warp_objnum >= 0) {
713 int ref_objnum = shipp->special_warp_objnum;
714 int valid_reference_ship = FALSE;
716 // Validate reference_objnum
717 if ((ref_objnum >= 0) && (ref_objnum < MAX_OBJECTS)) {
718 object *sp_objp = &Objects[ref_objnum];
719 if (sp_objp->type == OBJ_SHIP) {
720 if (Ship_info[Ships[sp_objp->instance].ship_info_index].flags & SIF_KNOSSOS_DEVICE) {
721 valid_reference_ship = TRUE;
726 if (valid_reference_ship != TRUE) {
727 shipp->special_warp_objnum = -1;
731 // only move warp effect pos if not special warp in.
732 if (shipp->special_warp_objnum >= 0) {
733 Assert(!(Game_mode & GM_MULTIPLAYER));
735 pm = model_get(shipp->modelnum);
736 vm_vec_scale_add(&shipp->warp_effect_pos, &objp->pos, &objp->orient.v.fvec, -pm->mins.xyz.z);
738 vm_vec_scale_add( &shipp->warp_effect_pos, &objp->pos, &objp->orient.v.fvec, objp->radius );
741 // The ending zero mean this is a warp-in effect
742 if ( !(shipp->flags & SF_INITIALLY_DOCKED) ) {
745 // Effect time is 'SHIPFX_WARP_DELAY' (1.5 secs) seconds to start, 'shipfx_calculate_warp_time'
746 // for ship to go thru, and 'SHIPFX_WARP_DELAY' (1.5 secs) to go away.
747 effect_time = shipfx_calculate_warp_time(objp) + SHIPFX_WARP_DELAY + SHIPFX_WARP_DELAY;
748 effect_radius = shipfx_calculate_effect_radius(objp);
750 // maybe special warpin
751 if (shipp->special_warp_objnum >= 0) {
752 // cap radius to size of knossos
753 effect_radius = min(effect_radius, 0.8f*Objects[shipp->special_warp_objnum].radius);
754 warp_objnum = fireball_create(&shipp->warp_effect_pos, FIREBALL_KNOSSOS_EFFECT, shipp->special_warp_objnum, effect_radius, 0, NULL, effect_time, shipp->ship_info_index);
756 warp_objnum = fireball_create(&shipp->warp_effect_pos, FIREBALL_WARP_EFFECT, OBJ_INDEX(objp), effect_radius, 0, NULL, effect_time, shipp->ship_info_index);
758 if (warp_objnum < 0 ) { // JAS: This must always be created, if not, just warp the ship in
759 shipfx_actually_warpin(shipp,objp);
763 shipp->warp_effect_fvec = Objects[warp_objnum].orient.v.fvec;
764 // maybe negate if special warp effect
765 if (shipp->special_warp_objnum >= 0) {
766 if (vm_vec_dotprod(&shipp->warp_effect_fvec, &objp->orient.v.fvec) < 0) {
767 vm_vec_negate(&shipp->warp_effect_fvec);
772 shipp->final_warp_time = timestamp(fl2i(SHIPFX_WARP_DELAY*1000.0f));
773 shipp->flags |= SF_ARRIVING_STAGE_1;
775 // see if this ship is docked with anything, and if so, make docked ship be "arriving"
777 if ( Ai_info[shipp->ai_index].dock_objnum != -1 ) {
778 Ships[Ai_info[shipp->ai_index].dock_objnum].final_warp_time = timestamp(fl2i(warp_time*1000.0f));
779 Ships[Ai_info[shipp->ai_index].dock_objnum].flags |= SF_ARRIVING_STAGE_1;
785 void shipfx_warpin_frame( object *objp, float frametime )
789 shipp = &Ships[objp->instance];
791 if ( shipp->flags & SF_DYING ) return;
793 if ( shipp->flags & SF_ARRIVING_STAGE_1 ) {
794 if ( timestamp_elapsed(shipp->final_warp_time) ) {
796 // let physics know the ship is going to warp in.
797 objp->phys_info.flags |= PF_WARP_IN;
799 // done doing stage 1 of warp, so go on to stage 2
800 shipp->flags &= (~SF_ARRIVING_STAGE_1);
801 shipp->flags |= SF_ARRIVING_STAGE_2;
803 float warp_time = shipfx_calculate_warp_time(objp);
804 float speed = shipfx_calculate_warp_speed(objp); // How long it takes to move through warp effect
806 // Make ship move at velocity so that it moves two radius's in warp_time seconds.
808 vel = objp->orient.v.fvec;
809 vm_vec_scale( &vel, speed );
810 objp->phys_info.vel = vel;
811 objp->phys_info.desired_vel = vel;
812 objp->phys_info.prev_ramp_vel.xyz.x = 0.0f;
813 objp->phys_info.prev_ramp_vel.xyz.y = 0.0f;
814 objp->phys_info.prev_ramp_vel.xyz.z = speed;
815 objp->phys_info.forward_thrust = 0.0f; // How much the forward thruster is applied. 0-1.
817 shipp->final_warp_time = timestamp(fl2i(warp_time*1000.0f));
820 // see if this ship is docked with anything, and if so, make docked ship be "arriving"
821 if ( Ai_info[shipp->ai_index].dock_objnum != -1 ) {
822 Ships[Ai_info[shipp->ai_index].dock_objnum].flags &= (~SF_ARRIVING_STAGE_1);
823 Ships[Ai_info[shipp->ai_index].dock_objnum].flags |= SF_ARRIVING_STAGE_2;
824 Ships[Ai_info[shipp->ai_index].dock_objnum].final_warp_time = timestamp(fl2i(warp_time*1000.0f));
828 } else if ( shipp->flags & SF_ARRIVING_STAGE_2 ) {
829 if ( timestamp_elapsed(shipp->final_warp_time) ) {
830 // done doing stage 2 of warp, so turn off arriving flag
831 shipfx_actually_warpin(shipp,objp);
833 // notify physics to slow down
834 if (Ship_info[shipp->ship_info_index].flags & SIF_SUPERCAP) {
835 // let physics know this is a special warp in
836 objp->phys_info.flags |= PF_SPECIAL_WARP_IN;
843 // This is called to actually warp this object out
844 // after all the flashy fx are done, or if the flashy
845 // fx don't work for some reason. OR to skip the flashy
847 void shipfx_actually_warpout( ship *shipp, object *objp )
849 // Once we get through effect, make the ship go away
851 if ( objp == Player_obj ) {
852 // Normally, this will never get called for the player. If it
853 // does, it is because some error (like the warpout effect
854 // couldn't start) so go ahead an warp the player out.
855 // All this does is set the event to go to debriefing, the
856 // same thing that happens after the player warp out effect
858 gameseq_post_event( GS_EVENT_DEBRIEF ); // proceed to debriefing
862 // Code for objects except player ship warping out
863 objp->flags |= OF_SHOULD_BE_DEAD;
864 // check to see if departing ship is docked with anything and if so, mark that object
866 docked_objp = ai_find_docked_object( objp );
868 docked_objp->flags |= OF_SHOULD_BE_DEAD;
871 ship_departed( objp->instance );
873 ship_departed( docked_objp->instance );
878 // compute_special_warpout_stuff();
879 int compute_special_warpout_stuff(object *objp, float *speed, float *warp_time, vector *warp_pos)
881 object *sp_objp = NULL;
883 int valid_refenence_ship = FALSE, ref_objnum;
884 vector facing_normal, vec_to_knossos;
887 // knossos warpout only valid in single player
888 if (Game_mode & GM_MULTIPLAYER) {
889 mprintf(("special warpout only for single player\n"));
893 // find special warp ship reference
894 valid_refenence_ship = FALSE;
895 ref_objnum = Ships[objp->instance].special_warp_objnum;
897 // Validate reference_objnum
898 if ((ref_objnum >= 0) && (ref_objnum < MAX_OBJECTS)) {
899 sp_objp = &Objects[ref_objnum];
900 if (sp_objp->type == OBJ_SHIP) {
901 shipp = &Ships[sp_objp->instance];
902 if (Ship_info[shipp->ship_info_index].flags & SIF_KNOSSOS_DEVICE) {
903 valid_refenence_ship = TRUE;
908 if (!valid_refenence_ship) {
910 mprintf(("special warpout reference ship not found\n"));
914 // get facing normal from knossos
915 vm_vec_sub(&vec_to_knossos, &sp_objp->pos, &objp->pos);
916 facing_normal = sp_objp->orient.v.fvec;
917 if (vm_vec_dotprod(&vec_to_knossos, &sp_objp->orient.v.fvec) > 0) {
918 vm_vec_negate(&facing_normal);
921 // find position to play the warp ani..
922 dist_to_plane = fvi_ray_plane(warp_pos, &sp_objp->pos, &facing_normal, &objp->pos, &objp->orient.v.fvec, 0.0f);
924 // calculate distance to warpout point.
925 polymodel *pm = model_get(Ships[objp->instance].modelnum);
926 dist_to_plane += pm->mins.xyz.z;
928 if (dist_to_plane < 0) {
929 mprintf(("warpout started too late\n"));
934 float max_warpout_angle = 0.707f; // 45 degree half-angle cone for small ships
935 if (Ship_info[Ships[objp->instance].ship_info_index].flags & (SIF_BIG_SHIP | SIF_HUGE_SHIP)) {
936 max_warpout_angle = 0.866f; // 30 degree half-angle cone for BIG or HUGE
939 if (-vm_vec_dotprod(&objp->orient.v.fvec, &facing_normal) < max_warpout_angle) { // within allowed angle
941 mprintf(("special warpout angle exceeded\n"));
945 // Calculate speed needed to get
946 *speed = shipfx_calculate_warp_speed(objp);
948 // Calculate how long to fly through the effect. Not to get to the effect, just through it.
949 *warp_time = shipfx_calculate_warp_time(objp);
950 *warp_time += dist_to_plane / *speed;
956 void compute_warpout_stuff(object *objp, float *speed, float *warp_time, vector *warp_pos)
958 // If we're warping through the knossos, do something different.
959 if (Ships[objp->instance].special_warp_objnum >= 0) {
960 if (compute_special_warpout_stuff(objp, speed, warp_time, warp_pos) != -1) {
963 mprintf(("Invalid special warp\n"));
967 object *docked_objp = ai_find_docked_object( objp );
968 vector center_pos = objp->pos;
969 float radius, ship_move_dist, warp_dist;
971 radius = objp->radius;
973 // If ship is docked then center wormhold about their center and make radius large enough.
975 vm_vec_avg(¢er_pos, &objp->pos, &docked_objp->pos);
976 radius += docked_objp->radius;
979 // Calculate how long to fly through the effect. Not to get to the effect, just through it.
980 *warp_time = shipfx_calculate_warp_time(objp);
982 // Pick some speed at which we want to go through the warp effect.
983 // This is determined by shipfx_calculate_warp_time specifying how long
984 // it should take to go through the effect, or 2R.
985 *speed = shipfx_calculate_warp_speed(objp);
987 if ( objp == Player_obj ) {
988 *speed = 0.8f*objp->phys_info.max_vel.xyz.z;
991 // Now we know our speed. Figure out how far the warp effect will be from here.
992 ship_move_dist = (*speed * SHIPFX_WARP_DELAY) + radius*1.5f; // We want to get to 1.5R away from effect
993 if ( ship_move_dist < radius*1.5f ) {
994 ship_move_dist = radius*1.5f;
997 // If this is a huge ship, set the distance to the length of the ship
998 if (Ship_info[Ships[objp->instance].ship_info_index].flags & SIF_HUGE_SHIP) {
999 ship_move_dist = 0.5f * ship_get_length(&Ships[objp->instance]);
1002 // Acount for time to get to warp effect, before we actually go through it.
1003 *warp_time += ship_move_dist / *speed;
1005 warp_dist = ship_move_dist;
1006 // allow for off center
1007 if (Ship_info[Ships[objp->instance].ship_info_index].flags & SIF_HUGE_SHIP) {
1008 polymodel *pm = model_get(Ships[objp->instance].modelnum);
1009 warp_dist -= pm->mins.xyz.z;
1012 vm_vec_scale_add( warp_pos, ¢er_pos, &objp->orient.v.fvec, warp_dist );
1015 // JAS - code to start the ship doing the warp out effect
1016 // This puts the ship into a mode specified by SF_DEPARTING
1017 // where it flies forward for a set time period at a set
1018 // velocity, then disappears when that time is reached. This
1019 // also starts the animating 3d effect playing.
1020 void shipfx_warpout_start( object *objp )
1024 shipp = &Ships[objp->instance];
1026 if ( shipp->flags & SF_DEPART_WARP ) {
1027 mprintf(( "Ship is already departing!\n" ));
1031 // if we're dying return
1032 if ( shipp->flags & SF_DYING ) {
1036 // if we're HUGE, keep alive - set guardian
1037 if (Ship_info[shipp->ship_info_index].flags & SIF_HUGE_SHIP) {
1038 objp->flags |= OF_GUARDIAN;
1041 // post a warpin event
1042 if(Game_mode & GM_DEMO_RECORD){
1043 demo_POST_warpout(objp->signature, shipp->flags);
1046 // don't send ship depart packets for player ships
1047 if ( (MULTIPLAYER_MASTER) && !(objp->flags & OF_PLAYER_SHIP) ){
1048 send_ship_depart_packet( objp );
1051 // don't do departure wormhole if ship flag is set which indicates no effect
1052 if ( shipp->flags & SF_NO_DEPARTURE_WARP ) {
1053 // DKA 5/25/99 If he's going to warpout, set it.
1054 // Next line fixes assert in wing cleanup code when no warp effect.
1055 shipp->flags |= SF_DEPART_WARP;
1057 shipfx_actually_warpout(shipp, objp);
1061 if ( objp == Player_obj ) {
1062 HUD_printf(XSTR( "Subspace node activated", 498) );
1065 float speed, effect_time, effect_radius;
1067 // warp time from compute warpout stuff includes time to get up to warp_pos
1068 compute_warpout_stuff(objp, &speed, &warp_time, &warp_pos);
1069 shipp->warp_effect_pos = warp_pos;
1071 // The ending one means this is a warp-out effect
1073 // Effect time is 'SHIPFX_WARP_DELAY' (1.5 secs) seconds to start, 'shipfx_calculate_warp_time'
1074 // for ship to go thru, and 'SHIPFX_WARP_DELAY' (1.5 secs) to go away.
1075 // effect_time = shipfx_calculate_warp_time(objp) + SHIPFX_WARP_DELAY + SHIPFX_WARP_DELAY;
1076 effect_time = warp_time + SHIPFX_WARP_DELAY;
1077 effect_radius = shipfx_calculate_effect_radius(objp);
1079 // maybe special warpout
1080 if (shipp->special_warp_objnum >= 0) {
1081 // cap radius to size of knossos
1082 effect_radius = min(effect_radius, 0.8f*Objects[shipp->special_warp_objnum].radius);
1083 warp_objnum = fireball_create(&shipp->warp_effect_pos, FIREBALL_KNOSSOS_EFFECT, shipp->special_warp_objnum, effect_radius, 1, NULL, effect_time, shipp->ship_info_index);
1085 warp_objnum = fireball_create(&shipp->warp_effect_pos, FIREBALL_WARP_EFFECT, OBJ_INDEX(objp), effect_radius, 1, NULL, effect_time, shipp->ship_info_index);
1087 if (warp_objnum < 0 ) { // JAS: This must always be created, if not, just warp the ship in
1088 shipfx_actually_warpout(shipp,objp);
1092 shipp->warp_effect_fvec = Objects[warp_objnum].orient.v.fvec;
1093 // maybe negate if special warp effect
1094 if (shipp->special_warp_objnum >= 0) {
1095 if (vm_vec_dotprod(&shipp->warp_effect_fvec, &objp->orient.v.fvec) > 0) {
1096 vm_vec_negate(&shipp->warp_effect_fvec);
1100 // Make the warp effect stage 1 last SHIP_WARP_TIME1 seconds.
1101 if ( objp == Player_obj ) {
1102 warp_time = fireball_lifeleft(&Objects[warp_objnum]);
1103 shipp->final_warp_time = timestamp(fl2i(warp_time*1000.0f));
1105 shipp->final_warp_time = timestamp(fl2i(warp_time*2.0f*1000.0f));
1107 shipp->flags |= SF_DEPART_WARP;
1109 // mprintf(( "Warp time = %.4f , effect time = %.4f ms\n", warp_time*1000.0f, effect_time ));
1111 // 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;
1113 // Set ship's velocity to 'speed'
1114 // This should actually be an AI that flies from the current
1115 // position through 'shipp->warp_effect_pos' in 'warp_time'
1117 if ( objp != Player_obj ) {
1119 vel = objp->orient.v.fvec;
1120 vm_vec_scale( &vel, speed );
1121 objp->phys_info.vel = vel;
1122 objp->phys_info.desired_vel = vel;
1123 objp->phys_info.prev_ramp_vel.xyz.x = 0.0f;
1124 objp->phys_info.prev_ramp_vel.xyz.y = 0.0f;
1125 objp->phys_info.prev_ramp_vel.xyz.z = speed;
1126 objp->phys_info.forward_thrust = 1.0f; // How much the forward thruster is applied. 0-1.
1128 // special case for HUGE ships
1129 if (Ship_info[shipp->ship_info_index].flags & SIF_HUGE_SHIP) {
1130 // objp->phys_info.flags |= PF_SPECIAL_WARP_OUT;
1136 void shipfx_warpout_frame( object *objp, float frametime )
1139 shipp = &Ships[objp->instance];
1141 if ( shipp->flags & SF_DYING ) return;
1144 float warp_pos; // position of warp effect in object's frame of reference
1146 vm_vec_sub( &tempv, &objp->pos, &shipp->warp_effect_pos );
1147 warp_pos = -vm_vec_dot( &tempv, &shipp->warp_effect_fvec );
1150 // Find the closest point on line from center of wormhole
1154 fvi_ray_plane(&pos,&objp->pos,&shipp->warp_effect_fvec,&shipp->warp_effect_pos, &shipp->warp_effect_fvec, 0.0f );
1155 dist = vm_vec_dist( &pos, &objp->pos );
1157 // mprintf(( "Warp pos = %.1f, rad=%.1f, center dist = %.1f\n", warp_pos, objp->radius, dist ));
1159 if ( objp == Player_obj ) {
1160 // Code for player warpout frame
1162 if ( (Player->control_mode==PCM_WARPOUT_STAGE2) && (warp_pos > objp->radius) ) {
1163 gameseq_post_event( GS_EVENT_PLAYER_WARPOUT_DONE_STAGE2 );
1166 if ( timestamp_elapsed(shipp->final_warp_time) ) {
1168 // Something went wrong... oh well, warp him out anyway.
1169 if ( Player->control_mode != PCM_WARPOUT_STAGE3 ) {
1170 mprintf(( "Hmmm... player ship warpout time elapsed, but he wasn't in warp stage 3.\n" ));
1173 gameseq_post_event( GS_EVENT_PLAYER_WARPOUT_DONE );
1174 ship_departed( objp->instance ); // mark log entry for the player
1178 // Code for all non-player ships warpout frame
1180 int timed_out = timestamp_elapsed(shipp->final_warp_time);
1182 // mprintf(("Frame %i: Ship %s missed departue cue.\n", Framecount, shipp->ship_name ));
1183 int delta_ms = timestamp_until(shipp->final_warp_time);
1184 if (delta_ms > 1000.0f * frametime ) {
1185 nprintf(("AI", "Frame %i: Ship %s missed departue cue by %7.3f seconds.\n", Framecount, shipp->ship_name, - (float) delta_ms/1000.0f));
1189 // MWA 10/21/97 -- added shipp->flags & SF_NO_DEPARTURE_WARP part of next if statement. For ships
1190 // that don't get a wormhole effect, I wanted to drop into this code immediately.
1191 if ( (warp_pos > objp->radius) || (shipp->flags & SF_NO_DEPARTURE_WARP) || timed_out ) {
1192 shipfx_actually_warpout( shipp, objp );
1198 //==================================================
1199 // Stuff for keeping track of which ships are in
1203 // Given point p0, in object's frame of reference, find if
1204 // it can see the sun.
1205 int shipfx_point_in_shadow( vector *p0, matrix *src_orient, vector *src_pos, float radius )
1218 // Move rp0 into world coordinates
1219 vm_vec_unrotate(&rp0, p0, src_orient);
1220 vm_vec_add2(&rp0, src_pos);
1222 // get the # of global lights
1223 n_lights = light_get_global_count();
1225 for(idx=0; idx<n_lights; idx++){
1226 // get the light dir for this light
1227 light_get_global_dir(&light_dir, idx);
1230 vm_vec_scale_add( &rp1, &rp0, &light_dir, 10000.0f );
1232 for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) {
1233 objp = &Objects[so->objnum];
1234 shipp = &Ships[objp->instance];
1236 mc.model_num = shipp->modelnum;
1237 mc.orient = &objp->orient;
1238 mc.pos = &objp->pos;
1241 mc.flags = MC_CHECK_MODEL;
1243 if ( model_collide(&mc) ){
1254 // Given an ship see if it is in a shadow.
1255 int shipfx_in_shadow( object * src_obj )
1269 // get the # of global lights
1270 n_lights = light_get_global_count();
1272 for(idx=0; idx<n_lights; idx++){
1273 // get the direction for this light
1274 light_get_global_dir(&light_dir, idx);
1277 for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) {
1278 objp = &Objects[so->objnum];
1280 if ( src_obj != objp ) {
1281 shipp = &Ships[objp->instance];
1283 vm_vec_scale_add( &rp1, &rp0, &light_dir, objp->radius*10.0f );
1285 mc.model_num = shipp->modelnum;
1286 mc.orient = &objp->orient;
1287 mc.pos = &objp->pos;
1290 mc.flags = MC_CHECK_MODEL;
1292 // mc.flags |= MC_CHECK_SPHERELINE;
1293 // mc.radius = src_obj->radius;
1295 if ( model_collide(&mc) ) {
1306 // Given world point see if it is in a shadow.
1307 int shipfx_eye_in_shadow( vector *eye_pos, object * src_obj, int sun_n )
1319 // get the light dir
1320 if(!light_get_global_dir(&light_dir, sun_n)){
1325 for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) {
1326 objp = &Objects[so->objnum];
1328 if ( src_obj != objp ) {
1329 shipp = &Ships[objp->instance];
1331 vm_vec_scale_add( &rp1, &rp0, &light_dir, objp->radius*10.0f );
1333 ship_model_start(objp);
1335 mc.model_num = shipp->modelnum;
1336 mc.orient = &objp->orient;
1337 mc.pos = &objp->pos;
1340 mc.flags = MC_CHECK_MODEL;
1342 // mc.flags |= MC_CHECK_SPHERELINE;
1343 // mc.radius = src_obj->radius;
1345 int hit = model_collide(&mc);
1347 ship_model_stop(objp);
1355 // Check all the big hull debris pieces.
1356 debris *db = Debris;
1359 for ( i = 0; i < MAX_DEBRIS_PIECES; i++, db++ ) {
1360 if ( !(db->flags & DEBRIS_USED) || !db->is_hull ){
1364 objp = &Objects[db->objnum];
1366 vm_vec_scale_add( &rp1, &rp0, &light_dir, objp->radius*10.0f );
1368 mc.model_num = db->model_num; // Fill in the model to check
1369 mc.submodel_num = db->submodel_num;
1370 model_clear_instance( mc.model_num );
1371 mc.orient = &objp->orient; // The object's orient
1372 mc.pos = &objp->pos; // The object's position
1373 mc.p0 = &rp0; // Point 1 of ray to check
1374 mc.p1 = &rp1; // Point 2 of ray to check
1375 mc.flags = (MC_CHECK_MODEL | MC_SUBMODEL);
1377 if (model_collide(&mc)) {
1386 //=====================================================================================
1387 // STUFF FOR DOING SHIP GUN FLASHES
1388 //=====================================================================================
1390 #define MAX_FLASHES 128 // How many flashes total
1391 #define FLASH_LIFE_PRIMARY 0.25f // How long flash lives
1392 #define FLASH_LIFE_SECONDARY 0.50f // How long flash lives
1395 typedef struct ship_flash {
1396 int objnum; // object number of parent ship
1397 int obj_signature; // signature of that object
1398 int light_num; // which light in the model this uses
1399 float life; // how long this should be around
1400 float max_life; // how long this has been around.
1403 int Ship_flash_inited = 0;
1404 int Ship_flash_highest = -1;
1405 ship_flash Ship_flash[MAX_FLASHES];
1407 // Resets the ship flash stuff. Call before each level.
1408 void shipfx_flash_init()
1412 for (i=0; i<MAX_FLASHES; i++ ) {
1413 Ship_flash[i].objnum = -1; // mark as unused
1415 Ship_flash_highest = -1;
1416 Ship_flash_inited = 1;
1420 // Given that a ship fired a weapon, light up the model
1422 void shipfx_flash_create(object *objp, ship * shipp, vector *gun_pos, vector *gun_dir, int is_primary, int weapon_info_index)
1425 int objnum = OBJ_INDEX(objp);
1427 Assert(Ship_flash_inited);
1429 polymodel *pm = model_get( shipp->modelnum );
1430 int closest_light = -1;
1431 float d, closest_dist = 0.0f;
1433 // ALWAYS do this - since this is called once per firing
1434 // if this is a cannon type weapon, create a muzzle flash
1435 // HACK - let the flak guns do this on their own since they fire so quickly
1436 if((Weapon_info[weapon_info_index].wi_flags & WIF_MFLASH) && !(Weapon_info[weapon_info_index].wi_flags & WIF_FLAK)){
1437 // spiffy new flash stuff
1438 mflash_create(gun_pos, gun_dir, Weapon_info[weapon_info_index].muzzle_flash);
1441 if ( pm->num_lights < 1 ) return;
1443 for (i=0; i<pm->num_lights; i++ ) {
1444 d = vm_vec_dist( &pm->lights[i].pos, gun_pos );
1446 if ( pm->lights[i].type == BSP_LIGHT_TYPE_WEAPON ) {
1447 if ( (closest_light==-1) || (d<closest_dist) ) {
1454 if ( closest_light == -1 ) return;
1456 int first_slot = -1;
1458 for (i=0; i<=Ship_flash_highest; i++ ) {
1459 if ( (first_slot==-1) && (Ship_flash[i].objnum < 0) ) {
1463 if ( (Ship_flash[i].objnum == objnum) && (Ship_flash[i].obj_signature==objp->signature) ) {
1464 if ( Ship_flash[i].light_num == closest_light ) {
1465 // This is already flashing!
1466 Ship_flash[i].life = 0.0f;
1468 Ship_flash[i].max_life = FLASH_LIFE_PRIMARY;
1470 Ship_flash[i].max_life = FLASH_LIFE_SECONDARY;
1477 if ( first_slot == -1 ) {
1478 if ( Ship_flash_highest < MAX_FLASHES-1 ) {
1479 Ship_flash_highest++;
1480 first_slot = Ship_flash_highest;
1482 //mprintf(( "SHIP_FLASH: Out of flash spots!\n" ));
1483 return; // out of flash slots
1487 Assert( Ship_flash[first_slot].objnum == -1 );
1489 Ship_flash[first_slot].objnum = objnum;
1490 Ship_flash[first_slot].obj_signature = objp->signature;
1491 Ship_flash[first_slot].life = 0.0f; // Start it up
1493 Ship_flash[first_slot].max_life = FLASH_LIFE_PRIMARY;
1495 Ship_flash[first_slot].max_life = FLASH_LIFE_SECONDARY;
1497 Ship_flash[first_slot].light_num = closest_light;
1500 // Sets the flash lights in the model used by this
1501 // ship to the appropriate values. There might not
1502 // be any flashes linked to this ship in which
1503 // case this function does nothing.
1504 void shipfx_flash_light_model( object *objp, ship * shipp )
1506 int i, objnum = OBJ_INDEX(objp);
1507 polymodel *pm = model_get( shipp->modelnum );
1509 for (i=0; i<=Ship_flash_highest; i++ ) {
1510 if ( (Ship_flash[i].objnum == objnum) && (Ship_flash[i].obj_signature==objp->signature) ) {
1511 float v = (Ship_flash[i].max_life - Ship_flash[i].life)/Ship_flash[i].max_life;
1513 pm->lights[Ship_flash[i].light_num].value += v / 255.0f;
1519 // Does whatever processing needs to be done each frame.
1520 void shipfx_flash_do_frame(float frametime)
1526 for (i=0, sf = &Ship_flash[0]; i<=Ship_flash_highest; i++, sf++ ) {
1527 if ( sf->objnum > -1 ) {
1528 if ( Objects[sf->objnum].signature != sf->obj_signature ) {
1531 sf->life += frametime;
1532 if ( sf->life >= sf->max_life ) kill_it = 1;
1536 if ( i == Ship_flash_highest ) {
1537 while( (Ship_flash_highest>0) && (Ship_flash[Ship_flash_highest].objnum == -1) ) {
1538 Ship_flash_highest--;
1547 float Particle_width = 1.2f;
1548 DCF(particle_width, "Multiplier for angular width of the particle spew")
1551 dc_get_arg(ARG_FLOAT);
1552 if ( (Dc_arg_float >= 0 ) && (Dc_arg_float <= 5) ) {
1553 Particle_width = Dc_arg_float;
1555 dc_printf( "Illegal value for particle width. (Must be from 0-5) \n\n");
1560 float Particle_number = 1.2f;
1561 DCF(particle_num, "Multiplier for the number of particles created")
1564 dc_get_arg(ARG_FLOAT);
1565 if ( (Dc_arg_float >= 0 ) && (Dc_arg_float <= 5) ) {
1566 Particle_number = Dc_arg_float;
1568 dc_printf( "Illegal value for particle num. (Must be from 0-5) \n\n");
1573 float Particle_life = 1.2f;
1574 DCF(particle_life, "Multiplier for the lifetime of particles created")
1577 dc_get_arg(ARG_FLOAT);
1578 if ( (Dc_arg_float >= 0 ) && (Dc_arg_float <= 5) ) {
1579 Particle_life = Dc_arg_float;
1581 dc_printf( "Illegal value for particle life. (Must be from 0-5) \n\n");
1586 // Make sparks fly off of ship n.
1587 // sn = spark number to spark, corrosponding to element in
1588 // ship->hitpos array. If this isn't -1, it is a just
1589 // got hit by weapon spark, otherwise pick one randomally.
1590 void shipfx_emit_spark( int n, int sn )
1592 int create_spark = 1;
1595 ship *shipp = &Ships[n];
1596 float ship_radius, spark_scale_factor;
1598 if ( shipp->num_hits <= 0 )
1601 // get radius of ship
1602 ship_radius = model_get_radius(Ship_info[shipp->ship_info_index].modelnum);
1604 // get spark_scale_factor -- how much to increase ship sparks, based on radius
1605 if (ship_radius > 40) {
1606 spark_scale_factor = 1.0f;
1607 } else if (ship_radius > 20) {
1608 spark_scale_factor = (ship_radius - 20.0f) / 20.0f;
1610 spark_scale_factor = 0.0f;
1613 float spark_time_scale = 1.0f + spark_scale_factor * (Particle_life - 1.0f);
1614 float spark_width_scale = 1.0f + spark_scale_factor * (Particle_width - 1.0f);
1615 float spark_num_scale = 1.0f + spark_scale_factor * (Particle_number - 1.0f);
1617 obj = &Objects[shipp->objnum];
1618 ship_info* si = &Ship_info[shipp->ship_info_index];
1620 float hull_percent = obj->hull_strength / si->initial_hull_strength;
1621 if (hull_percent < 0.001) {
1622 hull_percent = 0.001f;
1624 float fraction = 0.1f * obj->radius / hull_percent;
1625 if (fraction > 1.0f) {
1631 spark_num = myrand() % shipp->num_hits;
1636 // don't display sparks that have expired
1637 if ( timestamp_elapsed(shipp->sparks[spark_num].end_time) ) {
1641 // get spark position
1642 if (shipp->sparks[spark_num].submodel_num != -1) {
1643 ship_model_start(obj);
1644 model_find_world_point(&outpnt, &shipp->sparks[spark_num].pos, shipp->modelnum, shipp->sparks[spark_num].submodel_num, &obj->orient, &obj->pos);
1645 ship_model_stop(obj);
1647 // rotate sparks correctly with current ship orient
1648 vm_vec_unrotate(&outpnt, &shipp->sparks[spark_num].pos, &obj->orient);
1649 vm_vec_add2(&outpnt,&obj->pos);
1652 if ( shipp->flags & (SF_ARRIVING|SF_DEPART_WARP) ) {
1654 vm_vec_sub( &tmp, &outpnt, &shipp->warp_effect_pos );
1655 if ( vm_vec_dot( &tmp, &shipp->warp_effect_fvec ) < 0.0f ) {
1656 // if in front of warp plane, don't create.
1661 if ( create_spark ) {
1663 particle_emitter pe;
1665 pe.pos = outpnt; // Where the particles emit from
1667 if ( shipp->flags & (SF_ARRIVING|SF_DEPART_WARP) ) {
1668 // No velocity if going through warp.
1669 pe.vel = vmd_zero_vector;
1671 // Initial velocity of all the particles.
1672 // 0.0f = 0% of parent's.
1673 // 1.0f = 100% of parent's.
1674 vm_vec_copy_scale( &pe.vel, &obj->phys_info.vel, 0.7f );
1677 // TODO: add velocity from rotation if submodel is rotating
1680 // r = outpnt - model_find_world_point(0)
1682 // w = model_find_world_dir(
1683 // model_find_world_dir(&out_dir, &in_dir, model_num, submodel_num, &objorient, &objpos);
1685 vector tmp_norm, tmp_vel;
1686 vm_vec_sub( &tmp_norm, &outpnt, &obj->pos );
1687 vm_vec_normalize_safe(&tmp_norm);
1689 tmp_vel = obj->phys_info.vel;
1690 if ( vm_vec_normalize_safe(&tmp_vel) > 1.0f ) {
1691 vm_vec_scale_add2(&tmp_norm,&tmp_vel, -2.0f);
1692 vm_vec_normalize_safe(&tmp_norm);
1695 pe.normal = tmp_norm; // What normal the particle emit around
1696 pe.normal_variance = 0.3f; // How close they stick to that normal 0=good, 1=360 degree
1697 pe.min_rad = 0.20f; // Min radius
1698 pe.max_rad = 0.50f; // Max radius
1700 // first time through - set up end time and make heavier initially
1702 // Sparks for first time at this spot
1703 if (si->flags & SIF_FIGHTER) {
1704 if (hull_percent > 0.6f) {
1705 // sparks only once when hull > 60%
1706 float spark_duration = (float)pow(2.0f, -5.0f*(hull_percent-1.3f)) * (1.0f + 0.6f*(frand()-0.5f)); // +- 30%
1707 shipp->sparks[spark_num].end_time = timestamp( (int) (1000.0f * spark_duration) );
1709 // spark never ends when hull < 60% (~277 hr)
1710 shipp->sparks[spark_num].end_time = timestamp( 100000000 );
1714 if ( D3D_enabled ) {
1715 pe.num_low = 25; // Lowest number of particles to create (hardware)
1716 pe.num_high = 30; // Highest number of particles to create (hardware)
1718 pe.num_low = 5; // Lowest number of particles to create (software)
1719 pe.num_high = 7; // Highest number of particles to create (software)
1721 pe.normal_variance = 1.0f; // How close they stick to that normal 0=good, 1=360 degree
1722 pe.min_vel = 2.0f; // How fast the slowest particle can move
1723 pe.max_vel = 12.0f; // How fast the fastest particle can move
1724 pe.min_life = 0.05f; // How long the particles live
1725 pe.max_life = 0.55f; // How long the particles live
1727 particle_emit( &pe, PARTICLE_FIRE, 0 );
1730 pe.min_rad = 0.7f; // Min radius
1731 pe.max_rad = 1.3f; // Max radius
1732 if ( D3D_enabled ) {
1733 pe.num_low = int (20 * spark_num_scale); // Lowest number of particles to create (hardware)
1734 pe.num_high = int (50 * spark_num_scale); // Highest number of particles to create (hardware)
1736 pe.num_low = 2; // Lowest number of particles to create (software)
1737 pe.num_high = 8; // Highest number of particles to create (software)
1739 pe.normal_variance = 0.2f * spark_width_scale; // How close they stick to that normal 0=good, 1=360 degree
1740 pe.min_vel = 3.0f; // How fast the slowest particle can move
1741 pe.max_vel = 12.0f; // How fast the fastest particle can move
1742 pe.min_life = 0.35f*2.0f * spark_time_scale; // How long the particles live
1743 pe.max_life = 0.75f*2.0f * spark_time_scale; // How long the particles live
1745 particle_emit( &pe, PARTICLE_SMOKE, 0 );
1749 // Select time to do next spark
1750 // Ships[n].next_hit_spark = timestamp_rand(100,500);
1751 shipp->next_hit_spark = timestamp_rand(50,100);
1756 //=====================================================================================
1757 // STUFF FOR DOING LARGE SHIP EXPLOSIONS
1758 //=====================================================================================
1760 int Bs_exp_fire_low = 1;
1761 float Bs_exp_fire_time_mult = 1.0f;
1763 DCF_BOOL(bs_exp_fire_low, Bs_exp_fire_low)
1764 DCF(bs_exp_fire_time_mult, "Multiplier time between fireball in big ship explosion")
1767 dc_get_arg(ARG_FLOAT);
1768 if ( (Dc_arg_float >= 0.1 ) && (Dc_arg_float <= 5) ) {
1769 Bs_exp_fire_time_mult = Dc_arg_float;
1771 dc_printf( "Illegal value for bs_exp_fire_time_mult. (Must be from 0.1-5) \n\n");
1776 #define MAX_SPLIT_SHIPS 3 // How many can explode at once. Each one is about 1K.
1778 #define DEBRIS_NONE 0
1779 #define DEBRIS_DRAW 1
1780 #define DEBRIS_FREE 2
1782 typedef struct clip_ship {
1784 float length_left; // uncomsumed length
1786 physics_info phys_info;
1787 vector local_pivot; // world center of mass position of half ship
1788 vector model_center_disp_to_orig_center; // displacement from half ship center to original model center
1789 vector clip_plane_norm; // clip plane normal (local [0,0,1] or [0,0,-1])
1790 float cur_clip_plane_pt; // displacement from half ship clip plane to original model center
1791 float explosion_vel;
1792 ubyte draw_debris[MAX_DEBRIS_OBJECTS];
1796 typedef struct split_ship {
1797 int used; // 0 if not used, 1 if used
1798 clip_ship front_ship;
1799 clip_ship back_ship;
1800 int explosion_flash_timestamp;
1801 int explosion_flash_started;
1802 int sound_handle[NUM_SUB_EXPL_HANDLES];
1806 static split_ship Split_ships[MAX_SPLIT_SHIPS];
1807 static int Split_ships_inited = 0;
1809 static void split_ship_init_system()
1812 for (i=0; i<MAX_SPLIT_SHIPS; i++ ) {
1813 Split_ships[i].used = 0;
1815 Split_ships_inited = 1;
1818 static void maybe_fireball_wipe(clip_ship* half_ship, int* sound_handle);
1819 static void split_ship_init( ship* shipp, split_ship* split_ship )
1821 object* parent_ship_obj = &Objects[shipp->objnum];
1822 matrix* orient = &parent_ship_obj->orient;
1823 for (int ii=0; ii<NUM_SUB_EXPL_HANDLES; ii++) {
1824 split_ship->sound_handle[ii] = shipp->sub_expl_sound_handle[ii];
1827 // play 3d sound for shockwave explosion
1828 snd_play_3d( &Snds[SND_SHOCKWAVE_EXPLODE], &parent_ship_obj->pos, &View_position, 0.0f, NULL, 0, 1.0f, SND_PRIORITY_SINGLE_INSTANCE, NULL, 3.0f );
1830 // initialize both ships
1831 split_ship->front_ship.parent_obj = parent_ship_obj;
1832 split_ship->back_ship.parent_obj = parent_ship_obj;
1833 split_ship->explosion_flash_timestamp = timestamp((int)(0.00075f*parent_ship_obj->radius));
1834 split_ship->explosion_flash_started = 0;
1835 split_ship->front_ship.orient = *orient;
1836 split_ship->back_ship.orient = *orient;
1837 split_ship->front_ship.next_fireball = timestamp_rand(0, 100);
1838 split_ship->back_ship.next_fireball = timestamp_rand(0, 100);
1840 split_ship->front_ship.clip_plane_norm = vmd_z_vector;
1841 vm_vec_copy_scale(&split_ship->back_ship.clip_plane_norm, &vmd_z_vector, -1.0f);
1843 // find the point at which the ship splits (relative to its pivot)
1844 polymodel* pm = model_get(shipp->modelnum);
1845 float init_clip_plane_dist;
1846 if (pm->num_split_plane > 0) {
1847 int index = rand()%pm->num_split_plane;
1848 init_clip_plane_dist = pm->split_plane[index];
1850 init_clip_plane_dist = 0.5f * (0.5f - frand())*pm->core_radius;
1853 split_ship->back_ship.cur_clip_plane_pt = init_clip_plane_dist;
1854 split_ship->front_ship.cur_clip_plane_pt = init_clip_plane_dist;
1857 dist = (split_ship->front_ship.cur_clip_plane_pt+pm->maxs.xyz.z)/2.0f;
1858 vm_vec_copy_scale(&split_ship->front_ship.local_pivot, &orient->v.fvec, dist);
1859 (void) vm_vec_make(&split_ship->front_ship.model_center_disp_to_orig_center, 0.0f, 0.0f, -dist);
1860 dist = (split_ship->back_ship.cur_clip_plane_pt +pm->mins.xyz.z)/2.0f;
1861 vm_vec_copy_scale(&split_ship->back_ship.local_pivot, &orient->v.fvec, dist);
1862 (void) vm_vec_make(&split_ship->back_ship.model_center_disp_to_orig_center, 0.0f, 0.0f, -dist);
1863 vm_vec_add2(&split_ship->front_ship.local_pivot, &parent_ship_obj->pos );
1864 vm_vec_add2(&split_ship->back_ship.local_pivot, &parent_ship_obj->pos );
1866 // find which debris pieces are in the front and back split ships
1867 for (int i=0; i<pm->num_debris_objects; i++ ) {
1868 vector temp_pos = {0.0f, 0.0f, 0.0f};
1869 vector tmp = {0.0f, 0.0f, 0.0f };
1870 vector tmp1 = pm->submodel[pm->debris_objects[i]].offset;
1871 // tmp is world position, temp_pos is world_pivot, tmp1 is offset from world_pivot (in ship local coord)
1872 model_find_world_point(&tmp, &tmp1, shipp->modelnum, -1, &vmd_identity_matrix, &temp_pos );
1873 if (tmp.xyz.z > init_clip_plane_dist) {
1874 split_ship->front_ship.draw_debris[i] = DEBRIS_DRAW;
1875 split_ship->back_ship.draw_debris[i] = DEBRIS_NONE;
1877 split_ship->front_ship.draw_debris[i] = DEBRIS_NONE;
1878 split_ship->back_ship.draw_debris[i] = DEBRIS_DRAW;
1883 // set the remaining debris slots to not draw
1884 for (i=pm->num_debris_objects; i<MAX_DEBRIS_OBJECTS; i++) {
1885 split_ship->front_ship.draw_debris[i] = DEBRIS_NONE;
1886 split_ship->back_ship.draw_debris[i] = DEBRIS_NONE;
1890 physics_init( &split_ship->front_ship.phys_info );
1891 physics_init( &split_ship->back_ship.phys_info );
1892 split_ship->front_ship.phys_info.flags |= (PF_ACCELERATES | PF_DEAD_DAMP);
1893 split_ship->back_ship.phys_info.flags |= (PF_ACCELERATES | PF_DEAD_DAMP);
1894 split_ship->front_ship.phys_info.side_slip_time_const = 10000.0f;
1895 split_ship->back_ship.phys_info.side_slip_time_const = 10000.0f;
1896 split_ship->front_ship.phys_info.rotdamp = 10000.0f;
1897 split_ship->back_ship.phys_info.rotdamp = 10000.0f;
1899 // set up explosion vel and relative velocities (assuming mass depends on length)
1900 float front_length = pm->maxs.xyz.z - split_ship->front_ship.cur_clip_plane_pt;
1901 float back_length = split_ship->back_ship.cur_clip_plane_pt - pm->mins.xyz.z;
1902 float ship_length = front_length + back_length;
1903 split_ship->front_ship.length_left = front_length;
1904 split_ship->back_ship.length_left = back_length;
1906 float expl_length_scale = (ship_length - 200.0f) / 2000.0f;
1907 // s_r_f effects speed of "wipe" and rotvel
1908 float speed_reduction_factor = (1.0f + 0.001f*parent_ship_obj->radius);
1909 float explosion_time = (3.0f + expl_length_scale + (frand()-0.5f)) * speed_reduction_factor;
1910 float long_length = max(front_length, back_length);
1911 float expl_vel = long_length / explosion_time;
1912 split_ship->front_ship.explosion_vel = expl_vel;
1913 split_ship->back_ship.explosion_vel = -expl_vel;
1915 float rel_vel = (0.6f + 0.2f*frand()) * expl_vel * speed_reduction_factor;
1916 float front_vel = rel_vel * back_length / ship_length;
1917 float back_vel = -rel_vel * front_length / ship_length;
1918 // mprintf(("rel_vel %.1f, expl_vel %.1f\n", rel_vel, expl_vel));
1920 // set up rotational vel
1922 vm_vec_rand_vec_quick(&rotvel);
1923 rotvel.xyz.z = 0.0f;
1924 vm_vec_normalize(&rotvel);
1925 vm_vec_scale(&rotvel, 0.15f / speed_reduction_factor);
1926 split_ship->front_ship.phys_info.rotvel = rotvel;
1927 vm_vec_copy_scale(&split_ship->back_ship.phys_info.rotvel, &rotvel, -(front_length*front_length)/(back_length*back_length));
1928 split_ship->front_ship.phys_info.rotvel.xyz.z = parent_ship_obj->phys_info.rotvel.xyz.z;
1929 split_ship->back_ship.phys_info.rotvel.xyz.z = parent_ship_obj->phys_info.rotvel.xyz.z;
1932 // modify vel of each split ship based on rotvel of parent ship obj
1933 vector temp_rotvel = parent_ship_obj->phys_info.rotvel;
1934 temp_rotvel.xyz.z = 0.0f;
1935 vector vel_from_rotvel;
1936 vm_vec_crossprod(&vel_from_rotvel, &temp_rotvel, &split_ship->front_ship.local_pivot);
1937 // vm_vec_scale_add2(&split_ship->front_ship.phys_info.vel, &vel_from_rotvel, 0.5f);
1938 vm_vec_crossprod(&vel_from_rotvel, &temp_rotvel, &split_ship->back_ship.local_pivot);
1939 // vm_vec_scale_add2(&split_ship->back_ship.phys_info.vel, &vel_from_rotvel, 0.5f);
1941 // set up velocity and make initial fireballs and particles
1942 split_ship->front_ship.phys_info.vel = parent_ship_obj->phys_info.vel;
1943 split_ship->back_ship.phys_info.vel = parent_ship_obj->phys_info.vel;
1944 maybe_fireball_wipe(&split_ship->front_ship, (int*)&split_ship->sound_handle);
1945 maybe_fireball_wipe(&split_ship->back_ship, (int*)&split_ship->sound_handle);
1946 vm_vec_scale_add2(&split_ship->front_ship.phys_info.vel, &orient->v.fvec, front_vel);
1947 vm_vec_scale_add2(&split_ship->back_ship.phys_info.vel, &orient->v.fvec, back_vel);
1949 // HANDLE LIVE DEBRIS - blow off if not already gone
1950 shipfx_maybe_create_live_debris_at_ship_death( parent_ship_obj );
1954 static void half_ship_render_ship_and_debris(clip_ship* half_ship,ship *shipp)
1956 Assert( Split_ships_inited );
1958 polymodel *pm = model_get(shipp->modelnum);
1960 // get rotated clip plane normal and world coord of original ship center
1961 vector orig_ship_world_center, clip_plane_norm, model_clip_plane_pt, debris_clip_plane_pt;
1962 vm_vec_unrotate(&clip_plane_norm, &half_ship->clip_plane_norm, &half_ship->orient);
1963 vm_vec_unrotate(&orig_ship_world_center, &half_ship->model_center_disp_to_orig_center, &half_ship->orient);
1964 vm_vec_add2(&orig_ship_world_center, &half_ship->local_pivot);
1966 // *out_pivot = orig_ship_world_center;
1968 // get debris clip plane pt and draw debris
1969 vm_vec_unrotate(&debris_clip_plane_pt, &half_ship->model_center_disp_to_orig_center, &half_ship->orient);
1970 vm_vec_add2(&debris_clip_plane_pt, &half_ship->local_pivot);
1971 g3_start_user_clip_plane( &debris_clip_plane_pt, &clip_plane_norm);
1973 // set up render flags
1974 uint render_flags = MR_NORMAL;
1976 for (int i=0; i<pm->num_debris_objects; i++ ) {
1977 // draw DEBRIS_FREE in test only
1978 if (half_ship->draw_debris[i] == DEBRIS_DRAW) {
1979 vector temp_pos = orig_ship_world_center;
1980 vector tmp = {0.0f, 0.0f, 0.0f};
1981 vector tmp1 = pm->submodel[pm->debris_objects[i]].offset;
1983 // determine if explosion front has past debris piece
1984 // 67 ~ dist expl moves in 2 frames -- maybe fraction works better
1985 int is_live_debris = pm->submodel[pm->debris_objects[i]].is_live_debris;
1986 int create_debris = 0;
1988 if (half_ship->explosion_vel > 0) {
1989 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) {
1992 // is the debris visible
1993 // if (half_ship->cur_clip_plane_pt > tmp1.xyz.z + pm->submodel[pm->debris_objects[i]].min.xyz.z - 0.5f*half_ship->explosion_vel) {
1994 // render_debris = 1;
1998 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) {
2001 // is the debris visible
2002 // if (half_ship->cur_clip_plane_pt < tmp1.xyz.z + pm->submodel[pm->debris_objects[i]].max.xyz.z - 0.5f*half_ship->explosion_vel) {
2003 // render_debris = 1;
2007 // Draw debris, but not live debris
2008 if ( !is_live_debris ) {
2009 model_find_world_point(&tmp, &tmp1, shipp->modelnum, -1, &half_ship->orient, &temp_pos);
2010 submodel_render(shipp->modelnum, pm->debris_objects[i], &half_ship->orient, &tmp, render_flags);
2013 // make free piece of debris
2014 if ( create_debris ) {
2015 half_ship->draw_debris[i] = DEBRIS_FREE; // mark debris to not render with model
2016 vector center_to_debris, debris_vel, radial_vel;
2017 // check if last debris piece, ie, debris_count == 0
2018 int debris_count = 0;
2019 for (int j=0; j<pm->num_debris_objects; j++ ) {
2020 if (half_ship->draw_debris[j] == DEBRIS_DRAW) {
2024 // do debris create here, but not for live debris
2025 // debris vel (1) split ship vel (2) split ship rotvel (3) random
2026 if ( !is_live_debris ) {
2028 debris_obj = debris_create(half_ship->parent_obj, shipp->modelnum, pm->debris_objects[i], &tmp, &half_ship->local_pivot, 1, 1.0f);
2029 // AL: make sure debris_obj isn't NULL!
2031 vm_vec_scale(&debris_obj->phys_info.rotvel, 4.0f);
2032 debris_obj->orient = half_ship->orient;
2033 // if (debris_count > 0) {
2034 //mprintf(( "base rotvel %.1f, debris rotvel mag %.2f\n", vm_vec_mag(&half_ship->phys_info.rotvel), vm_vec_mag(&debris_obj->phys_info.rotvel) ));
2035 vm_vec_sub(¢er_to_debris, &tmp, &half_ship->local_pivot);
2036 vm_vec_crossprod(&debris_vel, ¢er_to_debris, &half_ship->phys_info.rotvel);
2037 vm_vec_add2(&debris_vel, &half_ship->phys_info.vel);
2038 vm_vec_copy_normalize(&radial_vel, ¢er_to_debris);
2039 float radial_mag = 10.0f + 30.0f*frand();
2040 vm_vec_scale_add2(&debris_vel, &radial_vel, radial_mag);
2041 debris_obj->phys_info.vel = debris_vel;
2043 debris_obj->phys_info.vel = half_ship->phys_info.vel;
2044 debris_obj->phys_info.rotvel = half_ship->phys_info.rotvel;
2052 // get model clip plane pt and draw model
2054 (void) vm_vec_make(&temp, 0.0f, 0.0f, half_ship->cur_clip_plane_pt);
2055 vm_vec_unrotate(&model_clip_plane_pt, &temp, &half_ship->orient);
2056 vm_vec_add2(&model_clip_plane_pt, &orig_ship_world_center);
2057 g3_start_user_clip_plane( &model_clip_plane_pt, &clip_plane_norm );
2058 model_render(shipp->modelnum, &half_ship->orient, &orig_ship_world_center, render_flags);
2061 void shipfx_large_blowup_level_init()
2063 split_ship_init_system();
2065 if(Ship_cannon_bitmap != -1){
2066 bm_unload(Ship_cannon_bitmap);
2067 Ship_cannon_bitmap = bm_load(SHIP_CANNON_BITMAP);
2071 // Returns 0 if couldn't init
2072 int shipfx_large_blowup_init(ship *shipp)
2075 if ( !Split_ships_inited ) {
2076 split_ship_init_system();
2080 for (i=0; i<MAX_SPLIT_SHIPS; i++ ) {
2081 if ( Split_ships[i].used == 0 ) {
2086 if ( i >= MAX_SPLIT_SHIPS ) {
2087 mprintf(( "Not enough split ship slots!! See John!\n" ));
2092 Split_ships[i].used = 1;
2093 shipp->large_ship_blowup_index = i;
2095 split_ship_init(shipp, &Split_ships[i] );
2100 // ----------------------------------------------------------------------------
2101 // uses list of model z values with constant increment to find the radius of the
2102 // cross section at the current model z value
2103 float get_model_cross_section_at_z(float z, polymodel* pm)
2105 if (pm->num_xc < 2) {
2109 float index, increment;
2110 increment = (pm->xc[pm->num_xc-1].z - pm->xc[0].z) / (float)(pm->num_xc - 1);
2111 index = (z - pm->xc[0].z) / increment;
2114 return pm->xc[0].radius;
2115 } else if (index > (pm->num_xc - 1.0f - 0.5f)) {
2116 return pm->xc[pm->num_xc-1].radius;
2118 int floor_index = (int)floor(index);
2119 int ceil_index = (int)ceil(index);
2120 return max(pm->xc[ceil_index].radius, pm->xc[floor_index].radius);
2124 // returns how long sound has been playing
2125 int get_sound_time_played(int snd_id, int handle)
2131 int bits_per_sample, frequency;
2132 snd_get_format(snd_id, &bits_per_sample, &frequency);
2133 int time_left = snd_time_remaining(handle, bits_per_sample, frequency);
2134 int duration = snd_get_duration(snd_id);
2136 return (duration - time_left);
2139 // sound manager for big ship sub explosions sounds.
2140 // forces playing of sub-explosion sounds. keeps track of active sounds, plays them for >= 750 ms
2141 // when sound has played >= 750, sound is stopped and new instance is started
2142 void do_sub_expl_sound(float radius, vector* sound_pos, int* sound_handle)
2144 int sound_index, handle;
2145 // multiplier for range (near and far distances) to apply attenuation
2146 float sound_range = 1.0f + 0.0043f*radius;
2148 int handle_index = rand()%NUM_SUB_EXPL_HANDLES;
2149 //mprintf(("handle_index %d\n", *handle_index));
2151 // sound_index = get_sub_explosion_sound_index(handle_index);
2152 sound_index = SND_SHIP_EXPLODE_1;
2153 handle = sound_handle[handle_index];
2156 // mprintf(("dist to sound %.1f snd_indx: %d, h1: %d, h2: %d\n", vm_vec_dist(&Player_obj->pos, sound_pos), next_sound_index, sound_handle[0], sound_handle[1]));
2159 // if no handle, get one
2160 sound_handle[handle_index] = snd_play_3d( &Snds[sound_index], sound_pos, &View_position, 0.0f, NULL, 0, 0.6f, SND_PRIORITY_MUST_PLAY, NULL, sound_range );
2161 } else if (!snd_is_playing(handle)) {
2162 // if sound not playing and old, get new one
2163 // I don't think will happen with SND_PRIORITY_MUST_PLAY
2164 if (get_sound_time_played(Snds[sound_index].id, handle) > 400) {
2165 //mprintf(("sound not playing %d, time_played %d, stopped\n", handle, get_sound_time_played(Snds[sound_index].id, handle)));
2166 snd_stop(sound_handle[handle_index]);
2167 sound_handle[handle_index] = snd_play_3d( &Snds[sound_index], sound_pos, &View_position, 0.0f, NULL, 0, 0.6f, SND_PRIORITY_MUST_PLAY, NULL, sound_range );
2169 } else if (get_sound_time_played(Snds[sound_index].id, handle) > 750) {
2170 //mprintf(("time %f, cur sound %d time_played %d num sounds %d\n", f2fl(Missiontime), handle_index, get_sound_time_played(Snds[sound_index].id, handle), snd_num_playing() ));
2171 sound_handle[handle_index] = snd_play_3d( &Snds[sound_index], sound_pos, &View_position, 0.0f, NULL, 0, 0.6f, SND_PRIORITY_MUST_PLAY, NULL, sound_range );
2175 // maybe create a fireball along model clip plane
2176 // also maybe plays explosion sound
2177 static void maybe_fireball_wipe(clip_ship* half_ship, int* sound_handle)
2179 // maybe make fireball to cover wipe.
2180 if ( timestamp_elapsed(half_ship->next_fireball) ) {
2181 if ( half_ship->length_left > 0.2f*fl_abs(half_ship->explosion_vel) ) {
2183 polymodel* pm = model_get(Ships[half_ship->parent_obj->instance].modelnum);
2185 vector model_clip_plane_pt, orig_ship_world_center, temp;
2187 vm_vec_unrotate(&orig_ship_world_center, &half_ship->model_center_disp_to_orig_center, &half_ship->orient);
2188 vm_vec_add2(&orig_ship_world_center, &half_ship->local_pivot);
2190 (void) vm_vec_make(&temp, 0.0f, 0.0f, half_ship->cur_clip_plane_pt);
2191 vm_vec_unrotate(&model_clip_plane_pt, &temp, &half_ship->orient);
2192 vm_vec_add2(&model_clip_plane_pt, &orig_ship_world_center);
2193 vm_vec_rand_vec_quick(&temp);
2194 vm_vec_scale(&temp, 0.1f*frand());
2195 vm_vec_add2(&model_clip_plane_pt, &temp);
2197 float rad = get_model_cross_section_at_z(half_ship->cur_clip_plane_pt, pm);
2199 rad = half_ship->parent_obj->radius * frand_range(0.4f, 0.6f);
2201 // make fireball radius (1.5 +/- .1) * model_cross_section value
2202 rad *= frand_range(1.4f, 1.6f);
2206 rad = min(rad, half_ship->parent_obj->radius);
2208 // mprintf(("xc %.1f model %.1f\n", rad, half_ship->parent_obj->radius*0.25));
2209 int fireball_type = FIREBALL_EXPLOSION_LARGE1 + rand()%FIREBALL_NUM_LARGE_EXPLOSIONS;
2210 int low_res_fireballs = Bs_exp_fire_low;
2211 fireball_create(&model_clip_plane_pt, fireball_type, OBJ_INDEX(half_ship->parent_obj), rad, 0, &half_ship->parent_obj->phys_info.vel, 0.0f, -1, NULL, low_res_fireballs);
2213 // start the next fireball up (3-4 per frame) + 30%
2214 int time_low, time_high;
2215 time_low = int(650 * Bs_exp_fire_time_mult);
2216 time_high = int(900 * Bs_exp_fire_time_mult);
2217 half_ship->next_fireball = timestamp_rand(time_low, time_high);
2220 do_sub_expl_sound(half_ship->parent_obj->radius, &model_clip_plane_pt, sound_handle);
2223 particle_emitter pe;
2225 pe.num_low = 40; // Lowest number of particles to create
2226 pe.num_high = 80; // Highest number of particles to create
2227 pe.pos = model_clip_plane_pt; // Where the particles emit from
2228 pe.vel = half_ship->phys_info.vel; // Initial velocity of all the particles
2230 #if defined(FS2_DEMO) || defined(FS1_DEMO)
2231 float range = 1.0f + 0.002f*half_ship->parent_obj->radius * 5.0f;
2233 float range = 1.0f + 0.002f*half_ship->parent_obj->radius;
2236 #if defined(FS2_DEMO) || defined(FS1_DEMO)
2237 pe.min_life = 2.0f*range; // How long the particles live
2238 pe.max_life = 10.0f*range; // How long the particles live
2240 pe.min_life = 0.5f*range; // How long the particles live
2241 pe.max_life = 6.0f*range; // How long the particles live
2243 pe.normal = vmd_x_vector; // What normal the particle emit around
2244 pe.normal_variance = 2.0f; // How close they stick to that normal 0=on normal, 1=180, 2=360 degree
2245 pe.min_vel = 0.0f; // How fast the slowest particle can move
2246 pe.max_vel = half_ship->explosion_vel; // How fast the fastest particle can move
2248 #if defined(FS2_DEMO) || defined(FS1_DEMO)
2249 float scale = half_ship->parent_obj->radius * 0.02f;
2251 float scale = half_ship->parent_obj->radius * 0.01f;
2253 pe.min_rad = 0.5f*scale; // Min radius
2254 pe.max_rad = 1.5f*scale; // Max radius
2256 particle_emit( &pe, PARTICLE_SMOKE2, 0, range );
2260 half_ship->next_fireball = timestamp(-1);
2266 // Returns 1 when explosion is done
2267 int shipfx_large_blowup_do_frame(ship *shipp, float frametime)
2269 // DAVE: I made this not do any movement just to try to get things working...
2272 Assert( Split_ships_inited );
2273 Assert( shipp->large_ship_blowup_index > -1 );
2275 split_ship *the_split_ship = &Split_ships[shipp->large_ship_blowup_index];
2276 Assert( the_split_ship->used ); // Get John
2278 // Do fireballs, particles, shockwave here
2279 // Note parent ship is still valid, vel and pos updated in obj_move_all
2281 if ( timestamp_elapsed(the_split_ship->explosion_flash_timestamp) ) {
2282 if ( !the_split_ship->explosion_flash_started ) {
2283 object* objp = &Objects[shipp->objnum];
2284 if (objp->flags & OF_WAS_RENDERED) {
2285 float excess_dist = vm_vec_dist(&Player_obj->pos, &objp->pos) - 2.0f*objp->radius - Player_obj->radius;
2286 float intensity = 1.0f - 0.1f*excess_dist / objp->radius;
2288 if (intensity > 1) {
2292 if (intensity > 0.1f) {
2293 // big_explosion_flash(intensity);
2296 the_split_ship->explosion_flash_started = 1;
2300 physics_sim(&the_split_ship->front_ship.local_pivot, &the_split_ship->front_ship.orient, &the_split_ship->front_ship.phys_info, frametime);
2301 physics_sim(&the_split_ship->back_ship.local_pivot, &the_split_ship->back_ship.orient, &the_split_ship->back_ship.phys_info, frametime);
2302 the_split_ship->front_ship.length_left -= the_split_ship->front_ship.explosion_vel*frametime;
2303 the_split_ship->back_ship.length_left += the_split_ship->back_ship.explosion_vel *frametime;
2304 the_split_ship->front_ship.cur_clip_plane_pt += the_split_ship->front_ship.explosion_vel*frametime;
2305 the_split_ship->back_ship.cur_clip_plane_pt += the_split_ship->back_ship.explosion_vel *frametime;
2307 float length_left = max( the_split_ship->front_ship.length_left, the_split_ship->back_ship.length_left );
2309 // mprintf(( "Blowup frame, dist = %.1f \n", length_left ));
2311 if ( length_left < 0 ) {
2312 the_split_ship->used = 0;
2316 maybe_fireball_wipe(&the_split_ship->front_ship, (int*)&the_split_ship->sound_handle);
2317 maybe_fireball_wipe(&the_split_ship->back_ship, (int*)&the_split_ship->sound_handle);
2321 void shipfx_large_blowup_render(ship* shipp)
2323 // This actually renders the original model like it should render.
2324 // object *objp = &Objects[shipp->objnum];
2325 // model_render( shipp->modelnum, &objp->orient, &objp->pos, MR_NORMAL );
2328 Assert( Split_ships_inited );
2329 Assert( shipp->large_ship_blowup_index > -1 );
2331 split_ship *the_split_ship = &Split_ships[shipp->large_ship_blowup_index];
2332 Assert( the_split_ship->used ); // Get John
2334 // vector front_global_pivot, back_global_pivot;
2336 if (the_split_ship->front_ship.length_left > 0) {
2337 half_ship_render_ship_and_debris(&the_split_ship->front_ship,shipp);
2340 if (the_split_ship->back_ship.length_left > 0) {
2341 half_ship_render_ship_and_debris(&the_split_ship->back_ship,shipp);
2344 g3_stop_user_clip_plane();
2348 // ================== DO THE ELECTRIC ARCING STUFF =====================
2349 // Creates any new ones, moves old ones.
2351 #define MAX_ARC_LENGTH_PERCENTAGE 0.25f
2353 #define MAX_EMP_ARC_TIMESTAMP (150.0f)
2355 void shipfx_do_damaged_arcs_frame( ship *shipp )
2359 object *obj = &Objects[shipp->objnum];
2360 ship_info * sip = &Ship_info[shipp->ship_info_index];
2364 float damage = obj->hull_strength / sip->initial_hull_strength;
2370 // don't draw an arc based on damage
2371 if ( damage > 0.30f ) {
2376 // we should draw an arc
2377 if( shipp->emp_intensity > 0.0f){
2381 // Kill off old sparks
2382 for(i=0; i<MAX_SHIP_ARCS; i++){
2383 if(timestamp_valid(shipp->arc_timestamp[i]) && timestamp_elapsed(shipp->arc_timestamp[i])){
2384 shipp->arc_timestamp[i] = timestamp(-1);
2388 // if we shouldn't draw an arc, return
2393 if (!timestamp_valid(shipp->arc_next_time)) {
2394 // start the next fireball up in the next 10 seconds or so...
2397 // if the emp effect is active
2398 if(shipp->emp_intensity > 0.0f){
2399 freq = fl2i(MAX_EMP_ARC_TIMESTAMP);
2401 // otherwise if we're arcing based upon damage
2403 freq = fl2i((damage+0.1f)*5000.0f);
2406 // set the next arc time
2407 shipp->arc_next_time = timestamp_rand(freq*2,freq*4);
2410 if ( timestamp_elapsed(shipp->arc_next_time) ) {
2412 shipp->arc_next_time = timestamp(-1); // invalid, so it gets restarted next frame
2414 //mprintf(( "Creating new ship arc!\n" ));
2416 int n, n_arcs = ((rand()>>5) % 3)+1; // Create 1-3 sparks
2418 vector v1, v2, v3, v4;
2419 submodel_get_two_random_points( shipp->modelnum, -1, &v1, &v2 );
2420 submodel_get_two_random_points( shipp->modelnum, -1, &v3, &v4 );
2422 // For large ships, cap the length to be 25% of max radius
2423 if ( obj->radius > 200.0f ) {
2424 float max_dist = obj->radius * MAX_ARC_LENGTH_PERCENTAGE;
2430 vm_vec_sub( &tmp, &v1, &v2 );
2431 d = vm_vec_mag_quick( &tmp );
2432 if ( d > max_dist ) {
2433 vm_vec_scale_add( &v1, &v2, &tmp, max_dist / d );
2437 vm_vec_sub( &tmp, &v3, &v2 );
2438 d = vm_vec_mag_quick( &tmp );
2439 if ( d > max_dist ) {
2440 vm_vec_scale_add( &v3, &v2, &tmp, max_dist / d );
2445 vm_vec_sub( &tmp, &v4, &v2 );
2446 d = vm_vec_mag_quick( &tmp );
2447 if ( d > max_dist ) {
2448 vm_vec_scale_add( &v4, &v2, &tmp, max_dist / d );
2455 // int a = 100, b = 1000;
2456 float factor = 1.0f + 0.0025f*obj->radius;
2457 int a = (int) (factor*100.0f);
2458 int b = (int) (factor*1000.0f);
2459 int lifetime = (myrand()%((b)-(a)+1))+(a);
2461 // Create the arc effects
2462 for (i=0; i<MAX_SHIP_ARCS; i++ ) {
2463 if ( !timestamp_valid( shipp->arc_timestamp[i] ) ) {
2464 //shipp->arc_timestamp[i] = timestamp_rand(400,1000); // live up to a second
2465 shipp->arc_timestamp[i] = timestamp(lifetime); // live up to a second
2469 shipp->arc_pts[i][0] = v1;
2470 shipp->arc_pts[i][1] = v2;
2473 shipp->arc_pts[i][0] = v2;
2474 shipp->arc_pts[i][1] = v3;
2478 shipp->arc_pts[i][0] = v2;
2479 shipp->arc_pts[i][1] = v4;
2486 // determine what kind of arc to create
2487 if(shipp->emp_intensity > 0.0f){
2488 shipp->arc_type[i] = MARC_TYPE_EMP;
2490 shipp->arc_type[i] = MARC_TYPE_NORMAL;
2495 break; // Don't need to create anymore
2498 // rotate v2 out of local coordinates into world.
2499 // Use v2 since it is used in every bolt. See above switch().
2501 vm_vec_unrotate(&snd_pos, &v2, &obj->orient);
2502 vm_vec_add2(&snd_pos, &obj->pos );
2504 //Play a sound effect
2505 if ( lifetime > 750 ) {
2506 // 1.00 second effect
2507 snd_play_3d( &Snds[SND_DEBRIS_ARC_05], &snd_pos, &View_position, obj->radius );
2508 } else if ( lifetime > 500 ) {
2509 // 0.75 second effect
2510 snd_play_3d( &Snds[SND_DEBRIS_ARC_04], &snd_pos, &View_position, obj->radius );
2511 } else if ( lifetime > 250 ) {
2512 // 0.50 second effect
2513 snd_play_3d( &Snds[SND_DEBRIS_ARC_03], &snd_pos, &View_position, obj->radius );
2514 } else if ( lifetime > 100 ) {
2515 // 0.25 second effect
2516 snd_play_3d( &Snds[SND_DEBRIS_ARC_02], &snd_pos, &View_position, obj->radius );
2518 // 0.10 second effect
2519 snd_play_3d( &Snds[SND_DEBRIS_ARC_01], &snd_pos, &View_position, obj->radius );
2524 // maybe move arc points around
2525 for (i=0; i<MAX_SHIP_ARCS; i++ ) {
2526 if ( timestamp_valid( shipp->arc_timestamp[i] ) ) {
2527 if ( !timestamp_elapsed( shipp->arc_timestamp[i] ) ) {
2528 // Maybe move a vertex.... 20% of the time maybe?
2530 if ( mr < RAND_MAX/5 ) {
2532 submodel_get_two_random_points( shipp->modelnum, -1, &v1, &v2 );
2537 static_one = shipp->arc_pts[i][0];
2539 static_one = shipp->arc_pts[i][1];
2542 // For large ships, cap the length to be 25% of max radius
2543 if ( obj->radius > 200.0f ) {
2544 float max_dist = obj->radius * MAX_ARC_LENGTH_PERCENTAGE;
2550 vm_vec_sub( &tmp, &v1, &static_one );
2551 d = vm_vec_mag_quick( &tmp );
2552 if ( d > max_dist ) {
2553 vm_vec_scale_add( &v1, &static_one, &tmp, max_dist / d );
2557 shipp->arc_pts[i][mr % 2] = v1;
2564 int l_cruiser_count = 1;
2565 int l_big_count = 2;
2566 int l_huge_count = 3;
2567 float l_max_radius = 3000.0f;
2568 void shipfx_do_lightning_frame( ship *shipp )
2574 vector v1, v2, n1, n2, temp, temp2;
2578 Assert(shipp != NULL);
2582 Assert(shipp->ship_info_index >= 0);
2583 if(shipp->ship_info_index < 0){
2586 Assert(shipp->objnum >= 0);
2587 if(shipp->objnum < 0){
2591 // get some pointers
2592 sip = &Ship_info[shipp->ship_info_index];
2593 objp = &Objects[shipp->objnum];
2595 // if this is not a nebula mission, don't do anything
2596 if(!(The_mission.flags & MISSION_FLAG_FULLNEB)){
2597 shipp->lightning_stamp = -1;
2601 // if this not a cruiser or big ship
2602 if(!((sip->flags & SIF_CRUISER) || (sip->flags & SIF_BIG_SHIP) || (sip->flags & SIF_HUGE_SHIP))){
2603 shipp->lightning_stamp = -1;
2607 // determine stamp and count values
2608 if(sip->flags & SIF_CRUISER){
2609 stamp = (int)((float)(Nebl_cruiser_min + ((Nebl_cruiser_max - Nebl_cruiser_min) * Nebl_intensity)) * frand_range(0.8f, 1.1f));
2610 count = l_cruiser_count;
2613 if(sip->flags & SIF_HUGE_SHIP){
2614 stamp = (int)((float)(Nebl_supercap_min + ((Nebl_supercap_max - Nebl_supercap_min) * Nebl_intensity)) * frand_range(0.8f, 1.1f));
2615 count = l_huge_count;
2617 stamp = (int)((float)(Nebl_cap_min + ((Nebl_cap_max - Nebl_cap_min) * Nebl_intensity)) * frand_range(0.8f, 1.1f));
2618 count = l_big_count;
2622 // if his timestamp is unset
2623 if(shipp->lightning_stamp == -1){
2624 shipp->lightning_stamp = timestamp(stamp);
2627 // if his timestamp is currently unelapsed
2628 if(!timestamp_elapsed(shipp->lightning_stamp)){
2632 mprintf(("SHIP BOLT\n"));
2634 // restamp him first
2635 shipp->lightning_stamp = timestamp(stamp);
2637 // ah, now we can create some lightning bolts
2638 count = (int)frand_range(0.0f, (float)count);
2640 // get 2 points on the hull of the ship
2641 submodel_get_two_random_points(shipp->modelnum, 0, &v1, &v2, &n1, &n2);
2643 // make up to 2 bolts
2644 if(objp->radius > l_max_radius){
2645 vm_vec_scale_add(&temp2, &v1, &n1, l_max_radius);
2647 vm_vec_scale_add(&temp2, &v1, &n1, objp->radius);
2649 vm_vec_unrotate(&temp, &temp2, &objp->orient);
2650 vm_vec_add2(&temp, &objp->pos);
2651 vm_vec_unrotate(&temp2, &v1, &objp->orient);
2652 vm_vec_add2(&temp2, &objp->pos);
2656 binfo.strike = temp2;
2657 binfo.num_strikes = 3;
2658 binfo.noise = 0.045f;
2660 binfo.delay = (int)frand_range(0.0f, 1600.0f);
2670 if(objp->radius > l_max_radius){
2671 vm_vec_scale_add(&temp2, &v2, &n2, l_max_radius);
2673 vm_vec_scale_add(&temp2, &v2, &n2, objp->radius);
2675 vm_vec_unrotate(&temp, &temp2, &objp->orient);
2676 vm_vec_add2(&temp, &objp->pos);
2677 vm_vec_unrotate(&temp2, &v2, &objp->orient);
2678 vm_vec_add2(&temp2, &objp->pos);
2682 binfo.strike = temp2;
2683 binfo.num_strikes = 3;
2684 binfo.noise = 0.045f;
2686 binfo.delay = (int)frand_range(0.0f, 1600.0f);
2693 // do all shockwaves for a ship blowing up
2694 void shipfx_do_shockwave_stuff(ship *shipp, shockwave_create_info *sci)
2699 vector temp, dir, shockwave_pos;
2700 vector head = vmd_zero_vector;
2701 vector tail = vmd_zero_vector;
2702 float len, step, cur;
2706 Assert(shipp != NULL);
2710 Assert(shipp->ship_info_index >= 0);
2711 if(shipp->ship_info_index < 0){
2714 Assert(shipp->objnum >= 0);
2715 if(shipp->objnum < 0){
2718 Assert(sci != NULL);
2723 // get some pointers
2724 sip = &Ship_info[shipp->ship_info_index];
2725 objp = &Objects[shipp->objnum];
2727 Assert(sip->shockwave_count > 0);
2728 if(sip->shockwave_count <= 0){
2732 // get vectors at the head and tail of the object, dead center
2733 pm = model_get(shipp->modelnum);
2737 head.xyz.x = pm->submodel[0].offset.xyz.x;
2738 head.xyz.y = pm->submodel[0].offset.xyz.y;
2739 head.xyz.z = pm->maxs.xyz.z;
2741 tail.xyz.x = pm->submodel[0].offset.xyz.x;
2742 tail.xyz.y = pm->submodel[0].offset.xyz.y;
2743 tail.xyz.z = pm->mins.xyz.z;
2745 // transform the vectors into world coords
2746 vm_vec_unrotate(&temp, &head, &objp->orient);
2747 vm_vec_add(&head, &temp, &objp->pos);
2748 vm_vec_unrotate(&temp, &tail, &objp->orient);
2749 vm_vec_add(&tail, &temp, &objp->pos);
2751 // now create as many shockwaves as needed
2752 vm_vec_sub(&dir, &head, &tail);
2753 len = vm_vec_mag(&dir);
2754 step = 1.0f / ((float)sip->shockwave_count + 1.0f);
2756 for(idx=0; idx<sip->shockwave_count; idx++){
2757 // get the shockwave position
2759 vm_vec_scale(&temp, cur);
2760 vm_vec_add(&shockwave_pos, &tail, &temp);
2762 // if knossos device, make shockwave in center
2763 if (Ship_info[shipp->ship_info_index].flags & SIF_KNOSSOS_DEVICE) {
2764 shockwave_pos = Objects[shipp->objnum].pos;
2767 // create the shockwave
2768 shockwave_create_info sci2;
2769 sci2.blast = (sci->blast / (float)sip->shockwave_count) * frand_range(0.75f, 1.25f);
2770 sci2.damage = (sci->damage / (float)sip->shockwave_count) * frand_range(0.75f, 1.25f);
2771 sci2.inner_rad = sci->inner_rad;
2772 sci2.outer_rad = sci->outer_rad;
2773 sci2.speed = sci->speed * frand_range(0.75f, 1.25f);
2774 sci2.rot_angle = frand_range(0.0f, 359.0f);
2776 shockwave_create(shipp->objnum, &shockwave_pos, &sci2, SW_SHIP_DEATH, (int)frand_range(0.0f, 350.0f));
2777 // shockwave_create(shipp->objnum, &objp->pos, sip->shockwave_speed, sip->inner_rad, sip->outer_rad, sip->damage, sip->blast, SW_SHIP_DEATH);
2785 DCF_BOOL(engine_wash, Wash_on);
2786 #define ENGINE_WASH_CHECK_INTERVAL 250 // (4x sec)
2787 // Do engine wash effect for ship
2788 // Assumes length of engine wash is greater than radius of engine wash hemisphere
2789 void engine_wash_ship_process(ship *shipp)
2792 object *objp, *ship_objp, *max_ship_intensity_objp;
2793 int started_with_no_wash = shipp->wash_intensity <= 0 ? 1 : 0;
2799 Assert(shipp != NULL);
2800 Assert(shipp->objnum >= 0);
2801 objp = &Objects[shipp->objnum];
2805 vector world_thruster_pos, world_thruster_norm, apex, thruster_to_ship, apex_to_ship, temp;
2806 float dist_sqr, inset_depth, dot_to_ship, max_ship_intensity;
2809 float max_wash_dist, half_angle, radius_mult;
2811 // if this is not a fighter or bomber, we don't care
2812 if ((objp->type != OBJ_SHIP) || !(Ship_info[shipp->ship_info_index].flags & (SIF_FIGHTER|SIF_BOMBER)) ) {
2816 // is it time to check for engine wash
2817 int time_to_next_hit = timestamp_until(shipp->wash_timestamp);
2818 if (time_to_next_hit < 0) {
2819 if (time_to_next_hit < -ENGINE_WASH_CHECK_INTERVAL) {
2820 time_to_next_hit = 0;
2823 // keep interval constant independent of framerate
2824 shipp->wash_timestamp = timestamp(ENGINE_WASH_CHECK_INTERVAL + time_to_next_hit);
2826 // initialize wash params
2827 shipp->wash_intensity = 0.0f;
2828 vm_vec_zero(&shipp->wash_rot_axis);
2829 max_ship_intensity_objp = NULL;
2830 max_ship_intensity = 0;
2835 // only do damage if we're within half of the max wash distance
2838 // go thru Ship_used_list and check if we're in wash from CAP or SUPERCAP (SIF_HUGE)
2839 for (so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so)) {
2840 ship_objp = &Objects[so->objnum];
2842 // don't do small ships
2843 if ( (Ship_info[Ships[ship_objp->instance].ship_info_index].flags & SIF_SMALL_SHIP) ) {
2847 pm = model_get(Ships[ship_objp->instance].modelnum);
2848 float ship_intensity = 0;
2850 // if engines disabled, no engine wash
2851 if (ship_get_subsystem_strength(&Ships[ship_objp->instance], SUBSYSTEM_ENGINE) < 0.01) {
2856 if (ship_objp->phys_info.speed > 20.0f)
2859 speed_scale = ship_objp->phys_info.speed/20.0f;
2861 for (idx = 0; idx < pm->n_thrusters; idx++) {
2862 thruster_bank *bank = &pm->thrusters[idx];
2864 // check if thruster bank has engine wash
2865 if (bank->wash_info_index < 0) {
2866 // if huge, give default engine wash
2867 if (Ship_info[Ships[ship_objp->instance].ship_info_index].flags & SIF_HUGE_SHIP) {
2868 bank->wash_info_index = 0;
2869 nprintf(("wash", "Adding default engine wash to ship %s", Ship_info[Ships[ship_objp->instance].ship_info_index].name));
2875 engine_wash_info *ewp = &Engine_wash_info[bank->wash_info_index];
2876 half_angle = ewp->angle;
2877 radius_mult = ewp->radius_mult;
2879 for (j=0; j<bank->num_slots; j++) {
2880 // get world pos of thruster
2881 vm_vec_unrotate(&world_thruster_pos, &bank->pnt[j], &ship_objp->orient);
2882 vm_vec_add2(&world_thruster_pos, &ship_objp->pos);
2884 // get world norm of thruster;
2885 vm_vec_unrotate(&world_thruster_norm, &bank->norm[j], &ship_objp->orient);
2887 // get vector from thruster to ship
2888 vm_vec_sub(&thruster_to_ship, &objp->pos, &world_thruster_pos);
2890 // check if on back side of thruster
2891 dot_to_ship = vm_vec_dotprod(&thruster_to_ship, &world_thruster_norm);
2892 if (dot_to_ship > 0) {
2894 // get max wash distance
2895 max_wash_dist = max(ewp->length, bank->radius[j]*ewp->radius_mult);
2897 // check if within dist range
2898 dist_sqr = vm_vec_mag_squared(&thruster_to_ship);
2899 if (dist_sqr < max_wash_dist*max_wash_dist) {
2901 // check if inside the sphere
2902 if (dist_sqr < radius_mult*radius_mult*bank->radius[j]*bank->radius[j]) {
2903 vm_vec_crossprod(&temp, &world_thruster_norm, &thruster_to_ship);
2904 vm_vec_scale_add2(&shipp->wash_rot_axis, &temp, dot_to_ship / dist_sqr);
2905 // shipp->wash_intensity += (1.0f - dist_sqr / (max_wash_dist*max_wash_dist));
2906 ship_intensity += (1.0f - dist_sqr / (max_wash_dist*max_wash_dist));
2908 if (dist_sqr < 0.25 * max_wash_dist * max_wash_dist) {
2913 // check if inside cone - first fine apex of cone
2914 inset_depth = float(bank->radius[j] / tan(half_angle));
2915 vm_vec_scale_add(&apex, &world_thruster_pos, &world_thruster_norm, -inset_depth);
2916 vm_vec_sub(&apex_to_ship, &objp->pos, &apex);
2917 vm_vec_normalize(&apex_to_ship);
2919 // check if inside cone angle
2920 if (vm_vec_dotprod(&apex_to_ship, &world_thruster_norm) > cos(half_angle)) {
2921 vm_vec_crossprod(&temp, &world_thruster_norm, &thruster_to_ship);
2922 vm_vec_scale_add2(&shipp->wash_rot_axis, &temp, dot_to_ship / dist_sqr);
2923 // shipp->wash_intensity += (1.0f - dist_sqr / (max_wash_dist*max_wash_dist));
2924 ship_intensity += (1.0f - dist_sqr / (max_wash_dist*max_wash_dist));
2926 if (dist_sqr < 0.25 * max_wash_dist * max_wash_dist) {
2936 shipp->wash_intensity += ship_intensity * speed_scale;
2937 if (ship_intensity > max_ship_intensity) {
2938 max_ship_intensity = ship_intensity;
2939 max_ship_intensity_objp = ship_objp;
2943 // apply damage at rate of 1%/sec
2944 if (shipp->wash_intensity > 0) {
2945 Assert(max_ship_intensity_objp != NULL);
2947 nprintf(("wash", "Wash intensity %.2f\n", shipp->wash_intensity));
2953 damage = (0.001f * 0.003f * ENGINE_WASH_CHECK_INTERVAL * Ship_info[shipp->ship_info_index].initial_hull_strength * shipp->wash_intensity);
2956 ship_apply_wash_damage(&Objects[shipp->objnum], max_ship_intensity_objp, damage);
2958 // if we had no wash before now, add the wash object sound
2959 if(started_with_no_wash){
2960 if(shipp != Player_ship){
2961 obj_snd_assign(shipp->objnum, SND_ENGINE_WASH, &vmd_zero_vector, 1);
2963 Player_engine_wash_loop = snd_play_looping( &Snds[SND_ENGINE_WASH], 0.0f , -1, -1, 1.0f);
2967 // if we've got no wash, kill any wash object sounds from this guy
2969 if(shipp != Player_ship){
2970 obj_snd_delete(shipp->objnum, SND_ENGINE_WASH);
2972 snd_stop(Player_engine_wash_loop);
2973 Player_engine_wash_loop = -1;
2978 // engine wash level init
2979 void shipfx_engine_wash_level_init()
2981 Player_engine_wash_loop = -1;
2984 // pause engine wash sounds
2985 void shipfx_stop_engine_wash_sound()
2987 if(Player_engine_wash_loop != -1){
2988 snd_stop(Player_engine_wash_loop);
2989 Player_engine_wash_loop = -1;