1 #include "lab/dialogs/actions.h"
2 #include "lab/labv2_internal.h"
3 #include "ship/shiphit.h"
4
5 #include <array>
6
7 /**********************Click event handlers********************************************/
8
destroy_subsystem(Tree * caller)9 void destroy_subsystem(Tree* caller) {
10 auto selected_subsys_index = caller->GetSelectedItem()->GetData();
11
12 if (getLabManager()->isSafeForShips()) {
13 auto sp = &Ships[Objects[getLabManager()->CurrentObject].instance];
14 auto ssp = GET_FIRST(&sp->subsys_list);
15 auto subsys_index = 0;
16 while (ssp != END_OF_LIST(&sp->subsys_list)) {
17 if (subsys_index == selected_subsys_index) {
18 ssp->current_hits = 0;
19 do_subobj_destroyed_stuff(sp, ssp, nullptr);
20 }
21
22 ssp = GET_NEXT(ssp);
23 ++subsys_index;
24 }
25
26 // recalculate when done
27 ship_recalc_subsys_strength(sp);
28 }
29 }
30
change_primary(Tree * caller)31 void change_primary(Tree* caller) {
32 auto weapon_name = caller->GetSelectedItem()->Name;
33 auto bank_num = caller->GetSelectedItem()->GetData();
34
35 auto weapon_index = weapon_info_lookup(weapon_name.c_str());
36
37 auto sp = &Ships[Objects[getLabManager()->CurrentObject].instance];
38 sp->weapons.primary_bank_weapons[bank_num] = weapon_index;
39
40 if (Weapon_info[weapon_index].wi_flags[Weapon::Info_Flags::Ballistic]) {
41 sp->weapons.primary_bank_ammo[bank_num] = 10000;
42 }
43 }
44
change_secondary(Tree * caller)45 void change_secondary(Tree* caller) {
46 auto weapon_name = caller->GetSelectedItem()->Name;
47 auto bank_num = caller->GetSelectedItem()->GetData();
48
49 auto weapon_index = weapon_info_lookup(weapon_name.c_str());
50
51 auto sp = &Ships[Objects[getLabManager()->CurrentObject].instance];
52 sp->weapons.secondary_bank_weapons[bank_num] = weapon_index;
53 }
54
destroy_ship(Button *)55 void destroy_ship(Button* /*caller*/) {
56 if (getLabManager()->isSafeForShips()) {
57 auto obj = &Objects[getLabManager()->CurrentObject];
58
59 obj->flags.remove(Object::Object_Flags::Player_ship);
60 ship_self_destruct(obj);
61 }
62 }
63
64 std::map<AnimationTriggerType, std::map<int, bool>> manual_animation_triggers = {};
65 std::map<AnimationTriggerType, bool> manual_animations = {};
66
67 std::array<bool, MAX_SHIP_PRIMARY_BANKS> triggered_primary_banks;
68 std::array<bool, MAX_SHIP_SECONDARY_BANKS> triggered_secondary_banks;
69
reset_animations(Tree *)70 void reset_animations(Tree*) {
71 if (getLabManager()->isSafeForShips()) {
72 auto shipp = &Ships[Objects[getLabManager()->CurrentObject].instance];
73
74 for (auto i = 0; i < MAX_SHIP_PRIMARY_BANKS; ++i) {
75 if (triggered_primary_banks[i]) {
76 model_anim_start_type(shipp, AnimationTriggerType::PrimaryBank, i, -1, false);
77 triggered_primary_banks[i] = false;
78 }
79 }
80
81 for (auto i = 0; i < MAX_SHIP_SECONDARY_BANKS; ++i) {
82 if (triggered_secondary_banks[i]) {
83 model_anim_start_type(shipp, AnimationTriggerType::SecondaryBank, i, -1, false);
84 triggered_secondary_banks[i] = false;
85 }
86 }
87
88 for (auto entry : manual_animations) {
89 if (manual_animations[entry.first]) {
90 model_anim_start_type(shipp, entry.first, 0, -1, false);
91 manual_animations[entry.first] = false;
92 }
93 }
94
95 for (const auto& entry : manual_animation_triggers) {
96 auto animation_type = entry.first;
97 auto manual_trigger_map = entry.second;
98
99 for (auto manual_trigger : manual_trigger_map) {
100 if (manual_trigger.second) {
101 model_anim_start_type(shipp, animation_type, manual_trigger.first, -1, false);
102 }
103 }
104 }
105 }
106 }
107
trigger_primary_bank(Tree * caller)108 void trigger_primary_bank(Tree* caller) {
109 if (getLabManager()->isSafeForShips()) {
110 auto shipp = &Ships[Objects[getLabManager()->CurrentObject].instance];
111 auto bank = caller->GetSelectedItem()->GetData();
112 model_anim_start_type(shipp, AnimationTriggerType::PrimaryBank, bank, triggered_primary_banks[bank] ? -1 : 1, false);
113 triggered_primary_banks[bank] = !triggered_primary_banks[bank];
114 }
115 }
116
trigger_secondary_bank(Tree * caller)117 void trigger_secondary_bank(Tree* caller) {
118 if (getLabManager()->isSafeForShips()) {
119 auto shipp = &Ships[Objects[getLabManager()->CurrentObject].instance];
120 auto bank = caller->GetSelectedItem()->GetData();
121 model_anim_start_type(shipp, AnimationTriggerType::SecondaryBank, bank, triggered_primary_banks[bank] ? -1 : 1, false);
122 triggered_primary_banks[bank] = !triggered_primary_banks[bank];
123 }
124 }
125
labviewer_actions_do_triggered_anim(AnimationTriggerType type,int subobj_num,int direction)126 void labviewer_actions_do_triggered_anim(AnimationTriggerType type, int subobj_num, int direction) {
127 if (getLabManager()->isSafeForShips()) {
128 auto shipp = &Ships[Objects[getLabManager()->CurrentObject].instance];
129 model_anim_start_type(shipp, type, subobj_num, direction);
130 }
131 }
132
trigger_animation(Tree * caller)133 void trigger_animation(Tree* caller) {
134 if (getLabManager()->isSafeForShips()) {
135 auto shipp = &Ships[Objects[getLabManager()->CurrentObject].instance];
136
137 auto anim_type = static_cast<AnimationTriggerType>(caller->GetSelectedItem()->GetData());
138
139 // bool model_anim_start_type(ship *shipp, AnimationTriggerType animation_type, int subtype, int direction, bool instant = false); // for all valid subsystems
140 model_anim_start_type(shipp, anim_type, 0, manual_animations[anim_type] ? -1 : 1, false);
141 manual_animations[anim_type] = !manual_animations[anim_type];
142 }
143 }
144
trigger_scripted(Tree * caller)145 void trigger_scripted(Tree* caller) {
146 auto subobj_num = caller->GetSelectedItem()->GetData();
147 auto scripted_anim_triggers = manual_animation_triggers[AnimationTriggerType::Scripted];
148 auto direction = scripted_anim_triggers[subobj_num] ? -1 : 1;
149
150 labviewer_actions_do_triggered_anim(AnimationTriggerType::Scripted, subobj_num, direction);
151 scripted_anim_triggers[subobj_num] = !scripted_anim_triggers[subobj_num];
152 }
153
trigger_turret_firing(Tree * caller)154 void trigger_turret_firing(Tree* caller) {
155 auto subobj_num = caller->GetSelectedItem()->GetData();
156 auto turret_firing_triggers = manual_animation_triggers[AnimationTriggerType::TurretFiring];
157 auto direction = turret_firing_triggers[subobj_num] ? -1 : 1;
158
159 labviewer_actions_do_triggered_anim(AnimationTriggerType::TurretFiring, subobj_num, direction);
160 turret_firing_triggers[subobj_num] = !turret_firing_triggers[subobj_num];
161 }
162
trigger_turret_fired(Tree * caller)163 void trigger_turret_fired(Tree* caller) {
164 auto subobj_num = caller->GetSelectedItem()->GetData();
165 auto turret_fired_triggers = manual_animation_triggers[AnimationTriggerType::TurretFired];
166 auto direction = turret_fired_triggers[subobj_num] ? -1 : 1;
167
168 labviewer_actions_do_triggered_anim(AnimationTriggerType::TurretFired, subobj_num, direction);
169 turret_fired_triggers[subobj_num] = !turret_fired_triggers[subobj_num];
170 }
171
172 /***********************Dialog implementations***************************************/
173
open(Button *)174 void Actions::open(Button* /*caller*/) {
175 if (dialogWindow != nullptr)
176 return;
177
178 getLabManager()->loadWeapons();
179
180 dialogWindow = (DialogWindow*)getLabManager()->Screen->Add(new DialogWindow("Actions", gr_screen.center_offset_x + 250, gr_screen.center_offset_y + 50));
181 Assert(Opener != nullptr);
182 dialogWindow->SetOwner(Opener->getDialog());
183
184 int y = 0;
185
186 auto btn = new Button("Destroy Ship", 0, y, destroy_ship);
187 dialogWindow->AddChild(btn);
188 y += btn->GetHeight();
189
190 for (auto &dialog : subDialogs) {
191 auto *dgo = new DialogOpener(dialog, 0, y);
192 dialog->setOpener(dgo);
193 dialogWindow->AddChild(dgo);
194 y += dgo->GetHeight();
195 }
196
197 update(getLabManager()->CurrentMode, getLabManager()->CurrentClass);
198 }
199
update(LabMode newLabMode,int classIndex)200 void Actions::update(LabMode newLabMode, int classIndex) {
201 for (auto &dialog : subDialogs) {
202 dialog->update(newLabMode, classIndex);
203 }
204 }
205
open(Button *)206 void DestroySubsystems::open(Button* /*caller*/) {
207 if (dialogWindow != nullptr)
208 return;
209
210 dialogWindow = (DialogWindow*)getLabManager()->Screen->Add(new DialogWindow("Destroy Subsystems", gr_screen.center_offset_x + 400, gr_screen.center_offset_y + 50));
211 Assert(Opener != nullptr);
212 dialogWindow->SetOwner(Opener->getDialog());
213
214 update(getLabManager()->CurrentMode, getLabManager()->CurrentClass);
215 }
216
update(LabMode,int)217 void DestroySubsystems::update(LabMode, int) {
218 if (dialogWindow == nullptr)
219 return;
220
221 dialogWindow->DeleteChildren();
222
223 auto subsys_tree = (Tree*)dialogWindow->AddChild(new Tree("Subsystem List", 0, 0));
224
225 if (getLabManager()->isSafeForShips()) {
226 auto sp = &Ships[Objects[getLabManager()->CurrentObject].instance];
227 auto ssp = GET_FIRST(&sp->subsys_list);
228 auto subsys_index = 0;
229 while (ssp != END_OF_LIST(&sp->subsys_list)) {
230 subsys_tree->AddItem(nullptr, ssp->system_info->name, subsys_index, true, destroy_subsystem);
231 ssp = GET_NEXT(ssp);
232 ++subsys_index;
233 }
234 }
235 }
236
open(Button *)237 void ChangeLoadout::open(Button* /*caller*/) {
238 if (dialogWindow != nullptr)
239 return;
240
241 dialogWindow = (DialogWindow*)getLabManager()->Screen->Add(new DialogWindow("Change Loadout", gr_screen.center_offset_x + 400, gr_screen.center_offset_y + 50));
242 Assert(Opener != nullptr);
243 dialogWindow->SetOwner(Opener->getDialog());
244
245 update(getLabManager()->CurrentMode, getLabManager()->CurrentClass);
246 }
247
update(LabMode,int)248 void ChangeLoadout::update(LabMode, int) {
249 if (dialogWindow == nullptr)
250 return;
251
252 dialogWindow->DeleteChildren();
253
254 auto weapons_tree = (Tree*)dialogWindow->AddChild(new Tree("Weapons stuff", 0, 0));
255
256 if (getLabManager()->isSafeForShips()) {
257 auto sp = &Ships[Objects[getLabManager()->CurrentObject].instance];
258 auto sip = &Ship_info[sp->ship_info_index];
259
260 if (sip->is_flyable()) {
261 auto primaries_head = weapons_tree->AddItem(nullptr, "Primaries", 0, false);
262 auto secondaries_head = weapons_tree->AddItem(nullptr, "Secondaries", 0, false);
263
264 for (auto bank_num = 0; bank_num < sip->num_primary_banks; ++bank_num) {
265 SCP_string bank_string;
266 sprintf(bank_string, "Bank %i", bank_num);
267 auto bank_head = weapons_tree->AddItem(primaries_head, bank_string, 0, false);
268 auto tabled_weapons_head = weapons_tree->AddItem(bank_head, "Tabled weapons", 0, false);
269 auto others_head = weapons_tree->AddItem(bank_head, "Other Primaries", 0, false);
270 auto beams_head = weapons_tree->AddItem(others_head, "Beams", 0, false);
271 auto ballistics_head = weapons_tree->AddItem(others_head, "Ballistics", 0, false);
272 auto player_allowed_ballistics = weapons_tree->AddItem(ballistics_head, "Player Allowed", 0, false);
273 auto lasers_head = weapons_tree->AddItem(others_head, "Lasers", 0, false);
274 auto player_allowed_lasers = weapons_tree->AddItem(lasers_head, "Player Allowed", 0, false);
275 auto capitals_head = weapons_tree->AddItem(bank_head, "Capital Ship weapons", 0, false);
276
277 auto n_weapons = MIN(Weapon_info.size(), MAX_WEAPON_TYPES);
278 for (size_t j = 0; j < n_weapons; ++j) {
279 auto wip = &Weapon_info[j];
280 if (wip->subtype == WP_LASER || wip->subtype == WP_BEAM) {
281 if (sip->allowed_weapons[j] != 0) {
282 weapons_tree->AddItem(tabled_weapons_head, wip->name, bank_num, true, change_primary);
283 }
284 else {
285 TreeItem* head_item = nullptr;
286
287 if (wip->wi_flags[Weapon::Info_Flags::Beam] || wip->subtype == WP_BEAM) {
288 head_item = beams_head;
289 }
290 else if (wip->wi_flags[Weapon::Info_Flags::Huge, Weapon::Info_Flags::Supercap]) {
291 head_item = capitals_head;
292 }
293 else if (wip->subtype == WP_LASER) {
294 if (wip->wi_flags[Weapon::Info_Flags::Ballistic]) {
295 if (wip->wi_flags[Weapon::Info_Flags::Player_allowed])
296 head_item = player_allowed_ballistics;
297 else
298 head_item = ballistics_head;
299 }
300 else {
301 if (wip->wi_flags[Weapon::Info_Flags::Player_allowed])
302 head_item = player_allowed_lasers;
303 else
304 head_item = lasers_head;
305 }
306 }
307
308 if (head_item != nullptr) {
309 weapons_tree->AddItem(head_item, wip->name, bank_num, true, change_primary);
310 }
311 }
312 }
313 }
314 }
315
316 for (auto i = 0; i < sip->num_secondary_banks; ++i) {
317 SCP_string bank_string;
318 sprintf(bank_string, "Bank %i", i);
319 auto bank_head = weapons_tree->AddItem(secondaries_head, bank_string, 0, false);
320 auto tabled_weapons_head = weapons_tree->AddItem(bank_head, "Tabled weapons", 0, false);
321 auto others_head = weapons_tree->AddItem(bank_head, "Others", 0, false);
322 auto missiles_head = weapons_tree->AddItem(others_head, "Missiles", 0, false);
323 auto bombs_head = weapons_tree->AddItem(others_head, "Bombs", 0, false);
324
325 auto n_weapons = MIN(Weapon_info.size(), MAX_WEAPON_TYPES);
326 for (size_t j = 0; j < n_weapons; ++j) {
327 auto wip = &Weapon_info[j];
328 if (wip->subtype == WP_MISSILE) {
329 TreeItem* head_item = nullptr;
330
331 if (sip->allowed_weapons[j] != 0) {
332 head_item = tabled_weapons_head;
333 }
334 else {
335 if (wip->wi_flags[Weapon::Info_Flags::Bomb]) {
336 head_item = bombs_head;
337 }
338 else {
339 head_item = missiles_head;
340 }
341 }
342
343 weapons_tree->AddItem(head_item, wip->name, i, true, change_secondary);
344 }
345 }
346 }
347 }
348 }
349 }
350
open(Button *)351 void WeaponFire::open(Button* /*caller*/) {
352 if (dialogWindow != nullptr)
353 return;
354
355 dialogWindow = (DialogWindow*)getLabManager()->Screen->Add(
356 new DialogWindow("Fire weapons", gr_screen.center_offset_x + 400, gr_screen.center_offset_y + 50)
357 );
358 Assert(Opener != nullptr);
359 dialogWindow->SetOwner(Opener->getDialog());
360
361 update(getLabManager()->CurrentMode, getLabManager()->CurrentClass);
362 }
363
update(LabMode,int)364 void WeaponFire::update(LabMode, int) {
365 if (dialogWindow == nullptr) return;
366
367 if (getLabManager()->isSafeForShips()) {
368 auto sp = &Ships[Objects[getLabManager()->CurrentObject].instance];
369
370 int y = 0;
371
372 for (auto i = 0; i < sp->weapons.num_primary_banks; ++i) {
373 SCP_string bank_string;
374 sprintf(bank_string, "Primary bank %i", i);
375 auto cbp = (Checkbox*)dialogWindow->AddChild(new Checkbox((bank_string), 2, y));
376 cbp->SetFlag(&getLabManager()->FirePrimaries, 1 << i);
377 y += cbp->GetHeight() + 1;
378 }
379
380 for (auto i = 0; i < sp->weapons.num_secondary_banks; ++i) {
381 SCP_string bank_string;
382 sprintf(bank_string, "Secondary bank %i", i);
383 auto cbp = (Checkbox*)dialogWindow->AddChild(new Checkbox((bank_string), 2, y));
384 cbp->SetFlag(&getLabManager()->FireSecondaries, 1 << i);
385 y += cbp->GetHeight() + 1;
386 }
387 }
388 }
389
open(Button *)390 void AnimationTrigger::open(Button* /*caller*/) {
391 if (dialogWindow != nullptr)
392 return;
393
394 dialogWindow = (DialogWindow*)getLabManager()->Screen->Add(
395 new DialogWindow("Trigger animations", gr_screen.center_offset_x + 400, gr_screen.center_offset_y + 50)
396 );
397 Assert(Opener != nullptr);
398 dialogWindow->SetOwner(Opener->getDialog());
399
400 update(getLabManager()->CurrentMode, getLabManager()->CurrentClass);
401 }
402
update(LabMode,int)403 void AnimationTrigger::update(LabMode, int) {
404 if (dialogWindow == nullptr)
405 return;
406
407 if (getLabManager()->isSafeForShips()) {
408 auto shipp = &Ships[Objects[getLabManager()->CurrentObject].instance];
409
410 auto animations_tree = (Tree*)dialogWindow->AddChild(new Tree("Animations stuff", 0, 0));
411
412 std::map<AnimationTriggerType, TreeItem*> subsystem_headers;
413
414 animations_tree->AddItem(nullptr, "Reset animation state", 0, true, reset_animations);
415
416 auto subsystems_head = animations_tree->AddItem(nullptr, "Subsystem triggers");
417
418 manual_animation_triggers.clear();
419
420 for (const auto& entry : Animation_type_names) {
421 if (entry.first == AnimationTriggerType::Initial)
422 continue;
423
424 subsystem_headers[entry.first] = animations_tree->AddItem(subsystems_head, entry.second);
425 }
426
427 for (auto i = 0; i < shipp->weapons.num_primary_banks; ++i) {
428 SCP_string bank_string;
429 sprintf(bank_string, "Trigger animations for Bank %i", i);
430 animations_tree->AddItem(subsystem_headers[AnimationTriggerType::PrimaryBank], bank_string, i, true, trigger_primary_bank);
431 }
432
433 for (bool& triggered_primary_bank : triggered_primary_banks)
434 triggered_primary_bank = false;
435
436 for (auto i = 0; i < shipp->weapons.num_secondary_banks; ++i) {
437 SCP_string bank_string;
438 sprintf(bank_string, "Trigger animations for Bank %i", i);
439 animations_tree->AddItem(subsystem_headers[AnimationTriggerType::SecondaryBank], bank_string, i, true, trigger_secondary_bank);
440 }
441
442 for (bool& triggered_secondary_bank : triggered_secondary_banks)
443 triggered_secondary_bank = false;
444
445 auto ssp = GET_FIRST(&shipp->subsys_list);
446 auto subsys_index = 0;
447
448 // We use these vectors to handle cases where several subsystems have animations defined for the same turret firing/having fired
449 SCP_vector<int> turret_firing_subsystem_triggers;
450 SCP_vector<int> turret_fired_subsystem_triggers;
451
452 std::map<int, model_subsystem*> stupid_workaround_map; // this is a stupid workaround to avoid having to iterate over the subsys list a billion times
453
454 while (ssp != END_OF_LIST(&shipp->subsys_list)) {
455 stupid_workaround_map[ssp->system_info->subobj_num] = ssp->system_info;
456
457 if (ssp->system_info->n_triggers != 0) {
458 for (auto i = 0; i < ssp->system_info->n_triggers; ++i) {
459 auto trigger = ssp->system_info->triggers[i];
460 if (trigger.type == AnimationTriggerType::Initial)
461 continue;
462
463 switch (trigger.type) {
464 case AnimationTriggerType::TurretFiring:
465 if (!SCP_vector_contains(turret_firing_subsystem_triggers, trigger.subtype)) {
466 // For "turret-firing" animations, subtype contains the subobject number of the firing turret
467 turret_firing_subsystem_triggers.push_back(trigger.subtype);
468 manual_animation_triggers[trigger.type][trigger.subtype] = false;
469
470 }
471 break;
472 case AnimationTriggerType::TurretFired:
473 if (!SCP_vector_contains(turret_fired_subsystem_triggers, trigger.subtype)) {
474 // Same as above
475 turret_fired_subsystem_triggers.push_back(trigger.subtype);
476 manual_animation_triggers[trigger.type][ssp->system_info->subobj_num] = false;
477 }
478 break;
479 case AnimationTriggerType::Scripted:
480 manual_animation_triggers[trigger.type][ssp->system_info->subobj_num] = false;
481 animations_tree->AddItem(subsystem_headers[AnimationTriggerType::Scripted], ssp->system_info->name, ssp->system_info->subobj_num, true, trigger_scripted);
482 break;
483 default:
484 break;
485 }
486 }
487 }
488
489 ssp = GET_NEXT(ssp);
490 ++subsys_index;
491 }
492
493 for (auto subobj_num : turret_firing_subsystem_triggers) {
494 animations_tree->AddItem(subsystem_headers[AnimationTriggerType::TurretFiring], stupid_workaround_map[subobj_num]->subobj_name, subobj_num, true, trigger_turret_firing);
495 }
496
497 for (auto subobj_num : turret_fired_subsystem_triggers) {
498 animations_tree->AddItem(subsystem_headers[AnimationTriggerType::TurretFired], stupid_workaround_map[subobj_num]->subobj_name, subobj_num, true, trigger_turret_fired);
499 }
500
501 auto shipwide_head = animations_tree->AddItem(nullptr, "Shipwide triggers");
502
503 for (const auto& entry : Animation_type_names) {
504 manual_animations[entry.first] = false;
505 animations_tree->AddItem(shipwide_head, entry.second, static_cast<int>(entry.first), false, trigger_animation);
506 }
507 }
508 }
509
close()510 void AnimationTrigger::close() {
511 if (dialogWindow != nullptr) {
512 dialogWindow->DeleteChildren();
513 dialogWindow = nullptr;
514 }
515 manual_animation_triggers.clear();
516 manual_animations.clear();
517
518 for (auto& trigger : triggered_primary_banks)
519 trigger = false;
520 for (auto& trigger : triggered_secondary_banks)
521 trigger = false;
522 }