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