]> icculus.org git repositories - taylor/freespace2.git/blob - src/ship/shipfx.cpp
warning cleanup
[taylor/freespace2.git] / src / ship / shipfx.cpp
1 /*
2  * $Logfile: /Freespace2/code/Ship/ShipFX.cpp $
3  * $Revision$
4  * $Date$
5  * $Author$
6  *
7  * Routines for ship effects (as in special)
8  *
9  * $Log$
10  * Revision 1.2  2002/05/07 03:16:52  theoddone33
11  * The Great Newline Fix
12  *
13  * Revision 1.1.1.1  2002/05/03 03:28:10  root
14  * Initial import.
15  *
16  * 
17  * 51    9/13/99 4:53p Andsager
18  * 
19  * 50    9/13/99 10:09a Andsager
20  * Add debug console commands to lower model render detail and fireball
21  * LOD for big ship explosiosns.
22  * 
23  * 49    9/08/99 10:44p Andsager
24  * Make HUGE ships not die when warping out, after warp effect started.
25  * 
26  * 48    9/06/99 10:16p Andsager
27  * Modify big ship explosions.  Less frequent fireballs, fewer particles,
28  * larger fireballs
29  * 
30  * 47    9/05/99 11:10a Andsager
31  * Fix up which split ship get debris pieces.  Dont render debris pieces
32  * for split ships until they are near clip plane.
33  * 
34  * 46    9/02/99 12:55a Mikek
35  * Scale engine wash by speed.
36  * 
37  * 45    9/01/99 10:15a Dave
38  * 
39  * 44    8/31/99 10:13p Andsager
40  * Add Knossos warp effect fireball
41  * 
42  * 43    8/23/99 11:09a Andsager
43  * Round 2 of Knossos explosion
44  * 
45  * 42    8/20/99 2:16p Andsager
46  * Make only supercaps slow down extra fast after warping in.
47  * 
48  * 41    8/20/99 1:42p Andsager
49  * Frist pass on Knossos explosion.
50  * 
51  * 40    8/19/99 4:36p Andsager
52  * Cleaned up special_warpout
53  * 
54  * 39    8/16/99 10:04p Andsager
55  * Add special-warp-dist and special-warpout-name sexp for Knossos device
56  * warpout.
57  * 
58  * 38    8/16/99 2:01p Andsager
59  * Knossos warp-in warp-out.
60  * 
61  * 37    8/13/99 10:49a Andsager
62  * Knossos and HUGE ship warp out.  HUGE ship warp in.  Stealth search
63  * modes dont collide big ships.
64  * 
65  * 36    8/05/99 2:06a Dave
66  * Whee.
67  * 
68  * 35    7/18/99 12:32p Dave
69  * Randomly oriented shockwaves.
70  * 
71  * 34    7/09/99 12:52a Andsager
72  * Modify engine wash (1) less damage (2) only at a closer range (3) no
73  * damage when engine is disabled
74  * 
75  * 33    7/06/99 10:45a Andsager
76  * Modify engine wash to work on any ship that is not small.  Add AWACS
77  * ask for help.
78  * 
79  * 32    7/02/99 9:55p Dave
80  * Player engine wash sound.
81  * 
82  * 31    7/02/99 4:31p Dave
83  * Much more sophisticated lightning support.
84  * 
85  * 30    7/01/99 4:23p Dave
86  * Full support for multiple linked ambient engine sounds. Added "big
87  * damage" flag.
88  * 
89  * 29    7/01/99 11:44a Dave
90  * Updated object sound system to allow multiple obj sounds per ship.
91  * Added hit-by-beam sound. Added killed by beam sound.
92  * 
93  * 28    6/17/99 12:06p Andsager
94  * Add a fireball for each live debris piece.
95  * 
96  * 27    6/14/99 3:21p Andsager
97  * Allow collisions between ship and its debris.  Fix up collision pairs
98  * when large ship is warping out.
99  * 
100  * 26    5/27/99 12:14p Andsager
101  * Some fixes for live debris when more than one subsys on ship with live
102  * debris.  Set subsys strength (when 0) blows off subsystem.
103  * sexp_hits_left_subsystem works for SUBSYSTEM_UNKNOWN.
104  * 
105  * 25    5/26/99 11:46a Dave
106  * Added ship-blasting lighting and made the randomization of lighting
107  * much more customizable.
108  * 
109  * 24    5/25/99 10:05a Andsager
110  * Fix assert with wing warping out with no warp effect.
111  * 
112  * 23    5/24/99 5:45p Dave
113  * Added detail levels to the nebula, with a decent speedup. Split nebula
114  * lightning into its own section.
115  * 
116  * 22    5/21/99 5:03p Andsager
117  * Add code to display engine wash death.  Modify ship_kill_packet
118  * 
119  * 21    5/19/99 11:09a Andsager
120  * Turn on engine wash.  Check every 1/4 sec.
121  * 
122  * 20    5/18/99 1:30p Dave
123  * Added muzzle flash table stuff.
124  * 
125  * 19    5/17/99 10:33a Dave
126  * Fixed warning.
127  * 
128  * 18    5/11/99 10:16p Andsager
129  * First pass on engine wash effect.  Rotation (control input), damage,
130  * shake.  
131  * 
132  * 17    5/09/99 6:00p Dave
133  * Lots of cool new effects. E3 build tweaks.
134  * 
135  * 16    4/28/99 11:13p Dave
136  * Temporary checkin of artillery code.
137  * 
138  * 15    3/29/99 6:17p Dave
139  * More work on demo system. Got just about everything in except for
140  * blowing ships up, secondary weapons and player death/warpout.
141  * 
142  * 14    3/28/99 5:58p Dave
143  * Added early demo code. Make objects move. Nice and framerate
144  * independant, but not much else. Don't use yet unless you're me :)
145  * 
146  * 13    3/23/99 2:29p Andsager
147  * Fix shockwaves for kamikazi and Fred defined.  Collect together
148  * shockwave_create_info struct.
149  * 
150  * 12    3/20/99 3:03p Andsager
151  * Fix get_model_cross_section_at_z() from getting invalid index .
152  * 
153  * 11    3/19/99 9:51a Dave
154  * Checkin to repair massive source safe crash. Also added support for
155  * pof-style nebulae, and some new weapons code.
156  * 
157  * 10    2/26/99 4:14p Dave
158  * Put in the ability to have multiple shockwaves for ships.
159  * 
160  * 9     1/29/99 12:47a Dave
161  * Put in sounds for beam weapon. A bunch of interface screens (tech
162  * database stuff).
163  * 
164  * 8     1/28/99 9:10a Andsager
165  * Particles increased in width, life, number.  Max particles increased
166  * 
167  * 7     1/27/99 9:56a Dave
168  * Temporary checkin of beam weapons for Dan to make cool sounds.
169  * 
170  * 6     1/11/99 12:42p Andsager
171  * Add live debris - debris which is created from a destroyed subsystem,
172  * when the ship is still alive
173  * 
174  * 5     11/18/98 10:29a Johnson
175  * fix assert in shipfx_remove_submodel_ship_sparks.  submodel may not
176  * have spark and be destroyed.
177  * 
178  * 4     11/17/98 4:27p Andsager
179  * Stop sparks from emitting from destroyed subobjects
180  * 
181  * 3     10/20/98 1:39p Andsager
182  * Make so sparks follow animated ship submodels.  Modify
183  * ship_weapon_do_hit_stuff() and ship_apply_local_damage() to add
184  * submodel_num.  Add submodel_num to multiplayer hit packet.
185  * 
186  * 2     10/07/98 10:53a Dave
187  * Initial checkin.
188  * 
189  * 1     10/07/98 10:51a Dave
190  * 
191  *
192  * $NoKeywords: $
193  */
194
195 #include "pstypes.h"
196 #include "systemvars.h"
197 #include "ship.h"
198 #include "fireballs.h"
199 #include "debris.h"
200 #include "hudtarget.h"
201 #include "shipfx.h"
202 #include "multi.h"
203 #include "gamesnd.h"
204 #include "timer.h"
205 #include "3d.h"                 // needed for View_position, which is used when playing a 3D sound
206 #include "multi.h"
207 #include "multiutil.h"
208 #include "hud.h"
209 #include "fvi.h"
210 #include "gamesequence.h"
211 #include "lighting.h"
212 #include "linklist.h"
213 #include "particle.h"
214 #include "multimsgs.h"
215 #include "multiutil.h"
216 #include "bmpman.h"
217 #include "freespace.h"
218 #include "muzzleflash.h"
219 #include "demo.h"
220 #include "shiphit.h"
221 #include "neblightning.h"
222 #include "objectsnd.h"
223
224 #ifndef NDEBUG
225 extern float flFrametime;
226 extern int Framecount;
227 #endif
228
229 #define SHIP_CANNON_BITMAP                      "argh"
230 int Ship_cannon_bitmap = -1;
231
232 int Player_engine_wash_loop = -1;
233
234 void shipfx_remove_submodel_ship_sparks(ship *shipp, int submodel_num)
235 {
236         Assert(submodel_num != -1);
237
238         // maybe no active sparks on submodel
239         if (shipp->num_hits == 0) {
240                 return;
241         }
242         
243         for (int spark_num=0; spark_num<shipp->num_hits; spark_num++) {
244                 if (shipp->sparks[spark_num].submodel_num == submodel_num) {
245                         shipp->sparks[spark_num].end_time = timestamp(1);
246                 }
247         }
248 }
249
250 // Check if subsystem has live debris and create
251 // DKA: 5/26/99 make velocity of debris scale according to size of debris subobject (at least for large subobjects)
252 void shipfx_subsystem_mabye_create_live_debris(object *ship_obj, ship *ship_p, ship_subsys *subsys, vector *exp_center, float exp_mag)
253 {
254         // initializations
255         polymodel *pm = model_get(ship_p->modelnum);
256         int submodel_num = subsys->system_info->subobj_num;
257         submodel_instance_info *sii = &subsys->submodel_info_1;
258
259         object *live_debris_obj;
260         int i, num_live_debris, live_debris_submodel;
261
262         // get number of live debris objects to create
263         num_live_debris = pm->submodel[submodel_num].num_live_debris;
264         if (num_live_debris <= 0) {
265                 return;
266         }
267
268         ship_model_start(ship_obj);
269
270         // copy angles
271         angles copy_angs = pm->submodel[submodel_num].angs;
272         angles zero_angs = {0.0f, 0.0f, 0.0f};
273
274         // make sure the axis point is set
275         if ( !sii->axis_set ) {
276                 model_init_submodel_axis_pt(sii, ship_p->modelnum, submodel_num);
277         }
278
279         // get the rotvel
280         vector model_axis, world_axis, rotvel, world_axis_pt;
281         void model_get_rotating_submodel_axis(vector *model_axis, vector *world_axis, int modelnum, int submodel_num, object *obj);
282         model_get_rotating_submodel_axis(&model_axis, &world_axis, ship_p->modelnum, submodel_num, ship_obj);
283         vm_vec_copy_scale(&rotvel, &world_axis, sii->cur_turn_rate);
284
285         model_find_world_point(&world_axis_pt, &sii->pt_on_axis, ship_p->modelnum, submodel_num, &ship_obj->orient, &ship_obj->pos);
286
287         matrix m_rot;   // rotation for debris orient about axis
288         vm_quaternion_rotate(&m_rot, vm_vec_mag((vector*)&sii->angs), &model_axis);
289
290
291         // create live debris pieces
292         for (i=0; i<num_live_debris; i++) {
293                 live_debris_submodel = pm->submodel[submodel_num].live_debris[i];
294                 vector start_world_pos, start_model_pos, end_world_pos;
295
296                 // get start world pos
297                 vm_vec_zero(&start_world_pos);
298                 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 );
299
300                 // convert to model coord of underlying submodel
301                 // set angle to zero
302                 pm->submodel[submodel_num].angs = zero_angs;
303                 world_find_model_point(&start_model_pos, &start_world_pos, pm, submodel_num, &ship_obj->orient, &ship_obj->pos);
304
305                 // rotate from submodel coord to world coords
306                 // reset angle to current angle
307                 pm->submodel[submodel_num].angs = copy_angs;
308                 model_find_world_point(&end_world_pos, &start_model_pos, ship_p->modelnum, submodel_num, &ship_obj->orient, &ship_obj->pos);
309
310                 // create fireball here.
311                 fireball_create(&end_world_pos, FIREBALL_EXPLOSION_MEDIUM, OBJ_INDEX(ship_obj), pm->submodel[live_debris_submodel].rad);
312
313                 // create debris
314                 live_debris_obj = debris_create(ship_obj, ship_p->modelnum, live_debris_submodel, &end_world_pos, exp_center, 1, exp_mag);
315
316                 // only do if debris is created
317                 if (live_debris_obj) {
318                         // get radial velocity of debris
319                         vector delta_x, radial_vel;
320                         vm_vec_sub(&delta_x, &end_world_pos, &world_axis_pt);
321                         vm_vec_crossprod(&radial_vel, &rotvel, &delta_x);
322
323                         if (Ship_info[ship_p->ship_info_index].flags & SIF_KNOSSOS_DEVICE) {
324                                 // set velocity to cross center of knossos device
325                                 vector rand_vec, vec_to_center;
326
327                                 float vel_mag = vm_vec_mag_quick(&radial_vel) * 1.3f * (0.9f + 0.2f*frand());
328                                 vm_vec_normalized_dir(&vec_to_center, &world_axis_pt, &end_world_pos);
329                                 vm_vec_rand_vec_quick(&rand_vec);
330                                 vm_vec_scale_add2(&vec_to_center, &rand_vec, 0.2f);
331                                 vm_vec_scale_add2(&live_debris_obj->phys_info.vel, &vec_to_center, vel_mag);
332
333                         } else {
334                                 // Get rotation of debris object
335                                 matrix copy = live_debris_obj->orient;
336                                 vm_matrix_x_matrix(&live_debris_obj->orient, &copy, &m_rot);
337
338                                 // Add radial velocity (at least as large as exp velocity)
339                                 vector temp_vel;        // explosion velocity with ship_obj velocity removed
340                                 vm_vec_sub(&temp_vel, &live_debris_obj->phys_info.vel, &ship_obj->phys_info.vel);
341
342                                 // find magnitudes of radial and temp velocity
343                                 float vel_mag = vm_vec_mag(&temp_vel);
344                                 float rotvel_mag = vm_vec_mag(&radial_vel);
345
346                                 if (rotvel_mag > 0.1) {
347                                         float scale = (1.2f + 0.2f * frand()) * vel_mag / rotvel_mag;
348                                         // always add *at least* rotvel
349                                         if (scale < 1) {
350                                                 scale = 1.0f;
351                                         }
352
353                                         if (exp_mag > 1) {      // whole ship going down
354                                                 scale = exp_mag;
355                                         }
356
357                                         if (Ship_info[ship_p->ship_info_index].flags & SIF_KNOSSOS_DEVICE) {
358                                                 scale = 1.0f;
359                                         }
360
361                                         vm_vec_scale_add2(&live_debris_obj->phys_info.vel, &radial_vel, scale);
362                                 }
363
364                                 // scale up speed of debris if ship_obj > 125, but not for knossos
365                                 if (ship_obj->radius > 250 && !(Ship_info[ship_p->ship_info_index].flags & SIF_KNOSSOS_DEVICE)) {
366                                         vm_vec_scale(&live_debris_obj->phys_info.vel, ship_obj->radius/250.0f);
367                                 }
368                         }
369                 }
370         }
371
372         ship_model_stop(ship_obj);
373 }
374
375 void set_ship_submodel_as_blown_off(ship *shipp, char *name)
376 {
377         int found =     FALSE;
378
379         // go through list of ship subsystems and find name
380         ship_subsys     *pss = NULL;
381         for (pss=GET_FIRST(&shipp->subsys_list); pss!=END_OF_LIST(&shipp->subsys_list); pss=GET_NEXT(pss)) {
382                 if ( stricmp(pss->system_info->name, name) == 0) {
383                         found = TRUE;
384                         break;
385                 }
386         }
387
388         // set its blown off flag to TRUE
389         Assert(found);
390         if (found) {
391                 pss->submodel_info_1.blown_off = 1;
392         }
393 }
394
395
396 // Create debris for ship submodel which has live debris (at ship death)
397 // when ship submodel has not already been blown off (and hence liberated live debris)
398 void shipfx_maybe_create_live_debris_at_ship_death( object *ship_obj )
399 {
400         // if ship has live debris, detonate that subsystem now
401         // search for any live debris
402
403         ship *shipp = &Ships[ship_obj->instance];
404         polymodel *pm = model_get(shipp->modelnum);
405
406         int live_debris_submodel = -1;
407         for (int idx=0; idx<pm->num_debris_objects; idx++) {
408                 if (pm->submodel[pm->debris_objects[idx]].is_live_debris) {
409                         live_debris_submodel = pm->debris_objects[idx];
410
411                         // get submodel that produces live debris
412                         int model_get_parent_submodel_for_live_debris( int model_num, int live_debris_model_num );
413                         int parent = model_get_parent_submodel_for_live_debris( shipp->modelnum, live_debris_submodel);
414                         Assert(parent != -1);
415
416                         // set model values only once (esp blown off)
417                         ship_model_start(ship_obj);
418
419                         // check if already blown off  (ship model set)
420                         if ( !pm->submodel[parent].blown_off ) {
421                 
422                                 // get ship_subsys for live_debris
423                                 // Go through all subsystems and look for submodel the subsystems with "parent" submodel.
424                                 ship_subsys     *pss = NULL;
425                                 for ( pss = GET_FIRST(&shipp->subsys_list); pss != END_OF_LIST(&shipp->subsys_list); pss = GET_NEXT(pss) ) {
426                                         if (pss->system_info->subobj_num == parent) {
427                                                 break;
428                                         }
429                                 }
430
431                                 Assert (pss != NULL);
432                                 if (pss != NULL) {
433                                         vector exp_center, tmp = {0.0f, 0.0f, 0.0f};
434                                         model_find_world_point(&exp_center, &tmp, shipp->modelnum, parent, &ship_obj->orient, &ship_obj->pos );
435
436                                         // if not blown off, blow it off
437                                         shipfx_subsystem_mabye_create_live_debris(ship_obj, shipp, pss, &exp_center, 3.0f);
438
439                                         // now set subsystem as blown off, so we only get one copy
440                                         pm->submodel[parent].blown_off = 1;
441                                         set_ship_submodel_as_blown_off(&Ships[ship_obj->instance], pss->system_info->name);
442                                 }
443                         }
444                 }
445         }
446
447         // clean up
448         ship_model_stop(ship_obj);
449
450 }
451
452 void shipfx_blow_off_subsystem(object *ship_obj,ship *ship_p,ship_subsys *subsys, vector *exp_center)
453 {
454         vector subobj_pos;
455
456         model_subsystem *psub = subsys->system_info;
457
458         get_subsystem_world_pos(ship_obj, subsys, &subobj_pos);
459
460 /*
461         if ( psub->turret_gun_sobj > -1 )
462                 debris_create( ship_obj, ship_p->modelnum, psub->turret_gun_sobj, &subobj_pos, exp_center, 0, 1.0f );
463
464         if ( psub->subobj_num > -1 )
465                 debris_create( ship_obj, ship_p->modelnum, psub->subobj_num, &subobj_pos, exp_center, 0, 1.0f );
466 */
467         // get rid of sparks on submodel that is destroyed
468         shipfx_remove_submodel_ship_sparks(ship_p, psub->subobj_num);
469
470         // create debris shards
471         shipfx_blow_up_model(ship_obj, ship_p->modelnum, psub->subobj_num, 50, &subobj_pos );
472
473         // create live debris objects, if any
474         // TODO:  some MULITPLAYER implcations here!!
475         shipfx_subsystem_mabye_create_live_debris(ship_obj, ship_p, subsys, exp_center, 1.0f);
476
477         // create first fireball
478         fireball_create( &subobj_pos, FIREBALL_EXPLOSION_MEDIUM, OBJ_INDEX(ship_obj), psub->radius );
479 }
480
481
482 void shipfx_blow_up_hull(object *obj, int model, vector *exp_center)
483 {
484         int i;
485         polymodel * pm;
486         ushort sig_save;
487
488
489         pm = model_get(model);
490         if (!pm) return;
491
492         // in multiplayer, send a debris_hull_create packet.  Save/restore the debris signature
493         // when in misison only (since we can create debris pieces before mission starts)
494         sig_save = 0;
495         if ( (Game_mode & GM_MULTIPLAYER) && (Game_mode & GM_IN_MISSION) ) {
496                 sig_save = multi_get_next_network_signature( MULTI_SIG_DEBRIS );
497                 multi_set_network_signature( (ushort)(Ships[obj->instance].arrival_distance), MULTI_SIG_DEBRIS );
498         }
499
500         bool try_live_debris = true;
501         for (i=0; i<pm->num_debris_objects; i++ )       {
502                 if (! pm->submodel[pm->debris_objects[i]].is_live_debris) {
503                         vector tmp = {0.0f, 0.0f, 0.0f };               
504                         model_find_world_point(&tmp, &pm->submodel[pm->debris_objects[i]].offset, model, 0, &obj->orient, &obj->pos );
505                         debris_create( obj, model, pm->debris_objects[i], &tmp, exp_center, 1, 3.0f );
506                 } else {
507                         if ( try_live_debris ) {
508                                 // only create live debris once
509                                 // this creates *all* the live debris for *all* the currently live subsystems.
510                                 try_live_debris = false;
511                                 shipfx_maybe_create_live_debris_at_ship_death(obj);
512                         }
513                 }
514         }
515
516         // restore the ship signature to it's original value.
517         if ( (Game_mode & GM_MULTIPLAYER) && (Game_mode & GM_IN_MISSION) ) {
518                 multi_set_network_signature( sig_save, MULTI_SIG_DEBRIS );
519         }
520 }
521
522
523 // Creates "ndebris" pieces of debris on random verts of the the "submodel" in the 
524 // ship's model.
525 void shipfx_blow_up_model(object *obj,int model, int submodel, int ndebris, vector *exp_center )
526 {
527         int i;
528
529         // if in a multiplayer game -- seed the random number generator with a value that will be the same
530         // on all clients in the game -- the net_signature of the object works nicely -- since doing so should
531         // ensure that all pieces of debris will get scattered in same direction on all machines
532         if ( Game_mode & GM_MULTIPLAYER )
533                 srand( obj->net_signature );
534
535         // made a change to allow anyone but multiplayer client to blow up hull.  Clients will do it when
536         // they get the create packet
537         if ( submodel == 0 ) {
538                 shipfx_blow_up_hull(obj,model, exp_center );
539         }
540
541         for (i=0; i<ndebris; i++ )      {
542                 vector pnt1, pnt2;
543
544                 // Gets two random points on the surface of a submodel
545                 submodel_get_two_random_points(model, submodel, &pnt1, &pnt2 );
546
547                 vector tmp, outpnt;
548
549                 vm_vec_avg( &tmp, &pnt1, &pnt2 );
550                 model_find_world_point(&outpnt, &tmp, model,submodel, &obj->orient, &obj->pos );
551
552                 debris_create( obj, -1, -1, &outpnt, exp_center, 0, 1.0f );
553         }
554 }
555
556
557 // =================================================
558 //          SHIP WARP IN EFFECT CODE
559 // =================================================
560
561
562 // Given an ship, find the radius of it as viewed from the front.
563 static float shipfx_calculate_effect_radius( object *objp )
564 {
565         float w,h,rad;
566         ship *shipp = &Ships[objp->instance];
567
568         polymodel *pm = model_get( shipp->modelnum );
569
570         w = pm->maxs.x - pm->mins.x;
571         h = pm->maxs.y - pm->mins.y;
572         
573         if ( w > h )    {
574                 rad = w / 2.0f;
575         } else {
576                 rad = h / 2.0f;
577         }
578
579         object *docked_objp = ai_find_docked_object( objp );
580
581         //      If ship is docked then center wormhold about their center and make radius large enough.
582         if ( docked_objp ) {
583                 ship *docked_shipp = &Ships[docked_objp->instance];
584                 
585                 pm = model_get( docked_shipp->modelnum );
586                 
587                 w = pm->maxs.x - pm->mins.x;
588                 h = pm->maxs.y - pm->mins.y;
589                 
590                 if ( w > h )    {
591                         rad += w / 2.0f;
592                 } else {
593                         rad += h / 2.0f;
594                 }
595         }
596         return rad*3.0f;
597 }
598
599 // How long the stage 1 & stage 2 of warp in effect lasts.
600 // There are different times for small, medium, and large ships.
601 // The appropriate values are picked depending on the ship's
602 // radius.
603 #define SHIPFX_WARP_DELAY       (2.0f)          // time for warp effect to ramp up before ship moves into it.
604
605 // Give object objp, calculate how long it should take the
606 // ship to go through the warp effect and how fast the ship
607 // should go.   For reference,  capital ship of 2780m 
608 // should take 7 seconds to fly through.   Fighters of 30, 
609 // should take 1.5 seconds to fly through.
610
611 #define LARGEST_RAD 1390.0f 
612 #define LARGEST_RAD_TIME 7.0f
613
614 #define SMALLEST_RAD 15.0f
615 #define SMALLEST_RAD_TIME 1.5f
616
617
618 static float shipfx_calculate_warp_time( object * objp )
619 {
620         // Find rad_percent from 0 to 1, 0 being smallest ship, 1 being largest
621         float rad_percent = (objp->radius-SMALLEST_RAD) / (LARGEST_RAD-SMALLEST_RAD);
622         if ( rad_percent < 0.0f ) {
623                 rad_percent = 0.0f;
624         } else if ( rad_percent > 1.0f )        {
625                 rad_percent = 1.0f;
626         }
627         float rad_time = rad_percent*(LARGEST_RAD_TIME-SMALLEST_RAD_TIME) + SMALLEST_RAD_TIME;
628
629         return rad_time;
630 }
631
632 // calculate warp speed
633 float shipfx_calculate_warp_speed(object *objp)
634 {
635         float length;
636
637         Assert(objp->type == OBJ_SHIP);
638         if (objp->type != OBJ_SHIP) {
639                 length = 2.0f * objp->radius;
640         } else {
641                 length = ship_get_length(&Ships[objp->instance]);
642         }
643         return length / shipfx_calculate_warp_time(objp);
644 }
645
646
647 // This is called to actually warp this object in
648 // after all the flashy fx are done, or if the flashy 
649 // fx don't work for some reason.
650 void shipfx_actually_warpin(    ship *shipp, object *objp )
651 {
652         shipp->flags &= (~SF_ARRIVING_STAGE_1);
653         shipp->flags &= (~SF_ARRIVING_STAGE_2);
654
655         // let physics in on it too.
656         objp->phys_info.flags &= (~PF_WARP_IN);
657 }
658
659 // JAS - code to start the ship doing the warp in effect
660 // This also starts the animating 3d effect playing.
661 // There are two modes, stage 1 and stage 2.   Stage 1 is
662 // when the ship just invisibly waits for a certain time
663 // period after the effect starts, and then stage 2 begins,
664 // where the ships flies through the effect at a set
665 // velocity so it gets through in a certain amount of
666 // time.
667 void shipfx_warpin_start( object *objp )
668 {
669         ship *shipp;
670         float effect_time, effect_radius;
671         
672         shipp = &Ships[objp->instance];
673
674         if ( shipp->flags & SF_ARRIVING )       {
675                 mprintf(( "Ship is already arriving!\n" ));
676                 return;
677         }
678
679         // post a warpin event
680         if(Game_mode & GM_DEMO_RECORD){
681                 demo_POST_warpin(objp->signature, shipp->flags);
682         }
683
684         // if there is no arrival warp, then skip the whole thing
685         if ( shipp->flags & SF_NO_ARRIVAL_WARP )        {
686                 shipfx_actually_warpin(shipp,objp);
687                 return;
688         }
689         
690         // VALIDATE special_warp_objnum
691         if (shipp->special_warp_objnum >= 0) {
692                         
693                 int ref_objnum = shipp->special_warp_objnum;
694                 int valid_reference_ship = FALSE;
695
696                 // Validate reference_objnum
697                 if ((ref_objnum >= 0) && (ref_objnum < MAX_OBJECTS)) {
698                         object *sp_objp = &Objects[ref_objnum];
699                         if (sp_objp->type == OBJ_SHIP) {
700                                 if (Ship_info[Ships[sp_objp->instance].ship_info_index].flags & SIF_KNOSSOS_DEVICE) {
701                                         valid_reference_ship = TRUE;
702                                 }
703                         }
704                 }
705
706                 if (valid_reference_ship != TRUE) {
707                         shipp->special_warp_objnum = -1;
708                 }
709         }
710
711         // only move warp effect pos if not special warp in.
712         if (shipp->special_warp_objnum >= 0) {
713                 Assert(!(Game_mode & GM_MULTIPLAYER));
714                 polymodel *pm;
715                 pm = model_get(shipp->modelnum);
716                 vm_vec_scale_add(&shipp->warp_effect_pos, &objp->pos, &objp->orient.fvec, -pm->mins.z);
717         } else {
718                 vm_vec_scale_add( &shipp->warp_effect_pos, &objp->pos, &objp->orient.fvec, objp->radius );
719         }
720         
721         // The ending zero mean this is a warp-in effect
722         if ( !(shipp->flags & SF_INITIALLY_DOCKED) ) {
723                 int warp_objnum;
724
725                 // Effect time is 'SHIPFX_WARP_DELAY' (1.5 secs) seconds to start, 'shipfx_calculate_warp_time' 
726                 // for ship to go thru, and 'SHIPFX_WARP_DELAY' (1.5 secs) to go away.
727                 effect_time = shipfx_calculate_warp_time(objp) + SHIPFX_WARP_DELAY + SHIPFX_WARP_DELAY;
728                 effect_radius = shipfx_calculate_effect_radius(objp);
729
730                 // maybe special warpin
731                 if (shipp->special_warp_objnum >= 0) {
732                         // cap radius to size of knossos
733                         effect_radius = min(effect_radius, 0.8f*Objects[shipp->special_warp_objnum].radius);
734                         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);
735                 } else {
736                         warp_objnum = fireball_create(&shipp->warp_effect_pos, FIREBALL_WARP_EFFECT, OBJ_INDEX(objp), effect_radius, 0, NULL, effect_time, shipp->ship_info_index);
737                 }
738                 if (warp_objnum < 0 )   {       // JAS: This must always be created, if not, just warp the ship in
739                         shipfx_actually_warpin(shipp,objp);
740                         return;
741                 }
742
743                 shipp->warp_effect_fvec = Objects[warp_objnum].orient.fvec;
744                 // maybe negate if special warp effect
745                 if (shipp->special_warp_objnum >= 0) {
746                         if (vm_vec_dotprod(&shipp->warp_effect_fvec, &objp->orient.fvec) < 0) {
747                                 vm_vec_negate(&shipp->warp_effect_fvec);
748                         }
749                 }
750
751
752                 shipp->final_warp_time = timestamp(fl2i(SHIPFX_WARP_DELAY*1000.0f));
753                 shipp->flags |= SF_ARRIVING_STAGE_1;
754
755                 // see if this ship is docked with anything, and if so, make docked ship be "arriving"
756                 /*
757                 if ( Ai_info[shipp->ai_index].dock_objnum != -1 ) {
758                         Ships[Ai_info[shipp->ai_index].dock_objnum].final_warp_time = timestamp(fl2i(warp_time*1000.0f));
759                         Ships[Ai_info[shipp->ai_index].dock_objnum].flags |= SF_ARRIVING_STAGE_1;
760                 }
761                 */
762         }
763 }
764
765 void shipfx_warpin_frame( object *objp, float frametime )
766 {
767         ship *shipp;
768
769         shipp = &Ships[objp->instance];
770
771         if ( shipp->flags & SF_DYING ) return;
772
773         if ( shipp->flags & SF_ARRIVING_STAGE_1 )       {
774                 if ( timestamp_elapsed(shipp->final_warp_time) ) {
775
776                         // let physics know the ship is going to warp in.
777                         objp->phys_info.flags |= PF_WARP_IN;
778
779                         // done doing stage 1 of warp, so go on to stage 2
780                         shipp->flags &= (~SF_ARRIVING_STAGE_1);
781                         shipp->flags |= SF_ARRIVING_STAGE_2;
782
783                         float warp_time = shipfx_calculate_warp_time(objp);
784                         float speed = shipfx_calculate_warp_speed(objp);                        // How long it takes to move through warp effect
785
786                         // Make ship move at velocity so that it moves two radius's in warp_time seconds.
787                         vector vel;
788                         vel = objp->orient.fvec;
789                         vm_vec_scale( &vel, speed );
790                         objp->phys_info.vel = vel;
791                         objp->phys_info.desired_vel = vel;
792                         objp->phys_info.prev_ramp_vel.x = 0.0f;
793                         objp->phys_info.prev_ramp_vel.y = 0.0f;
794                         objp->phys_info.prev_ramp_vel.z = speed;
795                         objp->phys_info.forward_thrust = 0.0f;          // How much the forward thruster is applied.  0-1.
796
797                         shipp->final_warp_time = timestamp(fl2i(warp_time*1000.0f));
798
799                         /*
800                         // see if this ship is docked with anything, and if so, make docked ship be "arriving"
801                         if ( Ai_info[shipp->ai_index].dock_objnum != -1 ) {
802                                 Ships[Ai_info[shipp->ai_index].dock_objnum].flags &= (~SF_ARRIVING_STAGE_1);
803                                 Ships[Ai_info[shipp->ai_index].dock_objnum].flags |= SF_ARRIVING_STAGE_2;
804                                 Ships[Ai_info[shipp->ai_index].dock_objnum].final_warp_time = timestamp(fl2i(warp_time*1000.0f));
805                         }
806                         */
807                 } 
808         } else if ( shipp->flags & SF_ARRIVING_STAGE_2 )        {
809                 if ( timestamp_elapsed(shipp->final_warp_time) ) {
810                         // done doing stage 2 of warp, so turn off arriving flag
811                         shipfx_actually_warpin(shipp,objp);
812
813                         // notify physics to slow down
814                         if (Ship_info[shipp->ship_info_index].flags & SIF_SUPERCAP) {
815                                 // let physics know this is a special warp in
816                                 objp->phys_info.flags |= PF_SPECIAL_WARP_IN;
817                         }
818                 }
819         }
820
821 }
822  
823 // This is called to actually warp this object out
824 // after all the flashy fx are done, or if the flashy 
825 // fx don't work for some reason.  OR to skip the flashy
826 // fx.
827 void shipfx_actually_warpout( ship *shipp, object *objp )
828 {
829         // Once we get through effect, make the ship go away
830
831         if ( objp == Player_obj )       {
832                 // Normally, this will never get called for the player. If it
833                 // does, it is because some error (like the warpout effect
834                 // couldn't start) so go ahead an warp the player out.
835                 // All this does is set the event to go to debriefing, the
836                 // same thing that happens after the player warp out effect
837                 // ends.
838                 gameseq_post_event( GS_EVENT_DEBRIEF ); // proceed to debriefing
839         } else {
840                 object *docked_objp;
841                 
842                 // Code for objects except player ship warping out
843                 objp->flags |= OF_SHOULD_BE_DEAD;
844                 // check to see if departing ship is docked with anything and if so, mark that object
845                 // gone as well
846                 docked_objp = ai_find_docked_object( objp );
847                 if ( docked_objp ) {
848                         docked_objp->flags |= OF_SHOULD_BE_DEAD;
849                 }
850
851                 ship_departed( objp->instance );
852                 if ( docked_objp ){
853                         ship_departed( docked_objp->instance );
854                 }
855         }
856 }
857
858 // compute_special_warpout_stuff();
859 int compute_special_warpout_stuff(object *objp, float *speed, float *warp_time, vector *warp_pos)
860 {
861         object  *sp_objp = NULL;
862         ship            *shipp;
863         int             valid_refenence_ship = FALSE, ref_objnum;
864         vector  facing_normal, vec_to_knossos;
865         float           dist_to_plane;
866
867         // knossos warpout only valid in single player
868         if (Game_mode & GM_MULTIPLAYER) {
869                 mprintf(("special warpout only for single player\n"));
870                 return -1;
871         }
872
873         // find special warp ship reference
874         valid_refenence_ship = FALSE;
875         ref_objnum = Ships[objp->instance].special_warp_objnum;
876
877         // Validate reference_objnum
878         if ((ref_objnum >= 0) && (ref_objnum < MAX_OBJECTS)) {
879                 sp_objp = &Objects[ref_objnum];
880                 if (sp_objp->type == OBJ_SHIP) {
881                         shipp = &Ships[sp_objp->instance];
882                         if (Ship_info[shipp->ship_info_index].flags & SIF_KNOSSOS_DEVICE) {
883                                 valid_refenence_ship = TRUE;
884                         }
885                 }
886         }
887         
888         if (!valid_refenence_ship) {
889                 Int3();
890                 mprintf(("special warpout reference ship not found\n"));
891                 return -1;
892         }
893
894         // get facing normal from knossos
895         vm_vec_sub(&vec_to_knossos, &sp_objp->pos, &objp->pos);
896         facing_normal = sp_objp->orient.fvec;
897         if (vm_vec_dotprod(&vec_to_knossos, &sp_objp->orient.fvec) > 0) {
898                 vm_vec_negate(&facing_normal);
899         }
900
901         // find position to play the warp ani..
902         dist_to_plane = fvi_ray_plane(warp_pos, &sp_objp->pos, &facing_normal, &objp->pos, &objp->orient.fvec, 0.0f);
903
904         // calculate distance to warpout point.
905         polymodel *pm = model_get(Ships[objp->instance].modelnum);
906         dist_to_plane += pm->mins.z;
907
908         if (dist_to_plane < 0) {
909                 mprintf(("warpout started too late\n"));
910                 return -1;
911         }
912
913         // validate angle
914         float max_warpout_angle = 0.707f;       // 45 degree half-angle cone for small ships
915         if (Ship_info[Ships[objp->instance].ship_info_index].flags & (SIF_BIG_SHIP | SIF_HUGE_SHIP)) {
916                 max_warpout_angle = 0.866f;     // 30 degree half-angle cone for BIG or HUGE
917         }
918
919         if (-vm_vec_dotprod(&objp->orient.fvec, &facing_normal) < max_warpout_angle) {  // within allowed angle
920                 Int3();
921                 mprintf(("special warpout angle exceeded\n"));
922                 return -1;
923         }
924
925         // Calculate speed needed to get 
926         *speed = shipfx_calculate_warp_speed(objp);
927         
928         // Calculate how long to fly through the effect.  Not to get to the effect, just through it.
929         *warp_time = shipfx_calculate_warp_time(objp);
930         *warp_time += dist_to_plane / *speed;
931
932         return 0;
933 }
934
935
936 void compute_warpout_stuff(object *objp, float *speed, float *warp_time, vector *warp_pos)
937 {
938         // If we're warping through the knossos, do something different.
939         if (Ships[objp->instance].special_warp_objnum >= 0) {
940                 if (compute_special_warpout_stuff(objp, speed, warp_time, warp_pos) != -1) {
941                         return;
942                 } else {
943                         mprintf(("Invalid special warp\n"));
944                 }
945         }
946
947         object  *docked_objp = ai_find_docked_object( objp );
948         vector  center_pos = objp->pos;
949         float           radius, ship_move_dist, warp_dist;
950
951         radius = objp->radius;
952
953         //      If ship is docked then center wormhold about their center and make radius large enough.
954         if ( docked_objp ) {
955                 vm_vec_avg(&center_pos, &objp->pos, &docked_objp->pos);
956                 radius += docked_objp->radius;
957         }
958
959         // Calculate how long to fly through the effect.  Not to get to the effect, just through it.
960         *warp_time = shipfx_calculate_warp_time(objp);
961
962         // Pick some speed at which we want to go through the warp effect.  
963         // This is determined by shipfx_calculate_warp_time specifying how long
964         // it should take to go through the effect, or 2R.
965         *speed = shipfx_calculate_warp_speed(objp);
966
967         if ( objp == Player_obj )       {
968                 *speed = 0.8f*objp->phys_info.max_vel.z;
969         }
970
971         // Now we know our speed. Figure out how far the warp effect will be from here.  
972         ship_move_dist = (*speed * SHIPFX_WARP_DELAY) + radius*1.5f;            // We want to get to 1.5R away from effect
973         if ( ship_move_dist < radius*1.5f ) {
974                 ship_move_dist = radius*1.5f;
975         }
976
977         // If this is a huge ship, set the distance to the length of the ship
978         if (Ship_info[Ships[objp->instance].ship_info_index].flags & SIF_HUGE_SHIP) {
979                 ship_move_dist = 0.5f * ship_get_length(&Ships[objp->instance]);
980         }
981
982         // Acount for time to get to warp effect, before we actually go through it.
983         *warp_time += ship_move_dist / *speed;
984
985         warp_dist = ship_move_dist;
986         // allow for off center
987         if (Ship_info[Ships[objp->instance].ship_info_index].flags & SIF_HUGE_SHIP) {
988                 polymodel *pm = model_get(Ships[objp->instance].modelnum);
989                 warp_dist -= pm->mins.z;
990         }
991
992         vm_vec_scale_add( warp_pos, &center_pos, &objp->orient.fvec, warp_dist );
993 }
994
995 // JAS - code to start the ship doing the warp out effect
996 // This puts the ship into a mode specified by SF_DEPARTING
997 // where it flies forward for a set time period at a set
998 // velocity, then disappears when that time is reached.  This
999 // also starts the animating 3d effect playing.
1000 void shipfx_warpout_start( object *objp )
1001 {
1002         float warp_time;
1003         ship *shipp;
1004         shipp = &Ships[objp->instance];
1005
1006         if (    shipp->flags & SF_DEPART_WARP ) {
1007                 mprintf(( "Ship is already departing!\n" ));
1008                 return;
1009         }
1010
1011         // if we're dying return
1012         if ( shipp->flags & SF_DYING ) {
1013                 return;
1014         }
1015
1016         // if we're HUGE, keep alive - set guardian
1017         if (Ship_info[shipp->ship_info_index].flags & SIF_HUGE_SHIP) {
1018                 objp->flags |= OF_GUARDIAN;
1019         }
1020
1021         // post a warpin event
1022         if(Game_mode & GM_DEMO_RECORD){
1023                 demo_POST_warpout(objp->signature, shipp->flags);
1024         }
1025
1026         // don't send ship depart packets for player ships
1027         if ( (MULTIPLAYER_MASTER) && !(objp->flags & OF_PLAYER_SHIP) ){
1028                 send_ship_depart_packet( objp );
1029         }
1030
1031         // don't do departure wormhole if ship flag is set which indicates no effect
1032         if ( shipp->flags & SF_NO_DEPARTURE_WARP ) {
1033                 // DKA 5/25/99 If he's going to warpout, set it.  
1034                 // Next line fixes assert in wing cleanup code when no warp effect.
1035                 shipp->flags |= SF_DEPART_WARP;
1036
1037                 shipfx_actually_warpout(shipp, objp);
1038                 return;
1039         }
1040
1041         if ( objp == Player_obj )       {
1042                 HUD_printf(XSTR( "Subspace node activated", 498) );
1043         }
1044
1045         float   speed, effect_time, effect_radius;
1046         vector  warp_pos;
1047         // warp time from compute warpout stuff includes time to get up to warp_pos
1048         compute_warpout_stuff(objp, &speed, &warp_time, &warp_pos);
1049         shipp->warp_effect_pos = warp_pos;
1050
1051         // The ending one means this is a warp-out effect
1052         int warp_objnum;
1053         // Effect time is 'SHIPFX_WARP_DELAY' (1.5 secs) seconds to start, 'shipfx_calculate_warp_time' 
1054         // for ship to go thru, and 'SHIPFX_WARP_DELAY' (1.5 secs) to go away.
1055         // effect_time = shipfx_calculate_warp_time(objp) + SHIPFX_WARP_DELAY + SHIPFX_WARP_DELAY;
1056         effect_time = warp_time + SHIPFX_WARP_DELAY;
1057         effect_radius = shipfx_calculate_effect_radius(objp);
1058
1059         // maybe special warpout
1060         if (shipp->special_warp_objnum >= 0) {
1061                 // cap radius to size of knossos
1062                 effect_radius = min(effect_radius, 0.8f*Objects[shipp->special_warp_objnum].radius);
1063                 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);
1064         } else {
1065                 warp_objnum = fireball_create(&shipp->warp_effect_pos, FIREBALL_WARP_EFFECT, OBJ_INDEX(objp), effect_radius, 1, NULL, effect_time, shipp->ship_info_index);
1066         }
1067         if (warp_objnum < 0 )   {       // JAS: This must always be created, if not, just warp the ship in
1068                 shipfx_actually_warpout(shipp,objp);
1069                 return;
1070         }
1071
1072         shipp->warp_effect_fvec = Objects[warp_objnum].orient.fvec;
1073         // maybe negate if special warp effect
1074         if (shipp->special_warp_objnum >= 0) {
1075                 if (vm_vec_dotprod(&shipp->warp_effect_fvec, &objp->orient.fvec) > 0) {
1076                         vm_vec_negate(&shipp->warp_effect_fvec);
1077                 }
1078         }
1079
1080         // Make the warp effect stage 1 last SHIP_WARP_TIME1 seconds.
1081         if ( objp == Player_obj )       {
1082                 warp_time = fireball_lifeleft(&Objects[warp_objnum]);
1083                 shipp->final_warp_time = timestamp(fl2i(warp_time*1000.0f));
1084         } else {
1085                 shipp->final_warp_time = timestamp(fl2i(warp_time*2.0f*1000.0f));
1086         }
1087         shipp->flags |= SF_DEPART_WARP;
1088
1089 //      mprintf(( "Warp time = %.4f , effect time = %.4f ms\n", warp_time*1000.0f, effect_time ));
1090
1091         // 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;
1092         
1093         // Set ship's velocity to 'speed'
1094         // This should actually be an AI that flies from the current
1095         // position through 'shipp->warp_effect_pos' in 'warp_time'
1096         // and keeps going 
1097         if ( objp != Player_obj )       {
1098                 vector vel;
1099                 vel = objp->orient.fvec;
1100                 vm_vec_scale( &vel, speed );
1101                 objp->phys_info.vel = vel;
1102                 objp->phys_info.desired_vel = vel;
1103                 objp->phys_info.prev_ramp_vel.x = 0.0f;
1104                 objp->phys_info.prev_ramp_vel.y = 0.0f;
1105                 objp->phys_info.prev_ramp_vel.z = speed;
1106                 objp->phys_info.forward_thrust = 1.0f;          // How much the forward thruster is applied.  0-1.
1107
1108                 // special case for HUGE ships
1109                 if (Ship_info[shipp->ship_info_index].flags & SIF_HUGE_SHIP) {
1110 //                      objp->phys_info.flags |= PF_SPECIAL_WARP_OUT;
1111                 }
1112         }
1113
1114 }
1115
1116 void shipfx_warpout_frame( object *objp, float frametime )
1117 {
1118         ship *shipp;
1119         shipp = &Ships[objp->instance];
1120
1121         if ( shipp->flags & SF_DYING ) return;
1122
1123         vector tempv;
1124         float warp_pos; // position of warp effect in object's frame of reference
1125
1126         vm_vec_sub( &tempv, &objp->pos, &shipp->warp_effect_pos );
1127         warp_pos = -vm_vec_dot( &tempv, &shipp->warp_effect_fvec );
1128
1129
1130         // Find the closest point on line from center of wormhole
1131         vector pos;
1132         float dist;
1133
1134         fvi_ray_plane(&pos,&objp->pos,&shipp->warp_effect_fvec,&shipp->warp_effect_pos, &shipp->warp_effect_fvec, 0.0f );
1135         dist = vm_vec_dist( &pos, &objp->pos );
1136
1137 //      mprintf(( "Warp pos = %.1f, rad=%.1f, center dist = %.1f\n", warp_pos, objp->radius, dist ));
1138
1139         if ( objp == Player_obj )       {
1140                 // Code for player warpout frame
1141
1142                 if ( (Player->control_mode==PCM_WARPOUT_STAGE2) && (warp_pos > objp->radius) )  {
1143                         gameseq_post_event( GS_EVENT_PLAYER_WARPOUT_DONE_STAGE2 );
1144                 }
1145
1146                 if ( timestamp_elapsed(shipp->final_warp_time) ) {
1147
1148                         // Something went wrong... oh well, warp him out anyway.
1149                         if ( Player->control_mode != PCM_WARPOUT_STAGE3 )       {
1150                                 mprintf(( "Hmmm... player ship warpout time elapsed, but he wasn't in warp stage 3.\n" ));
1151                         }
1152
1153                         gameseq_post_event( GS_EVENT_PLAYER_WARPOUT_DONE );
1154                         ship_departed( objp->instance );                                                                // mark log entry for the player
1155                 }
1156
1157         } else {
1158                 // Code for all non-player ships warpout frame
1159
1160                 int timed_out = timestamp_elapsed(shipp->final_warp_time);
1161                 if ( timed_out )        {
1162 //                      mprintf(("Frame %i: Ship %s missed departue cue.\n", Framecount, shipp->ship_name ));
1163                         int     delta_ms = timestamp_until(shipp->final_warp_time);
1164                         if (delta_ms > 1000.0f * frametime ) {
1165                                 nprintf(("AI", "Frame %i: Ship %s missed departue cue by %7.3f seconds.\n", Framecount, shipp->ship_name, - (float) delta_ms/1000.0f));
1166                         }
1167                 }
1168
1169                 // MWA 10/21/97 -- added shipp->flags & SF_NO_DEPARTURE_WARP part of next if statement.  For ships
1170                 // that don't get a wormhole effect, I wanted to drop into this code immediately.
1171                 if ( (warp_pos > objp->radius)  || (shipp->flags & SF_NO_DEPARTURE_WARP) || timed_out ) {
1172                         shipfx_actually_warpout( shipp, objp );
1173                 } 
1174         }
1175 }
1176
1177
1178 //==================================================
1179 // Stuff for keeping track of which ships are in
1180 // whose shadows.
1181
1182
1183 // Given point p0, in object's frame of reference, find if 
1184 // it can see the sun.
1185 int shipfx_point_in_shadow( vector *p0, matrix *src_orient, vector *src_pos, float radius )
1186 {
1187         mc_info mc;
1188         object *objp;
1189         ship_obj *so;
1190         ship *shipp;
1191         int n_lights;
1192         int idx;
1193
1194         vector rp0, rp1;
1195
1196         vector light_dir;
1197
1198         // Move rp0 into world coordinates      
1199         vm_vec_unrotate(&rp0, p0, src_orient);
1200         vm_vec_add2(&rp0, src_pos);
1201
1202         // get the # of global lights
1203         n_lights = light_get_global_count();
1204         
1205         for(idx=0; idx<n_lights; idx++){
1206                 // get the light dir for this light
1207                 light_get_global_dir(&light_dir, idx);
1208
1209                 // Find rp1
1210                 vm_vec_scale_add( &rp1, &rp0, &light_dir, 10000.0f );
1211
1212                 for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) )    {
1213                         objp = &Objects[so->objnum];
1214                         shipp = &Ships[objp->instance];
1215
1216                         mc.model_num = shipp->modelnum;
1217                         mc.orient = &objp->orient;
1218                         mc.pos = &objp->pos;
1219                         mc.p0 = &rp0;
1220                         mc.p1 = &rp1;
1221                         mc.flags = MC_CHECK_MODEL;      
1222
1223                         if ( model_collide(&mc) ){
1224                                 return 1;
1225                         }
1226                 }
1227         }
1228
1229         // not in shadow
1230         return 0;
1231 }
1232
1233
1234 // Given an ship see if it is in a shadow.
1235 int shipfx_in_shadow( object * src_obj )
1236 {
1237         mc_info mc;
1238         object *objp;
1239         ship_obj *so;
1240         ship *shipp;
1241         int n_lights;
1242         int idx;
1243
1244         vector rp0, rp1;
1245         vector light_dir;
1246
1247         rp0 = src_obj->pos;
1248         
1249         // get the # of global lights
1250         n_lights = light_get_global_count();
1251
1252         for(idx=0; idx<n_lights; idx++){
1253                 // get the direction for this light
1254                 light_get_global_dir(&light_dir, idx);
1255
1256                 // Find rp1
1257                 for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) )    {
1258                         objp = &Objects[so->objnum];
1259
1260                         if ( src_obj != objp )  {
1261                                 shipp = &Ships[objp->instance];
1262
1263                                 vm_vec_scale_add( &rp1, &rp0, &light_dir, objp->radius*10.0f );
1264
1265                                 mc.model_num = shipp->modelnum;
1266                                 mc.orient = &objp->orient;
1267                                 mc.pos = &objp->pos;
1268                                 mc.p0 = &rp0;
1269                                 mc.p1 = &rp1;
1270                                 mc.flags = MC_CHECK_MODEL;      
1271
1272         //                      mc.flags |= MC_CHECK_SPHERELINE;
1273         //                      mc.radius = src_obj->radius;
1274
1275                                 if ( model_collide(&mc) )       {
1276                                         return 1;
1277                                 }
1278                         }
1279                 }
1280         }
1281
1282         // not in shadow
1283         return 0;
1284 }
1285
1286 // Given world point see if it is in a shadow.
1287 int shipfx_eye_in_shadow( vector *eye_pos, object * src_obj, int sun_n )
1288 {
1289         mc_info mc;
1290         object *objp;
1291         ship_obj *so;
1292         ship *shipp;    
1293
1294         vector rp0, rp1;
1295         vector light_dir;
1296
1297         rp0 = *eye_pos; 
1298         
1299         // get the light dir
1300         if(!light_get_global_dir(&light_dir, sun_n)){
1301                 return 0;
1302         }
1303
1304         // Find rp1
1305         for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) )    {
1306                 objp = &Objects[so->objnum];
1307
1308                 if ( src_obj != objp )  {
1309                         shipp = &Ships[objp->instance];
1310
1311                         vm_vec_scale_add( &rp1, &rp0, &light_dir, objp->radius*10.0f );
1312
1313                         ship_model_start(objp);
1314
1315                         mc.model_num = shipp->modelnum;
1316                         mc.orient = &objp->orient;
1317                         mc.pos = &objp->pos;
1318                         mc.p0 = &rp0;
1319                         mc.p1 = &rp1;
1320                         mc.flags = MC_CHECK_MODEL;      
1321
1322         //                      mc.flags |= MC_CHECK_SPHERELINE;
1323         //                      mc.radius = src_obj->radius;
1324
1325                         int hit = model_collide(&mc);
1326
1327                         ship_model_stop(objp);
1328
1329                         if (hit) {
1330                                 return 1;
1331                         }
1332                 }
1333         }
1334
1335         // Check all the big hull debris pieces.
1336         debris  *db = Debris;
1337
1338         int i;
1339         for ( i = 0; i < MAX_DEBRIS_PIECES; i++, db++ ) {
1340                 if ( !(db->flags & DEBRIS_USED) || !db->is_hull ){
1341                         continue;
1342                 }
1343
1344                 objp = &Objects[db->objnum];
1345
1346                 vm_vec_scale_add( &rp1, &rp0, &light_dir, objp->radius*10.0f );
1347
1348                 mc.model_num = db->model_num;   // Fill in the model to check
1349                 mc.submodel_num = db->submodel_num;
1350                 model_clear_instance( mc.model_num );
1351                 mc.orient = &objp->orient;                                      // The object's orient
1352                 mc.pos = &objp->pos;                                                    // The object's position
1353                 mc.p0 = &rp0;                           // Point 1 of ray to check
1354                 mc.p1 = &rp1;                                   // Point 2 of ray to check
1355                 mc.flags = (MC_CHECK_MODEL | MC_SUBMODEL);
1356
1357                 if (model_collide(&mc)) {
1358                         return 1;
1359                 }
1360         }       
1361
1362         // not in shadow
1363         return 0;
1364 }
1365
1366 //=====================================================================================
1367 // STUFF FOR DOING SHIP GUN FLASHES
1368 //=====================================================================================
1369
1370 #define MAX_FLASHES     128                     // How many flashes total
1371 #define FLASH_LIFE_PRIMARY              0.25f                   // How long flash lives
1372 #define FLASH_LIFE_SECONDARY    0.50f                   // How long flash lives
1373
1374
1375 typedef struct ship_flash {
1376         int     objnum;                                 // object number of parent ship
1377         int     obj_signature;                  // signature of that object
1378         int     light_num;                              // which light in the model this uses
1379         float   life;                                           // how long this should be around
1380         float max_life;                         // how long this has been around.
1381 } ship_flash;
1382
1383 int Ship_flash_inited = 0;
1384 int Ship_flash_highest = -1;
1385 ship_flash Ship_flash[MAX_FLASHES];
1386
1387 // Resets the ship flash stuff. Call before each level.
1388 void shipfx_flash_init()
1389 {
1390         int i;
1391         
1392         for (i=0; i<MAX_FLASHES; i++ )  {
1393                 Ship_flash[i].objnum = -1;                      // mark as unused
1394         }
1395         Ship_flash_highest = -1;
1396         Ship_flash_inited = 1;  
1397 }
1398
1399
1400 // Given that a ship fired a weapon, light up the model
1401 // accordingly.
1402 void shipfx_flash_create(object *objp, ship * shipp, vector *gun_pos, vector *gun_dir, int is_primary, int weapon_info_index)
1403 {
1404         int i;
1405         int objnum = OBJ_INDEX(objp);
1406
1407         Assert(Ship_flash_inited);
1408
1409         polymodel *pm = model_get( shipp->modelnum );
1410         int closest_light = -1;
1411         float d, closest_dist = 0.0f;
1412
1413         // ALWAYS do this - since this is called once per firing
1414         // if this is a cannon type weapon, create a muzzle flash
1415         // HACK - let the flak guns do this on their own since they fire so quickly
1416         if((Weapon_info[weapon_info_index].wi_flags & WIF_MFLASH) && !(Weapon_info[weapon_info_index].wi_flags & WIF_FLAK)){
1417                 // spiffy new flash stuff
1418                 mflash_create(gun_pos, gun_dir, Weapon_info[weapon_info_index].muzzle_flash);           
1419         }
1420
1421         if ( pm->num_lights < 1 ) return;
1422
1423         for (i=0; i<pm->num_lights; i++ )       {
1424                 d = vm_vec_dist( &pm->lights[i].pos, gun_pos );
1425         
1426                 if ( pm->lights[i].type == BSP_LIGHT_TYPE_WEAPON ) {
1427                         if ( (closest_light==-1) || (d<closest_dist) )  {
1428                                 closest_light = i;
1429                                 closest_dist = d;
1430                         }
1431                 }
1432         }
1433
1434         if ( closest_light == -1 ) return;
1435
1436         int first_slot = -1;
1437
1438         for (i=0; i<=Ship_flash_highest; i++ )  {
1439                 if ( (first_slot==-1) && (Ship_flash[i].objnum < 0) )   {
1440                         first_slot = i;
1441                 }
1442
1443                 if ( (Ship_flash[i].objnum == objnum) && (Ship_flash[i].obj_signature==objp->signature) )       {
1444                         if ( Ship_flash[i].light_num == closest_light ) {
1445                                 // This is already flashing!
1446                                 Ship_flash[i].life = 0.0f;
1447                                 if ( is_primary )       {
1448                                         Ship_flash[i].max_life = FLASH_LIFE_PRIMARY;
1449                                 } else {
1450                                         Ship_flash[i].max_life = FLASH_LIFE_SECONDARY;
1451                                 }
1452                                 return;
1453                         }
1454                 }
1455         }
1456
1457         if ( first_slot == -1 ) {
1458                 if ( Ship_flash_highest < MAX_FLASHES-1 )       {
1459                         Ship_flash_highest++;
1460                         first_slot = Ship_flash_highest;
1461                 } else {
1462                         //mprintf(( "SHIP_FLASH: Out of flash spots!\n" ));
1463                         return;         // out of flash slots
1464                 }
1465         }
1466
1467         Assert( Ship_flash[first_slot].objnum == -1 );
1468
1469         Ship_flash[first_slot].objnum = objnum;
1470         Ship_flash[first_slot].obj_signature = objp->signature;
1471         Ship_flash[first_slot].life = 0.0f;             // Start it up
1472         if ( is_primary )       {
1473                 Ship_flash[first_slot].max_life = FLASH_LIFE_PRIMARY;
1474         } else {
1475                 Ship_flash[first_slot].max_life = FLASH_LIFE_SECONDARY;
1476         }
1477         Ship_flash[first_slot].light_num = closest_light;               
1478 }
1479
1480 // Sets the flash lights in the model used by this
1481 // ship to the appropriate values.  There might not
1482 // be any flashes linked to this ship in which
1483 // case this function does nothing.
1484 void shipfx_flash_light_model( object *objp, ship * shipp )
1485 {
1486         int i, objnum = OBJ_INDEX(objp);
1487         polymodel *pm = model_get( shipp->modelnum );
1488
1489         for (i=0; i<=Ship_flash_highest; i++ )  {
1490                 if ( (Ship_flash[i].objnum == objnum) && (Ship_flash[i].obj_signature==objp->signature) )       {
1491                         float v = (Ship_flash[i].max_life - Ship_flash[i].life)/Ship_flash[i].max_life;
1492
1493                         pm->lights[Ship_flash[i].light_num].value += v / 255.0f;
1494                 }
1495         }
1496
1497 }
1498
1499 // Does whatever processing needs to be done each frame.
1500 void shipfx_flash_do_frame(float frametime)
1501 {
1502         ship_flash *sf;
1503         int kill_it = 0;
1504         int i;
1505
1506         for (i=0, sf = &Ship_flash[0]; i<=Ship_flash_highest; i++, sf++ )       {
1507                 if ( sf->objnum > -1 )  {
1508                         if ( Objects[sf->objnum].signature != sf->obj_signature )       {
1509                                 kill_it = 1;
1510                         }
1511                         sf->life += frametime;
1512                         if ( sf->life >= sf->max_life ) kill_it = 1;
1513
1514                         if (kill_it) {
1515                                 sf->objnum = -1;
1516                                 if ( i == Ship_flash_highest )  {
1517                                         while( (Ship_flash_highest>0) && (Ship_flash[Ship_flash_highest].objnum == -1) )        {
1518                                                 Ship_flash_highest--;
1519                                         }
1520                                 }
1521                         }       
1522                 }
1523         }       
1524
1525 }
1526
1527 float Particle_width = 1.2f;
1528 DCF(particle_width, "Multiplier for angular width of the particle spew")
1529 {
1530         if ( Dc_command ) {
1531                 dc_get_arg(ARG_FLOAT);
1532                 if ( (Dc_arg_float >= 0 ) && (Dc_arg_float <= 5) ) {
1533                         Particle_width = Dc_arg_float;
1534                 } else {
1535                         dc_printf( "Illegal value for particle width. (Must be from 0-5) \n\n");
1536                 }
1537         }
1538 }
1539
1540 float Particle_number = 1.2f;
1541 DCF(particle_num, "Multiplier for the number of particles created")
1542 {
1543         if ( Dc_command ) {
1544                 dc_get_arg(ARG_FLOAT);
1545                 if ( (Dc_arg_float >= 0 ) && (Dc_arg_float <= 5) ) {
1546                         Particle_number = Dc_arg_float;
1547                 } else {
1548                         dc_printf( "Illegal value for particle num. (Must be from 0-5) \n\n");
1549                 }
1550         }
1551 }
1552
1553 float Particle_life = 1.2f;
1554 DCF(particle_life, "Multiplier for the lifetime of particles created")
1555 {
1556         if ( Dc_command ) {
1557                 dc_get_arg(ARG_FLOAT);
1558                 if ( (Dc_arg_float >= 0 ) && (Dc_arg_float <= 5) ) {
1559                         Particle_life = Dc_arg_float;
1560                 } else {
1561                         dc_printf( "Illegal value for particle life. (Must be from 0-5) \n\n");
1562                 }
1563         }
1564 }
1565
1566 // Make sparks fly off of ship n.
1567 // sn = spark number to spark, corrosponding to element in
1568 //      ship->hitpos array.  If this isn't -1, it is a just
1569 //      got hit by weapon spark, otherwise pick one randomally.
1570 void shipfx_emit_spark( int n, int sn )
1571 {
1572         int create_spark = 1;
1573         object * obj;
1574         vector outpnt;
1575         ship *shipp = &Ships[n];
1576         float ship_radius, spark_scale_factor;
1577         
1578         if ( shipp->num_hits <= 0 )
1579                 return;
1580
1581         // get radius of ship
1582         ship_radius = model_get_radius(Ship_info[shipp->ship_info_index].modelnum);
1583
1584         // get spark_scale_factor -- how much to increase ship sparks, based on radius
1585         if (ship_radius > 40) {
1586                 spark_scale_factor = 1.0f;
1587         } else if (ship_radius > 20) {
1588                 spark_scale_factor = (ship_radius - 20.0f) / 20.0f;
1589         } else {
1590                 spark_scale_factor = 0.0f;
1591         }
1592
1593         float spark_time_scale  = 1.0f + spark_scale_factor * (Particle_life   - 1.0f);
1594         float spark_width_scale = 1.0f + spark_scale_factor * (Particle_width  - 1.0f);
1595         float spark_num_scale   = 1.0f + spark_scale_factor * (Particle_number - 1.0f);
1596
1597         obj = &Objects[shipp->objnum];
1598         ship_info* si = &Ship_info[shipp->ship_info_index];
1599
1600         float hull_percent = obj->hull_strength / si->initial_hull_strength;
1601         if (hull_percent < 0.001) {
1602                 hull_percent = 0.001f;
1603         }
1604         float fraction = 0.1f * obj->radius / hull_percent;
1605         if (fraction > 1.0f) {
1606                 fraction = 1.0f;
1607         }
1608
1609         int spark_num;
1610         if ( sn == -1 ) {
1611                 spark_num = myrand() % shipp->num_hits;
1612         } else {
1613                 spark_num = sn;
1614         }
1615
1616         // don't display sparks that have expired
1617         if ( timestamp_elapsed(shipp->sparks[spark_num].end_time) ) {
1618                 return;
1619         }
1620
1621         // get spark position
1622         if (shipp->sparks[spark_num].submodel_num != -1) {
1623                 ship_model_start(obj);
1624                 model_find_world_point(&outpnt, &shipp->sparks[spark_num].pos, shipp->modelnum, shipp->sparks[spark_num].submodel_num, &obj->orient, &obj->pos);
1625                 ship_model_stop(obj);
1626         } else {
1627                 // rotate sparks correctly with current ship orient
1628                 vm_vec_unrotate(&outpnt, &shipp->sparks[spark_num].pos, &obj->orient);
1629                 vm_vec_add2(&outpnt,&obj->pos);
1630         }
1631
1632         if ( shipp->flags & (SF_ARRIVING|SF_DEPART_WARP) ) {
1633                 vector tmp;
1634                 vm_vec_sub( &tmp, &outpnt, &shipp->warp_effect_pos );
1635                 if ( vm_vec_dot( &tmp, &shipp->warp_effect_fvec ) < 0.0f )      {
1636                         // if in front of warp plane, don't create.
1637                         create_spark = 0;
1638                 }
1639         }
1640
1641         if ( create_spark )     {
1642
1643                 particle_emitter        pe;
1644
1645                 pe.pos = outpnt;                                // Where the particles emit from
1646
1647                 if ( shipp->flags & (SF_ARRIVING|SF_DEPART_WARP) ) {
1648                         // No velocity if going through warp.
1649                         pe.vel = vmd_zero_vector;
1650                 } else {
1651                         // Initial velocity of all the particles.
1652                         // 0.0f = 0% of parent's.
1653                         // 1.0f = 100% of parent's.
1654                         vm_vec_copy_scale( &pe.vel, &obj->phys_info.vel, 0.7f );
1655                 }
1656
1657                 // TODO: add velocity from rotation if submodel is rotating
1658                 // v_rot = w x r
1659
1660                 // r = outpnt - model_find_world_point(0)
1661
1662                 // w = model_find_world_dir(
1663                 // model_find_world_dir(&out_dir, &in_dir, model_num, submodel_num, &objorient, &objpos);
1664
1665                 vector tmp_norm, tmp_vel;
1666                 vm_vec_sub( &tmp_norm, &outpnt, &obj->pos );
1667                 vm_vec_normalize_safe(&tmp_norm);
1668
1669                 tmp_vel = obj->phys_info.vel;
1670                 if ( vm_vec_normalize_safe(&tmp_vel) > 1.0f )   {
1671                         vm_vec_scale_add2(&tmp_norm,&tmp_vel, -2.0f);
1672                         vm_vec_normalize_safe(&tmp_norm);
1673                 }
1674                                 
1675                 pe.normal = tmp_norm;                   // What normal the particle emit around
1676                 pe.normal_variance = 0.3f;              //      How close they stick to that normal 0=good, 1=360 degree
1677                 pe.min_rad = 0.20f;                             // Min radius
1678                 pe.max_rad = 0.50f;                             // Max radius
1679
1680                 // first time through - set up end time and make heavier initially
1681                 if ( sn > -1 )  {
1682                         // Sparks for first time at this spot
1683                         if (si->flags & SIF_FIGHTER) {
1684                                 if (hull_percent > 0.6f) {
1685                                         // sparks only once when hull > 60%
1686                                         float spark_duration = (float)pow(2.0f, -5.0f*(hull_percent-1.3f)) * (1.0f + 0.6f*(frand()-0.5f));      // +- 30%
1687                                         shipp->sparks[spark_num].end_time = timestamp( (int) (1000.0f * spark_duration) );
1688                                 } else {
1689                                         // spark never ends when hull < 60% (~277 hr)
1690                                         shipp->sparks[spark_num].end_time = timestamp( 100000000 );
1691                                 }
1692                         }
1693
1694                         if ( D3D_enabled ) {
1695                                 pe.num_low  = 25;                               // Lowest number of particles to create (hardware)
1696                                 pe.num_high = 30;                               // Highest number of particles to create (hardware)
1697                         } else {
1698                                 pe.num_low  = 5;                                // Lowest number of particles to create (software)
1699                                 pe.num_high = 7;                                // Highest number of particles to create (software)
1700                         }
1701                         pe.normal_variance = 1.0f;      //      How close they stick to that normal 0=good, 1=360 degree
1702                         pe.min_vel = 2.0f;                              // How fast the slowest particle can move
1703                         pe.max_vel = 12.0f;                             // How fast the fastest particle can move
1704                         pe.min_life = 0.05f;                            // How long the particles live
1705                         pe.max_life = 0.55f;                            // How long the particles live
1706
1707                         particle_emit( &pe, PARTICLE_FIRE, 0 );
1708                 } else {
1709
1710                         pe.min_rad = 0.7f;                              // Min radius
1711                         pe.max_rad = 1.3f;                              // Max radius
1712                         if ( D3D_enabled ) {
1713                                 pe.num_low  = int (20 * spark_num_scale);               // Lowest number of particles to create (hardware)
1714                                 pe.num_high = int (50 * spark_num_scale);               // Highest number of particles to create (hardware)
1715                         } else {
1716                                 pe.num_low  = 2;                        // Lowest number of particles to create (software)
1717                                 pe.num_high = 8;                // Highest number of particles to create (software)
1718                         }
1719                         pe.normal_variance = 0.2f * spark_width_scale;          //      How close they stick to that normal 0=good, 1=360 degree
1720                         pe.min_vel = 3.0f;                              // How fast the slowest particle can move
1721                         pe.max_vel = 12.0f;                             // How fast the fastest particle can move
1722                         pe.min_life = 0.35f*2.0f * spark_time_scale;            // How long the particles live
1723                         pe.max_life = 0.75f*2.0f * spark_time_scale;            // How long the particles live
1724                         
1725                         particle_emit( &pe, PARTICLE_SMOKE, 0 );
1726                 }
1727         }
1728
1729         // Select time to do next spark
1730 //      Ships[n].next_hit_spark = timestamp_rand(100,500);
1731         shipp->next_hit_spark = timestamp_rand(50,100);
1732 }
1733
1734
1735
1736 //=====================================================================================
1737 // STUFF FOR DOING LARGE SHIP EXPLOSIONS
1738 //=====================================================================================
1739
1740 int     Bs_exp_fire_low = 1;
1741 float   Bs_exp_fire_time_mult = 1.0f;
1742
1743 DCF_BOOL(bs_exp_fire_low, Bs_exp_fire_low)
1744 DCF(bs_exp_fire_time_mult, "Multiplier time between fireball in big ship explosion")
1745 {
1746         if ( Dc_command ) {
1747                 dc_get_arg(ARG_FLOAT);
1748                 if ( (Dc_arg_float >= 0.1 ) && (Dc_arg_float <= 5) ) {
1749                         Bs_exp_fire_time_mult = Dc_arg_float;
1750                 } else {
1751                         dc_printf( "Illegal value for bs_exp_fire_time_mult. (Must be from 0.1-5) \n\n");
1752                 }
1753         }
1754 }
1755
1756 #define MAX_SPLIT_SHIPS         3               // How many can explode at once.  Each one is about 1K.
1757
1758 #define DEBRIS_NONE                     0
1759 #define DEBRIS_DRAW                     1
1760 #define DEBRIS_FREE                     2
1761
1762 typedef struct clip_ship {
1763         object*                 parent_obj;
1764         float                   length_left;    // uncomsumed length
1765         matrix                  orient;
1766         physics_info    phys_info;
1767         vector                  local_pivot;                                                            // world center of mass position of half ship
1768         vector                  model_center_disp_to_orig_center;       // displacement from half ship center to original model center
1769         vector                  clip_plane_norm;                                                        // clip plane normal (local [0,0,1] or [0,0,-1])
1770         float                           cur_clip_plane_pt;                                              // displacement from half ship clip plane to original model center
1771         float                           explosion_vel;
1772         ubyte                           draw_debris[MAX_DEBRIS_OBJECTS];
1773         int                             next_fireball;
1774 } clip_ship;
1775
1776 typedef struct split_ship {
1777         int                             used;                                   // 0 if not used, 1 if used
1778         clip_ship               front_ship;
1779         clip_ship               back_ship;
1780         int                             explosion_flash_timestamp;
1781         int                             explosion_flash_started;
1782         int                             sound_handle[NUM_SUB_EXPL_HANDLES];
1783 } split_ship;
1784
1785
1786 static split_ship Split_ships[MAX_SPLIT_SHIPS];
1787 static int Split_ships_inited = 0;
1788
1789 static void split_ship_init_system()
1790 {
1791         int i;
1792         for (i=0; i<MAX_SPLIT_SHIPS; i++ )      {
1793                 Split_ships[i].used = 0;
1794         }
1795         Split_ships_inited = 1;
1796 }
1797
1798 static void maybe_fireball_wipe(clip_ship* half_ship, int* sound_handle);
1799 static void split_ship_init( ship* shipp, split_ship* split_ship )
1800 {
1801         object* parent_ship_obj = &Objects[shipp->objnum];
1802         matrix* orient = &parent_ship_obj->orient;
1803         for (int ii=0; ii<NUM_SUB_EXPL_HANDLES; ii++) {
1804                 split_ship->sound_handle[ii] = shipp->sub_expl_sound_handle[ii];
1805         }
1806
1807         // play 3d sound for shockwave explosion
1808         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 );
1809
1810         // initialize both ships
1811         split_ship->front_ship.parent_obj = parent_ship_obj;
1812         split_ship->back_ship.parent_obj  = parent_ship_obj;
1813         split_ship->explosion_flash_timestamp = timestamp((int)(0.00075f*parent_ship_obj->radius));
1814         split_ship->explosion_flash_started = 0;
1815         split_ship->front_ship.orient = *orient;
1816         split_ship->back_ship.orient  = *orient;
1817         split_ship->front_ship.next_fireball = timestamp_rand(0, 100);
1818         split_ship->back_ship.next_fireball  = timestamp_rand(0, 100);
1819
1820         split_ship->front_ship.clip_plane_norm = vmd_z_vector;
1821         vm_vec_copy_scale(&split_ship->back_ship.clip_plane_norm, &vmd_z_vector, -1.0f);
1822
1823         // find the point at which the ship splits (relative to its pivot)
1824         polymodel* pm = model_get(shipp->modelnum);
1825         float init_clip_plane_dist;
1826         if (pm->num_split_plane > 0) {
1827                 int index = rand()%pm->num_split_plane;
1828                 init_clip_plane_dist = pm->split_plane[index];
1829         } else {
1830                 init_clip_plane_dist = 0.5f * (0.5f - frand())*pm->core_radius;
1831         }
1832
1833         split_ship->back_ship.cur_clip_plane_pt =  init_clip_plane_dist;
1834         split_ship->front_ship.cur_clip_plane_pt = init_clip_plane_dist;
1835
1836         float dist;
1837         dist = (split_ship->front_ship.cur_clip_plane_pt+pm->maxs.z)/2.0f;
1838         vm_vec_copy_scale(&split_ship->front_ship.local_pivot, &orient->fvec, dist);
1839         vm_vec_make(&split_ship->front_ship.model_center_disp_to_orig_center, 0.0f, 0.0f, -dist);
1840         dist = (split_ship->back_ship.cur_clip_plane_pt +pm->mins.z)/2.0f;
1841         vm_vec_copy_scale(&split_ship->back_ship.local_pivot, &orient->fvec, dist);
1842         vm_vec_make(&split_ship->back_ship.model_center_disp_to_orig_center, 0.0f, 0.0f, -dist);
1843         vm_vec_add2(&split_ship->front_ship.local_pivot, &parent_ship_obj->pos );
1844         vm_vec_add2(&split_ship->back_ship.local_pivot,  &parent_ship_obj->pos );
1845         
1846         // find which debris pieces are in the front and back split ships
1847         for (int i=0; i<pm->num_debris_objects; i++ )   {
1848                 vector temp_pos = {0.0f, 0.0f, 0.0f};
1849                 vector tmp = {0.0f, 0.0f, 0.0f };               
1850                 vector tmp1 = pm->submodel[pm->debris_objects[i]].offset;
1851                 // tmp is world position,  temp_pos is world_pivot,  tmp1 is offset from world_pivot (in ship local coord)
1852                 model_find_world_point(&tmp, &tmp1, shipp->modelnum, -1, &vmd_identity_matrix, &temp_pos );
1853                 if (tmp.z > init_clip_plane_dist) {
1854                         split_ship->front_ship.draw_debris[i] = DEBRIS_DRAW;
1855                         split_ship->back_ship.draw_debris[i]  = DEBRIS_NONE;
1856                 } else {
1857                         split_ship->front_ship.draw_debris[i] = DEBRIS_NONE;
1858                         split_ship->back_ship.draw_debris[i]  = DEBRIS_DRAW;
1859                 }
1860         }
1861
1862         /*
1863         // set the remaining debris slots to not draw
1864         for (i=pm->num_debris_objects; i<MAX_DEBRIS_OBJECTS; i++) {
1865                 split_ship->front_ship.draw_debris[i] = DEBRIS_NONE;
1866                 split_ship->back_ship.draw_debris[i]  = DEBRIS_NONE;
1867         } */
1868
1869         // set up physics 
1870         physics_init( &split_ship->front_ship.phys_info );
1871         physics_init( &split_ship->back_ship.phys_info );
1872         split_ship->front_ship.phys_info.flags  |= (PF_ACCELERATES | PF_DEAD_DAMP);
1873         split_ship->back_ship.phys_info.flags |= (PF_ACCELERATES | PF_DEAD_DAMP);
1874         split_ship->front_ship.phys_info.side_slip_time_const = 10000.0f;
1875         split_ship->back_ship.phys_info.side_slip_time_const =  10000.0f;
1876         split_ship->front_ship.phys_info.rotdamp = 10000.0f;
1877         split_ship->back_ship.phys_info.rotdamp =  10000.0f;
1878
1879         // set up explosion vel and relative velocities (assuming mass depends on length)
1880         float front_length = pm->maxs.z - split_ship->front_ship.cur_clip_plane_pt;
1881         float back_length  = split_ship->back_ship.cur_clip_plane_pt - pm->mins.z;
1882         float ship_length = front_length + back_length;
1883         split_ship->front_ship.length_left = front_length;
1884         split_ship->back_ship.length_left  = back_length;
1885
1886         float expl_length_scale = (ship_length - 200.0f) / 2000.0f;
1887         // s_r_f effects speed of "wipe" and rotvel
1888         float speed_reduction_factor = (1.0f + 0.001f*parent_ship_obj->radius);
1889         float explosion_time = (3.0f + expl_length_scale + (frand()-0.5f)) * speed_reduction_factor;
1890         float long_length = max(front_length, back_length);
1891         float expl_vel = long_length / explosion_time;
1892         split_ship->front_ship.explosion_vel = expl_vel;
1893         split_ship->back_ship.explosion_vel  = -expl_vel;
1894
1895         float rel_vel = (0.6f + 0.2f*frand()) * expl_vel * speed_reduction_factor;
1896         float front_vel = rel_vel * back_length / ship_length;
1897         float back_vel = -rel_vel * front_length / ship_length;
1898         // mprintf(("rel_vel %.1f, expl_vel %.1f\n", rel_vel, expl_vel));
1899
1900         // set up rotational vel
1901         vector rotvel;
1902         vm_vec_rand_vec_quick(&rotvel);
1903         rotvel.z = 0.0f;
1904         vm_vec_normalize(&rotvel);
1905         vm_vec_scale(&rotvel, 0.15f / speed_reduction_factor);
1906         split_ship->front_ship.phys_info.rotvel = rotvel;
1907         vm_vec_copy_scale(&split_ship->back_ship.phys_info.rotvel, &rotvel, -(front_length*front_length)/(back_length*back_length));
1908         split_ship->front_ship.phys_info.rotvel.z = parent_ship_obj->phys_info.rotvel.z;
1909         split_ship->back_ship.phys_info.rotvel.z  = parent_ship_obj->phys_info.rotvel.z;
1910
1911
1912         // modify vel of each split ship based on rotvel of parent ship obj
1913         vector temp_rotvel = parent_ship_obj->phys_info.rotvel;
1914         temp_rotvel.z = 0.0f;
1915         vector vel_from_rotvel;
1916         vm_vec_crossprod(&vel_from_rotvel, &temp_rotvel, &split_ship->front_ship.local_pivot);
1917         //      vm_vec_scale_add2(&split_ship->front_ship.phys_info.vel, &vel_from_rotvel, 0.5f);
1918         vm_vec_crossprod(&vel_from_rotvel, &temp_rotvel, &split_ship->back_ship.local_pivot);
1919         //      vm_vec_scale_add2(&split_ship->back_ship.phys_info.vel, &vel_from_rotvel, 0.5f);
1920
1921         // set up velocity and make initial fireballs and particles
1922         split_ship->front_ship.phys_info.vel = parent_ship_obj->phys_info.vel;
1923         split_ship->back_ship.phys_info.vel  = parent_ship_obj->phys_info.vel;
1924         maybe_fireball_wipe(&split_ship->front_ship, (int*)&split_ship->sound_handle);
1925         maybe_fireball_wipe(&split_ship->back_ship,  (int*)&split_ship->sound_handle);
1926         vm_vec_scale_add2(&split_ship->front_ship.phys_info.vel, &orient->fvec, front_vel);
1927         vm_vec_scale_add2(&split_ship->back_ship.phys_info.vel,  &orient->fvec, back_vel);
1928
1929         // HANDLE LIVE DEBRIS - blow off if not already gone
1930         shipfx_maybe_create_live_debris_at_ship_death( parent_ship_obj );
1931 }
1932
1933
1934 static void half_ship_render_ship_and_debris(clip_ship* half_ship,ship *shipp)
1935 {
1936         Assert( Split_ships_inited );
1937
1938         polymodel *pm = model_get(shipp->modelnum);
1939
1940         // get rotated clip plane normal and world coord of original ship center
1941         vector orig_ship_world_center, clip_plane_norm, model_clip_plane_pt, debris_clip_plane_pt;
1942         vm_vec_unrotate(&clip_plane_norm, &half_ship->clip_plane_norm, &half_ship->orient);
1943         vm_vec_unrotate(&orig_ship_world_center, &half_ship->model_center_disp_to_orig_center, &half_ship->orient);
1944         vm_vec_add2(&orig_ship_world_center, &half_ship->local_pivot);
1945
1946         // *out_pivot = orig_ship_world_center;
1947
1948         // get debris clip plane pt and draw debris
1949         vm_vec_unrotate(&debris_clip_plane_pt, &half_ship->model_center_disp_to_orig_center, &half_ship->orient);
1950         vm_vec_add2(&debris_clip_plane_pt, &half_ship->local_pivot);
1951         g3_start_user_clip_plane( &debris_clip_plane_pt, &clip_plane_norm);
1952
1953         // set up render flags
1954         uint render_flags = MR_NORMAL;
1955
1956         for (int i=0; i<pm->num_debris_objects; i++ )   {
1957                 // draw DEBRIS_FREE in test only
1958                 if (half_ship->draw_debris[i] == DEBRIS_DRAW) {
1959                         vector temp_pos = orig_ship_world_center;
1960                         vector tmp = {0.0f, 0.0f, 0.0f};
1961                         vector tmp1 = pm->submodel[pm->debris_objects[i]].offset;
1962
1963                         // determine if explosion front has past debris piece
1964                         // 67 ~ dist expl moves in 2 frames -- maybe fraction works better
1965                         int is_live_debris = pm->submodel[pm->debris_objects[i]].is_live_debris;
1966                         int create_debris = 0;
1967                         // front ship
1968                         if (half_ship->explosion_vel > 0) {
1969                                 if (half_ship->cur_clip_plane_pt > tmp1.z + pm->submodel[pm->debris_objects[i]].max.z - 0.1f*half_ship->explosion_vel) {
1970                                         create_debris = 1;
1971                                 }
1972                                 // is the debris visible
1973 //                              if (half_ship->cur_clip_plane_pt > tmp1.z + pm->submodel[pm->debris_objects[i]].min.z - 0.5f*half_ship->explosion_vel) {
1974 //                                      render_debris = 1;
1975 //                              }
1976                         // back ship
1977                         } else {
1978                                 if (half_ship->cur_clip_plane_pt < tmp1.z + pm->submodel[pm->debris_objects[i]].min.z - 0.1f*half_ship->explosion_vel) {
1979                                         create_debris = 1;
1980                                 }
1981                                 // is the debris visible
1982 //                              if (half_ship->cur_clip_plane_pt < tmp1.z + pm->submodel[pm->debris_objects[i]].max.z - 0.5f*half_ship->explosion_vel) {
1983 //                                      render_debris = 1;
1984 //                              }
1985                         }
1986
1987                         // Draw debris, but not live debris
1988                         if ( !is_live_debris ) {
1989                                 model_find_world_point(&tmp, &tmp1, shipp->modelnum, -1, &half_ship->orient, &temp_pos);
1990                                 submodel_render(shipp->modelnum, pm->debris_objects[i], &half_ship->orient, &tmp, render_flags);
1991                         }
1992
1993                         // make free piece of debris
1994                         if ( create_debris ) {
1995                                 half_ship->draw_debris[i] = DEBRIS_FREE;                // mark debris to not render with model
1996                                 vector center_to_debris, debris_vel, radial_vel;
1997                                 // check if last debris piece, ie, debris_count == 0
1998                                 int debris_count = 0;
1999                                 for (int j=0; j<pm->num_debris_objects; j++ ) {
2000                                         if (half_ship->draw_debris[j] == DEBRIS_DRAW) {
2001                                                 debris_count++;
2002                                         }
2003                                 } 
2004                                 // do debris create here, but not for live debris
2005                                 // debris vel (1) split ship vel (2) split ship rotvel (3) random
2006                                 if ( !is_live_debris ) {
2007                                         object* debris_obj;
2008                                         debris_obj = debris_create(half_ship->parent_obj, shipp->modelnum, pm->debris_objects[i], &tmp, &half_ship->local_pivot, 1, 1.0f);
2009                                         // AL: make sure debris_obj isn't NULL!
2010                                         if ( debris_obj ) {
2011                                                 vm_vec_scale(&debris_obj->phys_info.rotvel, 4.0f);
2012                                                 debris_obj->orient = half_ship->orient;
2013                                                 // if (debris_count > 0) {
2014                                                         //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) ));
2015                                                         vm_vec_sub(&center_to_debris, &tmp, &half_ship->local_pivot);
2016                                                         vm_vec_crossprod(&debris_vel, &center_to_debris, &half_ship->phys_info.rotvel);
2017                                                         vm_vec_add2(&debris_vel, &half_ship->phys_info.vel);
2018                                                         vm_vec_copy_normalize(&radial_vel, &center_to_debris);
2019                                                         float radial_mag = 10.0f + 30.0f*frand();
2020                                                         vm_vec_scale_add2(&debris_vel, &radial_vel, radial_mag);
2021                                                         debris_obj->phys_info.vel = debris_vel;
2022                                                 /* } else {
2023                                                         debris_obj->phys_info.vel = half_ship->phys_info.vel;
2024                                                         debris_obj->phys_info.rotvel = half_ship->phys_info.rotvel;
2025                                                 } */
2026                                         }
2027                                 }
2028                         }
2029                 }
2030         }
2031
2032         // get model clip plane pt and draw model
2033         vector temp;
2034         vm_vec_make(&temp, 0.0f, 0.0f, half_ship->cur_clip_plane_pt);
2035         vm_vec_unrotate(&model_clip_plane_pt, &temp, &half_ship->orient);
2036         vm_vec_add2(&model_clip_plane_pt, &orig_ship_world_center);
2037         g3_start_user_clip_plane( &model_clip_plane_pt, &clip_plane_norm );
2038         model_render(shipp->modelnum, &half_ship->orient, &orig_ship_world_center, render_flags);
2039 }
2040
2041 void shipfx_large_blowup_level_init()
2042 {
2043         split_ship_init_system();
2044
2045         if(Ship_cannon_bitmap != -1){
2046                 bm_unload(Ship_cannon_bitmap);
2047                 Ship_cannon_bitmap = bm_load(SHIP_CANNON_BITMAP);
2048         }
2049 }
2050
2051 // Returns 0 if couldn't init
2052 int shipfx_large_blowup_init(ship *shipp)
2053 {
2054
2055         if ( !Split_ships_inited )      {
2056                 split_ship_init_system();
2057         }
2058
2059         int i;
2060         for (i=0; i<MAX_SPLIT_SHIPS; i++ )      {
2061                 if ( Split_ships[i].used == 0 ) {
2062                         break;
2063                 }
2064         }
2065
2066         if ( i >= MAX_SPLIT_SHIPS )     {
2067                 mprintf(( "Not enough split ship slots!! See John!\n" ));
2068                 Int3();
2069                 return 0;
2070         }
2071
2072         Split_ships[i].used = 1;
2073         shipp->large_ship_blowup_index = i;
2074
2075         split_ship_init(shipp, &Split_ships[i] );
2076         
2077         return 1;
2078 }
2079
2080 // ----------------------------------------------------------------------------
2081 // uses list of model z values with constant increment to find the radius of the 
2082 // cross section at the current model z value
2083 float get_model_cross_section_at_z(float z, polymodel* pm)
2084 {
2085         if (pm->num_xc < 2) {
2086                 return 0.0f;
2087         }
2088
2089         float index, increment;
2090         increment = (pm->xc[pm->num_xc-1].z - pm->xc[0].z) / (float)(pm->num_xc - 1);
2091         index = (z - pm->xc[0].z) / increment;
2092
2093         if (index < 0.5f) {
2094                 return pm->xc[0].radius;
2095         } else if (index > (pm->num_xc - 1.0f - 0.5f)) {
2096                 return pm->xc[pm->num_xc-1].radius;
2097         } else {
2098                 int floor_index = (int)floor(index);
2099                 int ceil_index  = (int)ceil(index);
2100                 return max(pm->xc[ceil_index].radius, pm->xc[floor_index].radius);
2101         }
2102 }
2103
2104 // returns how long sound has been playing
2105 int get_sound_time_played(int snd_id, int handle)
2106 {
2107         if (handle == -1) {
2108                 return 100000;
2109         }
2110
2111         int bits_per_sample, frequency;
2112         snd_get_format(snd_id, &bits_per_sample, &frequency);
2113         int time_left = snd_time_remaining(handle, bits_per_sample, frequency);
2114         int duration = snd_get_duration(snd_id);
2115         
2116         return (duration - time_left);
2117 }
2118
2119 // sound manager for big ship sub explosions sounds.
2120 // forces playing of sub-explosion sounds.  keeps track of active sounds, plays them for >= 750 ms
2121 // when sound has played >= 750, sound is stopped and new instance is started 
2122 void do_sub_expl_sound(float radius, vector* sound_pos, int* sound_handle)
2123 {
2124         int sound_index, handle;
2125         // multiplier for range (near and far distances) to apply attenuation
2126         float sound_range = 1.0f + 0.0043f*radius;
2127
2128         int handle_index = rand()%NUM_SUB_EXPL_HANDLES;
2129         //mprintf(("handle_index %d\n", *handle_index));
2130
2131         // sound_index = get_sub_explosion_sound_index(handle_index);
2132         sound_index = SND_SHIP_EXPLODE_1;
2133         handle = sound_handle[handle_index];
2134
2135
2136         // 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]));
2137
2138         if (handle == -1) {
2139                 // if no handle, get one
2140                 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 );
2141         } else if (!snd_is_playing(handle)) {
2142                 // if sound not playing and old, get new one
2143                 // I don't think will happen with SND_PRIORITY_MUST_PLAY
2144                 if (get_sound_time_played(Snds[sound_index].id, handle) > 400) {
2145                         //mprintf(("sound not playing %d, time_played %d, stopped\n", handle, get_sound_time_played(Snds[sound_index].id, handle)));
2146                         snd_stop(sound_handle[handle_index]);
2147                         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 );
2148                 }
2149         } else if (get_sound_time_played(Snds[sound_index].id, handle) > 750) {
2150                 //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() ));
2151                 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 );
2152         }
2153 }
2154
2155 // maybe create a fireball along model clip plane
2156 // also maybe plays explosion sound
2157 static void maybe_fireball_wipe(clip_ship* half_ship, int* sound_handle)
2158 {
2159         // maybe make fireball to cover wipe.
2160         if ( timestamp_elapsed(half_ship->next_fireball) ) {
2161                 if ( half_ship->length_left > 0.2f*fl_abs(half_ship->explosion_vel) )   {
2162
2163                         polymodel* pm = model_get(Ships[half_ship->parent_obj->instance].modelnum);
2164
2165                         vector model_clip_plane_pt, orig_ship_world_center, temp;
2166
2167                         vm_vec_unrotate(&orig_ship_world_center, &half_ship->model_center_disp_to_orig_center, &half_ship->orient);
2168                         vm_vec_add2(&orig_ship_world_center, &half_ship->local_pivot);
2169
2170                         vm_vec_make(&temp, 0.0f, 0.0f, half_ship->cur_clip_plane_pt);
2171                         vm_vec_unrotate(&model_clip_plane_pt, &temp, &half_ship->orient);
2172                         vm_vec_add2(&model_clip_plane_pt, &orig_ship_world_center);
2173                         vm_vec_rand_vec_quick(&temp);
2174                         vm_vec_scale(&temp, 0.1f*frand());
2175                         vm_vec_add2(&model_clip_plane_pt, &temp);
2176
2177                         float rad = get_model_cross_section_at_z(half_ship->cur_clip_plane_pt, pm);
2178                         if (rad < 1) {
2179                                 rad = half_ship->parent_obj->radius * frand_range(0.4f, 0.6f);
2180                         } else {
2181                                 // make fireball radius (1.5 +/- .1) * model_cross_section value
2182                                 rad *= frand_range(1.4f, 1.6f);
2183                         }
2184
2185                         rad *= 1.5f;
2186                         rad = min(rad, half_ship->parent_obj->radius);
2187
2188                         // mprintf(("xc %.1f model %.1f\n", rad, half_ship->parent_obj->radius*0.25));
2189                         int fireball_type = FIREBALL_EXPLOSION_LARGE1 + rand()%FIREBALL_NUM_LARGE_EXPLOSIONS;
2190                         int low_res_fireballs = Bs_exp_fire_low;
2191                         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);
2192
2193                         // start the next fireball up (3-4 per frame) + 30%
2194                         int time_low, time_high;
2195                         time_low = int(650 * Bs_exp_fire_time_mult);
2196                         time_high = int(900 * Bs_exp_fire_time_mult);
2197                         half_ship->next_fireball = timestamp_rand(time_low, time_high);
2198
2199                         // do sound
2200                         do_sub_expl_sound(half_ship->parent_obj->radius, &model_clip_plane_pt, sound_handle);
2201
2202                         // do particles
2203                         particle_emitter        pe;
2204
2205                         pe.num_low = 40;                                        // Lowest number of particles to create
2206                         pe.num_high = 80;                               // Highest number of particles to create
2207                         pe.pos = model_clip_plane_pt;   // Where the particles emit from
2208                         pe.vel = half_ship->phys_info.vel;              // Initial velocity of all the particles
2209
2210 #ifdef FS2_DEMO
2211                         float range = 1.0f + 0.002f*half_ship->parent_obj->radius * 5.0f;
2212 #else 
2213                         float range = 1.0f + 0.002f*half_ship->parent_obj->radius;
2214 #endif
2215
2216 #ifdef FS2_DEMO
2217                         pe.min_life = 2.0f*range;                               // How long the particles live
2218                         pe.max_life = 10.0f*range;                              // How long the particles live
2219 #else
2220                         pe.min_life = 0.5f*range;                               // How long the particles live
2221                         pe.max_life = 6.0f*range;                               // How long the particles live
2222 #endif
2223                         pe.normal = vmd_x_vector;               // What normal the particle emit around
2224                         pe.normal_variance = 2.0f;              //      How close they stick to that normal 0=on normal, 1=180, 2=360 degree
2225                         pe.min_vel = 0.0f;                              // How fast the slowest particle can move
2226                         pe.max_vel = half_ship->explosion_vel;                          // How fast the fastest particle can move
2227
2228 #ifdef FS2_DEMO
2229                         float scale = half_ship->parent_obj->radius * 0.02f;
2230 #else
2231                         float scale = half_ship->parent_obj->radius * 0.01f;
2232 #endif
2233                         pe.min_rad = 0.5f*scale;                                // Min radius
2234                         pe.max_rad = 1.5f*scale;                                // Max radius
2235
2236                         particle_emit( &pe, PARTICLE_SMOKE2, 0, range );
2237
2238                 } else {
2239                         // time out forever
2240                         half_ship->next_fireball = timestamp(-1);
2241                 }
2242         }
2243 }
2244
2245
2246 // Returns 1 when explosion is done
2247 int shipfx_large_blowup_do_frame(ship *shipp, float frametime)
2248 {
2249         // DAVE:  I made this not do any movement just to try to get things working...
2250         // return 0;
2251
2252         Assert( Split_ships_inited );
2253         Assert( shipp->large_ship_blowup_index > -1 );
2254
2255         split_ship *the_split_ship = &Split_ships[shipp->large_ship_blowup_index];
2256         Assert( the_split_ship->used );         // Get John
2257
2258         // Do fireballs, particles, shockwave here
2259         // Note parent ship is still valid, vel and pos updated in obj_move_all
2260
2261         if ( timestamp_elapsed(the_split_ship->explosion_flash_timestamp) ) {
2262                 if ( !the_split_ship->explosion_flash_started ) {
2263                         object* objp = &Objects[shipp->objnum];
2264                         if (objp->flags & OF_WAS_RENDERED) {
2265                                 float excess_dist = vm_vec_dist(&Player_obj->pos, &objp->pos) - 2.0f*objp->radius - Player_obj->radius;
2266                                 float intensity = 1.0f - 0.1f*excess_dist / objp->radius;
2267
2268                                 if (intensity > 1) {
2269                                         intensity = 1.0f;
2270                                 }
2271
2272                                 if (intensity > 0.1f) {
2273                                         // big_explosion_flash(intensity);
2274                                 }
2275                         }
2276                         the_split_ship->explosion_flash_started = 1;
2277                 }
2278         }
2279
2280         physics_sim(&the_split_ship->front_ship.local_pivot, &the_split_ship->front_ship.orient, &the_split_ship->front_ship.phys_info, frametime);
2281         physics_sim(&the_split_ship->back_ship.local_pivot,  &the_split_ship->back_ship.orient,  &the_split_ship->back_ship.phys_info,  frametime);
2282         the_split_ship->front_ship.length_left -= the_split_ship->front_ship.explosion_vel*frametime;
2283         the_split_ship->back_ship.length_left  += the_split_ship->back_ship.explosion_vel *frametime;
2284         the_split_ship->front_ship.cur_clip_plane_pt += the_split_ship->front_ship.explosion_vel*frametime;
2285         the_split_ship->back_ship.cur_clip_plane_pt  += the_split_ship->back_ship.explosion_vel *frametime;
2286
2287         float length_left = max( the_split_ship->front_ship.length_left, the_split_ship->back_ship.length_left );
2288
2289         //      mprintf(( "Blowup frame, dist = %.1f \n", length_left ));
2290
2291         if ( length_left < 0 )  {
2292                 the_split_ship->used = 0;
2293                 return 1;
2294         }
2295
2296         maybe_fireball_wipe(&the_split_ship->front_ship, (int*)&the_split_ship->sound_handle);
2297         maybe_fireball_wipe(&the_split_ship->back_ship,  (int*)&the_split_ship->sound_handle);
2298         return 0;
2299 }
2300
2301 void shipfx_large_blowup_render(ship* shipp)
2302 {
2303 // This actually renders the original model like it should render.
2304 //      object *objp = &Objects[shipp->objnum];
2305 //      model_render( shipp->modelnum, &objp->orient, &objp->pos, MR_NORMAL );
2306 //      return;
2307
2308         Assert( Split_ships_inited );
2309         Assert( shipp->large_ship_blowup_index > -1 );
2310
2311         split_ship *the_split_ship = &Split_ships[shipp->large_ship_blowup_index];
2312         Assert( the_split_ship->used );         // Get John
2313
2314         // vector front_global_pivot, back_global_pivot;
2315
2316         if (the_split_ship->front_ship.length_left > 0) {
2317                 half_ship_render_ship_and_debris(&the_split_ship->front_ship,shipp);
2318         }
2319
2320         if (the_split_ship->back_ship.length_left > 0) {
2321                 half_ship_render_ship_and_debris(&the_split_ship->back_ship,shipp);
2322         }
2323
2324         g3_stop_user_clip_plane();                      
2325 }
2326
2327
2328 // ================== DO THE ELECTRIC ARCING STUFF =====================
2329 // Creates any new ones, moves old ones.
2330
2331 #define MAX_ARC_LENGTH_PERCENTAGE 0.25f
2332
2333 #define MAX_EMP_ARC_TIMESTAMP            (150.0f)
2334
2335 void shipfx_do_damaged_arcs_frame( ship *shipp )
2336 {
2337         int i;
2338         int should_arc;
2339         object *obj = &Objects[shipp->objnum];
2340         ship_info * sip = &Ship_info[shipp->ship_info_index];
2341
2342         should_arc = 1;
2343
2344         float damage = obj->hull_strength / sip->initial_hull_strength; 
2345
2346         if (damage < 0) {
2347                 damage = 0.0f;
2348         }
2349
2350         // don't draw an arc based on damage
2351         if ( damage > 0.30f )   {
2352                 // Don't do spark.
2353                 should_arc = 0;
2354         }
2355
2356         // we should draw an arc
2357         if( shipp->emp_intensity > 0.0f){
2358                 should_arc = 1;
2359         }
2360
2361         // Kill off old sparks
2362         for(i=0; i<MAX_SHIP_ARCS; i++){
2363                 if(timestamp_valid(shipp->arc_timestamp[i]) && timestamp_elapsed(shipp->arc_timestamp[i])){                     
2364                         shipp->arc_timestamp[i] = timestamp(-1);
2365                 }
2366         }
2367
2368         // if we shouldn't draw an arc, return
2369         if(!should_arc){
2370                 return;
2371         }
2372
2373         if (!timestamp_valid(shipp->arc_next_time))     {
2374                 // start the next fireball up in the next 10 seconds or so... 
2375                 int freq;
2376                 
2377                 // if the emp effect is active
2378                 if(shipp->emp_intensity > 0.0f){
2379                         freq = fl2i(MAX_EMP_ARC_TIMESTAMP);
2380                 }
2381                 // otherwise if we're arcing based upon damage
2382                 else {
2383                         freq = fl2i((damage+0.1f)*5000.0f);
2384                 }
2385
2386                 // set the next arc time
2387                 shipp->arc_next_time = timestamp_rand(freq*2,freq*4);
2388         }
2389
2390         if ( timestamp_elapsed(shipp->arc_next_time) )  {
2391
2392                 shipp->arc_next_time = timestamp(-1);           // invalid, so it gets restarted next frame
2393
2394                 //mprintf(( "Creating new ship arc!\n" ));
2395
2396                 int n, n_arcs = ((rand()>>5) % 3)+1;            // Create 1-3 sparks
2397
2398                 vector v1, v2, v3, v4;
2399                 submodel_get_two_random_points( shipp->modelnum, -1, &v1, &v2 );
2400                 submodel_get_two_random_points( shipp->modelnum, -1, &v3, &v4 );
2401
2402                 // For large ships, cap the length to be 25% of max radius
2403                 if ( obj->radius > 200.0f )     {
2404                         float max_dist = obj->radius * MAX_ARC_LENGTH_PERCENTAGE;
2405                         
2406                         vector tmp;
2407                         float d;
2408
2409                         // Cap arc 2->1
2410                         vm_vec_sub( &tmp, &v1, &v2 );
2411                         d = vm_vec_mag_quick( &tmp );
2412                         if ( d > max_dist )     {
2413                                 vm_vec_scale_add( &v1, &v2, &tmp, max_dist / d );
2414                         }
2415
2416                         // Cap arc 2->3
2417                         vm_vec_sub( &tmp, &v3, &v2 );
2418                         d = vm_vec_mag_quick( &tmp );
2419                         if ( d > max_dist )     {
2420                                 vm_vec_scale_add( &v3, &v2, &tmp, max_dist / d );
2421                         }
2422
2423
2424                         // Cap arc 2->4
2425                         vm_vec_sub( &tmp, &v4, &v2 );
2426                         d = vm_vec_mag_quick( &tmp );
2427                         if ( d > max_dist )     {
2428                                 vm_vec_scale_add( &v4, &v2, &tmp, max_dist / d );
2429                         }
2430                         
2431                 }
2432                 
2433                 n = 0;
2434
2435 //              int a = 100, b = 1000;
2436                 float factor = 1.0f + 0.0025f*obj->radius;
2437                 int a = (int) (factor*100.0f);
2438                 int b = (int) (factor*1000.0f);
2439                 int lifetime = (myrand()%((b)-(a)+1))+(a);
2440
2441                 // Create the arc effects
2442                 for (i=0; i<MAX_SHIP_ARCS; i++ )        {
2443                         if ( !timestamp_valid( shipp->arc_timestamp[i] ) )      {
2444                                 //shipp->arc_timestamp[i] = timestamp_rand(400,1000);   // live up to a second
2445                                 shipp->arc_timestamp[i] = timestamp(lifetime);  // live up to a second
2446
2447                                 switch( n )     {
2448                                 case 0:
2449                                         shipp->arc_pts[i][0] = v1;
2450                                         shipp->arc_pts[i][1] = v2;
2451                                         break;
2452                                 case 1:
2453                                         shipp->arc_pts[i][0] = v2;
2454                                         shipp->arc_pts[i][1] = v3;
2455                                         break;
2456
2457                                 case 2:
2458                                         shipp->arc_pts[i][0] = v2;
2459                                         shipp->arc_pts[i][1] = v4;
2460                                         break;
2461
2462                                 default:
2463                                         Int3();
2464                                 }
2465
2466                                 // determine what kind of arc to create
2467                                 if(shipp->emp_intensity > 0.0f){
2468                                         shipp->arc_type[i] = MARC_TYPE_EMP;
2469                                 } else {
2470                                         shipp->arc_type[i] = MARC_TYPE_NORMAL;
2471                                 }
2472                                         
2473                                 n++;
2474                                 if ( n == n_arcs )
2475                                         break;  // Don't need to create anymore
2476                         }
2477         
2478                         // rotate v2 out of local coordinates into world.
2479                         // Use v2 since it is used in every bolt.  See above switch().
2480                         vector snd_pos;
2481                         vm_vec_unrotate(&snd_pos, &v2, &obj->orient);
2482                         vm_vec_add2(&snd_pos, &obj->pos );
2483
2484                         //Play a sound effect
2485                         if ( lifetime > 750 )   {
2486                                 // 1.00 second effect
2487                                 snd_play_3d( &Snds[SND_DEBRIS_ARC_05], &snd_pos, &View_position, obj->radius );
2488                         } else if ( lifetime >  500 )   {
2489                                 // 0.75 second effect
2490                                 snd_play_3d( &Snds[SND_DEBRIS_ARC_04], &snd_pos, &View_position, obj->radius );
2491                         } else if ( lifetime >  250 )   {
2492                                 // 0.50 second effect
2493                                 snd_play_3d( &Snds[SND_DEBRIS_ARC_03], &snd_pos, &View_position, obj->radius );
2494                         } else if ( lifetime >  100 )   {
2495                                 // 0.25 second effect
2496                                 snd_play_3d( &Snds[SND_DEBRIS_ARC_02], &snd_pos, &View_position, obj->radius );
2497                         } else {
2498                                 // 0.10 second effect
2499                                 snd_play_3d( &Snds[SND_DEBRIS_ARC_01], &snd_pos, &View_position, obj->radius );
2500                         }
2501                 }
2502         }
2503
2504         // maybe move arc points around
2505         for (i=0; i<MAX_SHIP_ARCS; i++ )        {
2506                 if ( timestamp_valid( shipp->arc_timestamp[i] ) )       {
2507                         if ( !timestamp_elapsed( shipp->arc_timestamp[i] ) )    {                                                       
2508                                 // Maybe move a vertex....  20% of the time maybe?
2509                                 int mr = myrand();
2510                                 if ( mr < RAND_MAX/5 )  {
2511                                         vector v1, v2;
2512                                         submodel_get_two_random_points( shipp->modelnum, -1, &v1, &v2 );
2513
2514                                         vector static_one;
2515
2516                                         if ( mr % 2 )   {
2517                                                 static_one = shipp->arc_pts[i][0];
2518                                         } else {
2519                                                 static_one = shipp->arc_pts[i][1];
2520                                         }
2521
2522                                         // For large ships, cap the length to be 25% of max radius
2523                                         if ( obj->radius > 200.0f )     {
2524                                                 float max_dist = obj->radius * MAX_ARC_LENGTH_PERCENTAGE;
2525                                                 
2526                                                 vector tmp;
2527                                                 float d;
2528
2529                                                 // Cap arc 2->1
2530                                                 vm_vec_sub( &tmp, &v1, &static_one );
2531                                                 d = vm_vec_mag_quick( &tmp );
2532                                                 if ( d > max_dist )     {
2533                                                         vm_vec_scale_add( &v1, &static_one, &tmp, max_dist / d );
2534                                                 }
2535                                         }
2536
2537                                         shipp->arc_pts[i][mr % 2] = v1;
2538                                 }
2539                         }
2540                 }
2541         }
2542 }
2543
2544 int l_cruiser_count = 1;
2545 int l_big_count = 2;
2546 int l_huge_count = 3;
2547 float l_max_radius = 3000.0f;
2548 void shipfx_do_lightning_frame( ship *shipp )
2549 {
2550         /*
2551         ship_info *sip;
2552         object *objp;
2553         int stamp, count;
2554         vector v1, v2, n1, n2, temp, temp2;
2555         bolt_info binfo;
2556
2557         // sanity checks
2558         Assert(shipp != NULL);
2559         if(shipp == NULL){
2560                 return;
2561         } 
2562         Assert(shipp->ship_info_index >= 0);
2563         if(shipp->ship_info_index < 0){
2564                 return;
2565         }       
2566         Assert(shipp->objnum >= 0);
2567         if(shipp->objnum < 0){
2568                 return;
2569         }       
2570
2571         // get some pointers
2572         sip = &Ship_info[shipp->ship_info_index];
2573         objp = &Objects[shipp->objnum]; 
2574
2575         // if this is not a nebula mission, don't do anything
2576         if(!(The_mission.flags & MISSION_FLAG_FULLNEB)){
2577                 shipp->lightning_stamp = -1;
2578                 return;
2579         }
2580         
2581         // if this not a cruiser or big ship
2582         if(!((sip->flags & SIF_CRUISER) || (sip->flags & SIF_BIG_SHIP) || (sip->flags & SIF_HUGE_SHIP))){
2583                 shipp->lightning_stamp = -1;
2584                 return;
2585         }
2586
2587         // determine stamp and count values
2588         if(sip->flags & SIF_CRUISER){
2589                 stamp = (int)((float)(Nebl_cruiser_min + ((Nebl_cruiser_max - Nebl_cruiser_min) * Nebl_intensity)) * frand_range(0.8f, 1.1f));
2590                 count = l_cruiser_count;
2591         } 
2592         else {
2593                 if(sip->flags & SIF_HUGE_SHIP){
2594                         stamp = (int)((float)(Nebl_supercap_min + ((Nebl_supercap_max - Nebl_supercap_min) * Nebl_intensity)) * frand_range(0.8f, 1.1f));
2595                         count = l_huge_count;
2596                 } else {
2597                         stamp = (int)((float)(Nebl_cap_min + ((Nebl_cap_max - Nebl_cap_min) * Nebl_intensity)) * frand_range(0.8f, 1.1f));
2598                         count = l_big_count;
2599                 }
2600         }
2601
2602         // if his timestamp is unset
2603         if(shipp->lightning_stamp == -1){
2604                 shipp->lightning_stamp = timestamp(stamp);
2605                 return;
2606         }
2607         // if his timestamp is currently unelapsed
2608         if(!timestamp_elapsed(shipp->lightning_stamp)){
2609                 return;
2610         }
2611
2612         mprintf(("SHIP BOLT\n"));
2613
2614         // restamp him first
2615         shipp->lightning_stamp = timestamp(stamp);
2616
2617         // ah, now we can create some lightning bolts
2618         count = (int)frand_range(0.0f, (float)count);
2619         while(count > 0){
2620                 // get 2 points on the hull of the ship
2621                 submodel_get_two_random_points(shipp->modelnum, 0, &v1, &v2, &n1, &n2);         
2622
2623                 // make up to 2 bolts
2624                 if(objp->radius > l_max_radius){
2625                         vm_vec_scale_add(&temp2, &v1, &n1, l_max_radius);
2626                 } else {
2627                         vm_vec_scale_add(&temp2, &v1, &n1, objp->radius);
2628                 }
2629                 vm_vec_unrotate(&temp, &temp2, &objp->orient);
2630                 vm_vec_add2(&temp, &objp->pos);
2631                 vm_vec_unrotate(&temp2, &v1, &objp->orient);
2632                 vm_vec_add2(&temp2, &objp->pos);
2633
2634                 // create the bolt
2635                 binfo.start = temp;
2636                 binfo.strike = temp2;
2637                 binfo.num_strikes = 3;
2638                 binfo.noise = 0.045f;
2639                 binfo.life = 375;
2640                 binfo.delay = (int)frand_range(0.0f, 1600.0f);
2641                 nebl_bolt(&binfo);
2642                 count--;
2643         
2644                 // done
2645                 if(count <= 0){
2646                         break;
2647                 }
2648
2649                 // one more             
2650                 if(objp->radius > l_max_radius){
2651                         vm_vec_scale_add(&temp2, &v2, &n2, l_max_radius);
2652                 } else {
2653                         vm_vec_scale_add(&temp2, &v2, &n2, objp->radius);
2654                 }
2655                 vm_vec_unrotate(&temp, &temp2, &objp->orient);
2656                 vm_vec_add2(&temp, &objp->pos);
2657                 vm_vec_unrotate(&temp2, &v2, &objp->orient);
2658                 vm_vec_add2(&temp2, &objp->pos);
2659
2660                 // create the bolt
2661                 binfo.start = temp;
2662                 binfo.strike = temp2;
2663                 binfo.num_strikes = 3;
2664                 binfo.noise = 0.045f;
2665                 binfo.life = 375;
2666                 binfo.delay = (int)frand_range(0.0f, 1600.0f);
2667                 nebl_bolt(&binfo);              
2668                 count--;
2669         }
2670         */
2671 }
2672
2673 // do all shockwaves for a ship blowing up
2674 void shipfx_do_shockwave_stuff(ship *shipp, shockwave_create_info *sci)
2675 {
2676         ship_info *sip;
2677         object *objp;
2678         polymodel *pm;
2679         vector temp, dir, shockwave_pos;
2680         vector head = vmd_zero_vector;
2681         vector tail = vmd_zero_vector;  
2682         float len, step, cur;
2683         int idx;
2684
2685         // sanity checks
2686         Assert(shipp != NULL);
2687         if(shipp == NULL){
2688                 return;
2689         } 
2690         Assert(shipp->ship_info_index >= 0);
2691         if(shipp->ship_info_index < 0){
2692                 return;
2693         }       
2694         Assert(shipp->objnum >= 0);
2695         if(shipp->objnum < 0){
2696                 return;
2697         }
2698         Assert(sci != NULL);
2699         if (sci == NULL) {
2700                 return;
2701         }
2702
2703         // get some pointers
2704         sip = &Ship_info[shipp->ship_info_index];
2705         objp = &Objects[shipp->objnum]; 
2706
2707         Assert(sip->shockwave_count > 0);
2708         if(sip->shockwave_count <= 0){
2709                 return;
2710         }
2711
2712         // get vectors at the head and tail of the object, dead center          
2713         pm = model_get(shipp->modelnum);
2714         if(pm == NULL){
2715                 return;
2716         }
2717         head.x = pm->submodel[0].offset.x;
2718         head.y = pm->submodel[0].offset.y;
2719         head.z = pm->maxs.z;
2720
2721         tail.x = pm->submodel[0].offset.x;
2722         tail.y = pm->submodel[0].offset.y;
2723         tail.z = pm->mins.z;
2724
2725         // transform the vectors into world coords
2726         vm_vec_unrotate(&temp, &head, &objp->orient);
2727         vm_vec_add(&head, &temp, &objp->pos);
2728         vm_vec_unrotate(&temp, &tail, &objp->orient);
2729         vm_vec_add(&tail, &temp, &objp->pos);
2730
2731         // now create as many shockwaves as needed
2732         vm_vec_sub(&dir, &head, &tail);
2733         len = vm_vec_mag(&dir);
2734         step = 1.0f / ((float)sip->shockwave_count + 1.0f);
2735         cur = step;
2736         for(idx=0; idx<sip->shockwave_count; idx++){
2737                 // get the shockwave position           
2738                 temp = dir;
2739                 vm_vec_scale(&temp, cur);
2740                 vm_vec_add(&shockwave_pos, &tail, &temp);
2741
2742                 // if knossos device, make shockwave in center
2743                 if (Ship_info[shipp->ship_info_index].flags & SIF_KNOSSOS_DEVICE) {
2744                         shockwave_pos = Objects[shipp->objnum].pos;
2745                 }
2746
2747                 // create the shockwave
2748                 shockwave_create_info sci2;
2749                 sci2.blast = (sci->blast / (float)sip->shockwave_count) * frand_range(0.75f, 1.25f);
2750                 sci2.damage = (sci->damage / (float)sip->shockwave_count) * frand_range(0.75f, 1.25f);
2751                 sci2.inner_rad = sci->inner_rad;
2752                 sci2.outer_rad = sci->outer_rad;
2753                 sci2.speed = sci->speed * frand_range(0.75f, 1.25f);
2754                 sci2.rot_angle = frand_range(0.0f, 359.0f);
2755
2756                 shockwave_create(shipp->objnum, &shockwave_pos, &sci2, SW_SHIP_DEATH, (int)frand_range(0.0f, 350.0f));
2757                 // shockwave_create(shipp->objnum, &objp->pos, sip->shockwave_speed, sip->inner_rad, sip->outer_rad, sip->damage, sip->blast, SW_SHIP_DEATH);
2758
2759                 // next shockwave
2760                 cur += step;
2761         }
2762 }
2763
2764 int Wash_on = 1;
2765 DCF_BOOL(engine_wash, Wash_on);
2766 #define ENGINE_WASH_CHECK_INTERVAL              250     // (4x sec)
2767 // Do engine wash effect for ship
2768 // Assumes length of engine wash is greater than radius of engine wash hemisphere
2769 void engine_wash_ship_process(ship *shipp)
2770 {
2771         int idx, j;             
2772         object *objp, *ship_objp, *max_ship_intensity_objp;
2773         int started_with_no_wash = shipp->wash_intensity <= 0 ? 1 : 0;
2774
2775         if (!Wash_on) {
2776                 return;
2777         }
2778
2779         Assert(shipp != NULL);
2780         Assert(shipp->objnum >= 0);
2781         objp = &Objects[shipp->objnum];
2782         ship_obj *so;
2783         ship_objp = NULL;
2784
2785         vector world_thruster_pos, world_thruster_norm, apex, thruster_to_ship, apex_to_ship, temp;
2786         float dist_sqr, inset_depth, dot_to_ship, max_ship_intensity;
2787         polymodel *pm;
2788
2789         float max_wash_dist, half_angle, radius_mult;
2790
2791         // if this is not a fighter or bomber, we don't care
2792         if ((objp->type != OBJ_SHIP) || !(Ship_info[shipp->ship_info_index].flags & (SIF_FIGHTER|SIF_BOMBER)) ) {
2793                 return;
2794         }
2795
2796         // is it time to check for engine wash 
2797         int time_to_next_hit = timestamp_until(shipp->wash_timestamp);
2798         if (time_to_next_hit < 0) {
2799                 if (time_to_next_hit < -ENGINE_WASH_CHECK_INTERVAL) {
2800                         time_to_next_hit = 0;
2801                 }
2802
2803                 // keep interval constant independent of framerate
2804                 shipp->wash_timestamp = timestamp(ENGINE_WASH_CHECK_INTERVAL + time_to_next_hit);
2805
2806                 // initialize wash params
2807                 shipp->wash_intensity = 0.0f;
2808                 vm_vec_zero(&shipp->wash_rot_axis);
2809                 max_ship_intensity_objp = NULL;
2810                 max_ship_intensity = 0;
2811         } else {
2812                 return;
2813         }
2814
2815         // only do damage if we're within half of the max wash distance
2816         int do_damage = 0;
2817
2818         // go thru Ship_used_list and check if we're in wash from CAP or SUPERCAP (SIF_HUGE)
2819         for (so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so)) {
2820                 ship_objp = &Objects[so->objnum];
2821
2822                 // don't do small ships
2823                 if ( (Ship_info[Ships[ship_objp->instance].ship_info_index].flags & SIF_SMALL_SHIP) ) {
2824                         continue;
2825                 }
2826
2827                 pm = model_get(Ships[ship_objp->instance].modelnum);
2828                 float ship_intensity = 0;
2829
2830                 // if engines disabled, no engine wash
2831                 if (ship_get_subsystem_strength(&Ships[ship_objp->instance], SUBSYSTEM_ENGINE) < 0.01) {
2832                         continue;
2833                 }
2834
2835                 float   speed_scale;
2836                 if (ship_objp->phys_info.speed > 20.0f)
2837                         speed_scale = 1.0f;
2838                 else
2839                         speed_scale = ship_objp->phys_info.speed/20.0f;
2840
2841                 for (idx = 0; idx < pm->n_thrusters; idx++) {
2842                         thruster_bank *bank = &pm->thrusters[idx];
2843
2844                         // check if thruster bank has engine wash
2845                         if (bank->wash_info_index < 0) {
2846                                 // if huge, give default engine wash
2847                                 if (Ship_info[Ships[ship_objp->instance].ship_info_index].flags & SIF_HUGE_SHIP) {
2848                                         bank->wash_info_index = 0;
2849                                         nprintf(("wash", "Adding default engine wash to ship %s", Ship_info[Ships[ship_objp->instance].ship_info_index].name));
2850                                 } else {
2851                                         continue;
2852                                 }
2853                         }
2854
2855                         engine_wash_info *ewp = &Engine_wash_info[bank->wash_info_index];
2856                         half_angle = ewp->angle;
2857                         radius_mult = ewp->radius_mult;
2858
2859                         for (j=0; j<bank->num_slots; j++) {
2860                                 // get world pos of thruster
2861                                 vm_vec_unrotate(&world_thruster_pos, &bank->pnt[j], &ship_objp->orient);
2862                                 vm_vec_add2(&world_thruster_pos, &ship_objp->pos);
2863                                 
2864                                 // get world norm of thruster;
2865                                 vm_vec_unrotate(&world_thruster_norm, &bank->norm[j], &ship_objp->orient);
2866
2867                                 // get vector from thruster to ship
2868                                 vm_vec_sub(&thruster_to_ship, &objp->pos, &world_thruster_pos);
2869
2870                                 // check if on back side of thruster
2871                                 dot_to_ship = vm_vec_dotprod(&thruster_to_ship, &world_thruster_norm);
2872                                 if (dot_to_ship > 0) {
2873
2874                                         // get max wash distance
2875                                         max_wash_dist = max(ewp->length, bank->radius[j]*ewp->radius_mult);
2876
2877                                         // check if within dist range
2878                                         dist_sqr = vm_vec_mag_squared(&thruster_to_ship);
2879                                         if (dist_sqr < max_wash_dist*max_wash_dist) {
2880
2881                                                 // check if inside the sphere
2882                                                 if (dist_sqr < radius_mult*radius_mult*bank->radius[j]*bank->radius[j]) {
2883                                                         vm_vec_crossprod(&temp, &world_thruster_norm, &thruster_to_ship);
2884                                                         vm_vec_scale_add2(&shipp->wash_rot_axis, &temp, dot_to_ship / dist_sqr);
2885 //                                                      shipp->wash_intensity += (1.0f - dist_sqr / (max_wash_dist*max_wash_dist));
2886                                                         ship_intensity += (1.0f - dist_sqr / (max_wash_dist*max_wash_dist));
2887                                                         if (!do_damage) {
2888                                                                 if (dist_sqr < 0.25 * max_wash_dist * max_wash_dist) {
2889                                                                         do_damage = 1;
2890                                                                 }
2891                                                         }
2892                                                 } else {
2893                                                         // check if inside cone - first fine apex of cone
2894                                                         inset_depth = float(bank->radius[j] / tan(half_angle));
2895                                                         vm_vec_scale_add(&apex, &world_thruster_pos, &world_thruster_norm, -inset_depth);
2896                                                         vm_vec_sub(&apex_to_ship, &objp->pos, &apex);
2897                                                         vm_vec_normalize(&apex_to_ship);
2898
2899                                                         // check if inside cone angle
2900                                                         if (vm_vec_dotprod(&apex_to_ship, &world_thruster_norm) > cos(half_angle)) {
2901                                                                 vm_vec_crossprod(&temp, &world_thruster_norm, &thruster_to_ship);
2902                                                                 vm_vec_scale_add2(&shipp->wash_rot_axis, &temp, dot_to_ship / dist_sqr);
2903 //                                                              shipp->wash_intensity += (1.0f - dist_sqr / (max_wash_dist*max_wash_dist));
2904                                                                 ship_intensity += (1.0f - dist_sqr / (max_wash_dist*max_wash_dist));
2905                                                                 if (!do_damage) {
2906                                                                         if (dist_sqr < 0.25 * max_wash_dist * max_wash_dist) {
2907                                                                                 do_damage = 1;
2908                                                                         }
2909                                                                 }
2910                                                         }
2911                                                 }
2912                                         }
2913                                 }
2914                         }
2915                 }
2916                 shipp->wash_intensity += ship_intensity * speed_scale;
2917                 if (ship_intensity > max_ship_intensity) {
2918                         max_ship_intensity = ship_intensity;
2919                         max_ship_intensity_objp = ship_objp;
2920                 }
2921         }
2922
2923         // apply damage at rate of 1%/sec
2924         if (shipp->wash_intensity > 0) {
2925                 Assert(max_ship_intensity_objp != NULL);
2926
2927                 nprintf(("wash", "Wash intensity %.2f\n", shipp->wash_intensity));
2928
2929                 float damage;
2930                 if (!do_damage) {
2931                         damage = 0;
2932                 } else {
2933                         damage = (0.001f * 0.003f * ENGINE_WASH_CHECK_INTERVAL * Ship_info[shipp->ship_info_index].initial_hull_strength * shipp->wash_intensity);
2934                 }
2935
2936                 ship_apply_wash_damage(&Objects[shipp->objnum], max_ship_intensity_objp, damage);
2937
2938                 // if we had no wash before now, add the wash object sound
2939                 if(started_with_no_wash){
2940                         if(shipp != Player_ship){
2941                                 obj_snd_assign(shipp->objnum, SND_ENGINE_WASH, &vmd_zero_vector, 1);
2942                         } else {                                
2943                                 Player_engine_wash_loop = snd_play_looping( &Snds[SND_ENGINE_WASH], 0.0f , -1, -1, 1.0f);
2944                         }
2945                 }
2946         } 
2947         // if we've got no wash, kill any wash object sounds from this guy
2948         else {
2949                 if(shipp != Player_ship){
2950                         obj_snd_delete(shipp->objnum, SND_ENGINE_WASH);
2951                 } else {
2952                         snd_stop(Player_engine_wash_loop);
2953                         Player_engine_wash_loop = -1;
2954                 }
2955         }
2956 }
2957
2958 // engine wash level init
2959 void shipfx_engine_wash_level_init()
2960 {
2961         Player_engine_wash_loop = -1;
2962 }
2963
2964 // pause engine wash sounds
2965 void shipfx_stop_engine_wash_sound()
2966 {
2967         if(Player_engine_wash_loop != -1){
2968                 snd_stop(Player_engine_wash_loop);
2969                 Player_engine_wash_loop = -1;
2970         }
2971 }