2 * Copyright (C) Volition, Inc. 1999. All rights reserved.
4 * All source code herein is the property of Volition, Inc. You may not sell
5 * or otherwise commercially exploit the source or things you created based on
10 * $Logfile: /Freespace2/code/Object/CollideShipWeapon.cpp $
15 * Routines to detect collisions and do physics, damage, etc for weapons and ships
18 * Revision 1.4 2002/06/17 06:33:10 relnev
19 * ryan's struct patch for gcc 2.95
21 * Revision 1.3 2002/06/09 04:41:24 relnev
22 * added copyright header
24 * Revision 1.2 2002/05/07 03:16:48 theoddone33
25 * The Great Newline Fix
27 * Revision 1.1.1.1 2002/05/03 03:28:10 root
31 * 13 8/30/99 11:06a Andsager
32 * Fix bug preventing ship_apply_whack()
34 * 12 8/16/99 11:58p Andsager
35 * Disable collision on proximity for ships with SIF_DONT_COLLIDE_INVIS
38 * 11 7/22/99 7:18p Dave
39 * Fixed excessive multiplayer collision whacks.
41 * 10 6/30/99 5:53p Dave
42 * Put in new anti-camper code.
44 * 9 6/25/99 3:37p Jasons
45 * Whoops. Only send pain packet for WP_LASER subtypes.
47 * 8 6/21/99 7:24p Dave
48 * netplayer pain packet. Added type E unmoving beams.
50 * 7 6/01/99 8:35p Dave
51 * Finished lockarm weapons. Added proper supercap weapons/damage. Added
52 * awacs-set-radius sexpression.
54 * 6 4/23/99 12:01p Johnson
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.
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.
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.
71 * 2 10/07/98 10:53a Dave
74 * 1 10/07/98 10:50a Dave
76 * 21 4/13/98 2:14p Mike
77 * Countermeasure balance testing for Jim.
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.
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.
90 * 18 3/25/98 1:33p Andsager
93 * 17 3/16/98 5:17p Mike
94 * Fix arm time. Make missiles not explode in first second.
96 * 16 3/14/98 5:02p Lawrance
97 * Support arbitrary wings in the wingman status gauge
99 * 15 2/23/98 4:30p Mike
100 * Make homing missiles detonate after they pass up their target. Make
101 * countermeasures less effective.
103 * 14 2/10/98 5:02p Andsager
104 * Big ship:weapon collision use flag SIF_BIG_SHIP or SIF_CAPITAL
106 * 13 2/10/98 2:57p Jasen
107 * Andsager: predict speed when big ship has zero acceleration (ie,
110 * 12 2/09/98 1:19p Andsager
112 * 11 2/01/98 2:53p Mike
113 * Much better time returned by hud_ai_get_dock_time or whatever it's
116 * 10 1/24/98 3:21p Lawrance
117 * Add flashing when hit, and correct association with the wingman status
120 * 9 1/14/98 1:43p Lawrance
121 * Puncture weapons don't pass through shields any more.
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.
127 * 7 11/18/97 6:00p Lawrance
128 * modify how hud_shield_quadrant_hit() gets called
130 * 6 11/08/97 11:08p Lawrance
131 * implement new "mini-shield" view that sits near bottom of reticle
133 * 5 11/07/97 4:36p Mike
134 * Change how ships determine they're under attack by dumbfire weapons.
136 * 4 10/27/97 8:35a John
137 * code for new player warpout sequence
139 * 3 9/18/97 4:08p John
140 * Cleaned up & restructured ship damage stuff.
142 * 2 9/17/97 5:12p John
143 * Restructured collision routines. Probably broke a lot of stuff.
145 * 1 9/17/97 2:14p John
151 #include "objcollide.h"
155 #include "hudshield.h"
157 #include "hudwingmanstatus.h"
159 #include "freespace.h"
161 #include "multiutil.h"
162 #include "multimsgs.h"
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;
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)
177 SDL_assert(ship_obj->type == OBJ_SHIP);
179 aip = &Ai_info[Ships[ship_obj->instance].ai_index];
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;
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);
190 if (danger_new_time < danger_old_time) {
191 aip->danger_weapon_objnum = weapon_obj-Objects;
192 aip->danger_weapon_signature = weapon_obj->signature;
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)
201 weapon *wp = &Weapons[weapon_obj->instance];
202 weapon_info *wip = &Weapon_info[wp->weapon_info_index];
203 ship *shipp = &Ships[ship_obj->instance];
207 // Apply hit & damage & stuff to weapon
208 weapon_hit(weapon_obj, ship_obj, world_hitpos);
210 damage = wip->damage;
212 // deterine whack whack
213 float blast = wip->mass;
214 vm_vec_copy_scale(&force, &weapon_obj->phys_info.vel, blast );
216 // send player pain packet
217 if ( (MULTIPLAYER_MASTER) && !(shipp->flags & SF_DYING) ){
218 int np_index = multi_find_player_by_object(ship_obj);
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);
226 ship_apply_local_damage(ship_obj, weapon_obj, world_hitpos, damage, quadrant_num, CREATE_SPARKS, submodel_num);
228 // let the hud shield gauge know when Player or Player target is hit
229 hud_shield_quadrant_hit(ship_obj, quadrant_num);
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);
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) ) {
241 ship_apply_whack( &force, hitpos, ship_obj );
245 extern int Framecount;
247 int ship_weapon_check_collision(object * ship_obj, object * weapon_obj, float time_limit = 0.0f, int *next_hit=NULL)
252 weapon *wp = &Weapons[weapon_obj->instance];
253 weapon_info *wip = &Weapon_info[wp->weapon_info_index];
255 SDL_assert( ship_obj->type == OBJ_SHIP );
256 SDL_assert( weapon_obj->type == OBJ_WEAPON );
258 num = ship_obj->instance;
259 SDL_assert( num >= 0 );
260 SDL_assert( Ships[num].objnum == OBJ_INDEX(ship_obj));
264 // Make ships that are warping in not get collision detection done
265 if ( shipp->flags & SF_ARRIVING ) return 0;
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)) )
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))
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);
283 ship_model_start(ship_obj);
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;
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 );
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
300 polymodel *pm = model_get( shipp->modelnum );
302 // Check the shields for an impact if necessary
304 if (!(ship_obj->flags & OF_NO_SHIELDS) && New_shield_system && (pm->shield.ntris > 0)) {
306 if (!(ship_obj->flags & OF_NO_SHIELDS) && (pm->shield.ntris > 0)) {
309 mc.flags = MC_CHECK_SHIELD;
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) ) {
320 // AL 1-14-97: "Puncture" doesn't mean penetrate shield anymore, it means that it punctures
321 // hull do inflict maximum subsystem damage
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);
330 valid_hit_occured = 1;
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
336 quadrant_num = -1; // ignore shield hit
341 // Check the model for an impact if necessary
342 if ( do_model_check ) {
343 mc.flags = MC_CHECK_MODEL; // flags
345 if (model_collide(&mc)) {
346 valid_hit_occured = 1;
350 //nprintf(("AI", "Frame %i, Hit tri = %i\n", Framecount, mc.shield_hit_tri));
351 ship_model_stop(ship_obj);
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) {
356 *next_hit = (int) (1000.0f * (mc.hit_dist*(flFrametime + time_limit) - flFrametime) );
358 // if hit occurs outside of this frame, do not do damage
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) {
368 vm_vec_normalized_dir(&vec_to_ship, &ship_obj->pos, &weapon_obj->pos);
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 valid_hit_occured = 1;
384 return valid_hit_occured;
388 // Checks ship-weapon collisions. pair->a is ship and pair->b is weapon.
389 // Returns 1 if all future collisions between these can be ignored
390 int collide_ship_weapon( obj_pair * pair )
393 object *ship = pair->a;
394 object *weapon = pair->b;
396 SDL_assert( ship->type == OBJ_SHIP );
397 SDL_assert( weapon->type == OBJ_WEAPON );
399 // Don't check collisions for player if past first warpout stage.
400 if ( Player->control_mode > PCM_WARPOUT_STAGE1) {
401 if ( ship == Player_obj )
405 // Cull lasers within big ship spheres by casting a vector forward for (1) exit sphere or (2) lifetime of laser
406 // If it does hit, don't check the pair until about 200 ms before collision.
407 // If it does not hit and is within error tolerance, cull the pair.
409 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) ) {
410 // if ( (ship->radius > 50) && (Weapon_info[Weapons[weapon->instance].weapon_info_index].subtype == WP_LASER) ) {
411 // Check when within ~1.1 radii.
412 // This allows good transition between sphere checking (leaving the laser about 200 ms from radius) and checking
413 // within the sphere with little time between. There may be some time for "small" big ships
414 if ( vm_vec_dist_squared(&ship->pos, &weapon->pos) < (1.2f*ship->radius*ship->radius) ) {
415 return check_inside_radius_for_big_ships( ship, weapon, pair );
420 // demo_do_rand_test();
421 did_hit = ship_weapon_check_collision( ship, weapon );
422 // demo_do_rand_test();
424 // Since we didn't hit, check to see if we can disable all future collisions
425 // between these two.
426 return weapon_will_never_hit( weapon, ship, pair );
432 // ----------------------------------------------------------------------------
433 // upper limit estimate ship speed at end of time
434 float estimate_ship_speed_upper_limit( object *ship, float time )
440 delta_v = Ship_info[Ships[ship->instance].ship_info_index].max_vel.xyz.z - ship->phys_info.speed;
441 if (ship->phys_info.forward_accel_time_const == 0) {
442 return ship->phys_info.speed;
444 exponent = time / ship->phys_info.forward_accel_time_const;
445 //SDL_assert( exponent >= 0);
448 factor = 1.0f - (float)exp( -exponent );
449 return ship->phys_info.speed + factor*delta_v;
452 // maximum error allowed in detecting collisions between laser and big ship inside the radius
453 // this is set for a 150 m radius ship. For ships with larger radius, the error scales according
454 // to the ration of radii, but is never less than 2 m
457 // ----------------------------------------------------------------------------
458 // check_inside_radius_for_big_ships()
459 // when inside radius of big ship, check if we can cull collision pair
460 // determine the time when pair should next be checked
461 // return 1 if pair can be culled
462 // return 0 if pair can not be culled
463 int check_inside_radius_for_big_ships( object *ship, object *weapon, obj_pair *pair )
465 vector error_vel; // vel perpendicular to laser
466 float error_vel_mag; // magnitude of error_vel
467 float time_to_max_error, time_to_exit_sphere;
468 float ship_speed_at_exit_sphere, error_at_exit_sphere;
469 float max_error = (float) ERROR_STD / 150.0f * ship->radius;
473 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);
474 ship_speed_at_exit_sphere = estimate_ship_speed_upper_limit( ship, time_to_exit_sphere );
475 // update estimated time to exit sphere
476 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);
477 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) );
478 error_vel_mag = vm_vec_mag_quick( &error_vel );
479 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);
480 // error_vel_mag is now average velocity over period
481 error_at_exit_sphere = error_vel_mag * time_to_exit_sphere;
482 time_to_max_error = max_error / error_at_exit_sphere * time_to_exit_sphere;
484 // find the minimum time we can safely check into the future.
485 // limited by (1) time to exit sphere (2) time to weapon expires
486 // if ship_weapon_check_collision comes back with a hit_time > error limit, ok
487 // if ship_weapon_check_collision comes finds no collision, next check time based on error time
488 float limit_time; // furthest time to check (either lifetime or exit sphere)
489 if ( time_to_exit_sphere < Weapons[weapon->instance].lifeleft ) {
490 limit_time = time_to_exit_sphere;
492 limit_time = Weapons[weapon->instance].lifeleft;
495 // Note: when estimated hit time is less than 200 ms, look at every frame
496 int hit_time = 0; // estimated time of hit in ms
498 // modify ship_weapon_check_collision to do damage if hit_time is negative (ie, hit occurs in this frame)
499 if ( ship_weapon_check_collision( ship, weapon, limit_time, &hit_time ) ) {
500 // hit occured in while in sphere
502 // hit occured in the frame
504 } else if (hit_time > 200) {
505 pair->next_check_time = timestamp(hit_time - 200);
507 // set next check time to time - 200
509 // set next check time to next frame
510 pair->next_check_time = 1;
514 if (limit_time > time_to_max_error) {
515 // no hit, but beyond error tolerance
516 if (1000*time_to_max_error > 200) {
517 pair->next_check_time = timestamp( (int)(1000*time_to_max_error) - 200 );
519 pair->next_check_time = 1;
523 // no hit and within error tolerance