1 /*
2 * Copyright (C) Volition, Inc. 1999. All rights reserved.
3 *
4 * All source code herein is the property of Volition, Inc. You may not sell
5 * or otherwise commercially exploit the source or things you created based on the
6 * source.
7 *
8 */
9
10
11
12 #include "debris/debris.h"
13 #include "render/3d.h"
14 #include "fireball/fireballs.h"
15 #include "radar/radar.h"
16 #include "gamesnd/gamesnd.h"
17 #include "object/objectsnd.h"
18 #include "globalincs/linklist.h"
19 #include "particle/particle.h"
20 #include "freespace2/freespace.h"
21 #include "object/objcollide.h"
22 #include "io/timer.h"
23 #include "species_defs/species_defs.h"
24 #include "ship/ship.h"
25 #include "ship/shipfx.h"
26 #include "radar/radarsetup.h"
27 #include "network/multi.h"
28 #include "network/multimsgs.h"
29 #include "network/multiutil.h"
30 #include "weapon/weapon.h"
31 #include "cmdline/cmdline.h"
32
33 #define MAX_LIFE 10.0f
34 #define MIN_RADIUS_FOR_PERSISTANT_DEBRIS 50 // ship radius at which debris from it becomes persistant
35 #define DEBRIS_SOUND_DELAY 2000 // time to start debris sound after created
36 #define MAX_HULL_PIECES MAX_DEBRIS_PIECES // limit the number of hull debris chunks that can exist.
37
38 int Num_hull_pieces; // number of hull pieces in existance
39 debris Hull_debris_list; // head of linked list for hull debris chunks, for quick search
40
41 debris Debris[MAX_DEBRIS_PIECES];
42
43 int Num_debris_pieces = 0;
44 int Debris_inited = 0;
45
46 int Debris_model = -1;
47 int Debris_vaporize_model = -1;
48 int Debris_num_submodels = 0;
49
50 #define MAX_DEBRIS_DIST 10000.0f // Debris goes away if it's this far away.
51 #define DEBRIS_DISTANCE_CHECK_TIME (10*1000) // Check every 10 seconds.
52 #define DEBRIS_INDEX(dp) (dp-Debris)
53
54 #define MAX_SPEED_SMALL_DEBRIS 200 // maximum velocity of small debris piece
55 #define MAX_SPEED_BIG_DEBRIS 150 // maximum velocity of big debris piece
56 #define MAX_SPEED_CAPITAL_DEBRIS 100 // maximum velocity of capital debris piece
57
58 /**
59 * Start the sequence of a piece of debris writhing in unholy agony!!!
60 */
debris_start_death_roll(object * debris_obj,debris * debris_p)61 static void debris_start_death_roll(object *debris_obj, debris *debris_p)
62 {
63 if (debris_p->is_hull) {
64 // tell everyone else to blow up the piece of debris
65 if( MULTIPLAYER_MASTER )
66 send_debris_update_packet(debris_obj,DEBRIS_UPDATE_NUKE);
67
68 int fireball_type = fireball_ship_explosion_type(&Ship_info[debris_p->ship_info_index]);
69 if(fireball_type < 0) {
70 fireball_type = FIREBALL_EXPLOSION_LARGE1 + rand()%FIREBALL_NUM_LARGE_EXPLOSIONS;
71 }
72 fireball_create( &debris_obj->pos, fireball_type, FIREBALL_LARGE_EXPLOSION, OBJ_INDEX(debris_obj), debris_obj->radius*1.75f);
73
74 // only play debris destroy sound if hull piece and it has been around for at least 2 seconds
75 if ( Missiontime > debris_p->time_started + 2*F1_0 ) {
76 snd_play_3d( &Snds[SND_MISSILE_IMPACT1], &debris_obj->pos, &View_position, debris_obj->radius );
77
78 }
79 }
80
81 debris_obj->flags |= OF_SHOULD_BE_DEAD;
82 }
83
84 /**
85 * This will get called at the start of each level.
86 */
debris_init()87 void debris_init()
88 {
89 int i;
90
91 if ( !Debris_inited ) {
92 Debris_inited = 1;
93 }
94
95 Debris_model = -1;
96 Debris_vaporize_model = -1;
97 Debris_num_submodels = 0;
98
99 // Reset everything between levels
100 Num_debris_pieces = 0;
101 for (i=0; i<MAX_DEBRIS_PIECES; i++ ) {
102 Debris[i].flags = 0;
103 Debris[i].sound_delay = 0;
104 Debris[i].objnum = -1;
105 }
106
107 Num_hull_pieces = 0;
108 list_init(&Hull_debris_list);
109 }
110
111 /**
112 * Page in debris bitmaps at level load
113 */
debris_page_in()114 void debris_page_in()
115 {
116 uint i;
117
118 Debris_model = model_load( NOX("debris01.pof"), 0, NULL );
119 if (Debris_model >= 0) {
120 polymodel * pm;
121 pm = model_get(Debris_model);
122 Debris_num_submodels = pm->n_models;
123 }
124
125 Debris_vaporize_model = model_load( NOX("debris02.pof"), 0, NULL );
126
127 for (i=0; i<Species_info.size(); i++ )
128 {
129 species_info *species = &Species_info[i];
130
131 nprintf(( "Paging", "Paging in debris texture '%s'\n", species->debris_texture.filename));
132
133 species->debris_texture.bitmap_id = bm_load(species->debris_texture.filename);
134 if (species->debris_texture.bitmap_id < 0)
135 {
136 Warning( LOCATION, "Couldn't load species %s debris\ntexture, '%s'\n", species->species_name, species->debris_texture.filename);
137 }
138
139 bm_page_in_texture(species->debris_texture.bitmap_id);
140 }
141
142 }
143
144 MONITOR(NumSmallDebrisRend)
MONITOR(NumHullDebrisRend)145 MONITOR(NumHullDebrisRend)
146
147 /**
148 * Render debris
149 */
150 void debris_render(object * obj)
151 {
152 int i, num, swapped;
153 polymodel *pm;
154 debris *db;
155
156
157 swapped = -1;
158 pm = NULL;
159 num = obj->instance;
160
161 Assert(num >= 0 && num < MAX_DEBRIS_PIECES);
162 db = &Debris[num];
163
164 Assert(db->flags & DEBRIS_USED);
165
166 texture_info *tbase = NULL;
167
168 model_clear_instance( db->model_num );
169
170 // Swap in a different texture depending on the species
171 if (db->species >= 0)
172 {
173 pm = model_get( db->model_num );
174
175 //WMC - Someday, we should have glowing debris.
176 if ( pm != NULL && (pm->n_textures == 1) ) {
177 tbase = &pm->maps[0].textures[TM_BASE_TYPE];
178 swapped = tbase->GetTexture();
179 tbase->SetTexture(Species_info[db->species].debris_texture.bitmap_id);
180 }
181 }
182
183 // Only render electrical arcs if within 500m of the eye (for a 10m piece)
184 if ( vm_vec_dist_quick( &obj->pos, &Eye_position ) < obj->radius*50.0f ) {
185 for (i=0; i<MAX_DEBRIS_ARCS; i++ ) {
186 if ( timestamp_valid( db->arc_timestamp[i] ) ) {
187 model_add_arc( db->model_num, db->submodel_num, &db->arc_pts[i][0], &db->arc_pts[i][1], MARC_TYPE_NORMAL );
188 }
189 }
190 }
191
192 if ( db->is_hull ) {
193 MONITOR_INC(NumHullDebrisRend,1);
194 submodel_render( db->model_num, db->submodel_num, &obj->orient, &obj->pos );
195 } else {
196 MONITOR_INC(NumSmallDebrisRend,1);
197 submodel_render( db->model_num, db->submodel_num, &obj->orient, &obj->pos, MR_NO_LIGHTING );
198 }
199
200 if (tbase != NULL && (swapped!=-1) && pm) {
201 tbase->SetTexture(swapped);
202 }
203 }
204
205 /**
206 * Removed the ::DEBRIS_EXPIRE flag, and remove item from ::Hull_debris_list
207 */
debris_clear_expired_flag(debris * db)208 void debris_clear_expired_flag(debris *db)
209 {
210 if ( db->flags & DEBRIS_EXPIRE ) {
211 db->flags &= ~DEBRIS_EXPIRE;
212 if ( db->is_hull ) {
213 Num_hull_pieces--;
214 list_remove(Hull_debris_list, db);
215 Assert( Num_hull_pieces >= 0 );
216 }
217 }
218 }
219
220 /**
221 * Delete the debris object.
222 * This is only ever called via obj_delete(). Do not call directly.
223 * Use debris_start_death_roll() if you want to force a debris piece to die.
224 */
debris_delete(object * obj)225 void debris_delete( object * obj )
226 {
227 int num;
228 debris *db;
229
230 num = obj->instance;
231 Assert( Debris[num].objnum == OBJ_INDEX(obj));
232
233 db = &Debris[num];
234
235 Assert( Num_debris_pieces >= 0 );
236 if ( db->is_hull && (db->flags & DEBRIS_EXPIRE) ) {
237 debris_clear_expired_flag(db);
238 }
239
240 db->flags = 0;
241 db->objnum = -1;
242 Num_debris_pieces--;
243 }
244
245 /**
246 * If debris piece *db is far away from all players, make it go away very soon.
247 * In single player game, delete if MAX_DEBRIS_DIST from player.
248 * In multiplayer game, delete if MAX_DEBRIS_DIST from all players.
249 */
maybe_delete_debris(debris * db)250 void maybe_delete_debris(debris *db)
251 {
252 object *objp;
253
254 if (timestamp_elapsed(db->next_distance_check) && timestamp_elapsed(db->must_survive_until)) {
255 if (!(Game_mode & GM_MULTIPLAYER)) { // In single player game, just check against player.
256 if (vm_vec_dist_quick(&Player_obj->pos, &Objects[db->objnum].pos) > MAX_DEBRIS_DIST)
257 db->lifeleft = 0.1f;
258 else
259 db->next_distance_check = timestamp(DEBRIS_DISTANCE_CHECK_TIME);
260 } else {
261 for ( objp = GET_FIRST(&obj_used_list); objp !=END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) ) {
262 if (objp->flags & OF_PLAYER_SHIP) {
263 if (vm_vec_dist_quick(&objp->pos, &Objects[db->objnum].pos) < MAX_DEBRIS_DIST) {
264 db->next_distance_check = timestamp(DEBRIS_DISTANCE_CHECK_TIME);
265 return;
266 }
267 }
268 }
269 db->lifeleft = 0.1f;
270 }
271 }
272 }
273
274 MONITOR(NumSmallDebris)
MONITOR(NumHullDebris)275 MONITOR(NumHullDebris)
276
277 /**
278 * Do various updates to debris: check if time to die, start fireballs
279 * Maybe delete debris if it's very far away from player.
280 *
281 * @param obj pointer to debris object
282 * @param frame_time time elapsed since last debris_move() called
283 */
284 void debris_process_post(object * obj, float frame_time)
285 {
286 int i, num;
287 num = obj->instance;
288
289 int objnum = OBJ_INDEX(obj);
290 Assert( Debris[num].objnum == objnum );
291 debris *db = &Debris[num];
292
293 if ( db->is_hull ) {
294 MONITOR_INC(NumHullDebris,1);
295 radar_plot_object( obj );
296
297 if ( timestamp_elapsed(db->sound_delay) ) {
298 obj_snd_assign(objnum, SND_DEBRIS, &vmd_zero_vector, 0);
299 db->sound_delay = 0;
300 }
301 } else {
302 MONITOR_INC(NumSmallDebris,1);
303 }
304
305 if ( db->lifeleft >= 0.0f) {
306 db->lifeleft -= frame_time;
307 if ( db->lifeleft < 0.0f ) {
308 debris_start_death_roll(obj, db);
309 }
310 }
311
312 maybe_delete_debris(db); // Make this debris go away if it's very far away.
313
314 // ================== DO THE ELECTRIC ARCING STUFF =====================
315 if ( db->arc_frequency <= 0 ) {
316 return; // If arc_frequency <= 0, this piece has no arcs on it
317 }
318
319 if ( !timestamp_elapsed(db->fire_timeout) && timestamp_elapsed(db->next_fireball)) {
320
321 db->next_fireball = timestamp_rand(db->arc_frequency,db->arc_frequency*2 );
322 db->arc_frequency += 100;
323
324 if (db->is_hull) {
325
326 int n, n_arcs = ((rand()>>5) % 3)+1; // Create 1-3 sparks
327
328 vec3d v1, v2, v3, v4;
329
330 if ( Cmdline_old_collision_sys ) {
331 submodel_get_two_random_points( db->model_num, db->submodel_num, &v1, &v2 );
332 submodel_get_two_random_points( db->model_num, db->submodel_num, &v3, &v4 );
333 } else {
334 submodel_get_two_random_points_better( db->model_num, db->submodel_num, &v1, &v2 );
335 submodel_get_two_random_points_better( db->model_num, db->submodel_num, &v3, &v4 );
336 }
337
338 n = 0;
339
340 int a = 100, b = 1000;
341 int lifetime = (myrand()%((b)-(a)+1))+(a);
342
343 // Create the spark effects
344 for (i=0; i<MAX_DEBRIS_ARCS; i++ ) {
345 if ( !timestamp_valid( db->arc_timestamp[i] ) ) {
346
347 db->arc_timestamp[i] = timestamp(lifetime); // live up to a second
348
349 switch( n ) {
350 case 0:
351 db->arc_pts[i][0] = v1;
352 db->arc_pts[i][1] = v2;
353 break;
354 case 1:
355 db->arc_pts[i][0] = v2;
356 db->arc_pts[i][1] = v3;
357 break;
358
359 case 2:
360 db->arc_pts[i][0] = v2;
361 db->arc_pts[i][1] = v4;
362 break;
363
364 default:
365 Int3();
366 }
367
368 n++;
369 if ( n == n_arcs )
370 break; // Don't need to create anymore
371 }
372 }
373
374
375 // rotate v2 out of local coordinates into world.
376 // Use v2 since it is used in every bolt. See above switch().
377 vec3d snd_pos;
378 vm_vec_unrotate(&snd_pos, &v2, &obj->orient);
379 vm_vec_add2(&snd_pos, &obj->pos );
380
381 //Play a sound effect
382 if ( lifetime > 750 ) {
383 // 1.00 second effect
384 snd_play_3d( &Snds[SND_DEBRIS_ARC_05], &snd_pos, &View_position, obj->radius );
385 } else if ( lifetime > 500 ) {
386 // 0.75 second effect
387 snd_play_3d( &Snds[SND_DEBRIS_ARC_04], &snd_pos, &View_position, obj->radius );
388 } else if ( lifetime > 250 ) {
389 // 0.50 second effect
390 snd_play_3d( &Snds[SND_DEBRIS_ARC_03], &snd_pos, &View_position, obj->radius );
391 } else if ( lifetime > 100 ) {
392 // 0.25 second effect
393 snd_play_3d( &Snds[SND_DEBRIS_ARC_02], &snd_pos, &View_position, obj->radius );
394 } else {
395 // 0.10 second effect
396 snd_play_3d( &Snds[SND_DEBRIS_ARC_01], &snd_pos, &View_position, obj->radius );
397 }
398 }
399 }
400
401 for (i=0; i<MAX_DEBRIS_ARCS; i++ ) {
402 if ( timestamp_valid( db->arc_timestamp[i] ) ) {
403 if ( timestamp_elapsed( db->arc_timestamp[i] ) ) {
404 // Kill off the spark
405 db->arc_timestamp[i] = timestamp(-1);
406 } else {
407 // Maybe move a vertex.... 20% of the time maybe?
408 int mr = myrand();
409 if ( mr < RAND_MAX/5 ) {
410 vec3d v1, v2;
411
412 if ( Cmdline_old_collision_sys ) {
413 submodel_get_two_random_points( db->model_num, db->submodel_num, &v1, &v2 );
414 } else {
415 submodel_get_two_random_points_better( db->model_num, db->submodel_num, &v1, &v2 );
416 }
417
418 db->arc_pts[i][mr % 2] = v1;
419 }
420 }
421 }
422 }
423 }
424
425 /**
426 * Locate the oldest hull debris chunk. Search through the ::Hull_debris_list, which is a list
427 * of all the hull debris chunks.
428 */
debris_find_oldest()429 int debris_find_oldest()
430 {
431 int oldest_index;
432 fix oldest_time;
433 debris *db;
434
435 oldest_index = -1;
436 oldest_time = 0x7fffffff;
437
438 for ( db = GET_FIRST(&Hull_debris_list); db != END_OF_LIST(&Hull_debris_list); db = GET_NEXT(db) ) {
439 if ( (db->time_started < oldest_time) && !(Objects[db->objnum].flags & OF_SHOULD_BE_DEAD) ) {
440 oldest_index = DEBRIS_INDEX(db);
441 oldest_time = db->time_started;
442 }
443 }
444
445 return oldest_index;
446 }
447
448 #define DEBRIS_ROTVEL_SCALE 5.0f
449 void calc_debris_physics_properties( physics_info *pi, vec3d *min, vec3d *max );
450
451 /**
452 * Create debris from an object
453 *
454 * @param source_obj Source object
455 * @param model_num Model number
456 * @param submodel_num Sub-model number
457 * @param pos Position in vector space
458 * @param exp_center Explosion center in vector space
459 * @param hull_flag Hull flag settings
460 * @param exp_force Explosion force, used to assign velocity to pieces. 1.0f assigns velocity like before. 2.0f assigns twice as much to non-inherited part of velocity
461 */
debris_create(object * source_obj,int model_num,int submodel_num,vec3d * pos,vec3d * exp_center,int hull_flag,float exp_force)462 object *debris_create(object *source_obj, int model_num, int submodel_num, vec3d *pos, vec3d *exp_center, int hull_flag, float exp_force)
463 {
464 int i, n, objnum, parent_objnum;
465 object *obj;
466 ship *shipp;
467 debris *db;
468 polymodel *pm;
469 int vaporize;
470 physics_info *pi = NULL;
471 ship_info *sip = NULL;
472
473 parent_objnum = OBJ_INDEX(source_obj);
474
475 Assert( (source_obj->type == OBJ_SHIP ) || (source_obj->type == OBJ_GHOST));
476 Assert( source_obj->instance >= 0 && source_obj->instance < MAX_SHIPS );
477 shipp = &Ships[source_obj->instance];
478 sip = &Ship_info[shipp->ship_info_index];
479 vaporize = (shipp->flags &SF_VAPORIZE);
480
481 if ( !hull_flag ) {
482 // Make vaporize debris seen from farther away
483 float dist = vm_vec_dist_quick( pos, &Eye_position );
484 if (vaporize) {
485 dist /= 2.0f;
486 }
487 if ( dist > 200.0f ) {
488 return NULL;
489 }
490 }
491
492 if ( hull_flag && (Num_hull_pieces >= MAX_HULL_PIECES ) ) {
493 // cause oldest hull debris chunk to blow up
494 n = debris_find_oldest();
495 if ( n >= 0 ) {
496 debris_start_death_roll(&Objects[Debris[n].objnum], &Debris[n] );
497 }
498 }
499
500 for (n=0; n<MAX_DEBRIS_PIECES; n++ ) {
501 if ( !(Debris[n].flags & DEBRIS_USED) )
502 break;
503 }
504
505 if (n == MAX_DEBRIS_PIECES) {
506 n = debris_find_oldest();
507
508 if (n >= 0)
509 debris_start_death_roll(&Objects[Debris[n].objnum], &Debris[n]);
510
511 nprintf(("Warning","Frame %i: Could not create debris, no more slots left\n", Framecount));
512 return NULL;
513 }
514
515 db = &Debris[n];
516
517 //WMC - We must survive until now, at least.
518 db->must_survive_until = timestamp();
519
520 if(hull_flag && sip->debris_min_lifetime >= 0.0f && sip->debris_max_lifetime >= 0.0f)
521 {
522 db->lifeleft = (( sip->debris_max_lifetime - sip->debris_min_lifetime ) * frand()) + sip->debris_min_lifetime;
523 }
524 else
525 {
526 // Create Debris piece n!
527 if ( hull_flag ) {
528 if (rand() < RAND_MAX/6) // Make some pieces blow up shortly after explosion.
529 db->lifeleft = 2.0f * ((float) myrand()/(float) RAND_MAX) + 0.5f;
530 else
531 db->lifeleft = -1.0f; // large hull pieces stay around forever
532 } else {
533 db->lifeleft = (i2fl(myrand())/i2fl(RAND_MAX))*2.0f+0.1f;
534 }
535 }
536
537 //WMC - Oh noes, we may need to change lifeleft
538 if(hull_flag)
539 {
540 if(sip->debris_min_lifetime >= 0.0f && sip->debris_max_lifetime >= 0.0f)
541 {
542 db->must_survive_until = timestamp(fl2i(sip->debris_min_lifetime * 1000.0f));
543 db->lifeleft = (( sip->debris_max_lifetime - sip->debris_min_lifetime ) * frand()) + sip->debris_min_lifetime;
544 }
545 else if(sip->debris_min_lifetime >= 0.0f)
546 {
547 db->must_survive_until = timestamp(fl2i(sip->debris_min_lifetime * 1000.0f));
548 if(db->lifeleft < sip->debris_min_lifetime)
549 db->lifeleft = sip->debris_min_lifetime;
550 }
551 else if(sip->debris_max_lifetime >= 0.0f)
552 {
553 if(db->lifeleft > sip->debris_max_lifetime)
554 db->lifeleft = sip->debris_max_lifetime;
555 }
556 }
557
558 // increase lifetime for vaporized debris
559 if (vaporize) {
560 db->lifeleft *= 3.0f;
561 }
562 db->flags |= DEBRIS_USED;
563 db->is_hull = hull_flag;
564 db->source_objnum = parent_objnum;
565 db->source_sig = source_obj->signature;
566 db->ship_info_index = shipp->ship_info_index;
567 db->team = shipp->team;
568 db->fire_timeout = 0; // if not changed, timestamp_elapsed() will return false
569 db->time_started = Missiontime;
570 db->species = Ship_info[shipp->ship_info_index].species;
571 db->next_distance_check = (myrand() % 2000) + 4*DEBRIS_DISTANCE_CHECK_TIME;
572 db->parent_alt_name = shipp->alt_type_index;
573 db->damage_mult = 1.0f;
574
575 for (i=0; i<MAX_DEBRIS_ARCS; i++ ) {
576 db->arc_timestamp[i] = timestamp(-1);
577 }
578
579 if ( db->is_hull ) {
580 // Percent of debris pieces with arcs controlled via table (default 50%)
581 if (frand() < sip->debris_arc_percent) {
582 db->arc_frequency = 1000;
583 } else {
584 db->arc_frequency = 0;
585 }
586 } else {
587 db->arc_frequency = 0;
588 }
589
590 if ( model_num < 0 ) {
591 if (vaporize) {
592 db->model_num = Debris_vaporize_model;
593 } else {
594 db->model_num = Debris_model;
595 }
596 db->submodel_num = (myrand()>>4) % Debris_num_submodels;
597 } else {
598 db->model_num = model_num;
599 db->submodel_num = submodel_num;
600 }
601 float radius = submodel_get_radius( db->model_num, db->submodel_num );
602
603 db->next_fireball = timestamp_rand(500,2000); //start one 1/2 - 2 secs later
604
605 if ( pos == NULL )
606 pos = &source_obj->pos;
607
608 uint flags = OF_RENDERS | OF_PHYSICS;
609 if ( hull_flag )
610 flags |= OF_COLLIDES;
611 objnum = obj_create( OBJ_DEBRIS, parent_objnum, n, &source_obj->orient, pos, radius, flags );
612 if ( objnum == -1 ) {
613 mprintf(("Couldn't create debris object -- out of object slots\n"));
614 return NULL;
615 }
616
617 db->objnum = objnum;
618
619 obj = &Objects[objnum];
620 pi = &obj->phys_info;
621
622 // assign the network signature. The signature will be 0 for non-hull pieces, but since that
623 // is our invalid signature, it should be okay.
624 obj->net_signature = 0;
625
626 if ( (Game_mode & GM_MULTIPLAYER) && hull_flag ) {
627 obj->net_signature = multi_get_next_network_signature( MULTI_SIG_DEBRIS );
628 }
629
630 if (source_obj->type == OBJ_SHIP) {
631 obj->hull_strength = Ships[source_obj->instance].ship_max_hull_strength/8.0f;
632 } else
633 obj->hull_strength = 10.0f;
634
635 if (hull_flag) {
636 if(sip->debris_min_hitpoints >= 0.0f && sip->debris_max_hitpoints >= 0.0f)
637 {
638 obj->hull_strength = (( sip->debris_max_hitpoints - sip->debris_min_hitpoints ) * frand()) + sip->debris_min_hitpoints;
639 }
640 else if(sip->debris_min_hitpoints >= 0.0f)
641 {
642 if(obj->hull_strength < sip->debris_min_hitpoints)
643 obj->hull_strength = sip->debris_min_hitpoints;
644 }
645 else if(sip->debris_max_hitpoints >= 0.0f)
646 {
647 if(obj->hull_strength > sip->debris_max_hitpoints)
648 obj->hull_strength = sip->debris_max_hitpoints;
649 }
650 db->damage_mult = sip->debris_damage_mult;
651 }
652
653 Num_debris_pieces++;
654
655 vec3d rotvel, radial_vel, to_center;
656
657 if ( exp_center )
658 vm_vec_sub( &to_center,pos, exp_center );
659 else
660 vm_vec_zero(&to_center);
661
662 float scale;
663
664 if ( hull_flag ) {
665 float t;
666 scale = exp_force * i2fl((myrand()%20) + 10); // for radial_vel away from location of blast center
667 db->sound_delay = timestamp(DEBRIS_SOUND_DELAY);
668
669 // set up physics mass and I_inv for hull debris pieces
670 pm = model_get(model_num);
671 vec3d *min, *max;
672 min = &pm->submodel[submodel_num].min;
673 max = &pm->submodel[submodel_num].max;
674 calc_debris_physics_properties( &obj->phys_info, min, max );
675
676 // limit the amount of time that fireballs appear
677 // let fireball length be linked to radius of ship. Range is .33 radius => 3.33 radius seconds.
678 t = 1000*Objects[db->source_objnum].radius/3 + myrand()%(fl2i(1000*3*Objects[db->source_objnum].radius));
679 db->fire_timeout = timestamp(fl2i(t)); // fireballs last from 5 - 30 seconds
680
681 if ( Objects[db->source_objnum].radius < MIN_RADIUS_FOR_PERSISTANT_DEBRIS ) {
682 db->flags |= DEBRIS_EXPIRE; // debris can expire
683 Num_hull_pieces++;
684 list_append(&Hull_debris_list, db);
685 } else {
686 nprintf(("Alan","A forever chunk of debris was created from ship with radius %f\n",Objects[db->source_objnum].radius));
687 }
688 }
689 else {
690 scale = exp_force * i2fl((myrand()%20) + 10); // for radial_vel away from blast center (non-hull)
691 }
692
693 if ( vm_vec_mag_squared( &to_center ) < 0.1f ) {
694 vm_vec_rand_vec_quick(&radial_vel);
695 vm_vec_scale(&radial_vel, scale );
696 }
697 else {
698 vm_vec_normalize(&to_center);
699 vm_vec_copy_scale(&radial_vel, &to_center, scale );
700 }
701
702 // DA: here we need to vel_from_rot = w x to_center, where w is world is unrotated to world coords and offset is the
703 // displacement fromt the center of the parent object to the center of the debris piece
704 vec3d world_rotvel, vel_from_rotvel;
705 vm_vec_unrotate ( &world_rotvel, &source_obj->phys_info.rotvel, &source_obj->orient );
706 vm_vec_crossprod ( &vel_from_rotvel, &world_rotvel, &to_center );
707 vm_vec_scale ( &vel_from_rotvel, DEBRIS_ROTVEL_SCALE);
708
709 vm_vec_add (&obj->phys_info.vel, &radial_vel, &source_obj->phys_info.vel);
710 vm_vec_add2(&obj->phys_info.vel, &vel_from_rotvel);
711
712 // make sure rotational velocity does not get too high
713 if (radius < 1.0) {
714 radius = 1.0f;
715 }
716
717 scale = ( 6.0f + i2fl(myrand()%4) ) / radius;
718
719 vm_vec_rand_vec_quick(&rotvel);
720 vm_vec_scale(&rotvel, scale);
721
722 pi->flags |= PF_DEAD_DAMP;
723 pi->rotvel = rotvel;
724 check_rotvel_limit( &obj->phys_info );
725
726 // check that debris is not created with too high a velocity
727 if (hull_flag)
728 {
729 shipfx_debris_limit_speed(db, shipp);
730 }
731
732 // blow out his reverse thrusters. Or drag, same thing.
733 pi->rotdamp = 10000.0f;
734 pi->side_slip_time_const = 10000.0f;
735 pi->flags |= (PF_REDUCED_DAMP | PF_DEAD_DAMP); // set damping equal for all axis and not changable
736
737 vm_vec_zero(&pi->max_vel); // make so he can't turn on his own VOLITION anymore.
738 vm_vec_zero(&pi->max_rotvel); // make so he can't change speed on his own VOLITION anymore.
739
740 // ensure vel is valid
741 Assert( !vm_is_vec_nan(&obj->phys_info.vel) );
742
743 return obj;
744 }
745
746 /**
747 * Alas, poor debris_obj got whacked. Fortunately, we know who did it, where and how hard, so we
748 * can do something about it.
749 */
debris_hit(object * debris_obj,object * other_obj,vec3d * hitpos,float damage)750 void debris_hit(object *debris_obj, object *other_obj, vec3d *hitpos, float damage)
751 {
752 debris *debris_p = &Debris[debris_obj->instance];
753
754 // Do a little particle spark shower to show we hit
755 {
756 particle_emitter pe;
757
758 pe.pos = *hitpos; // Where the particles emit from
759 pe.vel = debris_obj->phys_info.vel; // Initial velocity of all the particles
760
761 vec3d tmp_norm;
762 vm_vec_sub( &tmp_norm, hitpos, &debris_obj->pos );
763 vm_vec_normalize_safe(&tmp_norm);
764
765 pe.normal = tmp_norm; // What normal the particle emit around
766 pe.normal_variance = 0.3f; // How close they stick to that normal 0=good, 1=360 degree
767 pe.min_rad = 0.20f; // Min radius
768 pe.max_rad = 0.40f; // Max radius
769
770 // Sparks for first time at this spot
771 pe.num_low = 10; // Lowest number of particles to create
772 pe.num_high = 10; // Highest number of particles to create
773 pe.normal_variance = 0.3f; // How close they stick to that normal 0=good, 1=360 degree
774 pe.min_vel = 0.0f; // How fast the slowest particle can move
775 pe.max_vel = 10.0f; // How fast the fastest particle can move
776 pe.min_life = 0.25f; // How long the particles live
777 pe.max_life = 0.75f; // How long the particles live
778 particle_emit( &pe, PARTICLE_FIRE, 0 );
779 }
780
781 // multiplayer clients bail here
782 if(MULTIPLAYER_CLIENT){
783 return;
784 }
785
786 if ( damage < 0.0f ) {
787 damage = 0.0f;
788 }
789
790 debris_obj->hull_strength -= damage;
791
792 if (debris_obj->hull_strength < 0.0f) {
793 debris_start_death_roll(debris_obj, debris_p );
794 } else {
795 // otherwise, give all the other players an update on the debris
796 if(MULTIPLAYER_MASTER){
797 send_debris_update_packet(debris_obj,DEBRIS_UPDATE_UPDATE);
798 }
799 }
800 }
801
802 /**
803 * See if poor debris object *obj got whacked by evil *other_obj at point *hitpos.
804 * NOTE: debris_hit_info pointer NULL for debris:weapon collision, otherwise debris:ship collision.
805 * @return true if hit, else return false.
806 */
debris_check_collision(object * pdebris,object * other_obj,vec3d * hitpos,collision_info_struct * debris_hit_info)807 int debris_check_collision(object *pdebris, object *other_obj, vec3d *hitpos, collision_info_struct *debris_hit_info)
808 {
809 mc_info mc;
810 mc_info_init(&mc);
811 int num;
812
813 Assert( pdebris->type == OBJ_DEBRIS );
814
815 num = pdebris->instance;
816 Assert( num >= 0 );
817
818 Assert( Debris[num].objnum == OBJ_INDEX(pdebris));
819
820 // debris_hit_info NULL - so debris-weapon collision
821 if ( debris_hit_info == NULL ) {
822 // debris weapon collision
823 Assert( other_obj->type == OBJ_WEAPON );
824 mc.model_instance_num = -1;
825 mc.model_num = Debris[num].model_num; // Fill in the model to check
826 mc.submodel_num = Debris[num].submodel_num;
827 model_clear_instance( mc.model_num );
828 mc.orient = &pdebris->orient; // The object's orient
829 mc.pos = &pdebris->pos; // The object's position
830 mc.p0 = &other_obj->last_pos; // Point 1 of ray to check
831 mc.p1 = &other_obj->pos; // Point 2 of ray to check
832 mc.flags = (MC_CHECK_MODEL | MC_SUBMODEL);
833
834 if (model_collide(&mc)) {
835 *hitpos = mc.hit_point_world;
836 }
837
838 weapon *wp = &Weapons[other_obj->instance];
839 wp->collisionOccured = true;
840 memcpy(&wp->collisionInfo, &mc, sizeof(mc_info));
841
842 return mc.num_hits;
843 }
844
845 // debris ship collision -- use debris_hit_info to calculate physics
846 object *ship_obj = other_obj;
847 Assert( ship_obj->type == OBJ_SHIP );
848
849 object *heavy = debris_hit_info->heavy;
850 object *light = debris_hit_info->light;
851 object *heavy_obj = heavy;
852 object *light_obj = light;
853
854 vec3d zero, p0, p1;
855 vm_vec_zero(&zero);
856 vm_vec_sub(&p0, &light->last_pos, &heavy->last_pos);
857 vm_vec_sub(&p1, &light->pos, &heavy->pos);
858
859 mc.pos = &zero; // The object's position
860 mc.p0 = &p0; // Point 1 of ray to check
861 mc.p1 = &p1; // Point 2 of ray to check
862
863 // find the light object's position in the heavy object's reference frame at last frame and also in this frame.
864 vec3d p0_temp, p0_rotated;
865
866 // Collision detection from rotation enabled if at rotaion is less than 30 degree in frame
867 // This should account for all ships
868 if ( (vm_vec_mag_squared(&heavy->phys_info.rotvel) * flFrametime*flFrametime) < (PI*PI/36) ) {
869 // collide_rotate calculate (1) start position and (2) relative velocity
870 debris_hit_info->collide_rotate = 1;
871 vm_vec_rotate(&p0_temp, &p0, &heavy->last_orient);
872 vm_vec_unrotate(&p0_rotated, &p0_temp, &heavy->orient);
873 mc.p0 = &p0_rotated; // Point 1 of ray to check
874 vm_vec_sub(&debris_hit_info->light_rel_vel, &p1, &p0_rotated);
875 vm_vec_scale(&debris_hit_info->light_rel_vel, 1/flFrametime);
876 } else {
877 debris_hit_info->collide_rotate = 0;
878 vm_vec_sub(&debris_hit_info->light_rel_vel, &light->phys_info.vel, &heavy->phys_info.vel);
879 }
880
881 int mc_ret_val = 0;
882
883 if ( debris_hit_info->heavy == ship_obj ) { // ship is heavier, so debris is sphere. Check sphere collision against ship poly model
884 mc.model_instance_num = Ships[ship_obj->instance].model_instance_num;
885 mc.model_num = Ship_info[Ships[ship_obj->instance].ship_info_index].model_num; // Fill in the model to check
886 mc.orient = &ship_obj->orient; // The object's orient
887 mc.radius = pdebris->radius;
888 mc.flags = (MC_CHECK_MODEL | MC_CHECK_SPHERELINE);
889
890 // copy important data
891 int copy_flags = mc.flags; // make a copy of start end positions of sphere in big ship RF
892 vec3d copy_p0, copy_p1;
893 copy_p0 = *mc.p0;
894 copy_p1 = *mc.p1;
895
896 // first test against the sphere - if this fails then don't do any submodel tests
897 mc.flags = MC_ONLY_SPHERE | MC_CHECK_SPHERELINE;
898
899 SCP_vector<int> submodel_vector;
900 polymodel *pm;
901 polymodel_instance *pmi;
902
903 ship_model_start(ship_obj);
904
905 if (model_collide(&mc)) {
906
907 // Set earliest hit time
908 debris_hit_info->hit_time = FLT_MAX;
909
910 // Do collision the cool new way
911 if ( debris_hit_info->collide_rotate ) {
912 SCP_vector<int>::iterator smv;
913
914 // We collide with the sphere, find the list of rotating submodels and test one at a time
915 model_get_rotating_submodel_list(&submodel_vector, heavy_obj);
916
917 // Get polymodel and turn off all rotating submodels, collide against only 1 at a time.
918 pm = model_get(Ship_info[Ships[heavy_obj->instance].ship_info_index].model_num);
919 pmi = model_get_instance(Ships[heavy_obj->instance].model_instance_num);
920
921 // turn off all rotating submodels and test for collision
922 for (smv = submodel_vector.begin(); smv != submodel_vector.end(); ++smv) {
923 pmi->submodel[*smv].collision_checked = true;
924 }
925
926 // reset flags to check MC_CHECK_MODEL | MC_CHECK_SPHERELINE and maybe MC_CHECK_INVISIBLE_FACES and MC_SUBMODEL_INSTANCE
927 mc.flags = copy_flags | MC_SUBMODEL_INSTANCE;
928
929 // check each submodel in turn
930 for (smv = submodel_vector.begin(); smv != submodel_vector.end(); ++smv) {
931 // turn on submodel for collision test
932 pmi->submodel[*smv].collision_checked = false;
933
934 // set angles for last frame (need to set to prev to get p0)
935 angles copy_angles = pmi->submodel[*smv].angs;
936
937 // find the start and end positions of the sphere in submodel RF
938 pmi->submodel[*smv].angs = pmi->submodel[*smv].prev_angs;
939 world_find_model_instance_point(&p0, &light_obj->last_pos, pm, pmi, *smv, &heavy_obj->last_orient, &heavy_obj->last_pos);
940
941 pmi->submodel[*smv].angs = copy_angles;
942 world_find_model_instance_point(&p1, &light_obj->pos, pm, pmi, *smv, &heavy_obj->orient, &heavy_obj->pos);
943
944 mc.p0 = &p0;
945 mc.p1 = &p1;
946
947 mc.orient = &vmd_identity_matrix;
948 mc.submodel_num = *smv;
949
950 if ( model_collide(&mc) ) {
951 if ( mc.hit_dist < debris_hit_info->hit_time ) {
952 mc_ret_val = 1;
953
954 // set up debris_hit_info common
955 set_hit_struct_info(debris_hit_info, &mc, SUBMODEL_ROT_HIT);
956 model_instance_find_world_point(&debris_hit_info->hit_pos, &mc.hit_point, mc.model_num, mc.model_instance_num, mc.hit_submodel, &heavy_obj->orient, &zero);
957
958 // set up debris_hit_info for rotating submodel
959 if (debris_hit_info->edge_hit == 0) {
960 model_instance_find_obj_dir(&debris_hit_info->collision_normal, &mc.hit_normal, heavy_obj, mc.hit_submodel);
961 }
962
963 // find position in submodel RF of light object at collison
964 vec3d int_light_pos, diff;
965 vm_vec_sub(&diff, mc.p1, mc.p0);
966 vm_vec_scale_add(&int_light_pos, mc.p0, &diff, mc.hit_dist);
967 model_instance_find_world_point(&debris_hit_info->light_collision_cm_pos, &int_light_pos, mc.model_num, mc.model_instance_num, mc.hit_submodel, &heavy_obj->orient, &zero);
968 }
969 }
970 // Don't look at this submodel again
971 pmi->submodel[*smv].collision_checked = true;
972 }
973 }
974
975 // Recover and do usual ship_ship collision, but without rotating submodels
976 mc.flags = copy_flags;
977 *mc.p0 = copy_p0;
978 *mc.p1 = copy_p1;
979 mc.orient = &heavy_obj->orient;
980
981 // usual ship_ship collision test
982 if ( model_collide(&mc) ) {
983 // check if this is the earliest hit
984 if (mc.hit_dist < debris_hit_info->hit_time) {
985 mc_ret_val = 1;
986
987 set_hit_struct_info(debris_hit_info, &mc, SUBMODEL_NO_ROT_HIT);
988
989 // get collision normal if not edge hit
990 if (debris_hit_info->edge_hit == 0) {
991 model_instance_find_obj_dir(&debris_hit_info->collision_normal, &mc.hit_normal, heavy_obj, mc.hit_submodel);
992 }
993
994 // find position in submodel RF of light object at collison
995 vec3d diff;
996 vm_vec_sub(&diff, mc.p1, mc.p0);
997 vm_vec_scale_add(&debris_hit_info->light_collision_cm_pos, mc.p0, &diff, mc.hit_dist);
998
999 }
1000 }
1001 }
1002
1003 } else {
1004 // Debris is heavier obj
1005 mc.model_instance_num = -1;
1006 mc.model_num = Debris[num].model_num; // Fill in the model to check
1007 mc.submodel_num = Debris[num].submodel_num;
1008 model_clear_instance( mc.model_num );
1009 mc.orient = &pdebris->orient; // The object's orient
1010 mc.radius = model_get_core_radius(Ship_info[Ships[ship_obj->instance].ship_info_index].model_num);
1011
1012 // check for collision between debris model and ship sphere
1013 mc.flags = (MC_CHECK_MODEL | MC_SUBMODEL | MC_CHECK_SPHERELINE);
1014
1015 mc_ret_val = model_collide(&mc);
1016
1017 if (mc_ret_val) {
1018 set_hit_struct_info(debris_hit_info, &mc, SUBMODEL_NO_ROT_HIT);
1019
1020 // set normal if not edge hit
1021 if ( !debris_hit_info->edge_hit ) {
1022 vm_vec_unrotate(&debris_hit_info->collision_normal, &mc.hit_normal, &heavy->orient);
1023 }
1024
1025 // find position in submodel RF of light object at collison
1026 vec3d diff;
1027 vm_vec_sub(&diff, mc.p1, mc.p0);
1028 vm_vec_scale_add(&debris_hit_info->light_collision_cm_pos, mc.p0, &diff, mc.hit_dist);
1029
1030 }
1031 }
1032
1033
1034 if ( mc_ret_val ) {
1035
1036 // SET PHYSICS PARAMETERS
1037 // already have (hitpos - heavy) and light_cm_pos
1038 // get heavy cm pos - already have light_cm_pos
1039 debris_hit_info->heavy_collision_cm_pos = zero;
1040
1041 // get r_heavy and r_light
1042 debris_hit_info->r_heavy = debris_hit_info->hit_pos;
1043 vm_vec_sub(&debris_hit_info->r_light, &debris_hit_info->hit_pos, &debris_hit_info->light_collision_cm_pos);
1044
1045 // set normal for edge hit
1046 if ( debris_hit_info->edge_hit ) {
1047 vm_vec_copy_normalize(&debris_hit_info->collision_normal, &debris_hit_info->r_light);
1048 vm_vec_negate(&debris_hit_info->collision_normal);
1049 }
1050
1051 // get world hitpos
1052 vm_vec_add(hitpos, &debris_hit_info->heavy->pos, &debris_hit_info->r_heavy);
1053
1054 return 1;
1055 } else {
1056 // no hit
1057 return 0;
1058 }
1059 }
1060
1061 /**
1062 * Return the team field for a debris object
1063 */
debris_get_team(object * objp)1064 int debris_get_team(object *objp)
1065 {
1066 Assert( objp->type == OBJ_DEBRIS );
1067 Assert( objp->instance >= 0 && objp->instance < MAX_DEBRIS_PIECES );
1068 return Debris[objp->instance].team;
1069 }
1070
1071 /**
1072 * Fills in debris physics properties when created, specifically mass and moment of inertia
1073 */
calc_debris_physics_properties(physics_info * pi,vec3d * mins,vec3d * maxs)1074 void calc_debris_physics_properties( physics_info *pi, vec3d *mins, vec3d *maxs )
1075 {
1076 float dx, dy, dz, mass;
1077 dx = maxs->xyz.x - mins->xyz.x;
1078 dy = maxs->xyz.y - mins->xyz.y;
1079 dz = maxs->xyz.z - mins->xyz.z;
1080
1081 // John, with new bspgen, just set pi->mass = mass
1082 mass = 0.12f * dx * dy * dz;
1083 pi->mass = (float) pow(mass, 0.6666667f) * 4.65f;
1084
1085 pi->I_body_inv.vec.rvec.xyz.x = 12.0f / (pi->mass * (dy*dy + dz*dz));
1086 pi->I_body_inv.vec.rvec.xyz.y = 0.0f;
1087 pi->I_body_inv.vec.rvec.xyz.z = 0.0f;
1088
1089 pi->I_body_inv.vec.uvec.xyz.x = 0.0f;
1090 pi->I_body_inv.vec.uvec.xyz.y = 12.0f / (pi->mass * (dx*dx + dz*dz));
1091 pi->I_body_inv.vec.uvec.xyz.z = 0.0f;
1092
1093 pi->I_body_inv.vec.fvec.xyz.x = 0.0f;
1094 pi->I_body_inv.vec.fvec.xyz.y = 0.0f;
1095 pi->I_body_inv.vec.fvec.xyz.z = 12.0f / (pi->mass * (dx*dx + dy*dy));
1096 }
1097