Skip to content

Commit 36d6fbe

Browse files
authored
Lab firing upgrades (scp-fs2open#6703)
* Fix some issues preventing various secondary types from firing in the lab * Add ability to fire turrets from the lab * const char*, mjn please * more cleanup * range-based for loop * Update comment
1 parent 4c209f8 commit 36d6fbe

11 files changed

Lines changed: 393 additions & 115 deletions

File tree

code/ai/ai.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ typedef struct ai_info {
318318
//Unlike the predicted position stuff, also takes into account velocity
319319
//Only used against small ships
320320
fix next_aim_pos_time;
321-
vec3d last_aim_enemy_pos;
321+
vec3d last_aim_enemy_pos; // Normally this is the position of the enemy a turret is targeting but in the F3 Lab, this is used to pass raw target coordinates to the turret firing methods.
322322
vec3d last_aim_enemy_vel;
323323

324324
ai_goal goals[MAX_AI_GOALS];

code/ai/aicode.cpp

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12089,6 +12089,8 @@ void ai_process_subobjects(int objnum)
1208912089
ai_info *aip = &Ai_info[shipp->ai_index];
1209012090
ship_info *sip = &Ship_info[shipp->ship_info_index];
1209112091

12092+
bool in_lab = gameseq_get_state() == GS_STATE_LAB;
12093+
1209212094
// non-player ships that are playing dead do not process subsystems or turrets
1209312095
if ((!(objp->flags[Object::Object_Flags::Player_ship]) || Player_use_ai) && aip->mode == AIM_PLAY_DEAD)
1209412096
return;
@@ -12163,7 +12165,9 @@ void ai_process_subobjects(int objnum)
1216312165

1216412166
if ( psub->turret_num_firing_points > 0 )
1216512167
{
12166-
ai_turret_execute_behavior(shipp, pss);
12168+
if (!in_lab) {
12169+
ai_turret_execute_behavior(shipp, pss);
12170+
}
1216712171
} else {
1216812172
Warning( LOCATION, "Turret %s on ship %s has no firing points assigned to it.\nThis needs to be fixed in the model.\n", psub->name, shipp->ship_name );
1216912173
}
@@ -12208,6 +12212,11 @@ void ai_process_subobjects(int objnum)
1220812212
// NOTE: Subsystem submodels are no longer rotated here. See ship_move_subsystems().
1220912213
}
1221012214

12215+
// Lab doesn't need to do anything else here
12216+
if (in_lab) {
12217+
return;
12218+
}
12219+
1221112220
// Clients must not make actual AI changes! So bail here.
1221212221
if (MULTIPLAYER_CLIENT) {
1221312222
return;

code/ai/aiturret.cpp

Lines changed: 154 additions & 97 deletions
Large diffs are not rendered by default.

code/lab/dialogs/lab_ui.cpp

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -791,6 +791,120 @@ void LabUi::build_weapon_options(ship* shipp) const {
791791
}
792792
}
793793
}
794+
795+
with_TreeNode("Turrets")
796+
{
797+
auto subsys_idx = 0; // unique IDs
798+
799+
for (auto* subsys = GET_FIRST(&shipp->subsys_list); subsys != END_OF_LIST(&shipp->subsys_list);
800+
subsys = GET_NEXT(subsys)) {
801+
if (subsys->system_info->type != SUBSYSTEM_TURRET)
802+
continue;
803+
804+
SCP_string label;
805+
sprintf(label, "Turret %i - %s", subsys_idx, subsys->system_info->subobj_name);
806+
807+
if (TreeNodeEx(label.c_str(), ImGuiTreeNodeFlags_DefaultOpen)) {
808+
809+
Spacing();
810+
811+
// Find the turret in FireTurrets
812+
auto it = std::find_if(getLabManager()->FireTurrets.begin(),
813+
getLabManager()->FireTurrets.end(),
814+
[subsys](const auto& tuple) { return std::get<0>(tuple) == subsys; });
815+
816+
int mode = 0; // Default to UVEC aligned
817+
bool fire_now = false;
818+
819+
bool multipart = false;
820+
if (subsys->system_info->turret_gun_sobj >= 0 && subsys->system_info->subobj_num != subsys->system_info->turret_gun_sobj) {
821+
multipart = true;
822+
}
823+
824+
const char* aiming_options[] = {"Random", "UVEC Aligned", "Initial"};
825+
826+
// In the lab we need to force fire on target because we're faking having an actual target
827+
subsys->system_info->flags.set(Model::Subsystem_Flags::Fire_on_target);
828+
829+
// If already added, load current mode + fire state
830+
if (it != getLabManager()->FireTurrets.end()) {
831+
mode = static_cast<int>(std::get<1>(*it));
832+
fire_now = std::get<2>(*it);
833+
}
834+
835+
// Fire checkbox
836+
SCP_string cb_label;
837+
sprintf(cb_label, "Fire Turret##turret%i", subsys_idx);
838+
Checkbox(cb_label.c_str(), &fire_now);
839+
840+
// Mode dropdown
841+
ImGui::TextUnformatted("Turret Aiming:");
842+
ImGui::SameLine(150.0f); // Start combo box at 150px
843+
ImGui::SetNextItemWidth(200.0f);
844+
ImGui::Combo("##AimingMode", &mode, aiming_options, multipart ? 3 : 2);
845+
846+
auto modeToAimType = [multipart](int this_mode) -> LabTurretAimType {
847+
switch (this_mode) {
848+
case 0:
849+
return LabTurretAimType::RANDOM;
850+
case 1:
851+
return LabTurretAimType::UVEC;
852+
case 2:
853+
if (multipart) {
854+
return LabTurretAimType::INITIAL;
855+
} else {
856+
return LabTurretAimType::UVEC;
857+
}
858+
default:
859+
return LabTurretAimType::RANDOM; // fallback
860+
}
861+
};
862+
863+
// Update or add to FireTurrets
864+
if (it == getLabManager()->FireTurrets.end()) {
865+
getLabManager()->FireTurrets.emplace_back(subsys, modeToAimType(mode), fire_now);
866+
867+
// Set timestamps to allow turret to fire immediately
868+
subsys->turret_next_fire_stamp = timestamp(0);
869+
auto& weps = subsys->weapons;
870+
for (auto& stamp : weps.next_primary_fire_stamp) {
871+
stamp = timestamp(0);
872+
}
873+
874+
for (auto& stamp : weps.next_secondary_fire_stamp) {
875+
stamp = timestamp(0);
876+
}
877+
} else {
878+
std::get<1>(*it) = modeToAimType(mode);
879+
std::get<2>(*it) = fire_now;
880+
}
881+
882+
auto& weps = subsys->weapons;
883+
884+
// PRIMARY BANKS
885+
for (int bank = 0; bank < weps.num_primary_banks; ++bank) {
886+
SCP_string text;
887+
sprintf(text, "##TurretPrimaryBank%i_%i", subsys_idx, bank);
888+
889+
auto* wip = &Weapon_info[weps.primary_bank_weapons[bank]];
890+
build_primary_weapon_combobox(text, wip, weps.primary_bank_weapons[bank]);
891+
}
892+
893+
// SECONDARY BANKS
894+
for (int bank = 0; bank < weps.num_secondary_banks; ++bank) {
895+
SCP_string text;
896+
sprintf(text, "##TurretSecondaryBank%i_%i", subsys_idx, bank);
897+
898+
auto* wip = &Weapon_info[weps.secondary_bank_weapons[bank]];
899+
build_secondary_weapon_combobox(text, wip, weps.secondary_bank_weapons[bank]);
900+
}
901+
902+
TreePop(); // Close this turret node
903+
}
904+
905+
subsys_idx++;
906+
}
907+
}
794908
}
795909

