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 }