/* * 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 #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_interpolate.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> 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; } void check_subsystem_submodel_link(const ship *shipp, const ship_subsys *subsys, bool was_destroyed) { if (!Link_special_point_subsystems_to_destroyed_submodels) return; Assertion(shipp && subsys, "the ship and subsystem must exist!"); auto pmi = model_get_instance(shipp->model_instance_num); Assertion(pmi, "the ship's model instance must exist!"); // check subsystem-submodel link, but only for special-point subsystems // (not subsystems corresponding to a submodel) auto psub = subsys->system_info; if (psub->subobj_num >= 0) return; int j = submodel_find_destroyed_form(pmi->model_num, psub->subobj_name); if (j < 0) return; // show the submodel, or not, depending on what happened to the subsystem pmi->submodel[j].blown_off = !was_destroyed; } void restore_subsystem_submodels(const ship *shipp, const ship_subsys *subsys) { Assertion(shipp && subsys, "the ship and subsystem must exist!"); // any -destroyed form will be hidden when the submodels are next replicated if (subsys->submodel_instance_1) subsys->submodel_instance_1->blown_off = false; if (subsys->submodel_instance_2) subsys->submodel_instance_2->blown_off = false; // special case for subsystems that don't correspond to a submodel check_subsystem_submodel_link(shipp, subsys, false); } void blow_off_subsystem_submodels(const ship *shipp, const ship_subsys *subsys) { Assertion(shipp && subsys, "the ship and subsystem must exist!"); if (subsys->flags[Ship::Subsystem_Flags::No_disappear]) return; // any -destroyed form will be shown when the submodels are next replicated if (subsys->submodel_instance_1) subsys->submodel_instance_1->blown_off = true; if (subsys->submodel_instance_2) subsys->submodel_instance_2->blown_off = true; // special case for subsystems that don't correspond to a submodel check_subsystem_submodel_link(shipp, subsys, true); } bool subsystem_submodels_blown_off(const ship *shipp, const ship_subsys *subsys) { Assertion(shipp && subsys, "the ship and subsystem must exist!"); if (subsys->submodel_instance_1) return subsys->submodel_instance_1->blown_off; // special case for subsystems that don't correspond to a submodel if (!Link_special_point_subsystems_to_destroyed_submodels) return false; auto pmi = model_get_instance(shipp->model_instance_num); Assertion(pmi, "the ship's model instance must exist!"); int j = submodel_find_destroyed_form(pmi->model_num, subsys->system_info->subobj_name); return (j >= 0) && !pmi->submodel[j].blown_off; } // 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.none_of(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(std::make_unique(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; iradius); } // 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]) && (psub->subobj_num > -1)) { shipfx_blow_off_subsystem(ship_objp, ship_p, subsys, &g_subobj_pos, no_explosion, no_fireballs); } blow_off_subsystem_submodels(ship_p, subsys); 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.set(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; } 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, 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; SCP_vector 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 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. for (auto subsys : list_range(&ship_p->subsys_list)) { 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 = sz2i(subsys_list.size()); } 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.push_back({ dist, range, subsys }); } } } 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 count = sz2i(subsys_list.size()); for (int 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 (int i=0; i= 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, 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; SCP_vector 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 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. for (auto subsys : list_range(&ship_p->subsys_list)) { 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 = sz2i(subsys_list.size()); } 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.push_back({ dist, range, subsys }); } } } 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 count = sz2i(subsys_list.size()); for (int j=0; j= 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_PROP: p->killer_objtype = OBJ_PROP; p->killer_weapon_index = -1; strcpy_s(p->killer_parent_name, ""); p->killer_species = -1; p->killer_parent_name[0] = '\0'; 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_numsparks[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 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 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_PROP: 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->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 }, std::move(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 ((Game_mode & GM_MULTIPLAYER) && (objp->type == OBJ_SHIP)) { // temporarily set this as an uninterpolated ship, to make the collision // look more natural until the next update comes in. Interp_info[OBJ_INDEX(objp)].force_interpolation_mode(); } 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; } // The shield, hull and per-subsystem strength of a ship, recorded before a ship_do_damage() call. Only filled in while // the On Ship Damage hook is active. struct ship_damage_snapshot { float hull_strength = 0.0f; float shield_strength = 0.0f; SCP_vector> subsystem_hits; }; static void ship_take_damage_snapshot(object *ship_objp, ship_damage_snapshot &snapshot) { ship *shipp = &Ships[ship_objp->instance]; snapshot.hull_strength = ship_objp->hull_strength; snapshot.shield_strength = shield_get_strength(ship_objp); snapshot.subsystem_hits.clear(); for (ship_subsys *subsys = GET_FIRST(&shipp->subsys_list); subsys != END_OF_LIST(&shipp->subsys_list); subsys = GET_NEXT(subsys)) { snapshot.subsystem_hits.emplace_back(subsys, subsys->current_hits); } } // Runs the On Ship Damage hook for one ship_do_damage() call, reporting what the ship lost since the snapshot. // Does nothing if the ship lost none of its shield, hull or subsystem strength. static void ship_run_damage_hook(object *ship_objp, object *other_obj, int weapon_info_index, int damage_type_idx, int quadrant, DamageSource source, bool wash_damage, const ship_damage_snapshot &before) { const float shield_damage = MAX(0.0f, before.shield_strength - shield_get_strength(ship_objp)); const float hull_damage = MAX(0.0f, MAX(0.0f, before.hull_strength) - MAX(0.0f, ship_objp->hull_strength)); bool subsystem_hit = false; for (const auto &entry : before.subsystem_hits) { if (entry.first->current_hits < entry.second) { subsystem_hit = true; break; } } if (shield_damage <= 0.0f && hull_damage <= 0.0f && !subsystem_hit) { return; } const bool lethal_hit = before.hull_strength > 0.0f && ship_objp->hull_strength <= 0.0f; // the subsystems that lost strength, keyed by canonical subsystem name luacpp::LuaTable subsystem_losses = luacpp::LuaTable::create(Script_system.GetLuaSession()); for (const auto &entry : before.subsystem_hits) { const float lost = entry.second - entry.first->current_hits; if (lost > 0.0f) { subsystem_losses.addValue(ship_subsys_get_canonical_name(entry.first), lost); } } const weapon_info *wip = Weapon_info.in_bounds(weapon_info_index) ? &Weapon_info[weapon_info_index] : nullptr; if (!Damage_types.in_bounds(damage_type_idx) && wip != nullptr) { damage_type_idx = wip->damage_type_idx; } const char *damage_type = Damage_types.in_bounds(damage_type_idx) ? Damage_types[damage_type_idx].name : "UNKNOWN"; const char *source_kind = "OTHER"; if (source == DamageSource::Collision) { source_kind = "COLLISION"; } else if (source == DamageSource::Explosion) { source_kind = "EXPLOSION"; } else if (wash_damage) { source_kind = "WASH"; } else if (other_obj == ship_objp) { source_kind = "SELF"; } else if (other_obj != nullptr) { switch (other_obj->type) { case OBJ_WEAPON: source_kind = "NON_BEAM_WEAPON"; break; case OBJ_BEAM: source_kind = "BEAM"; break; case OBJ_SHOCKWAVE: source_kind = "SHOCKWAVE"; break; default: break; } } object *attacker_objp = nullptr; if (other_obj != nullptr) { if (other_obj->type == OBJ_BEAM) { int parent_objnum = beam_get_parent(other_obj); if (parent_objnum >= 0) { attacker_objp = &Objects[parent_objnum]; } } else if (other_obj->type == OBJ_SHIP) { attacker_objp = other_obj; } else if (other_obj->parent >= 0 && other_obj->parent < MAX_OBJECTS && Objects[other_obj->parent].signature == other_obj->parent_sig) { attacker_objp = &Objects[other_obj->parent]; } } scripting::hooks::OnShipDamage->run(scripting::hooks::ShipDamageConditions{ &Ships[ship_objp->instance], weapon_info_index }, scripting::hook_param_list( scripting::hook_param("Target", 'o', ship_objp), scripting::hook_param("Source", 'o', other_obj, other_obj != nullptr), scripting::hook_param("Attacker", 'o', attacker_objp, attacker_objp != nullptr), scripting::hook_param("SourceKind", 's', source_kind), scripting::hook_param("DamageType", 's', damage_type), scripting::hook_param("ShieldDamage", 'f', shield_damage), scripting::hook_param("HullDamage", 'f', hull_damage), scripting::hook_param("SubsystemDamage", 't', subsystem_losses), scripting::hook_param("Quadrant", 'i', quadrant), scripting::hook_param("LethalHit", 'b', lethal_hit))); } // ------------------------------------------------------------------------ // 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, DamageSource source = DamageSource::Inferred) { // 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]; // snapshot for the On Ship Damage hook, which reports what the ship actually lost const bool damage_hook_active = scripting::hooks::OnShipDamage->isActive(); bool damage_hook_ran = false; ship_damage_snapshot damage_before; if (damage_hook_active) { ship_take_damage_snapshot(ship_objp, damage_before); } // 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, 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>(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>(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>(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>(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>(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); if (damage_hook_active) { ship_run_damage_hook(ship_objp, other_obj, weapon_info_index, damage_type_idx, quadrant, source, wash_damage, damage_before); } return; } } ship_objp->hull_strength -= damage; // a lethal hit runs the hook now, before the ship's death processing below if (damage_hook_active && ship_objp->hull_strength <= 0.0f) { ship_run_damage_hook(ship_objp, other_obj, weapon_info_index, damage_type_idx, quadrant, source, wash_damage, damage_before); damage_hook_ran = true; } } // 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 (damage_hook_active && !damage_hook_ran) { ship_run_damage_hook(ship_objp, other_obj, weapon_info_index, damage_type_idx, quadrant, source, wash_damage, damage_before); } 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, 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>(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>(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>(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, DamageSource source) { 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, NumHitTypes> impact_data = {}; if (quadrant >= 0 && !(ship_objp->flags[Object::Object_Flags::No_shields])) { impact_data[static_cast>(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>(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, source); } // 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, DamageSource source) { 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, false, 1.f, nullptr, nullptr, source); } 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(ship_objp->shield_quadrant.size()); for (int i=0; i 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(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(); } }