796910
void LabUi::build_primary_weapon_combobox(SCP_string& text,

code/lab/dialogs/lab_ui.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@
44
#include "model/animation/modelanimation.h"
55
#include "species_defs/species_defs.h"
66

7+
enum class LabTurretAimType {
8+
RANDOM,
9+
UVEC,
10+
INITIAL,
11+
};
12+
713
class LabUi {
814
public:
915
void create_ui();

code/lab/manager/lab_manager.cpp

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
#include "lab/renderer/lab_renderer.h"
44
#include "io/key.h"
55
#include "asteroid/asteroid.h"
6+
#include "math/staticrand.h"
67
#include "missionui/missionscreencommon.h"
78
#include "debris/debris.h"
9+
#include "ship/ship.h"
810
#include "ship/shipfx.h"
911
#include "particle/particle.h"
1012
#include "weapon/muzzleflash.h"
@@ -16,6 +18,9 @@
1618
#include "io/mouse.h"
1719
#include "weapon/weapon.h"
1820

21+
//Turret firing forward declarations
22+
void ai_turret_execute_behavior(const ship* shipp, ship_subsys* ss);
23+
1924

2025
void lab_exit() {
2126
getLabManager()->notify_close();
@@ -247,6 +252,78 @@ void LabManager::onFrame(float frametime) {
247252
ship_fire_secondary(obj);
248253
}
249254
}
255+
256+
ship_process_post(obj, frametime);
257+
ai_process_subobjects(CurrentObject); // So that animations get reset
258+
259+
if (!getLabManager()->FireTurrets.empty()) {
260+
for (auto& [subsys, mode, fire] : getLabManager()->FireTurrets) {
261+
if (!fire || subsys == nullptr)
262+
continue;
263+
264+
vec3d new_pos, new_vec;
265+
ship_get_global_turret_info(&Objects[subsys->parent_objnum], subsys->system_info, &new_pos, &new_vec);
266+
267+
bool multipart = false;
268+
// Turret is multipart
269+
if (subsys->system_info->turret_gun_sobj >= 0 && subsys->system_info->subobj_num != subsys->system_info->turret_gun_sobj) {
270+
multipart = true;
271+
}
272+
273+
switch (mode) {
274+
case LabTurretAimType::UVEC: {
275+
subsys->last_aim_enemy_pos = new_pos + new_vec * 500.0f;
276+
break;
277+
}
278+
case LabTurretAimType::INITIAL: {
279+
subsys->last_aim_enemy_pos = vmd_zero_vector;
280+
break;
281+
}
282+
case LabTurretAimType::RANDOM: {
283+
bool gen_new_vec = !multipart || subsys->points_to_target <= 0.010f;
284+
if (gen_new_vec && timestamp_elapsed(subsys->turret_next_fire_stamp)) {
285+
vec3d rand_vec;
286+
const int MAX_ATTEMPTS = 100;
287+
bool valid_vec_found = false;
288+
289+
// You get 100 tries to find a set of random coords to fire at
290+
for (int attempt = 0; attempt < MAX_ATTEMPTS; ++attempt) {
291+
float full_fov_degrees = 2.0f * acosf(subsys->system_info->turret_fov) * (180.0f / PI);
292+
vm_vec_random_cone(&rand_vec, &new_vec, full_fov_degrees);
293+
294+
vec3d target_point;
295+
vm_vec_scale_add(&target_point, &new_pos, &rand_vec, 1000.0f);
296+
297+
// Create a vector from the turret to the random point.
298+
vec3d turret_to_target;
299+
vm_vec_sub(&turret_to_target, &target_point, &new_pos);
300+
vm_vec_normalize(&turret_to_target);
301+
302+
// Test if the generated vector is within the FOV
303+
if (turret_fov_test(subsys, &new_vec, &turret_to_target, 0.0f)) {
304+
valid_vec_found = true;
305+
rand_vec = target_point;
306+
break;
307+
}
308+
}
309+
310+
if (valid_vec_found) {
311+
subsys->last_aim_enemy_pos = rand_vec;
312+
} else {
313+
subsys->last_aim_enemy_pos = new_pos + new_vec * 500.0f;
314+
}
315+
}
316+
break;
317+
}
318+
default:
319+
Assertion(false, "Invalid Lab Turret Aim Type!");
320+
break;
321+
}
322+
323+
ai_turret_execute_behavior(&Ships[obj->instance], subsys);
324+
}
325+
}
326+
250327
}
251328

252329
// get correct revolution rate
@@ -290,6 +367,9 @@ void LabManager::changeDisplayedObject(LabMode mode, int info_index) {
290367
CurrentClass = info_index;
291368

292369
if (CurrentObject != -1) {
370+
// Stop any firing turrets
371+
FireTurrets.clear();
372+
293373
obj_delete_all();
294374
CurrentObject = -1;
295375
}

code/lab/manager/lab_manager.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ class LabManager {
7878

7979
int FirePrimaries = 0;
8080
int FireSecondaries = 0;
81+
82+
SCP_vector<std::tuple<ship_subsys*, LabTurretAimType, bool>> FireTurrets;
8183

8284
LabRotationMode RotationMode = LabRotationMode::Both;
8385
float RotationSpeedDivisor = 100.0f;

code/ship/ship.cpp

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13734,6 +13734,8 @@ int ship_fire_secondary( object *obj, int allow_swarm, bool rollback_shot )
1373413734
if(Ships[n].objnum != OBJ_INDEX(obj)){
1373513735
return 0;
1373613736
}
13737+
13738+
bool in_lab = (gameseq_get_state() == GS_STATE_LAB);
1373713739

1373813740
shipp = &Ships[n];
1373913741
swp = &shipp->weapons;
@@ -13826,7 +13828,7 @@ int ship_fire_secondary( object *obj, int allow_swarm, bool rollback_shot )
1382613828
}
1382713829

1382813830
// Ensure if this is a "require-lock" missile, that a lock actually exists
13829-
if ( wip->wi_flags[Weapon::Info_Flags::No_dumbfire] ) {
13831+
if (wip->wi_flags[Weapon::Info_Flags::No_dumbfire] && !in_lab) {
1383013832
if (!aip->current_target_is_locked && !ship_lock_present(shipp) && shipp->missile_locks_firing.empty() && aip->ai_missile_locks_firing.empty()) {
1383113833
if (obj == Player_obj) {
1383213834
if (!Weapon_energy_cheat) {
@@ -13858,7 +13860,7 @@ int ship_fire_secondary( object *obj, int allow_swarm, bool rollback_shot )
1385813860
}
1385913861
}
1386013862

13861-
if (wip->wi_flags[Weapon::Info_Flags::Tagged_only])
13863+
if (wip->wi_flags[Weapon::Info_Flags::Tagged_only] && !in_lab)
1386213864
{
1386313865
if (!ship_is_tagged(&Objects[aip->target_objnum]))
1386413866
{
@@ -13890,10 +13892,14 @@ int ship_fire_secondary( object *obj, int allow_swarm, bool rollback_shot )
1389013892
if ( (wip->wi_flags[Weapon::Info_Flags::Swarm]) && !allow_swarm ) {
1389113893
Assert(wip->swarm_count > 0);
1389213894
if (wip->multi_lock) {
13893-
if(obj == Player_obj || (MULTIPLAYER_MASTER && obj->flags[Object::Object_Flags::Player_ship]))
13894-
shipp->num_swarm_missiles_to_fire = (int)shipp->missile_locks_firing.size();
13895-
else // AI ships
13896-
shipp->num_swarm_missiles_to_fire = (int)aip->ai_missile_locks_firing.size();
13895+
if (in_lab) {
13896+
shipp->num_swarm_missiles_to_fire = wip->max_seeking;
13897+
} else {
13898+
if (obj == Player_obj || (MULTIPLAYER_MASTER && obj->flags[Object::Object_Flags::Player_ship]))
13899+
shipp->num_swarm_missiles_to_fire = static_cast<int>(shipp->missile_locks_firing.size());
13900+
else // AI ships
13901+
shipp->num_swarm_missiles_to_fire = static_cast<int>(aip->ai_missile_locks_firing.size());
13902+
}
1389713903
} else if(wip->swarm_count <= 0){
1389813904
shipp->num_swarm_missiles_to_fire = SWARM_DEFAULT_NUM_MISSILES_FIRED;
1389913905
} else {
@@ -13909,10 +13915,14 @@ int ship_fire_secondary( object *obj, int allow_swarm, bool rollback_shot )
1390913915
//phreak 11-9-02
1391013916
//changed this from 4 to custom number defined in tables
1391113917
if (wip->multi_lock) {
13912-
if (obj == Player_obj || (MULTIPLAYER_MASTER && obj->flags[Object::Object_Flags::Player_ship]))
13913-
shipp->num_corkscrew_to_fire = (ubyte)shipp->missile_locks_firing.size();
13914-
else // AI ships
13915-
shipp->num_corkscrew_to_fire = (ubyte)aip->ai_missile_locks_firing.size();
13918+
if (in_lab) {
13919+
shipp->num_corkscrew_to_fire = static_cast<ubyte>(wip->max_seeking);
13920+
} else {
13921+
if (obj == Player_obj || (MULTIPLAYER_MASTER && obj->flags[Object::Object_Flags::Player_ship]))
13922+
shipp->num_corkscrew_to_fire = static_cast<ubyte>(shipp->missile_locks_firing.size());
13923+
else // AI ships
13924+
shipp->num_corkscrew_to_fire = static_cast<ubyte>(aip->ai_missile_locks_firing.size());
13925+
}
1391613926
} else {
1391713927
shipp->num_corkscrew_to_fire = (ubyte)(shipp->num_corkscrew_to_fire + (ubyte)wip->cs_num_fired);
1391813928
}
@@ -14014,7 +14024,7 @@ int ship_fire_secondary( object *obj, int allow_swarm, bool rollback_shot )
1401414024
// and if I am a client in multiplayer
1401514025
check_ammo = 1;
1401614026

14017-
if ( MULTIPLAYER_CLIENT && (obj != Player_obj) ){
14027+
if (in_lab || (MULTIPLAYER_CLIENT && (obj != Player_obj))) {
1401814028
check_ammo = 0;
1401914029
}
1402014030

@@ -14229,7 +14239,7 @@ int ship_fire_secondary( object *obj, int allow_swarm, bool rollback_shot )
1422914239

1423014240
// if we are out of ammo in this bank then don't carry over firing swarm/corkscrew
1423114241
// missiles to a new bank
14232-
if (!ship_secondary_has_ammo(swp, bank) || shipp->weapon_energy < wip->energy_consumed) {
14242+
if (!in_lab && (!ship_secondary_has_ammo(swp, bank) || shipp->weapon_energy < wip->energy_consumed)) {
1423314243
// NOTE: these are set to 1 since they will get reduced by 1 in the
1423414244
// swarm/corkscrew code once this function returns
1423514245

code/weapon/swarm.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,7 @@ void turret_swarm_delete(int i)
309309
}
310310

311311
// Set up turret swarm info struct
312-
void turret_swarm_set_up_info(int parent_objnum, ship_subsys *turret, const weapon_info *wip, int weapon_num)
312+
void turret_swarm_set_up_info(int parent_objnum, ship_subsys *turret, const weapon_info *wip, int weapon_num, bool no_tracking_object)
313313
{
314314
turret_swarm_info *tsi;
315315
object *parent_obj, *target_obj;
@@ -334,7 +334,7 @@ void turret_swarm_set_up_info(int parent_objnum, ship_subsys *turret, const weap
334334
Assert(parent_obj->type == OBJ_SHIP);
335335
shipp = &Ships[parent_obj->instance];
336336
Assert(turret->turret_enemy_objnum < MAX_OBJECTS);
337-
if((turret->turret_enemy_objnum < 0) || (turret->turret_enemy_objnum >= MAX_OBJECTS)){
337+
if(!no_tracking_object && ((turret->turret_enemy_objnum < 0) || (turret->turret_enemy_objnum >= MAX_OBJECTS))){
338338
return;
339339
}
340340
target_obj = &Objects[turret->turret_enemy_objnum];

0 commit comments

Comments
 (0)