From 2bd6980f2bcf2c2ef89ddbc4de6222a8f288cc85 Mon Sep 17 00:00:00 2001 From: Kestrellius <902X@comcast.net> Date: Sun, 9 Aug 2026 08:17:02 -0700 Subject: [PATCH 01/14] early construction --- code/ai/aicode.cpp | 66 +++++++++++++++++++++++++++++++++------------- 1 file changed, 47 insertions(+), 19 deletions(-) diff --git a/code/ai/aicode.cpp b/code/ai/aicode.cpp index 9136144dae1..f06c491e232 100644 --- a/code/ai/aicode.cpp +++ b/code/ai/aicode.cpp @@ -5722,26 +5722,8 @@ static int ai_select_primary_weapon_OLD(const object *objp, Weapon::Info_Flags f return swp->current_primary_bank; } -// If: -// flags == Weapon::Info_Flags::Puncture -// Then Select a Puncture weapon. -// Else -// Select Any ol' weapon. -// Returns primary_bank index. -/** - * Etc. Etc. This is like the 4th rewrite of the code here. Special thanks to Bobboau - * for finding the get_shield_strength function. - * - * The AI will now intelligently choose the best weapon to use based on the overall shield - * status of the target. - */ -int ai_select_primary_weapon(object *objp, object *other_objp, Weapon::Info_Flags flags) +std::optional select_primary_setup(ship *shipp, ship *other_shipp, ship_weapon *swp, object *other_objp) { - // Pointer Set Up - ship *shipp = &Ships[objp->instance]; - ship *other_shipp = nullptr; - ship_weapon *swp = &shipp->weapons; - // Debugging if (other_objp==NULL) { @@ -5795,6 +5777,34 @@ int ai_select_primary_weapon(object *objp, object *other_objp, Weapon::Info_Flag // we're not prioritizing a specific primary weapon shipp->flags.remove(Ship::Ship_Flags::Force_primary_unlinking); + return std::nullopt; +} + +// If: +// flags == Weapon::Info_Flags::Puncture +// Then Select a Puncture weapon. +// Else +// Select Any ol' weapon. +// Returns primary_bank index. +/** + * Etc. Etc. This is like the 4th rewrite of the code here. Special thanks to Bobboau + * for finding the get_shield_strength function. + * + * The AI will now intelligently choose the best weapon to use based on the overall shield + * status of the target. + */ +int ai_select_primary_weapon(object *objp, object *other_objp, Weapon::Info_Flags flags) +{ + // Pointer Set Up + ship *shipp = &Ships[objp->instance]; + ship *other_shipp = nullptr; + ship_weapon *swp = &shipp->weapons; + + auto early_return_value = select_primary_setup(shipp, other_shipp, swp, other_objp); + if (early_return_value.has_value()) { + return early_return_value.value(); + } + //not using the new AI, use the old version of this function instead. if (!(Ai_info[shipp->ai_index].ai_profile_flags[AI::Profile_Flags::Smart_primary_weapon_selection])) @@ -5990,6 +6000,24 @@ int ai_select_primary_weapon(object *objp, object *other_objp, Weapon::Info_Flag } } +// Cleaner version of primary selection, with high modder control. +int ai_select_primary_weapon_configurable(object *objp, object *other_objp, Weapon::Info_Flags flags) +{ + ship *shipp = &Ships[objp->instance]; + ship *other_shipp = nullptr; + ship_weapon *swp = &shipp->weapons; + + auto early_return_value = select_primary_setup(shipp, other_shipp, swp, other_objp); + if (early_return_value.has_value()) { + return early_return_value.value(); + } + + for (auto& wip_i : swp->primary_bank_weapons) { + auto wip = &Weapon_info[wip_i]; + + } +} + /** * Maybe link primary weapons. */ From 7d4e87190721431ed785c6783714e6466b2e6f14 Mon Sep 17 00:00:00 2001 From: Kestrellius <902X@comcast.net> Date: Sun, 9 Aug 2026 19:25:32 -0700 Subject: [PATCH 02/14] build main function --- code/ai/ai.h | 7 ++ code/ai/aicode.cpp | 171 ++++++++++++++++++++++++++++++++++++++-- code/weapon/weapon.h | 9 +++ code/weapon/weapons.cpp | 71 +++++++++++------ 4 files changed, 230 insertions(+), 28 deletions(-) diff --git a/code/ai/ai.h b/code/ai/ai.h index b74f7b43527..67ebb4eb488 100644 --- a/code/ai/ai.h +++ b/code/ai/ai.h @@ -142,6 +142,10 @@ typedef struct ai_class { float ai_secondary_range_mult[NUM_SKILL_LEVELS]; bool ai_class_autoscale; //Defaults to true, but can be turned off in order to disable extra scaling of some AI behaviors //based on AI class index + float primary_selection_random_factor; + float primary_selection_oneshot_modifier; + float primary_selection_status_quo_bias; + } ai_class; // Submode definitions. @@ -373,6 +377,9 @@ typedef struct ai_info { int ai_chance_to_use_missiles_on_plr; float ai_max_aim_update_delay; float ai_turret_max_aim_update_delay; + float primary_selection_random_factor; + float primary_selection_oneshot_modifier; + float primary_selection_status_quo_bias; flagset ai_profile_flags; //Holds AI_Profiles flags (possibly overriden by AI class) that actually apply to AI ship_subsys* targeted_subsys; // Targeted subobject on current target. NULL if none; diff --git a/code/ai/aicode.cpp b/code/ai/aicode.cpp index f06c491e232..3711c83121c 100644 --- a/code/ai/aicode.cpp +++ b/code/ai/aicode.cpp @@ -691,6 +691,9 @@ void init_ai_class(ai_class *aicp) aicp->ai_secondary_range_mult[i] = FLT_MIN; } aicp->ai_class_autoscale = true; //Retail behavior is to do the stupid autoscaling + aicp->primary_selection_random_factor = 1.0f; + aicp->primary_selection_oneshot_modifier = 2.0f; + aicp->primary_selection_status_quo_bias = 1.0f; } void set_aic_flag(ai_class *aicp, const char *name, AI::Profile_Flags flag) @@ -797,6 +800,15 @@ void parse_ai_class() if (optional_string("$Autoscale by AI Class Index:")) stuff_boolean(&aicp->ai_class_autoscale); + if (optional_string("$Primary selection random factor:")) + stuff_float(&aicp->primary_selection_random_factor); + + if (optional_string("$Primary selection oneshot modifier:")) + stuff_float(&aicp->primary_selection_oneshot_modifier); + + if (optional_string("$Primary selection status quo bias:")) + stuff_float(&aicp->primary_selection_status_quo_bias); + //Parse optional values for stuff imported from ai_profiles if (optional_string("$AI Countermeasure Firing Chance:")) parse_float_list(aicp->ai_cmeasure_fire_chance, NUM_SKILL_LEVELS); @@ -5843,7 +5855,7 @@ int ai_select_primary_weapon(object *objp, object *other_objp, Weapon::Info_Flag float enemy_remaining_shield = get_shield_pct(other_objp); - if ( other_is_ship ) + if ( other_shipp ) { ship_info* other_sip = &Ship_info[other_shipp->ship_info_index]; @@ -5901,7 +5913,7 @@ int ai_select_primary_weapon(object *objp, object *other_objp, Weapon::Info_Flag return -1; } swp->current_primary_bank = i_hullfactor_prev_bank; // Select the best weapon - nprintf(("AI", "%i: Ship %s selecting weapon %s (no shields) vs target %s\n", Framecount, shipp->ship_name, Weapon_info[swp->primary_bank_weapons[i_hullfactor_prev_bank]].name, (other_is_ship ? other_shipp->ship_name : "non-ship") )); + nprintf(("AI", "%i: Ship %s selecting weapon %s (no shields) vs target %s\n", Framecount, shipp->ship_name, Weapon_info[swp->primary_bank_weapons[i_hullfactor_prev_bank]].name, (other_shipp ? other_shipp->ship_name : "non-ship") )); return i_hullfactor_prev_bank; // Return } @@ -6000,22 +6012,165 @@ int ai_select_primary_weapon(object *objp, object *other_objp, Weapon::Info_Flag } } -// Cleaner version of primary selection, with high modder control. +// More comprehensive version of primary selection, with high modder control. int ai_select_primary_weapon_configurable(object *objp, object *other_objp, Weapon::Info_Flags flags) { ship *shipp = &Ships[objp->instance]; - ship *other_shipp = nullptr; + ship *target_shipp = nullptr; ship_weapon *swp = &shipp->weapons; + ship_info *sinfop = &Ship_info[shipp->ship_info_index]; - auto early_return_value = select_primary_setup(shipp, other_shipp, swp, other_objp); + auto early_return_value = select_primary_setup(shipp, target_shipp, swp, other_objp); if (early_return_value.has_value()) { return early_return_value.value(); } + weapon_info *wip; + shockwave_create_info *sci; + bool has_shockwave; + bool is_beam; + + ship_subsys *target_subsys = Ai_info[shipp->ai_index].targeted_subsys; + float relevant_shields_left = 100.0f; + + SCP_unordered_map weapon_values = {}; + + // TODO: handle non-ship target cases + for (auto& wip_i : swp->primary_bank_weapons) { - auto wip = &Weapon_info[wip_i]; + if (wip_i < 0) { + continue; + } + wip = &Weapon_info[wip_i]; + sci = &wip->shockwave; + has_shockwave = (sci->inner_rad != 0.0f || sci->outer_rad != 0.0f); + is_beam = wip->wi_flags[Weapon::Info_Flags::Beam]; + float dph = wip->damage; // damage per hit + float damage_scale = -1.0f; + float shockwave_damage = 0.0f; + if (has_shockwave) { + shockwave_damage = sci->damage; + } + + + if (target_shipp) { + if (relevant_shields_left > 0.0f) { + damage_scale = weapon_get_damage_scale(wip, nullptr, other_objp); + dph *= damage_scale; + dph = Armor_types[target_shipp->shield_armor_type_idx].GetDamage(dph, wip->damage_type_idx, 1.0, is_beam); + if (!is_beam || Beams_use_damage_factors) { + dph *= wip->shield_factor; + } + } else if (target_subsys) { + if (!is_beam || Beams_use_damage_factors) { + if (target_subsys->flags[Ship::Subsystem_Flags::Damage_as_hull]) { + dph *= wip->armor_factor; + dph = Armor_types[target_subsys->armor_type_idx].GetDamage(dph, wip->damage_type_idx, 1.0, is_beam); + } else { + dph *= wip->subsystem_factor; + dph = Armor_types[target_subsys->armor_type_idx].GetDamage(dph, wip->damage_type_idx, 1.0, is_beam); + } + } + } else { + if (damage_scale == -1.0f) { + damage_scale = weapon_get_damage_scale(wip, nullptr, other_objp); + } + dph *= damage_scale; + dph = Armor_types[target_shipp->armor_type_idx].GetDamage(dph, wip->damage_type_idx, 1.0, is_beam); + if (!is_beam || Beams_use_damage_factors) { + if (wip->wi_flags[Weapon::Info_Flags::Puncture]) { + dph /= 4; + } + dph *= wip->armor_factor; + } + } + if (has_shockwave) { + shockwave_damage = sci->damage; + if (Weapon_shockwaves_respect_huge || sci->speed <= 0.0f) { + if (damage_scale == -1.0f) { + damage_scale = weapon_get_damage_scale(wip, nullptr, other_objp); + } + shockwave_damage *= damage_scale; + } + if (relevant_shields_left) { + shockwave_damage = Armor_types[target_shipp->shield_armor_type_idx].GetDamage(shockwave_damage, sci->damage_type_idx, 1.0, is_beam); + } else if (target_subsys) { + shockwave_damage = Armor_types[target_subsys->armor_type_idx].GetDamage(shockwave_damage, sci->damage_type_idx, 1.0, is_beam); + } else { + shockwave_damage = Armor_types[target_shipp->armor_type_idx].GetDamage(shockwave_damage, sci->damage_type_idx, 1.0, is_beam); + } + } + } else if ( other_objp->type == OBJ_WEAPON ) { + ArmorType *weapon_armor = &Armor_types[Weapon_info[Weapons[other_objp->instance].weapon_info_index].armor_type_idx]; + dph = weapon_armor->GetDamage(dph, wip->damage_type_idx, 1.0, is_beam); + if (has_shockwave) { + shockwave_damage = weapon_armor->GetDamage(dph, sci->damage_type_idx, 1.0, is_beam); + if (sci->speed <= 0.0f) { + shockwave_damage *= weapon_get_damage_scale(wip, nullptr, other_objp); + } + } + } else { + if (has_shockwave && sci->speed <= 0.0f) { + shockwave_damage *= weapon_get_damage_scale(wip, nullptr, other_objp); + } + } + + dph += shockwave_damage; + + ai_info *aip = &Ai_info[shipp->ai_index]; + float effective_dps; + float oneshot_value = 0.0f; + float relevant_hits_left; + if (relevant_shields_left > 0.0f) { + relevant_hits_left = relevant_shields_left; + } else if (target_subsys) { + relevant_hits_left = target_subsys->current_hits; + } else { + relevant_hits_left = other_objp->hull_strength; + } + int burst_shots = wip->burst_shots + 1; + int effective_burst_shots = std::max(burst_shots, fl2i(shipp->weapon_energy / (wip->energy_consumed * burst_shots))); + if (dph * effective_burst_shots >= relevant_hits_left) { + oneshot_value = dph * aip->primary_selection_oneshot_modifier; + } + float fire_rate; + if (wip->burst_shots > 1 && wip->burst_flags[Weapon::Burst_Flags::Random_length]) { + fire_rate = (wip->burst_shots / (wip->fire_wait + wip->burst_delay * (wip->burst_shots - 1)) + (1 / wip->fire_wait)) / 2; + } + else if (wip->burst_shots > 1) { + fire_rate = wip->burst_shots / (wip->fire_wait + wip->burst_delay * (wip->burst_shots - 1)); + } + else { + fire_rate = 1 / wip->fire_wait; + } + // if fire can't be sustained indefinitely given energy consumption, take the average between max fire rate and fire rate when we're drained and have to wait for each shot + if (fire_rate * wip->energy_consumed > (shipp->max_weapon_regen_per_second * sinfop->max_weapon_reserve)) { + fire_rate = (fire_rate + ((shipp->max_weapon_regen_per_second * sinfop->max_weapon_reserve) / wip->energy_consumed)) / 2.0f; + } + effective_dps = (dph * burst_shots) * fire_rate; + float weapon_value = std::max(effective_dps, oneshot_value) * aip->primary_selection_random_factor; + + + //TODO: handle weapon selection flags + + + + + + + if (wip_i == swp->current_primary_bank) { + weapon_value *= aip->primary_selection_status_quo_bias; + } + weapon_values.emplace(wip_i, weapon_value); } + std::pair best_pair = std::make_pair(-1, 0.0f); + for (auto pair : weapon_values) { + if (std::max(pair.second, 0.0f) >= best_pair.second) { + best_pair = pair; + } + } + return best_pair.first; } /** @@ -15964,6 +16119,10 @@ void init_aip_from_class_and_profile(ai_info *aip, ai_class *aicp, ai_profile_t aip->ai_secondary_range_mult = aicp->ai_secondary_range_mult[Game_skill_level]; aip->ai_class_autoscale = aicp->ai_class_autoscale; + aip->primary_selection_random_factor = aicp->primary_selection_random_factor; + aip->primary_selection_oneshot_modifier = aicp->primary_selection_oneshot_modifier; + aip->primary_selection_status_quo_bias = aicp->primary_selection_status_quo_bias; + //Apply overrides from ai class to ai profiles values //Only override values which were explicitly set in the AI class aip->ai_cmeasure_fire_chance = (aicp->ai_cmeasure_fire_chance[Game_skill_level] == FLT_MIN) ? diff --git a/code/weapon/weapon.h b/code/weapon/weapon.h index ed59726b94f..8d031d8b1be 100644 --- a/code/weapon/weapon.h +++ b/code/weapon/weapon.h @@ -720,6 +720,15 @@ struct weapon_info animation::ModelAnimationSet animations; + enum class PrimarySelectionTargetType { + ARMOR, + SHIP_TYPE, + SHIP_CLASS, + WEAPON_CLASS, + }; + + std::array, 4> primary_selection_target_flags; + enum class WeaponLaunchCurveOutputs { // outputs FIRE_WAIT_MULT, diff --git a/code/weapon/weapons.cpp b/code/weapon/weapons.cpp index 393b8b5e218..9ff9b84179e 100644 --- a/code/weapon/weapons.cpp +++ b/code/weapon/weapons.cpp @@ -2126,6 +2126,32 @@ int parse_weapon(int subtype, bool replace, const char *filename) if (optional_string("$Disallow Support Rearm:")) { stuff_boolean(&wip->disallow_rearm); } + + if (optional_string("$Primary Selection Target Flags:")) { + SCP_string type; + int instance_index; + float value; + while (optional_string("+")) { + stuff_string(type, F_NAME); + if (type == "ARMOR:") { + stuff_string(fname, F_NAME, NAME_LENGTH); + instance_index = armor_type_get_idx(fname); + stuff_float(&value); + wip->primary_selection_target_flags[PrimarySelectionTargetType::ARMOR].emplace(instance_index, value); + } else if (type == "SHIP_TYPE:") { + stuff_string(fname, F_NAME, NAME_LENGTH); + instance_index = ship_type_name_lookup(fname); + stuff_float(&value); + wip->primary_selection_target_flags[PrimarySelectionTargetType::ARMOR].emplace(instance_index, value); + } else if (type == "SHIP_CLASS:") { + + } else if (type == "WEAPON_CLASS:") { + + } else { + + } + } + } if (optional_string("+Weapon Range:")) { stuff_float(&wip->weapon_range); @@ -9053,36 +9079,37 @@ void weapon_get_laser_color(color *c, object *objp) */ float weapon_get_damage_scale(const weapon_info *wip, const object *wep, const object *target) { - weapon *wp; - int from_player = 0; float total_scale = 1.0f; float hull_pct; int is_big_damage_ship = 0; - + // Goober5000 - additional sanity (target can be NULL) Assert(wip); - Assert(wep); - + // sanity - if((wip == NULL) || (wep == NULL) || (target == NULL)){ - return 1.0f; - } - - // don't scale any damage if its not a weapon - if((wep->type != OBJ_WEAPON) || (wep->instance < 0) || (wep->instance >= MAX_WEAPONS)){ + if((wip == nullptr) || (target == nullptr)){ return 1.0f; } - wp = &Weapons[wep->instance]; - - // was the weapon fired by the player - from_player = 0; - if((wep->parent >= 0) && (wep->parent < MAX_OBJECTS) && (Objects[wep->parent].flags[Object::Object_Flags::Player_ship])){ - from_player = 1; - } - - // if this is a lockarm weapon, and it was fired unlocked - if((wip->wi_flags[Weapon::Info_Flags::Lockarm]) && !(wp->weapon_flags[Weapon::Weapon_Flags::Locked_when_fired])){ - total_scale *= 0.1f; + + int from_player = 0; + if (wep) { + weapon *wp; + // don't scale any damage if its not a weapon + if((wep->type != OBJ_WEAPON) || (wep->instance < 0) || (wep->instance >= MAX_WEAPONS)){ + return 1.0f; + } + wp = &Weapons[wep->instance]; + + // was the weapon fired by the player + from_player = 0; + if((wep->parent >= 0) && (wep->parent < MAX_OBJECTS) && (Objects[wep->parent].flags[Object::Object_Flags::Player_ship])){ + from_player = 1; + } + + // if this is a lockarm weapon, and it was fired unlocked + if((wip->wi_flags[Weapon::Info_Flags::Lockarm]) && !(wp->weapon_flags[Weapon::Weapon_Flags::Locked_when_fired])){ + total_scale *= 0.1f; + } } // if the hit object was a ship and we're doing damage scaling From 6184e14ef81089f6dfbd457a9eabb15fa00cf72e Mon Sep 17 00:00:00 2001 From: Kestrellius <902X@comcast.net> Date: Mon, 10 Aug 2026 13:19:41 -0700 Subject: [PATCH 03/14] connect things --- code/ai/ai_flags.h | 1 + code/ai/ai_profiles.cpp | 2 ++ code/ai/aicode.cpp | 66 ++++++++++++++++++++++++++++++----------- code/weapon/weapon.h | 18 ++++++----- code/weapon/weapons.cpp | 32 +++++++++++++++----- 5 files changed, 86 insertions(+), 33 deletions(-) diff --git a/code/ai/ai_flags.h b/code/ai/ai_flags.h index 427ec5f8395..41c6d0c8877 100644 --- a/code/ai/ai_flags.h +++ b/code/ai/ai_flags.h @@ -127,6 +127,7 @@ namespace AI { Require_turret_to_have_target_in_fov, Shockwaves_damage_small_ship_subsystems, Smart_afterburner_management, + Configurable_primary_weapon_selection, Smart_primary_weapon_selection, Smart_secondary_weapon_selection, Smart_shield_management, diff --git a/code/ai/ai_profiles.cpp b/code/ai/ai_profiles.cpp index d65ba45ecfc..6b78be57cd2 100644 --- a/code/ai/ai_profiles.cpp +++ b/code/ai/ai_profiles.cpp @@ -382,6 +382,8 @@ void parse_ai_profiles_tbl(const char *filename) set_flag(profile, "$big ships can attack beam turrets on untargeted ships:", AI::Profile_Flags::Big_ships_can_attack_beam_turrets_on_untargeted_ships); + set_flag(profile, "$Configurable primary weapon selection:", AI::Profile_Flags::Configurable_primary_weapon_selection); + set_flag(profile, "$smart primary weapon selection:", AI::Profile_Flags::Smart_primary_weapon_selection); set_flag(profile, "$smart secondary weapon selection:", AI::Profile_Flags::Smart_secondary_weapon_selection); diff --git a/code/ai/aicode.cpp b/code/ai/aicode.cpp index 3711c83121c..e86cd31ac2a 100644 --- a/code/ai/aicode.cpp +++ b/code/ai/aicode.cpp @@ -919,6 +919,8 @@ void parse_ai_class() parse_float_list(aicp->ai_turret_max_aim_update_delay, NUM_SKILL_LEVELS); set_aic_flag(aicp, "$big ships can attack beam turrets on untargeted ships:", AI::Profile_Flags::Big_ships_can_attack_beam_turrets_on_untargeted_ships); + + set_aic_flag(aicp, "$configurable primary weapon selection:", AI::Profile_Flags::Configurable_primary_weapon_selection); set_aic_flag(aicp, "$smart primary weapon selection:", AI::Profile_Flags::Smart_primary_weapon_selection); @@ -1594,6 +1596,8 @@ int set_target_objnum(ai_info *aip, int objnum) int ai_select_primary_weapon(object *objp, object *other_objp, Weapon::Info_Flags flags); +int ai_select_primary_weapon_configurable(object *objp, object *other_objp, Weapon::Info_Flags flags); + /** * Make new_subsys the targeted subsystem of ship *aip. */ @@ -1610,7 +1614,7 @@ ship_subsys *set_targeted_subsys(ai_info *aip, ship_subsys *new_subsys, int pare if (new_subsys->system_info->type == SUBSYSTEM_ENGINE) { if ( aip != Player_ai ) { Assert( aip->shipnum >= 0 ); - ai_select_primary_weapon(&Objects[Ships[aip->shipnum].objnum], &Objects[parent_objnum], Weapon::Info_Flags::Puncture); + ai_select_primary_weapon_configurable(&Objects[Ships[aip->shipnum].objnum], &Objects[parent_objnum], Weapon::Info_Flags::Puncture); ship_primary_changed(&Ships[aip->shipnum]); // AL: maybe send multiplayer information when AI ship changes primaries } } @@ -5817,7 +5821,6 @@ int ai_select_primary_weapon(object *objp, object *other_objp, Weapon::Info_Flag return early_return_value.value(); } - //not using the new AI, use the old version of this function instead. if (!(Ai_info[shipp->ai_index].ai_profile_flags[AI::Profile_Flags::Smart_primary_weapon_selection])) { @@ -6020,6 +6023,12 @@ int ai_select_primary_weapon_configurable(object *objp, object *other_objp, Weap ship_weapon *swp = &shipp->weapons; ship_info *sinfop = &Ship_info[shipp->ship_info_index]; + //if we're not using configurable, use the previous version of this function instead. + if (!(Ai_info[shipp->ai_index].ai_profile_flags[AI::Profile_Flags::Configurable_primary_weapon_selection])) + { + return ai_select_primary_weapon(objp, other_objp, flags); + } + auto early_return_value = select_primary_setup(shipp, target_shipp, swp, other_objp); if (early_return_value.has_value()) { return early_return_value.value(); @@ -6031,7 +6040,11 @@ int ai_select_primary_weapon_configurable(object *objp, object *other_objp, Weap bool is_beam; ship_subsys *target_subsys = Ai_info[shipp->ai_index].targeted_subsys; - float relevant_shields_left = 100.0f; + float relevant_shields_left = 100.0f; //TODO: get shield quadrant stuff + int relevant_armor_type_idx = -1; + int relevant_ship_type_idx = -1; + int relevant_ship_class_idx = -1; + int relevant_weapon_class_idx = -1; SCP_unordered_map weapon_values = {}; @@ -6054,21 +6067,25 @@ int ai_select_primary_weapon_configurable(object *objp, object *other_objp, Weap if (target_shipp) { + relevant_ship_type_idx = Ship_info[target_shipp->ship_info_index].class_type; + relevant_ship_class_idx = target_shipp->ship_info_index; if (relevant_shields_left > 0.0f) { damage_scale = weapon_get_damage_scale(wip, nullptr, other_objp); dph *= damage_scale; - dph = Armor_types[target_shipp->shield_armor_type_idx].GetDamage(dph, wip->damage_type_idx, 1.0, is_beam); + relevant_armor_type_idx = target_shipp->shield_armor_type_idx; + dph = Armor_types[relevant_armor_type_idx].GetDamage(dph, wip->damage_type_idx, 1.0, is_beam); if (!is_beam || Beams_use_damage_factors) { dph *= wip->shield_factor; } } else if (target_subsys) { if (!is_beam || Beams_use_damage_factors) { + relevant_armor_type_idx = target_subsys->armor_type_idx; if (target_subsys->flags[Ship::Subsystem_Flags::Damage_as_hull]) { dph *= wip->armor_factor; - dph = Armor_types[target_subsys->armor_type_idx].GetDamage(dph, wip->damage_type_idx, 1.0, is_beam); + dph = Armor_types[relevant_armor_type_idx].GetDamage(dph, wip->damage_type_idx, 1.0, is_beam); } else { dph *= wip->subsystem_factor; - dph = Armor_types[target_subsys->armor_type_idx].GetDamage(dph, wip->damage_type_idx, 1.0, is_beam); + dph = Armor_types[relevant_armor_type_idx].GetDamage(dph, wip->damage_type_idx, 1.0, is_beam); } } } else { @@ -6076,7 +6093,8 @@ int ai_select_primary_weapon_configurable(object *objp, object *other_objp, Weap damage_scale = weapon_get_damage_scale(wip, nullptr, other_objp); } dph *= damage_scale; - dph = Armor_types[target_shipp->armor_type_idx].GetDamage(dph, wip->damage_type_idx, 1.0, is_beam); + relevant_armor_type_idx = target_shipp->armor_type_idx; + dph = Armor_types[relevant_armor_type_idx].GetDamage(dph, wip->damage_type_idx, 1.0, is_beam); if (!is_beam || Beams_use_damage_factors) { if (wip->wi_flags[Weapon::Info_Flags::Puncture]) { dph /= 4; @@ -6101,10 +6119,12 @@ int ai_select_primary_weapon_configurable(object *objp, object *other_objp, Weap } } } else if ( other_objp->type == OBJ_WEAPON ) { - ArmorType *weapon_armor = &Armor_types[Weapon_info[Weapons[other_objp->instance].weapon_info_index].armor_type_idx]; - dph = weapon_armor->GetDamage(dph, wip->damage_type_idx, 1.0, is_beam); + weapon *target_weapon = &Weapons[other_objp->instance]; + relevant_armor_type_idx = Weapon_info[target_weapon->weapon_info_index].armor_type_idx; + relevant_weapon_class_idx = target_weapon->weapon_info_index; + dph = Armor_types[relevant_armor_type_idx].GetDamage(dph, wip->damage_type_idx, 1.0, is_beam); if (has_shockwave) { - shockwave_damage = weapon_armor->GetDamage(dph, sci->damage_type_idx, 1.0, is_beam); + shockwave_damage = Armor_types[relevant_armor_type_idx].GetDamage(dph, sci->damage_type_idx, 1.0, is_beam); if (sci->speed <= 0.0f) { shockwave_damage *= weapon_get_damage_scale(wip, nullptr, other_objp); } @@ -6117,6 +6137,8 @@ int ai_select_primary_weapon_configurable(object *objp, object *other_objp, Weap dph += shockwave_damage; + //TODO: decide whether to take $Shots (/$Cycle_multishot) into account + ai_info *aip = &Ai_info[shipp->ai_index]; float effective_dps; float oneshot_value = 0.0f; @@ -6151,13 +6173,23 @@ int ai_select_primary_weapon_configurable(object *objp, object *other_objp, Weap float weapon_value = std::max(effective_dps, oneshot_value) * aip->primary_selection_random_factor; + SCP_unordered_map armor_flags = wip->primary_selection_target_flags[PrimarySelectionTargetType::ARMOR]; + SCP_unordered_map shiptype_flags = wip->primary_selection_target_flags[PrimarySelectionTargetType::SHIP_TYPE]; + SCP_unordered_map shipclass_flags = wip->primary_selection_target_flags[PrimarySelectionTargetType::SHIP_CLASS]; + SCP_unordered_map weaponclass_flags = wip->primary_selection_target_flags[PrimarySelectionTargetType::WEAPON_CLASS]; - //TODO: handle weapon selection flags - - - - - + if (relevant_armor_type_idx >= 0 && armor_flags.contains(relevant_armor_type_idx)) { + weapon_value *= armor_flags[relevant_armor_type_idx]; + } + if (relevant_ship_type_idx >= 0 && shiptype_flags.contains(relevant_ship_type_idx)) { + weapon_value *= armor_flags[relevant_ship_type_idx]; + } + if (relevant_ship_class_idx >= 0 && shipclass_flags.contains(relevant_ship_class_idx)) { + weapon_value *= armor_flags[relevant_ship_class_idx]; + } + if (relevant_weapon_class_idx >= 0 && weaponclass_flags.contains(relevant_weapon_class_idx)) { + weapon_value *= armor_flags[relevant_weapon_class_idx]; + } if (wip_i == swp->current_primary_bank) { weapon_value *= aip->primary_selection_status_quo_bias; @@ -6490,7 +6522,7 @@ int ai_fire_primary_weapon(object *objp) if ( aip->targeted_subsys != NULL ) { flags = Weapon::Info_Flags::Puncture; } - ai_select_primary_weapon(objp, enemy_objp, flags); + ai_select_primary_weapon_configurable(objp, enemy_objp, flags); ship_primary_changed(shipp); // AL: maybe send multiplayer information when AI ship changes primaries aip->primary_select_timestamp = timestamp(5 * MILLISECONDS_PER_SECOND); // Maybe change primary weapon five seconds from now. } diff --git a/code/weapon/weapon.h b/code/weapon/weapon.h index 8d031d8b1be..56a7018a093 100644 --- a/code/weapon/weapon.h +++ b/code/weapon/weapon.h @@ -372,6 +372,15 @@ struct WeaponLaunchCurveData { float target_radius; }; +enum PrimarySelectionTargetType { + ARMOR, + SHIP_TYPE, + SHIP_CLASS, + WEAPON_CLASS, + + MAX, +}; + struct weapon_info; extern SCP_vector Weapon_info; @@ -720,14 +729,7 @@ struct weapon_info animation::ModelAnimationSet animations; - enum class PrimarySelectionTargetType { - ARMOR, - SHIP_TYPE, - SHIP_CLASS, - WEAPON_CLASS, - }; - - std::array, 4> primary_selection_target_flags; + std::array, PrimarySelectionTargetType::MAX> primary_selection_target_flags; enum class WeaponLaunchCurveOutputs { // outputs diff --git a/code/weapon/weapons.cpp b/code/weapon/weapons.cpp index 9ff9b84179e..0c794314492 100644 --- a/code/weapon/weapons.cpp +++ b/code/weapon/weapons.cpp @@ -2133,22 +2133,34 @@ int parse_weapon(int subtype, bool replace, const char *filename) float value; while (optional_string("+")) { stuff_string(type, F_NAME); + stuff_string(fname, F_NAME, NAME_LENGTH); + stuff_float(&value); if (type == "ARMOR:") { - stuff_string(fname, F_NAME, NAME_LENGTH); instance_index = armor_type_get_idx(fname); - stuff_float(&value); + if (instance_index < 0) { + error_display(0, "Weapon '%s', primary selection target flags: '%s' is not a valid armor type!\n", wip->name, fname); + } wip->primary_selection_target_flags[PrimarySelectionTargetType::ARMOR].emplace(instance_index, value); } else if (type == "SHIP_TYPE:") { - stuff_string(fname, F_NAME, NAME_LENGTH); instance_index = ship_type_name_lookup(fname); - stuff_float(&value); - wip->primary_selection_target_flags[PrimarySelectionTargetType::ARMOR].emplace(instance_index, value); + if (instance_index < 0) { + error_display(0, "Weapon '%s', primary selection target flags: '%s' is not a valid ship type!\n", wip->name, fname); + } + wip->primary_selection_target_flags[PrimarySelectionTargetType::SHIP_TYPE].emplace(instance_index, value); } else if (type == "SHIP_CLASS:") { - + instance_index = ship_info_lookup(fname); + if (instance_index < 0) { + error_display(0, "Weapon '%s', primary selection target flags: '%s' is not a valid ship class!\n", wip->name, fname); + } + wip->primary_selection_target_flags[PrimarySelectionTargetType::SHIP_CLASS].emplace(instance_index, value); } else if (type == "WEAPON_CLASS:") { - + instance_index = weapon_info_lookup(fname); + if (instance_index < 0) { + error_display(0, "Weapon '%s', primary selection target flags: '%s' is not a valid weapon class!\n", wip->name, fname); + } + wip->primary_selection_target_flags[PrimarySelectionTargetType::WEAPON_CLASS].emplace(instance_index, value); } else { - + error_display(0, "Invalid primary selection target flag type '%s' in weapon '%s'!", type.c_str(), wip->name); } } } @@ -10173,6 +10185,10 @@ void weapon_info::reset() // Reset using default constructor this->impact_decal = decals::creation_info(); + for (i = 0; i < PrimarySelectionTargetType::MAX; i++) { + this->primary_selection_target_flags[i] = {}; + } + this->on_create_program = actions::ProgramSet(); } From 333fcd3c6eb9e9988afc2d521143070153021ea7 Mon Sep 17 00:00:00 2001 From: Kestrellius <902X@comcast.net> Date: Mon, 10 Aug 2026 21:55:30 -0700 Subject: [PATCH 04/14] aip flags and shields --- code/ai/ai.h | 6 ++- code/ai/ai_flags.h | 1 + code/ai/ai_profiles.cpp | 4 +- code/ai/aicode.cpp | 88 ++++++++++++++++++++++++++++++-------- code/debris/debris.h | 1 + code/decals/decals.h | 2 + code/graphics/openxr.cpp | 1 + code/lab/dialogs/lab_ui.h | 1 + code/mod_table/mod_table.h | 4 +- code/ship/shield.cpp | 2 +- 10 files changed, 86 insertions(+), 24 deletions(-) diff --git a/code/ai/ai.h b/code/ai/ai.h index 67ebb4eb488..7116b9d8f71 100644 --- a/code/ai/ai.h +++ b/code/ai/ai.h @@ -21,6 +21,7 @@ #include "parse/sexp.h" #include "physics/physics.h" #include "ship/ship_flags.h" +#include "utils/RandomRange.h" class ship_weapon; class ship_subsys; @@ -142,7 +143,7 @@ typedef struct ai_class { float ai_secondary_range_mult[NUM_SKILL_LEVELS]; bool ai_class_autoscale; //Defaults to true, but can be turned off in order to disable extra scaling of some AI behaviors //based on AI class index - float primary_selection_random_factor; + ::util::ParsedRandomFloatRange primary_selection_random_factor; float primary_selection_oneshot_modifier; float primary_selection_status_quo_bias; @@ -344,6 +345,7 @@ typedef struct ai_info { float prev_dot_to_goal; // dot of fvec to goal last frame, used to see if making progress towards goal. vec3d goal_point; // Used in AIM_SAFETY, AIM_STILL and in circling. vec3d prev_goal_point; // Previous location of goal point, used at least for evading. + float enemy_shield_is_down; //Values copied from the AI class float ai_accuracy, ai_evasion, ai_courage, ai_patience; @@ -377,7 +379,7 @@ typedef struct ai_info { int ai_chance_to_use_missiles_on_plr; float ai_max_aim_update_delay; float ai_turret_max_aim_update_delay; - float primary_selection_random_factor; + ::util::ParsedRandomFloatRange primary_selection_random_factor; float primary_selection_oneshot_modifier; float primary_selection_status_quo_bias; flagset ai_profile_flags; //Holds AI_Profiles flags (possibly overriden by AI class) that actually apply to AI diff --git a/code/ai/ai_flags.h b/code/ai/ai_flags.h index 41c6d0c8877..9a7568c47ed 100644 --- a/code/ai/ai_flags.h +++ b/code/ai/ai_flags.h @@ -127,6 +127,7 @@ namespace AI { Require_turret_to_have_target_in_fov, Shockwaves_damage_small_ship_subsystems, Smart_afterburner_management, + Always_do_primary_select_when_target_change, Configurable_primary_weapon_selection, Smart_primary_weapon_selection, Smart_secondary_weapon_selection, diff --git a/code/ai/ai_profiles.cpp b/code/ai/ai_profiles.cpp index 6b78be57cd2..4b5759fc1b3 100644 --- a/code/ai/ai_profiles.cpp +++ b/code/ai/ai_profiles.cpp @@ -382,7 +382,9 @@ void parse_ai_profiles_tbl(const char *filename) set_flag(profile, "$big ships can attack beam turrets on untargeted ships:", AI::Profile_Flags::Big_ships_can_attack_beam_turrets_on_untargeted_ships); - set_flag(profile, "$Configurable primary weapon selection:", AI::Profile_Flags::Configurable_primary_weapon_selection); + set_flag(profile, "$always do primary select when target change:", AI::Profile_Flags::Always_do_primary_select_when_target_change); + + set_flag(profile, "$configurable primary weapon selection:", AI::Profile_Flags::Configurable_primary_weapon_selection); set_flag(profile, "$smart primary weapon selection:", AI::Profile_Flags::Smart_primary_weapon_selection); diff --git a/code/ai/aicode.cpp b/code/ai/aicode.cpp index e86cd31ac2a..0957ff071d6 100644 --- a/code/ai/aicode.cpp +++ b/code/ai/aicode.cpp @@ -691,7 +691,7 @@ void init_ai_class(ai_class *aicp) aicp->ai_secondary_range_mult[i] = FLT_MIN; } aicp->ai_class_autoscale = true; //Retail behavior is to do the stupid autoscaling - aicp->primary_selection_random_factor = 1.0f; + aicp->primary_selection_random_factor = ::util::UniformFloatRange(1.0f); aicp->primary_selection_oneshot_modifier = 2.0f; aicp->primary_selection_status_quo_bias = 1.0f; } @@ -801,7 +801,7 @@ void parse_ai_class() stuff_boolean(&aicp->ai_class_autoscale); if (optional_string("$Primary selection random factor:")) - stuff_float(&aicp->primary_selection_random_factor); + aicp->primary_selection_random_factor = ::util::ParsedRandomFloatRange::parseRandomRange(); if (optional_string("$Primary selection oneshot modifier:")) stuff_float(&aicp->primary_selection_oneshot_modifier); @@ -919,6 +919,8 @@ void parse_ai_class() parse_float_list(aicp->ai_turret_max_aim_update_delay, NUM_SKILL_LEVELS); set_aic_flag(aicp, "$big ships can attack beam turrets on untargeted ships:", AI::Profile_Flags::Big_ships_can_attack_beam_turrets_on_untargeted_ships); + + set_aic_flag(aicp, "$always do primary select when target change:", AI::Profile_Flags::Always_do_primary_select_when_target_change); set_aic_flag(aicp, "$configurable primary weapon selection:", AI::Profile_Flags::Configurable_primary_weapon_selection); @@ -1557,6 +1559,10 @@ vec3d ai_get_acc_limit(vec3d* vel_limit, const object* objp) { } +int ai_select_primary_weapon(object *objp, object *other_objp, Weapon::Info_Flags flags); + +int ai_select_primary_weapon_configurable(object *objp, object *other_objp, Weapon::Info_Flags flags, float relevant_shields_left); + // Set aip->target_objnum to objnum // Update aip->previous_target_objnum. // If new target (objnum) is different than old target, reset target_time. @@ -1589,15 +1595,18 @@ int set_target_objnum(ai_info *aip, int objnum) aip->target_signature = (objnum >= 0) ? Objects[objnum].signature : -1; // clear targeted subsystem set_targeted_subsys(aip, NULL, -1); + if (aip->ai_profile_flags[AI::Profile_Flags::Always_do_primary_select_when_target_change]) { + object *target_object = nullptr; + if (aip->target_objnum >= 0) { + target_object = &Objects[aip->target_objnum]; + } + ai_select_primary_weapon_configurable(&Objects[Ships[aip->shipnum].objnum], target_object, Weapon::Info_Flags::Puncture, -1.0f); + } } return aip->target_objnum; } -int ai_select_primary_weapon(object *objp, object *other_objp, Weapon::Info_Flags flags); - -int ai_select_primary_weapon_configurable(object *objp, object *other_objp, Weapon::Info_Flags flags); - /** * Make new_subsys the targeted subsystem of ship *aip. */ @@ -1611,10 +1620,10 @@ ship_subsys *set_targeted_subsys(ai_info *aip, ship_subsys *new_subsys, int pare if ( new_subsys ) { // Make new_subsys target - if (new_subsys->system_info->type == SUBSYSTEM_ENGINE) { + if (new_subsys->system_info->type == SUBSYSTEM_ENGINE || aip->ai_profile_flags[AI::Profile_Flags::Always_do_primary_select_when_target_change]) { if ( aip != Player_ai ) { Assert( aip->shipnum >= 0 ); - ai_select_primary_weapon_configurable(&Objects[Ships[aip->shipnum].objnum], &Objects[parent_objnum], Weapon::Info_Flags::Puncture); + ai_select_primary_weapon_configurable(&Objects[Ships[aip->shipnum].objnum], &Objects[parent_objnum], Weapon::Info_Flags::Puncture, -1.0f); ship_primary_changed(&Ships[aip->shipnum]); // AL: maybe send multiplayer information when AI ship changes primaries } } @@ -6016,7 +6025,7 @@ int ai_select_primary_weapon(object *objp, object *other_objp, Weapon::Info_Flag } // More comprehensive version of primary selection, with high modder control. -int ai_select_primary_weapon_configurable(object *objp, object *other_objp, Weapon::Info_Flags flags) +int ai_select_primary_weapon_configurable(object *objp, object *other_objp, Weapon::Info_Flags flags, float relevant_shields_left = -1.0f) { ship *shipp = &Ships[objp->instance]; ship *target_shipp = nullptr; @@ -6040,17 +6049,25 @@ int ai_select_primary_weapon_configurable(object *objp, object *other_objp, Weap bool is_beam; ship_subsys *target_subsys = Ai_info[shipp->ai_index].targeted_subsys; - float relevant_shields_left = 100.0f; //TODO: get shield quadrant stuff int relevant_armor_type_idx = -1; int relevant_ship_type_idx = -1; int relevant_ship_class_idx = -1; int relevant_weapon_class_idx = -1; + if (relevant_shields_left < 0.0f) { + vec3d ship_local_pos = objp->pos; + vm_vec_sub2(&ship_local_pos, &other_objp->pos); + vm_vec_rotate(&ship_local_pos, &ship_local_pos, &other_objp->orient); + int relevant_quadrant = get_quadrant(&ship_local_pos, other_objp); + if (relevant_quadrant > 0 && relevant_quadrant < sz2i(other_objp->shield_quadrant.size())) { + relevant_shields_left = shield_get_quad(other_objp, relevant_quadrant) - ship_shield_hitpoint_threshold(other_objp, false); + } + } + SCP_unordered_map weapon_values = {}; - // TODO: handle non-ship target cases - - for (auto& wip_i : swp->primary_bank_weapons) { + for (int i; i < MAX_SHIP_PRIMARY_BANKS; i++) { + int wip_i = swp->primary_bank_weapons[i]; if (wip_i < 0) { continue; } @@ -6065,7 +6082,6 @@ int ai_select_primary_weapon_configurable(object *objp, object *other_objp, Weap shockwave_damage = sci->damage; } - if (target_shipp) { relevant_ship_type_idx = Ship_info[target_shipp->ship_info_index].class_type; relevant_ship_class_idx = target_shipp->ship_info_index; @@ -6137,7 +6153,22 @@ int ai_select_primary_weapon_configurable(object *objp, object *other_objp, Weap dph += shockwave_damage; - //TODO: decide whether to take $Shots (/$Cycle_multishot) into account + int shot_count; + FiringPattern firing_pattern; + if (sinfop->flags[Ship::Info_Flags::Dyn_primary_linking]) { + firing_pattern = sinfop->dyn_firing_patterns_allowed[i][swp->dynamic_firing_pattern[i]]; + } else { + firing_pattern = wip->firing_pattern; + } + if (sinfop->flags[Ship::Info_Flags::Dyn_primary_linking]) { + shot_count = wip->cycle_multishot; + } else if (wip->b_info.beam_shots) { + shot_count = wip->shots; + } else if (firing_pattern != FiringPattern::STANDARD) { + shot_count = wip->cycle_multishot; + } else { + shot_count = wip->shots; + } ai_info *aip = &Ai_info[shipp->ai_index]; float effective_dps; @@ -6169,9 +6200,10 @@ int ai_select_primary_weapon_configurable(object *objp, object *other_objp, Weap if (fire_rate * wip->energy_consumed > (shipp->max_weapon_regen_per_second * sinfop->max_weapon_reserve)) { fire_rate = (fire_rate + ((shipp->max_weapon_regen_per_second * sinfop->max_weapon_reserve) / wip->energy_consumed)) / 2.0f; } - effective_dps = (dph * burst_shots) * fire_rate; + int num_slots = model_get(sinfop->model_num)->gun_banks[i].num_slots; + effective_dps = (dph * i2fl(shot_count) * i2fl(num_slots) * burst_shots) * fire_rate; - float weapon_value = std::max(effective_dps, oneshot_value) * aip->primary_selection_random_factor; + float weapon_value = std::max(effective_dps, oneshot_value) * aip->primary_selection_random_factor.next(); SCP_unordered_map armor_flags = wip->primary_selection_target_flags[PrimarySelectionTargetType::ARMOR]; SCP_unordered_map shiptype_flags = wip->primary_selection_target_flags[PrimarySelectionTargetType::SHIP_TYPE]; @@ -6516,13 +6548,30 @@ int ai_fire_primary_weapon(object *objp) enemy_sip = NULL; } + bool shield_changed = false; + float relevant_shields_left = -1.0f; + if (aip->ai_profile_flags[AI::Profile_Flags::Always_do_primary_select_when_target_change] && enemy_objp) { + vec3d ship_local_pos = objp->pos; + vm_vec_sub2(&ship_local_pos, &enemy_objp->pos); + vm_vec_rotate(&ship_local_pos, &ship_local_pos, &enemy_objp->orient); + int relevant_quadrant = get_quadrant(&ship_local_pos, enemy_objp); + if (relevant_quadrant > 0 && relevant_quadrant < sz2i(enemy_objp->shield_quadrant.size())) { + relevant_shields_left = std::max(shield_get_quad(enemy_objp, relevant_quadrant) - ship_shield_hitpoint_threshold(enemy_objp, false), 0.0f); + bool shield_is_down = relevant_shields_left > 0.0f; + if (shield_is_down != aip->enemy_shield_is_down) { + shield_changed = true; + aip->enemy_shield_is_down = shield_is_down; + } + } + } + //plieblang - added check for size of Preferred_primaries to force reevaluation if good-primary-time has been used in the meantime - if ( (swp->current_primary_bank < 0) || (swp->current_primary_bank >= swp->num_primary_banks) || timestamp_elapsed(aip->primary_select_timestamp)) { + if ( (swp->current_primary_bank < 0) || (swp->current_primary_bank >= swp->num_primary_banks) || timestamp_elapsed(aip->primary_select_timestamp) || shield_changed) { Weapon::Info_Flags flags = Weapon::Info_Flags::NUM_VALUES; if ( aip->targeted_subsys != NULL ) { flags = Weapon::Info_Flags::Puncture; } - ai_select_primary_weapon_configurable(objp, enemy_objp, flags); + ai_select_primary_weapon_configurable(objp, enemy_objp, flags, relevant_shields_left); ship_primary_changed(shipp); // AL: maybe send multiplayer information when AI ship changes primaries aip->primary_select_timestamp = timestamp(5 * MILLISECONDS_PER_SECOND); // Maybe change primary weapon five seconds from now. } @@ -16005,6 +16054,7 @@ void init_ai_object(int objnum) aip->goal_check_time = timestamp(0); aip->last_predicted_enemy_pos = near_vec; aip->prev_goal_point = near_vec; + aip->enemy_shield_is_down = false; aip->goal_point = near_vec; aip->time_enemy_in_range = 0.0f; aip->time_enemy_near = 0.0f; diff --git a/code/debris/debris.h b/code/debris/debris.h index 7377f0e7fad..a97b4f99fbd 100644 --- a/code/debris/debris.h +++ b/code/debris/debris.h @@ -15,6 +15,7 @@ #include "globalincs/pstypes.h" #include "globalincs/flagset.h" #include "gamesnd/gamesnd.h" +#include "ship/ship.h" class object; struct CFILE; diff --git a/code/decals/decals.h b/code/decals/decals.h index 4a737930814..f84f0c6953f 100644 --- a/code/decals/decals.h +++ b/code/decals/decals.h @@ -7,6 +7,8 @@ #include "utils/RandomRange.h" #include "utils/reset_on_move.h" +class ship; + namespace decals { class DecalDefinition { diff --git a/code/graphics/openxr.cpp b/code/graphics/openxr.cpp index e878ec7a16f..1a640ba01c6 100644 --- a/code/graphics/openxr.cpp +++ b/code/graphics/openxr.cpp @@ -6,6 +6,7 @@ #include "mod_table/mod_table.h" #include "render/3d.h" #include "starfield/starfield.h" +#include "hud/hudparse.h" std::unique_ptr Stars_XRBuffer; diff --git a/code/lab/dialogs/lab_ui.h b/code/lab/dialogs/lab_ui.h index b70c0e89a81..d133993b4be 100644 --- a/code/lab/dialogs/lab_ui.h +++ b/code/lab/dialogs/lab_ui.h @@ -3,6 +3,7 @@ #include "model/model.h" #include "model/animation/modelanimation.h" #include "species_defs/species_defs.h" +#include "weapon/weapon.h" enum class LabTurretAimType { RANDOM, diff --git a/code/mod_table/mod_table.h b/code/mod_table/mod_table.h index d74e35b0395..f2a386500cb 100644 --- a/code/mod_table/mod_table.h +++ b/code/mod_table/mod_table.h @@ -12,7 +12,9 @@ #include "globalincs/pstypes.h" #include "globalincs/systemvars.h" #include "graphics/2d.h" -#include "hud/hudtarget.h" +#include "globalincs/version.h" + +enum class leadIndicatorBehavior; // Typedef for Overhead View styles typedef enum { diff --git a/code/ship/shield.cpp b/code/ship/shield.cpp index 218dacd32cb..f8e291e4888 100644 --- a/code/ship/shield.cpp +++ b/code/ship/shield.cpp @@ -948,7 +948,7 @@ int get_quadrant(const vec3d *hit_pnt, const object *shipobjp) float closest_dist = FLT_MAX; for (unsigned int i=0; iinstance].shield_points.size(); i++) { - float dist = vm_vec_dist(hit_pnt, &Ships[shipobjp->instance].shield_points.at(i)); + float dist = vm_vec_dist_squared(hit_pnt, &Ships[shipobjp->instance].shield_points.at(i)); if (dist < closest_dist) { closest = i; From 64f5c0db72255a2dc23e7129e64a928aac10867b Mon Sep 17 00:00:00 2001 From: Kestrellius <902X@comcast.net> Date: Tue, 11 Aug 2026 09:36:33 -0700 Subject: [PATCH 05/14] bugfixing --- code/ai/aicode.cpp | 59 ++++++++++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 25 deletions(-) diff --git a/code/ai/aicode.cpp b/code/ai/aicode.cpp index 0957ff071d6..47913e87bca 100644 --- a/code/ai/aicode.cpp +++ b/code/ai/aicode.cpp @@ -5747,7 +5747,7 @@ static int ai_select_primary_weapon_OLD(const object *objp, Weapon::Info_Flags f return swp->current_primary_bank; } -std::optional select_primary_setup(ship *shipp, ship *other_shipp, ship_weapon *swp, object *other_objp) +std::optional select_primary_setup(ship *shipp, ship *target_shipp, ship_weapon *swp, object *other_objp) { // Debugging if (other_objp==NULL) @@ -5763,18 +5763,14 @@ std::optional select_primary_setup(ship *shipp, ship *other_shipp, ship_wea if (swp->num_primary_banks <= 0) return -1; - bool other_is_ship = (other_objp->type == OBJ_SHIP); - - if (other_is_ship) + if (target_shipp) { - other_shipp = &Ships[other_objp->instance]; - //if the good-primary-time sexp has been used, return that weapon immediately //if smart primary weapon selection isn't set, turning this override behavior off might not do anything //however this seems acceptable for (const auto &ppi : Preferred_primary_info) { - if (ppi.subject.matches(shipp) && ppi.target.matches(other_shipp)) + if (ppi.subject.matches(shipp) && ppi.target.matches(target_shipp)) { int weapon_idx = ppi.weapon_index; @@ -5825,6 +5821,10 @@ int ai_select_primary_weapon(object *objp, object *other_objp, Weapon::Info_Flag ship *other_shipp = nullptr; ship_weapon *swp = &shipp->weapons; + if (other_objp->type == OBJ_SHIP) { + other_shipp = &Ships[other_objp->instance]; + } + auto early_return_value = select_primary_setup(shipp, other_shipp, swp, other_objp); if (early_return_value.has_value()) { return early_return_value.value(); @@ -6032,6 +6032,10 @@ int ai_select_primary_weapon_configurable(object *objp, object *other_objp, Weap ship_weapon *swp = &shipp->weapons; ship_info *sinfop = &Ship_info[shipp->ship_info_index]; + if (other_objp->type == OBJ_SHIP) { + target_shipp = &Ships[other_objp->instance]; + } + //if we're not using configurable, use the previous version of this function instead. if (!(Ai_info[shipp->ai_index].ai_profile_flags[AI::Profile_Flags::Configurable_primary_weapon_selection])) { @@ -6048,7 +6052,10 @@ int ai_select_primary_weapon_configurable(object *objp, object *other_objp, Weap bool has_shockwave; bool is_beam; - ship_subsys *target_subsys = Ai_info[shipp->ai_index].targeted_subsys; + ship_subsys *target_subsys = nullptr; + if (Ai_info[shipp->ai_index].targeted_subsys) { + target_subsys = Ai_info[shipp->ai_index].targeted_subsys; + } int relevant_armor_type_idx = -1; int relevant_ship_type_idx = -1; int relevant_ship_class_idx = -1; @@ -6059,14 +6066,14 @@ int ai_select_primary_weapon_configurable(object *objp, object *other_objp, Weap vm_vec_sub2(&ship_local_pos, &other_objp->pos); vm_vec_rotate(&ship_local_pos, &ship_local_pos, &other_objp->orient); int relevant_quadrant = get_quadrant(&ship_local_pos, other_objp); - if (relevant_quadrant > 0 && relevant_quadrant < sz2i(other_objp->shield_quadrant.size())) { + if (relevant_quadrant >= 0 && relevant_quadrant < sz2i(other_objp->shield_quadrant.size())) { relevant_shields_left = shield_get_quad(other_objp, relevant_quadrant) - ship_shield_hitpoint_threshold(other_objp, false); } } SCP_unordered_map weapon_values = {}; - for (int i; i < MAX_SHIP_PRIMARY_BANKS; i++) { + for (int i = 0; i < MAX_SHIP_PRIMARY_BANKS; i++) { int wip_i = swp->primary_bank_weapons[i]; if (wip_i < 0) { continue; @@ -6082,35 +6089,40 @@ int ai_select_primary_weapon_configurable(object *objp, object *other_objp, Weap shockwave_damage = sci->damage; } - if (target_shipp) { + if (target_shipp != nullptr) { relevant_ship_type_idx = Ship_info[target_shipp->ship_info_index].class_type; relevant_ship_class_idx = target_shipp->ship_info_index; if (relevant_shields_left > 0.0f) { damage_scale = weapon_get_damage_scale(wip, nullptr, other_objp); dph *= damage_scale; relevant_armor_type_idx = target_shipp->shield_armor_type_idx; - dph = Armor_types[relevant_armor_type_idx].GetDamage(dph, wip->damage_type_idx, 1.0, is_beam); + if (relevant_armor_type_idx >= 0) { + dph = Armor_types[relevant_armor_type_idx].GetDamage(dph, wip->damage_type_idx, 1.0, is_beam); + } if (!is_beam || Beams_use_damage_factors) { dph *= wip->shield_factor; } - } else if (target_subsys) { + } else if (target_subsys != nullptr) { if (!is_beam || Beams_use_damage_factors) { - relevant_armor_type_idx = target_subsys->armor_type_idx; if (target_subsys->flags[Ship::Subsystem_Flags::Damage_as_hull]) { dph *= wip->armor_factor; - dph = Armor_types[relevant_armor_type_idx].GetDamage(dph, wip->damage_type_idx, 1.0, is_beam); } else { dph *= wip->subsystem_factor; - dph = Armor_types[relevant_armor_type_idx].GetDamage(dph, wip->damage_type_idx, 1.0, is_beam); } } + relevant_armor_type_idx = target_subsys->armor_type_idx; + if (relevant_armor_type_idx >= 0) { + dph = Armor_types[relevant_armor_type_idx].GetDamage(dph, wip->damage_type_idx, 1.0, is_beam); + } } else { if (damage_scale == -1.0f) { damage_scale = weapon_get_damage_scale(wip, nullptr, other_objp); } dph *= damage_scale; relevant_armor_type_idx = target_shipp->armor_type_idx; - dph = Armor_types[relevant_armor_type_idx].GetDamage(dph, wip->damage_type_idx, 1.0, is_beam); + if (relevant_armor_type_idx >= 0) { + dph = Armor_types[relevant_armor_type_idx].GetDamage(dph, wip->damage_type_idx, 1.0, is_beam); + } if (!is_beam || Beams_use_damage_factors) { if (wip->wi_flags[Weapon::Info_Flags::Puncture]) { dph /= 4; @@ -6126,12 +6138,8 @@ int ai_select_primary_weapon_configurable(object *objp, object *other_objp, Weap } shockwave_damage *= damage_scale; } - if (relevant_shields_left) { - shockwave_damage = Armor_types[target_shipp->shield_armor_type_idx].GetDamage(shockwave_damage, sci->damage_type_idx, 1.0, is_beam); - } else if (target_subsys) { - shockwave_damage = Armor_types[target_subsys->armor_type_idx].GetDamage(shockwave_damage, sci->damage_type_idx, 1.0, is_beam); - } else { - shockwave_damage = Armor_types[target_shipp->armor_type_idx].GetDamage(shockwave_damage, sci->damage_type_idx, 1.0, is_beam); + if (relevant_armor_type_idx >= 0) { + shockwave_damage = Armor_types[relevant_armor_type_idx].GetDamage(shockwave_damage, sci->damage_type_idx, 1.0, is_beam); } } } else if ( other_objp->type == OBJ_WEAPON ) { @@ -6223,10 +6231,10 @@ int ai_select_primary_weapon_configurable(object *objp, object *other_objp, Weap weapon_value *= armor_flags[relevant_weapon_class_idx]; } - if (wip_i == swp->current_primary_bank) { + if (i == swp->current_primary_bank) { weapon_value *= aip->primary_selection_status_quo_bias; } - weapon_values.emplace(wip_i, weapon_value); + weapon_values.emplace(i, weapon_value); } std::pair best_pair = std::make_pair(-1, 0.0f); for (auto pair : weapon_values) { @@ -6234,6 +6242,7 @@ int ai_select_primary_weapon_configurable(object *objp, object *other_objp, Weap best_pair = pair; } } + swp->current_primary_bank = best_pair.first; return best_pair.first; } From bcec774bdd7cf4a0b31857db9ed62ba6a107d3dd Mon Sep 17 00:00:00 2001 From: Kestrellius <902X@comcast.net> Date: Tue, 11 Aug 2026 15:32:50 -0700 Subject: [PATCH 06/14] cleanup --- code/ai/aicode.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/code/ai/aicode.cpp b/code/ai/aicode.cpp index 47913e87bca..96a1fca9efe 100644 --- a/code/ai/aicode.cpp +++ b/code/ai/aicode.cpp @@ -5763,6 +5763,10 @@ std::optional select_primary_setup(ship *shipp, ship *target_shipp, ship_we if (swp->num_primary_banks <= 0) return -1; + // if we only have one bank to choose from, just short-circuit + if (swp->num_primary_banks == 1 && swp->current_primary_bank == 0) + return swp->current_primary_bank; + if (target_shipp) { //if the good-primary-time sexp has been used, return that weapon immediately @@ -6073,7 +6077,7 @@ int ai_select_primary_weapon_configurable(object *objp, object *other_objp, Weap SCP_unordered_map weapon_values = {}; - for (int i = 0; i < MAX_SHIP_PRIMARY_BANKS; i++) { + for (int i = 0; i < swp->num_primary_banks; i++) { int wip_i = swp->primary_bank_weapons[i]; if (wip_i < 0) { continue; From a95511eb4f8c5d51ff54ad109920343301a31e2f Mon Sep 17 00:00:00 2001 From: Kestrellius <902X@comcast.net> Date: Wed, 12 Aug 2026 22:17:29 -0700 Subject: [PATCH 07/14] resolve parsing situation --- code/ai/aicode.cpp | 21 +++++------ code/weapon/weapons.cpp | 78 ++++++++++++++++++++++++++--------------- 2 files changed, 58 insertions(+), 41 deletions(-) diff --git a/code/ai/aicode.cpp b/code/ai/aicode.cpp index 96a1fca9efe..bf38e2a8a9b 100644 --- a/code/ai/aicode.cpp +++ b/code/ai/aicode.cpp @@ -6217,22 +6217,17 @@ int ai_select_primary_weapon_configurable(object *objp, object *other_objp, Weap float weapon_value = std::max(effective_dps, oneshot_value) * aip->primary_selection_random_factor.next(); - SCP_unordered_map armor_flags = wip->primary_selection_target_flags[PrimarySelectionTargetType::ARMOR]; - SCP_unordered_map shiptype_flags = wip->primary_selection_target_flags[PrimarySelectionTargetType::SHIP_TYPE]; - SCP_unordered_map shipclass_flags = wip->primary_selection_target_flags[PrimarySelectionTargetType::SHIP_CLASS]; - SCP_unordered_map weaponclass_flags = wip->primary_selection_target_flags[PrimarySelectionTargetType::WEAPON_CLASS]; - - if (relevant_armor_type_idx >= 0 && armor_flags.contains(relevant_armor_type_idx)) { - weapon_value *= armor_flags[relevant_armor_type_idx]; + if (wip->primary_selection_target_flags[PrimarySelectionTargetType::ARMOR].contains(relevant_armor_type_idx)) { + weapon_value *= wip->primary_selection_target_flags[PrimarySelectionTargetType::ARMOR][relevant_armor_type_idx]; } - if (relevant_ship_type_idx >= 0 && shiptype_flags.contains(relevant_ship_type_idx)) { - weapon_value *= armor_flags[relevant_ship_type_idx]; + if (wip->primary_selection_target_flags[PrimarySelectionTargetType::SHIP_TYPE].contains(relevant_ship_type_idx)) { + weapon_value *= wip->primary_selection_target_flags[PrimarySelectionTargetType::SHIP_TYPE][relevant_ship_type_idx]; } - if (relevant_ship_class_idx >= 0 && shipclass_flags.contains(relevant_ship_class_idx)) { - weapon_value *= armor_flags[relevant_ship_class_idx]; + if (wip->primary_selection_target_flags[PrimarySelectionTargetType::SHIP_CLASS].contains(relevant_ship_class_idx)) { + weapon_value *= wip->primary_selection_target_flags[PrimarySelectionTargetType::SHIP_CLASS][relevant_ship_class_idx]; } - if (relevant_weapon_class_idx >= 0 && weaponclass_flags.contains(relevant_weapon_class_idx)) { - weapon_value *= armor_flags[relevant_weapon_class_idx]; + if (wip->primary_selection_target_flags[PrimarySelectionTargetType::WEAPON_CLASS].contains(relevant_weapon_class_idx)) { + weapon_value *= wip->primary_selection_target_flags[PrimarySelectionTargetType::WEAPON_CLASS][relevant_weapon_class_idx]; } if (i == swp->current_primary_bank) { diff --git a/code/weapon/weapons.cpp b/code/weapon/weapons.cpp index 0c794314492..59aebc75c27 100644 --- a/code/weapon/weapons.cpp +++ b/code/weapon/weapons.cpp @@ -773,6 +773,8 @@ static particle::ParticleEffectHandle convertLegacyPspewBuffer(const pspew_legac hasAnim ? bm_load_either(pspew_buffer.particle_spew_anim.c_str()) : particle::Anim_bitmap_id_smoke)); //Bitmap or Anim } +SCP_unordered_map, PrimarySelectionTargetType::MAX>> primary_selection_target_flags_temp; + /** * Parse the information for a specific ship type. * Return weapon index if successful, otherwise return -1 @@ -2128,37 +2130,27 @@ int parse_weapon(int subtype, bool replace, const char *filename) } if (optional_string("$Primary Selection Target Flags:")) { + const int wi_index = static_cast(wip - Weapon_info.data()); SCP_string type; - int instance_index; + SCP_string type_name; float value; - while (optional_string("+")) { + for (int i = 0; i < PrimarySelectionTargetType::MAX; i++) { + primary_selection_target_flags_temp[i] = {}; + } + while (optional_string("+Condition Type:")) { stuff_string(type, F_NAME); - stuff_string(fname, F_NAME, NAME_LENGTH); + required_string("+Target Class:"); + stuff_string(type_name, F_NAME); + required_string("+Value Multiplier:"); stuff_float(&value); - if (type == "ARMOR:") { - instance_index = armor_type_get_idx(fname); - if (instance_index < 0) { - error_display(0, "Weapon '%s', primary selection target flags: '%s' is not a valid armor type!\n", wip->name, fname); - } - wip->primary_selection_target_flags[PrimarySelectionTargetType::ARMOR].emplace(instance_index, value); - } else if (type == "SHIP_TYPE:") { - instance_index = ship_type_name_lookup(fname); - if (instance_index < 0) { - error_display(0, "Weapon '%s', primary selection target flags: '%s' is not a valid ship type!\n", wip->name, fname); - } - wip->primary_selection_target_flags[PrimarySelectionTargetType::SHIP_TYPE].emplace(instance_index, value); - } else if (type == "SHIP_CLASS:") { - instance_index = ship_info_lookup(fname); - if (instance_index < 0) { - error_display(0, "Weapon '%s', primary selection target flags: '%s' is not a valid ship class!\n", wip->name, fname); - } - wip->primary_selection_target_flags[PrimarySelectionTargetType::SHIP_CLASS].emplace(instance_index, value); - } else if (type == "WEAPON_CLASS:") { - instance_index = weapon_info_lookup(fname); - if (instance_index < 0) { - error_display(0, "Weapon '%s', primary selection target flags: '%s' is not a valid weapon class!\n", wip->name, fname); - } - wip->primary_selection_target_flags[PrimarySelectionTargetType::WEAPON_CLASS].emplace(instance_index, value); + if (type == "ARMOR") { + primary_selection_target_flags_temp[wi_index][PrimarySelectionTargetType::ARMOR].emplace(type_name, value); + } else if (type == "SHIP TYPE") { + primary_selection_target_flags_temp[wi_index][PrimarySelectionTargetType::SHIP_TYPE].emplace(type_name, value); + } else if (type == "SHIP CLASS") { + primary_selection_target_flags_temp[wi_index][PrimarySelectionTargetType::SHIP_CLASS].emplace(type_name, value); + } else if (type == "WEAPON CLASS") { + primary_selection_target_flags_temp[wi_index][PrimarySelectionTargetType::WEAPON_CLASS].emplace(type_name, value); } else { error_display(0, "Invalid primary selection target flag type '%s' in weapon '%s'!", type.c_str(), wip->name); } @@ -4400,6 +4392,34 @@ int parse_weapon(int subtype, bool replace, const char *filename) return w_id; } +void populate_primary_selection_flags() +{ + for (int i = 0; i < sz2i(Weapon_info.size()); i++) { + weapon_info *wip = &Weapon_info[i]; + for (int flavor; flavor < PrimarySelectionTargetType::MAX; flavor++) { + for (auto& [index_name, mult] : primary_selection_target_flags_temp[i][flavor]) { + int index; + switch (flavor) { + case PrimarySelectionTargetType::ARMOR: + index = armor_type_get_idx(index_name.c_str()); + break; + case PrimarySelectionTargetType::SHIP_TYPE: + index = ship_type_name_lookup(index_name.c_str()); + break; + case PrimarySelectionTargetType::SHIP_CLASS: + index = ship_info_lookup(index_name.c_str()); + break; + case PrimarySelectionTargetType::WEAPON_CLASS: + index = weapon_info_lookup(index_name.c_str()); + break; + } + wip->primary_selection_target_flags[flavor].emplace(index, mult); + } + } + } + primary_selection_target_flags_temp.clear(); +} + /** * For all weapons that spawn weapons, given an index at weaponp->spawn_type, * convert the strings in Spawn_names to indices in the Weapon_types array. @@ -5162,7 +5182,7 @@ void weapon_do_post_parse() translate_spawn_types(); } -// Called after ship_init() to resolve proximity ship type/class names into indices. +// Called after ship_init() to resolve proximity ship type/class names into indices, as well as doing the same for primary selection flags. void weapon_post_ship_init() { const int num_weapons = static_cast(Weapon_info.size()); @@ -5195,6 +5215,8 @@ void weapon_post_ship_init() Pending_proximity_type_names.clear(); Pending_proximity_class_names.clear(); + + populate_primary_selection_flags(); } /** From 9db078eaf15059eacb609e3a58ec724f79afffed Mon Sep 17 00:00:00 2001 From: Kestrellius <902X@comcast.net> Date: Wed, 12 Aug 2026 23:57:18 -0700 Subject: [PATCH 08/14] delay parameters --- code/ai/ai.h | 6 +++++- code/ai/aicode.cpp | 40 ++++++++++++++++++++++++++++------------ 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/code/ai/ai.h b/code/ai/ai.h index 7116b9d8f71..7e24bd9ad6f 100644 --- a/code/ai/ai.h +++ b/code/ai/ai.h @@ -143,6 +143,8 @@ typedef struct ai_class { float ai_secondary_range_mult[NUM_SKILL_LEVELS]; bool ai_class_autoscale; //Defaults to true, but can be turned off in order to disable extra scaling of some AI behaviors //based on AI class index + ::util::ParsedRandomFloatRange primary_select_delay; + ::util::ParsedRandomFloatRange primary_select_delay_on_change; ::util::ParsedRandomFloatRange primary_selection_random_factor; float primary_selection_oneshot_modifier; float primary_selection_status_quo_bias; @@ -379,7 +381,9 @@ typedef struct ai_info { int ai_chance_to_use_missiles_on_plr; float ai_max_aim_update_delay; float ai_turret_max_aim_update_delay; - ::util::ParsedRandomFloatRange primary_selection_random_factor; + ::util::ParsedRandomFloatRange primary_select_delay; + ::util::ParsedRandomFloatRange primary_select_delay_on_change; + ::util::ParsedRandomFloatRange primary_selection_random_factor; float primary_selection_oneshot_modifier; float primary_selection_status_quo_bias; flagset ai_profile_flags; //Holds AI_Profiles flags (possibly overriden by AI class) that actually apply to AI diff --git a/code/ai/aicode.cpp b/code/ai/aicode.cpp index bf38e2a8a9b..2ce30dda9a8 100644 --- a/code/ai/aicode.cpp +++ b/code/ai/aicode.cpp @@ -691,6 +691,8 @@ void init_ai_class(ai_class *aicp) aicp->ai_secondary_range_mult[i] = FLT_MIN; } aicp->ai_class_autoscale = true; //Retail behavior is to do the stupid autoscaling + aicp->primary_select_delay = ::util::UniformFloatRange(5.0f); + aicp->primary_select_delay_on_change = ::util::UniformFloatRange(FLT_MAX); aicp->primary_selection_random_factor = ::util::UniformFloatRange(1.0f); aicp->primary_selection_oneshot_modifier = 2.0f; aicp->primary_selection_status_quo_bias = 1.0f; @@ -800,6 +802,12 @@ void parse_ai_class() if (optional_string("$Autoscale by AI Class Index:")) stuff_boolean(&aicp->ai_class_autoscale); + if (optional_string("$Primary select delay:")) + aicp->primary_select_delay = ::util::ParsedRandomFloatRange::parseRandomRange(); + + if (optional_string("$Primary select delay on target change:")) + aicp->primary_select_delay_on_change = ::util::ParsedRandomFloatRange::parseRandomRange(); + if (optional_string("$Primary selection random factor:")) aicp->primary_selection_random_factor = ::util::ParsedRandomFloatRange::parseRandomRange(); @@ -1595,12 +1603,10 @@ int set_target_objnum(ai_info *aip, int objnum) aip->target_signature = (objnum >= 0) ? Objects[objnum].signature : -1; // clear targeted subsystem set_targeted_subsys(aip, NULL, -1); - if (aip->ai_profile_flags[AI::Profile_Flags::Always_do_primary_select_when_target_change]) { - object *target_object = nullptr; - if (aip->target_objnum >= 0) { - target_object = &Objects[aip->target_objnum]; - } - ai_select_primary_weapon_configurable(&Objects[Ships[aip->shipnum].objnum], target_object, Weapon::Info_Flags::Puncture, -1.0f); + int candidate_timestamp = timestamp(fl2i(aip->primary_select_delay_on_change.next() * i2fl(MILLISECONDS_PER_SECOND))); + // we don't want to bother checking for an early selection if the ai's going to do it sooner anyway + if (aip->primary_select_timestamp > candidate_timestamp) { + aip->primary_select_timestamp = candidate_timestamp; } } @@ -1620,12 +1626,18 @@ ship_subsys *set_targeted_subsys(ai_info *aip, ship_subsys *new_subsys, int pare if ( new_subsys ) { // Make new_subsys target - if (new_subsys->system_info->type == SUBSYSTEM_ENGINE || aip->ai_profile_flags[AI::Profile_Flags::Always_do_primary_select_when_target_change]) { + if (new_subsys->system_info->type == SUBSYSTEM_ENGINE) { if ( aip != Player_ai ) { Assert( aip->shipnum >= 0 ); ai_select_primary_weapon_configurable(&Objects[Ships[aip->shipnum].objnum], &Objects[parent_objnum], Weapon::Info_Flags::Puncture, -1.0f); ship_primary_changed(&Ships[aip->shipnum]); // AL: maybe send multiplayer information when AI ship changes primaries } + } else { + int candidate_timestamp = timestamp(fl2i(aip->primary_select_delay_on_change.next() * i2fl(MILLISECONDS_PER_SECOND))); + // we don't want to bother checking for an early selection if the ai's going to do it sooner anyway + if (aip->primary_select_timestamp > candidate_timestamp) { + aip->primary_select_timestamp = candidate_timestamp; + } } if ( aip == Player_ai ) { @@ -6556,9 +6568,10 @@ int ai_fire_primary_weapon(object *objp) enemy_sip = NULL; } - bool shield_changed = false; float relevant_shields_left = -1.0f; - if (aip->ai_profile_flags[AI::Profile_Flags::Always_do_primary_select_when_target_change] && enemy_objp) { + int candidate_timestamp = timestamp(fl2i(aip->primary_select_delay_on_change.next() * i2fl(MILLISECONDS_PER_SECOND))); + // we don't want to bother checking for an early selection if the ai's going to do it sooner anyway + if (enemy_objp && (aip->primary_select_timestamp > candidate_timestamp)) { vec3d ship_local_pos = objp->pos; vm_vec_sub2(&ship_local_pos, &enemy_objp->pos); vm_vec_rotate(&ship_local_pos, &ship_local_pos, &enemy_objp->orient); @@ -6567,21 +6580,21 @@ int ai_fire_primary_weapon(object *objp) relevant_shields_left = std::max(shield_get_quad(enemy_objp, relevant_quadrant) - ship_shield_hitpoint_threshold(enemy_objp, false), 0.0f); bool shield_is_down = relevant_shields_left > 0.0f; if (shield_is_down != aip->enemy_shield_is_down) { - shield_changed = true; aip->enemy_shield_is_down = shield_is_down; + aip->primary_select_timestamp = candidate_timestamp; } } } //plieblang - added check for size of Preferred_primaries to force reevaluation if good-primary-time has been used in the meantime - if ( (swp->current_primary_bank < 0) || (swp->current_primary_bank >= swp->num_primary_banks) || timestamp_elapsed(aip->primary_select_timestamp) || shield_changed) { + if ( (swp->current_primary_bank < 0) || (swp->current_primary_bank >= swp->num_primary_banks) || timestamp_elapsed(aip->primary_select_timestamp)) { Weapon::Info_Flags flags = Weapon::Info_Flags::NUM_VALUES; if ( aip->targeted_subsys != NULL ) { flags = Weapon::Info_Flags::Puncture; } ai_select_primary_weapon_configurable(objp, enemy_objp, flags, relevant_shields_left); ship_primary_changed(shipp); // AL: maybe send multiplayer information when AI ship changes primaries - aip->primary_select_timestamp = timestamp(5 * MILLISECONDS_PER_SECOND); // Maybe change primary weapon five seconds from now. + aip->primary_select_timestamp = timestamp(aip->primary_select_delay.next() * MILLISECONDS_PER_SECOND); // Maybe change primary weapon in a while (five seconds by default). } // if the ship has no primary weapon selected, whether because it has no primary banks or because no bank contains a weapon, then there is nothing to fire @@ -16209,6 +16222,9 @@ void init_aip_from_class_and_profile(ai_info *aip, ai_class *aicp, ai_profile_t aip->ai_secondary_range_mult = aicp->ai_secondary_range_mult[Game_skill_level]; aip->ai_class_autoscale = aicp->ai_class_autoscale; + aip->primary_select_delay = aicp->primary_select_delay; + aip->primary_select_delay_on_change = aicp->primary_select_delay_on_change; + aip->primary_selection_random_factor = aicp->primary_selection_random_factor; aip->primary_selection_oneshot_modifier = aicp->primary_selection_oneshot_modifier; aip->primary_selection_status_quo_bias = aicp->primary_selection_status_quo_bias; From 3aa8b6951eb5bf6fe49294a63adf1c5ecacaa21c Mon Sep 17 00:00:00 2001 From: Kestrellius <902X@comcast.net> Date: Fri, 14 Aug 2026 23:58:28 -0700 Subject: [PATCH 09/14] parsing --- code/ai/ai.h | 10 +++--- code/ai/ai_flags.h | 1 - code/ai/ai_profiles.cpp | 17 ++++++++-- code/ai/ai_profiles.h | 3 ++ code/ai/aicode.cpp | 70 +++++++++++++++++++++++------------------ 5 files changed, 62 insertions(+), 39 deletions(-) diff --git a/code/ai/ai.h b/code/ai/ai.h index 7e24bd9ad6f..27015022158 100644 --- a/code/ai/ai.h +++ b/code/ai/ai.h @@ -143,11 +143,11 @@ typedef struct ai_class { float ai_secondary_range_mult[NUM_SKILL_LEVELS]; bool ai_class_autoscale; //Defaults to true, but can be turned off in order to disable extra scaling of some AI behaviors //based on AI class index - ::util::ParsedRandomFloatRange primary_select_delay; - ::util::ParsedRandomFloatRange primary_select_delay_on_change; - ::util::ParsedRandomFloatRange primary_selection_random_factor; - float primary_selection_oneshot_modifier; - float primary_selection_status_quo_bias; + ::util::ParsedRandomFloatRange primary_select_delay[NUM_SKILL_LEVELS]; + ::util::ParsedRandomFloatRange primary_select_delay_on_change[NUM_SKILL_LEVELS]; + ::util::ParsedRandomFloatRange primary_selection_random_factor[NUM_SKILL_LEVELS]; + float primary_selection_oneshot_modifier[NUM_SKILL_LEVELS]; + float primary_selection_status_quo_bias[NUM_SKILL_LEVELS]; } ai_class; diff --git a/code/ai/ai_flags.h b/code/ai/ai_flags.h index 9a7568c47ed..41c6d0c8877 100644 --- a/code/ai/ai_flags.h +++ b/code/ai/ai_flags.h @@ -127,7 +127,6 @@ namespace AI { Require_turret_to_have_target_in_fov, Shockwaves_damage_small_ship_subsystems, Smart_afterburner_management, - Always_do_primary_select_when_target_change, Configurable_primary_weapon_selection, Smart_primary_weapon_selection, Smart_secondary_weapon_selection, diff --git a/code/ai/ai_profiles.cpp b/code/ai/ai_profiles.cpp index 4b5759fc1b3..19ef2c8c3a9 100644 --- a/code/ai/ai_profiles.cpp +++ b/code/ai/ai_profiles.cpp @@ -202,6 +202,18 @@ void parse_ai_profiles_tbl(const char *filename) if (optional_string("$AI In Range Time:")) parse_float_list(profile->in_range_time, NUM_SKILL_LEVELS); + if (optional_string("$Primary select delay:")) { + for (int i = 0; i < NUM_SKILL_LEVELS; i++) { + profile->primary_select_delay[i] = ::util::ParsedRandomFloatRange::parseRandomRange(); + } + } + + if (optional_string("$Primary select delay on target change:")) { + for (int i = 0; i < NUM_SKILL_LEVELS; i++) { + profile->primary_select_delay_on_change[i] = ::util::ParsedRandomFloatRange::parseRandomRange(); + } + } + if (optional_string("$AI Always Links Ammo Weapons:")) parse_float_list(profile->link_ammo_levels_always, NUM_SKILL_LEVELS); @@ -382,8 +394,6 @@ void parse_ai_profiles_tbl(const char *filename) set_flag(profile, "$big ships can attack beam turrets on untargeted ships:", AI::Profile_Flags::Big_ships_can_attack_beam_turrets_on_untargeted_ships); - set_flag(profile, "$always do primary select when target change:", AI::Profile_Flags::Always_do_primary_select_when_target_change); - set_flag(profile, "$configurable primary weapon selection:", AI::Profile_Flags::Configurable_primary_weapon_selection); set_flag(profile, "$smart primary weapon selection:", AI::Profile_Flags::Smart_primary_weapon_selection); @@ -924,6 +934,9 @@ void ai_profile_t::reset() delay_bomb_arm_timer[i] = 0; chance_to_use_missiles_on_plr[i] = 0; player_autoaim_fov[i] = 0; + + primary_select_delay[i] = ::util::UniformFloatRange(5.0f); + primary_select_delay_on_change[i] = ::util::UniformFloatRange(FLT_MAX); } for (int i = 0; i <= MAX_DETAIL_VALUE; ++i) { diff --git a/code/ai/ai_profiles.h b/code/ai/ai_profiles.h index 9f03bce4782..efcd062a2f4 100644 --- a/code/ai/ai_profiles.h +++ b/code/ai/ai_profiles.h @@ -13,6 +13,7 @@ #include "globalincs/pstypes.h" #include "globalincs/systemvars.h" #include "ai/ai_flags.h" +#include "utils/RandomRange.h" // AI Path types #define AI_PATH_MODE_NORMAL 0 @@ -69,6 +70,8 @@ class ai_profile_t { float stalemate_dist_thresh[NUM_SKILL_LEVELS]; // SUSHI: The maximum distance the AI and target must be within for a stalemate float max_aim_update_delay[NUM_SKILL_LEVELS]; // SUSHI: The maximum delay before the AI updates their aim against small ships float turret_max_aim_update_delay[NUM_SKILL_LEVELS]; // SUSHI: As above, but for turrets updating their aim + ::util::ParsedRandomFloatRange primary_select_delay[NUM_SKILL_LEVELS]; // Time in seconds before next time the AI updates its primary weapon choice + ::util::ParsedRandomFloatRange primary_select_delay_on_change[NUM_SKILL_LEVELS]; // Same, but for special updates taking place when the target ship or subsystem changes, or target's shields change // Multiplicative delay factors for increasing skill levels. float ship_fire_delay_scale_hostile[NUM_SKILL_LEVELS]; diff --git a/code/ai/aicode.cpp b/code/ai/aicode.cpp index 2ce30dda9a8..17c466210c5 100644 --- a/code/ai/aicode.cpp +++ b/code/ai/aicode.cpp @@ -677,6 +677,11 @@ void init_ai_class(ai_class *aicp) aicp->ai_evasion[i] = FLT_MIN; aicp->ai_courage[i] = FLT_MIN; aicp->ai_patience[i] = FLT_MIN; + aicp->primary_select_delay[i] = ::util::UniformFloatRange(FLT_MIN); + aicp->primary_select_delay_on_change[i] = ::util::UniformFloatRange(FLT_MIN); + aicp->primary_selection_random_factor[i] = ::util::UniformFloatRange(1.0f); + aicp->primary_selection_oneshot_modifier[i] = 2.0f; + aicp->primary_selection_status_quo_bias[i] = 1.0f; } aicp->ai_profile_flags.reset(); aicp->ai_profile_flags_set.reset(); @@ -691,11 +696,6 @@ void init_ai_class(ai_class *aicp) aicp->ai_secondary_range_mult[i] = FLT_MIN; } aicp->ai_class_autoscale = true; //Retail behavior is to do the stupid autoscaling - aicp->primary_select_delay = ::util::UniformFloatRange(5.0f); - aicp->primary_select_delay_on_change = ::util::UniformFloatRange(FLT_MAX); - aicp->primary_selection_random_factor = ::util::UniformFloatRange(1.0f); - aicp->primary_selection_oneshot_modifier = 2.0f; - aicp->primary_selection_status_quo_bias = 1.0f; } void set_aic_flag(ai_class *aicp, const char *name, AI::Profile_Flags flag) @@ -801,29 +801,38 @@ void parse_ai_class() if (optional_string("$Autoscale by AI Class Index:")) stuff_boolean(&aicp->ai_class_autoscale); + + //Parse optional values for stuff imported from ai_profiles + if (optional_string("$AI Countermeasure Firing Chance:")) + parse_float_list(aicp->ai_cmeasure_fire_chance, NUM_SKILL_LEVELS); + + if (optional_string("$AI In Range Time:")) + parse_float_list(aicp->ai_in_range_time, NUM_SKILL_LEVELS); + + if (optional_string("$Primary select delay:")) { + for (int i = 0; i < NUM_SKILL_LEVELS; i++) { + aicp->primary_select_delay[i] = ::util::ParsedRandomFloatRange::parseRandomRange(); + } + } - if (optional_string("$Primary select delay:")) - aicp->primary_select_delay = ::util::ParsedRandomFloatRange::parseRandomRange(); - - if (optional_string("$Primary select delay on target change:")) - aicp->primary_select_delay_on_change = ::util::ParsedRandomFloatRange::parseRandomRange(); + if (optional_string("$Primary select delay on target change:")) { + for (int i = 0; i < NUM_SKILL_LEVELS; i++) { + aicp->primary_select_delay_on_change[i] = ::util::ParsedRandomFloatRange::parseRandomRange(); + } + } - if (optional_string("$Primary selection random factor:")) - aicp->primary_selection_random_factor = ::util::ParsedRandomFloatRange::parseRandomRange(); + if (optional_string("$Primary selection random factor:")) { + for (int i = 0; i < NUM_SKILL_LEVELS; i++) { + aicp->primary_selection_random_factor[i] = ::util::ParsedRandomFloatRange::parseRandomRange(); + } + } if (optional_string("$Primary selection oneshot modifier:")) - stuff_float(&aicp->primary_selection_oneshot_modifier); + parse_float_list(aicp->primary_selection_oneshot_modifier, NUM_SKILL_LEVELS); if (optional_string("$Primary selection status quo bias:")) - stuff_float(&aicp->primary_selection_status_quo_bias); - - //Parse optional values for stuff imported from ai_profiles - if (optional_string("$AI Countermeasure Firing Chance:")) - parse_float_list(aicp->ai_cmeasure_fire_chance, NUM_SKILL_LEVELS); - - if (optional_string("$AI In Range Time:")) - parse_float_list(aicp->ai_in_range_time, NUM_SKILL_LEVELS); - + parse_float_list(aicp->primary_selection_status_quo_bias, NUM_SKILL_LEVELS); + if (optional_string("$AI Always Links Ammo Weapons:")) parse_float_list(aicp->ai_link_ammo_levels_always, NUM_SKILL_LEVELS); @@ -927,8 +936,6 @@ void parse_ai_class() parse_float_list(aicp->ai_turret_max_aim_update_delay, NUM_SKILL_LEVELS); set_aic_flag(aicp, "$big ships can attack beam turrets on untargeted ships:", AI::Profile_Flags::Big_ships_can_attack_beam_turrets_on_untargeted_ships); - - set_aic_flag(aicp, "$always do primary select when target change:", AI::Profile_Flags::Always_do_primary_select_when_target_change); set_aic_flag(aicp, "$configurable primary weapon selection:", AI::Profile_Flags::Configurable_primary_weapon_selection); @@ -1652,7 +1659,7 @@ ship_subsys *set_targeted_subsys(ai_info *aip, ship_subsys *new_subsys, int pare } return aip->targeted_subsys; -} +} /** * Called to init the data for single ai object. @@ -16222,12 +16229,9 @@ void init_aip_from_class_and_profile(ai_info *aip, ai_class *aicp, ai_profile_t aip->ai_secondary_range_mult = aicp->ai_secondary_range_mult[Game_skill_level]; aip->ai_class_autoscale = aicp->ai_class_autoscale; - aip->primary_select_delay = aicp->primary_select_delay; - aip->primary_select_delay_on_change = aicp->primary_select_delay_on_change; - - aip->primary_selection_random_factor = aicp->primary_selection_random_factor; - aip->primary_selection_oneshot_modifier = aicp->primary_selection_oneshot_modifier; - aip->primary_selection_status_quo_bias = aicp->primary_selection_status_quo_bias; + aip->primary_selection_random_factor = aicp->primary_selection_random_factor[Game_skill_level]; + aip->primary_selection_oneshot_modifier = aicp->primary_selection_oneshot_modifier[Game_skill_level]; + aip->primary_selection_status_quo_bias = aicp->primary_selection_status_quo_bias[Game_skill_level]; //Apply overrides from ai class to ai profiles values //Only override values which were explicitly set in the AI class @@ -16277,6 +16281,10 @@ void init_aip_from_class_and_profile(ai_info *aip, ai_class *aicp, ai_profile_t profile->max_aim_update_delay[Game_skill_level] : aicp->ai_max_aim_update_delay[Game_skill_level]; aip->ai_turret_max_aim_update_delay = (aicp->ai_turret_max_aim_update_delay[Game_skill_level] == FLT_MIN) ? profile->turret_max_aim_update_delay[Game_skill_level] : aicp->ai_turret_max_aim_update_delay[Game_skill_level]; + aip->primary_select_delay = (aicp->primary_select_delay[Game_skill_level].min() == FLT_MIN && aicp->primary_select_delay[Game_skill_level].max() == FLT_MIN) ? + profile->primary_select_delay[Game_skill_level] : aicp->primary_select_delay[Game_skill_level]; + aip->primary_select_delay_on_change = (aicp->primary_select_delay_on_change[Game_skill_level].min() == FLT_MIN && aicp->primary_select_delay_on_change[Game_skill_level].max() == FLT_MIN) ? + profile->primary_select_delay_on_change[Game_skill_level] : aicp->primary_select_delay_on_change[Game_skill_level]; //Combine AI profile and AI class flags aip->ai_profile_flags = profile->flags | (aicp->ai_profile_flags & aicp->ai_profile_flags_set); From ad2d08353934b65206a7079639eaeccb1de6b5a4 Mon Sep 17 00:00:00 2001 From: Kestrellius <902X@comcast.net> Date: Sat, 15 Aug 2026 05:00:46 -0700 Subject: [PATCH 10/14] bugfixing --- code/ai/ai_profiles.cpp | 2 +- code/ai/aicode.cpp | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/code/ai/ai_profiles.cpp b/code/ai/ai_profiles.cpp index 19ef2c8c3a9..05ee3d7dc79 100644 --- a/code/ai/ai_profiles.cpp +++ b/code/ai/ai_profiles.cpp @@ -936,7 +936,7 @@ void ai_profile_t::reset() player_autoaim_fov[i] = 0; primary_select_delay[i] = ::util::UniformFloatRange(5.0f); - primary_select_delay_on_change[i] = ::util::UniformFloatRange(FLT_MAX); + primary_select_delay_on_change[i] = ::util::UniformFloatRange(9999.0f); } for (int i = 0; i <= MAX_DETAIL_VALUE; ++i) { diff --git a/code/ai/aicode.cpp b/code/ai/aicode.cpp index 17c466210c5..63700a21c86 100644 --- a/code/ai/aicode.cpp +++ b/code/ai/aicode.cpp @@ -1610,9 +1610,9 @@ int set_target_objnum(ai_info *aip, int objnum) aip->target_signature = (objnum >= 0) ? Objects[objnum].signature : -1; // clear targeted subsystem set_targeted_subsys(aip, NULL, -1); - int candidate_timestamp = timestamp(fl2i(aip->primary_select_delay_on_change.next() * i2fl(MILLISECONDS_PER_SECOND))); + int candidate_timestamp = timestamp(std::max(fl2i(aip->primary_select_delay_on_change.next() * i2fl(MILLISECONDS_PER_SECOND)), 0)); // we don't want to bother checking for an early selection if the ai's going to do it sooner anyway - if (aip->primary_select_timestamp > candidate_timestamp) { + if (timestamp_until(aip->primary_select_timestamp) > timestamp_until(candidate_timestamp)) { aip->primary_select_timestamp = candidate_timestamp; } } @@ -1640,9 +1640,9 @@ ship_subsys *set_targeted_subsys(ai_info *aip, ship_subsys *new_subsys, int pare ship_primary_changed(&Ships[aip->shipnum]); // AL: maybe send multiplayer information when AI ship changes primaries } } else { - int candidate_timestamp = timestamp(fl2i(aip->primary_select_delay_on_change.next() * i2fl(MILLISECONDS_PER_SECOND))); + int candidate_timestamp = timestamp(std::max(fl2i(aip->primary_select_delay_on_change.next() * i2fl(MILLISECONDS_PER_SECOND)), 0)); // we don't want to bother checking for an early selection if the ai's going to do it sooner anyway - if (aip->primary_select_timestamp > candidate_timestamp) { + if (timestamp_until(aip->primary_select_timestamp) > timestamp_until(candidate_timestamp)) { aip->primary_select_timestamp = candidate_timestamp; } } @@ -6576,9 +6576,9 @@ int ai_fire_primary_weapon(object *objp) } float relevant_shields_left = -1.0f; - int candidate_timestamp = timestamp(fl2i(aip->primary_select_delay_on_change.next() * i2fl(MILLISECONDS_PER_SECOND))); + int candidate_timestamp = timestamp(std::max(fl2i(aip->primary_select_delay_on_change.next() * i2fl(MILLISECONDS_PER_SECOND)), 0)); // we don't want to bother checking for an early selection if the ai's going to do it sooner anyway - if (enemy_objp && (aip->primary_select_timestamp > candidate_timestamp)) { + if (enemy_objp && (timestamp_until(aip->primary_select_timestamp) > timestamp_until(candidate_timestamp))) { vec3d ship_local_pos = objp->pos; vm_vec_sub2(&ship_local_pos, &enemy_objp->pos); vm_vec_rotate(&ship_local_pos, &ship_local_pos, &enemy_objp->orient); From 27e08ab0a8033029cb3f1e19ede0d6ed421ed6b4 Mon Sep 17 00:00:00 2001 From: Kestrellius <902X@comcast.net> Date: Sat, 15 Aug 2026 05:58:20 -0700 Subject: [PATCH 11/14] clang --- code/ai/ai_profiles.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/code/ai/ai_profiles.cpp b/code/ai/ai_profiles.cpp index 05ee3d7dc79..d438f18454d 100644 --- a/code/ai/ai_profiles.cpp +++ b/code/ai/ai_profiles.cpp @@ -203,14 +203,14 @@ void parse_ai_profiles_tbl(const char *filename) parse_float_list(profile->in_range_time, NUM_SKILL_LEVELS); if (optional_string("$Primary select delay:")) { - for (int i = 0; i < NUM_SKILL_LEVELS; i++) { - profile->primary_select_delay[i] = ::util::ParsedRandomFloatRange::parseRandomRange(); + for (int level = 0; level < NUM_SKILL_LEVELS; level++) { + profile->primary_select_delay[level] = ::util::ParsedRandomFloatRange::parseRandomRange(); } } if (optional_string("$Primary select delay on target change:")) { - for (int i = 0; i < NUM_SKILL_LEVELS; i++) { - profile->primary_select_delay_on_change[i] = ::util::ParsedRandomFloatRange::parseRandomRange(); + for (int level = 0; level < NUM_SKILL_LEVELS; level++) { + profile->primary_select_delay_on_change[level] = ::util::ParsedRandomFloatRange::parseRandomRange(); } } From d55d17a6a9004ea6f098e7a99ca3f4bb1a893af0 Mon Sep 17 00:00:00 2001 From: Kestrellius <902X@comcast.net> Date: Sat, 15 Aug 2026 06:08:16 -0700 Subject: [PATCH 12/14] clang --- code/weapon/weapons.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/weapon/weapons.cpp b/code/weapon/weapons.cpp index 59aebc75c27..375f479327d 100644 --- a/code/weapon/weapons.cpp +++ b/code/weapon/weapons.cpp @@ -4396,7 +4396,7 @@ void populate_primary_selection_flags() { for (int i = 0; i < sz2i(Weapon_info.size()); i++) { weapon_info *wip = &Weapon_info[i]; - for (int flavor; flavor < PrimarySelectionTargetType::MAX; flavor++) { + for (int flavor = 0; flavor < PrimarySelectionTargetType::MAX; flavor++) { for (auto& [index_name, mult] : primary_selection_target_flags_temp[i][flavor]) { int index; switch (flavor) { From 5b59c25abf486f6799e0c3415d485ebc81f74af3 Mon Sep 17 00:00:00 2001 From: Kestrellius <902X@comcast.net> Date: Sat, 15 Aug 2026 06:36:48 -0700 Subject: [PATCH 13/14] clang --- code/ai/ai_profiles.cpp | 8 ++++---- code/ai/aicode.cpp | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/code/ai/ai_profiles.cpp b/code/ai/ai_profiles.cpp index d438f18454d..d817cd294ad 100644 --- a/code/ai/ai_profiles.cpp +++ b/code/ai/ai_profiles.cpp @@ -203,14 +203,14 @@ void parse_ai_profiles_tbl(const char *filename) parse_float_list(profile->in_range_time, NUM_SKILL_LEVELS); if (optional_string("$Primary select delay:")) { - for (int level = 0; level < NUM_SKILL_LEVELS; level++) { - profile->primary_select_delay[level] = ::util::ParsedRandomFloatRange::parseRandomRange(); + for (auto& entry : profile->primary_select_delay) { + entry = ::util::ParsedRandomFloatRange::parseRandomRange(); } } if (optional_string("$Primary select delay on target change:")) { - for (int level = 0; level < NUM_SKILL_LEVELS; level++) { - profile->primary_select_delay_on_change[level] = ::util::ParsedRandomFloatRange::parseRandomRange(); + for (auto& entry : profile->primary_select_delay_on_change) { + entry = ::util::ParsedRandomFloatRange::parseRandomRange(); } } diff --git a/code/ai/aicode.cpp b/code/ai/aicode.cpp index 63700a21c86..4d80778ed43 100644 --- a/code/ai/aicode.cpp +++ b/code/ai/aicode.cpp @@ -804,26 +804,26 @@ void parse_ai_class() //Parse optional values for stuff imported from ai_profiles if (optional_string("$AI Countermeasure Firing Chance:")) - parse_float_list(aicp->ai_cmeasure_fire_chance, NUM_SKILL_LEVELS); + parse_float_list(aicp->ai_cmeasure_fire_chance, NUM_SKILL_LEVELS); if (optional_string("$AI In Range Time:")) - parse_float_list(aicp->ai_in_range_time, NUM_SKILL_LEVELS); + parse_float_list(aicp->ai_in_range_time, NUM_SKILL_LEVELS); if (optional_string("$Primary select delay:")) { - for (int i = 0; i < NUM_SKILL_LEVELS; i++) { - aicp->primary_select_delay[i] = ::util::ParsedRandomFloatRange::parseRandomRange(); + for (auto& entry : aicp->primary_select_delay) { + entry = ::util::ParsedRandomFloatRange::parseRandomRange(); } } if (optional_string("$Primary select delay on target change:")) { - for (int i = 0; i < NUM_SKILL_LEVELS; i++) { - aicp->primary_select_delay_on_change[i] = ::util::ParsedRandomFloatRange::parseRandomRange(); + for (auto& entry : aicp->primary_select_delay_on_change) { + entry = ::util::ParsedRandomFloatRange::parseRandomRange(); } } if (optional_string("$Primary selection random factor:")) { - for (int i = 0; i < NUM_SKILL_LEVELS; i++) { - aicp->primary_selection_random_factor[i] = ::util::ParsedRandomFloatRange::parseRandomRange(); + for (auto& entry : aicp->primary_selection_random_factor) { + entry = ::util::ParsedRandomFloatRange::parseRandomRange(); } } From 21663d36da779a3763b3595f8c7d6b633f758ea6 Mon Sep 17 00:00:00 2001 From: Kestrellius <902X@comcast.net> Date: Sat, 15 Aug 2026 06:53:25 -0700 Subject: [PATCH 14/14] berserk 2016 --- code/ai/ai.h | 2 +- code/ai/aicode.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/code/ai/ai.h b/code/ai/ai.h index 27015022158..8cc85a256eb 100644 --- a/code/ai/ai.h +++ b/code/ai/ai.h @@ -347,7 +347,7 @@ typedef struct ai_info { float prev_dot_to_goal; // dot of fvec to goal last frame, used to see if making progress towards goal. vec3d goal_point; // Used in AIM_SAFETY, AIM_STILL and in circling. vec3d prev_goal_point; // Previous location of goal point, used at least for evading. - float enemy_shield_is_down; + bool enemy_shield_is_down; //Values copied from the AI class float ai_accuracy, ai_evasion, ai_courage, ai_patience; diff --git a/code/ai/aicode.cpp b/code/ai/aicode.cpp index 4d80778ed43..7a332fa76b2 100644 --- a/code/ai/aicode.cpp +++ b/code/ai/aicode.cpp @@ -6601,7 +6601,7 @@ int ai_fire_primary_weapon(object *objp) } ai_select_primary_weapon_configurable(objp, enemy_objp, flags, relevant_shields_left); ship_primary_changed(shipp); // AL: maybe send multiplayer information when AI ship changes primaries - aip->primary_select_timestamp = timestamp(aip->primary_select_delay.next() * MILLISECONDS_PER_SECOND); // Maybe change primary weapon in a while (five seconds by default). + aip->primary_select_timestamp = timestamp(fl2i(aip->primary_select_delay.next() * i2fl(MILLISECONDS_PER_SECOND))); // Maybe change primary weapon in a while (five seconds by default). } // if the ship has no primary weapon selected, whether because it has no primary banks or because no bank contains a weapon, then there is nothing to fire