mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-09 17:52:08 -10:00
* Allow 'No Impact Debris' to also apply to explosions The ship/subsystem flag `no impact debris` prevents the default debris shards from spawning, which is very valuable especially now that impact effects can be completely customized via the -particles table. Now that particle effects are also an option for ship and subsystems explosions, it is also invaluable to have the ability to turn off the spawning of shards when either of those explosions happen. This PR uses the same flag of `no impact debris` and simply does not spawn the default shards for ship explosions or subsystem explosions of that flag is enabled. I thought about adding two new flags, but that would add to the flag clutter, and most folks who use these flags to turn off impacts I would estimate also want them off for actual explosions. Also, a new flag called `no explosion debris` would likely be confusing, so if a new flag were to be added it should be called something like `no default explosion debris shards` or something. Overall I've opted with the simpler and cleaner solution to just have it all under the impact flags. If two new flags are desired though I can easily add them. Tested and works as expected. * throw clang a bone * use two different flags * update flag name * typo and logic fix * allow custom generic debris to always work * update to better methods * cleanup logic * clang
3220 lines
115 KiB
C++
Executable File
3220 lines
115 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/hudartillery.h"
|
|
#include "hud/hudets.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/hook_api.h"
|
|
#include "scripting/global_hooks.h"
|
|
#include "scripting/api/objs/subsystem.h"
|
|
#include "scripting/api/objs/vecmath.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)
|
|
|
|
typedef struct spark_pair {
|
|
int index1, index2;
|
|
float dist;
|
|
} spark_pair;
|
|
|
|
#define MAX_SPARK_PAIRS ((MAX_SHIP_SPARKS * MAX_SHIP_SPARKS - MAX_SHIP_SPARKS) / 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;
|
|
|
|
const std::shared_ptr<scripting::Hook<>> OnPainFlashHook = scripting::Hook<>::Factory(
|
|
"On Pain Flash", "Called when a pain flash is displayed.",
|
|
{
|
|
{"Pain_Type", "number", "The type of pain flash displayed: shield = 0 and hull = 1."},
|
|
});
|
|
|
|
|
|
//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)
|
|
// NOTE: if this function is used with ship_recalc_subsys_strength, it MUST be called first. If
|
|
// a child subsystem needs to be destroyed, the strength calculation needs to take it into account.
|
|
void do_subobj_destroyed_stuff( ship *ship_p, ship_subsys *subsys, const vec3d* hitpos, bool no_explosion )
|
|
{
|
|
ship_info *sip;
|
|
object *ship_objp;
|
|
ship_subsys *ssp;
|
|
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);
|
|
|
|
// see if this subsystem is on a submodel
|
|
if (psub->subobj_num >= 0) {
|
|
polymodel *pm = model_get(sip->model_num);
|
|
|
|
// see if there are any subsystems which have this submodel as a parent
|
|
for (ssp = GET_FIRST(&ship_p->subsys_list); ssp != END_OF_LIST(&ship_p->subsys_list); ssp = GET_NEXT(ssp)) {
|
|
// is it another subsys which has a submodel?
|
|
if (ssp != subsys && ssp->system_info->subobj_num >= 0) {
|
|
// is this other submodel a child of the one being destroyed?
|
|
if (pm->submodel[ssp->system_info->subobj_num].parent == psub->subobj_num) {
|
|
// is it not yet destroyed? (this is a valid check because we already know there is a submodel)
|
|
if (!ssp->submodel_instance_1->blown_off) {
|
|
// then destroy it first
|
|
ssp->current_hits = 0;
|
|
do_subobj_destroyed_stuff(ship_p, ssp, nullptr, no_explosion);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// create fireballs when subsys destroy for large ships.
|
|
if (!(subsys->flags[Ship::Subsystem_Flags::Vanished, Ship::Subsystem_Flags::No_disappear]) && !no_explosion) {
|
|
vec3d center_to_subsys;
|
|
vm_vec_sub(¢er_to_subsys, &g_subobj_pos, &ship_objp->pos);
|
|
|
|
particle::ParticleEffectHandle death_effect;
|
|
|
|
if (psub->death_effect.isValid()) {
|
|
death_effect = psub->death_effect;
|
|
} else {
|
|
death_effect = sip->default_subsys_death_effect;
|
|
}
|
|
|
|
if (death_effect.isValid()) {
|
|
vec3d subsys_local_pos;
|
|
if (psub->subobj_num >= 0) {
|
|
// the vmd_zero_vector here should probably be psub->pnt instead, but this matches the behavior of get_subsystem_world_pos
|
|
model_instance_local_to_global_point(&subsys_local_pos, &vmd_zero_vector, ship_p->model_instance_num, psub->subobj_num);
|
|
} else {
|
|
subsys_local_pos = psub->pnt;
|
|
}
|
|
vec3d normalized_center_to_subsys = center_to_subsys;
|
|
vm_vec_normalize(&normalized_center_to_subsys);
|
|
// spawn particle effect
|
|
auto source = particle::ParticleManager::get()->createSource(death_effect);
|
|
source->setHost(make_unique<EffectHostObject>(ship_objp, subsys_local_pos, vmd_identity_matrix));
|
|
source->setTriggerRadius(psub->radius);
|
|
source->setNormal(normalized_center_to_subsys);
|
|
source->finishCreation();
|
|
} else if (ship_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, rand_vec;
|
|
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(¢er_to_subsys, &rand_vec);
|
|
vm_vec_scale_add2(&rand_vec, ¢er_to_subsys, -dot/vm_vec_mag_squared(¢er_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, &ship_objp->phys_info.rotvel, ¢er_to_subsys);
|
|
vm_vec_add2(&fb_vel, &ship_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, ship_p->objnum, fireball_rad, false, &fb_vel );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( MULTIPLAYER_MASTER ) {
|
|
int index;
|
|
|
|
index = ship_get_subsys_index(subsys);
|
|
|
|
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 = 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.
|
|
int subsystem_index;
|
|
for (subsystem_index = 0; subsystem_index < sip->n_subsystems; ++subsystem_index ) {
|
|
if ( &(sip->subsystems[subsystem_index]) == psub )
|
|
break;
|
|
}
|
|
Assert( subsystem_index < sip->n_subsystems );
|
|
Assert( subsystem_index < 65535 );
|
|
log_index = ((ship_p->ship_info_index << 16) & 0xffff0000) | (subsystem_index & 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 (!(sip->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
|
|
}
|
|
}
|
|
|
|
// call a scripting hook for the subsystem (regardless of whether it's added to the mission log)
|
|
if (scripting::hooks::OnSubsystemDestroyed->isActive()) {
|
|
scripting::hooks::OnSubsystemDestroyed->run(scripting::hooks::SubsystemDeathConditions{ ship_p, subsys },
|
|
scripting::hook_param_list(
|
|
scripting::hook_param("Ship", 'o', ship_objp),
|
|
scripting::hook_param("Subsystem", 'o', scripting::api::l_Subsystem.Set(scripting::api::ship_subsys_h(ship_objp, subsys)))
|
|
));
|
|
}
|
|
|
|
bool no_fireballs = psub->death_effect.isValid() || sip->default_subsys_death_effect.isValid();
|
|
|
|
if (!(subsys->flags[Ship::Subsystem_Flags::No_disappear])) {
|
|
if (psub->subobj_num > -1) {
|
|
shipfx_blow_off_subsystem(ship_objp, ship_p, subsys, &g_subobj_pos, no_explosion, no_fireballs);
|
|
subsys->submodel_instance_1->blown_off = true;
|
|
}
|
|
|
|
if ((psub->subobj_num != psub->turret_gun_sobj) && (psub->turret_gun_sobj >= 0)) {
|
|
subsys->submodel_instance_2->blown_off = true;
|
|
}
|
|
}
|
|
|
|
if (notify && !no_explosion) {
|
|
// play sound effect when subsys gets blown up
|
|
gamesnd_id sound_index;
|
|
if (ship_has_sound(ship_objp, GameSounds::SUBSYS_EXPLODE)) {
|
|
sound_index = ship_get_sound(ship_objp, GameSounds::SUBSYS_EXPLODE);
|
|
} else {
|
|
if ( sip->is_huge_ship() ) {
|
|
sound_index = GameSounds::CAPSHIP_SUBSYS_EXPLODE;
|
|
} else if ( sip->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, 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(const 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:
|
|
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(const object *other_obj, const 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_SUBSYS_LIST 200 //DTP MAX SUBSYS LIST BUMPED FROM 32 to 200, ahmm 32???
|
|
|
|
typedef struct {
|
|
float dist;
|
|
float range;
|
|
ship_subsys *ptr;
|
|
} sublist;
|
|
|
|
// fundamentally similar to do_subobj_hit_stuff, but without many checks inherent to damaging instead of healing
|
|
// most notably this does NOT return "remaining healing" (healing always carries), this is will NOT subtract from hull healing
|
|
|
|
std::pair<std::optional<ConditionData>, float> do_subobj_heal_stuff(const object* ship_objp, const object* other_obj, const vec3d* hitpos, int submodel_num, float healing)
|
|
{
|
|
vec3d g_subobj_pos;
|
|
float healing_left;
|
|
int weapon_info_index;
|
|
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 healing; index into subsys_list
|
|
vec3d hitpos2;
|
|
|
|
Assertion(ship_objp, "do_subobj_heal_stuff wasn't given an object to heal!");
|
|
Assertion(hitpos, "do_subobj_heal_stuff wasn't given a hit position!");
|
|
Assertion(other_obj, "do_subobj_heal_stuff wasn't given a healing object! (weapon/beam/shockwave)");
|
|
|
|
ship_p = &Ships[ship_objp->instance];
|
|
|
|
std::optional<ConditionData> subsys_impact = std::nullopt;
|
|
|
|
if (other_obj->type == OBJ_SHOCKWAVE)
|
|
{
|
|
healing_left = shockwave_get_damage(other_obj->instance) / 2.0f;
|
|
hitpos2 = other_obj->pos;
|
|
}
|
|
else {
|
|
healing_left = healing;
|
|
hitpos2 = *hitpos;
|
|
}
|
|
|
|
// First, create a list of the N subsystems within range.
|
|
// Then, one at a time, process them in order.
|
|
int count = 0;
|
|
for (auto 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;
|
|
|
|
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
|
|
auto pmi = model_get_instance(ship_p->model_instance_num);
|
|
auto pm = model_get(pmi->model_num);
|
|
model_instance_local_to_global_point(&g_subobj_pos, &vmd_zero_vector, pm, pmi, submodel_num, &ship_objp->orient, &ship_objp->pos);
|
|
dist = vm_vec_dist_quick(&hitpos2, &g_subobj_pos);
|
|
|
|
// Healing attenuation range of barrel radius * 2 makes full healing
|
|
// 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 healed 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 healing 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;
|
|
}
|
|
|
|
// Now scan the sorted list of subsystems in range.
|
|
// Apply healing to the nearest one first (exception: subsys_hit_first),
|
|
// subtracting off healing as we go.
|
|
int i, j;
|
|
for (j = 0; j < count; j++)
|
|
{
|
|
float dist, range;
|
|
ship_subsys* subsystem;
|
|
|
|
int min_index = -1;
|
|
{
|
|
float min_dist = 9999999.9f;
|
|
|
|
// find the closest subsystem
|
|
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);
|
|
}
|
|
|
|
// if the closest system does *not* override a submodel impact, and we have a submodel impact, use it instead
|
|
if (Damage_impacted_subsystem_first && subsys_hit_first >= 0 && !subsys_list[min_index].ptr->system_info->flags[Model::Subsystem_Flags::Override_submodel_impact]) {
|
|
min_index = subsys_hit_first;
|
|
subsys_hit_first = -1; // prevent the submodel impact from taking priority on the next loop iteration
|
|
}
|
|
|
|
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
|
|
|
|
// Make sure this subsystem still has hitpoints.
|
|
if (subsystem->current_hits <= 0.0f) {
|
|
continue;
|
|
}
|
|
|
|
// only do this for the closest affected subsystem
|
|
if ((j == 0) && (!(parent_armor_flags & SAF_IGNORE_SS_ARMOR))) {
|
|
if (subsystem->armor_type_idx > -1)
|
|
{
|
|
healing_left = Armor_types[subsystem->armor_type_idx].GetDamage(healing_left, dmg_type_idx, 1.0f, other_obj->type == OBJ_BEAM);
|
|
}
|
|
}
|
|
|
|
// scale subsystem healing if appropriate
|
|
float ss_factor = 1.0f;
|
|
float hull_factor = 1.0f;
|
|
weapon_info_index = shiphit_get_damage_weapon(other_obj);
|
|
if ((weapon_info_index >= 0) && ((other_obj->type == OBJ_WEAPON) ||
|
|
(Beams_use_damage_factors && (other_obj->type == OBJ_BEAM)))) {
|
|
ss_factor = Weapon_info[weapon_info_index].subsystem_factor;
|
|
hull_factor = Weapon_info[weapon_info_index].armor_factor;
|
|
}
|
|
|
|
float heal_to_apply = 0.0f;
|
|
if (dist < range / 2.0f) {
|
|
if (subsystem->flags[Ship::Subsystem_Flags::Damage_as_hull])
|
|
heal_to_apply = healing_left * hull_factor;
|
|
else
|
|
heal_to_apply = healing_left * ss_factor;
|
|
}
|
|
else if (dist < range) {
|
|
if (subsystem->flags[Ship::Subsystem_Flags::Damage_as_hull])
|
|
heal_to_apply = healing_left * hull_factor * (1.0f - dist / range);
|
|
else
|
|
heal_to_apply = healing_left * ss_factor * (1.0f - dist / range);
|
|
}
|
|
|
|
// if we're not in CLIENT_NODAMAGE multiplayer mode (which is a the NEW way of doing things)
|
|
if ((heal_to_apply > 0.1f) && !(MULTIPLAYER_CLIENT))
|
|
{
|
|
healing_left -= (heal_to_apply);
|
|
|
|
//Apply armor to healing
|
|
if (subsystem->armor_type_idx >= 0)
|
|
// Nuke: this will finally factor it in to heal_to_apply and i wont need to factor it in anywhere after this
|
|
heal_to_apply = Armor_types[subsystem->armor_type_idx].GetDamage(heal_to_apply, dmg_type_idx, 1.0f, other_obj->type == OBJ_BEAM);
|
|
|
|
if (j == 0) {
|
|
subsys_impact = ConditionData {
|
|
ImpactCondition(subsystem->armor_type_idx),
|
|
HitType::SUBSYS,
|
|
heal_to_apply,
|
|
subsystem->current_hits,
|
|
subsystem->max_hits,
|
|
};
|
|
}
|
|
|
|
subsystem->current_hits += heal_to_apply;
|
|
|
|
float* agg_hits = &ship_p->subsys_info[subsystem->system_info->type].aggregate_current_hits;
|
|
float agg_max_hits = ship_p->subsys_info[subsystem->system_info->type].aggregate_max_hits;
|
|
if (!(subsystem->flags[Ship::Subsystem_Flags::No_aggregate])) {
|
|
*agg_hits += heal_to_apply;
|
|
}
|
|
|
|
if (subsystem->current_hits > subsystem->max_hits) {
|
|
healing_left += subsystem->current_hits - subsystem->max_hits;
|
|
if (!(subsystem->flags[Ship::Subsystem_Flags::No_aggregate])) {
|
|
*agg_hits += subsystem->current_hits - subsystem->max_hits;
|
|
}
|
|
subsystem->current_hits = subsystem->max_hits;
|
|
}
|
|
|
|
if (*agg_hits > agg_max_hits)
|
|
*agg_hits = agg_max_hits;
|
|
|
|
|
|
if (healing_left <= 0) // no more healing to distribute, so stop checking
|
|
break;
|
|
}
|
|
}
|
|
return std::make_pair(subsys_impact, healing);
|
|
}
|
|
|
|
// 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.
|
|
|
|
std::pair<std::optional<ConditionData>, float> do_subobj_hit_stuff(object *ship_objp, const object *other_obj, const vec3d *hitpos, int submodel_num, float damage, bool *hull_should_apply_armor, float hit_dot, bool shield_hit)
|
|
{
|
|
vec3d g_subobj_pos;
|
|
float damage_left, damage_if_hull;
|
|
int weapon_info_index;
|
|
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
|
|
|
|
const bool other_obj_is_weapon = other_obj && other_obj->type == OBJ_WEAPON;
|
|
const bool other_obj_is_shockwave = other_obj && other_obj->type == OBJ_SHOCKWAVE;
|
|
const bool other_obj_is_beam = other_obj && other_obj->type == OBJ_BEAM;
|
|
|
|
//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];
|
|
|
|
std::optional<ConditionData> subsys_impact = std::nullopt;
|
|
|
|
// Don't damage player subsystems in a training mission.
|
|
if ( The_mission.game_type & MISSION_TYPE_TRAINING ) {
|
|
if (ship_objp == Player_obj){
|
|
return std::make_pair(subsys_impact, damage);
|
|
}
|
|
}
|
|
|
|
// Shockwave damage is applied like weapon damage. It gets consumed.
|
|
if (other_obj_is_shockwave)
|
|
{
|
|
// 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 std::make_pair(subsys_impact, 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_is_weapon) {
|
|
weapon* wp = &Weapons[other_obj->instance];
|
|
weapon_info* wip = &Weapon_info[weapon_info_index];
|
|
|
|
if ( wip->wi_flags[Weapon::Info_Flags::Training] ) {
|
|
return std::make_pair(subsys_impact, damage_left);
|
|
}
|
|
|
|
damage_left *= wip->subsystem_factor;
|
|
damage_left *= wip->weapon_hit_curves.get_output(weapon_info::WeaponHitCurveOutputs::SUBSYS_DAMAGE_MULT, std::forward_as_tuple(*wp, *ship_objp, hit_dot), &wp->modular_curves_instance);
|
|
damage_if_hull *= wip->armor_factor;
|
|
damage_if_hull *= wip->weapon_hit_curves.get_output(weapon_info::WeaponHitCurveOutputs::HULL_DAMAGE_MULT, std::forward_as_tuple(*wp, *ship_objp, hit_dot), &wp->modular_curves_instance);
|
|
} else if ((weapon_info_index >= 0) && other_obj_is_beam) {
|
|
weapon_info* wip = &Weapon_info[weapon_info_index];
|
|
|
|
if (Beams_use_damage_factors) {
|
|
if ( wip->wi_flags[Weapon::Info_Flags::Training] ) {
|
|
return std::make_pair(subsys_impact, damage_left);
|
|
}
|
|
damage_left *= wip->subsystem_factor;
|
|
damage_if_hull *= wip->armor_factor;
|
|
}
|
|
|
|
beam* b = &Beams[other_obj->instance];
|
|
damage_left *= wip->beam_hit_curves.get_output(weapon_info::BeamHitCurveOutputs::SUBSYS_DAMAGE_MULT, std::forward_as_tuple(*b, *other_obj), &b->modular_curves_instance);
|
|
damage_if_hull *= wip->beam_hit_curves.get_output(weapon_info::BeamHitCurveOutputs::HULL_DAMAGE_MULT, std::forward_as_tuple(*b, *other_obj), &b->modular_curves_instance);
|
|
}
|
|
|
|
|
|
#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) {
|
|
auto subsys = ship_get_subsys_for_submodel(ship_p, submodel_num);
|
|
|
|
if ( !(Ship_info[ship_p->ship_info_index].flags[Ship::Info_Flags::Disable_all_generic_impact_debris]) &&
|
|
( subsys == nullptr || !(subsys->system_info->flags[Model::Subsystem_Flags::Disable_all_generic_impact_debris]) ) ) {
|
|
create_generic_debris(ship_objp, hitpos, 1.0f, 5.0f, 1.0f, false);
|
|
}
|
|
}
|
|
|
|
polymodel_instance *pmi = nullptr;
|
|
polymodel *pm = nullptr;
|
|
|
|
// First, create a list of the N subsystems within range.
|
|
// Then, one at a time, process them in order.
|
|
int count = 0;
|
|
for ( auto 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, global_damage ? nullptr : 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
|
|
if (pmi == nullptr) {
|
|
pmi = model_get_instance(ship_p->model_instance_num);
|
|
pm = model_get(pmi->model_num);
|
|
}
|
|
model_instance_local_to_global_point(&g_subobj_pos, &vmd_zero_vector, pm, pmi, 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;
|
|
{
|
|
float min_dist = 9999999.9f;
|
|
|
|
// find the closest subsystem
|
|
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);
|
|
}
|
|
|
|
// if the closest system does *not* override a submodel impact, and we have a submodel impact, use it instead
|
|
if (Damage_impacted_subsystem_first && subsys_hit_first >= 0 && !subsys_list[min_index].ptr->system_info->flags[Model::Subsystem_Flags::Override_submodel_impact]) {
|
|
min_index = subsys_hit_first;
|
|
subsys_hit_first = -1; // prevent the submodel impact from taking priority on the next loop iteration
|
|
}
|
|
|
|
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
|
|
|
|
// Make sure this subsystem still has hitpoints. If it's a child of a parent that was destroyed, it will have been destroyed already.
|
|
if (subsystem->current_hits <= 0.0f) {
|
|
continue;
|
|
}
|
|
|
|
// 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, other_obj_is_beam); // 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_is_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];
|
|
}
|
|
|
|
// maybe modify damage FROM player ships
|
|
if (other_obj && other_obj->parent >= 0 && Objects[other_obj->parent].signature == other_obj->parent_sig) {
|
|
if (Objects[other_obj->parent].flags[Object::Object_Flags::Player_ship])
|
|
ss_dif_scale *= The_mission.ai_profile->player_damage_inflicted_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.0f, 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_is_shockwave || !(subsystem->system_info->flags[Model::Subsystem_Flags::Carry_shockwave])) {
|
|
float subsystem_factor = 0.0f;
|
|
if ((weapon_info_index >= 0) && (other_obj_is_weapon || other_obj_is_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, other_obj_is_beam);
|
|
} else { // Nuke: no get damage call to apply difficulty scaling, so factor it in now
|
|
damage_to_apply *= ss_dif_scale;
|
|
}
|
|
|
|
if (j == 0 && !shield_hit) {
|
|
subsys_impact = ConditionData {
|
|
ImpactCondition(subsystem->armor_type_idx),
|
|
HitType::SUBSYS,
|
|
damage_to_apply,
|
|
subsystem->current_hits,
|
|
subsystem->max_hits,
|
|
};
|
|
}
|
|
|
|
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, global_damage ? nullptr : 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 std::make_pair(subsys_impact, damage);
|
|
}
|
|
|
|
// Store who/what killed the player, so we can tell the player how he died
|
|
static void shiphit_record_player_killer(const 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;
|
|
if (killer_objp->parent >= 0 && killer_objp->parent < MAX_OBJECTS) {
|
|
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);
|
|
}
|
|
} else {
|
|
p->killer_species = -1;
|
|
strcpy_s(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);
|
|
}
|
|
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);
|
|
}
|
|
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);
|
|
}
|
|
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(const object *ship_objp, const 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_sparks;
|
|
if (n >= MAX_SHIP_SPARKS) {
|
|
n = Random::next(MAX_SHIP_SPARKS);
|
|
} else {
|
|
ship_p->num_sparks++;
|
|
}
|
|
|
|
// 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(const 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 = fl2i(ship_objp->radius * 0.08f);
|
|
if (num_sparks > MAX_SHIP_SPARKS) {
|
|
return MAX_SHIP_SPARKS;
|
|
} else if (num_sparks < 3) {
|
|
return 3;
|
|
} 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(const object *ship_objp, const vec3d *hitpos)
|
|
{
|
|
int i, j, count, num_sparks, num_spark_pairs, spark_num;
|
|
vec3d world_hitpos[MAX_SHIP_SPARKS];
|
|
spark_pair spark_pairs[MAX_SPARK_PAIRS];
|
|
ship *shipp = &Ships[ship_objp->instance];
|
|
auto pmi = model_get_instance(shipp->model_instance_num);
|
|
auto pm = model_get(pmi->model_num);
|
|
|
|
// only choose next spark when all slots are full
|
|
Assert(get_max_sparks(ship_objp) == Ships[ship_objp->instance].num_sparks);
|
|
|
|
// get num_sparks
|
|
num_sparks = shipp->num_sparks;
|
|
Assert(num_sparks <= MAX_SHIP_SPARKS);
|
|
|
|
// get num_spark_pairs -- 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_local_to_global_point(&world_hitpos[spark_num], &shipp->sparks[spark_num].pos, pm, pmi, 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 Random::next(num_sparks);
|
|
}
|
|
|
|
// initialize spark pairs
|
|
// (we must always initialize at least one, because the default is the pair at index 0)
|
|
for (i=0; i<std::max(1,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<num_spark_pairs; 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(const object *ship_objp, const vec3d *hitpos, int submodel_num)
|
|
{
|
|
vec3d tempv;
|
|
ship *shipp = &Ships[ship_objp->instance];
|
|
ship_info *sip = &Ship_info[shipp->ship_info_index];
|
|
polymodel *pm = nullptr;
|
|
polymodel_instance *pmi = nullptr;
|
|
|
|
int n, max_sparks;
|
|
|
|
n = shipp->num_sparks;
|
|
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 = Random::next(max_sparks);
|
|
}
|
|
} else {
|
|
shipp->num_sparks++;
|
|
}
|
|
|
|
bool instancing = false;
|
|
// decide whether to do instancing
|
|
if (submodel_num != -1) {
|
|
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;
|
|
pmi = model_get_instance(shipp->model_instance_num);
|
|
}
|
|
}
|
|
|
|
if (instancing) {
|
|
// get the hit position in the subobject RF
|
|
vec3d temp_zero, temp_x, temp_y, temp_z;
|
|
model_instance_local_to_global_point(&temp_zero, &vmd_zero_vector, pm, pmi, submodel_num, &ship_objp->orient, &ship_objp->pos);
|
|
model_instance_local_to_global_point(&temp_x, &vmd_x_vector, pm, pmi, submodel_num, &ship_objp->orient, &ship_objp->pos);
|
|
model_instance_local_to_global_point(&temp_y, &vmd_y_vector, pm, pmi, submodel_num, &ship_objp->orient, &ship_objp->pos);
|
|
model_instance_local_to_global_point(&temp_z, &vmd_z_vector, pm, pmi, 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(const 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.
|
|
const object *other_objp = nullptr;
|
|
|
|
// 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:
|
|
UNREACHABLE("Unhandled object type %d in player_died_start()", killer_objp->type); // 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 != nullptr);
|
|
|
|
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_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);
|
|
objp->phys_info.gravity_const = sip->dying_gravity_const;
|
|
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 explosion? Check the ship type (objecttypes.tbl) as well as the ship (ships.tbl)
|
|
float skipChance;
|
|
if (sp->flags[Ship::Ship_Flags::Vaporize])
|
|
skipChance = 1.0f;
|
|
else if (sip->skip_deathroll_chance > 0.0f)
|
|
skipChance = sip->skip_deathroll_chance;
|
|
else if (sip->class_type >= 0)
|
|
skipChance = Ship_types[sip->class_type].skip_deathroll_chance;
|
|
else
|
|
skipChance = 0.0f;
|
|
|
|
if (frand() < skipChance)
|
|
{
|
|
// LIVE FOR 100 MS
|
|
sp->final_death_time = timestamp(100);
|
|
}
|
|
|
|
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
|
|
auto snd_id = ship_get_sound(objp, GameSounds::DEATH_ROLL);
|
|
if (snd_id.isValid())
|
|
sp->death_roll_snd = snd_play_3d( gamesnd_get_game_sound(snd_id), &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;
|
|
}
|
|
|
|
rotvel_mag *= sip->death_roll_rotation_mult;
|
|
|
|
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, sip->death_roll_xrotation_cap);
|
|
sp->deathroll_rotvel.xyz.y += (frand() - 0.5f) * 3.0f * rotvel_mag;
|
|
saturate_fabs(&sp->deathroll_rotvel.xyz.y, sip->death_roll_yrotation_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, sip->death_roll_zrotation_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 != nullptr);
|
|
if (shipp == nullptr) {
|
|
return;
|
|
}
|
|
Assert((shipp->objnum >= 0) && (shipp->objnum < MAX_OBJECTS));
|
|
if ( (shipp->objnum < 0) || (shipp->objnum >= MAX_OBJECTS) ) {
|
|
return;
|
|
}
|
|
ship_objp = &Objects[shipp->objnum];
|
|
ship_info* sip = &Ship_info[shipp->ship_info_index];
|
|
|
|
// create debris shards
|
|
if ( !(sip->flags[Ship::Info_Flags::Disable_all_generic_explosion_debris]) ) {
|
|
create_generic_debris(ship_objp, &ship_objp->pos, (float)sip->generic_debris_spew_num, sip->generic_debris_spew_num * 2.0f, 1.4f, true);
|
|
}
|
|
}
|
|
|
|
// *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, const vec3d *hitpos, float percent_killed, bool self_destruct, bool always_log_other_obj)
|
|
{
|
|
Assert(ship_objp); // Goober5000 - but not other_obj, not only for sexp but also for self-destruct
|
|
ship *sp = &Ships[ship_objp->instance];
|
|
|
|
// don't kill the ship if it's already dying
|
|
if (sp->flags[Ship::Ship_Flags::Dying])
|
|
return;
|
|
|
|
if (scripting::hooks::OnShipDeathStarted->isActive())
|
|
{
|
|
// add scripting hook for 'On Ship Death Started' -- Goober5000
|
|
// hook is placed at the beginning of this function to allow the scripter to
|
|
// actually have access to the ship before any death routines (such as mission logging) are executed
|
|
scripting::hooks::OnShipDeathStarted->run(scripting::hooks::ShipDeathConditions{ sp },
|
|
scripting::hook_param_list(
|
|
scripting::hook_param("Ship", 'o', ship_objp),
|
|
scripting::hook_param("Killer", 'o', other_obj),
|
|
scripting::hook_param("Hitpos",
|
|
'o',
|
|
scripting::api::l_Vector.Set(hitpos ? *hitpos : vmd_zero_vector),
|
|
hitpos != nullptr)));
|
|
}
|
|
|
|
// if the OnDeath override is enabled, run the hook and then exit
|
|
if (scripting::hooks::OnDeath->isActive())
|
|
{
|
|
auto onDeathParamList = scripting::hook_param_list(scripting::hook_param("Self", 'o', ship_objp),
|
|
scripting::hook_param("Ship", 'o', ship_objp),
|
|
scripting::hook_param("Killer", 'o', other_obj),
|
|
scripting::hook_param("Hitpos",
|
|
'o',
|
|
scripting::api::l_Vector.Set(hitpos ? *hitpos : vmd_zero_vector),
|
|
hitpos != nullptr));
|
|
|
|
if (scripting::hooks::OnDeath->isOverride(scripting::hooks::ObjectDeathConditions{ ship_objp }, onDeathParamList)) {
|
|
scripting::hooks::OnDeath->run(scripting::hooks::ObjectDeathConditions{ ship_objp }, std::move(onDeathParamList));
|
|
return;
|
|
}
|
|
}
|
|
|
|
// if the OnShipDeath override is enabled, run the hook and then exit
|
|
if (scripting::hooks::OnShipDeath->isActive())
|
|
{
|
|
auto onDeathParamList = scripting::hook_param_list(
|
|
scripting::hook_param("Ship", 'o', ship_objp),
|
|
scripting::hook_param("Killer", 'o', other_obj),
|
|
scripting::hook_param("Hitpos",
|
|
'o',
|
|
scripting::api::l_Vector.Set(hitpos ? *hitpos : vmd_zero_vector),
|
|
hitpos != nullptr));
|
|
|
|
if (scripting::hooks::OnShipDeath->isOverride(scripting::hooks::ShipDeathConditions{ sp }, onDeathParamList)) {
|
|
scripting::hooks::OnShipDeath->run(scripting::hooks::ShipDeathConditions{ sp }, onDeathParamList);
|
|
return;
|
|
}
|
|
}
|
|
|
|
char *killer_ship_name;
|
|
int killer_damage_percent = 0;
|
|
int killer_index = -1;
|
|
object *killer_objp = NULL;
|
|
|
|
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(const object *objp, const 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) ) {
|
|
// don't skip should-be-dead objects here
|
|
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);
|
|
}
|
|
|
|
// are we going to insist that other_obj was the killer?
|
|
if (always_log_other_obj && other_obj) {
|
|
auto named_objp = other_obj;
|
|
|
|
if (named_objp->type != OBJ_SHIP && named_objp->parent >= 0) {
|
|
named_objp = &Objects[other_obj->parent];
|
|
}
|
|
if (named_objp->type == OBJ_SHIP) {
|
|
killer_objp = nullptr;
|
|
killer_ship_name = Ships[named_objp->instance].ship_name;
|
|
killer_damage_percent = (int)(percent_killed * 100.0f);
|
|
}
|
|
}
|
|
|
|
if (self_destruct) {
|
|
// try and find a player
|
|
if(Game_mode & GM_MULTIPLAYER){
|
|
int np_index = multi_find_player_by_object(ship_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[ship_objp->instance].ship_name, NULL );
|
|
}
|
|
} else {
|
|
mission_log_add_entry(LOG_SELF_DESTRUCTED, Ships[ship_objp->instance].ship_name, NULL );
|
|
}
|
|
} else {
|
|
// 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 != nullptr) && (other_obj->parent >= 0) ) {
|
|
if (other_obj->parent_sig == Player_obj->signature) {
|
|
ship_maybe_praise_player(sp);
|
|
} else if (Objects[other_obj->parent].type == OBJ_SHIP) {
|
|
ship_maybe_praise_self(sp, &Ships[Objects[other_obj->parent].instance]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Goober5000 - since we added a mission log entry above, immediately set the status. For destruction, ship_cleanup isn't called until a little bit later
|
|
auto entry = &Ship_registry[Ship_registry_map[sp->ship_name]];
|
|
entry->status = ShipStatus::DEATH_ROLL;
|
|
|
|
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();
|
|
}
|
|
|
|
if (scripting::hooks::OnDeath->isActive()) {
|
|
auto onDeathParamList = scripting::hook_param_list(scripting::hook_param("Self", 'o', ship_objp),
|
|
scripting::hook_param("Ship", 'o', ship_objp),
|
|
scripting::hook_param("Killer", 'o', other_obj),
|
|
scripting::hook_param("Hitpos",
|
|
'o',
|
|
scripting::api::l_Vector.Set(hitpos ? *hitpos : vmd_zero_vector),
|
|
hitpos != nullptr));
|
|
|
|
scripting::hooks::OnDeath->run(scripting::hooks::ObjectDeathConditions{ ship_objp }, onDeathParamList);
|
|
}
|
|
if (scripting::hooks::OnShipDeath->isActive()) {
|
|
auto onDeathParamList = scripting::hook_param_list(
|
|
scripting::hook_param("Ship", 'o', ship_objp),
|
|
scripting::hook_param("Killer", 'o', other_obj),
|
|
scripting::hook_param("Hitpos",
|
|
'o',
|
|
scripting::api::l_Vector.Set(hitpos ? *hitpos : vmd_zero_vector),
|
|
hitpos != nullptr));
|
|
|
|
scripting::hooks::OnShipDeath->run(scripting::hooks::ShipDeathConditions{ sp }, onDeathParamList);
|
|
}
|
|
}
|
|
|
|
// function to simply explode a ship where it is currently at
|
|
void ship_self_destruct( object *objp )
|
|
{
|
|
Assert ( objp->type == OBJ_SHIP );
|
|
|
|
// don't self-destruct if this ship is already dying
|
|
if (Ships[objp->instance].flags[Ship::Ship_Flags::Dying])
|
|
return;
|
|
|
|
// 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, nullptr, nullptr, 1.0f, true);
|
|
}
|
|
|
|
// Call this instead of physics_apply_whack directly to
|
|
// deal with two docked ships properly.
|
|
// Goober5000 - note... hit_pos is in *world* coordinates
|
|
void ship_apply_whack(const vec3d *force, const vec3d *hit_pos, object *objp)
|
|
{
|
|
Assertion((objp != nullptr) && (force != nullptr) && (hit_pos != nullptr), "ship_apply_whack invalid argument(s)");
|
|
|
|
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))
|
|
{
|
|
dock_calculate_and_apply_whack_docked_object(force, hit_pos, objp);
|
|
}
|
|
else
|
|
{
|
|
// this one needs local position but since it doesn't have the objp its easier to just do this now
|
|
vec3d rel_hit_pos;
|
|
vm_vec_sub(&rel_hit_pos, hit_pos, &objp->pos);
|
|
physics_calculate_and_apply_whack(force, &rel_hit_pos, &objp->phys_info, &objp->orient, &objp->phys_info.I_body_inv);
|
|
}
|
|
}
|
|
|
|
// 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(const object *ship_objp, const object *other_obj, float *damage)
|
|
{
|
|
ship_subsys *subsys;
|
|
ship *shipp;
|
|
weapon_info *wip;
|
|
ship_info *sip;
|
|
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;
|
|
}
|
|
|
|
shipp = &Ships[ship_objp->instance];
|
|
sip = &Ship_info[shipp->ship_info_index];
|
|
|
|
if (!sip->is_huge_ship()) {
|
|
return 0;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
// If the shockwave was caused by a weapon, then check if the weapon can deal lethal damage to the ship.
|
|
// If it cannot, then neither should the shockwave caused by the weapon be able to do the same.
|
|
// The code for this is copied from part of weapon_get_damage_scale.
|
|
int wp_index = shockwave_get_weapon_index(other_obj->instance);
|
|
if ((wp_index >= 0) && Weapon_shockwaves_respect_huge) {
|
|
wip = &Weapon_info[wp_index];
|
|
|
|
if (!(wip->wi_flags[Weapon::Info_Flags::Heals])) {
|
|
float hull_pct = get_hull_pct(ship_objp);
|
|
|
|
// First handle Supercap ships.
|
|
if ((sip->flags[Ship::Info_Flags::Supercap]) && !(wip->wi_flags[Weapon::Info_Flags::Supercap])) {
|
|
if (hull_pct <= 0.75f) {
|
|
*damage = 0.0f;
|
|
return 1;
|
|
}
|
|
else {
|
|
// If hull isn't below 3/4, then allow damage to be applied just like in weapon_get_damage_scale.
|
|
// SUPERCAP_DAMAGE_SCALE is defined in weapon.h.
|
|
max_damage *= SUPERCAP_DAMAGE_SCALE;
|
|
}
|
|
}
|
|
|
|
// Next handle big damage ships.
|
|
bool is_big_damage_ship = (sip->flags[Ship::Info_Flags::Big_damage]);
|
|
if (is_big_damage_ship && !(wip->hurts_big_ships())) {
|
|
if (hull_pct > 0.1f) {
|
|
max_damage *= hull_pct;
|
|
}
|
|
else {
|
|
*damage = 0.0f;
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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(const object *ship_objp, const object *weapon_obj, float damage);
|
|
static void ship_do_damage(object *ship_objp, object *other_obj, const vec3d *hitpos, float damage, int quadrant, int submodel_num, int damage_type_idx = -1, bool wash_damage = false, float hit_dot = 1.f, const vec3d* hit_normal = nullptr, const vec3d* local_hitpos = nullptr)
|
|
{
|
|
// mprintf(("doing damage\n"));
|
|
|
|
ship *shipp;
|
|
bool other_obj_is_weapon;
|
|
bool other_obj_is_beam;
|
|
bool other_obj_is_shockwave;
|
|
float difficulty_scale_factor = 1.0f;
|
|
|
|
Assertion(ship_objp, "No ship_objp in ship_do_damage!");
|
|
Assertion(hitpos, "No hitpos in ship_do_damage!");
|
|
|
|
Assertion(ship_objp->instance >= 0 && ship_objp->type == OBJ_SHIP, "invalid ship target in ship_do_damage");
|
|
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));
|
|
}
|
|
else
|
|
{
|
|
other_obj_is_weapon = 0;
|
|
other_obj_is_beam = 0;
|
|
other_obj_is_shockwave = 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);
|
|
}
|
|
|
|
// damage scaling due to big damage, supercap, etc
|
|
float damage_scale = 1.0f;
|
|
// if this is a weapon
|
|
if (other_obj_is_weapon){
|
|
// Cyborg - Coverity 1523515, this was the only place in ship_do_damage that we weren't checking weapon_info_index
|
|
Assertion(Weapons[other_obj->instance].weapon_info_index > -1, "Weapon info index in ship_do_damage was found to be a negative value of %d. Please report this to an SCP coder!", Weapons[other_obj->instance].weapon_info_index);
|
|
if (Weapons[other_obj->instance].weapon_info_index > -1){
|
|
damage_scale = weapon_get_damage_scale(&Weapon_info[Weapons[other_obj->instance].weapon_info_index], other_obj, ship_objp);
|
|
}
|
|
}
|
|
|
|
if (other_obj && other_obj->parent >= 0 && Objects[other_obj->parent].signature == other_obj->parent_sig) {
|
|
if(Objects[other_obj->parent].flags[Object::Object_Flags::Player_ship])
|
|
difficulty_scale_factor *= The_mission.ai_profile->player_damage_inflicted_scale[Game_skill_level];
|
|
}
|
|
|
|
MONITOR_INC( ShipHits, 1 );
|
|
|
|
std::array<std::optional<ConditionData>, NumHitTypes> impact_data = {};
|
|
|
|
// Don't damage player ship in the process of warping out.
|
|
if ( Player->control_mode >= PCM_WARPOUT_STAGE2 ) {
|
|
if ( ship_objp == Player_obj ){
|
|
if (!global_damage) {
|
|
maybe_play_conditional_impacts(impact_data, other_obj, ship_objp, true, submodel_num, hitpos, local_hitpos, hit_normal);
|
|
}
|
|
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
|
|
|
|
// check for nulls, check that the player is the one being hit, and check that we're actually doing the pain flash
|
|
if ((other_obj != nullptr) && (ship_objp == Player_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) < weapon_info_size()));
|
|
if (((Weapon_info[beam_get_weapon_info_index(other_obj)].subtype != WP_LASER) || special_check))
|
|
{
|
|
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 < weapon_info_size()));
|
|
if (((Weapon_info[Weapons[other_obj->instance].weapon_info_index].subtype != WP_LASER) || special_check))
|
|
{
|
|
ship_hit_pain(damage * difficulty_scale_factor, quadrant);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// If the ship is invulnerable, do nothing
|
|
if (ship_objp->flags[Object::Object_Flags::Invulnerable]) {
|
|
if (!global_damage) {
|
|
maybe_play_conditional_impacts(impact_data, other_obj, ship_objp, true, submodel_num, hitpos, local_hitpos, hit_normal);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// if ship is already dying, shorten deathroll.
|
|
if (shipp->flags[Ship::Ship_Flags::Dying]) {
|
|
if (quadrant >= 0 && !(ship_objp->flags[Object::Object_Flags::No_shields])) {
|
|
impact_data[static_cast<std::underlying_type_t<HitType>>(HitType::SHIELD)] = ConditionData {
|
|
ImpactCondition(shipp->shield_armor_type_idx),
|
|
HitType::SHIELD,
|
|
0.0f,
|
|
// we have to do this annoying thing where we reduce the shield health a bit because it turns out the last X percent of a shield doesn't matter
|
|
MAX(0.0f, ship_objp->shield_quadrant[quadrant] - ship_shield_hitpoint_threshold(ship_objp)),
|
|
shield_get_max_quad(ship_objp) - ship_shield_hitpoint_threshold(ship_objp),
|
|
};
|
|
} else {
|
|
impact_data[static_cast<std::underlying_type_t<HitType>>(HitType::HULL)] = ConditionData {
|
|
ImpactCondition(shipp->armor_type_idx),
|
|
HitType::HULL,
|
|
0.0f,
|
|
ship_objp->hull_strength,
|
|
shipp->ship_max_hull_strength,
|
|
};
|
|
}
|
|
if (!global_damage) {
|
|
maybe_play_conditional_impacts(impact_data, other_obj, ship_objp, true, submodel_num, hitpos, local_hitpos, hit_normal);
|
|
}
|
|
|
|
shiphit_hit_after_death(ship_objp, (damage * difficulty_scale_factor));
|
|
return;
|
|
}
|
|
|
|
int weapon_info_index = shiphit_get_damage_weapon(other_obj);
|
|
|
|
// 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_damage = damage * damage_scale;
|
|
|
|
auto shield_impact = ConditionData {
|
|
ImpactCondition(shipp->shield_armor_type_idx),
|
|
HitType::SHIELD,
|
|
0.0f,
|
|
// we have to do this annoying thing where we reduce the shield health a bit because it turns out the last X percent of a shield doesn't matter
|
|
MAX(0.0f, ship_objp->shield_quadrant[quadrant] - ship_shield_hitpoint_threshold(ship_objp)),
|
|
shield_get_max_quad(ship_objp) - ship_shield_hitpoint_threshold(ship_objp),
|
|
};
|
|
|
|
if ( damage > 0.0f ) {
|
|
float piercing_pct = 0.0f;
|
|
|
|
// apply any armor types and the difficulty scaling
|
|
if(shipp->shield_armor_type_idx != -1) {
|
|
piercing_pct = Armor_types[shipp->shield_armor_type_idx].GetShieldPiercePCT(damage_type_idx);
|
|
// reduce shield damage by the piercing percent
|
|
shield_damage = shield_damage * (1.0f - piercing_pct);
|
|
|
|
// 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)
|
|
shield_damage = Armor_types[shipp->shield_armor_type_idx].GetDamage(shield_damage, damage_type_idx, difficulty_scale_factor, other_obj_is_beam);
|
|
} else {
|
|
shield_damage *= difficulty_scale_factor;
|
|
}
|
|
|
|
float shield_factor = 1.0f;
|
|
if (weapon_info_index >= 0 && (!other_obj_is_beam || Beams_use_damage_factors))
|
|
shield_factor = Weapon_info[weapon_info_index].shield_factor;
|
|
|
|
shield_impact.damage = shield_damage * shield_factor;
|
|
// apply shield damage
|
|
float remaining_damage = shield_apply_damage(ship_objp, quadrant, shield_damage * shield_factor);
|
|
// remove the shield factor, since the overflow will no longer be thrown at shields
|
|
remaining_damage /= shield_factor;
|
|
|
|
// Unless the backwards compatible flag is on, remove difficulty scaling as well
|
|
// The hull/subsystem code below will re-add it where necessary
|
|
if (!The_mission.ai_profile->flags[AI::Profile_Flags::Carry_shield_difficulty_scaling_bug])
|
|
remaining_damage /= difficulty_scale_factor;
|
|
|
|
// the rest of the damage is what overflowed from the shield damage and pierced
|
|
damage = remaining_damage + (damage * piercing_pct);
|
|
}
|
|
|
|
impact_data[static_cast<std::underlying_type_t<HitType>>(HitType::SHIELD)] = shield_impact;
|
|
}
|
|
|
|
// Apply leftover damage to the ship's subsystem and hull.
|
|
if ( (damage > 0.0f) ) {
|
|
bool apply_hull_armor = true;
|
|
|
|
bool shield_hit = quadrant >= 0;
|
|
|
|
// apply damage to subsystems, and get back any remaining damage that needs to go to the hull
|
|
auto damage_pair = do_subobj_hit_stuff(ship_objp, other_obj, hitpos, submodel_num, damage, &apply_hull_armor, hit_dot, shield_hit);
|
|
|
|
damage = damage_pair.second;
|
|
|
|
impact_data[static_cast<std::underlying_type_t<HitType>>(HitType::SUBSYS)] = damage_pair.first;
|
|
|
|
// damage scaling doesn't apply to subsystems, but it does to the hull
|
|
damage *= damage_scale;
|
|
|
|
// Do armor stuff
|
|
if (apply_hull_armor && shipp->armor_type_idx != -1) {
|
|
damage = Armor_types[shipp->armor_type_idx].GetDamage(damage, damage_type_idx, difficulty_scale_factor, other_obj_is_beam);
|
|
} else {
|
|
damage *= difficulty_scale_factor;
|
|
}
|
|
|
|
// if weapon is vampiric, slap healing on shooter instead of target
|
|
if (weapon_info_index >= 0) {
|
|
weapon_info* wip = &Weapon_info[weapon_info_index];
|
|
|
|
if ((wip->wi_flags[Weapon::Info_Flags::Vampiric]) && (other_obj->parent > 0)) {
|
|
object* parent = &Objects[other_obj->parent];
|
|
|
|
if ((parent->type == OBJ_SHIP) && (parent->signature == other_obj->parent_sig)) {
|
|
ship* shipp_parent = &Ships[parent->instance];
|
|
|
|
if (!parent->flags[Object::Object_Flags::Should_be_dead]) {
|
|
parent->hull_strength += damage * wip->vamp_regen;
|
|
|
|
if (parent->hull_strength > shipp_parent->ship_max_hull_strength) {
|
|
parent->hull_strength = shipp_parent->ship_max_hull_strength;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// continue with damage?
|
|
if (damage > 0.0f) {
|
|
if ( weapon_info_index >= 0 && (!other_obj_is_beam || Beams_use_damage_factors)) {
|
|
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.0f, damage);
|
|
}
|
|
}
|
|
|
|
if (!shield_hit) {
|
|
impact_data[static_cast<std::underlying_type_t<HitType>>(HitType::HULL)] = ConditionData {
|
|
ImpactCondition(shipp->armor_type_idx),
|
|
HitType::HULL,
|
|
damage,
|
|
ship_objp->hull_strength,
|
|
shipp->ship_max_hull_strength,
|
|
};
|
|
}
|
|
|
|
// multiplayer clients don't do damage
|
|
if (((Game_mode & GM_MULTIPLAYER) && MULTIPLAYER_CLIENT)) {
|
|
} else {
|
|
// Check if this is simulated damage.
|
|
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.0f, 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, true);
|
|
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, hitpos, percent_killed);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!global_damage) {
|
|
maybe_play_conditional_impacts(impact_data, other_obj, ship_objp, true, submodel_num, hitpos, local_hitpos, hit_normal);
|
|
}
|
|
|
|
// handle weapon and afterburner leeching here
|
|
if(other_obj_is_weapon || other_obj_is_beam) {
|
|
Assert(weapon_info_index >= 0);
|
|
weapon_info* wip = &Weapon_info[weapon_info_index];
|
|
|
|
float mult = 1.0f;
|
|
if (other_obj_is_beam)
|
|
mult = flFrametime;
|
|
|
|
// 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 * mult;
|
|
shipp->afterburner_fuel = (shipp->afterburner_fuel < 0.0f) ? 0.0f : shipp->afterburner_fuel;
|
|
|
|
// reduce weapon energy
|
|
shipp->weapon_energy -= wip->weapon_reduce * mult;
|
|
shipp->weapon_energy = (shipp->weapon_energy < 0.0f) ? 0.0f : shipp->weapon_energy;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void ship_do_healing(object* ship_objp, const object* other_obj, const vec3d* hitpos, float healing, int quadrant, int submodel_num, int damage_type_idx = -1, const vec3d* hit_normal = nullptr, const vec3d* local_hitpos = nullptr)
|
|
{
|
|
// multiplayer clients dont do healing
|
|
if (MULTIPLAYER_CLIENT)
|
|
return;
|
|
|
|
ship* shipp;
|
|
bool other_obj_is_weapon, other_obj_is_beam, other_obj_is_shockwave;
|
|
|
|
Assertion(ship_objp, "No ship_objp in ship_do_healing!");
|
|
Assertion(other_obj, "No other_obj in ship_do_healing!");
|
|
Assertion(hitpos, "No hitpos in ship_do_healing!");
|
|
|
|
Assertion(ship_objp->instance >= 0 && ship_objp->type == OBJ_SHIP, "invalid ship target in ship_do_healing");
|
|
shipp = &Ships[ship_objp->instance];
|
|
|
|
Assert(other_obj->type == OBJ_WEAPON || other_obj->type == OBJ_BEAM || other_obj->type == OBJ_SHOCKWAVE);
|
|
|
|
// maybe adjust "damage" done by shockwave for BIG|HUGE
|
|
maybe_shockwave_damage_adjust(ship_objp, other_obj, &healing);
|
|
|
|
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));
|
|
|
|
MONITOR_INC(ShipHits, 1);
|
|
|
|
std::array<std::optional<ConditionData>, NumHitTypes> impact_data = {};
|
|
|
|
// Don't heal player ship in the process of warping out.
|
|
if ((Player->control_mode >= PCM_WARPOUT_STAGE2) && (ship_objp == Player_obj)) {
|
|
if (!global_damage) {
|
|
maybe_play_conditional_impacts(impact_data, other_obj, ship_objp, true, submodel_num, hitpos, local_hitpos, hit_normal);
|
|
}
|
|
return;
|
|
}
|
|
|
|
int wip_index = -1;
|
|
if (other_obj_is_weapon)
|
|
wip_index = Weapons[other_obj->instance].weapon_info_index;
|
|
else if (other_obj_is_beam)
|
|
wip_index = Beams[other_obj->instance].weapon_info_index;
|
|
else if (other_obj_is_shockwave)
|
|
wip_index = shockwave_get_weapon_index(other_obj->instance);
|
|
|
|
if (wip_index < 0)
|
|
return;
|
|
weapon_info* wip = &Weapon_info[wip_index];
|
|
|
|
float shield_health;
|
|
if (quadrant >= 0) {
|
|
shield_health = MAX(0.0f, ship_objp->shield_quadrant[quadrant] - ship_shield_hitpoint_threshold(ship_objp, false));
|
|
} else {
|
|
//if we haven't hit a shield, assume the shield is fully depleted, because we have no way of knowing what the relevant quadrant would be
|
|
shield_health = 0.f;
|
|
}
|
|
|
|
// handle shield healing
|
|
if (!(ship_objp->flags[Object::Object_Flags::No_shields])) {
|
|
auto shield_impact = ConditionData {
|
|
ImpactCondition(shipp->shield_armor_type_idx),
|
|
HitType::SHIELD,
|
|
0.0f,
|
|
shield_health,
|
|
shield_get_max_quad(ship_objp) - ship_shield_hitpoint_threshold(ship_objp, false),
|
|
};
|
|
|
|
float shield_healing = healing * wip->shield_factor;
|
|
|
|
if (shield_healing > 0.0f) {
|
|
if (shipp->shield_armor_type_idx != -1)
|
|
shield_healing = Armor_types[shipp->shield_armor_type_idx].GetDamage(shield_healing, damage_type_idx, 1.0f, other_obj_is_beam);
|
|
|
|
shield_impact.damage = shield_healing;
|
|
shield_apply_healing(ship_objp, shield_healing);
|
|
}
|
|
impact_data[static_cast<std::underlying_type_t<HitType>>(HitType::SHIELD)] = shield_impact;
|
|
}
|
|
|
|
// now for subsystems and hull
|
|
if ((healing > 0.0f)) {
|
|
|
|
auto healing_pair = do_subobj_heal_stuff(ship_objp, other_obj, hitpos, submodel_num, healing);
|
|
|
|
healing = healing_pair.second;
|
|
|
|
impact_data[static_cast<std::underlying_type_t<HitType>>(HitType::SUBSYS)] = healing_pair.first;
|
|
|
|
//Do armor stuff
|
|
if (shipp->armor_type_idx != -1)
|
|
healing = Armor_types[shipp->armor_type_idx].GetDamage(healing, damage_type_idx, 1.0f, other_obj_is_beam);
|
|
|
|
if (wip->wi_flags[Weapon::Info_Flags::Puncture])
|
|
healing /= 4;
|
|
|
|
healing *= wip->armor_factor;
|
|
|
|
impact_data[static_cast<std::underlying_type_t<HitType>>(HitType::HULL)] = ConditionData {
|
|
ImpactCondition(shipp->armor_type_idx),
|
|
HitType::HULL,
|
|
healing,
|
|
ship_objp->hull_strength,
|
|
shipp->ship_max_hull_strength,
|
|
};
|
|
|
|
ship_objp->hull_strength += healing;
|
|
if (ship_objp->hull_strength > shipp->ship_max_hull_strength)
|
|
ship_objp->hull_strength = shipp->ship_max_hull_strength;
|
|
}
|
|
|
|
maybe_play_conditional_impacts(impact_data, other_obj, ship_objp, true, submodel_num, hitpos, local_hitpos, hit_normal);
|
|
|
|
// fix up the ship's sparks :)
|
|
// turn off a random spark, if its a beam, do this on average twice a second
|
|
if(!other_obj_is_beam || frand() > flFrametime * 2.0f )
|
|
shipp->sparks[Random::next(MAX_SHIP_SPARKS)].end_time = timestamp(0);
|
|
|
|
// if we brought it to full health, fix ALL sparks
|
|
if (ship_objp->hull_strength == shipp->ship_max_hull_strength) {
|
|
for(auto & spark : shipp->sparks )
|
|
spark.end_time = timestamp(0);
|
|
}
|
|
|
|
// handle weapon and afterburner leeching here
|
|
if (other_obj_is_weapon || other_obj_is_beam) {
|
|
float mult = 1.0f;
|
|
if (other_obj_is_beam)
|
|
mult = flFrametime;
|
|
|
|
// 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 * mult;
|
|
shipp->afterburner_fuel = (shipp->afterburner_fuel < 0.0f) ? 0.0f : shipp->afterburner_fuel;
|
|
|
|
// reduce weapon energy
|
|
shipp->weapon_energy -= wip->weapon_reduce * mult;
|
|
shipp->weapon_energy = (shipp->weapon_energy < 0.0f) ? 0.0f : shipp->weapon_energy;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Goober5000
|
|
// (it might be possible to make `target` const, but that would set off another const-cascade)
|
|
void ship_apply_tag(ship *shipp, int tag_level, float tag_time, object *target, const vec3d *start, int ssm_index, int ssm_team)
|
|
{
|
|
// set time first tagged
|
|
if (shipp->time_first_tagged == 0)
|
|
shipp->time_first_tagged = Missiontime;
|
|
|
|
// do tag effect
|
|
if (tag_level == 1)
|
|
{
|
|
// mprintf(("TAGGED %s for %f seconds\n", shipp->ship_name, tag_time));
|
|
shipp->tag_left = tag_time;
|
|
shipp->tag_total = tag_time;
|
|
}
|
|
else if (tag_level == 2)
|
|
{
|
|
// mprintf(("Level 2 TAGGED %s for %f seconds\n", shipp->ship_name, tag_time));
|
|
shipp->level2_tag_left = tag_time;
|
|
shipp->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.
|
|
// (it might be possible to make `other_obj` const, but that would set off another const-cascade)
|
|
void ship_apply_local_damage(object *ship_objp, object *other_obj, const vec3d *hitpos, float damage, int damage_type_idx, int quadrant, bool create_spark, int submodel_num, const vec3d *hit_normal, float hit_dot, const vec3d* local_hitpos)
|
|
{
|
|
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]) ) {
|
|
// need to play the impact effect(s) for the weapon if we have one, since we won't get the chance to do it later
|
|
// we won't account for subsystems; that's a lot of extra logic for little benefit in this edge case
|
|
std::array<std::optional<ConditionData>, NumHitTypes> impact_data = {};
|
|
if (quadrant >= 0 && !(ship_objp->flags[Object::Object_Flags::No_shields])) {
|
|
impact_data[static_cast<std::underlying_type_t<HitType>>(HitType::SHIELD)] = ConditionData {
|
|
ImpactCondition(ship_p->shield_armor_type_idx),
|
|
HitType::SHIELD,
|
|
0.0f,
|
|
// we have to do this annoying thing where we reduce the shield health a bit because it turns out the last X percent of a shield doesn't matter
|
|
MAX(0.0f, ship_objp->shield_quadrant[quadrant] - ship_shield_hitpoint_threshold(ship_objp)),
|
|
shield_get_max_quad(ship_objp) - ship_shield_hitpoint_threshold(ship_objp),
|
|
};
|
|
} else {
|
|
impact_data[static_cast<std::underlying_type_t<HitType>>(HitType::HULL)] = ConditionData {
|
|
ImpactCondition(ship_p->armor_type_idx),
|
|
HitType::HULL,
|
|
0.0f,
|
|
ship_objp->hull_strength,
|
|
ship_p->ship_max_hull_strength,
|
|
};
|
|
}
|
|
maybe_play_conditional_impacts(impact_data, other_obj, ship_objp, true, submodel_num, hitpos, local_hitpos, hit_normal);
|
|
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, 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]) {
|
|
ship_apply_tag(ship_p, wip->tag_level, wip->tag_time, ship_objp, hitpos, wip->SSM_index, wp->team);
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
int wip_index = -1;
|
|
if (other_obj->type == OBJ_WEAPON)
|
|
wip_index = Weapons[other_obj->instance].weapon_info_index;
|
|
else if (other_obj->type == OBJ_BEAM)
|
|
wip_index = Beams[other_obj->instance].weapon_info_index;
|
|
else if (other_obj->type == OBJ_SHOCKWAVE)
|
|
wip_index = shockwave_get_weapon_index(other_obj->instance);
|
|
|
|
global_damage = false;
|
|
if (wip_index >= 0 && Weapon_info[wip_index].wi_flags[Weapon::Info_Flags::Heals]) {
|
|
ship_do_healing(ship_objp, other_obj, hitpos, damage, quadrant, submodel_num, -1, hit_normal, local_hitpos);
|
|
create_sparks = false;
|
|
} else {
|
|
ship_do_damage(ship_objp, other_obj, hitpos, damage, quadrant, submodel_num, damage_type_idx, false, hit_dot, hit_normal, local_hitpos);
|
|
}
|
|
|
|
// 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] || wip->wi_flags[Weapon::Info_Flags::No_impact_spew]) {
|
|
create_sparks = false;
|
|
}
|
|
}
|
|
|
|
if (create_sparks) {
|
|
auto subsys = ship_get_subsys_for_submodel(ship_p, submodel_num);
|
|
|
|
if (subsys != nullptr && subsys->system_info->flags[Model::Subsystem_Flags::No_sparks]) {
|
|
// Spark creation was explicitly disabled for this subsystem
|
|
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, const vec3d *force_center, float damage, int damage_type_idx)
|
|
{
|
|
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);
|
|
|
|
// world_hitpos use force_center for shockwave
|
|
// Goober5000 check for NULL
|
|
if (other_obj && (other_obj->type == OBJ_SHOCKWAVE) && (Ship_info[Ships[ship_objp->instance].ship_info_index].is_huge_ship()))
|
|
world_hitpos = *force_center;
|
|
|
|
int wip_index = -1;
|
|
if(other_obj != nullptr && other_obj->type == OBJ_SHOCKWAVE)
|
|
wip_index = shockwave_get_weapon_index(other_obj->instance);
|
|
|
|
// the healing case should only ever be true for shockwaves
|
|
if (wip_index >= 0 && Weapon_info[wip_index].wi_flags[Weapon::Info_Flags::Heals])
|
|
ship_do_healing(ship_objp, other_obj, &world_hitpos, damage, -1, damage_type_idx);
|
|
else
|
|
// Do damage on local point
|
|
ship_do_damage(ship_objp, other_obj, &world_hitpos, damage, shield_quad, -1 , damage_type_idx);
|
|
} 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 );
|
|
|
|
int n_quadrants = static_cast<int>(ship_objp->shield_quadrant.size());
|
|
for (int i=0; i<n_quadrants; i++){
|
|
ship_do_damage(ship_objp, other_obj, &world_hitpos, damage/n_quadrants, i, -1, damage_type_idx);
|
|
}
|
|
}
|
|
|
|
// 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, true);
|
|
|
|
// 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]))
|
|
{
|
|
int pain_type;
|
|
if (Shield_pain_flash_factor != 0.0f && quadrant >= 0)
|
|
{
|
|
float effect = (Shield_pain_flash_factor * Player_obj->shield_quadrant[quadrant] * static_cast<int>(Player_obj->shield_quadrant.size())) / 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);
|
|
pain_type = 0;
|
|
}
|
|
else
|
|
{
|
|
game_flash(damage * Generic_pain_flash_factor / 15.0f, -damage * Generic_pain_flash_factor / 30.0f, -damage * Generic_pain_flash_factor / 30.0f);
|
|
pain_type = 1;
|
|
}
|
|
|
|
if (OnPainFlashHook->isActive())
|
|
{
|
|
// add scripting hook for 'On Pain Flash' --wookieejedi
|
|
OnPainFlashHook->run(scripting::hook_param_list(scripting::hook_param("Pain_Type", 'i', pain_type)));
|
|
}
|
|
}
|
|
|
|
// kill any active popups when you get hit.
|
|
if ( Game_mode & GM_MULTIPLAYER ){
|
|
popup_kill_any_active();
|
|
}
|
|
}
|