mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-07 22:56:30 -10:00
* Add an "On Ship Damage" script hook Runs after resolved damage has been applied to a ship (lethal hits run before the destruction callback). Hook variables: Target, Source, Attacker, WeaponClass, WeaponSubtype, SourceKind (PROJECTILE, BEAM, SHOCKWAVE, COLLISION, OTHER), DamageType, ShieldDamage, HullDamage, Quadrant, Ballistic, BeamWeapon and LethalHit. Conditions can filter on the target ship. Nothing is computed unless a script has registered the hook. * Move the On Ship Damage hook call out of a lambda ship_do_damage() now calls a file-level static function, ship_run_damage_hook(), that takes the values it needs as arguments instead of capturing the function state. Hook behaviour is unchanged. * Report subsystem damage in the On Ship Damage hook Adds SubsystemDamage, the total subsystem strength a damage application removed, so a hit absorbed by subsystems no longer looks like a small hull hit. The hook description now says it only runs where damage is applied, which excludes multiplayer clients. * Address On Ship Damage review comments - Remove WeaponClass, WeaponSubtype, Ballistic and BeamWeapon from the hook. Scripts read these from Source when it is a weapon. - Add a "Weapon class" condition for the On Ship Damage hook. It matches only when the damage source is a weapon of that class; a weapon name that does not exist never matches. - SubsystemDamage is now a table keyed by subsystem handle that lists only the subsystems that lost strength. The total is removed. - Rename PROJECTILE to NON_BEAM_WEAPON. - Classify SourceKind by damage source: COLLISION for ship, debris and asteroid collisions; EXPLOSION for ship-death and docked-ship blasts and asteroid blasts; WASH for engine wash; SELF for kills where the ship damages itself; OTHER for other ship-sourced damage. - Reword the hook description. * Key On Ship Damage SubsystemDamage by canonical name Lua tables cannot use subsystem handles as keys in a usable way, so key the SubsystemDamage table by each subsystem's CanonicalName instead. * Tag ship-vs-prop damage as a collision and use in_bounds The ship-vs-prop collision call didn't pass DamageSource::Collision, so the On Ship Damage hook reported those hits as OTHER. Every other collision path was already tagged. Also use SCP_vector::in_bounds() for the Weapon_info and Damage_types index checks in ship_run_damage_hook().
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, 1.f, nullptr, DamageSource::Collision);
|
|
|
|
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, 1.f, nullptr, DamageSource::Collision);
|
|
|
|
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, 1.f, nullptr, DamageSource::Collision);
|
|
|
|
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;
|
|
} |