mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-09 04:49:39 -10:00
get_I_inv converts an object's inverse moment of inertia tensor from body to world coordinates for collision physics. It was written as though vm_matrix_x_matrix(dest, a, b) computes a * b, but that function computes b * a, so the result was (R)(I_inv_body)(Rt) rather than the (Rt)(I_inv_body)(R) stated in the comment. The collision response therefore used the tensor of an object facing the inverse of its actual orientation. This dates back to retail. The result is only correct for an isotropic tensor or an unrotated object. Every retail ship has an anisotropic tensor, with a median principal-axis ratio of about 2.5 and as high as 13 for capital ships, so the spin imparted by a collision was typically about the wrong axis. Since this changes collision behavior for every mod, the fix is controlled by the new game_settings.tbl option "$Fix collision moment of inertia:", which defaults to true for mods targeting 26.2 or later. The retail path is otherwise unchanged. PR #2575 fixed a related bug in physics_ship_init: retail scaled the inverse moment of inertia by $Density rather than by its inverse, so a denser ship was easier to rotate rather than harder. That fix was merged without a flag. For consistency it is now controlled by "$Fix density moment of inertia:", which defaults to true for mods targeting 21.0 or later. Co-Authored-By: Claude Fable 5.1 <[email protected]> Co-Authored-By: Claude Opus 5.5 <[email protected]>
1861 lines
76 KiB
C++
1861 lines
76 KiB
C++
/*
|
|
* Copyright (C) Volition, Inc. 1999. All rights reserved.
|
|
*
|
|
* All source code herein is the property of Volition, Inc. You may not sell
|
|
* or otherwise commercially exploit the source or things you created based on the
|
|
* source.
|
|
*
|
|
*/
|
|
|
|
|
|
|
|
#include "asteroid/asteroid.h"
|
|
#include "debris/debris.h"
|
|
#include "debugconsole/console.h"
|
|
#include "freespace.h"
|
|
#include "gamesequence/gamesequence.h"
|
|
#include "gamesnd/gamesnd.h"
|
|
#include "hud/hudmessage.h"
|
|
#include "hud/hudshield.h"
|
|
#include "io/joy_ff.h"
|
|
#include "io/timer.h"
|
|
#include "mod_table/mod_table.h"
|
|
#include "network/multi.h"
|
|
#include "network/multi_interpolate.h"
|
|
#include "object/objcollide.h"
|
|
#include "object/object.h"
|
|
#include "object/objectdock.h"
|
|
#include "object/objectshield.h"
|
|
#include "scripting/global_hooks.h"
|
|
#include "scripting/api/objs/model.h"
|
|
#include "playerman/player.h"
|
|
#include "prop/prop.h"
|
|
#include "render/3d.h" // needed for View_position, which is used when playing 3d sound
|
|
#include "ship/ship.h"
|
|
#include "ship/shipfx.h"
|
|
#include "ship/shiphit.h"
|
|
|
|
|
|
#define COLLIDE_DEBUG
|
|
#undef COLLIDE_DEBUG
|
|
|
|
// GENERAL COLLISIONS FUNCTIONS
|
|
// calculates the inverse moment of inertia matrix in world coordinates
|
|
static void get_I_inv (matrix* I_inv, matrix* I_inv_body, matrix* orient);
|
|
|
|
// calculate the physics of extended two body collisions
|
|
void calculate_ship_ship_collision_physics(collision_info_struct *ship_ship_hit_info);
|
|
|
|
#ifndef NDEBUG
|
|
static int Collide_friendly = 1;
|
|
DCF_BOOL( collide_friendly, Collide_friendly )
|
|
#endif
|
|
|
|
static sound_handle Player_collide_sound, AI_collide_sound;
|
|
static sound_handle Player_collide_shield_sound, AI_collide_shield_sound;
|
|
|
|
/**
|
|
* Return true if two ships are docking or if one of the two is indirectly docking to the other.
|
|
*/
|
|
static bool check_for_docking_collision(object *objp1, object *objp2)
|
|
{
|
|
ai_info *aip1, *aip2;
|
|
ship *shipp1, *shipp2;
|
|
|
|
shipp1 = &Ships[objp1->instance];
|
|
shipp2 = &Ships[objp2->instance];
|
|
|
|
aip1 = &Ai_info[shipp1->ai_index];
|
|
aip2 = &Ai_info[shipp2->ai_index];
|
|
|
|
if (dock_check_find_docked_object(objp1, objp2)) {
|
|
return true;
|
|
}
|
|
|
|
|
|
if (aip1->mode == AIM_DOCK && aip1->goal_objnum >= 0 && aip1->goal_objnum < MAX_OBJECTS) {
|
|
if (dock_check_find_docked_object(&Objects[aip1->goal_objnum], objp2))
|
|
return true;
|
|
}
|
|
|
|
if (aip2->mode == AIM_DOCK && aip2->goal_objnum >= 0 && aip2->goal_objnum < MAX_OBJECTS) {
|
|
if (dock_check_find_docked_object(&Objects[aip2->goal_objnum], objp1))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
/**
|
|
* If light_obj emerging from or departing to dock bay in heavy_obj, no collision detection.
|
|
*/
|
|
static int bay_emerge_or_depart(object *heavy_objp, object *light_objp)
|
|
{
|
|
if (light_objp->type != OBJ_SHIP)
|
|
return 0;
|
|
|
|
ai_info *aip = &Ai_info[Ships[light_objp->instance].ai_index];
|
|
|
|
// the player shouldn't be allowed to fly through the ship just cause the rest of their wing can
|
|
if ((Player_obj == light_objp) && !Player_use_ai){
|
|
return 0;
|
|
}
|
|
|
|
if ((aip->mode == AIM_BAY_EMERGE) || (aip->mode == AIM_BAY_DEPART)) {
|
|
if (aip->goal_objnum == OBJ_INDEX(heavy_objp))
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ship_ship_check_collision(collision_info_struct *ship_ship_hit_info)
|
|
{
|
|
object *heavy_obj = ship_ship_hit_info->heavy;
|
|
object *light_obj = ship_ship_hit_info->light;
|
|
__UNUSED int player_involved; // flag to indicate that A or B is the Player_obj
|
|
|
|
Assert( heavy_obj->type == OBJ_SHIP );
|
|
Assert( light_obj->type == OBJ_SHIP );
|
|
|
|
ship *heavy_shipp = &Ships[heavy_obj->instance];
|
|
ship *light_shipp = &Ships[light_obj->instance];
|
|
|
|
ship_info *heavy_sip = &Ship_info[heavy_shipp->ship_info_index];
|
|
ship_info *light_sip = &Ship_info[light_shipp->ship_info_index];
|
|
|
|
// AL 12-4-97: we use the player_involved flag to ensure collisions are always
|
|
// done with the player, regardless of team.
|
|
if ( heavy_obj == Player_obj || light_obj == Player_obj ) {
|
|
player_involved = 1;
|
|
} else {
|
|
player_involved = 0;
|
|
}
|
|
|
|
// Make ships that are warping in not get collision detection done
|
|
if ( heavy_shipp->is_arriving(ship::warpstage::STAGE1, false) ||
|
|
light_shipp->is_arriving(ship::warpstage::STAGE1, false)) {
|
|
return 0;
|
|
}
|
|
|
|
// Don't do collision detection for docking ships, since they will always collide while trying to dock
|
|
if (check_for_docking_collision(heavy_obj, light_obj) ) {
|
|
return 0;
|
|
}
|
|
|
|
// If light_obj emerging from or departing to dock bay in heavy_obj, no collision detection.
|
|
if (bay_emerge_or_depart(heavy_obj, light_obj)) {
|
|
return 0;
|
|
}
|
|
|
|
// Ships which are dying should not do collision detection.
|
|
// Also, this is the only clean way I could figure to get ships to not do damage to each other for one frame
|
|
// when they are docked and departing. Due to sequencing, they would not show up as docked, yet they
|
|
// would still come through here, so they would harm each other, if on opposing teams. -- MK, 2/2/98
|
|
if ((heavy_obj->flags[Object::Object_Flags::Should_be_dead]) || (light_obj->flags[Object::Object_Flags::Should_be_dead])) {
|
|
return 0;
|
|
}
|
|
|
|
#ifndef NDEBUG
|
|
// Don't do collision detection on a pair of ships on the same team.
|
|
// Change this someday, but for now, it's a problem.
|
|
if ( !Collide_friendly ) { // Collide_friendly is a global value changed via debug console
|
|
if ( (!player_involved) && (heavy_shipp->team == light_shipp->team) ) {
|
|
return 0;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// Apparently we're doing same team collisions.
|
|
// But, if both are offscreen, ignore the collision
|
|
if (heavy_shipp->team == light_shipp->team) {
|
|
if ( (!(heavy_obj->flags[Object::Object_Flags::Was_rendered]) && !(light_obj->flags[Object::Object_Flags::Was_rendered])) ) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// Set up model_collide info
|
|
mc_info mc;
|
|
|
|
// Do in heavy object RF
|
|
mc.model_num = heavy_sip->model_num; // Fill in the model to check
|
|
mc.model_instance_num = heavy_shipp->model_instance_num;
|
|
mc.orient = &heavy_obj->orient; // The object's orient
|
|
|
|
vec3d zero, p0, p1;
|
|
vm_vec_zero(&zero); // we need the physical vector and can not set its value to zero
|
|
vm_vec_sub(&p0, &light_obj->last_pos, &heavy_obj->last_pos);
|
|
vm_vec_sub(&p1, &light_obj->pos, &heavy_obj->pos);
|
|
|
|
// find the light object's position in the heavy object's reference frame at last frame and also in this frame.
|
|
vec3d p0_temp, p0_rotated;
|
|
|
|
// Collision detection from rotation enabled if at max rotational velocity and 5fps, rotation is less than PI/2
|
|
// This should account for all ships
|
|
if ( (vm_vec_mag_squared( &heavy_obj->phys_info.max_rotvel ) * .04) < (PI*PI/4) ) {
|
|
// collide_rotate calculate (1) start position and (2) relative velocity
|
|
ship_ship_hit_info->collide_rotate = true;
|
|
vm_vec_rotate(&p0_temp, &p0, &heavy_obj->last_orient);
|
|
vm_vec_unrotate(&p0_rotated, &p0_temp, &heavy_obj->orient);
|
|
mc.p0 = &p0_rotated; // Point 1 of ray to check
|
|
vm_vec_sub(&ship_ship_hit_info->light_rel_vel, &p1, &p0_rotated);
|
|
vm_vec_scale(&ship_ship_hit_info->light_rel_vel, 1/flFrametime);
|
|
}
|
|
// should be no ships that can rotate this fast
|
|
else {
|
|
#ifndef NDEBUG
|
|
static bool Warned_about_fast_rotational_collisions = false;
|
|
if (!Warned_about_fast_rotational_collisions) {
|
|
Warning(LOCATION, "Ship '%s' rotates too quickly! Rotational collision detection has been skipped.", heavy_sip->name);
|
|
Warned_about_fast_rotational_collisions = true;
|
|
}
|
|
#endif
|
|
ship_ship_hit_info->collide_rotate = false;
|
|
mc.p0 = &p0; // Point 1 of ray to check
|
|
vm_vec_sub(&ship_ship_hit_info->light_rel_vel, &light_obj->phys_info.vel, &heavy_obj->phys_info.vel);
|
|
}
|
|
|
|
// Set up collision info
|
|
mc.pos = &zero; // The object's position
|
|
mc.p1 = &p1; // Point 2 of ray to check
|
|
mc.radius = model_get_core_radius(light_sip->model_num);
|
|
mc.flags = (MC_CHECK_MODEL | MC_CHECK_SPHERELINE); // flags
|
|
|
|
// Only check invisible face polygons for ship:ship of different teams.
|
|
if ( !(heavy_shipp->flags[Ship::Ship_Flags::Dont_collide_invis]) ) {
|
|
if ((heavy_obj->flags[Object::Object_Flags::Player_ship]) || (light_obj->flags[Object::Object_Flags::Player_ship]) || (heavy_shipp->team != light_shipp->team) ) {
|
|
mc.flags |= MC_CHECK_INVISIBLE_FACES;
|
|
}
|
|
}
|
|
|
|
// copy important data
|
|
int orig_flags = mc.flags; // make a copy of start end positions of sphere in big ship RF
|
|
vec3d orig_p0 = *mc.p0;
|
|
vec3d orig_p1 = *mc.p1;
|
|
|
|
// first test against the sphere - if this fails then don't do any submodel tests
|
|
mc.flags = MC_ONLY_SPHERE | MC_CHECK_SPHERELINE;
|
|
|
|
int valid_hit_occured = 0;
|
|
polymodel *pm_light;
|
|
|
|
pm_light = model_get(Ship_info[light_shipp->ship_info_index].model_num);
|
|
|
|
if(pm_light->submodel[pm_light->detail[0]].flags[Model::Submodel_flags::No_collisions]) {
|
|
return 0;
|
|
}
|
|
|
|
if (model_collide(&mc)) {
|
|
|
|
// Set earliest hit time
|
|
ship_ship_hit_info->hit_time = FLT_MAX;
|
|
|
|
auto pmi = model_get_instance(heavy_shipp->model_instance_num);
|
|
auto pm = model_get(pmi->model_num);
|
|
|
|
// Do collision the cool new way
|
|
if ( ship_ship_hit_info->collide_rotate ) {
|
|
// We collide with the sphere, find the list of moving submodels and test one at a time
|
|
SCP_vector<int> submodel_vector;
|
|
model_get_moving_submodel_list(submodel_vector, heavy_obj);
|
|
|
|
// turn off all moving submodels, collide against only 1 at a time.
|
|
mc.collision_checked.assign(pm->n_models, 0);
|
|
for (auto submodel : submodel_vector) {
|
|
mc.collision_checked[submodel] = true;
|
|
}
|
|
|
|
// Only check single submodel now, since children of moving submodels are handled as moving as well
|
|
mc.flags = orig_flags | MC_SUBMODEL;
|
|
|
|
if (heavy_sip->collision_lod > -1) {
|
|
mc.lod = heavy_sip->collision_lod;
|
|
}
|
|
|
|
// check each submodel in turn
|
|
for (auto submodel : submodel_vector) {
|
|
// turn on just one submodel for collision test
|
|
mc.collision_checked[submodel] = false;
|
|
|
|
if (pmi->submodel[submodel].blown_off)
|
|
{
|
|
mc.collision_checked[submodel] = true;
|
|
continue;
|
|
}
|
|
|
|
// find the start and end positions of the sphere in submodel RF
|
|
model_instance_global_to_local_point(&p0, &light_obj->last_pos, pm, pmi, submodel, &heavy_obj->last_orient, &heavy_obj->last_pos, true);
|
|
model_instance_global_to_local_point(&p1, &light_obj->pos, pm, pmi, submodel, &heavy_obj->orient, &heavy_obj->pos);
|
|
|
|
mc.p0 = &p0;
|
|
mc.p1 = &p1;
|
|
|
|
mc.orient = &vmd_identity_matrix;
|
|
mc.submodel_num = submodel;
|
|
|
|
if ( model_collide(&mc) ) {
|
|
if (mc.hit_dist < ship_ship_hit_info->hit_time ) {
|
|
valid_hit_occured = 1;
|
|
|
|
// set up ship_ship_hit_info common
|
|
set_hit_struct_info(ship_ship_hit_info, &mc, true);
|
|
model_instance_local_to_global_point(&ship_ship_hit_info->hit_pos, &mc.hit_point, pm, pmi, mc.hit_submodel, &heavy_obj->orient, &zero);
|
|
|
|
// set up ship_ship_hit_info for rotating submodel
|
|
if (!ship_ship_hit_info->edge_hit) {
|
|
model_instance_local_to_global_dir(&ship_ship_hit_info->collision_normal, &mc.hit_normal, pm, pmi, mc.hit_submodel, &heavy_obj->orient);
|
|
}
|
|
|
|
// find position in submodel RF of light object at collison
|
|
vec3d int_light_pos, diff;
|
|
vm_vec_sub(&diff, mc.p1, mc.p0);
|
|
vm_vec_scale_add(&int_light_pos, mc.p0, &diff, mc.hit_dist);
|
|
model_instance_local_to_global_point(&ship_ship_hit_info->light_collision_cm_pos, &int_light_pos, pm, pmi, mc.hit_submodel, &heavy_obj->orient, &zero);
|
|
}
|
|
}
|
|
|
|
// re-disable this submodel before enabling the next one
|
|
mc.collision_checked[submodel] = true;
|
|
}
|
|
|
|
// Clear collision_checked before subsequent model_collide calls so it auto-inits fresh
|
|
mc.collision_checked.clear();
|
|
}
|
|
|
|
// Now complete base model collision checks that do not take into account rotating submodels.
|
|
mc.flags = orig_flags;
|
|
mc.p0 = &orig_p0;
|
|
mc.p1 = &orig_p1;
|
|
mc.orient = &heavy_obj->orient;
|
|
|
|
// usual ship_ship collision test
|
|
if ( model_collide(&mc) ) {
|
|
// check if this is the earliest hit
|
|
if (mc.hit_dist < ship_ship_hit_info->hit_time) {
|
|
valid_hit_occured = 1;
|
|
|
|
set_hit_struct_info(ship_ship_hit_info, &mc, false);
|
|
|
|
// get collision normal if not edge hit
|
|
if (!ship_ship_hit_info->edge_hit) {
|
|
model_instance_local_to_global_dir(&ship_ship_hit_info->collision_normal, &mc.hit_normal, pm, pmi, mc.hit_submodel, &heavy_obj->orient);
|
|
}
|
|
|
|
// find position in submodel RF of light object at collison
|
|
vec3d diff;
|
|
vm_vec_sub(&diff, mc.p1, mc.p0);
|
|
vm_vec_scale_add(&ship_ship_hit_info->light_collision_cm_pos, mc.p0, &diff, mc.hit_dist);
|
|
}
|
|
}
|
|
}
|
|
|
|
// check if the hit point is beyond the clip plane if one of the ships is warping in or out.
|
|
if (valid_hit_occured) {
|
|
WarpEffect* warp_effect = nullptr;
|
|
|
|
// this is extremely confusing but mc.hit_point_world isn't actually in world coords
|
|
// everything above was calculated relative to the heavy's position
|
|
vec3d actual_world_hit_pos = mc.hit_point_world + heavy_obj->pos;
|
|
|
|
if (heavy_shipp->flags[Ship::Ship_Flags::Depart_warp] && heavy_shipp->warpout_effect != nullptr)
|
|
warp_effect = heavy_shipp->warpout_effect.get();
|
|
else if (heavy_shipp->flags[Ship::Ship_Flags::Arriving_stage_2] && heavy_shipp->warpin_effect != nullptr)
|
|
warp_effect = heavy_shipp->warpin_effect.get();
|
|
|
|
bool heavy_warp_no_collide = false;
|
|
if (warp_effect != nullptr)
|
|
heavy_warp_no_collide = point_is_clipped_by_warp(&actual_world_hit_pos, warp_effect);
|
|
|
|
warp_effect = nullptr;
|
|
if (light_shipp->flags[Ship::Ship_Flags::Depart_warp] && light_shipp->warpout_effect != nullptr)
|
|
warp_effect = light_shipp->warpout_effect.get();
|
|
else if (light_shipp->flags[Ship::Ship_Flags::Arriving_stage_2] && light_shipp->warpin_effect != nullptr)
|
|
warp_effect = light_shipp->warpin_effect.get();
|
|
|
|
bool light_warp_no_collide = false;
|
|
if (warp_effect != nullptr)
|
|
light_warp_no_collide = point_is_clipped_by_warp(&actual_world_hit_pos, warp_effect);
|
|
|
|
|
|
if (heavy_warp_no_collide || light_warp_no_collide)
|
|
valid_hit_occured = 0;
|
|
}
|
|
|
|
if (valid_hit_occured) {
|
|
|
|
// Collision debug stuff
|
|
#ifndef NDEBUG
|
|
object *collide_obj = NULL;
|
|
if (heavy_obj == Player_obj) {
|
|
collide_obj = light_obj;
|
|
} else if (light_obj == Player_obj) {
|
|
collide_obj = heavy_obj;
|
|
}
|
|
if ((collide_obj != NULL) && (Ship_info[Ships[collide_obj->instance].ship_info_index].is_fighter_bomber())) {
|
|
const char *submode_string = "";
|
|
const char *mode_string = "";
|
|
ai_info *aip;
|
|
|
|
extern const char *Mode_text[];
|
|
aip = &Ai_info[Ships[collide_obj->instance].ai_index];
|
|
|
|
if (aip->mode >= 0)
|
|
mode_string = Mode_text[aip->mode];
|
|
|
|
if (aip->mode == AIM_CHASE)
|
|
submode_string = Submode_text[aip->submode];
|
|
|
|
nprintf(("AI", "Player collided with ship %s, AI mode = %s, submode = %s\n", Ships[collide_obj->instance].ship_name, mode_string, submode_string));
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
return valid_hit_occured;
|
|
}
|
|
|
|
/**
|
|
* Gets modified mass of cruiser in cruiser/asteroid collision so cruisers don't get bumped so hard.
|
|
* modified mass is 10x, 4x, or 2x larger than asteroid mass
|
|
* @return 1 if modified mass is larger than given mass, 0 otherwise
|
|
*/
|
|
static int check_special_cruiser_asteroid_collision(object *heavy, object *lighter, float *cruiser_mass, int *cruiser_light)
|
|
{
|
|
int asteroid_type;
|
|
|
|
if (heavy->type == OBJ_ASTEROID) {
|
|
if (lighter->type == OBJ_SHIP && Ship_info[Ships[lighter->instance].ship_info_index].is_big_or_huge()) {
|
|
|
|
asteroid_type = Asteroids[heavy->instance].asteroid_type;
|
|
if (asteroid_type == 0) {
|
|
*cruiser_mass = 10.0f * heavy->phys_info.mass;
|
|
} else if (asteroid_type == 1) {
|
|
*cruiser_mass = 4.0f * heavy->phys_info.mass;
|
|
} else {
|
|
*cruiser_mass = 2.0f * heavy->phys_info.mass;
|
|
}
|
|
|
|
if (*cruiser_mass > lighter->phys_info.mass) {
|
|
*cruiser_light = 1;
|
|
return 1;
|
|
}
|
|
}
|
|
} else if (lighter->type == OBJ_ASTEROID) {
|
|
if (heavy->type == OBJ_SHIP && Ship_info[Ships[heavy->instance].ship_info_index].is_big_or_huge()) {
|
|
|
|
asteroid_type = Asteroids[lighter->instance].asteroid_type;
|
|
if (asteroid_type == 0) {
|
|
*cruiser_mass = 10.0f * lighter->phys_info.mass;
|
|
} else if (asteroid_type == 1) {
|
|
*cruiser_mass = 4.0f * lighter->phys_info.mass;
|
|
} else {
|
|
*cruiser_mass = 2.0f * lighter->phys_info.mass;
|
|
}
|
|
|
|
if (*cruiser_mass > heavy->phys_info.mass) {
|
|
*cruiser_light = 0;
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Find the subobject corresponding to the submodel hit
|
|
*/
|
|
static bool check_subsystem_landing_allowed(ship_info *heavy_sip, collision_info_struct *ship_ship_hit_info) {
|
|
if (!(heavy_sip->flags[Ship::Info_Flags::Allow_landings]))
|
|
return false;
|
|
|
|
for (int i = 0; i < heavy_sip->n_subsystems; i++) {
|
|
if (heavy_sip->subsystems[i].flags[Model::Subsystem_Flags::Allow_landing] &&
|
|
heavy_sip->subsystems[i].subobj_num == ship_ship_hit_info->heavy_submodel_num)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Helper function that calculates the next time this ship should be allowed to collide with a client player on multiplayer.
|
|
*/
|
|
static int calculate_next_multiplayer_client_collision_time(float impulse_magnitude)
|
|
{
|
|
// Uses a continuous cubic rational function that allows us to space out collisions based on collision strength.
|
|
// based on some quick tests from Asteroth about how strong impuse is on average.
|
|
constexpr float IMPULSE_RANGE_FACTOR = 0.000000000001031f; // The inverse of the range of regular impulse values, 9900, to the third power
|
|
constexpr float MIN_IMPULSE = 100.0f; // The lowest values of collisions that we see. Ignore if lower than this.
|
|
constexpr float LONGEST_COLLISION_INTERVAL = 175.0f; // 1/5th of a second is usually enough time to get an update from the server.
|
|
constexpr float MINIMUM_INTERVAL = 25.0f; // if there is sufficient impulse, don't go below this value.
|
|
|
|
impulse_magnitude -= MIN_IMPULSE;
|
|
|
|
// if barely anything is happening, then do not limit collisions
|
|
if (impulse_magnitude < 0.0f) {
|
|
return 0;
|
|
}
|
|
|
|
// calculate the percentage of the max we are going to use
|
|
float factor = std::clamp(powf(impulse_magnitude, 3.0f) * IMPULSE_RANGE_FACTOR, 0.0f, 1.0f);
|
|
|
|
// then multiply times the longest interval we could want.
|
|
factor *= LONGEST_COLLISION_INTERVAL;
|
|
factor += MINIMUM_INTERVAL;
|
|
|
|
return static_cast<int>(factor);
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
// input: ship_ship_hit => structure containing ship_ship hit info
|
|
// (includes) A, B => objects colliding
|
|
// r_A, r_B => position to collision from center of mass
|
|
// collision_normal => collision_normal (outward from B)
|
|
//
|
|
// output: velocity, angular velocity, impulse
|
|
//
|
|
// ------------------------------------------------------------------------------------------------
|
|
//
|
|
// calculates correct physics response to collision between two objects given
|
|
// masses, moments of inertia, velocities, angular velocities,
|
|
// relative collision positions, and the impulse direction
|
|
//
|
|
void calculate_ship_ship_collision_physics(collision_info_struct *ship_ship_hit_info)
|
|
{
|
|
// important parameters passed thru ship_ship_or_debris_hit
|
|
// calculate the whack applied to each ship from collision
|
|
|
|
// make local copies of hit_struct parameters
|
|
object *heavy = ship_ship_hit_info->heavy;
|
|
object *lighter = ship_ship_hit_info->light;
|
|
|
|
// gurgh... this includes asteroids and debris too
|
|
Assert(heavy->type == OBJ_SHIP || heavy->type == OBJ_ASTEROID || heavy->type == OBJ_DEBRIS || heavy->type == OBJ_PROP);
|
|
Assert(lighter->type == OBJ_SHIP || lighter->type == OBJ_ASTEROID || lighter->type == OBJ_DEBRIS || lighter->type == OBJ_PROP);
|
|
|
|
ship_info *light_sip = (lighter->type == OBJ_SHIP) ? &Ship_info[Ships[lighter->instance].ship_info_index] : NULL;
|
|
ship_info *heavy_sip = (heavy->type == OBJ_SHIP) ? &Ship_info[Ships[heavy->instance].ship_info_index] : NULL;
|
|
|
|
// make cruiser/asteroid collision softer on cruisers.
|
|
int special_cruiser_asteroid_collision;
|
|
int cruiser_light = 0;
|
|
float cruiser_mass = 0.0f, copy_mass = 0.0f;
|
|
special_cruiser_asteroid_collision = check_special_cruiser_asteroid_collision(heavy, lighter, &cruiser_mass, &cruiser_light);
|
|
|
|
if (special_cruiser_asteroid_collision) {
|
|
if (cruiser_light) {
|
|
Assert(lighter->phys_info.mass < cruiser_mass);
|
|
copy_mass = lighter->phys_info.mass;
|
|
lighter->phys_info.mass = cruiser_mass;
|
|
} else {
|
|
Assert(heavy->phys_info.mass < cruiser_mass);
|
|
copy_mass = heavy->phys_info.mass;
|
|
heavy->phys_info.mass = cruiser_mass;
|
|
}
|
|
}
|
|
|
|
float coeff_restitution; // parameter controls amount of bounce
|
|
float v_rel_normal_m; // relative collision velocity in the direction of the collision normal
|
|
vec3d v_rel_parallel_m; // normalized v_rel (Va-Vb) projected onto collision surface
|
|
vec3d world_rotvel_heavy_m, world_rotvel_light_m, vel_from_rotvel_heavy_m, vel_from_rotvel_light_m, v_rel_m, vel_heavy_m, vel_light_m;
|
|
|
|
coeff_restitution = 0.1f; // relative velocity wrt normal is zero after the collision ( range 0-1 )
|
|
|
|
// find velocity of each obj at collision point
|
|
|
|
// heavy object is in cm reference frame so we don't get a v_heavy term.
|
|
if ( ship_ship_hit_info->collide_rotate ) {
|
|
// if we have collisions from rotation, the effective velocity from rotation of the large body is alreay taken account
|
|
vm_vec_zero( &vel_heavy_m );
|
|
} else {
|
|
// take account the effective velocity from rotation
|
|
vm_vec_unrotate(&world_rotvel_heavy_m, &heavy->phys_info.rotvel, &heavy->orient); // heavy's world rotvel before collision
|
|
vm_vec_cross(&vel_from_rotvel_heavy_m, &world_rotvel_heavy_m, &ship_ship_hit_info->r_heavy); // heavy's velocity from rotvel before collision
|
|
vel_heavy_m = vel_from_rotvel_heavy_m;
|
|
}
|
|
|
|
// if collision from rotating submodel of heavy obj, add in vel from rotvel of submodel
|
|
vec3d local_vel_from_submodel;
|
|
|
|
if (ship_ship_hit_info->submodel_move_hit) {
|
|
polymodel *pm;
|
|
polymodel_instance *pmi = NULL;
|
|
int model_instance_num = -1;
|
|
|
|
if (heavy->type == OBJ_SHIP) {
|
|
pm = model_get(heavy_sip->model_num);
|
|
model_instance_num = Ships[heavy->instance].model_instance_num;
|
|
pmi = model_get_instance(model_instance_num);
|
|
} else if (heavy->type == OBJ_ASTEROID) {
|
|
pm = model_get(Asteroid_info[Asteroids[heavy->instance].asteroid_type].subtypes[Asteroids[heavy->instance].asteroid_subtype].model_number);
|
|
model_instance_num = Asteroids[heavy->instance].model_instance_num;
|
|
pmi = model_get_instance(model_instance_num);
|
|
} else if (heavy->type == OBJ_DEBRIS) {
|
|
pm = model_get(Debris[heavy->instance].model_num);
|
|
} else if (heavy->type == OBJ_PROP) {
|
|
prop* propp = prop_id_lookup(heavy->instance);
|
|
pm = model_get(Prop_info[propp->prop_info_index].model_num);
|
|
model_instance_num = propp->model_instance_num;
|
|
pmi = model_get_instance(model_instance_num);
|
|
} else {
|
|
// we should have caught this already
|
|
Int3();
|
|
pm = NULL;
|
|
}
|
|
|
|
//Previously, a side effect of moving submodel collision excluded turrets from imparting momentum to colliders
|
|
if ( pmi != nullptr && pm->submodel[ship_ship_hit_info->heavy_submodel_num].rotation_type != MOVEMENT_TYPE_TURRET) {
|
|
//Find the global movement of the position that hit the ship
|
|
vec3d last_frame_col_pos, col_pos;
|
|
model_instance_local_to_global_point(&last_frame_col_pos, &ship_ship_hit_info->hit_pos, pm, pmi, ship_ship_hit_info->heavy_submodel_num, &heavy->orient, &heavy->pos, true);
|
|
model_instance_local_to_global_point(&col_pos, &ship_ship_hit_info->hit_pos, pm, pmi, ship_ship_hit_info->heavy_submodel_num, &heavy->orient, &heavy->pos);
|
|
|
|
//Calculate the movement speed from that
|
|
vm_vec_sub(&local_vel_from_submodel, &col_pos, &last_frame_col_pos);
|
|
|
|
//By artificially inflating moving submodel collision speed by 20%, the chance to accidentally tunnel through fast (~100m/s) moving submodels is drastically reduced with only very little noticeable in-game change
|
|
vm_vec_scale2(&local_vel_from_submodel, 1.2f, flFrametime);
|
|
} else {
|
|
vm_vec_zero(&local_vel_from_submodel);
|
|
}
|
|
} else {
|
|
// didn't collide with submodel
|
|
vm_vec_zero(&local_vel_from_submodel);
|
|
}
|
|
|
|
vm_vec_unrotate(&world_rotvel_light_m, &lighter->phys_info.rotvel, &lighter->orient); // light's world rotvel before collision
|
|
vm_vec_cross(&vel_from_rotvel_light_m, &world_rotvel_light_m, &ship_ship_hit_info->r_light); // light's velocity from rotvel before collision
|
|
vm_vec_add(&vel_light_m, &vel_from_rotvel_light_m, &ship_ship_hit_info->light_rel_vel);
|
|
vm_vec_sub(&v_rel_m, &vel_light_m, &vel_heavy_m);
|
|
|
|
// Add in effect of rotating submodel
|
|
vm_vec_sub2(&v_rel_m, &local_vel_from_submodel);
|
|
|
|
v_rel_normal_m = vm_vec_dot(&v_rel_m, &ship_ship_hit_info->collision_normal);// if less than zero, colliding contact taking place
|
|
// (v_slow - v_fast) dot (n_fast)
|
|
|
|
if (v_rel_normal_m > 0) {
|
|
// This can happen in 2 situations.
|
|
// (1) The rotational velocity is large enough to cause ships to miss. In this case, there would most likely
|
|
// have been a collision, but at a later time, so reset v_rel_normal_m
|
|
|
|
// (2) We could also have just gotten a slightly incorrect hitpos, where r dot v_rel is nearly zero.
|
|
// In this case, we know there was a collision, but slight collision and the normal is correct, so reset v_rel_normal_m
|
|
// need a normal direction. We can just take the -v_light normalized. v_rel_normal_m = -v_rel_normal_m;
|
|
|
|
nprintf(("Physics", "Frame %i reset v_rel_normal_m %f Edge %i\n", Framecount, v_rel_normal_m, ship_ship_hit_info->edge_hit));
|
|
v_rel_normal_m = -v_rel_normal_m;
|
|
}
|
|
|
|
//Maybe treat the current collision as a landing
|
|
//Init values just to be safe
|
|
vec3d light_local_vel(ship_ship_hit_info->light_rel_vel);
|
|
float light_uvec_dot_norm = 0.0f;
|
|
float light_fvec_dot_norm = 0.0f;
|
|
float light_rvec_dot_norm = 0.0f;
|
|
bool subsys_landing_allowed = lighter->type == OBJ_SHIP && heavy->type == OBJ_SHIP && check_subsystem_landing_allowed(heavy_sip, ship_ship_hit_info);
|
|
if (subsys_landing_allowed) {
|
|
vm_vec_rotate(&light_local_vel, &ship_ship_hit_info->light_rel_vel, &lighter->orient);
|
|
light_uvec_dot_norm = vm_vec_dot(&ship_ship_hit_info->collision_normal, &lighter->orient.vec.uvec);
|
|
light_fvec_dot_norm = vm_vec_dot(&ship_ship_hit_info->collision_normal, &lighter->orient.vec.fvec);
|
|
light_rvec_dot_norm = vm_vec_dot(&ship_ship_hit_info->collision_normal, &lighter->orient.vec.rvec);
|
|
}
|
|
if (subsys_landing_allowed &&
|
|
light_local_vel.xyz.z < light_sip->collision_physics.landing_max_z &&
|
|
light_local_vel.xyz.z > light_sip->collision_physics.landing_min_z &&
|
|
light_local_vel.xyz.y > light_sip->collision_physics.landing_min_y &&
|
|
fl_abs(light_local_vel.xyz.x) < light_sip->collision_physics.landing_max_x &&
|
|
light_uvec_dot_norm > 0 &&
|
|
light_fvec_dot_norm < light_sip->collision_physics.landing_max_angle &&
|
|
light_fvec_dot_norm > light_sip->collision_physics.landing_min_angle &&
|
|
fl_abs(light_rvec_dot_norm) < light_sip->collision_physics.landing_max_rot_angle)
|
|
{
|
|
ship_ship_hit_info->is_landing = true;
|
|
}
|
|
|
|
vec3d rotational_impulse_heavy, rotational_impulse_light, delta_rotvel_heavy, delta_rotvel_light;
|
|
vec3d delta_vel_from_delta_rotvel_heavy, delta_vel_from_delta_rotvel_light, impulse;
|
|
float impulse_mag, heavy_denom, light_denom;
|
|
matrix heavy_I_inv, light_I_inv;
|
|
|
|
// include a frictional collision impulse F parallel to the collision plane
|
|
// F = I * sin (collision_normal, normalized v_rel_m) [sin is ratio of v_rel_parallel_m to v_rel_m]
|
|
// note: (-) sign is needed to account for the direction of the v_rel_parallel_m
|
|
if (IS_VEC_NULL(&v_rel_m)) {
|
|
// If the relative velocity is zero then the compuatation below would cause NaN errors
|
|
vm_vec_zero(&impulse);
|
|
} else {
|
|
float collision_speed_parallel;
|
|
float parallel_mag;
|
|
impulse = ship_ship_hit_info->collision_normal;
|
|
vm_vec_projection_onto_plane(&v_rel_parallel_m, &v_rel_m, &ship_ship_hit_info->collision_normal);
|
|
collision_speed_parallel = vm_vec_normalize_safe(&v_rel_parallel_m);
|
|
float friction = (lighter->type == OBJ_SHIP) ? light_sip->collision_physics.friction : COLLISION_FRICTION_FACTOR;
|
|
parallel_mag = float(-friction) * collision_speed_parallel / vm_vec_mag(&v_rel_m);
|
|
vm_vec_scale_add2(&impulse, &v_rel_parallel_m, parallel_mag);
|
|
}
|
|
|
|
// calculate the effect on the velocity of the collison point per unit impulse
|
|
// first find the effect thru change in rotvel
|
|
// then find the change in the cm vel
|
|
vm_vec_cross(&rotational_impulse_heavy, &ship_ship_hit_info->r_heavy, &impulse);
|
|
get_I_inv(&heavy_I_inv, &heavy->phys_info.I_body_inv, &heavy->orient);
|
|
vm_vec_rotate(&delta_rotvel_heavy, &rotational_impulse_heavy, &heavy_I_inv);
|
|
float rotation_factor_heavy = (heavy->type == OBJ_SHIP) ? heavy_sip->collision_physics.rotation_factor : COLLISION_ROTATION_FACTOR;
|
|
if (heavy == Player_obj) {
|
|
rotation_factor_heavy *= The_mission.ai_profile->rot_fac_multiplier_ply_collisions;
|
|
}
|
|
vm_vec_scale(&delta_rotvel_heavy, rotation_factor_heavy); // hack decrease rotation (delta_rotvel)
|
|
vm_vec_cross(&delta_vel_from_delta_rotvel_heavy, &delta_rotvel_heavy , &ship_ship_hit_info->r_heavy);
|
|
heavy_denom = vm_vec_dot(&delta_vel_from_delta_rotvel_heavy, &ship_ship_hit_info->collision_normal);
|
|
if (heavy_denom < 0) {
|
|
// sanity check
|
|
heavy_denom = 0.0f;
|
|
}
|
|
if (heavy->type != OBJ_PROP) {
|
|
heavy_denom += 1.0f / heavy->phys_info.mass;
|
|
}
|
|
|
|
// calculate the effect on the velocity of the collison point per unit impulse
|
|
// first find the effect thru change in rotvel
|
|
// then find the change in the cm vel
|
|
vm_vec_cross(&rotational_impulse_light, &ship_ship_hit_info->r_light, &impulse);
|
|
get_I_inv(&light_I_inv, &lighter->phys_info.I_body_inv, &lighter->orient);
|
|
vm_vec_rotate(&delta_rotvel_light, &rotational_impulse_light, &light_I_inv);
|
|
float rotation_factor_light = (lighter->type == OBJ_SHIP) ? light_sip->collision_physics.rotation_factor : COLLISION_ROTATION_FACTOR;
|
|
if (subsys_landing_allowed) {
|
|
// SUSHI: If on a landing surface, use the same shortcut the player gets
|
|
// This is a bit of a hack, but gets around some nasty unpredictable collision behavior
|
|
// when trying to do AI landings for certain ships
|
|
rotation_factor_light *= 0.0f;
|
|
} else if (lighter == Player_obj) {
|
|
rotation_factor_light *= The_mission.ai_profile->rot_fac_multiplier_ply_collisions;
|
|
}
|
|
vm_vec_scale(&delta_rotvel_light, rotation_factor_light); // hack decrease rotation (delta_rotvel)
|
|
vm_vec_cross(&delta_vel_from_delta_rotvel_light, &delta_rotvel_light, &ship_ship_hit_info->r_light);
|
|
light_denom = vm_vec_dot(&delta_vel_from_delta_rotvel_light, &ship_ship_hit_info->collision_normal);
|
|
if (light_denom < 0) {
|
|
// sanity check
|
|
light_denom = 0.0f;
|
|
}
|
|
if (lighter->type != OBJ_PROP) {
|
|
light_denom += 1.0f / lighter->phys_info.mass;
|
|
}
|
|
|
|
// calculate the necessary impulse to achieved the desired relative velocity after the collision
|
|
// update damage info in mc
|
|
impulse_mag = -(1.0f + coeff_restitution)*v_rel_normal_m / (heavy_denom + light_denom);
|
|
ship_ship_hit_info->impulse = impulse_mag;
|
|
if (impulse_mag < 0) {
|
|
nprintf(("Physics", "negative impulse mag -- Get Dave A if not Descent Physics\n"));
|
|
impulse_mag = -impulse_mag;
|
|
}
|
|
|
|
// On multi clients should this collision occur? (limiting the frequency of applying the impulse prevents bugs)
|
|
bool should_collide = (!MULTIPLAYER_CLIENT
|
|
|| (lighter == Player_obj && heavy->type == OBJ_SHIP && timestamp_elapsed(Ships[heavy->instance].multi_client_collision_timestamp))
|
|
|| (heavy == Player_obj && lighter->type == OBJ_SHIP && timestamp_elapsed(Ships[lighter->instance].multi_client_collision_timestamp)));
|
|
|
|
// update the physics info structs for heavy and light objects
|
|
// since we have already calculated delta rotvel for heavy and light in world coords
|
|
// physics should not have to recalculate this, just change into body coords (done in collide_whack)
|
|
// Cyborg - to complicate this, multiplayer clients should never ever whack non-player ships.
|
|
if (should_collide){
|
|
auto light_rot_mag = light_sip ? light_sip->collision_physics.rotation_mag_max : -1.0f;
|
|
vm_vec_scale(&impulse, impulse_mag);
|
|
vm_vec_scale(&delta_rotvel_light, impulse_mag);
|
|
physics_collide_whack(&impulse, &delta_rotvel_light, &lighter->phys_info, &lighter->orient, ship_ship_hit_info->is_landing, light_rot_mag);
|
|
|
|
auto heavy_rot_mag = heavy_sip ? heavy_sip->collision_physics.rotation_mag_max : -1.0f;
|
|
vm_vec_negate(&impulse);
|
|
vm_vec_scale(&delta_rotvel_heavy, -impulse_mag);
|
|
physics_collide_whack(&impulse, &delta_rotvel_heavy, &heavy->phys_info, &heavy->orient, true, heavy_rot_mag);
|
|
}
|
|
|
|
// If within certain bounds, we want to add some more rotation towards the "resting orientation" of the ship
|
|
// These bounds are defined separately from normal "landing" bounds so that they can be more generous:
|
|
// we can have crash landings that still re-orient the ship.
|
|
if (subsys_landing_allowed &&
|
|
light_local_vel.xyz.z < light_sip->collision_physics.reorient_max_z &&
|
|
light_local_vel.xyz.z > light_sip->collision_physics.reorient_min_z &&
|
|
light_local_vel.xyz.y > light_sip->collision_physics.reorient_min_y &&
|
|
fl_abs(light_local_vel.xyz.x) < light_sip->collision_physics.reorient_max_x &&
|
|
light_uvec_dot_norm > 0 &&
|
|
light_fvec_dot_norm < light_sip->collision_physics.reorient_max_angle &&
|
|
light_fvec_dot_norm > light_sip->collision_physics.reorient_min_angle &&
|
|
fl_abs(light_rvec_dot_norm) < light_sip->collision_physics.reorient_max_rot_angle)
|
|
{
|
|
vec3d landing_delta_rotvel;
|
|
landing_delta_rotvel.xyz.x = (light_fvec_dot_norm * light_sip->collision_physics.reorient_mult)
|
|
- light_sip->collision_physics.landing_rest_angle;
|
|
// For yaw, use the dot product between vel vector (normalized) and orientation on the xz plane
|
|
// This reduces to the following math
|
|
// We also clamp to reduce huge nose swings at low speeds
|
|
float xzVelMag = sqrt(light_local_vel.xyz.x * light_local_vel.xyz.x + light_local_vel.xyz.z * light_local_vel.xyz.z);
|
|
float xzVelDotOrient = MIN(MAX((xzVelMag > 0 ? (light_local_vel.xyz.x / xzVelMag) : 0), -0.5f), 0.5f);
|
|
landing_delta_rotvel.xyz.y = (xzVelMag > 2) ? (xzVelDotOrient * light_sip->collision_physics.reorient_mult) : 0;
|
|
landing_delta_rotvel.xyz.z = light_rvec_dot_norm * light_sip->collision_physics.reorient_mult * -1;
|
|
vm_vec_add2( &lighter->phys_info.rotvel, &landing_delta_rotvel );
|
|
}
|
|
|
|
// Find final positions
|
|
// We will try not to worry about the left over time in the frame
|
|
// heavy's position unchanged by collision
|
|
// light's position is heavy's position plus relative position from heavy
|
|
if (should_collide && lighter->flags.none_of(Object::Object_Flags::Dont_change_position, Object::Object_Flags::Immobile)) {
|
|
vm_vec_add(&lighter->pos, &heavy->pos, &ship_ship_hit_info->light_collision_cm_pos);
|
|
}
|
|
|
|
// Try to move each body back to its position just before collision occured to prevent interpenetration
|
|
// Move away in direction of light and away in direction of normal
|
|
vec3d direction_light; // direction light is moving relative to heavy
|
|
vm_vec_sub(&direction_light, &ship_ship_hit_info->light_rel_vel, &local_vel_from_submodel);
|
|
vm_vec_normalize_safe(&direction_light);
|
|
|
|
float heavy_light_mass_ratio = heavy->phys_info.mass / (heavy->phys_info.mass + lighter->phys_info.mass);
|
|
if (heavy->type == OBJ_PROP) {
|
|
heavy_light_mass_ratio = 1.0f;
|
|
} else if (lighter->type == OBJ_PROP) {
|
|
heavy_light_mass_ratio = 0.0f;
|
|
}
|
|
|
|
if (should_collide){
|
|
if (heavy->flags.none_of(Object::Object_Flags::Dont_change_position, Object::Object_Flags::Immobile)) {
|
|
Assert(!vm_is_vec_nan(&direction_light));
|
|
vm_vec_scale_add2(&heavy->pos, &direction_light, 0.2f * (1.0f - heavy_light_mass_ratio));
|
|
vm_vec_scale_add2(&heavy->pos, &ship_ship_hit_info->collision_normal, -0.1f * (1.0f - heavy_light_mass_ratio));
|
|
}
|
|
|
|
// while we are in a block that has already checked if we should collide, set the MP client timestamps
|
|
if (MULTIPLAYER_CLIENT){
|
|
if (lighter == Player_obj && heavy->type == OBJ_SHIP){
|
|
Ships[heavy->instance].multi_client_collision_timestamp = _timestamp( calculate_next_multiplayer_client_collision_time(impulse_mag) );
|
|
} else if (lighter->type == OBJ_SHIP) {
|
|
Ships[lighter->instance].multi_client_collision_timestamp = _timestamp( calculate_next_multiplayer_client_collision_time(impulse_mag) );
|
|
}
|
|
}
|
|
}
|
|
|
|
//For landings, we want minimal movement on the light ship (just enough to keep the collision detection honest)
|
|
if (ship_ship_hit_info->is_landing) {
|
|
vm_vec_scale_add2(&lighter->pos, &ship_ship_hit_info->collision_normal, LANDING_POS_OFFSET);
|
|
} else if (lighter->flags.none_of(Object::Object_Flags::Dont_change_position, Object::Object_Flags::Immobile)) {
|
|
vm_vec_scale_add2(&lighter->pos, &direction_light, -0.2f * heavy_light_mass_ratio);
|
|
vm_vec_scale_add2(&lighter->pos, &ship_ship_hit_info->collision_normal, 0.1f * heavy_light_mass_ratio);
|
|
}
|
|
|
|
// restore mass in case of special cruiser / asteroid collision
|
|
if (special_cruiser_asteroid_collision) {
|
|
if (cruiser_light) {
|
|
lighter->phys_info.mass = copy_mass;
|
|
} else {
|
|
heavy->phys_info.mass = copy_mass;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
// get_I_inv()
|
|
//
|
|
// input: I_inv_body => inverse moment of inertia matrix in body coordinates
|
|
// orient => orientation matrix
|
|
//
|
|
// output: I_inv => inverse moment of inertia matrix in world coordinates
|
|
// ------------------------------------------------------------------------------------------------
|
|
//
|
|
// calculates the inverse moment of inertia matrix from the body matrix and oreint matrix
|
|
//
|
|
static void get_I_inv (matrix* I_inv, matrix* I_inv_body, matrix* orient)
|
|
{
|
|
matrix orient_t;
|
|
// I_inv = (Rt)(I_inv_body)(R)
|
|
// This is opposite to what is commonly seen in books since we are rotating coordianates axes
|
|
// which is equivalent to rotating in the opposite direction (or transpose)
|
|
|
|
vm_copy_transpose(&orient_t, orient);
|
|
|
|
if (Fix_collision_moment_of_inertia)
|
|
*I_inv = orient_t * (*I_inv_body) * (*orient);
|
|
else
|
|
*I_inv = (*orient) * (*I_inv_body) * orient_t; // retail multiplied in the opposite order
|
|
}
|
|
|
|
#define PLANET_DAMAGE_SCALE 4.0f
|
|
#define PLANET_DAMAGE_RANGE 3 // If within this factor of radius, apply damage.
|
|
|
|
fix Last_planet_damage_time = 0;
|
|
extern void hud_start_text_flash(char *txt, int t, int interval);
|
|
|
|
/**
|
|
* Procss player_ship:planet damage.
|
|
* If within range of planet, apply damage to ship.
|
|
*/
|
|
static void mcp_1(obj_pair * pair, const std::any& data)
|
|
{
|
|
float planet_radius;
|
|
float dist;
|
|
|
|
bool ship_is_first = std::any_cast<bool>(data);
|
|
object* planet_objp = ship_is_first ? pair->b : pair->a;
|
|
object* player_objp = ship_is_first ? pair->a : pair->b;
|
|
|
|
planet_radius = planet_objp->radius;
|
|
dist = vm_vec_dist_quick(&player_objp->pos, &planet_objp->pos);
|
|
|
|
if (dist > planet_radius*PLANET_DAMAGE_RANGE)
|
|
return;
|
|
|
|
ship_apply_global_damage( player_objp, planet_objp, NULL, PLANET_DAMAGE_SCALE * flFrametime * (float)pow((planet_radius*PLANET_DAMAGE_RANGE)/dist, 3.0f), -1 );
|
|
|
|
if ((Missiontime - Last_planet_damage_time > F1_0) || (Missiontime < Last_planet_damage_time)) {
|
|
HUD_sourced_printf(HUD_SOURCE_HIDDEN, "%s", XSTR( "Too close to planet. Taking damage!", 465));
|
|
Last_planet_damage_time = Missiontime;
|
|
snd_play_3d( gamesnd_get_game_sound(ship_get_sound(player_objp, GameSounds::ABURN_ENGAGE)), &player_objp->pos, &View_position );
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Return true if *objp is a planet, else return false.
|
|
* Hack: Just checking first six letters of name.
|
|
*/
|
|
static int is_planet(object *objp)
|
|
{
|
|
return (strnicmp(Ships[objp->instance].ship_name, NOX("planet"), 6) == 0);
|
|
}
|
|
|
|
|
|
/**
|
|
* If exactly one of these is a planet and the other is a player ship, do something special.
|
|
* @return true if this was a ship:planet (or planet_ship) collision (planet involved / bool is ship first) and we processed it. Else return false.
|
|
*/
|
|
static std::pair<bool, bool> maybe_collide_planet (object *obj1, object *obj2)
|
|
{
|
|
ship_info *sip1, *sip2;
|
|
|
|
sip1 = &Ship_info[Ships[obj1->instance].ship_info_index];
|
|
sip2 = &Ship_info[Ships[obj2->instance].ship_info_index];
|
|
|
|
if (sip1->flags[Ship::Info_Flags::Player_ship]) {
|
|
if (is_planet(obj2)) {
|
|
return {true, true};
|
|
}
|
|
} else if (sip2->flags[Ship::Info_Flags::Player_ship]) {
|
|
if (is_planet(obj1)) {
|
|
return {true, false};
|
|
}
|
|
}
|
|
|
|
return {false, false};
|
|
}
|
|
|
|
/**
|
|
* Given a global point and an object, get the quadrant number the point belongs to.
|
|
*/
|
|
int get_ship_quadrant_from_global(vec3d *global_pos, object *objp)
|
|
{
|
|
vec3d tpos;
|
|
vec3d rotpos;
|
|
|
|
vm_vec_sub(&tpos, global_pos, &objp->pos);
|
|
vm_vec_rotate(&rotpos, &tpos, &objp->orient);
|
|
return get_quadrant(&rotpos, objp);
|
|
}
|
|
|
|
#define MIN_REL_SPEED_FOR_LOUD_COLLISION 50 // relative speed of two colliding objects at which we play the "loud" collide sound
|
|
|
|
void collide_ship_ship_sounds_init()
|
|
{
|
|
Player_collide_sound = sound_handle::invalid();
|
|
AI_collide_sound = sound_handle::invalid();
|
|
Player_collide_shield_sound = sound_handle::invalid();
|
|
AI_collide_shield_sound = sound_handle::invalid();
|
|
}
|
|
|
|
gamesnd_id choose_collision_sound(gamesnd_id default_snd, object *A, object *B)
|
|
{
|
|
gamesnd_id a_snd, b_snd;
|
|
|
|
if (default_snd == gamesnd_id(GameSounds::SHIP_SHIP_HEAVY))
|
|
{
|
|
if (A->type == OBJ_SHIP)
|
|
a_snd = Ship_info[Ships[A->instance].ship_info_index].collision_physics.collision_sound_heavy_idx;
|
|
else if (A->type == OBJ_DEBRIS)
|
|
a_snd = Ship_info[Debris[A->instance].ship_info_index].debris_collision_sound_heavy;
|
|
|
|
if (B->type == OBJ_SHIP)
|
|
b_snd = Ship_info[Ships[B->instance].ship_info_index].collision_physics.collision_sound_heavy_idx;
|
|
else if (B->type == OBJ_DEBRIS)
|
|
b_snd = Ship_info[Debris[B->instance].ship_info_index].debris_collision_sound_heavy;
|
|
}
|
|
else if (default_snd == gamesnd_id(GameSounds::SHIP_SHIP_LIGHT))
|
|
{
|
|
if (A->type == OBJ_SHIP)
|
|
a_snd = Ship_info[Ships[A->instance].ship_info_index].collision_physics.collision_sound_light_idx;
|
|
else if (A->type == OBJ_DEBRIS)
|
|
a_snd = Ship_info[Debris[A->instance].ship_info_index].debris_collision_sound_light;
|
|
|
|
if (B->type == OBJ_SHIP)
|
|
b_snd = Ship_info[Ships[B->instance].ship_info_index].collision_physics.collision_sound_light_idx;
|
|
else if (B->type == OBJ_DEBRIS)
|
|
b_snd = Ship_info[Debris[B->instance].ship_info_index].debris_collision_sound_light;
|
|
}
|
|
else if (default_snd == gamesnd_id(GameSounds::SHIP_SHIP_SHIELD))
|
|
{
|
|
if (A->type == OBJ_SHIP)
|
|
a_snd = Ship_info[Ships[A->instance].ship_info_index].collision_physics.collision_sound_shielded_idx;
|
|
if (B->type == OBJ_SHIP)
|
|
b_snd = Ship_info[Ships[B->instance].ship_info_index].collision_physics.collision_sound_shielded_idx;
|
|
}
|
|
|
|
// if both A *and* B have a sound, arbitrarily choose A's sound
|
|
if (a_snd.isValid())
|
|
return a_snd;
|
|
else if (b_snd.isValid())
|
|
return b_snd;
|
|
else
|
|
return default_snd;
|
|
}
|
|
|
|
/**
|
|
* Determine what sound to play when two ships collide
|
|
*/
|
|
void collide_ship_ship_do_sound(vec3d *world_hit_pos, object *A, object *B, int player_involved)
|
|
{
|
|
vec3d rel_vel;
|
|
float rel_speed;
|
|
|
|
vm_vec_sub(&rel_vel, &A->phys_info.desired_vel, &B->phys_info.desired_vel);
|
|
rel_speed = vm_vec_mag_quick(&rel_vel);
|
|
|
|
if ( rel_speed > MIN_REL_SPEED_FOR_LOUD_COLLISION ) {
|
|
auto snd_id = choose_collision_sound(GameSounds::SHIP_SHIP_HEAVY, A, B);
|
|
snd_play_3d( gamesnd_get_game_sound(snd_id), world_hit_pos, &View_position );
|
|
} else {
|
|
auto snd_id = choose_collision_sound(GameSounds::SHIP_SHIP_LIGHT, A, B);
|
|
if ( player_involved ) {
|
|
if ( !snd_is_playing(Player_collide_sound) ) {
|
|
Player_collide_sound = snd_play_3d( gamesnd_get_game_sound(snd_id), world_hit_pos, &View_position );
|
|
}
|
|
} else {
|
|
if ( !snd_is_playing(AI_collide_sound) ) {
|
|
AI_collide_sound = snd_play_3d( gamesnd_get_game_sound(snd_id), world_hit_pos, &View_position );
|
|
}
|
|
}
|
|
}
|
|
|
|
// maybe play a "shield" collision sound overlay if appropriate
|
|
if ( (shield_get_strength(A) > 5) || (shield_get_strength(B) > 5) ) {
|
|
auto snd_id = choose_collision_sound(GameSounds::SHIP_SHIP_SHIELD, A, B);
|
|
if ( player_involved ) {
|
|
if ( !snd_is_playing(Player_collide_sound) ) {
|
|
Player_collide_shield_sound = snd_play_3d( gamesnd_get_game_sound(snd_id), world_hit_pos, &View_position );
|
|
}
|
|
} else {
|
|
if ( !snd_is_playing(Player_collide_sound) ) {
|
|
AI_collide_shield_sound = snd_play_3d( gamesnd_get_game_sound(snd_id), world_hit_pos, &View_position );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* obj1 and obj2 collided.
|
|
* If different teams, kamikaze bit set and other ship is large, auto-explode!
|
|
*/
|
|
static void do_kamikaze_crash(object *obj1, object *obj2)
|
|
{
|
|
ai_info *aip1, *aip2;
|
|
ship *ship1, *ship2;
|
|
|
|
ship1 = &Ships[obj1->instance];
|
|
ship2 = &Ships[obj2->instance];
|
|
|
|
aip1 = &Ai_info[ship1->ai_index];
|
|
aip2 = &Ai_info[ship2->ai_index];
|
|
|
|
if (ship1->team != ship2->team) {
|
|
if (aip1->ai_flags[AI::AI_Flags::Kamikaze]) {
|
|
if (Ship_info[ship2->ship_info_index].is_big_or_huge()) {
|
|
obj1->hull_strength = KAMIKAZE_HULL_ON_DEATH;
|
|
shield_set_strength(obj1, 0.0f);
|
|
}
|
|
}
|
|
if (aip2->ai_flags[AI::AI_Flags::Kamikaze]) {
|
|
if (Ship_info[ship1->ship_info_index].is_big_or_huge()) {
|
|
obj2->hull_strength = KAMIKAZE_HULL_ON_DEATH;
|
|
shield_set_strength(obj2, 0.0f);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Response when hit by fast moving cap ship
|
|
*/
|
|
static void maybe_push_little_ship_from_fast_big_ship(object *big_obj, object *small_obj, float impulse, vec3d *normal)
|
|
{
|
|
// Move player out of the way of a BIG|HUGE ship warping in or out
|
|
int big_class = Ship_info[Ships[big_obj->instance].ship_info_index].class_type;
|
|
int small_class = Ship_info[Ships[small_obj->instance].ship_info_index].class_type;
|
|
if (big_class > -1 && Ship_types[big_class].flags[Ship::Type_Info_Flags::Warp_pushes]) {
|
|
if (small_class > -1 && Ship_types[small_class].flags[Ship::Type_Info_Flags::Warp_pushable]) {
|
|
float big_speed = vm_vec_mag_quick(&big_obj->phys_info.vel);
|
|
if (big_speed > 3*big_obj->phys_info.max_vel.xyz.z) {
|
|
// push player away in direction perp to forward of big ship
|
|
// get perp vec
|
|
vec3d temp, perp;
|
|
vm_vec_sub(&temp, &small_obj->pos, &big_obj->pos);
|
|
vm_vec_scale_add(&perp, &temp, &big_obj->orient.vec.fvec, -vm_vec_dot(&temp, &big_obj->orient.vec.fvec));
|
|
vm_vec_normalize_quick(&perp);
|
|
|
|
// don't drive into sfc we just collided with
|
|
if (vm_vec_dot(&perp, normal) < 0) {
|
|
vm_vec_negate(&perp);
|
|
}
|
|
|
|
// get magnitude of added perp vel
|
|
float added_perp_vel_mag = impulse / small_obj->phys_info.mass;
|
|
|
|
// add to vel and ramp vel
|
|
vm_vec_scale_add2(&small_obj->phys_info.vel, &perp, added_perp_vel_mag);
|
|
vm_vec_rotate(&small_obj->phys_info.prev_ramp_vel, &small_obj->phys_info.vel, &small_obj->orient);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void collide_ship_ship_process(obj_pair * pair, const std::any& collision_data) {
|
|
auto ship_ship_hit_info = std::any_cast<collision_info_struct>(collision_data);
|
|
|
|
object *A = pair->a;
|
|
object *B = pair->b;
|
|
|
|
bool a_override = false, b_override = false;
|
|
|
|
// get world hitpos - do it here in case the override hooks need it
|
|
vec3d world_hit_pos;
|
|
vm_vec_add(&world_hit_pos, &ship_ship_hit_info.heavy->pos, &ship_ship_hit_info.hit_pos);
|
|
|
|
// get submodel handle if scripting needs it
|
|
bool has_submodel = (ship_ship_hit_info.heavy_submodel_num >= 0);
|
|
scripting::api::submodel_h smh(ship_ship_hit_info.heavy_model_num, ship_ship_hit_info.heavy_submodel_num);
|
|
|
|
if (scripting::hooks::OnShipCollision->isActive()) {
|
|
a_override = scripting::hooks::OnShipCollision->isOverride(scripting::hooks::CollisionConditions{ {A, B} },
|
|
scripting::hook_param_list(scripting::hook_param("Self", 'o', A),
|
|
scripting::hook_param("Object", 'o', B),
|
|
scripting::hook_param("Ship", 'o', A),
|
|
scripting::hook_param("ShipB", 'o', B),
|
|
scripting::hook_param("Hitpos", 'o', world_hit_pos),
|
|
scripting::hook_param("ShipSubmodel", 'o', scripting::api::l_Submodel.Set(smh), has_submodel && (ship_ship_hit_info.heavy == A)),
|
|
scripting::hook_param("ShipBSubmodel", 'o', scripting::api::l_Submodel.Set(smh), has_submodel && (ship_ship_hit_info.heavy == B))));
|
|
|
|
// Yes, this should be reversed.
|
|
b_override = scripting::hooks::OnShipCollision->isOverride(scripting::hooks::CollisionConditions{ {A, B} },
|
|
scripting::hook_param_list(scripting::hook_param("Self", 'o', B),
|
|
scripting::hook_param("Object", 'o', A),
|
|
scripting::hook_param("Ship", 'o', B),
|
|
scripting::hook_param("ShipB", 'o', A),
|
|
scripting::hook_param("Hitpos", 'o', world_hit_pos),
|
|
scripting::hook_param("ShipSubmodel", 'o', scripting::api::l_Submodel.Set(smh), has_submodel && (ship_ship_hit_info.heavy == B)),
|
|
scripting::hook_param("ShipBSubmodel", 'o', scripting::api::l_Submodel.Set(smh), has_submodel && (ship_ship_hit_info.heavy == A))));
|
|
}
|
|
|
|
object* heavy_obj = ship_ship_hit_info.heavy;
|
|
object* light_obj = ship_ship_hit_info.light;
|
|
|
|
if(!a_override && !b_override)
|
|
{
|
|
//
|
|
// Start of a codeblock that was originally taken from ship_ship_check_collision
|
|
// Moved here to properly handle ship-ship collision overrides and not process their physics when overridden by lua
|
|
//
|
|
|
|
ship *light_shipp = &Ships[ship_ship_hit_info.light->instance];
|
|
ship *heavy_shipp = &Ships[ship_ship_hit_info.heavy->instance];
|
|
|
|
const ship_info* light_sip = &Ship_info[light_shipp->ship_info_index];
|
|
const ship_info* heavy_sip = &Ship_info[heavy_shipp->ship_info_index];
|
|
|
|
// Update ai to deal with collisions
|
|
if (OBJ_INDEX(heavy_obj) == Ai_info[light_shipp->ai_index].target_objnum) {
|
|
Ai_info[light_shipp->ai_index].ai_flags.set(AI::AI_Flags::Target_collision);
|
|
}
|
|
if (OBJ_INDEX(light_obj) == Ai_info[heavy_shipp->ai_index].target_objnum) {
|
|
Ai_info[heavy_shipp->ai_index].ai_flags.set(AI::AI_Flags::Target_collision);
|
|
}
|
|
|
|
// SET PHYSICS PARAMETERS
|
|
// already have (hitpos - heavy) and light_cm_pos
|
|
|
|
// get r_heavy and r_light
|
|
ship_ship_hit_info.r_heavy = ship_ship_hit_info.hit_pos;
|
|
vm_vec_sub(&ship_ship_hit_info.r_light, &ship_ship_hit_info.hit_pos, &ship_ship_hit_info.light_collision_cm_pos);
|
|
|
|
// set normal for edge hit
|
|
if (ship_ship_hit_info.edge_hit) {
|
|
vm_vec_copy_normalize(&ship_ship_hit_info.collision_normal, &ship_ship_hit_info.r_light);
|
|
vm_vec_negate(&ship_ship_hit_info.collision_normal);
|
|
}
|
|
|
|
// do physics
|
|
calculate_ship_ship_collision_physics(&ship_ship_hit_info);
|
|
|
|
// Provide some separation for the case of same team
|
|
if (heavy_shipp->team == light_shipp->team) {
|
|
// If a couple of small ships, just move them apart.
|
|
|
|
if ((heavy_sip->is_small_ship()) && (light_sip->is_small_ship())) {
|
|
if ((heavy_obj->flags[Object::Object_Flags::Player_ship]) || (light_obj->flags[Object::Object_Flags::Player_ship])) {
|
|
vec3d h_to_l_vec;
|
|
vec3d rel_vel_h;
|
|
vec3d perp_rel_vel;
|
|
|
|
vm_vec_sub(&h_to_l_vec, &heavy_obj->pos, &light_obj->pos);
|
|
vm_vec_sub(&rel_vel_h, &heavy_obj->phys_info.vel, &light_obj->phys_info.vel);
|
|
float mass_sum = light_obj->phys_info.mass + heavy_obj->phys_info.mass;
|
|
|
|
// get comp of rel_vel perp to h_to_l_vec;
|
|
float mag = vm_vec_dot(&h_to_l_vec, &rel_vel_h) / vm_vec_mag_squared(&h_to_l_vec);
|
|
vm_vec_scale_add(&perp_rel_vel, &rel_vel_h, &h_to_l_vec, -mag);
|
|
vm_vec_normalize(&perp_rel_vel);
|
|
|
|
vm_vec_scale_add2(&heavy_obj->phys_info.vel, &perp_rel_vel,
|
|
heavy_sip->collision_physics.both_small_bounce * light_obj->phys_info.mass / mass_sum);
|
|
vm_vec_scale_add2(&light_obj->phys_info.vel, &perp_rel_vel,
|
|
-(light_sip->collision_physics.both_small_bounce) * heavy_obj->phys_info.mass / mass_sum);
|
|
|
|
vm_vec_rotate(&heavy_obj->phys_info.prev_ramp_vel, &heavy_obj->phys_info.vel, &heavy_obj->orient);
|
|
vm_vec_rotate(&light_obj->phys_info.prev_ramp_vel, &light_obj->phys_info.vel, &light_obj->orient);
|
|
}
|
|
}
|
|
else {
|
|
// add extra velocity to separate the two objects, backing up the direction we came in.
|
|
// TODO: add effect of velocity from rotating submodel
|
|
float rel_vel = vm_vec_mag_quick(&ship_ship_hit_info.light_rel_vel);
|
|
if (rel_vel < 1) {
|
|
rel_vel = 1.0f;
|
|
}
|
|
float mass_sum = heavy_obj->phys_info.mass + light_obj->phys_info.mass;
|
|
vm_vec_scale_add2(&heavy_obj->phys_info.vel, &ship_ship_hit_info.light_rel_vel,
|
|
heavy_sip->collision_physics.bounce * light_obj->phys_info.mass / (mass_sum * rel_vel));
|
|
vm_vec_rotate(&heavy_obj->phys_info.prev_ramp_vel, &heavy_obj->phys_info.vel, &heavy_obj->orient);
|
|
vm_vec_scale_add2(&light_obj->phys_info.vel, &ship_ship_hit_info.light_rel_vel,
|
|
-(light_sip->collision_physics.bounce) * heavy_obj->phys_info.mass / (mass_sum * rel_vel));
|
|
vm_vec_rotate(&light_obj->phys_info.prev_ramp_vel, &light_obj->phys_info.vel, &light_obj->orient);
|
|
}
|
|
}
|
|
|
|
//
|
|
// End of the codeblock that was originally taken from ship_ship_check_collision
|
|
//
|
|
|
|
float damage;
|
|
|
|
if ( ship_ship_hit_info.player_involved && (Player->control_mode == PCM_WARPOUT_STAGE1) ) {
|
|
gameseq_post_event( GS_EVENT_PLAYER_WARPOUT_STOP );
|
|
HUD_printf("%s", XSTR( "Warpout sequence aborted.", 466));
|
|
}
|
|
|
|
damage = 0.005f * ship_ship_hit_info.impulse; // Cut collision-based damage in half.
|
|
// Decrease heavy damage by 2x.
|
|
if (damage > 5.0f){
|
|
damage = 5.0f + (damage - 5.0f)/2.0f;
|
|
}
|
|
|
|
do_kamikaze_crash(A, B);
|
|
|
|
if (ship_ship_hit_info.impulse > 0) {
|
|
//Only flash the "Collision" text if not landing
|
|
if ( ship_ship_hit_info.player_involved && !ship_ship_hit_info.is_landing) {
|
|
hud_start_text_flash(XSTR("Collision", 1431), 2000);
|
|
}
|
|
}
|
|
|
|
//If this is a landing, play a different sound
|
|
if (ship_ship_hit_info.is_landing) {
|
|
if (vm_vec_mag(&ship_ship_hit_info.light_rel_vel) > MIN_LANDING_SOUND_VEL) {
|
|
if ( ship_ship_hit_info.player_involved ) {
|
|
if ( !snd_is_playing(Player_collide_sound) ) {
|
|
Player_collide_sound = snd_play_3d( gamesnd_get_game_sound(light_sip->collision_physics.landing_sound_idx), &world_hit_pos, &View_position );
|
|
}
|
|
} else {
|
|
if ( !snd_is_playing(AI_collide_sound) ) {
|
|
AI_collide_sound = snd_play_3d( gamesnd_get_game_sound(light_sip->collision_physics.landing_sound_idx), &world_hit_pos, &View_position );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
collide_ship_ship_do_sound(&world_hit_pos, A, B, ship_ship_hit_info.player_involved);
|
|
}
|
|
|
|
// check if we should do force feedback stuff
|
|
if (ship_ship_hit_info.player_involved && (ship_ship_hit_info.impulse > 0)) {
|
|
float scaler;
|
|
vec3d v;
|
|
|
|
scaler = -ship_ship_hit_info.impulse / Player_obj->phys_info.mass * 300;
|
|
vm_vec_copy_normalize(&v, &world_hit_pos);
|
|
joy_ff_play_vector_effect(&v, scaler);
|
|
}
|
|
|
|
#ifndef NDEBUG
|
|
if ( !Collide_friendly ) {
|
|
if ( Ships[A->instance].team == Ships[B->instance].team ) {
|
|
vec3d collision_vec, right_angle_vec;
|
|
vm_vec_normalized_dir(&collision_vec, &ship_ship_hit_info.hit_pos, &A->pos);
|
|
if (vm_vec_dot(&collision_vec, &A->orient.vec.fvec) > 0.999f){
|
|
right_angle_vec = A->orient.vec.rvec;
|
|
} else {
|
|
vm_vec_cross(&right_angle_vec, &A->orient.vec.uvec, &collision_vec);
|
|
}
|
|
|
|
vm_vec_scale_add2( &A->phys_info.vel, &right_angle_vec, +2.0f);
|
|
vm_vec_scale_add2( &B->phys_info.vel, &right_angle_vec, -2.0f);
|
|
|
|
return;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
//Only do damage if not a landing
|
|
if (!ship_ship_hit_info.is_landing) {
|
|
// Scale damage based on skill level for player.
|
|
if ((light_obj->flags[Object::Object_Flags::Player_ship]) || (heavy_obj->flags[Object::Object_Flags::Player_ship])) {
|
|
|
|
// Cyborg17 - Pretty hackish, but it's our best option, limit the amount of times a collision can
|
|
// happen to multiplayer clients, because otherwise the server can kill clients far too quickly.
|
|
// So here it goes, first only do this on the master (has an intrinsic multiplayer check)
|
|
if (MULTIPLAYER_MASTER) {
|
|
// check to see if both colliding ships are player ships
|
|
bool second_player_check = false;
|
|
if ((light_obj->flags[Object::Object_Flags::Player_ship]) && (heavy_obj->flags[Object::Object_Flags::Player_ship]))
|
|
second_player_check = true;
|
|
|
|
// iterate through each player
|
|
for (net_player & current_player : Net_players) {
|
|
// check that this player's ship is valid, and that it's not the server ship.
|
|
if ((current_player.m_player != nullptr) && !(current_player.flags & NETINFO_FLAG_AM_MASTER) && (current_player.m_player->objnum > 0) && current_player.m_player->objnum < MAX_OBJECTS) {
|
|
// check that one of the colliding ships is this player's ship
|
|
if ((light_obj == &Objects[current_player.m_player->objnum]) || (heavy_obj == &Objects[current_player.m_player->objnum])) {
|
|
// finally if the host is also a player, ignore making these adjustments for him because he is in a pure simulation.
|
|
if (&Ships[Objects[current_player.m_player->objnum].instance] != Player_ship) {
|
|
Assertion(Interp_info.find(current_player.m_player->objnum) != Interp_info.end(), "Somehow the collision code thinks there is not a player ship interp record in multi when there really *should* be. This is a coder mistake, please report!");
|
|
|
|
// temp set this as an uninterpolated ship, to make the collision look more natural until the next update comes in.
|
|
Interp_info[current_player.m_player->objnum].force_interpolation_mode();
|
|
|
|
// check to see if it has been long enough since the last collision, if not, negate the damage
|
|
if (!timestamp_elapsed(current_player.s_info.player_collision_timestamp)) {
|
|
damage = 0.0f;
|
|
} else {
|
|
// make the usual adjustments
|
|
damage *= (float)(Game_skill_level * Game_skill_level + 1) / (NUM_SKILL_LEVELS + 1);
|
|
// if everything is good to go, set the timestamp for the next collision
|
|
current_player.s_info.player_collision_timestamp = _timestamp(PLAYER_COLLISION_TIMESTAMP);
|
|
}
|
|
}
|
|
|
|
// did we find the player we were looking for?
|
|
if (!second_player_check) {
|
|
break;
|
|
// if we found one of the players we were looking for, set this to false so that the next one breaks the loop
|
|
} else {
|
|
second_player_check = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// if not in multiplayer, just do the damage adjustment.
|
|
} else {
|
|
damage *= (float) (Game_skill_level*Game_skill_level+1)/(NUM_SKILL_LEVELS+1);
|
|
}
|
|
} else if (Ships[light_obj->instance].team == Ships[heavy_obj->instance].team) {
|
|
// Decrease damage if non-player ships and not large.
|
|
// Looks dumb when fighters are taking damage from bumping into each other.
|
|
if ((light_obj->radius < 50.0f) && (heavy_obj->radius <50.0f)) {
|
|
damage /= 4.0f;
|
|
}
|
|
}
|
|
|
|
int quadrant_num = -1;
|
|
if (!The_mission.ai_profile->flags[AI::Profile_Flags::No_shield_damage_from_ship_collisions] && !(ship_ship_hit_info.heavy->flags[Object::Object_Flags::No_shields])) {
|
|
quadrant_num = get_ship_quadrant_from_global(&world_hit_pos, ship_ship_hit_info.heavy);
|
|
if (!ship_is_shield_up(ship_ship_hit_info.heavy, quadrant_num))
|
|
quadrant_num = -1;
|
|
}
|
|
|
|
float damage_heavy = (100.0f * damage / heavy_obj->phys_info.mass);
|
|
ship_apply_local_damage(ship_ship_hit_info.heavy, ship_ship_hit_info.light, &world_hit_pos, damage_heavy, light_shipp->collision_damage_type_idx,
|
|
quadrant_num, CREATE_SPARKS, ship_ship_hit_info.heavy_submodel_num, &ship_ship_hit_info.collision_normal);
|
|
|
|
hud_shield_quadrant_hit(ship_ship_hit_info.heavy, quadrant_num);
|
|
|
|
// don't draw sparks (using sphere hitpos)
|
|
float damage_light = (100.0f * damage / light_obj->phys_info.mass);
|
|
ship_apply_local_damage(ship_ship_hit_info.light, ship_ship_hit_info.heavy, &world_hit_pos, damage_light, heavy_shipp->collision_damage_type_idx,
|
|
MISS_SHIELDS, NO_SPARKS, -1, &ship_ship_hit_info.collision_normal);
|
|
|
|
hud_shield_quadrant_hit(ship_ship_hit_info.light, -1);
|
|
|
|
maybe_push_little_ship_from_fast_big_ship(ship_ship_hit_info.heavy, ship_ship_hit_info.light, ship_ship_hit_info.impulse, &ship_ship_hit_info.collision_normal);
|
|
}
|
|
}
|
|
|
|
if (!scripting::hooks::OnShipCollision->isActive()) {
|
|
return;
|
|
}
|
|
|
|
if(!b_override || a_override)
|
|
{
|
|
scripting::hooks::OnShipCollision->run(scripting::hooks::CollisionConditions{ {A, B} },
|
|
scripting::hook_param_list(scripting::hook_param("Self", 'o', A),
|
|
scripting::hook_param("Object", 'o', B),
|
|
scripting::hook_param("Ship", 'o', A),
|
|
scripting::hook_param("ShipB", 'o', B),
|
|
scripting::hook_param("Hitpos", 'o', world_hit_pos),
|
|
scripting::hook_param("ShipSubmodel", 'o', scripting::api::l_Submodel.Set(smh), has_submodel && (ship_ship_hit_info.heavy == A)),
|
|
scripting::hook_param("ShipBSubmodel", 'o', scripting::api::l_Submodel.Set(smh), has_submodel && (ship_ship_hit_info.heavy == B))));
|
|
}
|
|
if((b_override && !a_override) || (!b_override && !a_override))
|
|
{
|
|
// Yes, this should be reversed.
|
|
scripting::hooks::OnShipCollision->run(scripting::hooks::CollisionConditions{ {A, B} },
|
|
scripting::hook_param_list(scripting::hook_param("Self", 'o', B),
|
|
scripting::hook_param("Object", 'o', A),
|
|
scripting::hook_param("Ship", 'o', B),
|
|
scripting::hook_param("ShipB", 'o', A),
|
|
scripting::hook_param("Hitpos", 'o', world_hit_pos),
|
|
scripting::hook_param("ShipSubmodel", 'o', scripting::api::l_Submodel.Set(smh), has_submodel && (ship_ship_hit_info.heavy == B)),
|
|
scripting::hook_param("ShipBSubmodel", 'o', scripting::api::l_Submodel.Set(smh), has_submodel && (ship_ship_hit_info.heavy == A))));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks ship-ship collisions.
|
|
* @return 1 if all future collisions between these can be ignored because pair->a or pair->b aren't ships
|
|
* @return Otherwise always returns 0, since two ships can always collide unless one (1) dies or (2) warps out.
|
|
*/
|
|
//returns never_hits, process_data
|
|
collision_result collide_ship_ship_check( obj_pair * pair )
|
|
{
|
|
int player_involved;
|
|
float dist;
|
|
object *A = pair->a;
|
|
object *B = pair->b;
|
|
|
|
if ( A->type == OBJ_WAYPOINT ) return { true, std::any(), &collide_ship_ship_process };
|
|
if ( B->type == OBJ_WAYPOINT ) return { true, std::any(), &collide_ship_ship_process };
|
|
|
|
Assert( A->type == OBJ_SHIP );
|
|
Assert( B->type == OBJ_SHIP );
|
|
|
|
// Cyborg17 - no ship-ship collisions when doing multiplayer rollback
|
|
if ( (Game_mode & GM_MULTIPLAYER) && multi_ship_record_get_rollback_wep_mode() ) {
|
|
return { false, std::any(), &collide_ship_ship_process };
|
|
}
|
|
|
|
if (reject_due_collision_groups(A,B))
|
|
return { false, std::any(), &collide_ship_ship_process };
|
|
|
|
// If the player is one of the two colliding ships, flag this... it is used in
|
|
// several places this function.
|
|
if ( A == Player_obj || B == Player_obj ) {
|
|
player_involved = 1;
|
|
} else {
|
|
player_involved = 0;
|
|
|
|
// This is the most convenient place to do this check. Clients should *not* be doing anything
|
|
// collision related. Yes, from time to time that will look strange, but there are too many
|
|
// side effects if we allow it.
|
|
if (MULTIPLAYER_CLIENT){
|
|
return { false, std::any(), &collide_ship_ship_process };
|
|
}
|
|
}
|
|
|
|
// Don't check collisions for warping out player if past stage 1.
|
|
if ( player_involved && (Player->control_mode > PCM_WARPOUT_STAGE1) ) {
|
|
return { false, std::any(), &collide_ship_ship_process };
|
|
}
|
|
|
|
dist = vm_vec_dist( &A->pos, &B->pos );
|
|
|
|
// If one of these is a planet, do special stuff.
|
|
const auto& [planet_collision, planet_collision_data] = maybe_collide_planet(A, B);
|
|
if (planet_collision)
|
|
return { false, planet_collision_data, &mcp_1 };
|
|
|
|
if ( dist < A->radius + B->radius ) {
|
|
int hit;
|
|
|
|
object *HeavyOne, *LightOne;
|
|
// if two objects have the same mass, make the one with the larger pointer address the HeavyOne.
|
|
if ( fl_abs(A->phys_info.mass - B->phys_info.mass) < 1 ) {
|
|
if (A > B) {
|
|
HeavyOne = A;
|
|
LightOne = B;
|
|
} else {
|
|
HeavyOne = B;
|
|
LightOne = A;
|
|
}
|
|
} else {
|
|
if (A->phys_info.mass > B->phys_info.mass) {
|
|
HeavyOne = A;
|
|
LightOne = B;
|
|
} else {
|
|
HeavyOne = B;
|
|
LightOne = A;
|
|
}
|
|
}
|
|
|
|
collision_info_struct ship_ship_hit_info;
|
|
init_collision_info_struct(&ship_ship_hit_info);
|
|
|
|
ship_ship_hit_info.heavy = HeavyOne; // heavy object, generally slower moving
|
|
ship_ship_hit_info.light = LightOne; // light object, generally faster moving
|
|
ship_ship_hit_info.player_involved = player_involved;
|
|
|
|
hit = ship_ship_check_collision(&ship_ship_hit_info);
|
|
|
|
pair->next_check_time = timestamp(0);
|
|
|
|
if ( hit )
|
|
{
|
|
return { false, ship_ship_hit_info, &collide_ship_ship_process };
|
|
}
|
|
}
|
|
else {
|
|
// estimate earliest time at which pair can hit
|
|
|
|
// cap ships warping in/out can exceed ship's expected velocity
|
|
// if ship is warping in, in stage 1, its velocity is 0, so make ship try to collide next frame
|
|
|
|
// if ship is huge and warping in or out
|
|
if (((Ships[A->instance].is_arriving(ship::warpstage::STAGE1, false)) && (Ship_info[Ships[A->instance].ship_info_index].is_big_or_huge()))
|
|
|| ((Ships[B->instance].is_arriving(ship::warpstage::STAGE1, false)) && (Ship_info[Ships[B->instance].ship_info_index].is_big_or_huge())) ) {
|
|
pair->next_check_time = timestamp(0); // check next time
|
|
return { false, std::any(), &collide_ship_ship_process };
|
|
}
|
|
|
|
// get max of (1) max_vel.z, (2) 10, (3) afterburner_max_vel.z, (4) vel.z (for warping in ships exceeding expected max vel)
|
|
float shipA_max_speed, shipB_max_speed, time;
|
|
|
|
// get shipA max speed
|
|
if (ship_is_beginning_warpout_speedup(A)) {
|
|
shipA_max_speed = MAX(ship_get_max_speed(&Ships[A->instance]), ship_get_warpout_speed(A));
|
|
} else {
|
|
shipA_max_speed = ship_get_max_speed(&Ships[A->instance]);
|
|
}
|
|
|
|
// Maybe warping in or finished warping in with excessive speed
|
|
shipA_max_speed = MAX(shipA_max_speed, vm_vec_mag(&A->phys_info.vel));
|
|
shipA_max_speed = MAX(shipA_max_speed, 10.0f);
|
|
|
|
// get shipB max speed
|
|
if (ship_is_beginning_warpout_speedup(B)) {
|
|
shipB_max_speed = MAX(ship_get_max_speed(&Ships[B->instance]), ship_get_warpout_speed(B));
|
|
} else {
|
|
shipB_max_speed = ship_get_max_speed(&Ships[B->instance]);
|
|
}
|
|
|
|
// Maybe warping in or finished warping in with excessive speed
|
|
shipB_max_speed = MAX(shipB_max_speed, vm_vec_mag(&B->phys_info.vel));
|
|
shipB_max_speed = MAX(shipB_max_speed, 10.0f);
|
|
|
|
time = 1000.0f * (dist - A->radius - B->radius) / (shipA_max_speed + shipB_max_speed);
|
|
time -= 200.0f; // allow one frame slow frame at ~5 fps
|
|
|
|
if (time > 0) {
|
|
pair->next_check_time = timestamp( fl2i(time) );
|
|
} else {
|
|
pair->next_check_time = timestamp(0); // check next time
|
|
}
|
|
}
|
|
|
|
return { false, std::any(), &collide_ship_ship_process };
|
|
}
|
|
|
|
int collide_ship_ship( obj_pair * pair ) {
|
|
const auto& [never_check_again, collision_data, process_fnc] = collide_ship_ship_check(pair);
|
|
|
|
if (collision_data.has_value()) {
|
|
process_fnc(pair, collision_data);
|
|
}
|
|
|
|
return never_check_again ? 1 : 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* Checks prop-ship collisions.
|
|
* @return 1 if all future collisions between these can be ignored
|
|
*/
|
|
int collide_prop_ship(obj_pair* pair)
|
|
{
|
|
float dist;
|
|
object* prop_objp = pair->a;
|
|
object* ship_objp = pair->b;
|
|
|
|
Assert(prop_objp->type == OBJ_PROP);
|
|
Assert(ship_objp->type == OBJ_SHIP);
|
|
|
|
// Cyborg17 - no ship-ship collisions when doing multiplayer rollback
|
|
if ((Game_mode & GM_MULTIPLAYER) && multi_ship_record_get_rollback_wep_mode()) {
|
|
return 0;
|
|
}
|
|
|
|
if (ship_objp != Player_obj) {
|
|
// This is the most convenient place to do this check. Clients should *not* be doing anything
|
|
// collision related. Yes, from time to time that will look strange, but there are too many
|
|
// side effects if we allow it.
|
|
if (MULTIPLAYER_CLIENT) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// Don't check collisions for warping out player if past stage 1.
|
|
if (ship_objp == Player_obj && (Player->control_mode > PCM_WARPOUT_STAGE1)) {
|
|
return 0;
|
|
}
|
|
|
|
ship* shipp = &Ships[ship_objp->instance];
|
|
dist = vm_vec_dist(&prop_objp->pos, &ship_objp->pos);
|
|
|
|
if (dist < prop_objp->radius + ship_objp->radius) {
|
|
int hit;
|
|
|
|
object* heavy_obj, * light_obj;
|
|
if (prop_objp->radius > ship_objp->radius) {
|
|
heavy_obj = prop_objp;
|
|
light_obj = ship_objp;
|
|
}
|
|
else {
|
|
heavy_obj = ship_objp;
|
|
light_obj = prop_objp;
|
|
}
|
|
|
|
|
|
ship_info* sip = &Ship_info[shipp->ship_info_index];
|
|
|
|
collision_info_struct prop_ship_hit_info;
|
|
init_collision_info_struct(&prop_ship_hit_info);
|
|
|
|
prop_ship_hit_info.heavy = heavy_obj; // heavy object, generally slower moving
|
|
prop_ship_hit_info.light = light_obj; // light object, generally faster moving
|
|
|
|
vec3d world_hit_pos;
|
|
hit = prop_check_collision(prop_objp, ship_objp, &world_hit_pos, &prop_ship_hit_info);
|
|
|
|
pair->next_check_time = timestamp(0);
|
|
|
|
if (hit)
|
|
{
|
|
bool a_override = false, b_override = false;
|
|
|
|
// get submodel handle if scripting needs it
|
|
bool has_submodel = (prop_ship_hit_info.heavy_submodel_num >= 0);
|
|
scripting::api::submodel_h smh(prop_ship_hit_info.heavy_model_num, prop_ship_hit_info.heavy_submodel_num);
|
|
|
|
if (scripting::hooks::OnShipCollision->isActive()) {
|
|
a_override = scripting::hooks::OnShipCollision->isOverride(scripting::hooks::CollisionConditions{{prop_objp, ship_objp}},
|
|
scripting::hook_param_list(scripting::hook_param("Self", 'o', prop_objp),
|
|
scripting::hook_param("Object", 'o', ship_objp),
|
|
scripting::hook_param("Prop", 'o', prop_objp),
|
|
scripting::hook_param("Ship", 'o', ship_objp),
|
|
scripting::hook_param("Hitpos", 'o', world_hit_pos),
|
|
scripting::hook_param("PropSubmodel", 'o', scripting::api::l_Submodel.Set(smh), has_submodel && (prop_ship_hit_info.heavy == prop_objp)),
|
|
scripting::hook_param("ShipSubmodel", 'o', scripting::api::l_Submodel.Set(smh), has_submodel && (prop_ship_hit_info.heavy == ship_objp))));
|
|
}
|
|
|
|
if (scripting::hooks::OnPropCollision->isActive()) {
|
|
|
|
// Yes, this should be reversed.
|
|
b_override = scripting::hooks::OnPropCollision->isOverride(scripting::hooks::CollisionConditions{ {prop_objp, ship_objp} },
|
|
scripting::hook_param_list(scripting::hook_param("Self", 'o', ship_objp),
|
|
scripting::hook_param("Object", 'o', prop_objp),
|
|
scripting::hook_param("Ship", 'o', ship_objp),
|
|
scripting::hook_param("Prop", 'o', prop_objp),
|
|
scripting::hook_param("Hitpos", 'o', world_hit_pos),
|
|
scripting::hook_param("PropSubmodel", 'o', scripting::api::l_Submodel.Set(smh), has_submodel && (prop_ship_hit_info.heavy == prop_objp)),
|
|
scripting::hook_param("ShipSubmodel", 'o', scripting::api::l_Submodel.Set(smh), has_submodel && (prop_ship_hit_info.heavy == ship_objp))));
|
|
}
|
|
|
|
if (!a_override && !b_override)
|
|
{
|
|
|
|
// SET PHYSICS PARAMETERS
|
|
// already have (hitpos - heavy) and light_cm_pos
|
|
|
|
// get r_heavy and r_light
|
|
prop_ship_hit_info.r_heavy = prop_ship_hit_info.hit_pos;
|
|
vm_vec_sub(&prop_ship_hit_info.r_light, &prop_ship_hit_info.hit_pos, &prop_ship_hit_info.light_collision_cm_pos);
|
|
|
|
// set normal for edge hit
|
|
if (prop_ship_hit_info.edge_hit) {
|
|
vm_vec_copy_normalize(&prop_ship_hit_info.collision_normal, &prop_ship_hit_info.r_light);
|
|
vm_vec_negate(&prop_ship_hit_info.collision_normal);
|
|
}
|
|
|
|
// do physics
|
|
calculate_ship_ship_collision_physics(&prop_ship_hit_info);
|
|
|
|
// If a couple of small ships, just move them apart.
|
|
|
|
if (sip->is_small_ship()) {
|
|
if (ship_objp == Player_obj) {
|
|
vec3d h_to_l_vec;
|
|
vec3d perp_rel_vel;
|
|
|
|
vm_vec_sub(&h_to_l_vec, &heavy_obj->pos, &light_obj->pos);
|
|
|
|
// get comp of rel_vel perp to h_to_l_vec;
|
|
float mag = vm_vec_dot(&h_to_l_vec, &ship_objp->phys_info.vel) / vm_vec_mag_squared(&h_to_l_vec);
|
|
vm_vec_scale_add(&perp_rel_vel, &ship_objp->phys_info.vel, &h_to_l_vec, -mag);
|
|
vm_vec_normalize(&perp_rel_vel);
|
|
|
|
ship_objp->phys_info.vel -= perp_rel_vel * sip->collision_physics.bounce;
|
|
|
|
vm_vec_rotate(&ship_objp->phys_info.prev_ramp_vel, &ship_objp->phys_info.vel, &ship_objp->orient);
|
|
}
|
|
} else if (ship_objp == light_obj) {
|
|
// add extra velocity to separate the two objects, backing up the direction we came in.
|
|
// TODO: add effect of velocity from rotating submodel
|
|
float rel_vel = vm_vec_mag_quick(&prop_ship_hit_info.light_rel_vel);
|
|
if (rel_vel < 1) {
|
|
rel_vel = 1.0f;
|
|
}
|
|
|
|
light_obj->phys_info.vel -= prop_ship_hit_info.light_rel_vel * sip->collision_physics.bounce;
|
|
|
|
vm_vec_rotate(&light_obj->phys_info.prev_ramp_vel, &light_obj->phys_info.vel, &light_obj->orient);
|
|
}
|
|
|
|
|
|
float damage;
|
|
|
|
if (ship_objp == Player_obj && (Player->control_mode == PCM_WARPOUT_STAGE1)) {
|
|
gameseq_post_event(GS_EVENT_PLAYER_WARPOUT_STOP);
|
|
HUD_printf("%s", XSTR("Warpout sequence aborted.", 466));
|
|
}
|
|
|
|
damage = 0.005f * prop_ship_hit_info.impulse; // Cut collision-based damage in half.
|
|
|
|
if (damage > 5.0f) {
|
|
damage = 5.0f + (damage - 5.0f) / 2.0f;
|
|
}
|
|
|
|
if (prop_ship_hit_info.impulse > 0 && ship_objp == Player_obj) {
|
|
hud_start_text_flash(XSTR("Collision", 1431), 2000);
|
|
}
|
|
|
|
collide_ship_ship_do_sound(&world_hit_pos, prop_objp, ship_objp, ship_objp == Player_obj);
|
|
|
|
// check if we should do force feedback stuff
|
|
if (ship_objp == Player_obj && (prop_ship_hit_info.impulse > 0)) {
|
|
float scaler;
|
|
vec3d v;
|
|
|
|
scaler = -prop_ship_hit_info.impulse / Player_obj->phys_info.mass * 300;
|
|
vm_vec_copy_normalize(&v, &world_hit_pos);
|
|
joy_ff_play_vector_effect(&v, scaler);
|
|
}
|
|
|
|
//Only do damage if not a landing
|
|
// Scale damage based on skill level for player.
|
|
if ((ship_objp->flags[Object::Object_Flags::Player_ship])) {
|
|
|
|
// Cyborg17 - Pretty hackish, but it's our best option, limit the amount of times a collision can
|
|
// happen to multiplayer clients, because otherwise the server can kill clients far too quickly.
|
|
// So here it goes, first only do this on the master (has an intrinsic multiplayer check)
|
|
if (MULTIPLAYER_MASTER) {
|
|
|
|
// iterate through each player
|
|
for (net_player& current_player : Net_players) {
|
|
// check that this player's ship is valid, and that it's not the server ship.
|
|
if ((current_player.m_player != nullptr) && !(current_player.flags & NETINFO_FLAG_AM_MASTER) && (current_player.m_player->objnum > 0) && current_player.m_player->objnum < MAX_OBJECTS) {
|
|
// check that the colliding ship is this player's ship
|
|
if (ship_objp == &Objects[current_player.m_player->objnum]) {
|
|
// finally if the host is also a player, ignore making these adjustments for him because he is in a pure simulation.
|
|
if (&Ships[Objects[current_player.m_player->objnum].instance] != Player_ship) {
|
|
Assertion(Interp_info.find(current_player.m_player->objnum) != Interp_info.end(), "Somehow the collision code thinks there is not a player ship interp record in multi when there really *should* be. This is a coder mistake, please report!");
|
|
|
|
// temp set this as an uninterpolated ship, to make the collision look more natural until the next update comes in.
|
|
Interp_info[current_player.m_player->objnum].force_interpolation_mode();
|
|
|
|
// check to see if it has been long enough since the last collision, if not, negate the damage
|
|
if (!timestamp_elapsed(current_player.s_info.player_collision_timestamp)) {
|
|
damage = 0.0f;
|
|
}
|
|
else {
|
|
// make the usual adjustments
|
|
damage *= (float)(Game_skill_level * Game_skill_level + 1) / (NUM_SKILL_LEVELS + 1);
|
|
// if everything is good to go, set the timestamp for the next collision
|
|
current_player.s_info.player_collision_timestamp = _timestamp(PLAYER_COLLISION_TIMESTAMP);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// if not in multiplayer, just do the damage adjustment.
|
|
}
|
|
else {
|
|
damage *= (float)(Game_skill_level * Game_skill_level + 1) / (NUM_SKILL_LEVELS + 1);
|
|
}
|
|
}
|
|
|
|
// don't draw sparks (using sphere hitpos)
|
|
damage = (100.0f * damage / ship_objp->phys_info.mass);
|
|
ship_apply_local_damage(ship_objp, prop_objp, &world_hit_pos, damage, -1, MISS_SHIELDS, CREATE_SPARKS, -1, &prop_ship_hit_info.collision_normal);
|
|
|
|
hud_shield_quadrant_hit(ship_objp, -1);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
else {
|
|
// estimate earliest time at which pair can hit
|
|
|
|
// cap ships warping in/out can exceed ship's expected velocity
|
|
// if ship is warping in, in stage 1, its velocity is 0, so make ship try to collide next frame
|
|
|
|
// if ship is huge and warping in or out
|
|
if (shipp->is_arriving(ship::warpstage::STAGE1, false) && Ship_info[shipp->ship_info_index].is_big_or_huge()) {
|
|
pair->next_check_time = timestamp(0); // check next time
|
|
return 0;
|
|
}
|
|
|
|
// get max of (1) max_vel.z, (2) 10, (3) afterburner_max_vel.z, (4) vel.z (for warping in ships exceeding expected max vel)
|
|
float ship_max_speed, time;
|
|
|
|
// get shipA max speed
|
|
if (ship_is_beginning_warpout_speedup(ship_objp)) {
|
|
ship_max_speed = MAX(ship_get_max_speed(shipp), ship_get_warpout_speed(ship_objp));
|
|
}
|
|
else {
|
|
ship_max_speed = ship_get_max_speed(shipp);
|
|
}
|
|
|
|
// Maybe warping in or finished warping in with excessive speed
|
|
ship_max_speed = MAX(ship_max_speed, vm_vec_mag(&ship_objp->phys_info.vel));
|
|
ship_max_speed = MAX(ship_max_speed, 10.0f);
|
|
|
|
time = 1000.0f * (dist - ship_objp->radius - prop_objp->radius) / (ship_max_speed);
|
|
time -= 200.0f; // allow one frame slow frame at ~5 fps
|
|
|
|
if (time > 0) {
|
|
pair->next_check_time = timestamp(fl2i(time));
|
|
}
|
|
else {
|
|
pair->next_check_time = timestamp(0); // check next time
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
} |