Files
fs2open.github.com/code/ship/shiphit.cpp
T
asarium 8f06e338a4 Add typesafe handle types and use it in game sound system
This adds a new class which allows to create type safe resource handle
types. At the moment, the engine uses ints pretty much everywhere for
providing handles to resources created by the specific subsystems. Since
all ints are the same to the C/C++ typesystem it is perfectly legal to
pass a sound handle to the graphics API as a buffer handle which will
obviously cause issues.

This adds a new type called "ID" which is a wrapper class for a generic
handle but with added type safety. An instance of this class cannot be
passed to a function expecting another type of handle which provided
compile-time type safety for these APIs.

As an initial test I converted the game sound handles to this new type.
I already found a bug in the mainhall system thanks to this new type.
The mainhall used the game sound handles as sound instance handles which
probably meant that that specific code never really worked right. I
removed the code since there was no better alternative for fixing it.
2018-04-22 16:22:45 +02:00

2581 lines
88 KiB
C++
Executable File

/*
* 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 <algorithm>
#include "asteroid/asteroid.h"
#include "debris/debris.h"
#include "fireball/fireballs.h"
#include "freespace.h"
#include "gamesequence/gamesequence.h"
#include "gamesnd/eventmusic.h"
#include "gamesnd/gamesnd.h"
#include "globalincs/linklist.h"
#include "hud/hud.h"
#include "hud/hudmessage.h"
#include "hud/hudtarget.h"
#include "iff_defs/iff_defs.h"
#include "io/joy_ff.h"
#include "io/timer.h"
#include "mission/missionlog.h"
#include "mod_table/mod_table.h"
#include "network/multi.h"
#include "network/multi_pmsg.h"
#include "network/multi_respawn.h"
#include "network/multimsgs.h"
#include "network/multiutil.h"
#include "object/object.h"
#include "object/objectdock.h"
#include "object/objectshield.h"
#include "object/objectsnd.h"
#include "parse/parselo.h"
#include "scripting/scripting.h"
#include "playerman/player.h"
#include "popup/popup.h"
#include "render/3d.h"
#include "ship/ship.h"
#include "ship/shipfx.h"
#include "ship/shiphit.h"
#include "weapon/beam.h"
#include "weapon/emp.h"
#include "weapon/shockwave.h"
#include "weapon/weapon.h"
#include "tracing/Monitor.h"
//#pragma optimize("", off)
//#pragma auto_inline(off)
struct ssm_firing_info;
extern void ssm_create(object *target, vec3d *start, size_t ssm_index, ssm_firing_info *override, int team);
typedef struct spark_pair {
int index1, index2;
float dist;
} spark_pair;
#define MAX_SPARK_PAIRS ((MAX_SHIP_HITS * MAX_SHIP_HITS - MAX_SHIP_HITS) / 2)
#define BIG_SHIP_MIN_RADIUS 80.0f // ship radius above which death rolls can't be shortened by excessive damage
vec3d Dead_camera_pos;
vec3d Original_vec_to_deader;
static bool global_damage = false;
//WMC - Camera rough draft stuff
/*
camid dead_get_camera()
{
static camid dead_camera;
if(!dead_camera.isValid())
dead_camera = cam_create("Dead camera");
return dead_camera;
}
*/
static bool is_subsys_destroyed(ship *shipp, int submodel)
{
ship_subsys *subsys;
if (submodel == -1) {
return false;
}
for ( subsys=GET_FIRST(&shipp->subsys_list); subsys != END_OF_LIST(&shipp->subsys_list); subsys = GET_NEXT(subsys) ) {
if (subsys->system_info->subobj_num == submodel) {
if (subsys->current_hits > 0.0f) {
return false;
} else {
return true;
}
}
}
return false;
}
// do_subobj_destroyed_stuff is called when a subobject for a ship is killed. Separated out
// to separate function on 10/15/97 by MWA for easy multiplayer access. It does all of the
// cool things like blowing off the model (if applicable, writing the logs, etc)
void do_subobj_destroyed_stuff( ship *ship_p, ship_subsys *subsys, vec3d* hitpos, bool no_explosion )
{
ship_info *sip;
object *ship_objp;
model_subsystem *psub;
vec3d g_subobj_pos;
int type, i, log_index;
// get some local variables
sip = &Ship_info[ship_p->ship_info_index];
ship_objp = &Objects[ship_p->objnum];
psub = subsys->system_info;
type = psub->type;
get_subsystem_world_pos(ship_objp, subsys, &g_subobj_pos);
// create fireballs when subsys destroy for large ships.
object* objp = &Objects[ship_p->objnum];
if (!(subsys->flags[Ship::Subsystem_Flags::Vanished]) && !no_explosion) {
if (objp->radius > 100.0f) {
// number of fireballs determined by radius of subsys
int num_fireballs;
if ( psub->radius < 3 ) {
num_fireballs = 1;
} else {
num_fireballs = 5;
}
vec3d temp_vec, center_to_subsys, rand_vec;
vm_vec_sub(&center_to_subsys, &g_subobj_pos, &objp->pos);
for (i=0; i<num_fireballs; i++) {
if (i==0) {
// make first fireball at hitpos
if (hitpos) {
temp_vec = *hitpos;
} else {
temp_vec = g_subobj_pos;
}
} else {
// make other fireballs at random positions, but try to keep on the surface
vm_vec_rand_vec_quick(&rand_vec);
float dot = vm_vec_dot(&center_to_subsys, &rand_vec);
vm_vec_scale_add2(&rand_vec, &center_to_subsys, -dot/vm_vec_mag_squared(&center_to_subsys));
vm_vec_scale_add(&temp_vec, &g_subobj_pos, &rand_vec, 0.5f*psub->radius);
}
// scale fireball size according to size of subsystem, but not less than 10
float fireball_rad = psub->radius * 0.2f;
if (fireball_rad < 10) {
fireball_rad = 10.0f;
}
vec3d fb_vel;
vm_vec_cross(&fb_vel, &objp->phys_info.rotvel, &center_to_subsys);
vm_vec_add2(&fb_vel, &objp->phys_info.vel);
int fireball_type = fireball_ship_explosion_type(sip);
if(fireball_type < 0) {
fireball_type = FIREBALL_EXPLOSION_MEDIUM;
}
fireball_create( &temp_vec, fireball_type, FIREBALL_MEDIUM_EXPLOSION, OBJ_INDEX(objp), fireball_rad, 0, &fb_vel );
}
}
}
if ( MULTIPLAYER_MASTER ) {
int index;
index = ship_get_index_from_subsys(subsys, ship_p->objnum);
vec3d hit;
if (hitpos) {
hit = *hitpos;
} else {
hit = g_subobj_pos;
}
send_subsystem_destroyed_packet( ship_p, index, hit );
}
// next do a quick sanity check on the current hits that we are keeping for the generic subsystems
// I think that there might be rounding problems with the floats. This code keeps us safe.
if ( ship_p->subsys_info[type].type_count == 1 ) {
ship_p->subsys_info[type].aggregate_current_hits = 0.0f;
} else {
float hits;
ship_subsys *ssp;
hits = 0.0f;
for ( ssp=GET_FIRST(&ship_p->subsys_list); ssp != END_OF_LIST(&ship_p->subsys_list); ssp = GET_NEXT(ssp) ) {
// type matches?
if ( (ssp->system_info->type == type) && !(ssp->flags[Ship::Subsystem_Flags::No_aggregate]) ) {
hits += ssp->current_hits;
}
}
ship_p->subsys_info[type].aggregate_current_hits = hits;
}
// store an event in the event log. Also, determine if all turrets or all
// engines have been destroyed (if the subsystem is a turret or engine).
// put a disabled or disarmed entry in the log if this is the case
//
// MWA -- 1/8/98 A problem was found when trying to determine (via sexpression) when some subsystems
// were destroyed. The bottom line is that is the psub->name and psub->subobj_name are different,
// then direct detection doesn't work. (This scenario happens mainly with turrets and probably with
// engines). So, my solution is to encode the ship_info index, and the subsystem index into one
// integer, and pass that as the "index" parameter to add_entry. We'll use that information to
// print out the info in the mission log.
Assert( ship_p->ship_info_index < 65535 );
// get the "index" of this subsystem in the ship info structure.
for ( i = 0; i < sip->n_subsystems; i++ ) {
if ( &(sip->subsystems[i]) == psub )
break;
}
Assert( i < sip->n_subsystems );
Assert( i < 65535 );
log_index = ((ship_p->ship_info_index << 16) & 0xffff0000) | (i & 0xffff);
// Don't log, display info, or play sounds about the activation subsytem
// FUBAR/Goober5000 - or about vanishing subsystems, per precedent with ship-vanish
int notify = (psub->type != SUBSYSTEM_ACTIVATION) && !(subsys->flags[Ship::Subsystem_Flags::Vanished]);
if (notify)
{
mission_log_add_entry(LOG_SHIP_SUBSYS_DESTROYED, ship_p->ship_name, psub->subobj_name, log_index );
if ( ship_objp == Player_obj )
{
if (!no_explosion) {
snd_play( gamesnd_get_game_sound(GameSounds::SUBSYS_DIE_1), 0.0f );
}
if (strlen(psub->alt_dmg_sub_name))
HUD_printf(XSTR( "Your %s subsystem has been destroyed", 499), psub->alt_dmg_sub_name);
else {
char r_name[NAME_LENGTH];
strcpy_s(r_name, ship_subsys_get_name(subsys));
for (i = 0; r_name[i] > 0; i++) {
if (r_name[i] == '|')
r_name[i] = ' ';
}
HUD_printf(XSTR( "Your %s subsystem has been destroyed", 499), r_name );
}
}
}
if ( psub->type == SUBSYSTEM_TURRET ) {
if ( ship_p->subsys_info[type].aggregate_current_hits <= 0.0f ) {
// Don't create "disarmed" event for small ships.
if (!(Ship_info[ship_p->ship_info_index].is_small_ship())) {
mission_log_add_entry(LOG_SHIP_DISARMED, ship_p->ship_name, NULL );
// ship_p->flags |= SF_DISARMED;
}
}
} else if (psub->type == SUBSYSTEM_ENGINE ) {
// when an engine is destroyed, we must change the max velocity of the ship
// to be some fraction of its normal maximum value
if ( ship_p->subsys_info[type].aggregate_current_hits <= 0.0f ) {
mission_log_add_entry(LOG_SHIP_DISABLED, ship_p->ship_name, NULL );
ship_p->flags.set(Ship::Ship_Flags::Disabled); // add the disabled flag
}
}
if ( psub->subobj_num > -1 ) {
shipfx_blow_off_subsystem(ship_objp,ship_p,subsys,&g_subobj_pos,no_explosion);
subsys->submodel_info_1.blown_off = 1;
}
if ( (psub->subobj_num != psub->turret_gun_sobj) && (psub->turret_gun_sobj >= 0) ) {
subsys->submodel_info_2.blown_off = 1;
}
if (notify && !no_explosion) {
// play sound effect when subsys gets blown up
gamesnd_id sound_index;
if ( Ship_info[ship_p->ship_info_index].is_huge_ship() ) {
sound_index=GameSounds::CAPSHIP_SUBSYS_EXPLODE;
} else if ( Ship_info[ship_p->ship_info_index].is_big_ship() ) {
sound_index=GameSounds::SUBSYS_EXPLODE;
}
if ( sound_index.isValid() ) {
snd_play_3d( gamesnd_get_game_sound(sound_index), &g_subobj_pos, &View_position );
}
}
// make the shipsounds work as they should...
if(subsys->subsys_snd_flags[Ship::Subsys_Sound_Flags::Alive])
{
obj_snd_delete_type(ship_p->objnum, subsys->system_info->alive_snd, subsys);
subsys->subsys_snd_flags.remove(Ship::Subsys_Sound_Flags::Alive);
}
if(subsys->subsys_snd_flags[Ship::Subsys_Sound_Flags::Turret_rotation])
{
obj_snd_delete_type(ship_p->objnum, subsys->system_info->turret_base_rotation_snd, subsys);
obj_snd_delete_type(ship_p->objnum, subsys->system_info->turret_gun_rotation_snd, subsys);
subsys->subsys_snd_flags.remove(Ship::Subsys_Sound_Flags::Turret_rotation);
}
if(subsys->subsys_snd_flags[Ship::Subsys_Sound_Flags::Rotate])
{
obj_snd_delete_type(ship_p->objnum, subsys->system_info->rotation_snd, subsys);
subsys->subsys_snd_flags.remove(Ship::Subsys_Sound_Flags::Rotate);
}
if((subsys->system_info->dead_snd.isValid()) && !(subsys->subsys_snd_flags[Ship::Subsys_Sound_Flags::Dead]))
{
obj_snd_assign(ship_p->objnum, subsys->system_info->dead_snd, &subsys->system_info->pnt, 0, OS_SUBSYS_DEAD, subsys);
subsys->subsys_snd_flags.remove(Ship::Subsys_Sound_Flags::Dead);
}
}
// Return weapon type that is associated with damaging_objp
// input: damaging_objp => object pointer responsible for damage
// exit: -1 => no weapon type is associated with damage object
// >=0 => weapon type associated with damage object
static int shiphit_get_damage_weapon(object *damaging_objp)
{
int weapon_info_index = -1;
if ( damaging_objp ) {
switch(damaging_objp->type) {
case OBJ_WEAPON:
weapon_info_index = Weapons[damaging_objp->instance].weapon_info_index;
break;
case OBJ_SHOCKWAVE:
weapon_info_index = shockwave_get_weapon_index(damaging_objp->instance);
break;
case OBJ_BEAM:
if (Beams_use_damage_factors) {
weapon_info_index = beam_get_weapon_info_index(damaging_objp);
}
break;
default:
weapon_info_index = -1;
break;
}
}
return weapon_info_index;
}
// Return range at which this object can apply damage.
// Based on object type and subsystem type.
static float subsys_get_range(object *other_obj, ship_subsys *subsys)
{
float range;
Assert(subsys); // Goober5000
if ((other_obj) && (other_obj->type == OBJ_SHOCKWAVE)) { // Goober5000 - check for NULL when via sexp
range = shockwave_get_max_radius(other_obj->instance) * 0.75f; // Shockwaves were too lethal to subsystems.
} else if ( subsys->system_info->type == SUBSYSTEM_TURRET ) {
range = subsys->system_info->radius*3;
} else {
range = subsys->system_info->radius*2;
}
return range;
}
#define MAX_DEBRIS_SHARDS 16 // cap the amount of debris shards that fly off per hit
// Make some random debris particles. Previous way was not very random. Create debris 75% of the time.
// Don't worry about multiplayer since this debris is the small stuff that cannot collide
static void create_subsys_debris(object *ship_objp, vec3d *hitpos)
{
float show_debris = frand();
if ( show_debris <= 0.75f ) {
int ndebris;
ndebris = (int)(show_debris * Detail.num_small_debris) + 1; // number of pieces of debris to create
if ( ndebris > MAX_DEBRIS_SHARDS )
ndebris = MAX_DEBRIS_SHARDS;
//mprintf(( "Damage = %.1f, ndebris=%d\n", show_debris, ndebris ));
for (int i=0; i<ndebris; i++ ) {
debris_create( ship_objp, -1, -1, hitpos, hitpos, 0, 1.0f );
}
}
}
static void create_vaporize_debris(object *ship_objp, vec3d *hitpos)
{
int ndebris;
float show_debris = frand();
ndebris = (int)(4.0f * ((0.5f + show_debris) * Detail.num_small_debris)) + 5; // number of pieces of debris to create
if ( ndebris > MAX_DEBRIS_SHARDS ) {
ndebris = MAX_DEBRIS_SHARDS;
}
//mprintf(( "Damage = %.1f, ndebris=%d\n", show_debris, ndebris ));
for (int i=0; i<ndebris; i++ ) {
debris_create( ship_objp, -1, -1, hitpos, hitpos, 0, 1.4f );
}
}
#define MAX_SUBSYS_LIST 200 //DTP MAX SUBSYS LIST BUMPED FROM 32 to 200, ahmm 32???
typedef struct {
float dist;
float range;
ship_subsys *ptr;
} sublist;
// do_subobj_hit_stuff() is called when a collision is detected between a ship and something
// else. This is where we see if any sub-objects on the ship should take damage.
//
// Depending on where the collision occurs, the sub-system and surrounding hull will take
// different amounts of damage. The amount of damage a sub-object takes depending on how
// close the colliding object is to the center of the sub-object. The remaining hull damage
// will be returned to the caller via the damage parameter.
//
//
// 0 -> 0.5 radius : 100% subobject 0% hull
// 0.5 -> 1.0 radius : 50% subobject 50% hull
// 1.0 -> 2.0 radius : 25% subobject 75% hull
// > 2.0 radius : 0% subobject 100% hull
//
//
// The weapon damage is not neccesarily distributed evently between sub-systems when more than
// one sub-system is to take damage. Whenever damage is to be assigned to a sub-system, the above
// percentages are used. So, if more than one sub-object is taking damage, the second sub-system
// to be assigned damage will take less damage. Eg. weapon hits in the 25% damage range of two
// subsytems, and the weapon damage is 12. First subsystem takes 3 points damage. Second subsystem
// will take 0.25*9 = 2.25 damage. Should be close enough for most cases, and hull would receive
// 0.75 * 9 = 6.75 damage.
//
// Used to use the following constants, but now damage is linearly scaled up to 2x the subsystem
// radius. Same damage applied as defined by constants below.
//
// Returns unapplied damage, which will probably be applied to the hull.
//
// Shockwave damage is handled here. If other_obj->type == OBJ_SHOCKWAVE, it's a shockwave.
// apply the same damage to all subsystems.
// Note: A negative damage number means to destroy the corresponding subsystem. For example, call with -SUBSYSTEM_ENGINE to destroy engine.
//
//WMC - hull_should_apply armor means that the initial subsystem had no armor, so the hull should apply armor instead.
float do_subobj_hit_stuff(object *ship_objp, object *other_obj, vec3d *hitpos, int submodel_num, float damage, bool *hull_should_apply_armor)
{
vec3d g_subobj_pos;
float damage_left, damage_if_hull;
int weapon_info_index;
ship_subsys *subsys;
ship *ship_p;
sublist subsys_list[MAX_SUBSYS_LIST];
int subsys_hit_first = -1; // the subsys which should be hit first and take most of the damage; index into subsys_list
vec3d hitpos2;
float ss_dif_scale = 1.0f; // Nuke: Set a base dificulty scale for compatibility
//WMC - first, set this to damage if it isn't NULL, in case we want to return with no damage to subsystems
if(hull_should_apply_armor != NULL) {
*hull_should_apply_armor = true;
}
Assert(ship_objp); // Goober5000 (but other_obj might be NULL via sexp)
Assert(hitpos); // Goober5000
ship_p = &Ships[ship_objp->instance];
// Don't damage player subsystems in a training mission.
if ( The_mission.game_type & MISSION_TYPE_TRAINING ) {
if (ship_objp == Player_obj){
return damage;
}
}
// Shockwave damage is applied like weapon damage. It gets consumed.
if ((other_obj != NULL) && (other_obj->type == OBJ_SHOCKWAVE)) // Goober5000 check for NULL
{
// MK, 9/2/99. Shockwaves do zero subsystem damage on small ships.
// Goober5000 - added back in via flag
if ((Ship_info[ship_p->ship_info_index].is_small_ship()) && !(The_mission.ai_profile->flags[AI::Profile_Flags::Shockwaves_damage_small_ship_subsystems]))
return damage;
else {
damage_left = shockwave_get_damage(other_obj->instance) / 4.0f;
damage_if_hull = damage_left;
}
hitpos2 = other_obj->pos;
} else {
damage_left = damage;
damage_if_hull = damage;
hitpos2 = *hitpos;
}
// scale subsystem damage if appropriate
weapon_info_index = shiphit_get_damage_weapon(other_obj); // Goober5000 - a NULL other_obj returns -1
if ((weapon_info_index >= 0) && ((other_obj->type == OBJ_WEAPON) ||
(Beams_use_damage_factors && (other_obj->type == OBJ_BEAM)))) {
if ( Weapon_info[weapon_info_index].wi_flags[Weapon::Info_Flags::Training] ) {
return damage_left;
}
damage_left *= Weapon_info[weapon_info_index].subsystem_factor;
damage_if_hull *= Weapon_info[weapon_info_index].armor_factor;
}
#ifndef NDEBUG
float hitpos_dist = vm_vec_dist( hitpos, &ship_objp->pos );
if ( hitpos_dist > ship_objp->radius * 2.0f ) {
mprintf(( "BOGUS HITPOS PASSED TO DO_SUBOBJ_HIT_STUFF (%.1f > %.1f)!\nInvestigate ship %s (%s), a hit was registered on this ship outside this ship's radius.\n", hitpos_dist, ship_objp->radius * 2.0f, ship_p->ship_name, Ship_info[ship_p->ship_info_index].name ));
// Get John ASAP!!!! Someone passed a local coordinate instead of world for hitpos probably.
}
#endif
if (!global_damage) {
create_subsys_debris(ship_objp, hitpos);
}
// First, create a list of the N subsystems within range.
// Then, one at a time, process them in order.
int count = 0;
for ( subsys=GET_FIRST(&ship_p->subsys_list); subsys != END_OF_LIST(&ship_p->subsys_list); subsys = GET_NEXT(subsys) )
{
model_subsystem *mss = subsys->system_info;
//Deal with cheat correctly. If damage is the negative of the subsystem type, then we'll just kill the subsystem
//See process_debug_keys() in keycontrol.cpp for details.
if (damage < 0.0f) {
// single player or multiplayer
Assert(Player_ai->targeted_subsys != NULL);
if ( (subsys == Player_ai->targeted_subsys) && (subsys->current_hits > 0.0f) ) {
Assert(mss->type == (int) -damage);
if (!(subsys->flags[Ship::Subsystem_Flags::No_aggregate])) {
ship_p->subsys_info[mss->type].aggregate_current_hits -= subsys->current_hits;
if (ship_p->subsys_info[mss->type].aggregate_current_hits < 0.0f) {
ship_p->subsys_info[mss->type].aggregate_current_hits = 0.0f;
}
}
subsys->current_hits = 0.0f;
do_subobj_destroyed_stuff( ship_p, subsys, hitpos );
continue;
} else {
continue;
}
}
if (subsys->current_hits > 0.0f) {
float dist, range;
if (Fixed_turret_collisions && submodel_num != -1 && submodel_num == mss->turret_gun_sobj) {
// Special case:
// if the subsystem is a turret and the hit submodel is its barrel,
// get the distance between the hit and the turret barrel center
find_submodel_instance_world_point(&g_subobj_pos, ship_p->model_instance_num, submodel_num, &ship_objp->orient, &ship_objp->pos);
dist = vm_vec_dist_quick(&hitpos2, &g_subobj_pos);
// Damage attenuation range of barrel radius * 2 makes full damage
// be taken regardless of where the barrel is hit
range = submodel_get_radius(Ship_info[ship_p->ship_info_index].model_num, submodel_num) * 2;
} else {
// Default behavior:
// get the distance between the hit and the subsystem center
get_subsystem_world_pos(ship_objp, subsys, &g_subobj_pos);
dist = vm_vec_dist_quick(&hitpos2, &g_subobj_pos);
range = subsys_get_range(other_obj, subsys);
}
if ( dist < range) {
if (Damage_impacted_subsystem_first && submodel_num != -1 && (submodel_num == mss->subobj_num || submodel_num == mss->turret_gun_sobj)) {
// If the hit impacted this subsystem's submodel, then make sure this subsys
// gets dealt damage first, even if another subsystem is closer to the hit location
subsys_hit_first = count;
}
if (mss->flags[Model::Subsystem_Flags::Collide_submodel]) {
if (submodel_num != -1 && submodel_num != mss->subobj_num && submodel_num != mss->turret_gun_sobj) {
// If this subsystem only wants to take damage when its submodel receives
// a direct hit and the current hit did not do so, skip it.
continue;
}
}
subsys_list[count].dist = dist;
subsys_list[count].range = range;
subsys_list[count].ptr = subsys;
count++;
if (count >= MAX_SUBSYS_LIST){
break;
}
}
}
}
int dmg_type_idx = -1;
int parent_armor_flags = 0;
if(ship_p->armor_type_idx > -1)
parent_armor_flags = Armor_types[ship_p->armor_type_idx].flags;
if (other_obj)
{
if(other_obj->type == OBJ_SHOCKWAVE) {
dmg_type_idx = shockwave_get_damage_type_idx(other_obj->instance);
} else if(other_obj->type == OBJ_WEAPON) {
dmg_type_idx = Weapon_info[Weapons[other_obj->instance].weapon_info_index].damage_type_idx;
} else if(other_obj->type == OBJ_BEAM) {
dmg_type_idx = Weapon_info[beam_get_weapon_info_index(other_obj)].damage_type_idx;
} else if(other_obj->type == OBJ_ASTEROID) {
dmg_type_idx = Asteroid_info[Asteroids[other_obj->instance].asteroid_type].damage_type_idx;
} else if(other_obj->type == OBJ_DEBRIS) {
dmg_type_idx = Debris[other_obj->instance].damage_type_idx;
} else if(other_obj->type == OBJ_SHIP) {
dmg_type_idx = Ships[other_obj->instance].collision_damage_type_idx;
}
}
// Now scan the sorted list of subsystems in range.
// Apply damage to the nearest one first (exception: subsys_hit_first),
// subtracting off damage as we go.
int i, j;
for (j=0; j<count; j++)
{
float dist, range;
ship_subsys *subsystem;
int min_index = -1;
if (Damage_impacted_subsystem_first && subsys_hit_first > -1) {
min_index = subsys_hit_first;
subsys_hit_first = -1;
} else {
float min_dist = 9999999.9f;
for (i=0; i<count; i++) {
if (subsys_list[i].dist < min_dist) {
min_dist = subsys_list[i].dist;
min_index = i;
}
}
}
Assert(min_index != -1);
float damage_to_apply = 0.0f;
subsystem = subsys_list[min_index].ptr;
range = subsys_list[min_index].range;
dist = subsys_list[min_index].dist;
subsys_list[min_index].dist = 9999999.9f; // Make sure we don't use this one again.
Assert(range > 0.0f); // Goober5000 - avoid div-0 below
// only do this for the closest affected subsystem
if ( (j == 0) && (!(parent_armor_flags & SAF_IGNORE_SS_ARMOR))) {
if(subsystem->armor_type_idx > -1)
{
damage = Armor_types[subsystem->armor_type_idx].GetDamage(damage, dmg_type_idx, 1.0f); // Nuke: I don't think we need to apply damage sacaling to this one, using 1.0f
if(hull_should_apply_armor) {
*hull_should_apply_armor = false;
}
}
}
// HORRIBLE HACK!
// MK, 9/4/99
// When Helios bombs are dual fired against the Juggernaut in sm3-01 (FS2), they often
// miss their target. There is code dating to FS1 in the collision code to detect that a bomb or
// missile has somehow missed its target. It gets its lifeleft set to 0.1 and then it detonates.
// Unfortunately, the shockwave damage was cut by 4 above. So boost it back up here.
if ((weapon_info_index >= 0) && (dist < 10.0f) && ((other_obj) && (other_obj->type == OBJ_SHOCKWAVE))) { // Goober5000 check for NULL
damage_left *= 4.0f * Weapon_info[weapon_info_index].subsystem_factor;
damage_if_hull *= 4.0f * Weapon_info[weapon_info_index].armor_factor;
}
if ( dist < range/2.0f ) {
if (subsystem->flags[Ship::Subsystem_Flags::Damage_as_hull])
damage_to_apply = damage_if_hull;
else
damage_to_apply = damage_left;
} else if ( dist < range ) {
if (subsystem->flags[Ship::Subsystem_Flags::Damage_as_hull])
damage_to_apply = damage_if_hull * (1.0f - dist/range);
else
damage_to_apply = damage_left * (1.0f - dist/range);
}
// if we're not in CLIENT_NODAMAGE multiplayer mode (which is a the NEW way of doing things)
if ( (damage_to_apply > 0.1f) && !(MULTIPLAYER_CLIENT) )
{
// Decrease damage to subsystems to player ships.
if (ship_objp->flags[Object::Object_Flags::Player_ship]){
ss_dif_scale = The_mission.ai_profile->subsys_damage_scale[Game_skill_level];
}
// Goober5000 - subsys guardian
if (subsystem->subsys_guardian_threshold > 0)
{
float min_subsys_strength = 0.01f * subsystem->subsys_guardian_threshold * subsystem->max_hits;
if ( (subsystem->current_hits - (damage_to_apply * ss_dif_scale)) < min_subsys_strength ) {
// find damage needed to take object to min subsys strength
damage_to_apply = subsystem->current_hits - min_subsys_strength;
// make sure damage is positive
damage_to_apply = MAX(0, damage_to_apply);
}
}
// decrease the damage left to apply to the ship subsystems
// WMC - since armor aborbs damage, subtract the amount of damage before we apply armor
damage_left -= (damage_to_apply * ss_dif_scale);
// if this subsystem doesn't carry damage then subtract it off of our total return
if (subsystem->system_info->flags[Model::Subsystem_Flags::Carry_no_damage]) {
if ((other_obj->type != OBJ_SHOCKWAVE) || (!(subsystem->system_info->flags[Model::Subsystem_Flags::Carry_shockwave]))) {
float subsystem_factor = 0.0f;
if ((weapon_info_index >= 0) && ((other_obj->type == OBJ_WEAPON) || (other_obj->type == OBJ_SHOCKWAVE))) {
if (subsystem->flags[Ship::Subsystem_Flags::Damage_as_hull]) {
subsystem_factor = Weapon_info[weapon_info_index].armor_factor;
} else {
subsystem_factor = Weapon_info[weapon_info_index].subsystem_factor;
}
}
if (subsystem_factor > 0.0f) {
damage -= ((MIN(subsystem->current_hits, (damage_to_apply * ss_dif_scale))) / subsystem_factor);
} else {
damage -= MIN(subsystem->current_hits, (damage_to_apply * ss_dif_scale));
}
}
}
//Apply armor to damage
if (subsystem->armor_type_idx >= 0) {
// Nuke: this will finally factor it in to damage_to_apply and i wont need to factor it in anywhere after this
damage_to_apply = Armor_types[subsystem->armor_type_idx].GetDamage(damage_to_apply, dmg_type_idx, ss_dif_scale);
} else { // Nuke: no get damage call to apply difficulty scaling, so factor it in now
damage_to_apply *= ss_dif_scale;
}
subsystem->current_hits -= damage_to_apply;
if (!(subsystem->flags[Ship::Subsystem_Flags::No_aggregate])) {
ship_p->subsys_info[subsystem->system_info->type].aggregate_current_hits -= damage_to_apply;
}
if (subsystem->current_hits < 0.0f) {
damage_left -= subsystem->current_hits;
if (!(subsystem->flags[Ship::Subsystem_Flags::No_aggregate])) {
ship_p->subsys_info[subsystem->system_info->type].aggregate_current_hits -= subsystem->current_hits;
}
subsystem->current_hits = 0.0f; // set to 0 so repair on subsystem takes immediate effect
}
if ( ship_p->subsys_info[subsystem->system_info->type].aggregate_current_hits < 0.0f ){
ship_p->subsys_info[subsystem->system_info->type].aggregate_current_hits = 0.0f;
}
// multiplayer clients never blow up subobj stuff on their own
if ( (subsystem->current_hits <= 0.0f) && !MULTIPLAYER_CLIENT) {
do_subobj_destroyed_stuff( ship_p, subsystem, hitpos );
}
if (damage_left <= 0) { // no more damage to distribute, so stop checking
damage_left = 0.0f;
break;
}
}
}
if (damage < 0.0f) {
damage = 0.0f;
}
// Note: I changed this to return damage_left and it completely screwed up balance.
// It had taken a few MX-50s to destory an Anubis (with 40% hull), then it took maybe ten.
// So, I left it alone. -- MK, 4/15/98
return damage;
}
// Store who/what killed the player, so we can tell the player how he died
static void shiphit_record_player_killer(object *killer_objp, player *p)
{
switch (killer_objp->type) {
case OBJ_WEAPON:
p->killer_objtype=OBJ_WEAPON;
p->killer_weapon_index=Weapons[killer_objp->instance].weapon_info_index;
p->killer_species = Ship_info[Ships[Objects[killer_objp->parent].instance].ship_info_index].species;
if ( &Objects[killer_objp->parent] == Player_obj ) {
// killed by a missile?
if(Weapon_info[p->killer_weapon_index].subtype == WP_MISSILE){
p->flags |= PLAYER_FLAGS_KILLED_SELF_MISSILES;
} else {
p->flags |= PLAYER_FLAGS_KILLED_SELF_UNKNOWN;
}
}
// in multiplayer, record callsign of killer if killed by another player
if ( (Game_mode & GM_MULTIPLAYER) && ( Objects[killer_objp->parent].flags[Object::Object_Flags::Player_ship]) ) {
int pnum;
pnum = multi_find_player_by_object( &Objects[killer_objp->parent] );
if ( pnum != -1 ) {
strcpy_s(p->killer_parent_name, Net_players[pnum].m_player->callsign);
} else {
nprintf(("Network", "Couldn't find player object of weapon for killer of %s\n", p->callsign));
}
} else {
strcpy_s(p->killer_parent_name, Ships[Objects[killer_objp->parent].instance].ship_name);
end_string_at_first_hash_symbol(p->killer_parent_name);
}
break;
case OBJ_SHOCKWAVE:
p->killer_objtype=OBJ_SHOCKWAVE;
p->killer_weapon_index = shockwave_get_weapon_index(killer_objp->instance);
p->killer_species = Ship_info[Ships[Objects[killer_objp->parent].instance].ship_info_index].species;
if ( &Objects[killer_objp->parent] == Player_obj ) {
p->flags |= PLAYER_FLAGS_KILLED_SELF_SHOCKWAVE;
}
if ( (Game_mode & GM_MULTIPLAYER) && ( Objects[killer_objp->parent].flags[Object::Object_Flags::Player_ship]) ) {
int pnum;
pnum = multi_find_player_by_object( &Objects[killer_objp->parent] );
if ( pnum != -1 ) {
strcpy_s(p->killer_parent_name, Net_players[pnum].m_player->callsign);
} else {
nprintf(("Network", "Couldn't find player object of shockwave for killer of %s\n", p->callsign));
}
} else {
strcpy_s(p->killer_parent_name, Ships[Objects[killer_objp->parent].instance].ship_name);
end_string_at_first_hash_symbol(p->killer_parent_name);
}
break;
case OBJ_SHIP:
p->killer_objtype=OBJ_SHIP;
p->killer_weapon_index=-1;
p->killer_species = Ship_info[Ships[killer_objp->instance].ship_info_index].species;
if ( Ships[killer_objp->instance].flags[Ship::Ship_Flags::Exploded] ) {
p->flags |= PLAYER_FLAGS_KILLED_BY_EXPLOSION;
}
if ( Ships[Objects[p->objnum].instance].wash_killed ) {
p->flags |= PLAYER_FLAGS_KILLED_BY_ENGINE_WASH;
}
// in multiplayer, record callsign of killer if killed by another player
if ( (Game_mode & GM_MULTIPLAYER) && (killer_objp->flags[Object::Object_Flags::Player_ship]) ) {
int pnum;
pnum = multi_find_player_by_object( killer_objp );
if ( pnum != -1 ) {
strcpy_s(p->killer_parent_name, Net_players[pnum].m_player->callsign);
} else {
nprintf(("Network", "Couldn't find player object for killer of %s\n", p->callsign));
}
} else {
strcpy_s(p->killer_parent_name, Ships[killer_objp->instance].ship_name);
end_string_at_first_hash_symbol(p->killer_parent_name);
}
break;
case OBJ_DEBRIS:
case OBJ_ASTEROID:
if ( killer_objp->type == OBJ_DEBRIS ) {
p->killer_objtype = OBJ_DEBRIS;
} else {
p->killer_objtype = OBJ_ASTEROID;
}
p->killer_weapon_index=-1;
p->killer_species = -1;
p->killer_parent_name[0] = '\0';
break;
case OBJ_BEAM:
int beam_obj;
beam_obj = beam_get_parent(killer_objp);
p->killer_species = -1;
p->killer_objtype = OBJ_BEAM;
if(beam_obj != -1){
if((Objects[beam_obj].type == OBJ_SHIP) && (Objects[beam_obj].instance >= 0)){
strcpy_s(p->killer_parent_name, Ships[Objects[beam_obj].instance].ship_name);
end_string_at_first_hash_symbol(p->killer_parent_name);
}
p->killer_species = Ship_info[Ships[Objects[beam_obj].instance].ship_info_index].species;
} else {
strcpy_s(p->killer_parent_name, "");
}
break;
case OBJ_NONE:
if ( Game_mode & GM_MULTIPLAYER ) {
Int3();
}
p->killer_objtype=-1;
p->killer_weapon_index=-1;
p->killer_parent_name[0]=0;
p->killer_species = -1;
break;
default:
Int3();
break;
}
}
// Say dead stuff.
static void show_dead_message(object *ship_objp, object *other_obj)
{
player *player_p;
// not doing anything when a non player dies.
if ( !(ship_objp->flags[Object::Object_Flags::Player_ship]) ){
return;
}
if(other_obj == NULL){
return;
}
// Get a pointer to the player (we are assured a player ship was killed)
if ( Game_mode & GM_NORMAL ) {
player_p = Player;
} else {
// in multiplayer, get a pointer to the player that died.
int pnum = multi_find_player_by_object( ship_objp );
if ( pnum == -1 ) {
return;
}
player_p = Net_players[pnum].m_player;
}
// multiplayer clients should already have this information.
if ( !MULTIPLAYER_CLIENT ){
shiphit_record_player_killer( other_obj, player_p );
}
// display a hud message is the guy killed isn't me (multiplayer only)
/*
if ( (Game_mode & GM_MULTIPLAYER) && (ship_obj != Player_obj) ) {
char death_text[256];
player_generate_death_text( player_p, death_text );
HUD_sourced_printf(HUD_SOURCE_HIDDEN, death_text);
}
*/
}
/* JAS: THIS DOESN'T SEEM TO BE USED, SO I COMMENTED IT OUT
// Apply damage to a ship, destroying if necessary, etc.
// Returns portion of damage that exceeds ship shields, ie the "unused" portion of the damage.
// Note: This system does not use the mesh shield. It applies damage to the overall ship shield.
float apply_damage_to_ship(object *objp, float damage)
{
float _ss;
add_shield_strength(objp, -damage);
// check if shields are below 0%, if so take leftover damage and apply to ship integrity
if ((_ss = get_shield_strength(objp)) < 0.0f ) {
damage = -_ss;
set_shield_strength(objp, 0.0f);
} else
damage = 0.0f;
return damage;
}
*/
// Do music processing for a ship hit.
static void ship_hit_music(object *ship_objp, object *other_obj)
{
Assert(ship_objp); // Goober5000
Assert(other_obj); // Goober5000
ship* ship_p = &Ships[ship_objp->instance];
object *parent;
// Switch to battle track when a ship is hit by fire
//
// If the ship hit has an AI class of none, it is a Cargo, NavBuoy or other non-aggressive
// ship, so don't start the battle music
if (!stricmp(Ai_class_names[Ai_info[ship_p->ai_index].ai_class], NOX("none")))
return;
// Only start if ship hit and firing ship are from different teams
int attackee_team, attacker_team;
attackee_team = Ships[ship_objp->instance].team;
// avoid uninitialized value by matching them
attacker_team = attackee_team;
switch ( other_obj->type )
{
case OBJ_SHIP:
attacker_team = Ships[other_obj->instance].team;
// Nonthreatening ship collided with ship, no big deal
if ( !stricmp(Ai_class_names[Ai_info[Ships[other_obj->instance].ai_index].ai_class], NOX("none")) )
return;
break;
case OBJ_WEAPON:
// parent of weapon is object num of ship that fired it
parent = &Objects[other_obj->parent];
if (parent->signature == other_obj->parent_sig)
attacker_team = Ships[parent->instance].team;
break;
default:
// Unexpected object type collided with ship, no big deal.
return;
}
// start the music if it was an attacking ship
if (iff_x_attacks_y(attacker_team, attackee_team))
event_music_battle_start();
}
// Make sparks fly off a ship.
// Currently used in misison_parse to create partially damaged ships.
// NOTE: hitpos is in model coordinates on the detail[0] submodel (highest detail hull)
// WILL NOT WORK RIGHT IF ON A ROTATING SUBMODEL
void ship_hit_sparks_no_rotate(object *ship_objp, vec3d *hitpos)
{
ship *ship_p = &Ships[ship_objp->instance];
int n = ship_p->num_hits;
if (n >= MAX_SHIP_HITS) {
n = rand() % MAX_SHIP_HITS;
} else {
ship_p->num_hits++;
}
// No rotation. Just make the spark
ship_p->sparks[n].pos = *hitpos;
ship_p->sparks[n].submodel_num = -1;
shipfx_emit_spark(ship_objp->instance, n); // Create the first wave of sparks
if ( n == 0 ) {
ship_p->next_hit_spark = timestamp(0); // when a hit spot will spark
}
}
// find the max number of sparks allowed for ship
// limited for fighter by hull % others by radius.
int get_max_sparks(object* ship_objp)
{
Assert(ship_objp->type == OBJ_SHIP);
Assert((ship_objp->instance >= 0) && (ship_objp->instance < MAX_SHIPS));
if(ship_objp->type != OBJ_SHIP){
return 1;
}
if((ship_objp->instance < 0) || (ship_objp->instance >= MAX_SHIPS)){
return 1;
}
ship *ship_p = &Ships[ship_objp->instance];
ship_info* si = &Ship_info[ship_p->ship_info_index];
if (si->flags[Ship::Info_Flags::Fighter]) {
float hull_percent = ship_objp->hull_strength / ship_p->ship_max_hull_strength;
if (hull_percent > 0.8f) {
return 1;
} else if (hull_percent > 0.3f) {
return 2;
} else {
return 3;
}
} else {
int num_sparks = (int) (ship_objp->radius * 0.08f);
if (num_sparks < 3) {
return 3;
} else if (num_sparks > MAX_SHIP_HITS) {
return MAX_SHIP_HITS;
} else {
return num_sparks;
}
}
}
// helper function to std::sort, sorting spark pairs by distance
static int spark_compare(const spark_pair &pair1, const spark_pair &pair2)
{
Assert(pair1.dist >= 0);
Assert(pair2.dist >= 0);
return (pair1.dist < pair2.dist);
}
// for big ships, when all spark slots are filled, make intelligent choice of one to be recycled
static int choose_next_spark(object *ship_objp, vec3d *hitpos)
{
int i, j, count, num_sparks, num_spark_pairs, spark_num;
vec3d world_hitpos[MAX_SHIP_HITS];
spark_pair spark_pairs[MAX_SPARK_PAIRS];
ship *shipp = &Ships[ship_objp->instance];
// only choose next spark when all slots are full
Assert(get_max_sparks(ship_objp) == Ships[ship_objp->instance].num_hits);
// get num_sparks
num_sparks = shipp->num_hits;
Assert(num_sparks <= MAX_SHIP_HITS);
// get num_spark_paris -- only sort these
num_spark_pairs = (num_sparks * num_sparks - num_sparks) / 2;
// get the world hitpos for all sparks
for (spark_num=0; spark_num<num_sparks; spark_num++) {
if (shipp->sparks[spark_num].submodel_num != -1) {
model_instance_find_world_point(&world_hitpos[spark_num], &shipp->sparks[spark_num].pos, shipp->model_instance_num, shipp->sparks[spark_num].submodel_num, &ship_objp->orient, &ship_objp->pos);
} else {
// rotate sparks correctly with current ship orient
vm_vec_unrotate(&world_hitpos[spark_num], &shipp->sparks[spark_num].pos, &ship_objp->orient);
vm_vec_add2(&world_hitpos[spark_num], &ship_objp->pos);
}
}
// check we're not making a spark in the same location as a current one
for (i=0; i<num_sparks; i++) {
float dist = vm_vec_dist_squared(&world_hitpos[i], hitpos);
if (dist < 1) {
return i;
}
}
// not same location, so maybe do random recyling
if (frand() > 0.5f) {
return (rand() % num_sparks);
}
// initialize spark pairs
for (i=0; i<num_spark_pairs; i++) {
spark_pairs[i].index1 = 0;
spark_pairs[i].index2 = 0;
spark_pairs[i].dist = FLT_MAX;
}
// set spark pairs
count = 0;
for (i=1; i<num_sparks; i++) {
for (j=0; j<i; j++) {
spark_pairs[count].index1 = i;
spark_pairs[count].index2 = j;
spark_pairs[count++].dist = vm_vec_dist_squared(&world_hitpos[i], &world_hitpos[j]);
}
}
Assert(count == num_spark_pairs);
// sort pairs
std::sort(spark_pairs, spark_pairs + count, spark_compare);
//mprintf(("Min spark pair dist %.1f\n", spark_pairs[0].dist));
// look through the first few sorted pairs, counting number of indices of closest pair
int index1 = spark_pairs[0].index1;
int index2 = spark_pairs[0].index2;
int count1 = 0;
int count2 = 0;
for (i=1; i<6; i++) {
if (spark_pairs[i].index1 == index1) {
count1++;
}
if (spark_pairs[i].index2 == index1) {
count1++;
}
if (spark_pairs[i].index1 == index2) {
count2++;
}
if (spark_pairs[i].index2 == index2) {
count2++;
}
}
// recycle spark which has most indices in sorted list of pairs
if (count1 > count2) {
return index1;
} else {
return index2;
}
}
// Make sparks fly off a ship.
static void ship_hit_create_sparks(object *ship_objp, vec3d *hitpos, int submodel_num)
{
vec3d tempv;
ship *shipp = &Ships[ship_objp->instance];
ship_info *sip = &Ship_info[shipp->ship_info_index];
int n, max_sparks;
n = shipp->num_hits;
max_sparks = get_max_sparks(ship_objp);
if (n >= max_sparks) {
if (sip->is_big_or_huge()) {
// large ship, choose intelligently
n = choose_next_spark(ship_objp, hitpos);
} else {
// otherwise, normal choice
n = rand() % max_sparks;
}
} else {
shipp->num_hits++;
}
bool instancing = false;
// decide whether to do instancing
if (submodel_num != -1) {
polymodel *pm = model_get(sip->model_num);
if (pm->detail[0] != submodel_num) {
// submodel is not hull
// OPTIMIZE ... check if submodel can not rotate
instancing = true;
}
}
if (instancing) {
// get the hit position in the subobject RF
vec3d temp_zero, temp_x, temp_y, temp_z;
model_instance_find_world_point(&temp_zero, &vmd_zero_vector, shipp->model_instance_num, submodel_num, &ship_objp->orient, &ship_objp->pos);
model_instance_find_world_point(&temp_x, &vmd_x_vector, shipp->model_instance_num, submodel_num, &ship_objp->orient, &ship_objp->pos);
model_instance_find_world_point(&temp_y, &vmd_y_vector, shipp->model_instance_num, submodel_num, &ship_objp->orient, &ship_objp->pos);
model_instance_find_world_point(&temp_z, &vmd_z_vector, shipp->model_instance_num, submodel_num, &ship_objp->orient, &ship_objp->pos);
// find submodel x,y,z axes
vm_vec_sub2(&temp_x, &temp_zero);
vm_vec_sub2(&temp_y, &temp_zero);
vm_vec_sub2(&temp_z, &temp_zero);
// find displacement from submodel origin
vec3d diff;
vm_vec_sub(&diff, hitpos, &temp_zero);
// find displacement from submodel origin in submodel RF
shipp->sparks[n].pos.xyz.x = vm_vec_dot(&diff, &temp_x);
shipp->sparks[n].pos.xyz.y = vm_vec_dot(&diff, &temp_y);
shipp->sparks[n].pos.xyz.z = vm_vec_dot(&diff, &temp_z);
shipp->sparks[n].submodel_num = submodel_num;
shipp->sparks[n].end_time = timestamp(-1);
} else {
// Rotate hitpos into ship_objp's frame of reference.
vm_vec_sub(&tempv, hitpos, &ship_objp->pos);
vm_vec_rotate(&shipp->sparks[n].pos, &tempv, &ship_objp->orient);
shipp->sparks[n].submodel_num = -1;
shipp->sparks[n].end_time = timestamp(-1);
}
// Create the first wave of sparks
shipfx_emit_spark(ship_objp->instance, n);
if ( n == 0 ) {
shipp->next_hit_spark = timestamp(0); // when a hit spot will spark
}
}
// Called from ship_hit_kill() when we detect the player has been killed.
static void player_died_start(object *killer_objp)
{
nprintf(("Network", "starting my player death\n"));
gameseq_post_event(GS_EVENT_DEATH_DIED);
/* vm_vec_scale_add(&Dead_camera_pos, &Player_obj->pos, &Player_obj->orient.fvec, -10.0f);
vm_vec_scale_add2(&Dead_camera_pos, &Player_obj->orient.uvec, 3.0f);
vm_vec_scale_add2(&Dead_camera_pos, &Player_obj->orient.rvec, 5.0f);
*/
// Create a good vector for the camera to move along during death sequence.
object *other_objp = NULL;
// on multiplayer clients, there have been occasions where we haven't been able to determine
// the killer of a ship (due to bogus/mismatched/timed-out signatures on the client side). If
// we don't know the killer, use the Player_obj as the other_objp for camera position.
if ( killer_objp ) {
switch (killer_objp->type) {
case OBJ_WEAPON:
case OBJ_SHOCKWAVE:
other_objp = &Objects[killer_objp->parent];
break;
case OBJ_SHIP:
case OBJ_DEBRIS:
case OBJ_ASTEROID:
case OBJ_NONE: // Something that just got deleted due to also dying -- it happened to me! --MK.
other_objp = killer_objp;
break;
case OBJ_BEAM:
int beam_obj_parent;
beam_obj_parent = beam_get_parent(killer_objp);
if(beam_obj_parent == -1){
other_objp = killer_objp;
} else {
other_objp = &Objects[beam_obj_parent];
}
break;
default:
Int3(); // Killed by an object of a peculiar type. What is it?
other_objp = killer_objp; // Enable to continue, just in case we shipped it with this bug...
}
} else {
other_objp = Player_obj;
}
vm_vec_add(&Original_vec_to_deader, &Player_obj->orient.vec.fvec, &Player_obj->orient.vec.rvec);
vm_vec_scale(&Original_vec_to_deader, 2.0f);
vm_vec_add2(&Original_vec_to_deader, &Player_obj->orient.vec.uvec);
vm_vec_normalize(&Original_vec_to_deader);
vec3d vec_from_killer;
vec3d *side_vec;
float dist;
Assert(other_objp != NULL);
if (Player_obj == other_objp) {
dist = 50.0f;
vec_from_killer = Player_obj->orient.vec.fvec;
} else {
dist = vm_vec_normalized_dir(&vec_from_killer, &Player_obj->pos, &other_objp->pos);
}
if (dist > 100.0f)
dist = 100.0f;
vm_vec_scale_add(&Dead_camera_pos, &Player_obj->pos, &vec_from_killer, dist);
float dot = vm_vec_dot(&Player_obj->orient.vec.rvec, &vec_from_killer);
if (fl_abs(dot) > 0.8f)
side_vec = &Player_obj->orient.vec.fvec;
else
side_vec = &Player_obj->orient.vec.rvec;
vm_vec_scale_add2(&Dead_camera_pos, side_vec, 10.0f);
Player_ai->target_objnum = -1; // Clear targeting. Otherwise, camera pulls away from player as soon as he blows up.
// stop any playing emp effect
emp_stop_local();
}
//#define DEATHROLL_TIME 3000 // generic deathroll is 3 seconds (3 * 1000 milliseconds) - Moved to ships.tbl
#define MIN_PLAYER_DEATHROLL_TIME 1000 // at least one second deathroll for a player
#define DEATHROLL_ROTVEL_CAP 6.3f // maximum added deathroll rotvel in rad/sec (about 1 rev / sec)
#define DEATHROLL_ROTVEL_MIN 0.8f // minimum added deathroll rotvel in rad/sec (about 1 rev / 12 sec)
#define DEATHROLL_MASS_STANDARD 50 // approximate mass of lightest ship
#define DEATHROLL_VELOCITY_STANDARD 70 // deathroll rotvel is scaled according to ship velocity
#define DEATHROLL_ROTVEL_SCALE 4 // constant determines how quickly deathroll rotvel is ramped up (smaller is faster)
static void saturate_fabs(float *f, float max)
{
if ( fl_abs(*f) > max) {
if (*f > 0.0f)
*f = max;
else
*f = -max;
}
}
// function to do generic things when a ship explodes
void ship_generic_kill_stuff( object *objp, float percent_killed )
{
float rotvel_mag;
int delta_time;
ship *sp;
Assert(objp->type == OBJ_SHIP);
Assert(objp->instance >= 0 && objp->instance < MAX_SHIPS );
if((objp->type != OBJ_SHIP) || (objp->instance < 0) || (objp->instance >= MAX_SHIPS)){
return;
}
sp = &Ships[objp->instance];
ship_info *sip = &Ship_info[sp->ship_info_index];
ai_announce_ship_dying(objp);
ship_stop_fire_primary(objp); //mostly for stopping fighter beam looping sounds -Bobboau
sp->flags.set(Ship::Ship_Flags::Dying);
objp->phys_info.flags |= (PF_DEAD_DAMP | PF_REDUCED_DAMP);
delta_time = (int) (sip->death_roll_base_time);
// For smaller ships, subtract off time proportional to excess damage delivered.
if (objp->radius < BIG_SHIP_MIN_RADIUS)
delta_time -= (int) (1.01f - 4*percent_killed);
// Cut down cargo death rolls. Looks a little silly. -- MK, 3/30/98.
if (sip->flags[Ship::Info_Flags::Cargo]) {
delta_time /= 4;
}
// Prevent bogus timestamps.
if (delta_time < 2)
delta_time = 2;
if (objp->flags[Object::Object_Flags::Player_ship]) {
// Note: Kamikaze ships have no minimum death time.
if (!(Ai_info[Ships[objp->instance].ai_index].ai_flags[AI::AI_Flags::Kamikaze]) && (delta_time < MIN_PLAYER_DEATHROLL_TIME))
delta_time = MIN_PLAYER_DEATHROLL_TIME;
}
//nprintf(("AI", "ShipHit.cpp: Frame %i, Gametime = %7.3f, Ship %s will die in %7.3f seconds.\n", Framecount, f2fl(Missiontime), Ships[objp->instance].ship_name, (float) delta_time/1000.0f));
// Make big ships have longer deathrolls.
// This is debug code by MK to increase the deathroll time so ships have time to evade the shockwave.
// Perhaps deathroll time should be specified in ships.tbl.
float damage = ship_get_exp_damage(objp);
if (damage >= 250.0f)
delta_time += 3000 + (int)(damage*4.0f + 4.0f*objp->radius);
if (Ai_info[sp->ai_index].ai_flags[AI::AI_Flags::Kamikaze])
delta_time = 2;
// Knossos gets 7-10 sec to die, time for "little" explosions
if (Ship_info[sp->ship_info_index].flags[Ship::Info_Flags::Knossos_device]) {
delta_time = 7000 + (int)(frand() * 3000.0f);
Ship_info[sp->ship_info_index].explosion_propagates = 0;
}
// Goober5000 - ship-specific deathroll time, woot
if (sp->special_exp_deathroll_time > 0)
{
delta_time = sp->special_exp_deathroll_time;
// prevent bogus timestamps, per comment several lines up
if (delta_time < 2)
delta_time = 2;
}
sp->death_time = sp->final_death_time = timestamp(delta_time); // Give him 3 secs to explode
//SUSHI: What are the chances of an instant vaporization? Check the ship type first (objecttypes.tbl), then the ship (ships.tbl)
ship_type_info *stp = &Ship_types[sip->class_type];
float vapChance = stp->vaporize_chance;
if (sip->vaporize_chance > 0.0f)
vapChance = sip->vaporize_chance;
if (sp->flags[Ship::Ship_Flags::Vaporize] || frand() < vapChance) {
// Assert(Ship_info[sp->ship_info_index].flags & SIF_SMALL_SHIP);
// LIVE FOR 100 MS
sp->final_death_time = timestamp(100);
}
//nprintf(("AI", "Time = %7.3f: final_death_time set to %7.3f\n", (float) timestamp_ticker/1000.0f, (float) sp->final_death_time/1000.0f));
sp->pre_death_explosion_happened = 0; // The little fireballs haven't came in yet.
sp->next_fireball = timestamp(0); //start one right away
ai_deathroll_start(objp);
// play death roll begin sound
sp->death_roll_snd = snd_play_3d( gamesnd_get_game_sound(GameSounds::DEATH_ROLL), &objp->pos, &View_position, objp->radius );
if (objp == Player_obj)
joy_ff_deathroll();
// apply a whack
// rotational velocity proportional to original translational velocity, with a bit added in.
// Also, preserve half of original rotational velocity.
// At standard speed (70) and standard mass (50), deathroll rotvel should be capped at DEATHROLL_ROTVEL_CAP
// Minimum deathroll velocity is set DEATHROLL_ROTVEL_MIN
// At lower speed, lower death rotvel (scaled linearly)
// At higher mass, lower death rotvel (scaled logarithmically)
// variable scale calculates the deathroll rotational velocity magnitude
float logval = (float) log10(objp->phys_info.mass / (0.05f*DEATHROLL_MASS_STANDARD));
float velval = ((vm_vec_mag_quick(&objp->phys_info.vel) + 3.0f) / DEATHROLL_VELOCITY_STANDARD);
float p1 = (float) (DEATHROLL_ROTVEL_CAP - DEATHROLL_ROTVEL_MIN);
rotvel_mag = (float) DEATHROLL_ROTVEL_MIN * 2.0f/(logval + 2.0f);
rotvel_mag += (float) (p1 * velval/logval) * 0.75f;
// set so maximum velocity from rotation is less than 200
if (rotvel_mag*objp->radius > 150) {
rotvel_mag = 150.0f / objp->radius;
}
if (object_is_dead_docked(objp)) {
// don't change current rotvel
sp->deathroll_rotvel = objp->phys_info.rotvel;
} else {
// if added rotvel is too random, we should decrease the random component, putting a const in front of the rotvel.
sp->deathroll_rotvel = objp->phys_info.rotvel;
sp->deathroll_rotvel.xyz.x += (frand() - 0.5f) * 2.0f * rotvel_mag;
saturate_fabs(&sp->deathroll_rotvel.xyz.x, 0.75f*DEATHROLL_ROTVEL_CAP);
sp->deathroll_rotvel.xyz.y += (frand() - 0.5f) * 3.0f * rotvel_mag;
saturate_fabs(&sp->deathroll_rotvel.xyz.y, 0.75f*DEATHROLL_ROTVEL_CAP);
sp->deathroll_rotvel.xyz.z += (frand() - 0.5f) * 6.0f * rotvel_mag;
// make z component 2x larger than larger of x,y
float largest_mag = MAX(fl_abs(sp->deathroll_rotvel.xyz.x), fl_abs(sp->deathroll_rotvel.xyz.y));
if (fl_abs(sp->deathroll_rotvel.xyz.z) < 2.0f*largest_mag) {
sp->deathroll_rotvel.xyz.z *= (2.0f * largest_mag / fl_abs(sp->deathroll_rotvel.xyz.z));
}
saturate_fabs(&sp->deathroll_rotvel.xyz.z, 0.75f*DEATHROLL_ROTVEL_CAP);
// nprintf(("Physics", "Frame: %i rotvel_mag: %5.2f, rotvel: (%4.2f, %4.2f, %4.2f)\n", Framecount, rotvel_mag, sp->deathroll_rotvel.x, sp->deathroll_rotvel.y, sp->deathroll_rotvel.z));
}
// blow out his reverse thrusters. Or drag, same thing.
objp->phys_info.rotdamp = (float) DEATHROLL_ROTVEL_SCALE / rotvel_mag;
objp->phys_info.side_slip_time_const = 10000.0f;
vm_vec_zero(&objp->phys_info.max_vel); // make so he can't turn on his own VOLITION anymore.
vm_vec_zero(&objp->phys_info.max_rotvel); // make so he can't change speed on his own VOLITION anymore.
}
// called from ship_hit_kill if the ship is vaporized
static void ship_vaporize(ship *shipp)
{
object *ship_objp;
// sanity
Assert(shipp != NULL);
if(shipp == NULL){
return;
}
Assert((shipp->objnum >= 0) && (shipp->objnum < MAX_OBJECTS));
if((shipp->objnum < 0) || (shipp->objnum >= MAX_OBJECTS)){
return;
}
ship_objp = &Objects[shipp->objnum];
// create debris shards
create_vaporize_debris(ship_objp, &ship_objp->pos);
}
// *ship_objp was hit and we've determined he's been killed! By *other_obj!
void ship_hit_kill(object *ship_objp, object *other_obj, float percent_killed, int self_destruct)
{
Assert(ship_objp); // Goober5000 - but not other_obj, not only for sexp but also for self-destruct
Script_system.SetHookObject("Self", ship_objp);
if(other_obj != NULL)
Script_system.SetHookObject("Killer", other_obj);
else
Script_system.SetHookObject("Killer", 0);
if(Script_system.IsConditionOverride(CHA_DEATH, ship_objp))
{
//WMC - Do scripting stuff
Script_system.RunCondition(CHA_DEATH, 0, NULL, ship_objp);
Script_system.RemHookVars(2, "Self", "Killer");
return;
}
ship *sp;
char *killer_ship_name;
int killer_damage_percent = 0;
int killer_index = -1;
object *killer_objp = NULL;
sp = &Ships[ship_objp->instance];
show_dead_message(ship_objp, other_obj);
if (ship_objp == Player_obj) {
player_died_start(other_obj);
}
// maybe vaporize him
if(sp->flags[Ship::Ship_Flags::Vaporize]){
ship_vaporize(sp);
}
// hehe
extern void game_tst_mark(object *objp, ship *shipp);
game_tst_mark(ship_objp, sp);
// single player and multiplayer masters evaluate the scoring and kill stuff
if ( !MULTIPLAYER_CLIENT ) {
killer_index = scoring_eval_kill( ship_objp );
// ship is destroyed -- send this event to the mission log stuff to record this event. Try to find who
// killed this ship. scoring_eval_kill above should leave the obj signature of the ship who killed
// this guy (or a -1 if no one got the kill).
killer_ship_name = NULL;
killer_damage_percent = -1;
if ( killer_index >= 0 ) {
object *objp;
int sig;
sig = sp->damage_ship_id[killer_index];
for ( objp = GET_FIRST(&obj_used_list); objp != END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) ) {
if ( objp->signature == sig ){
break;
}
}
// if the object isn't around, the try to find the object in the list of ships which has exited
if ( objp != END_OF_LIST(&obj_used_list) ) {
Assert ( (objp->type == OBJ_SHIP ) || (objp->type == OBJ_GHOST) ); // I suppose that this should be true
killer_ship_name = Ships[objp->instance].ship_name;
killer_objp = objp;
} else {
int ei;
ei = ship_find_exited_ship_by_signature( sig );
if ( ei != -1 ){
killer_ship_name = Ships_exited[ei].ship_name;
}
}
killer_damage_percent = (int)(sp->damage_ship[killer_index]/sp->total_damage_received * 100.0f);
}
if(!self_destruct){
// multiplayer
if(Game_mode & GM_MULTIPLAYER){
char name1[256] = "";
char name2[256] = "";
int np_index;
// get first name
np_index = multi_find_player_by_object(ship_objp);
if((np_index >= 0) && (np_index < MAX_PLAYERS) && (Net_players[np_index].m_player != NULL)){
strcpy_s(name1, Net_players[np_index].m_player->callsign);
} else {
strcpy_s(name1, sp->ship_name);
}
// argh
if((killer_objp != NULL) || (killer_ship_name != NULL)){
// second name
if(killer_objp == NULL){
strcpy_s(name2, killer_ship_name);
} else {
np_index = multi_find_player_by_object(killer_objp);
if((np_index >= 0) && (np_index < MAX_PLAYERS) && (Net_players[np_index].m_player != NULL)){
strcpy_s(name2, Net_players[np_index].m_player->callsign);
} else {
strcpy_s(name2, killer_ship_name);
}
}
}
mission_log_add_entry(LOG_SHIP_DESTROYED, name1, name2, killer_damage_percent);
} else {
// DKA: 8/23/99 allow message log in single player with no killer name
//if(killer_ship_name != NULL){
mission_log_add_entry(LOG_SHIP_DESTROYED, sp->ship_name, killer_ship_name, killer_damage_percent);
//}
}
}
// maybe praise the player for this kill
if ( (killer_damage_percent > 10) && (other_obj != NULL) ) {
if (other_obj->parent_sig == Player_obj->signature) {
ship_maybe_praise_player(sp);
}
else if ((other_obj->parent_type == OBJ_SHIP) || (other_obj->parent_type == OBJ_START)) {
ship_maybe_praise_self(sp, &Ships[Objects[other_obj->parent].instance]);
}
}
}
ship_generic_kill_stuff( ship_objp, percent_killed );
// mwa -- removed 2/25/98 -- why is this here? ship_objp->flags &= ~(OF_PLAYER_SHIP);
// if it is for observers, must deal with it a separate way!!!!
if ( MULTIPLAYER_MASTER ) {
// check to see if this ship needs to be respawned
multi_respawn_check(ship_objp);
// send the kill packet to all players
// maybe send vaporize packet to all players
send_ship_kill_packet( ship_objp, other_obj, percent_killed, self_destruct );
}
// if a non-player is dying, play a scream
if ( !(ship_objp->flags[Object::Object_Flags::Player_ship]) ) {
ship_maybe_scream(sp);
}
// if the player is dying, have wingman lament
if ( ship_objp == Player_obj ) {
ship_maybe_lament();
}
Script_system.RunCondition(CHA_DEATH, 0, NULL, ship_objp);
Script_system.RemHookVars(2, "Self", "Killer");
}
// function to simply explode a ship where it is currently at
void ship_self_destruct( object *objp )
{
Assert ( objp->type == OBJ_SHIP );
// try and find a player
if((Game_mode & GM_MULTIPLAYER) && (multi_find_player_by_object(objp) >= 0)){
int np_index = multi_find_player_by_object(objp);
if((np_index >= 0) && (np_index < MAX_PLAYERS) && (Net_players[np_index].m_player != NULL)){
mission_log_add_entry(LOG_SELF_DESTRUCTED, Net_players[np_index].m_player->callsign, NULL );
} else {
mission_log_add_entry(LOG_SELF_DESTRUCTED, Ships[objp->instance].ship_name, NULL );
}
} else {
mission_log_add_entry(LOG_SELF_DESTRUCTED, Ships[objp->instance].ship_name, NULL );
}
// check to see if this ship needs to be respawned
if(MULTIPLAYER_MASTER){
// player ship?
int np_index = multi_find_player_by_object(objp);
if((np_index >= 0) && (np_index < MAX_PLAYERS) && MULTI_CONNECTED(Net_players[np_index]) && (Net_players[np_index].m_player != NULL)){
char msg[512] = "";
sprintf(msg, "%s %s", Net_players[np_index].m_player->callsign, XSTR("Self destructed", 1476));
// send a message
send_game_chat_packet(Net_player, msg, MULTI_MSG_ALL, NULL, NULL, 2);
// printf
if(!(Game_mode & GM_STANDALONE_SERVER)){
HUD_printf("%s", msg);
}
}
}
// self destruct
ship_hit_kill(objp, NULL, 1.0f, 1);
}
extern int Homing_hits, Homing_misses;
// Call this instead of physics_apply_whack directly to
// deal with two docked ships properly.
// Goober5000 - note... hit_pos is in *local* coordinates
void ship_apply_whack(vec3d *force, vec3d *hit_pos, object *objp)
{
if (objp == Player_obj) {
nprintf(("Sandeep", "Playing stupid joystick effect\n"));
vec3d test;
vm_vec_unrotate(&test, force, &objp->orient);
game_whack_apply( -test.xyz.x, -test.xyz.y );
}
if (object_is_docked(objp))
{
float overall_mass = dock_calc_total_docked_mass(objp);
// Goober5000 - this code attempts to account properly for whacking a docked object as one mass.
// It isn't perfect, because physics doesn't completely account for it (particularly because it
// still uses the moment of inertia for the whacked object, not for all objects). Commenting
// out the contents of the block restores the Volition behavior, but it doesn't calculate the
// correct torque.
// Addendum: this block is now not executed for docked fighters or bombers because the whack
// looks like the fighter is doing evasive maneuvers
if ((objp->type != OBJ_SHIP) || !(Ship_info[Ships[objp->instance].ship_info_index].is_fighter_bomber()))
{
vec3d world_hit_pos, world_center_pos;
// calc world hit pos of the hit ship
vm_vec_unrotate(&world_hit_pos, hit_pos, &objp->orient);
vm_vec_add2(&world_hit_pos, &objp->pos);
// calc overall world center of ships
dock_calc_docked_center(&world_center_pos, objp);
// the new hitpos is the vector from world center to world hitpos
vm_vec_sub(hit_pos, &world_hit_pos, &world_center_pos);
}
// whack it
physics_apply_whack(force, hit_pos, &objp->phys_info, &objp->orient, overall_mass);
}
else
{
physics_apply_whack(force, hit_pos, &objp->phys_info, &objp->orient, objp->phys_info.mass);
}
}
// If a ship is dying and it gets hit, shorten its deathroll.
// But, if it's a player, don't decrease below MIN_PLAYER_DEATHROLL_TIME
static void shiphit_hit_after_death(object *ship_objp, float damage)
{
float percent_killed;
int delta_time, time_remaining;
ship *shipp = &Ships[ship_objp->instance];
ship_info *sip = &Ship_info[shipp->ship_info_index];
// Since the explosion has two phases (final_death_time and really_final_death_time)
// we should only shorten the deathroll time if that is the phase we're in.
// And you can tell by seeing if the timestamp is valid, since it gets set to
// invalid after it does the first large explosion.
if ( !timestamp_valid(shipp->final_death_time) ) {
return;
}
// Don't adjust vaporized ship
if (shipp->flags[Ship::Ship_Flags::Vaporize]) {
return;
}
// Don't shorten deathroll on very large ships.
if (ship_objp->radius > BIG_SHIP_MIN_RADIUS)
return;
percent_killed = damage/shipp->ship_max_hull_strength;
if (percent_killed > 1.0f)
percent_killed = 1.0f;
delta_time = (int) (4 * sip->death_roll_base_time * percent_killed);
time_remaining = timestamp_until(shipp->final_death_time);
//nprintf(("AI", "Gametime = %7.3f, Time until %s dies = %7.3f, delta = %7.3f\n", f2fl(Missiontime), Ships[ship_objp->instance].ship_name, (float)time_remaining/1000.0f, delta_time));
if (ship_objp->flags[Object::Object_Flags::Player_ship])
if (time_remaining < MIN_PLAYER_DEATHROLL_TIME)
return;
// nprintf(("AI", "Subtracting off %7.3f seconds from deathroll, reducing to %7.3f\n", (float) delta_time/1000.0f, (float) (time_remaining - delta_time)/1000.0f));
delta_time = time_remaining - delta_time;
if (ship_objp->flags[Object::Object_Flags::Player_ship])
if (delta_time < MIN_PLAYER_DEATHROLL_TIME)
delta_time = MIN_PLAYER_DEATHROLL_TIME;
// Prevent bogus timestamp.
if (delta_time < 2)
delta_time = 2;
shipp->final_death_time = timestamp(delta_time); // Adjust time until explosion.
}
MONITOR( ShipHits )
MONITOR( ShipNumDied )
static int maybe_shockwave_damage_adjust(object *ship_objp, object *other_obj, float *damage)
{
ship_subsys *subsys;
ship *shipp;
float dist, nearest_dist = FLT_MAX;
vec3d g_subobj_pos;
float max_damage;
float inner_radius, outer_radius;
Assert(ship_objp); // Goober5000 (but not other_obj in case of sexp)
Assert(damage); // Goober5000
Assert(ship_objp->type == OBJ_SHIP);
if (!other_obj) {
return 0;
}
if (other_obj->type != OBJ_SHOCKWAVE) {
return 0;
}
if (!(Ship_info[Ships[ship_objp->instance].ship_info_index].is_huge_ship())) {
return 0;
}
shipp = &Ships[ship_objp->instance];
// find closest subsystem distance to shockwave origin
for (subsys=GET_FIRST(&shipp->subsys_list); subsys != END_OF_LIST(&shipp->subsys_list); subsys = GET_NEXT(subsys) ) {
get_subsystem_world_pos(ship_objp, subsys, &g_subobj_pos);
dist = vm_vec_dist_quick(&g_subobj_pos, &other_obj->pos);
if (dist < nearest_dist) {
nearest_dist = dist;
}
}
// get max damage and adjust if needed to account for shockwave created from destroyed weapon
max_damage = shockwave_get_damage(other_obj->instance);
if (shockwave_get_flags(other_obj->instance) & SW_WEAPON_KILL) {
max_damage *= 4.0f;
}
outer_radius = shockwave_get_max_radius(other_obj->instance);
inner_radius = shockwave_get_min_radius(other_obj->instance);
// scale damage
// floor of 25%, max if within inner_radius, linear between
if (nearest_dist > outer_radius) {
*damage = max_damage / 4.0f;
} else if (nearest_dist < inner_radius) {
*damage = max_damage;
} else {
*damage = max_damage * (1.0f - 0.75f * (nearest_dist - inner_radius) / (outer_radius - inner_radius));
}
return 1;
}
// ------------------------------------------------------------------------
// ship_do_damage()
//
// Do damage assessment on a ship. This should only be called
// internally by ship_apply_global_damage and ship_apply_local_damage
//
//
// input: ship_objp => object pointer for ship receiving damage
// other_obj => object pointer to object causing damage
// hitpos => impact world pos on the ship
// TODO: get a better value for hitpos
// damage => damage to apply to the ship
// quadrant => which part of shield takes damage, -1 if not shield hit
// submodel_num=> which submodel was hit, -1 if none in particular
// wash_damage => 1 if damage is done by engine wash
// Goober5000 - sanity checked this whole function in the case that other_obj is NULL, which
// will happen with the explosion-effect sexp
void ai_update_lethality(object *ship_objp, object *weapon_obj, float damage);
static void ship_do_damage(object *ship_objp, object *other_obj, vec3d *hitpos, float damage, int quadrant, int submodel_num, int wash_damage=0)
{
// mprintf(("doing damage\n"));
ship *shipp;
float subsystem_damage = damage; // damage to be applied to subsystems
int other_obj_is_weapon;
int other_obj_is_beam;
int other_obj_is_shockwave;
int other_obj_is_asteroid;
int other_obj_is_debris;
int other_obj_is_ship;
float difficulty_scale_factor = 1.0f;
Assert(ship_objp); // Goober5000
Assert(hitpos); // Goober5000
Assert(ship_objp->instance >= 0);
Assert(ship_objp->type == OBJ_SHIP);
shipp = &Ships[ship_objp->instance];
// maybe adjust damage done by shockwave for BIG|HUGE
maybe_shockwave_damage_adjust(ship_objp, other_obj, &damage);
// Goober5000 - check to see what other_obj is
if (other_obj)
{
other_obj_is_weapon = ((other_obj->type == OBJ_WEAPON) && (other_obj->instance >= 0) && (other_obj->instance < MAX_WEAPONS));
other_obj_is_beam = ((other_obj->type == OBJ_BEAM) && (other_obj->instance >= 0) && (other_obj->instance < MAX_BEAMS));
other_obj_is_shockwave = ((other_obj->type == OBJ_SHOCKWAVE) && (other_obj->instance >= 0) && (other_obj->instance < MAX_SHOCKWAVES));
other_obj_is_asteroid = ((other_obj->type == OBJ_ASTEROID) && (other_obj->instance >= 0) && (other_obj->instance < MAX_ASTEROIDS));
other_obj_is_debris = ((other_obj->type == OBJ_DEBRIS) && (other_obj->instance >= 0) && (other_obj->instance < MAX_DEBRIS_PIECES));
other_obj_is_ship = ((other_obj->type == OBJ_SHIP) && (other_obj->instance >= 0) && (other_obj->instance < MAX_SHIPS));
}
else
{
other_obj_is_weapon = 0;
other_obj_is_beam = 0;
other_obj_is_shockwave = 0;
other_obj_is_asteroid = 0;
other_obj_is_debris = 0;
other_obj_is_ship = 0;
}
// update lethality of ship doing damage - modified by Goober5000
if (other_obj_is_weapon || other_obj_is_shockwave) {
ai_update_lethality(ship_objp, other_obj, damage);
}
// if this is a weapon
if (other_obj_is_weapon)
damage *= weapon_get_damage_scale(&Weapon_info[Weapons[other_obj->instance].weapon_info_index], other_obj, ship_objp);
MONITOR_INC( ShipHits, 1 );
// Don't damage player ship in the process of warping out.
if ( Player->control_mode >= PCM_WARPOUT_STAGE2 ) {
if ( ship_objp == Player_obj ){
return;
}
}
if ( other_obj_is_weapon ) {
// for tvt and dogfight missions, don't scale damage
if( (Game_mode & GM_MULTIPLAYER) && ((Netgame.type_flags & NG_TYPE_TEAM) || (Netgame.type_flags & NG_TYPE_DOGFIGHT)) ){
}
else {
// Do a little "skill" balancing for the player in single player and coop multiplayer
if (ship_objp->flags[Object::Object_Flags::Player_ship]) {
// Nuke - store it in a couple factor and we will apply it where needed
difficulty_scale_factor *= The_mission.ai_profile->player_damage_scale[Game_skill_level];
}
}
}
// if this is not a laser, or i'm not a multiplayer client
// apply pain to me
// Goober5000: make sure other_obj doesn't cause a read violation!
if (other_obj && !(Ship_info[Ships[Player_obj->instance].ship_info_index].flags[Ship::Info_Flags::No_pain_flash]))
{
// For the record, ship_hit_pain seems to simply be the red flash that appears
// on the screen when you're hit.
int special_check = !MULTIPLAYER_CLIENT;
// now the actual checks
if (other_obj->type == OBJ_BEAM)
{
Assert((beam_get_weapon_info_index(other_obj) >= 0) && (beam_get_weapon_info_index(other_obj) < Num_weapon_types));
if (((Weapon_info[beam_get_weapon_info_index(other_obj)].subtype != WP_LASER) || special_check) && (Player_obj != NULL) && (ship_objp == Player_obj))
{
ship_hit_pain(damage * difficulty_scale_factor, quadrant);
}
}
if (other_obj_is_weapon)
{
Assert((Weapons[other_obj->instance].weapon_info_index > -1) && (Weapons[other_obj->instance].weapon_info_index < Num_weapon_types));
if (((Weapon_info[Weapons[other_obj->instance].weapon_info_index].subtype != WP_LASER) || special_check) && (Player_obj != NULL) && (ship_objp == Player_obj))
{
ship_hit_pain(damage * difficulty_scale_factor, quadrant);
}
}
} // read violation sanity check
// If the ship is invulnerable, do nothing
if (ship_objp->flags[Object::Object_Flags::Invulnerable]) {
return;
}
// if ship is already dying, shorten deathroll.
if (shipp->flags[Ship::Ship_Flags::Dying]) {
shiphit_hit_after_death(ship_objp, (damage * difficulty_scale_factor));
return;
}
// If we hit the shield, reduce it's strength and found
// out how much damage is left over.
if ( quadrant >= 0 && !(ship_objp->flags[Object::Object_Flags::No_shields]) ) {
// mprintf(("applying damage ge to shield\n"));
float shield_factor = -1.0f;
int weapon_info_index;
weapon_info_index = shiphit_get_damage_weapon(other_obj);
if ( weapon_info_index >= 0 ) {
shield_factor = Weapon_info[weapon_info_index].shield_factor;
}
if ( shield_factor >= 0.0f ) {
damage *= shield_factor;
subsystem_damage *= shield_factor;
}
if ( damage > 0.0f ) {
float piercing_pct = 0.0f;
int dmg_type_idx = -1;
//Do armor stuff
if(other_obj_is_weapon) {
dmg_type_idx = Weapon_info[Weapons[other_obj->instance].weapon_info_index].damage_type_idx;
} else if(other_obj_is_beam) {
dmg_type_idx = Weapon_info[beam_get_weapon_info_index(other_obj)].damage_type_idx;
} else if(other_obj_is_shockwave) {
dmg_type_idx = shockwave_get_damage_type_idx(other_obj->instance);
} else if(other_obj_is_asteroid) {
dmg_type_idx = Asteroid_info[Asteroids[other_obj->instance].asteroid_type].damage_type_idx;
} else if(other_obj_is_debris) {
dmg_type_idx = Debris[other_obj->instance].damage_type_idx;
} else if(other_obj_is_ship) {
dmg_type_idx = Ships[other_obj->instance].collision_damage_type_idx;
}
if(shipp->shield_armor_type_idx != -1)
{
piercing_pct = Armor_types[shipp->shield_armor_type_idx].GetShieldPiercePCT(dmg_type_idx);
}
float pre_shield = damage; // Nuke: don't use the difficulty scaling in here, since its also applied in Armor_type.GetDamage. Don't want it to apply twice
float pre_shield_ss = subsystem_damage; // Nuke - same here
if (piercing_pct > 0.0f) {
damage = pre_shield * (1.0f - piercing_pct);
}
// Nuke: apply pre_shield difficulty scaling here, since it was meant to be applied through damage
pre_shield *= difficulty_scale_factor;
if(shipp->shield_armor_type_idx != -1)
{
// Nuke: this call will decide when to use the damage factor, but it will get used, unless the modder is dumb (like setting +Difficulty Scale Type: to 'manual' and not manually applying it in their calculations)
damage = Armor_types[shipp->shield_armor_type_idx].GetDamage(damage, dmg_type_idx, difficulty_scale_factor, other_obj_is_beam);
} else { // Nuke: if that didn't get called, difficulty would not be applied to damage so apply it here
damage *= difficulty_scale_factor;
}
damage = shield_apply_damage(ship_objp, quadrant, damage);
if (damage > 0.0f) {
subsystem_damage *= (damage / pre_shield);
} else {
subsystem_damage = 0.0f;
}
if (piercing_pct > 0.0f) {
damage += (piercing_pct * pre_shield);
subsystem_damage += (piercing_pct * pre_shield_ss);
}
}
// if shield damage was increased, don't carry over leftover damage at scaled level
if ( shield_factor > 1.0f ) {
damage /= shield_factor;
subsystem_damage /= shield_factor;
}
}
// Apply leftover damage to the ship's subsystem and hull.
if ( (damage > 0.0f) || (subsystem_damage > 0.0f) ) {
int weapon_info_index;
float pre_subsys = subsystem_damage; // Nuke: should be the last time we need to do this in this function
bool apply_hull_armor = true;
bool apply_diff_scale = true;
subsystem_damage = do_subobj_hit_stuff(ship_objp, other_obj, hitpos, submodel_num, subsystem_damage, &apply_hull_armor);
if (subsystem_damage > 0.0f) {
damage *= (subsystem_damage / pre_subsys);
} else {
damage = 0.0f;
}
//Do armor stuff
if (apply_hull_armor)
{
int dmg_type_idx = -1;
if(other_obj_is_weapon) {
dmg_type_idx = Weapon_info[Weapons[other_obj->instance].weapon_info_index].damage_type_idx;
} else if(other_obj_is_beam) {
dmg_type_idx = Weapon_info[beam_get_weapon_info_index(other_obj)].damage_type_idx;
} else if(other_obj_is_shockwave) {
dmg_type_idx = shockwave_get_damage_type_idx(other_obj->instance);
} else if(other_obj_is_asteroid) {
dmg_type_idx = Asteroid_info[Asteroids[other_obj->instance].asteroid_type].damage_type_idx;
} else if(other_obj_is_debris) {
dmg_type_idx = Debris[other_obj->instance].damage_type_idx;
} else if(other_obj_is_ship) {
dmg_type_idx = Ships[other_obj->instance].collision_damage_type_idx;
}
if(shipp->armor_type_idx != -1)
{
damage = Armor_types[shipp->armor_type_idx].GetDamage(damage, dmg_type_idx, difficulty_scale_factor, other_obj_is_beam);
apply_diff_scale = false;
}
}
// Nuke: this is done incase difficulty scaling is not applied into damage by getDamage() above
if (apply_diff_scale) {
damage *= difficulty_scale_factor; // Nuke: we can finally stop doing this now
}
// continue with damage?
if (damage > 0.0f) {
weapon_info_index = shiphit_get_damage_weapon(other_obj); // Goober5000 - a NULL other_obj returns -1
if ( weapon_info_index >= 0 ) {
if (Weapon_info[weapon_info_index].wi_flags[Weapon::Info_Flags::Puncture]) {
damage /= 4;
}
damage *= Weapon_info[weapon_info_index].armor_factor;
}
// if ship is flagged as can not die, don't let it die
if (shipp->ship_guardian_threshold > 0) {
float min_hull_strength = 0.01f * shipp->ship_guardian_threshold * shipp->ship_max_hull_strength;
if ( (ship_objp->hull_strength - damage) < min_hull_strength ) {
// find damage needed to take object to min hull strength
damage = ship_objp->hull_strength - min_hull_strength;
// make sure damage is positive
damage = MAX(0, damage);
}
}
// multiplayer clients don't do damage
if (((Game_mode & GM_MULTIPLAYER) && MULTIPLAYER_CLIENT)) {
} else {
// Check if this is simulated damage.
weapon_info_index = shiphit_get_damage_weapon(other_obj);
if ( weapon_info_index >= 0 ) {
if (Weapon_info[weapon_info_index].wi_flags[Weapon::Info_Flags::Training]) {
// diag_printf2("Simulated Hull for Ship %s hit, dropping from %.32f to %d.\n", shipp->ship_name, (int) ( ship_objp->sim_hull_strength * 100 ), (int) ( ( ship_objp->sim_hull_strength - damage ) * 100 ) );
ship_objp->sim_hull_strength -= damage;
ship_objp->sim_hull_strength = MAX( 0, ship_objp->sim_hull_strength );
return;
}
}
ship_objp->hull_strength -= damage;
}
// let damage gauge know that player ship just took damage
if ( Player_obj == ship_objp ) {
hud_gauge_popup_start(HUD_DAMAGE_GAUGE, 5000);
}
// DB - removed 1/12/99 - scoring code properly bails if MULTIPLAYER_CLIENT
// in multiplayer, if I am not the host, get out of this function here!!
//if ( MULTIPLAYER_CLIENT ) {
//return;
//}
if (other_obj)
{
switch (other_obj->type)
{
case OBJ_SHOCKWAVE:
scoring_add_damage(ship_objp,other_obj,damage);
break;
case OBJ_ASTEROID:
// don't call scoring for asteroids
break;
case OBJ_WEAPON:
if((other_obj->parent < 0) || (other_obj->parent >= MAX_OBJECTS)){
scoring_add_damage(ship_objp, NULL, damage);
} else {
scoring_add_damage(ship_objp, &Objects[other_obj->parent], damage);
}
break;
case OBJ_BEAM://give kills for fighter beams-Bobboau
{
int bobjn = beam_get_parent(other_obj);
// Goober5000 - only count beams fired by fighters or bombers unless the ai profile says different
if (bobjn >= 0)
{
if ( !(The_mission.ai_profile->flags[AI::Profile_Flags::Include_beams_in_stat_calcs]) &&
!(Ship_info[Ships[Objects[bobjn].instance].ship_info_index].is_fighter_bomber()) &&
!(Objects[bobjn].flags[Object::Object_Flags::Player_ship]) ) {
bobjn = -1;
}
}
if(bobjn == -1){
scoring_add_damage(ship_objp, NULL, damage);
} else {
scoring_add_damage(ship_objp, &Objects[bobjn], damage);
}
break;
}
default:
break;
}
} // other_obj
if (ship_objp->hull_strength <= 0.0f) {
MONITOR_INC( ShipNumDied, 1 );
ship_info *sip = &Ship_info[shipp->ship_info_index];
// If massive beam hitting small ship, vaporize otherwise normal damage pipeline
// Only vaporize once
// multiplayer clients should skip this
if(!MULTIPLAYER_CLIENT) {
if ( !(shipp->flags[Ship::Ship_Flags::Vaporize]) ) {
// Only small ships can be vaporized
if (sip->is_small_ship()) {
if (other_obj) { // Goober5000 check for NULL
if (other_obj->type == OBJ_BEAM)
{
int beam_weapon_info_index = beam_get_weapon_info_index(other_obj);
if ( (beam_weapon_info_index > -1) && (Weapon_info[beam_weapon_info_index].wi_flags[Weapon::Info_Flags::Huge]) ) {
// Flag as vaporized
shipp->flags.set(Ship::Ship_Flags::Vaporize);
}
}
}
}
}
}
// maybe engine wash death
if (wash_damage) {
shipp->wash_killed = 1;
}
float percent_killed = -get_hull_pct(ship_objp);
if (percent_killed > 1.0f){
percent_killed = 1.0f;
}
if ( !(shipp->flags[Ship::Ship_Flags::Dying]) && !MULTIPLAYER_CLIENT) { // if not killed, then kill
ship_hit_kill(ship_objp, other_obj, percent_killed, 0);
}
}
}
}
// if the hitting object is a weapon, maybe do some fun stuff here
if(other_obj_is_weapon)
{
weapon_info *wip;
Assert(other_obj->instance >= 0);
if (other_obj->instance < 0) {
return;
}
Assert(Weapons[other_obj->instance].weapon_info_index >= 0);
if (Weapons[other_obj->instance].weapon_info_index < 0) {
return;
}
wip = &Weapon_info[Weapons[other_obj->instance].weapon_info_index];
// if its a leech weapon - NOTE - unknownplayer: Perhaps we should do something interesting like direct the leeched energy into the attacker ?
if (wip->wi_flags[Weapon::Info_Flags::Energy_suck]) {
// reduce afterburner fuel
shipp->afterburner_fuel -= wip->afterburner_reduce;
shipp->afterburner_fuel = (shipp->afterburner_fuel < 0.0f) ? 0.0f : shipp->afterburner_fuel;
// reduce weapon energy
shipp->weapon_energy -= wip->weapon_reduce;
shipp->weapon_energy = (shipp->weapon_energy < 0.0f) ? 0.0f : shipp->weapon_energy;
}
}
}
// Goober5000
void ship_apply_tag(int ship_num, int tag_level, float tag_time, object *target, vec3d *start, int ssm_index, int ssm_team)
{
// set time first tagged
if (Ships[ship_num].time_first_tagged == 0)
Ships[ship_num].time_first_tagged = Missiontime;
// do tag effect
if (tag_level == 1)
{
// mprintf(("TAGGED %s for %f seconds\n", Ships[ship_num].ship_name, tag_time));
Ships[ship_num].tag_left = tag_time;
Ships[ship_num].tag_total = tag_time;
}
else if (tag_level == 2)
{
// mprintf(("Level 2 TAGGED %s for %f seconds\n", Ships[ship_num].ship_name, tag_time));
Ships[ship_num].level2_tag_left = tag_time;
Ships[ship_num].level2_tag_total = tag_time;
}
else if (tag_level == 3)
{
// tag C creates an SSM strike, yay -Bobboau
Assert(target);
Assert(start);
if (ssm_index < 0) // TAG-C? Is that you? -MageKing17
return;
ssm_create(target, start, ssm_index, NULL, ssm_team);
}
}
// This gets called to apply damage when something hits a particular point on a ship.
// This assumes that whoever called this knows if the shield got hit or not.
// hitpos is in world coordinates.
// if quadrant is not -1, then that part of the shield takes damage properly.
void ship_apply_local_damage(object *ship_objp, object *other_obj, vec3d *hitpos, float damage, int quadrant, bool create_spark, int submodel_num, vec3d *hit_normal)
{
Assert(ship_objp); // Goober5000
Assert(other_obj); // Goober5000
ship *ship_p = &Ships[ship_objp->instance];
weapon *wp = &Weapons[other_obj->instance];
bool create_sparks = true;
// If got hit by a weapon, tell the AI so it can react. Only do this line in single player,
// or if I am the master in a multiplayer game
if ((other_obj->type == OBJ_WEAPON) && ( !(Game_mode & GM_MULTIPLAYER) || MULTIPLAYER_MASTER )) {
// If weapon hits ship on same team and that ship not targeted and parent of weapon not player,
// don't do damage.
// Ie, player can always do damage. AI can only damage team if that ship is targeted.
if (wp->target_num != OBJ_INDEX(ship_objp)) {
if ((ship_p->team == wp->team) && !(Objects[other_obj->parent].flags[Object::Object_Flags::Player_ship]) ) {
return;
}
}
}
// only want to check the following in single player or if I am the multiplayer game server
// Added OBJ_BEAM for traitor detection - FUBAR
if ( !MULTIPLAYER_CLIENT && ((other_obj->type == OBJ_SHIP) || (other_obj->type == OBJ_WEAPON) || (other_obj->type == OBJ_BEAM)) ) {
ai_ship_hit(ship_objp, other_obj, hitpos, quadrant, hit_normal);
}
// Cut damage done on the player by 4x in training missions, but do full accredidation
if ( The_mission.game_type & MISSION_TYPE_TRAINING ){
if (ship_objp == Player_obj){
damage /= 4.0f;
}
}
// maybe tag the ship
if(!MULTIPLAYER_CLIENT && (other_obj->type == OBJ_WEAPON || other_obj->type == OBJ_BEAM)) {
weapon_info *wip = NULL;
if (other_obj->type == OBJ_WEAPON)
wip = &Weapon_info[Weapons[other_obj->instance].weapon_info_index];
else if (other_obj->type == OBJ_BEAM)
wip = &Weapon_info[Beams[other_obj->instance].weapon_info_index];
Assert(wip != NULL);
if (wip->wi_flags[Weapon::Info_Flags::Tag]) {
// ssm stuff
vec3d *start = hitpos;
int ssm_index = wip->SSM_index;
ship_apply_tag(ship_objp->instance, wip->tag_level, wip->tag_time, ship_objp, start, ssm_index, wp->team);
}
}
#ifndef NDEBUG
if (other_obj->type == OBJ_WEAPON) {
weapon_info *wip = &Weapon_info[Weapons[other_obj->instance].weapon_info_index];
if (wip->is_homing()) {
Homing_hits++;
// nprintf(("AI", " Hit! Hits = %i/%i\n", Homing_hits, (Homing_hits + Homing_misses)));
}
}
#endif
if ( Event_Music_battle_started == 0 ) {
ship_hit_music(ship_objp, other_obj);
}
if (damage < 0.0f){
damage = 0.0f;
}
// evaluate any possible player stats implications
scoring_eval_hit(ship_objp,other_obj);
global_damage = false;
ship_do_damage(ship_objp, other_obj, hitpos, damage, quadrant, submodel_num );
// DA 5/5/98: move ship_hit_create_sparks() after do_damage() since number of sparks depends on hull strength
// doesn't hit shield and we want sparks
if ((quadrant == MISS_SHIELDS) && create_spark) {
// check if subsys destroyed
if ( !is_subsys_destroyed(ship_p, submodel_num) ) {
// Simulated weapons don't cause sparks
if(other_obj->type == OBJ_WEAPON || other_obj->type == OBJ_BEAM) {
weapon_info *wip = NULL;
if (other_obj->type == OBJ_WEAPON)
wip = &Weapon_info[Weapons[other_obj->instance].weapon_info_index];
else if (other_obj->type == OBJ_BEAM)
wip = &Weapon_info[Beams[other_obj->instance].weapon_info_index];
Assert(wip != NULL);
if (wip->wi_flags[Weapon::Info_Flags::Training]) {
create_sparks = false;
}
}
if (create_sparks) {
ship_hit_create_sparks(ship_objp, hitpos, submodel_num);
}
}
//fireball_create( hitpos, FIREBALL_SHIP_EXPLODE1, OBJ_INDEX(ship_objp), 0.25f );
}
}
// This gets called to apply damage when a damaging force hits a ship, but at no
// point in particular. Like from a shockwave. This routine will see if the
// shield got hit and if so, apply damage to it.
// You can pass force_center==NULL if you the damage doesn't come from anywhere,
// like for debug keys to damage an object or something. It will
// assume damage is non-directional and will apply it correctly.
void ship_apply_global_damage(object *ship_objp, object *other_obj, vec3d *force_center, float damage )
{
Assert(ship_objp); // Goober5000 (but not other_obj in case of sexp)
vec3d tmp, world_hitpos;
global_damage = true;
if ( force_center ) {
int shield_quad;
vec3d local_hitpos;
// find world hitpos
vm_vec_sub( &tmp, force_center, &ship_objp->pos );
vm_vec_normalize_safe( &tmp );
vm_vec_scale_add( &world_hitpos, &ship_objp->pos, &tmp, ship_objp->radius );
// Rotate world_hitpos into local coordinates (local_hitpos)
vm_vec_sub(&tmp, &world_hitpos, &ship_objp->pos );
vm_vec_rotate( &local_hitpos, &tmp, &ship_objp->orient );
// shield_quad = quadrant facing the force_center
shield_quad = get_quadrant(&local_hitpos, ship_objp);
// Do damage on local point
ship_do_damage(ship_objp, other_obj, &world_hitpos, damage, shield_quad, -1 );
} else {
// Since an force_center wasn't specified, this is probably just a debug key
// to kill an object. So pick a shield quadrant and a point on the
// radius of the object.
vm_vec_scale_add( &world_hitpos, &ship_objp->pos, &ship_objp->orient.vec.fvec, ship_objp->radius );
for (int i=0; i<ship_objp->n_quadrants; i++){
ship_do_damage(ship_objp, other_obj, &world_hitpos, damage/ship_objp->n_quadrants, i, -1);
}
}
// AL 3-30-98: Show flashing blast icon if player ship has taken blast damage
if ( ship_objp == Player_obj ) {
// only show blast icon if playing on medium skill or lower -> unknownplayer: why? I think this should be changed.
// Goober5000 - agreed; commented out
//if ( Game_skill_level <= 2 ) {
hud_start_text_flash(XSTR("Blast", 1428), 2000);
//}
}
// evaluate any player stats scoring conditions (specifically, blasts from remotely detonated secondary weapons)
scoring_eval_hit(ship_objp,other_obj,1);
}
void ship_apply_wash_damage(object *ship_objp, object *other_obj, float damage)
{
vec3d world_hitpos, direction_vec, rand_vec;
// Since an force_center wasn't specified, this is probably just a debug key
// to kill an object. So pick a shield quadrant and a point on the
// radius of the object
vm_vec_rand_vec_quick(&rand_vec);
vm_vec_scale_add(&direction_vec, &ship_objp->orient.vec.fvec, &rand_vec, 0.5f);
vm_vec_normalize_quick(&direction_vec);
vm_vec_scale_add( &world_hitpos, &ship_objp->pos, &direction_vec, ship_objp->radius );
// Do damage to hull and not to shields
global_damage = true;
ship_do_damage(ship_objp, other_obj, &world_hitpos, damage, -1, -1, 1);
// AL 3-30-98: Show flashing blast icon if player ship has taken blast damage
if ( ship_objp == Player_obj ) {
// only show blast icon if playing on medium skill or lower
// Goober5000 - commented out
//if ( Game_skill_level <= 2 ) {
hud_start_text_flash(XSTR("Engine Wash", 1429), 2000);
//}
}
// evaluate any player stats scoring conditions (specifically, blasts from remotely detonated secondary weapons)
scoring_eval_hit(ship_objp,other_obj,1);
}
// player pain
void ship_hit_pain(float damage, int quadrant)
{
ship *shipp = &Ships[Player_obj->instance];
ship_info *sip = &Ship_info[shipp->ship_info_index];
if (!(Player_obj->flags[Object::Object_Flags::Invulnerable]))
{
if (Shield_pain_flash_factor != 0.0f && quadrant >= 0)
{
float effect = (Shield_pain_flash_factor * Player_obj->shield_quadrant[quadrant] * Player_obj->n_quadrants) / shield_get_max_strength(Player_obj);
if (Shield_pain_flash_factor < 0.0f)
effect -= Shield_pain_flash_factor;
game_flash((sip->shield_color[0] * effect) / 255.0f, (sip->shield_color[1] * effect) / 255.0f, (sip->shield_color[2] * effect) / 255.0f);
}
else
game_flash(damage * Generic_pain_flash_factor / 15.0f, -damage * Generic_pain_flash_factor / 30.0f, -damage * Generic_pain_flash_factor / 30.0f);
}
// kill any active popups when you get hit.
if ( Game_mode & GM_MULTIPLAYER ){
popup_kill_any_active();
}
}