@@ -6506,6 +6506,11 @@ bool ai_select_secondary_weapon(object *objp, ship_weapon *swp, flagset<Weapon::
65066506 ignore_mask.set(Weapon::Info_Flags::Bomber_plus);
65076507 }
65086508
6509+ // Ignore mines in normal combat... they must be explicitly prioritized via SEXP
6510+ if (!(prio1[Weapon::Info_Flags::Mine] || prio2[Weapon::Info_Flags::Mine])) {
6511+ ignore_mask.set(Weapon::Info_Flags::Mine);
6512+ }
6513+
65096514#ifndef NDEBUG
65106515 for (i=0; i<MAX_WEAPON_TYPES; i++) {
65116516 weapon_id_list[i] = -1;
@@ -7469,6 +7474,21 @@ void attack_set_accel(ai_info *aip, ship_info *sip, float dist_to_enemy, float d
74697474 if (wip != nullptr && wip->optimum_range > 0)
74707475 optimal_range = wip->optimum_range;
74717476
7477+ // Standoff for mines: keep at least 1.2x the target mine's proximity radius
7478+ // so we shoot it from outside its detonation zone.
7479+ if (En_objp->type == OBJ_WEAPON) {
7480+ weapon_info *target_wip = &Weapon_info[Weapons[En_objp->instance].weapon_info_index];
7481+ if (target_wip->is_mine() && target_wip->proximity_radius > 0.0f) {
7482+ float standoff = target_wip->proximity_radius * 1.2f;
7483+ // Don't stand off farther than our weapon can actually reach, or we'd hold
7484+ // position outside firing range and never destroy the mine. If the weapon
7485+ // can't outrange the detonation zone, close in and accept the risk.
7486+ if (wip != nullptr)
7487+ standoff = MIN(standoff, MIN(wip->max_speed * wip->lifetime, wip->weapon_range));
7488+ optimal_range = MAX(optimal_range, standoff);
7489+ }
7490+ }
7491+
74727492 if (dist_to_enemy > optimal_range + vm_vec_mag_quick(&En_objp->phys_info.vel) * dot_from_enemy + Pl_objp->phys_info.speed * speed_ratio) {
74737493 if (dist_to_enemy > optimal_range + 600.0f) {
74747494 if (ai_willing_to_afterburn_hard(aip)) {
@@ -10519,10 +10539,65 @@ void maybe_update_guard_object(object *hit_objp, object *hitter_objp)
1051910539 }
1052010540}
1052110541
10522- // Scan missile list looking for bombs homing on guarded_objp
10523- // return 1 if bomb is found (and targeted by guarding_objp), otherwise return 0
10542+ // Find the closest hostile mine threatening 'against_objp', ranked by distance from 'from_objp'.
10543+ // If 'against_objp' is null, defaults to 'from_objp' (i.e. find mines threatening the searcher itself).
10544+ // imminent_only: also requires the mine to be within proximity_radius * 1.5 of against_objp;
10545+ // used to preempt a current target when the mine is about to detonate.
10546+ static object *ai_find_nearby_mine_threat(object *from_objp, object *against_objp = nullptr, bool imminent_only = false)
10547+ {
10548+ if (against_objp == nullptr)
10549+ against_objp = from_objp;
10550+
10551+ object *closest_mine = nullptr;
10552+ float closest_dist_from = std::numeric_limits<float>::max();
10553+
10554+ for (const auto *mo : list_range(&Missile_obj_list)) {
10555+ Assert(mo->objnum >= 0 && mo->objnum < MAX_OBJECTS);
10556+ object *mine_objp = &Objects[mo->objnum];
10557+ if (mine_objp->flags[Object::Object_Flags::Should_be_dead])
10558+ continue;
10559+
10560+ weapon *wp = &Weapons[mine_objp->instance];
10561+ weapon_info *wip = &Weapon_info[wp->weapon_info_index];
10562+
10563+ if (!wip->is_mine())
10564+ continue;
10565+
10566+ // Only engage destructible mines
10567+ if (!wip->wi_flags[Weapon::Info_Flags::Fighter_Interceptable])
10568+ continue;
10569+
10570+ // Only engage mines hostile to the threatened ship
10571+ if (!iff_x_attacks_y(wp->team, obj_team(against_objp)))
10572+ continue;
10573+
10574+ float dist_against = vm_vec_dist(&mine_objp->pos, &against_objp->pos);
10575+
10576+ // Mine must be within its own targetable range of the threatened ship
10577+ if (dist_against > wip->mine_targetable_range)
10578+ continue;
10579+
10580+ // Imminent gate: only consider mines about to detonate on the threatened ship.
10581+ // proximity_radius is always > 0 for mines (parser enforces).
10582+ if (imminent_only && dist_against > wip->proximity_radius * 1.5f)
10583+ continue;
10584+
10585+ float dist_from = (from_objp == against_objp) ? dist_against : vm_vec_dist(&mine_objp->pos, &from_objp->pos);
10586+ if (dist_from < closest_dist_from) {
10587+ closest_dist_from = dist_from;
10588+ closest_mine = mine_objp;
10589+ }
10590+ }
10591+
10592+ return closest_mine;
10593+ }
10594+
10595+ // Scan the missile list for a threat to guarded_objp and, if one is found, target it with guarding_objp.
10596+ // Bombs homing on guarded_objp take priority; if none are found, fall back to the closest hostile mine
10597+ // threatening guarded_objp (mines are stationary so they can't home, hence the lower priority).
10598+ // Returns 1 if a threat was found and targeted, otherwise 0.
1052410599int ai_guard_find_nearby_bomb(object *guarding_objp, object *guarded_objp)
10525- {
10600+ {
1052610601 missile_obj *mo;
1052710602 object *bomb_objp, *closest_bomb_objp=NULL;
1052810603 float dist, dist_to_guarding_obj,closest_dist_to_guarding_obj=999999.0f;
@@ -10566,6 +10641,13 @@ int ai_guard_find_nearby_bomb(object *guarding_objp, object *guarded_objp)
1056610641 return 1;
1056710642 }
1056810643
10644+ // No homing threat found... check for mines threatening the guarded ship.
10645+ // Mines are stationary so they won't home; handle them as a lower priority threat.
10646+ if (object *closest_mine_objp = ai_find_nearby_mine_threat(guarding_objp, guarded_objp)) {
10647+ guard_object_was_hit(guarding_objp, closest_mine_objp);
10648+ return 1;
10649+ }
10650+
1056910651 return 0;
1057010652}
1057110653
@@ -10676,6 +10758,13 @@ void ai_guard_find_nearby_object()
1067610758 if ( (aip->target_objnum == -1) && asteroid_count() ) {
1067710759 ai_guard_find_nearby_asteroid(Pl_objp, guardobjp);
1067810760 }
10761+
10762+ // if still not attacking anything and flag is set, engage mines near the guarding ship itself
10763+ if (aip->target_objnum == -1 && The_mission.ai_profile->flags[AI::Profile_Flags::Ships_intercept_mines]) {
10764+ object *mine_objp = ai_find_nearby_mine_threat(Pl_objp);
10765+ if (mine_objp)
10766+ guard_object_was_hit(Pl_objp, mine_objp);
10767+ }
1067910768 }
1068010769}
1068110770
@@ -15305,6 +15394,22 @@ void ai_frame(int objnum)
1530515394
1530615395 ai_maybe_depart(Pl_objp);
1530715396
15397+ // Imminent-mine override: even if we have a current target, swap to a mine
15398+ // that is about to detonate on us (within proximity_radius * 1.5).
15399+ // Skip departing ships (AIM_WARP_OUT/AIM_BAY_DEPART): ai_maybe_depart() ran just above and may
15400+ // have committed this ship to leaving, so we must not divert it back to a mine here.
15401+ if (The_mission.ai_profile->flags[AI::Profile_Flags::Ships_intercept_mines]
15402+ && aip->mode != AIM_GUARD && aip->mode != AIM_EVADE_WEAPON
15403+ && aip->mode != AIM_WARP_OUT && aip->mode != AIM_BAY_DEPART
15404+ && Ship_info[shipp->ship_info_index].class_type > -1
15405+ && Ship_types[Ship_info[shipp->ship_info_index].class_type].flags[Ship::Type_Info_Flags::AI_auto_attacks]) {
15406+ object *imminent_mine = ai_find_nearby_mine_threat(Pl_objp, nullptr, true);
15407+ if (imminent_mine != nullptr && (target_objnum < 0 || &Objects[target_objnum] != imminent_mine)) {
15408+ aip->aspect_locked_time = 0.0f;
15409+ target_objnum = set_target_objnum(aip, OBJ_INDEX(imminent_mine));
15410+ }
15411+ }
15412+
1530815413 // Find an enemy if don't already have one.
1530915414 En_objp = NULL;
1531015415 if ( ai_need_new_target(Pl_objp, target_objnum) ) {
@@ -15324,6 +15429,14 @@ void ai_frame(int objnum)
1532415429 {
1532515430 En_objp = &Objects[target_objnum];
1532615431 }
15432+ } else if (aip->mode != AIM_GUARD && The_mission.ai_profile->flags[AI::Profile_Flags::Ships_intercept_mines]) {
15433+ // No enemy found so check for nearby hostile mines within their targetable range
15434+ object *mine_objp = ai_find_nearby_mine_threat(Pl_objp);
15435+ if (mine_objp) {
15436+ target_objnum = set_target_objnum(aip, OBJ_INDEX(mine_objp));
15437+ if (target_objnum >= 0)
15438+ En_objp = &Objects[target_objnum];
15439+ }
1532715440 }
1532815441 }
1532915442 }
0 commit comments