]> icculus.org git repositories - taylor/freespace2.git/blob - src/object/collideshipweapon.cpp
make first pass at async popups
[taylor/freespace2.git] / src / object / collideshipweapon.cpp
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
6  * the source.
7  */
8
9 /*
10  * $Logfile: /Freespace2/code/Object/CollideShipWeapon.cpp $
11  * $Revision$
12  * $Date$
13  * $Author$
14  *
15  * Routines to detect collisions and do physics, damage, etc for weapons and ships
16  *
17  * $Log$
18  * Revision 1.4  2002/06/17 06:33:10  relnev
19  * ryan's struct patch for gcc 2.95
20  *
21  * Revision 1.3  2002/06/09 04:41:24  relnev
22  * added copyright header
23  *
24  * Revision 1.2  2002/05/07 03:16:48  theoddone33
25  * The Great Newline Fix
26  *
27  * Revision 1.1.1.1  2002/05/03 03:28:10  root
28  * Initial import.
29  *
30  * 
31  * 13    8/30/99 11:06a Andsager
32  * Fix bug preventing ship_apply_whack()
33  * 
34  * 12    8/16/99 11:58p Andsager
35  * Disable collision on proximity for ships with SIF_DONT_COLLIDE_INVIS
36  * hulls.
37  * 
38  * 11    7/22/99 7:18p Dave
39  * Fixed excessive multiplayer collision whacks.
40  * 
41  * 10    6/30/99 5:53p Dave
42  * Put in new anti-camper code.
43  * 
44  * 9     6/25/99 3:37p Jasons
45  * Whoops. Only send pain packet for WP_LASER subtypes.
46  * 
47  * 8     6/21/99 7:24p Dave
48  * netplayer pain packet. Added type E unmoving beams.
49  * 
50  * 7     6/01/99 8:35p Dave
51  * Finished lockarm weapons. Added proper supercap weapons/damage. Added
52  * awacs-set-radius sexpression.
53  * 
54  * 6     4/23/99 12:01p Johnson
55  * Added SIF_HUGE_SHIP
56  * 
57  * 5     3/10/99 6:50p Dave
58  * Changed the way we buffer packets for all clients. Optimized turret
59  * fired packets. Did some weapon firing optimizations.
60  * 
61  * 4     1/12/99 5:45p Dave
62  * Moved weapon pipeline in multiplayer to almost exclusively client side.
63  * Very good results. Bandwidth goes down, playability goes up for crappy
64  * connections. Fixed object update problem for ship subsystems.
65  * 
66  * 3     10/20/98 1:39p Andsager
67  * Make so sparks follow animated ship submodels.  Modify
68  * ship_weapon_do_hit_stuff() and ship_apply_local_damage() to add
69  * submodel_num.  Add submodel_num to multiplayer hit packet.
70  * 
71  * 2     10/07/98 10:53a Dave
72  * Initial checkin.
73  * 
74  * 1     10/07/98 10:50a Dave
75  * 
76  * 21    4/13/98 2:14p Mike
77  * Countermeasure balance testing for Jim.
78  * 
79  * 20    4/01/98 1:48p Allender
80  * major changes to ship collision in multiplayer.  Clients now do own
81  * ship/ship collisions (with their own ship only)  Modifed the hull
82  * update packet to be sent quicker when object is target of player.
83  * 
84  * 19    3/31/98 5:18p John
85  * Removed demo/save/restore.  Made NDEBUG defined compile.  Removed a
86  * bunch of debug stuff out of player file.  Made model code be able to
87  * unload models and malloc out only however many models are needed.
88  *  
89  * 
90  * 18    3/25/98 1:33p Andsager
91  * Comment out assert
92  * 
93  * 17    3/16/98 5:17p Mike
94  * Fix arm time.  Make missiles not explode in first second.
95  * 
96  * 16    3/14/98 5:02p Lawrance
97  * Support arbitrary wings in the wingman status gauge
98  * 
99  * 15    2/23/98 4:30p Mike
100  * Make homing missiles detonate after they pass up their target.  Make
101  * countermeasures less effective.
102  * 
103  * 14    2/10/98 5:02p Andsager
104  * Big ship:weapon collision use flag SIF_BIG_SHIP or SIF_CAPITAL
105  * 
106  * 13    2/10/98 2:57p Jasen
107  * Andsager: predict speed when big ship has zero acceleration (ie,
108  * instalation)
109  * 
110  * 12    2/09/98 1:19p Andsager
111  * 
112  * 11    2/01/98 2:53p Mike
113  * Much better time returned by hud_ai_get_dock_time or whatever it's
114  * called.
115  * 
116  * 10    1/24/98 3:21p Lawrance
117  * Add flashing when hit, and correct association with the wingman status
118  * gauge.
119  * 
120  * 9     1/14/98 1:43p Lawrance
121  * Puncture weapons don't pass through shields any more.
122  * 
123  * 8     1/13/98 8:09p John
124  * Removed the old collision system that checked all pairs.   Added code
125  * to disable collisions and particles.
126  * 
127  * 7     11/18/97 6:00p Lawrance
128  * modify how hud_shield_quadrant_hit() gets called
129  * 
130  * 6     11/08/97 11:08p Lawrance
131  * implement new "mini-shield" view that sits near bottom of reticle
132  * 
133  * 5     11/07/97 4:36p Mike
134  * Change how ships determine they're under attack by dumbfire weapons.
135  * 
136  * 4     10/27/97 8:35a John
137  * code for new player warpout sequence
138  * 
139  * 3     9/18/97 4:08p John
140  * Cleaned up & restructured ship damage stuff.
141  * 
142  * 2     9/17/97 5:12p John
143  * Restructured collision routines.  Probably broke a lot of stuff.
144  * 
145  * 1     9/17/97 2:14p John
146  * Initial revision
147  *
148  * $NoKeywords: $
149  */
150
151 #include "objcollide.h"
152 #include "model.h"
153 #include "shiphit.h"
154 #include "player.h"
155 #include "hudshield.h"
156 #include "hud.h"
157 #include "hudwingmanstatus.h"
158 #include "timer.h"
159 #include "freespace.h"
160 #include "multi.h"
161 #include "multiutil.h"
162 #include "multimsgs.h"
163
164
165 extern float ai_endangered_time(object *ship_objp, object *weapon_objp);
166 int check_inside_radius_for_big_ships( object *ship, object *weapon, obj_pair *pair );
167 float estimate_ship_speed_upper_limit( object *ship, float time );
168 extern float flFrametime;
169
170
171 //      If weapon_obj is likely to hit ship_obj sooner than current aip->danger_weapon_objnum,
172 //      then update danger_weapon_objnum.
173 void update_danger_weapon(object *ship_obj, object *weapon_obj)
174 {
175         ai_info *aip;
176
177         SDL_assert(ship_obj->type == OBJ_SHIP);
178
179         aip = &Ai_info[Ships[ship_obj->instance].ai_index];
180
181         if (aip->danger_weapon_objnum == -1) {
182                 aip->danger_weapon_objnum = weapon_obj-Objects;
183                 aip->danger_weapon_signature = weapon_obj->signature;
184         } else if (aip->danger_weapon_signature == Objects[aip->danger_weapon_objnum].signature) {
185                 float   danger_old_time, danger_new_time;
186
187                 danger_old_time = ai_endangered_time(ship_obj, &Objects[aip->danger_weapon_objnum]);
188                 danger_new_time = ai_endangered_time(ship_obj, weapon_obj);
189
190                 if (danger_new_time < danger_old_time) {
191                         aip->danger_weapon_objnum = weapon_obj-Objects;
192                         aip->danger_weapon_signature = weapon_obj->signature;
193                 }
194         }
195 }
196
197 // function to actually deal with weapon-ship hit stuff.  separated from check_collision routine below
198 // because of multiplayer reasons.
199 void ship_weapon_do_hit_stuff(object *ship_obj, object *weapon_obj, vector *world_hitpos, vector *hitpos, int quadrant_num, int submodel_num)
200 {
201         weapon  *wp = &Weapons[weapon_obj->instance];
202         weapon_info     *wip = &Weapon_info[wp->weapon_info_index];
203         ship *shipp = &Ships[ship_obj->instance];       
204         float damage;
205         vector force;           
206
207         // Apply hit & damage & stuff to weapon
208         weapon_hit(weapon_obj, ship_obj,  world_hitpos);
209
210         damage = wip->damage;
211
212         // deterine whack whack
213         float           blast = wip->mass;
214         vm_vec_copy_scale(&force, &weapon_obj->phys_info.vel, blast );  
215
216         // send player pain packet
217         if ( (MULTIPLAYER_MASTER) && !(shipp->flags & SF_DYING) ){
218                 int np_index = multi_find_player_by_object(ship_obj);
219
220                 // if this is a player ship
221                 if((np_index >= 0) && (np_index != MY_NET_PLAYER_NUM) && (wip->subtype == WP_LASER)){
222                         send_player_pain_packet(&Net_players[np_index], wp->weapon_info_index, wip->damage * weapon_get_damage_scale(wip, weapon_obj, ship_obj), &force, hitpos);
223                 }
224         }       
225
226         ship_apply_local_damage(ship_obj, weapon_obj, world_hitpos, damage, quadrant_num, CREATE_SPARKS, submodel_num);
227
228         // let the hud shield gauge know when Player or Player target is hit
229         hud_shield_quadrant_hit(ship_obj, quadrant_num);
230
231         // Let wingman status gauge know a wingman ship was hit
232         if ( (Ships[ship_obj->instance].wing_status_wing_index >= 0) && ((Ships[ship_obj->instance].wing_status_wing_pos >= 0)) ) {
233                 hud_wingman_status_start_flash(shipp->wing_status_wing_index, shipp->wing_status_wing_pos);
234         }
235
236         // Apply a wack.  This used to be inside of ship_hit... duh! Ship_hit
237         // is to apply damage, not physics, so I moved it here.
238         // don't apply whack for multiplayer_client from laser - will occur with pain packet
239         if( !((wip->subtype == WP_LASER) && MULTIPLAYER_CLIENT) ) {             
240                 // apply a whack                
241                 ship_apply_whack( &force, hitpos, ship_obj );
242         }
243 }
244
245 extern int Framecount;
246
247 int ship_weapon_check_collision(object * ship_obj, object * weapon_obj, float time_limit = 0.0f, int *next_hit=NULL)
248 {
249         mc_info mc;
250         int     num;
251         ship    *shipp;
252         weapon  *wp = &Weapons[weapon_obj->instance];
253         weapon_info     *wip = &Weapon_info[wp->weapon_info_index];
254
255         SDL_assert( ship_obj->type == OBJ_SHIP );
256         SDL_assert( weapon_obj->type == OBJ_WEAPON );
257
258         num = ship_obj->instance;
259         SDL_assert( num >= 0 );
260         SDL_assert( Ships[num].objnum == OBJ_INDEX(ship_obj));
261
262         shipp = &Ships[num];
263
264         // Make ships that are warping in not get collision detection done
265         if ( shipp->flags & SF_ARRIVING ) return 0;
266
267         // if one object is a capital, only check player and player weapons with
268         // the capital -- too slow for now otherwise.
269 //      if ( Polygon_models[Ships[num].modelnum].use_grid && !( (other_obj == Player_obj) || (&Objects[other_obj->parent] == Player_obj)) )
270 //              return 0;
271
272         //      If either of these objects doesn't get collision checks, abort.
273         if (!(Ship_info[shipp->ship_info_index].flags & SIF_DO_COLLISION_CHECK))
274                 return 0;
275
276         //      Return information for AI to detect incoming fire.
277         //      Could perhaps be done elsewhere at lower cost --MK, 11/7/97
278         float   dist = vm_vec_dist_quick(&ship_obj->pos, &weapon_obj->pos);
279         if (dist < weapon_obj->phys_info.speed) {
280                 update_danger_weapon(ship_obj, weapon_obj);
281         }
282         
283         ship_model_start(ship_obj);
284
285         int     valid_hit_occured = 0;                          // If this is set, then hitpos is set
286         int     do_model_check = 1;                                     // Assume we need to check the model
287         int     quadrant_num = -1;
288
289         //      total time is flFrametime + time_limit (time_limit used to predict collisions into the future)
290         vector weapon_end_pos;
291         vm_vec_scale_add( &weapon_end_pos, &weapon_obj->pos, &weapon_obj->phys_info.vel, time_limit );
292
293         memset(&mc, -1, sizeof(mc_info));
294         mc.model_num = shipp->modelnum;                 // Fill in the model to check
295         mc.orient = &ship_obj->orient;                  // The object's orient
296         mc.pos = &ship_obj->pos;                                        // The object's position
297         mc.p0 = &weapon_obj->last_pos;                  // Point 1 of ray to check
298         mc.p1 = &weapon_end_pos;                                        // Point 2 of ray to check
299
300         polymodel *pm = model_get( shipp->modelnum );
301
302         // Check the shields for an impact if necessary
303 #ifndef NDEBUG
304         if (!(ship_obj->flags & OF_NO_SHIELDS) && New_shield_system && (pm->shield.ntris > 0)) {
305 #else
306         if (!(ship_obj->flags & OF_NO_SHIELDS) &&  (pm->shield.ntris > 0)) {
307 #endif
308
309                 mc.flags = MC_CHECK_SHIELD;
310
311                 if ( model_collide(&mc) )       {
312                         quadrant_num = get_quadrant(&mc.hit_point);
313                         //      Note: This code is obviously stupid. We want to add the shield point if there is shield to hit, but:
314                         //              1. We want the size/color of the hit effect to indicate shield damage done.  (Ie, for already-weak shield, smaller effect.)
315                         //              2. Currently (8/9/97), apply_damage_to_shield() passes lefer damage to hull, which might not make sense.  If
316                         //                      wouldn't have collided with hull, shouldn't do damage.  Once this is fixed, the code below needs to cast the
317                         //                      vector through to the hull if there is leftover damage.
318                         if (!(shipp->flags & SF_DYING) && ship_is_shield_up(ship_obj,quadrant_num) ) {
319
320                                 // AL 1-14-97: "Puncture" doesn't mean penetrate shield anymore, it means that it punctures
321                                 //                                      hull do inflict maximum subsystem damage
322 /*
323                                 if ( Weapon_info[Weapons[other_obj->instance].weapon_info_index].wi_flags & WIF_PUNCTURE )      {
324                                         // If this weapon punctures the shield, then do
325                                         // the hit effect, but act like a shield collision never occurred.
326                                         quadrant_num = -1;      // ignore shield hit
327                                         add_shield_point(obj-Objects, mc.shield_hit_tri, &mc.hit_point);
328                                 } else {
329 */
330                                 valid_hit_occured = 1;
331                                 // shield effect
332                                 add_shield_point(ship_obj-Objects, mc.shield_hit_tri, &mc.hit_point);
333                                 do_model_check = 0;     // since we hit the shield, no need to check the model
334
335                         } else {
336                                 quadrant_num = -1;      // ignore shield hit
337                         }
338                 }
339         } 
340
341         // Check the model for an impact if necessary
342         if ( do_model_check )   {                       
343                 mc.flags = MC_CHECK_MODEL;                      // flags
344
345                 if (model_collide(&mc)) {
346                         valid_hit_occured = 1;
347                 }
348         }
349
350         //nprintf(("AI", "Frame %i, Hit tri = %i\n", Framecount, mc.shield_hit_tri));
351         ship_model_stop(ship_obj);
352
353         // deal with predictive collisions.  Find their actual hit time and see if they occured in current frame
354         if (next_hit && valid_hit_occured) {
355                 // find hit time
356                 *next_hit = (int) (1000.0f * (mc.hit_dist*(flFrametime + time_limit) - flFrametime) );
357                 if (*next_hit > 0)
358                         // if hit occurs outside of this frame, do not do damage 
359                         return 1;
360         }
361
362         if ( valid_hit_occured )        {
363                 ship_weapon_do_hit_stuff(ship_obj, weapon_obj, &mc.hit_point_world, &mc.hit_point, quadrant_num, mc.hit_submodel);
364         } else if ((Missiontime - wp->creation_time > F1_0/2) && (wip->wi_flags & WIF_HOMING) && (wp->homing_object == ship_obj)) {
365                 if (dist < wip->inner_radius) {
366                         vector  vec_to_ship;
367
368                         vm_vec_normalized_dir(&vec_to_ship, &ship_obj->pos, &weapon_obj->pos);
369
370                         if (vm_vec_dot(&vec_to_ship, &weapon_obj->orient.v.fvec) < 0.0f) {
371                                 // check if we're colliding against "invisible" ship
372                                 if (!(Ship_info[shipp->ship_info_index].flags & SIF_DONT_COLLIDE_INVIS)) {
373                                         wp->lifeleft = 0.001f;
374                                         if (ship_obj == Player_obj) {
375                                                 nprintf(("Jim", "Frame %i: Weapon %i set to detonate, dist = %7.3f.\n", Framecount, weapon_obj-Objects, dist));
376                                         }
377                                         valid_hit_occured = 1;
378                                 }
379                         }
380
381                 }
382         }
383
384         
385         return valid_hit_occured;
386 }
387
388
389 // Checks ship-weapon collisions.  pair->a is ship and pair->b is weapon.
390 // Returns 1 if all future collisions between these can be ignored
391 int collide_ship_weapon( obj_pair * pair )
392 {
393         int             did_hit;
394         object *ship = pair->a;
395         object *weapon = pair->b;
396         
397         SDL_assert( ship->type == OBJ_SHIP );
398         SDL_assert( weapon->type == OBJ_WEAPON );
399
400         // Don't check collisions for player if past first warpout stage.
401         if ( Player->control_mode > PCM_WARPOUT_STAGE1) {
402                 if ( ship == Player_obj )
403                         return 0;
404         }
405
406         // Cull lasers within big ship spheres by casting a vector forward for (1) exit sphere or (2) lifetime of laser
407         // If it does hit, don't check the pair until about 200 ms before collision.  
408         // If it does not hit and is within error tolerance, cull the pair.
409
410         if ( (Ship_info[Ships[ship->instance].ship_info_index].flags & (SIF_BIG_SHIP | SIF_HUGE_SHIP)) && (Weapon_info[Weapons[weapon->instance].weapon_info_index].subtype == WP_LASER) ) {
411 //      if (  (ship->radius > 50) && (Weapon_info[Weapons[weapon->instance].weapon_info_index].subtype == WP_LASER) ) {
412                 // Check when within ~1.1 radii.  
413                 // This allows good transition between sphere checking (leaving the laser about 200 ms from radius) and checking
414                 // within the sphere with little time between.  There may be some time for "small" big ships
415                 if ( vm_vec_dist_squared(&ship->pos, &weapon->pos) < (1.2f*ship->radius*ship->radius) ) {
416                         return check_inside_radius_for_big_ships( ship, weapon, pair );
417                 }
418         }
419
420
421 //      demo_do_rand_test();
422         did_hit = ship_weapon_check_collision( ship, weapon );
423 //      demo_do_rand_test();
424         if ( !did_hit ) {
425                 // Since we didn't hit, check to see if we can disable all future collisions
426                 // between these two.
427                 return weapon_will_never_hit( weapon, ship, pair );
428         }
429
430         return 0;
431 }
432
433 // ----------------------------------------------------------------------------
434 // upper limit estimate ship speed at end of time
435 float estimate_ship_speed_upper_limit( object *ship, float time ) 
436 {
437         float exponent;
438         float delta_v;
439         float factor;
440
441         delta_v = Ship_info[Ships[ship->instance].ship_info_index].max_vel.xyz.z - ship->phys_info.speed;
442         if (ship->phys_info.forward_accel_time_const == 0) {
443                 return ship->phys_info.speed;
444         }
445         exponent = time / ship->phys_info.forward_accel_time_const;
446         //SDL_assert( exponent >= 0);
447
448
449         factor = 1.0f - (float)exp( -exponent );
450         return ship->phys_info.speed + factor*delta_v;
451 }
452
453 // maximum error allowed in detecting collisions between laser and big ship inside the radius
454 // this is set for a 150 m radius ship.  For ships with larger radius, the error scales according
455 // to the ration of radii, but is never less than 2 m
456 #define ERROR_STD       2       
457
458 // ----------------------------------------------------------------------------                                                 
459 // check_inside_radius_for_big_ships()                                                  
460 // when inside radius of big ship, check if we can cull collision pair
461 // determine the time when pair should next be checked
462 // return 1 if pair can be culled
463 // return 0 if pair can not be culled
464 int check_inside_radius_for_big_ships( object *ship, object *weapon, obj_pair *pair )
465 {
466         vector error_vel;               // vel perpendicular to laser
467         float error_vel_mag;    // magnitude of error_vel
468         float time_to_max_error, time_to_exit_sphere;
469         float ship_speed_at_exit_sphere, error_at_exit_sphere;  
470         float max_error = (float) ERROR_STD / 150.0f * ship->radius;
471         if (max_error < 2)
472                 max_error = 2.0f;
473
474         time_to_exit_sphere = (ship->radius + vm_vec_dist(&ship->pos, &weapon->pos)) / (weapon->phys_info.max_vel.xyz.z - ship->phys_info.max_vel.xyz.z);
475         ship_speed_at_exit_sphere = estimate_ship_speed_upper_limit( ship, time_to_exit_sphere );
476         // update estimated time to exit sphere
477         time_to_exit_sphere = (ship->radius + vm_vec_dist(&ship->pos, &weapon->pos)) / (weapon->phys_info.max_vel.xyz.z - ship_speed_at_exit_sphere);
478         vm_vec_scale_add( &error_vel, &ship->phys_info.vel, &weapon->orient.v.fvec, -vm_vec_dotprod(&ship->phys_info.vel, &weapon->orient.v.fvec) );
479         error_vel_mag = vm_vec_mag_quick( &error_vel );
480         error_vel_mag += 0.5f * (ship->phys_info.max_vel.xyz.z - error_vel_mag)*(time_to_exit_sphere/ship->phys_info.forward_accel_time_const);
481         // error_vel_mag is now average velocity over period
482         error_at_exit_sphere = error_vel_mag * time_to_exit_sphere;
483         time_to_max_error = max_error / error_at_exit_sphere * time_to_exit_sphere;
484
485         // find the minimum time we can safely check into the future.
486         // limited by (1) time to exit sphere (2) time to weapon expires
487         // if ship_weapon_check_collision comes back with a hit_time > error limit, ok
488         // if ship_weapon_check_collision comes finds no collision, next check time based on error time
489         float limit_time;               // furthest time to check (either lifetime or exit sphere)
490         if ( time_to_exit_sphere < Weapons[weapon->instance].lifeleft ) {
491                 limit_time = time_to_exit_sphere;
492         } else {
493                 limit_time = Weapons[weapon->instance].lifeleft;
494         }
495
496         // Note:  when estimated hit time is less than 200 ms, look at every frame
497         int hit_time = 0;       // estimated time of hit in ms
498
499         // modify ship_weapon_check_collision to do damage if hit_time is negative (ie, hit occurs in this frame)
500         if ( ship_weapon_check_collision( ship, weapon, limit_time, &hit_time ) ) {
501                 // hit occured in while in sphere
502                 if (hit_time < 0) {
503                         // hit occured in the frame
504                         return 1;
505                 } else if (hit_time > 200) {
506                         pair->next_check_time = timestamp(hit_time - 200);
507                         return 0;
508                         // set next check time to time - 200
509                 } else {
510                         // set next check time to next frame
511                         pair->next_check_time = 1;
512                         return 0;
513                 }
514         } else {
515                 if (limit_time > time_to_max_error) {
516                 // no hit, but beyond error tolerance
517                         if (1000*time_to_max_error > 200) {
518                                 pair->next_check_time = timestamp( (int)(1000*time_to_max_error) - 200 );
519                         } else {
520                                 pair->next_check_time = 1;
521                         }
522                         return 0;
523                 } else {
524                         // no hit and within error tolerance
525                         return 1;
526                 }
527         }
528 }
529