/* * 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 "freespace.h" #include "math/curve.h" #include "network/multi.h" #include "object/objcollide.h" #include "object/object.h" #include "scripting/global_hooks.h" #include "scripting/scripting.h" #include "scripting/api/objs/vecmath.h" #include "ship/ship.h" #include "stats/scoring.h" #include "weapon/weapon.h" /** * Checks weapon-weapon collisions. * @param pair obj_pair pointer to the two objects. pair->a and pair->b are weapons. * @return 1 if all future collisions between these can be ignored */ int collide_weapon_weapon( obj_pair * pair ) { float A_radius, B_radius; object *A = pair->a; object *B = pair->b; Assert( A->type == OBJ_WEAPON ); Assert( B->type == OBJ_WEAPON ); // Don't allow ship to shoot down its own missile. if (A->parent_sig == B->parent_sig) return 1; float dot = vm_vec_dot(&A->orient.vec.fvec, &B->orient.vec.fvec); // Only shoot down teammate's missile if not traveling in nearly same direction. if (Weapons[A->instance].team == Weapons[B->instance].team) if (dot > 0.7f) return 1; // Ignore collisions involving a bomb if the bomb is not yet armed. weapon *wpA, *wpB; weapon_info *wipA, *wipB; wpA = &Weapons[A->instance]; wpB = &Weapons[B->instance]; wipA = &Weapon_info[wpA->weapon_info_index]; wipB = &Weapon_info[wpB->weapon_info_index]; A_radius = A->radius; B_radius = B->radius; float A_time_alive = f2fl(Missiontime - wpA->creation_time); float B_time_alive = f2fl(Missiontime - wpB->creation_time); if (wipA->weapon_hitpoints > 0) { if (!(wipA->wi_flags[Weapon::Info_Flags::No_radius_doubling])) { A_radius *= 2; // Makes bombs easier to hit } // the erroneous extra time a bomb stays invulnerable without the fix float extra_buggy_time = 0.0f; if (!(The_mission.ai_profile->flags[AI::Profile_Flags::Aspect_invulnerability_fix]) && wipA->is_locked_homing()) extra_buggy_time = (wipA->lifetime * LOCKED_HOMING_EXTENDED_LIFE_FACTOR) - wipA->lifetime; if ((The_mission.ai_profile->flags[AI::Profile_Flags::Aspect_invulnerability_fix]) && (wipA->is_locked_homing()) && (wpA->homing_object != &obj_used_list)) { if (A_time_alive < The_mission.ai_profile->delay_bomb_arm_timer[Game_skill_level] ) return 0; } else if (A_time_alive - extra_buggy_time < The_mission.ai_profile->delay_bomb_arm_timer[Game_skill_level] ) return 0; } if (wipB->weapon_hitpoints > 0) { if (!(wipB->wi_flags[Weapon::Info_Flags::No_radius_doubling])) { B_radius *= 2; // Makes bombs easier to hit } // the erroneous extra time a bomb stays invulnerable without the fix float extra_buggy_time = 0.0f; if (!(The_mission.ai_profile->flags[AI::Profile_Flags::Aspect_invulnerability_fix]) && wipB->is_locked_homing()) extra_buggy_time = (wipB->lifetime * LOCKED_HOMING_EXTENDED_LIFE_FACTOR) - wipB->lifetime; if ((The_mission.ai_profile->flags[AI::Profile_Flags::Aspect_invulnerability_fix]) && (wipB->is_locked_homing()) && (wpB->homing_object != &obj_used_list)) { if (B_time_alive < The_mission.ai_profile->delay_bomb_arm_timer[Game_skill_level] ) return 0; } else if (B_time_alive - extra_buggy_time < The_mission.ai_profile->delay_bomb_arm_timer[Game_skill_level] ) return 0; } // Rats, do collision detection. if (collide_subdivide(&A->last_pos, &A->pos, A_radius, &B->last_pos, &B->pos, B_radius)) { bool a_override = false, b_override = false; if (scripting::hooks::OnWeaponCollision->isActive()) { a_override = scripting::hooks::OnWeaponCollision->isOverride(scripting::hooks::CollisionConditions{ {A, B} }, scripting::hook_param_list(scripting::hook_param("Self", 'o', A), scripting::hook_param("Object", 'o', B), scripting::hook_param("Weapon", 'o', A), scripting::hook_param("WeaponB", 'o', B), scripting::hook_param("Hitpos", 'o', B->pos))); //Yes, this should be reversed b_override = scripting::hooks::OnWeaponCollision->isOverride(scripting::hooks::CollisionConditions{ {A, B} }, scripting::hook_param_list(scripting::hook_param("Self", 'o', B), scripting::hook_param("Object", 'o', A), scripting::hook_param("Weapon", 'o', B), scripting::hook_param("WeaponB", 'o', A), scripting::hook_param("Hitpos", 'o', A->pos))); } // damage calculation should not be done on clients, the server will tell the client version of the bomb when to die if(!a_override && !b_override && !MULTIPLAYER_CLIENT) { float dot_curve = -dot; float aDamage = wipA->damage; aDamage *= wipA->weapon_hit_curves.get_output(weapon_info::WeaponHitCurveOutputs::DAMAGE_MULT, std::forward_as_tuple(*wpA, *B, dot_curve), &wpA->modular_curves_instance); aDamage *= wipA->weapon_hit_curves.get_output(weapon_info::WeaponHitCurveOutputs::HULL_DAMAGE_MULT, std::forward_as_tuple(*wpA, *B, dot_curve), &wpA->modular_curves_instance); if (wipB->armor_type_idx >= 0) aDamage = Armor_types[wipB->armor_type_idx].GetDamage(aDamage, wipA->damage_type_idx, 1.0f, false); float bDamage = wipB->damage; bDamage *= wipB->weapon_hit_curves.get_output(weapon_info::WeaponHitCurveOutputs::DAMAGE_MULT, std::forward_as_tuple(*wpB, *A, dot_curve), &wpB->modular_curves_instance); bDamage *= wipB->weapon_hit_curves.get_output(weapon_info::WeaponHitCurveOutputs::HULL_DAMAGE_MULT, std::forward_as_tuple(*wpB, *A, dot_curve), &wpB->modular_curves_instance); if (wipA->armor_type_idx >= 0) bDamage = Armor_types[wipA->armor_type_idx].GetDamage(bDamage, wipB->damage_type_idx, 1.0f, false); if (wipA->weapon_hitpoints > 0) { if (wipB->weapon_hitpoints > 0) { // Two bombs collide, detonate both. if ((wipA->wi_flags[Weapon::Info_Flags::Bomb]) && (wipB->wi_flags[Weapon::Info_Flags::Bomb])) { wpA->weapon_flags.set(Weapon::Weapon_Flags::Destroyed_by_weapon); std::array, NumHitTypes> impact_data_b = {}; impact_data_b[static_cast>(HitType::HULL)] = ConditionData { ImpactCondition(wipB->armor_type_idx), HitType::HULL, aDamage, B->hull_strength, i2fl(wipB->weapon_hitpoints), }; bool a_armed = weapon_hit(A, B, &A->pos, -1); maybe_play_conditional_impacts(impact_data_b, A, B, a_armed, -1, &A->pos); wpB->weapon_flags.set(Weapon::Weapon_Flags::Destroyed_by_weapon); std::array, NumHitTypes> impact_data_a = {}; impact_data_a[static_cast>(HitType::HULL)] = ConditionData { ImpactCondition(wipA->armor_type_idx), HitType::HULL, bDamage, A->hull_strength, i2fl(wipA->weapon_hitpoints), }; bool b_armed = weapon_hit(B, A, &B->pos, -1); maybe_play_conditional_impacts(impact_data_a, B, A, b_armed, -1, &B->pos); } else { A->hull_strength -= bDamage; B->hull_strength -= aDamage; // safety to make sure either of the weapons die - allow 'bulkier' to keep going if ((A->hull_strength > 0.0f) && (B->hull_strength > 0.0f)) { if (wipA->weapon_hitpoints > wipB->weapon_hitpoints) { B->hull_strength = -1.0f; } else { A->hull_strength = -1.0f; } } if (A->hull_strength < 0.0f) { wpA->weapon_flags.set(Weapon::Weapon_Flags::Destroyed_by_weapon); std::array, NumHitTypes> impact_data_b = {}; impact_data_b[static_cast>(HitType::HULL)] = ConditionData { ImpactCondition(wipB->armor_type_idx), HitType::HULL, aDamage, B->hull_strength, i2fl(wipB->weapon_hitpoints), }; bool a_armed = weapon_hit(A, B, &A->pos, -1); maybe_play_conditional_impacts(impact_data_b, A, B, a_armed, -1, &A->pos); } if (B->hull_strength < 0.0f) { wpB->weapon_flags.set(Weapon::Weapon_Flags::Destroyed_by_weapon); std::array, NumHitTypes> impact_data_a = {}; impact_data_a[static_cast>(HitType::HULL)] = ConditionData { ImpactCondition(wipA->armor_type_idx), HitType::HULL, bDamage, A->hull_strength, i2fl(wipA->weapon_hitpoints), }; bool b_armed = weapon_hit(B, A, &B->pos, -1); maybe_play_conditional_impacts(impact_data_a, B, A, b_armed, -1, &B->pos); } } } else { A->hull_strength -= bDamage; wpB->weapon_flags.set(Weapon::Weapon_Flags::Destroyed_by_weapon); std::array, NumHitTypes> impact_data_a = {}; impact_data_a[static_cast>(HitType::HULL)] = ConditionData { ImpactCondition(wipA->armor_type_idx), HitType::HULL, bDamage, A->hull_strength, i2fl(wipA->weapon_hitpoints), }; bool b_armed = weapon_hit(B, A, &B->pos, -1); maybe_play_conditional_impacts(impact_data_a, B, A, b_armed, -1, &B->pos); if (A->hull_strength < 0.0f) { wpA->weapon_flags.set(Weapon::Weapon_Flags::Destroyed_by_weapon); std::array, NumHitTypes> impact_data_b = {}; impact_data_b[static_cast>(HitType::HULL)] = ConditionData { ImpactCondition(wipB->armor_type_idx), HitType::HULL, aDamage, B->hull_strength, i2fl(wipB->weapon_hitpoints), }; bool a_armed = weapon_hit(A, B, &A->pos, -1); maybe_play_conditional_impacts(impact_data_b, A, B, a_armed, -1, &A->pos); } } } else if (wipB->weapon_hitpoints > 0) { B->hull_strength -= aDamage; wpA->weapon_flags.set(Weapon::Weapon_Flags::Destroyed_by_weapon); std::array, NumHitTypes> impact_data_b = {}; impact_data_b[0] = ConditionData { ImpactCondition(wipB->armor_type_idx), HitType::HULL, aDamage, B->hull_strength, i2fl(wipB->weapon_hitpoints), }; bool a_armed = weapon_hit(A, B, &A->pos, -1); maybe_play_conditional_impacts(impact_data_b, A, B, a_armed, -1, &A->pos); if (B->hull_strength < 0.0f) { wpB->weapon_flags.set(Weapon::Weapon_Flags::Destroyed_by_weapon); std::array, NumHitTypes> impact_data_a = {}; impact_data_a[0] = ConditionData { ImpactCondition(wipA->armor_type_idx), HitType::HULL, bDamage, A->hull_strength, i2fl(wipA->weapon_hitpoints), }; bool b_armed = weapon_hit(B, A, &B->pos, -1); maybe_play_conditional_impacts(impact_data_a, B, A, b_armed, -1, &B->pos); } } // single player and multiplayer masters evaluate the scoring and kill stuff if (!MULTIPLAYER_CLIENT) { // If bomb was destroyed, do scoring if (wipA->wi_flags[Weapon::Info_Flags::Bomb]) { //Update stats. -Halleck scoring_eval_hit(A, B, 0); if (wpA->weapon_flags[Weapon::Weapon_Flags::Destroyed_by_weapon]) { scoring_eval_kill_on_weapon(A, B); } } if (wipB->wi_flags[Weapon::Info_Flags::Bomb]) { //Update stats. -Halleck scoring_eval_hit(B, A, 0); if (wpB->weapon_flags[Weapon::Weapon_Flags::Destroyed_by_weapon]) { scoring_eval_kill_on_weapon(B, A); } } } } if (!scripting::hooks::OnWeaponCollision->isActive()) { return 1; } if(!(b_override && !a_override)) { scripting::hooks::OnWeaponCollision->run(scripting::hooks::CollisionConditions{ {A, B} }, scripting::hook_param_list(scripting::hook_param("Self", 'o', A), scripting::hook_param("Object", 'o', B), scripting::hook_param("Weapon", 'o', A), scripting::hook_param("WeaponB", 'o', B), scripting::hook_param("Hitpos", 'o', B->pos))); } else { // Yes, this should be reversed. scripting::hooks::OnWeaponCollision->run(scripting::hooks::CollisionConditions{ {A, B} }, scripting::hook_param_list(scripting::hook_param("Self", 'o', B), scripting::hook_param("Object", 'o', A), scripting::hook_param("Weapon", 'o', B), scripting::hook_param("WeaponB", 'o', A), scripting::hook_param("Hitpos", 'o', A->pos))); } return 1; } return 0; }