1 
2 /*
3  * Copyright (C) Volition, Inc. 1999.  All rights reserved.
4  *
5  * All source code herein is the property of Volition, Inc. You may not sell
6  * or otherwise commercially exploit the source or things you created based on the
7  * source.
8  *
9 */
10 
11 
12 
13 #include <csetjmp>
14 #include <algorithm>
15 
16 #include "ai/aibig.h"
17 #include "ai/aigoals.h"
18 #include "asteroid/asteroid.h"
19 #include "autopilot/autopilot.h"
20 #include "cmdline/cmdline.h"
21 #include "cmeasure/cmeasure.h"
22 #include "debugconsole/console.h"
23 #include "fireball/fireballs.h"
24 #include "freespace.h"
25 #include "gamesequence/gamesequence.h"
26 #include "gamesnd/eventmusic.h"
27 #include "gamesnd/gamesnd.h"
28 #include "globalincs/alphacolors.h"
29 #include "graphics/matrix.h"
30 #include "graphics/shadows.h"
31 #include "def_files/def_files.h"
32 #include "globalincs/linklist.h"
33 #include "hud/hud.h"
34 #include "hud/hudartillery.h"
35 #include "hud/hudets.h"
36 #include "hud/hudmessage.h"
37 #include "hud/hudshield.h"
38 #include "hud/hudsquadmsg.h"
39 #include "hud/hudtargetbox.h"
40 #include "hud/hudwingmanstatus.h"
41 #include "iff_defs/iff_defs.h"
42 #include "io/joy_ff.h"
43 #include "io/timer.h"
44 #include "jumpnode/jumpnode.h"
45 #include "lighting/lighting.h"
46 #include "localization/localize.h"
47 #include "math/fvi.h"
48 #include "math/staticrand.h"
49 #include "math/vecmat.h"
50 #include "mission/missioncampaign.h"
51 #include "mission/missionlog.h"
52 #include "mission/missionmessage.h"
53 #include "missionui/missionshipchoice.h"
54 #include "missionui/redalert.h"
55 #include "mod_table/mod_table.h"
56 #include "model/model.h"
57 #include "model/modelanimation_segments.h"
58 #include "model/modelrender.h"
59 #include "nebula/neb.h"
60 #include "network/multimsgs.h"
61 #include "network/multiutil.h"
62 #include "object/deadobjectdock.h"
63 #include "object/objcollide.h"
64 #include "object/object.h"
65 #include "object/objectdock.h"
66 #include "object/objectshield.h"
67 #include "object/objectsnd.h"
68 #include "object/waypoint.h"
69 #include "parse/parselo.h"
70 #include "scripting/hook_api.h"
71 #include "scripting/scripting.h"
72 #include "particle/particle.h"
73 #include "playerman/player.h"
74 #include "radar/radar.h"
75 #include "radar/radarsetup.h"
76 #include "render/3d.h"
77 #include "render/batching.h"
78 #include "scripting/api/objs/vecmath.h"
79 #include "ship/afterburner.h"
80 #include "ship/ship.h"
81 #include "ship/shipcontrails.h"
82 #include "ship/shipfx.h"
83 #include "ship/shiphit.h"
84 #include "ship/subsysdamage.h"
85 #include "species_defs/species_defs.h"
86 #include "tracing/Monitor.h"
87 #include "tracing/tracing.h"
88 #include "utils/Random.h"
89 #include "weapon/beam.h"
90 #include "weapon/corkscrew.h"
91 #include "weapon/emp.h"
92 #include "weapon/flak.h"								//phreak addded 11/05/02 for flak primaries
93 #include "weapon/shockwave.h"
94 #include "weapon/swarm.h"
95 #include "weapon/weapon.h"
96 
97 using namespace Ship;
98 
99 #ifdef MessageBox
100 #undef MessageBox
101 #endif
102 
103 #define NUM_SHIP_SUBSYSTEMS_PER_SET		200 	// Reduced from 1000 to 400 by MK on 4/1/98.  DTP; bumped from 700 to 2100
104 												// Reduced to 200 by taylor on 3/13/07  --  it's managed in dynamically allocated sets now
105 												//    Highest I saw was 164 in sm2-03a which Sandeep says has a lot of ships.
106 												//    JAS: sm3-01 needs 460.   You cannot know this number until *all* ships
107 												//    have warped in.   So I put code in the paging code which knows all ships
108 												//    that will warp in.
109 
110 static int Num_ship_subsystems = 0;
111 static int Num_ship_subsystems_allocated = 0;
112 
113 static SCP_vector<ship_subsys*> Ship_subsystems;
114 ship_subsys ship_subsys_free_list;
115 
116 extern bool splodeing;
117 extern float splode_level;
118 extern int splodeingtexture;
119 
120 #define SHIP_REPAIR_SUBSYSTEM_RATE	0.01f
121 
122 // The minimum required fuel to engage afterburners
123 static const float DEFAULT_MIN_AFTERBURNER_FUEL_TO_ENGAGE = 10.0f;
124 
125 
126 int	Ai_render_debug_flag=0;
127 #ifndef NDEBUG
128 int	Ship_auto_repair = 1;		// flag to indicate auto-repair of subsystem should occur
129 #endif
130 
131 int	Num_wings = 0;
132 int	Num_reinforcements = 0;
133 ship	Ships[MAX_SHIPS];
134 
135 ship	*Player_ship;
136 int		*Player_cockpit_textures;
137 SCP_vector<cockpit_display> Player_displays;
138 
139 wing	Wings[MAX_WINGS];
140 bool	Ships_inited = false;
141 bool	Armor_inited = false;
142 
143 int	Starting_wings[MAX_STARTING_WINGS];  // wings player starts a mission with (-1 = none)
144 
145 // Goober5000
146 int Squadron_wings[MAX_SQUADRON_WINGS];
147 int TVT_wings[MAX_TVT_WINGS];
148 
149 // Goober5000
150 char Starting_wing_names[MAX_STARTING_WINGS][NAME_LENGTH];
151 char Squadron_wing_names[MAX_SQUADRON_WINGS][NAME_LENGTH];
152 bool Squadron_wing_names_found[MAX_SQUADRON_WINGS];
153 char TVT_wing_names[MAX_TVT_WINGS][NAME_LENGTH];
154 
155 SCP_vector<engine_wash_info> Engine_wash_info;
156 
157 static engine_wash_info *get_engine_wash_pointer(char* engine_wash_name);
158 static int subsys_set(int objnum, int ignore_subsys_info = 0);
159 static void ship_add_cockpit_display(cockpit_display_info *display, int cockpit_model_num);
160 static void ship_set_hud_cockpit_targets();
161 static int thruster_glow_anim_load(generic_anim *ga);
162 static int ship_get_exp_propagates(ship *sp);
163 static bool ship_subsys_is_fighterbay(ship_subsys *ss);
164 static int ship_template_lookup(const char *token);
165 static void ship_set_eye(object *obj, int eye_index);
166 static void ship_start_targeting_laser(ship *shipp);
167 static void ship_add_ship_type_kill_count(int ship_info_index);
168 static int ship_info_lookup_sub(const char *token);
169 
170 void ship_reset_disabled_physics(object *objp, int ship_class);
171 
172 // forward declaring for parse_ship()
173 static void parse_ship_values(ship_info* sip, const bool is_template, const bool first_time, const bool replace);
174 
175 // information for ships which have exited the game
176 SCP_vector<exited_ship> Ships_exited;
177 
178 SCP_vector<ship_registry_entry> Ship_registry;
179 SCP_unordered_map<SCP_string, int, SCP_string_lcase_hash, SCP_string_lcase_equal_to> Ship_registry_map;
180 
ship_registry_get(const char * name)181 const ship_registry_entry *ship_registry_get(const char *name)
182 {
183 	auto ship_it = Ship_registry_map.find(name);
184 	if (ship_it != Ship_registry_map.end())
185 		return &Ship_registry[ship_it->second];
186 
187 	return nullptr;
188 }
189 
190 
191 int	Num_engine_wash_types;
192 int	Num_ship_subobj_types;
193 int	Num_ship_subobjects;
194 int	Player_ship_class;	// needs to be player specific, move to player structure
195 
196 #define		SHIP_OBJ_USED	(1<<0)				// flag used in ship_obj struct
197 #define		MAX_SHIP_OBJS	MAX_SHIPS			// max number of ships tracked in ship list
198 ship_obj		Ship_objs[MAX_SHIP_OBJS];		// array used to store ship object indexes
199 ship_obj		Ship_obj_list;							// head of linked list of ship_obj structs
200 
201 SCP_vector<ship_info>	Ship_info;
202 reinforcements	Reinforcements[MAX_REINFORCEMENTS];
203 SCP_vector<ship_info>	Ship_templates;
204 
205 SCP_vector<ship_type_info> Ship_types;
206 
207 SCP_vector<ArmorType> Armor_types;
208 SCP_vector<DamageTypeStruct>	Damage_types;
209 
210 flag_def_list Armor_flags[] = {
211 	{ "ignore subsystem armor",		SAF_IGNORE_SS_ARMOR,	0 }
212 };
213 
214 const int Num_armor_flags = sizeof(Armor_flags)/sizeof(flag_def_list);
215 
216 
217 flag_def_list_new<Thruster_Flags> Man_types[] = {
218     { "Bank right", Thruster_Flags::Bank_right, true, false },
219     { "Bank left",	Thruster_Flags::Bank_left,	true, false },
220     { "Pitch up",	Thruster_Flags::Pitch_up,	true, false },
221     { "Pitch down", Thruster_Flags::Pitch_down, true, false },
222     { "Roll right", Thruster_Flags::Roll_right, true, false },
223     { "Roll left",	Thruster_Flags::Roll_left,	true, false },
224     { "Slide right",Thruster_Flags::Slide_right,true, false },
225     { "Slide left", Thruster_Flags::Slide_left, true, false },
226     { "Slide up",	Thruster_Flags::Slide_up,	true, false },
227     { "Slide down", Thruster_Flags::Slide_down, true, false },
228     { "Forward",	Thruster_Flags::Forward,	true, false },
229     { "Reverse",	Thruster_Flags::Reverse,	true, false }
230 };
231 
232 const size_t Num_man_types = sizeof(Man_types) / sizeof(flag_def_list_new<Thruster_Flags>);
233 
234 // Goober5000 - I figured we should keep this separate
235 // from Comm_orders, considering how I redid it :p
236 // (and also because we may want to change either
237 // the order text or the flag text in the future)
238 flag_def_list Player_orders[] = {
239 	// common stuff
240 	{ "attack ship",		ATTACK_TARGET_ITEM,		0 },
241 	{ "disable ship",		DISABLE_TARGET_ITEM,	0 },
242 	{ "disarm ship",		DISARM_TARGET_ITEM,		0 },
243 	{ "disable subsys",		DISABLE_SUBSYSTEM_ITEM,	0 },
244 	{ "guard ship",			PROTECT_TARGET_ITEM,	0 },
245 	{ "ignore ship",		IGNORE_TARGET_ITEM,		0 },
246 	{ "form on wing",		FORMATION_ITEM,			0 },
247 	{ "cover me",			COVER_ME_ITEM,			0 },
248 	{ "attack any",			ENGAGE_ENEMY_ITEM,		0 },
249 
250 	// transports mostly
251 	{ "dock",				CAPTURE_TARGET_ITEM,	0 },
252 
253 	// support ships
254 	{ "rearm me",			REARM_REPAIR_ME_ITEM,	0 },
255 	{ "abort rearm",		ABORT_REARM_REPAIR_ITEM,	0 },
256 
257 	// all ships
258 	{ "depart",				DEPART_ITEM,			0 },
259 
260 	// extra stuff for support
261 	{ "stay near me",		STAY_NEAR_ME_ITEM,		0 },
262 	{ "stay near ship",		STAY_NEAR_TARGET_ITEM,	0 },
263 	{ "keep safe dist",		KEEP_SAFE_DIST_ITEM,	0 }
264 };
265 
266 const int Num_player_orders = sizeof(Player_orders)/sizeof(flag_def_list);
267 
268 // Use the last parameter here to tell the parser whether to stuff the flag into flags or flags2
269 flag_def_list_new<Model::Subsystem_Flags> Subsystem_flags[] = {
270 	{ "untargetable",			    Model::Subsystem_Flags::Untargetable,		                true, false },
271 	{ "carry no damage",		    Model::Subsystem_Flags::Carry_no_damage,	                true, false },
272 	{ "use multiple guns",		    Model::Subsystem_Flags::Use_multiple_guns,	                true, false },
273 	{ "fire down normals",		    Model::Subsystem_Flags::Fire_on_normal,	                    true, false },
274 	{ "check hull",				    Model::Subsystem_Flags::Turret_hull_check,	                true, false },
275 	{ "fixed firingpoints",		    Model::Subsystem_Flags::Turret_fixed_fp,	                true, false },
276 	{ "salvo mode",				    Model::Subsystem_Flags::Turret_salvo,		                true, false },
277 	{ "no subsystem targeting",	    Model::Subsystem_Flags::No_ss_targeting,	                true, false },
278 	{ "fire on target",			    Model::Subsystem_Flags::Fire_on_target,	                    true, false },
279     { "reset when idle",		    Model::Subsystem_Flags::Turret_reset_idle,	                true, false },
280 	{ "carry shockwave",		    Model::Subsystem_Flags::Carry_shockwave,	                true, false },
281 	{ "allow landing",			    Model::Subsystem_Flags::Allow_landing,		                true, false },
282 	{ "target requires fov",	    Model::Subsystem_Flags::Fov_required,		                true, false },
283 	{ "fov edge checks",		    Model::Subsystem_Flags::Fov_edge_check,	                    true, false },
284 	{ "no replace",				    Model::Subsystem_Flags::No_replace,		                    true, false },
285 	{ "no live debris",			    Model::Subsystem_Flags::No_live_debris,	                    true, false },
286 	{ "ignore if dead",			    Model::Subsystem_Flags::Ignore_if_dead,	                    true, false },
287 	{ "allow vanishing",		    Model::Subsystem_Flags::Allow_vanishing,	                true, false },
288 	{ "damage as hull",			    Model::Subsystem_Flags::Damage_as_hull,	                    true, false },
289 	{ "starts locked",              Model::Subsystem_Flags::Turret_locked,                      true, false },
290 	{ "no aggregate",			    Model::Subsystem_Flags::No_aggregate,		                true, false },
291 	{ "wait for animation",         Model::Subsystem_Flags::Turret_anim_wait,                   true, false },
292 	{ "play fire sound for player", Model::Subsystem_Flags::Player_turret_sound,                true, false },
293 	{ "only target if can fire",    Model::Subsystem_Flags::Turret_only_target_if_can_fire,     true, false },
294 	{ "no disappear",			    Model::Subsystem_Flags::No_disappear,                       true, false },
295 	{ "collide submodel",		    Model::Subsystem_Flags::Collide_submodel,                   true, false },
296 	{ "allow destroyed rotation",	Model::Subsystem_Flags::Destroyed_rotation,                 true, false },
297 	{ "turret use ammo",		    Model::Subsystem_Flags::Turret_use_ammo,                    true, false },
298 	{ "autorepair if disabled",	    Model::Subsystem_Flags::Autorepair_if_disabled,             true, false },
299 	{ "don't autorepair if disabled", Model::Subsystem_Flags::No_autorepair_if_disabled,        true, false },
300 	{ "share fire direction",       Model::Subsystem_Flags::Share_fire_direction,               true, false },
301 	{ "no damage spew",             Model::Subsystem_Flags::No_sparks,                          true, false },
302 	{ "no impact debris",           Model::Subsystem_Flags::No_impact_debris,                    true, false },
303 };
304 
305 const size_t Num_subsystem_flags = sizeof(Subsystem_flags)/sizeof(flag_def_list_new<Model::Subsystem_Flags>);
306 
307 
308 flag_def_list_new<Info_Flags> Ship_flags[] = {
309     { "no_collide",					Info_Flags::No_collide,				true, false },
310     { "player_ship",				Info_Flags::Player_ship,			true, false },
311     { "default_player_ship",		Info_Flags::Default_player_ship,	true, false },
312     { "repair_rearm",				Info_Flags::Support,				true, false },
313     { "cargo",						Info_Flags::Cargo,					true, false },
314     { "fighter",					Info_Flags::Fighter,				true, false },
315     { "bomber",						Info_Flags::Bomber,					true, false },
316     { "transport",					Info_Flags::Transport,				true, false },
317     { "freighter",					Info_Flags::Freighter,				true, false },
318     { "capital",					Info_Flags::Capital,				true, false },
319     { "supercap",					Info_Flags::Supercap,				true, false },
320     { "drydock",					Info_Flags::Drydock,				true, false },
321     { "cruiser",					Info_Flags::Cruiser,				true, false },
322     { "navbuoy",					Info_Flags::Navbuoy,				true, false },
323     { "sentrygun",					Info_Flags::Sentrygun,				true, false },
324     { "escapepod",					Info_Flags::Escapepod,				true, false },
325     { "stealth",					Info_Flags::Stealth,				true, false },
326     { "no type",					Info_Flags::No_ship_type,			true, false },
327     { "ship copy",					Info_Flags::Ship_copy,				true, false },
328     { "in tech database",			Info_Flags::In_tech_database,		true, false },
329     { "in tech database multi",		Info_Flags::In_tech_database_m,		true, false },
330     { "don't collide invisible",	Info_Flags::Ship_class_dont_collide_invis, true, false },
331     { "big damage",					Info_Flags::Big_damage,				true, false },
332     { "corvette",					Info_Flags::Corvette,				true, false },
333     { "gas miner",					Info_Flags::Gas_miner,				true, false },
334     { "awacs",						Info_Flags::Awacs,					true, false },
335     { "knossos",					Info_Flags::Knossos_device,			true, false },
336     { "no_fred",					Info_Flags::No_fred,				true, false },
337     { "flash",						Info_Flags::Flash,					true, false },
338     { "surface shields",			Info_Flags::Surface_shields,		true, false },
339     { "show ship",					Info_Flags::Show_ship_model,		true, false },
340     { "generate icon",				Info_Flags::Generate_hud_icon,		true, false },
341     { "no weapon damage scaling",	Info_Flags::Disable_weapon_damage_scaling, true, false },
342     { "gun convergence",			Info_Flags::Gun_convergence,		true, false },
343     { "no thruster geometry noise", Info_Flags::No_thruster_geo_noise,	true, false },
344     { "intrinsic no shields",		Info_Flags::Intrinsic_no_shields,	true, false },
345     { "dynamic primary linking",	Info_Flags::Dyn_primary_linking,	true, false },
346     { "no primary linking",			Info_Flags::No_primary_linking,		true, false },
347     { "no pain flash",				Info_Flags::No_pain_flash,			true, false },
348     { "no ets",						Info_Flags::No_ets,					true, false },
349     { "no lighting",				Info_Flags::No_lighting,			true, false },
350     { "auto spread shields",		Info_Flags::Auto_spread_shields,	true, false },
351     { "model point shields",		Info_Flags::Model_point_shields,	true, false },
352     { "repair disabled subsystems", Info_Flags::Subsys_repair_when_disabled, true, false},
353 	{ "don't bank when turning",	Info_Flags::Dont_bank_when_turning,		true, false },
354 	{ "don't clamp max velocity",	Info_Flags::Dont_clamp_max_velocity,	true, false },
355 	{ "instantaneous acceleration",	Info_Flags::Instantaneous_acceleration,	true, false },
356 	{ "large ship deathroll",		Info_Flags::Large_ship_deathroll,	true, false },
357 	{ "no impact debris",			Info_Flags::No_impact_debris,		true, false },
358     // to keep things clean, obsolete options go last
359     { "ballistic primaries",		Info_Flags::Ballistic_primaries,	false, false }
360 };
361 
362 const size_t Num_ship_flags = sizeof(Ship_flags) / sizeof(flag_def_list_new<Info_Flags>);
363 
364 /*
365 ++Here be dragons.. err.. begins the section for the ai targeting revision
366 ++  First flag_def_list (& its size) for object types (ship/asteroid/weapon)
367 ++  List of reasonable object flags (from object.h)
368 ++  List of potentially useful ship class flags
369 ++  List of potentially useful weapon class flags
370 */
371 flag_def_list ai_tgt_objects[] = {
372 	{ "ship",		OBJ_SHIP,		0 },
373 	{ "asteroid",	OBJ_ASTEROID,	0 },
374 	{ "weapon",		OBJ_WEAPON,		0 },
375 	{ "debris",		OBJ_DEBRIS,		0 },
376 };
377 
378 const int num_ai_tgt_objects = sizeof(ai_tgt_objects) / sizeof(flag_def_list);
379 
380 flag_def_list_new<Object::Object_Flags> ai_tgt_obj_flags[] = {
381 	{ "no shields",			Object::Object_Flags::No_shields,			true, false },
382 	{ "targetable as bomb",	Object::Object_Flags::Targetable_as_bomb,	true, false },
383 	{ "renders",			Object::Object_Flags::Renders,				true, false },
384 	{ "collides",			Object::Object_Flags::Collides,				true, false },
385 	{ "physics",			Object::Object_Flags::Physics,				true, false },
386 	{ "invulnerable",		Object::Object_Flags::Invulnerable,			true, false },
387 	{ "player ship",		Object::Object_Flags::Player_ship,			true, false },
388 	{ "special warpin",		Object::Object_Flags::Special_warpin,		true, false },
389 	{ "immobile",			Object::Object_Flags::Immobile,				true, false },
390 };
391 
392 const int num_ai_tgt_obj_flags = sizeof(ai_tgt_obj_flags) / sizeof(flag_def_list_new<Object::Object_Flags>);
393 
394 
395 flag_def_list_new<Ship::Info_Flags> ai_tgt_ship_flags[] = {
396 	{ "afterburners",					Info_Flags::Afterburner,					true, false },
397 	{ "big damage",						Info_Flags::Big_damage,						true, false },
398 	{ "has awacs",						Info_Flags::Has_awacs,						true, false },
399 	{ "no collide",						Info_Flags::No_collide,						true, false },
400 	{ "player ship",					Info_Flags::Player_ship,					true, false },
401 	{ "default player ship", 			Info_Flags::Default_player_ship,			true, false },
402 	{ "path fixup",						Info_Flags::Path_fixup,						true, false },
403 	{ "support",						Info_Flags::Support,						true, false },
404 	{ "cargo",							Info_Flags::Cargo,							true, false },
405 	{ "fighter",						Info_Flags::Fighter,						true, false },
406 	{ "bomber",							Info_Flags::Bomber,							true, false },
407 	{ "cruiser",						Info_Flags::Cruiser,						true, false },
408 	{ "freighter",						Info_Flags::Freighter,						true, false },
409 	{ "capital",						Info_Flags::Capital,						true, false },
410 	{ "transport",						Info_Flags::Transport,						true, false },
411 	{ "navbuoy",						Info_Flags::Navbuoy,						true, false },
412 	{ "sentrygun",						Info_Flags::Sentrygun,						true, false },
413 	{ "escapepod",						Info_Flags::Escapepod,						true, false },
414 	{ "no type",						Info_Flags::No_ship_type,					true, false },
415 	{ "ship copy",						Info_Flags::Ship_copy,						true, false },
416 	{ "in tech database", 				Info_Flags::In_tech_database,				true, false },
417 	{ "in tech database multi",			Info_Flags::In_tech_database_m,				true, false },
418 	{ "stealth",						Info_Flags::Stealth,						true, false },
419 	{ "supercap",						Info_Flags::Supercap,						true, false },
420 	{ "drydock",						Info_Flags::Drydock,						true, false },
421 	{ "dont collide invisible",			Info_Flags::Ship_class_dont_collide_invis,	true, false },
422 	{ "corvette",						Info_Flags::Corvette,						true, false },
423 	{ "gas miner",						Info_Flags::Gas_miner,						true, false },
424 	{ "awacs",							Info_Flags::Awacs,							true, false },
425 	{ "knossos",						Info_Flags::Knossos_device,					true, false },
426 	{ "no fred",						Info_Flags::No_fred,						true, false },
427 	{ "default in tech database",		Info_Flags::Default_in_tech_database,		true, false },
428 	{ "default in tech database multi",	Info_Flags::Default_in_tech_database_m,		true, false },
429 	{ "flash",							Info_Flags::Flash,							true, false },
430 	{ "show ship",						Info_Flags::Show_ship_model,				true, false },
431 	{ "surface shields",				Info_Flags::Surface_shields,				true, false },
432 	{ "generate icon",					Info_Flags::Generate_hud_icon,				true, false },
433 	{ "no weapon damage scaling",		Info_Flags::Disable_weapon_damage_scaling,	true, false },
434 	{ "gun convergence",				Info_Flags::Gun_convergence,				true, false },
435 	{ "no thruster geometry noise",		Info_Flags::No_thruster_geo_noise,			true, false },
436 	{ "intrinsic no shields",			Info_Flags::Intrinsic_no_shields,			true, false },
437 	{ "no primary linking",				Info_Flags::No_primary_linking,				true, false },
438 	{ "no pain flash",					Info_Flags::No_pain_flash,					true, false },
439 	{ "allow landings",					Info_Flags::Allow_landings,					true, false },
440 	{ "no ets",							Info_Flags::No_ets,							true, false },
441 	{ "no lighting",					Info_Flags::No_lighting,					true, false },
442 	{ "dyn primary linking",			Info_Flags::Dyn_primary_linking,			true, false },
443 	{ "auto spread shields",			Info_Flags::Auto_spread_shields,			true, false },
444 	{ "draw weapon models",				Info_Flags::Draw_weapon_models,				true, false },
445 	{ "model point shields",			Info_Flags::Model_point_shields,			true, false },
446 	{ "repair disabled subsystems",		Info_Flags::Subsys_repair_when_disabled,	true, false },
447 };
448 
449 const int num_ai_tgt_ship_flags = sizeof(ai_tgt_ship_flags) / sizeof(flag_def_list_new<Ship::Info_Flags>);
450 
451 flag_def_list_new<Weapon::Info_Flags> ai_tgt_weapon_flags[] = {
452     { "bomb",				        Weapon::Info_Flags::Bomb,			                    true, false },
453     { "huge damage",		        Weapon::Info_Flags::Huge,			                    true, false },
454     { "supercap damage",	        Weapon::Info_Flags::Supercap,		                    true, false },
455     { "bomber+",			        Weapon::Info_Flags::Bomber_plus,	                    true, false },
456     { "electronics",		        Weapon::Info_Flags::Electronics,	                    true, false },
457     { "puncture",			        Weapon::Info_Flags::Puncture,		                    true, false },
458     { "emp",				        Weapon::Info_Flags::Emp,			                    true, false },
459     { "heat seeking",		        Weapon::Info_Flags::Homing_heat,	                    true, false },
460     { "aspect seeking",		        Weapon::Info_Flags::Homing_aspect,	                    true, false },
461     { "engine seeking",		        Weapon::Info_Flags::Homing_javelin,                     true, false },
462     { "pierce shields",		        Weapon::Info_Flags::Pierce_shields,                     true, false },
463     { "local ssm",			        Weapon::Info_Flags::Local_ssm,		                    true, false },
464     { "capital+",			        Weapon::Info_Flags::Capital_plus,	                    true, false },
465     { "heat-seeking",				Weapon::Info_Flags::Homing_heat,						true, false },
466     { "aspect-seeking",				Weapon::Info_Flags::Homing_aspect,						true, false },
467     { "javelin",					Weapon::Info_Flags::Homing_javelin,						true, false },
468     { "spawn",						Weapon::Info_Flags::Spawn,								true, false },
469     { "remote detonate",			Weapon::Info_Flags::Remote,								true, false },
470     { "countermeasure",				Weapon::Info_Flags::Cmeasure,							true, false },
471     { "turns",						Weapon::Info_Flags::Turns,								true, false },
472     { "swarm",						Weapon::Info_Flags::Swarm,								true, false },
473     { "trail",						Weapon::Info_Flags::Trail,								true, false },
474     { "big ship",					Weapon::Info_Flags::Big_only,							true, false },
475     { "child",						Weapon::Info_Flags::Child,								true, false },
476     { "no dumbfire",				Weapon::Info_Flags::No_dumbfire,						true, false },
477 	{ "no doublefire",				Weapon::Info_Flags::No_doublefire,						true, false },
478     { "thruster",					Weapon::Info_Flags::Thruster,							true, false },
479     { "in tech database",			Weapon::Info_Flags::In_tech_database,					true, false },
480     { "player allowed",				Weapon::Info_Flags::Player_allowed,						true, false },
481     { "corkscrew",					Weapon::Info_Flags::Corkscrew,							true, false },
482     { "particle spew",				Weapon::Info_Flags::Particle_spew,						true, false },
483     { "esuck",						Weapon::Info_Flags::Energy_suck,						true, false },
484     { "flak",						Weapon::Info_Flags::Flak,								true, false },
485     //{ "beam",						Weapon::Info_Flags::Beam,								true, false },	// Okay, this one probably doesn't make sense.
486     { "tag",						Weapon::Info_Flags::Tag,								true, false },
487     { "shudder",					Weapon::Info_Flags::Shudder,							true, false },
488     { "lockarm",					Weapon::Info_Flags::Lockarm,							true, false },
489     { "stream",						Weapon::Info_Flags::Stream,								true, false },
490     { "ballistic",					Weapon::Info_Flags::Ballistic,							true, false },
491     { "default in tech database",	Weapon::Info_Flags::Default_in_tech_database,			true, false },
492     { "tagged only",				Weapon::Info_Flags::Tagged_only,						true, false },
493     { "cycle",						Weapon::Info_Flags::Cycle,								true, false },
494     { "small only",					Weapon::Info_Flags::Small_only,					    	true, false },
495     { "same turret cooldown",		Weapon::Info_Flags::Same_turret_cooldown,				true, false },
496     { "apply no light",				Weapon::Info_Flags::Mr_no_lighting,				    	true, false },
497     { "transparent",				Weapon::Info_Flags::Transparent,						true, false },
498     { "training",					Weapon::Info_Flags::Training,							true, false },
499     { "smart spawn",				Weapon::Info_Flags::Smart_spawn,						true, false },
500     { "inherit parent target",		Weapon::Info_Flags::Inherit_parent_target,				true, false },
501     { "no emp kill",				Weapon::Info_Flags::No_emp_kill,						true, false },
502     { "variable lead homing",		Weapon::Info_Flags::Variable_lead_homing,				true, false },
503     { "untargeted heat seeker",		Weapon::Info_Flags::Untargeted_heat_seeker,			    true, false },
504     { "no radius doubling",			Weapon::Info_Flags::No_radius_doubling,					true, false },
505     { "no subsystem homing",		Weapon::Info_Flags::Non_subsys_homing,					true, false },
506     { "no lifeleft penalty",		Weapon::Info_Flags::No_life_lost_if_missed,			    true, false },
507     { "custom seeker str",			Weapon::Info_Flags::Custom_seeker_str,					true, false },
508     { "can be targeted",			Weapon::Info_Flags::Can_be_targeted,					true, false },
509     { "show on radar",				Weapon::Info_Flags::Shown_on_radar,					    true, false },
510     { "show friendly on radar",		Weapon::Info_Flags::Show_friendly,						true, false },
511     { "chain external model fps",	Weapon::Info_Flags::External_weapon_fp,				    true, false },
512     { "external model launcher",	Weapon::Info_Flags::External_weapon_lnch,				true, false },
513     { "takes blast damage",			Weapon::Info_Flags::Takes_blast_damage,				    true, false },
514     { "takes shockwave damage",		Weapon::Info_Flags::Takes_shockwave_damage,			    true, false },
515     { "hide from radar",			Weapon::Info_Flags::Dont_show_on_radar,				    true, false },
516     { "render flak",				Weapon::Info_Flags::Render_flak,						true, false },
517     { "ciws",						Weapon::Info_Flags::Ciws,								true, false },
518     { "anti-subsystem beam",		Weapon::Info_Flags::Antisubsysbeam,					    true, false },
519     { "no primary linking",			Weapon::Info_Flags::Nolink,							    true, false },
520     { "same emp time for capships",	Weapon::Info_Flags::Use_emp_time_for_capship_turrets,	true, false },
521     { "no primary linked penalty",	Weapon::Info_Flags::No_linked_penalty,					true, false },
522     { "no homing speed ramp",		Weapon::Info_Flags::No_homing_speed_ramp,				true, false },
523     { "pulls aspect seekers",		Weapon::Info_Flags::Cmeasure_aspect_home_on,			true, false },
524     { "turret interceptable",		Weapon::Info_Flags::Turret_Interceptable,				true, false },
525     { "fighter interceptable",		Weapon::Info_Flags::Fighter_Interceptable,				true, false },
526     { "aoe electronics",			Weapon::Info_Flags::Aoe_Electronics,					true, false },
527     { "apply recoil",				Weapon::Info_Flags::Apply_Recoil,						true, false },
528     { "don't spawn if shot",		Weapon::Info_Flags::Dont_spawn_if_shot,				    true, false },
529     { "die on lost lock",			Weapon::Info_Flags::Die_on_lost_lock,					true, false },
530 	{ "no impact spew",				Weapon::Info_Flags::No_impact_spew,						true, false },
531 	{ "require exact los",			Weapon::Info_Flags::Require_exact_los,					true, false },
532 	{ "multilock target dead subsys", Weapon::Info_Flags::Multilock_target_dead_subsys,		true, false },
533 };
534 
535 const int num_ai_tgt_weapon_info_flags = sizeof(ai_tgt_weapon_flags) / sizeof(flag_def_list_new<Weapon::Info_Flags>);
536 
537 SCP_vector <ai_target_priority> Ai_tp_list;
538 
539 //	Constant for flag,				Name of flag,				In flags or flags2
540 //  When adding new flags remember to bump MAX_SHIP_FLAG_NAMES in ship.h
541 ship_flag_name Ship_flag_names[] = {
542 	{ Ship_Flags::Vaporize,						"vaporize" },
543 	{ Ship_Flags::Warp_broken,					"break-warp" },
544 	{ Ship_Flags::Warp_never,					"never-warp" },
545 	{ Ship_Flags::Scannable,					"scannable" },
546 	{ Ship_Flags::Cargo_revealed,				"cargo-known" },
547 	{ Ship_Flags::Hidden_from_sensors,			"hidden-from-sensors" },
548 	{ Ship_Flags::Stealth,						"stealth" },
549 	{ Ship_Flags::Friendly_stealth_invis,		"friendly-stealth-invisible" },
550 	{ Ship_Flags::Hide_ship_name,				"hide-ship-name" },
551 	{ Ship_Flags::Primitive_sensors,			"primitive-sensors" },
552 	{ Ship_Flags::Afterburner_locked,			"afterburners-locked" },
553 	{ Ship_Flags::Primaries_locked,				"primaries-locked" },
554 	{ Ship_Flags::Secondaries_locked,			"secondaries-locked" },
555 	{ Ship_Flags::No_subspace_drive,			"no-subspace-drive" },
556 	{ Ship_Flags::Dont_collide_invis,			"don't-collide-invisible" },
557 	{ Ship_Flags::No_ets,						"no-ets" },
558 	{ Ship_Flags::Toggle_subsystem_scanning,	"toggle-subsystem-scanning" },
559 	{ Ship_Flags::No_secondary_lockon,			"no-secondary-lock-on"},
560 	{ Ship_Flags::No_disabled_self_destruct,	"no-disabled-self-destruct"},
561 };
562 
563 static int Laser_energy_out_snd_timer;	// timer so we play out of laser sound effect periodically
564 static int Missile_out_snd_timer;	// timer so we play out of laser sound effect periodically
565 
566 SCP_vector<ship_counts>	Ship_type_counts;
567 
568 SCP_vector<wing_formation> Wing_formations;
569 
570 const std::shared_ptr<scripting::Hook> OnCountermeasureFireHook = scripting::Hook::Factory("On Countermeasure Fire",
571 	"Called when a ship fires a countermeasure.",
572 	{
573 		{"Ship", "ship", "The ship that has fired the countermeasure."},
574 		{"CountermeasuresLeft", "number", "The number of countermeasures left on the ship after firing the current countermeasure."},
575 		{"Countermeasure", "weapon", "The countermeasure object that was just fired."},
576 	});
577 
578 // I don't want to do an AI cargo check every frame, so I made a global timer to limit check to
579 // every SHIP_CARGO_CHECK_INTERVAL ms.  Didn't want to make a timer in each ship struct.  Ensure
580 // inited to 1 at mission start.
581 static int Ship_cargo_check_timer;
582 
583 static int Thrust_anim_inited = 0;
584 
585 static int ship_get_subobj_model_num(ship_info* sip, char* subobj_name);
586 
587 SCP_vector<ship_effect> Ship_effects;
588 
589 int ship_render_mode = MODEL_RENDER_ALL;
590 /**
591  * Set the ship_obj struct fields to default values
592  */
ship_obj_list_reset_slot(int index)593 static void ship_obj_list_reset_slot(int index)
594 {
595 	Ship_objs[index].flags = 0;
596 	Ship_objs[index].next = NULL;
597 	Ship_objs[index].prev = (ship_obj*)-1;
598 }
599 
600 /**
601  * If the given ship is in my squadron wings
602  */
ship_in_my_squadron(ship * shipp)603 static int ship_in_my_squadron(ship *shipp)
604 {
605 	int i;
606 
607 	for (i=0; i<MAX_STARTING_WINGS; i++)
608 	{
609 		if (shipp->wingnum == Starting_wings[i])
610 			return 1;
611 	}
612 
613 	for (i=0; i<MAX_TVT_WINGS; i++)
614 	{
615 		if (shipp->wingnum == TVT_wings[i])
616 			return 1;
617 	}
618 
619 	// not in
620 	return 0;
621 }
622 
623 /**
624  * Initialise Ship_obj_list
625  */
ship_obj_list_init()626 static void ship_obj_list_init()
627 {
628 	int i;
629 
630 	list_init(&Ship_obj_list);
631 	for ( i = 0; i < MAX_SHIP_OBJS; i++ ) {
632 		ship_obj_list_reset_slot(i);
633 	}
634 }
635 
636 /**
637  * Function to add a node to the Ship_obj_list.  Only
638  * called from ::ship_create()
639  */
ship_obj_list_add(int objnum)640 static int ship_obj_list_add(int objnum)
641 {
642 	int i;
643 
644 	for ( i = 0; i < MAX_SHIP_OBJS; i++ ) {
645 		if ( !(Ship_objs[i].flags & SHIP_OBJ_USED) )
646 			break;
647 	}
648 	if ( i == MAX_SHIP_OBJS ) {
649 		Error(LOCATION, "Fatal Error: Ran out of ship object nodes\n");
650 		return -1;
651 	}
652 
653 	Ship_objs[i].flags = 0;
654 	Ship_objs[i].objnum = objnum;
655 	list_append(&Ship_obj_list, &Ship_objs[i]);
656 	Ship_objs[i].flags |= SHIP_OBJ_USED;
657 
658 	return i;
659 }
660 
661 /**
662  * Function to remove a node from the Ship_obj_list.  Only
663  * called from ::ship_delete()
664  */
ship_obj_list_remove(int index)665 static void ship_obj_list_remove(int index)
666 {
667 	Assert(index >= 0 && index < MAX_SHIP_OBJS);
668 	list_remove( Ship_obj_list, &Ship_objs[index]);
669 	ship_obj_list_reset_slot(index);
670 }
671 
get_ship_obj_ptr_from_index(int index)672 ship_obj *get_ship_obj_ptr_from_index(int index)
673 {
674 	Assert(index >= 0 && index < MAX_SHIP_OBJS);
675 	return &Ship_objs[index];
676 }
677 
678 /**
679  * Return number of ships in the game.
680  */
ship_get_num_ships()681 int ship_get_num_ships()
682 {
683 	int count;
684 	ship_obj *so;
685 
686 	count = 0;
687 	for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) )
688 		count++;
689 
690 	return count;
691 }
692 
engine_wash_info_init(engine_wash_info * ewi)693 static void engine_wash_info_init(engine_wash_info *ewi)
694 {
695 	ewi->name[0] = '\0';
696 	ewi->angle = PI / 10.0f;
697 	ewi->radius_mult = 1.0f;
698 	ewi->length = 500.0f;
699 	ewi->intensity = 1.0f;
700 }
701 
702 /**
703  * Parse an engine wash info record
704  */
parse_engine_wash(bool replace)705 static void parse_engine_wash(bool replace)
706 {
707 	engine_wash_info ewt;
708 	engine_wash_info_init(&ewt);
709 
710 	engine_wash_info *ewp;
711 	bool create_if_not_found  = true;
712 
713 	// name of engine wash info
714 	required_string("$Name:");
715 	stuff_string(ewt.name, F_NAME, NAME_LENGTH);
716 
717 	if(optional_string("+nocreate")) {
718 		if(!replace) {
719 			Warning(LOCATION, "+nocreate flag used for engine wash in non-modular table");
720 		}
721 		create_if_not_found = false;
722 	}
723 
724 	//Does this engine wash exist already?
725 	//If so, load this new info into it
726 	//Otherwise, increment Num_engine_wash_types
727 	ewp = get_engine_wash_pointer(ewt.name);
728 	if(ewp != NULL)
729 	{
730 		if(replace)
731 		{
732 			nprintf(("Warning", "More than one version of engine wash %s exists; using newer version.", ewt.name));
733 		}
734 		else
735 		{
736 			error_display(1, "Error:  Engine wash %s already exists.  All engine wash names must be unique.", ewt.name);
737 		}
738 	}
739 	else
740 	{
741 		//Don't create engine wash if it has +nocreate and is in a modular table.
742 		if (!create_if_not_found && replace)
743 		{
744 			if (!skip_to_start_of_string_either("$Name:", "#End")) {
745 				error_display(1, "Missing [#End] or [$Name] after engine wash %s", ewt.name);
746 			}
747 			return;
748 		}
749 		Engine_wash_info.push_back(ewt);
750 		ewp = &Engine_wash_info[Num_engine_wash_types++];
751 	}
752 
753 
754 	// half angle of cone of wash from thruster
755 	if(optional_string("$Angle:"))
756 	{
757 		stuff_float(&ewp->angle);
758 		ewp->angle *= (PI / 180.0f);
759 	}
760 
761 	// radius multiplier for hemisphere around thruster pt
762 	if(optional_string("$Radius Mult:")) {
763 		stuff_float(&ewp->radius_mult);
764 	}
765 
766 	// length of cone
767 	if(optional_string("$Length:")) {
768 		stuff_float(&ewp->length);
769 	}
770 
771 	// intensity inside hemisphere (or at 0 distance from frustated cone)
772 	if(optional_string("$Intensity:")) {
773 		stuff_float(&ewp->intensity);
774 	}
775 }
776 
parse_wing_formation(bool replace)777 static void parse_wing_formation(bool replace)
778 {
779 	char name[NAME_LENGTH];
780 	SCP_vector<vec3d> position_list;
781 
782 	required_string("$Name:");
783 	stuff_string(name, F_NAME, NAME_LENGTH);
784 
785 	stuff_vec3d_list(position_list);
786 	if (position_list.size() != MAX_SHIPS_PER_WING - 1)
787 	{
788 		error_display(0, "Wing formation %s did not have " SIZE_T_ARG " positions.  Ignoring.", name, (size_t)(MAX_SHIPS_PER_WING - 1));
789 		return;
790 	}
791 
792 	if (!stricmp(name, "Retail") || !stricmp(name, "Default"))
793 	{
794 		error_display(0, "A wing formation cannot be named \"Retail\" or \"Default\".  Overriding the default formation is not currently supported.");
795 		return;
796 	}
797 
798 	int idx = wing_formation_lookup(name);
799 	if (idx < 0)
800 	{
801 		idx = (int)Wing_formations.size();
802 		Wing_formations.emplace_back();
803 	}
804 	else if (!replace)
805 		error_display(0, "Formation %s already exists.  All formation names must be unique.", name);
806 
807 	strcpy_s(Wing_formations[idx].name, name);
808 	std::copy_n(position_list.begin(), MAX_SHIPS_PER_WING - 1, Wing_formations[idx].positions.begin());
809 }
810 
811 static const char *Lightning_types[] = {
812 	"None",
813 	"Default",
814 };
815 
816 static int Num_lightning_types = sizeof(Lightning_types)/sizeof(char*);
817 
lightningtype_match(char * p)818 static int lightningtype_match(char *p)
819 {
820 	int i;
821 	for(i = 0; i < Num_lightning_types; i++)
822 	{
823 		if(!stricmp(Lightning_types[i], p))
824 			return i;
825 	}
826 
827 	return -1;
828 }
829 
830 // Kazan -- Volition had this set to 1500, Set it to 4K for WC Saga
831 #define SHIP_MULTITEXT_LENGTH 4096
832 #define DEFAULT_DELTA_BANK_CONST	0.5f
833 
834 const float DEFAULT_ASK_HELP_SHIELD_PERCENT = 0.1f; // percent shields at which ship will ask for help
835 const float DEFAULT_ASK_HELP_HULL_PERCENT = 0.3f;   // percent hull at which ship will ask for help
836 const float AWACS_HELP_HULL_HI = 0.75f;     // percent hull at which ship will ask for help
837 const float AWACS_HELP_HULL_LOW = 0.25f;    // percent hull at which ship will ask for help
838 
839 #define CHECK_THEN_COPY(attribute) \
840 do {\
841 	if (other.attribute != NULL)\
842 		attribute = vm_strdup( other.attribute );\
843 } while(false)
844 
clone(const ship_info & other)845 void ship_info::clone(const ship_info& other)
846 {
847 	strcpy_s(name, other.name);
848 	strcpy_s(display_name, other.display_name);
849 	strcpy_s(short_name, other.short_name);
850 	species = other.species;
851 	class_type = other.class_type;
852 
853 	CHECK_THEN_COPY(type_str);
854 	CHECK_THEN_COPY(maneuverability_str);
855 	CHECK_THEN_COPY(armor_str);
856 	CHECK_THEN_COPY(manufacturer_str);
857 	CHECK_THEN_COPY(desc);
858 	CHECK_THEN_COPY(tech_desc);
859 
860 	strcpy_s(tech_title, other.tech_title);
861 
862 	CHECK_THEN_COPY(ship_length);
863 	CHECK_THEN_COPY(gun_mounts);
864 	CHECK_THEN_COPY(missile_banks);
865 
866 	strcpy_s(cockpit_pof_file, other.cockpit_pof_file);
867 	cockpit_offset = other.cockpit_offset;
868 	strcpy_s(pof_file, other.pof_file);
869 	strcpy_s(pof_file_hud, other.pof_file_hud);
870 	strcpy_s(pof_file_tech, other.pof_file_tech);
871 	num_detail_levels = other.num_detail_levels;
872 	memcpy(detail_distance, other.detail_distance, sizeof(int) * MAX_SHIP_DETAIL_LEVELS);
873 	collision_lod = other.collision_lod;
874 
875 	// I'm not sure if these three are A) a good idea or B) relevant at all. -MageKing17
876 	cockpit_model_num = other.cockpit_model_num;
877 	model_num = other.model_num;
878 	model_num_hud = other.model_num_hud;
879 
880 	hud_target_lod = other.hud_target_lod;
881 	density = other.density;
882 	damp = other.damp;
883 	rotdamp = other.rotdamp;
884 	delta_bank_const = other.delta_bank_const;
885 	max_vel = other.max_vel;
886 	min_vel = other.min_vel;
887 	max_rotvel = other.max_rotvel;
888 	rotation_time = other.rotation_time;
889 	srotation_time = other.srotation_time;
890 	max_rear_vel = other.max_rear_vel;
891 	forward_accel = other.forward_accel;
892 	forward_decel = other.forward_decel;
893 	slide_accel = other.slide_accel;
894 	slide_decel = other.slide_decel;
895 
896 	warpin_params_index = other.warpin_params_index;
897 	warpout_params_index = other.warpout_params_index;
898 
899 	flags = other.flags;
900 	ai_class = other.ai_class;
901 	max_speed = other.max_speed;
902 	min_speed = other.min_speed;
903 	max_accel = other.max_accel;
904 
905 	collision_damage_type_idx = other.collision_damage_type_idx;
906 	collision_physics = other.collision_physics;
907 
908 	memcpy(&shockwave, &other.shockwave, sizeof(shockwave_create_info));
909 	explosion_propagates = other.explosion_propagates;
910 	explosion_splits_ship = other.explosion_splits_ship;
911 	big_exp_visual_rad = other.big_exp_visual_rad;
912 	prop_exp_rad_mult = other.prop_exp_rad_mult;
913 	death_roll_r_mult = other.death_roll_r_mult;
914 	death_fx_r_mult = other.death_fx_r_mult;
915 	death_roll_time_mult = other.death_roll_time_mult;
916 	death_roll_rotation_mult = other.death_roll_rotation_mult;
917 	death_roll_xrotation_cap = other.death_roll_xrotation_cap;
918 	death_roll_yrotation_cap = other.death_roll_yrotation_cap;
919 	death_roll_zrotation_cap = other.death_roll_zrotation_cap;
920 	death_roll_base_time = other.death_roll_base_time;
921 	death_fx_count = other.death_fx_count;
922 	shockwave_count = other.shockwave_count;
923 	explosion_bitmap_anims = other.explosion_bitmap_anims;
924 	vaporize_chance = other.vaporize_chance;
925 
926 	impact_spew = other.impact_spew;
927 	damage_spew = other.damage_spew;
928 	split_particles = other.split_particles;
929 	knossos_end_particles = other.knossos_end_particles;
930 	regular_end_particles = other.regular_end_particles;
931 
932 	death_effect = other.death_effect;
933 
934 	debris_min_lifetime = other.debris_min_lifetime;
935 	debris_max_lifetime = other.debris_max_lifetime;
936 	debris_min_speed = other.debris_min_speed;
937 	debris_max_speed = other.debris_max_speed;
938 	debris_min_rotspeed = other.debris_min_rotspeed;
939 	debris_max_rotspeed = other.debris_max_rotspeed;
940 	debris_damage_type_idx = other.debris_damage_type_idx;
941 	debris_min_hitpoints = other.debris_min_hitpoints;
942 	debris_max_hitpoints = other.debris_max_hitpoints;
943 	debris_damage_mult = other.debris_damage_mult;
944 	debris_arc_percent = other.debris_arc_percent;
945 	debris_ambient_sound = other.debris_ambient_sound;
946 	debris_collision_sound_light = other.debris_collision_sound_light;
947 	debris_collision_sound_heavy = other.debris_collision_sound_heavy;
948 	debris_explosion_sound = other.debris_explosion_sound;
949 	strcpy_s(generic_debris_pof_file, other.generic_debris_pof_file);
950 	generic_debris_model_num = other.generic_debris_model_num;
951 	generic_debris_num_submodels = other.generic_debris_num_submodels;
952 	generic_debris_spew_num = other.generic_debris_spew_num;
953 
954 	if ( other.n_subsystems > 0 ) {
955 		if( n_subsystems < 1 ) {
956 			subsystems = new model_subsystem[other.n_subsystems];
957 		} else {
958 			for ( int i = 0; i < n_subsystems; i++ ) {
959 				for ( int j = 0; j < subsystems[i].n_triggers; j++ ) {
960 					if ( i < other.n_subsystems ) {
961 						subsystems[i].triggers = (queued_animation*)vm_realloc(subsystems[i].triggers, sizeof(queued_animation) * (other.subsystems[i].n_triggers));
962 					} else {
963 						vm_free(subsystems[i].triggers);
964 					}
965 				}
966 			}
967 			delete[] subsystems;
968 			subsystems = new model_subsystem[other.n_subsystems];
969 		}
970 
971 		Assert(subsystems != nullptr);
972 		for ( int i = n_subsystems; i < other.n_subsystems; i++ ) {
973 			subsystems[i].triggers = (queued_animation*) vm_malloc(sizeof(queued_animation) * (other.subsystems[i].n_triggers));
974 		}
975 
976 		for ( int i = 0; i < other.n_subsystems; i++ ) {
977 			queued_animation* triggers = subsystems[i].triggers;
978 			subsystems[i] = other.subsystems[i];
979 			subsystems[i].triggers = triggers;
980 			memcpy(subsystems[i].triggers, other.subsystems[i].triggers, sizeof(queued_animation) * (subsystems[i].n_triggers));
981 		}
982 	}
983 	n_subsystems = other.n_subsystems;
984 
985 	animations = other.animations;
986 
987 	power_output = other.power_output;
988 	max_overclocked_speed = other.max_overclocked_speed;
989 	max_weapon_reserve = other.max_weapon_reserve;
990 	max_shield_regen_per_second = other.max_shield_regen_per_second;
991 	shield_regen_hit_delay = other.shield_regen_hit_delay;
992 	max_weapon_regen_per_second = other.max_weapon_regen_per_second;
993 
994 	shield_weap_amount = other.shield_weap_amount;
995 	shield_weap_efficiency = other.shield_weap_efficiency;
996 	shield_weap_speed = other.shield_weap_speed;
997 	weap_shield_amount = other.weap_shield_amount;
998 	weap_shield_efficiency = other.weap_shield_efficiency;
999 	weap_shield_speed = other.weap_shield_speed;
1000 
1001 	afterburner_max_vel = other.afterburner_max_vel;
1002 	afterburner_forward_accel = other.afterburner_forward_accel;
1003 	afterburner_fuel_capacity = other.afterburner_fuel_capacity;
1004 	afterburner_burn_rate = other.afterburner_burn_rate;
1005 	afterburner_recover_rate = other.afterburner_recover_rate;
1006 	afterburner_max_reverse_vel = other.afterburner_max_reverse_vel;
1007 	afterburner_reverse_accel = other.afterburner_reverse_accel;
1008 	afterburner_min_start_fuel = other.afterburner_min_start_fuel;
1009 	afterburner_min_fuel_to_burn = other.afterburner_min_fuel_to_burn;
1010 	afterburner_cooldown_time = other.afterburner_cooldown_time;
1011 
1012 	cmeasure_type = other.cmeasure_type;
1013 	cmeasure_max = other.cmeasure_max;
1014 
1015 	num_primary_banks = other.num_primary_banks;
1016 	memcpy(primary_bank_weapons, other.primary_bank_weapons, sizeof(int) * MAX_SHIP_PRIMARY_BANKS);
1017 	memcpy(primary_bank_ammo_capacity, other.primary_bank_ammo_capacity, sizeof(int) * MAX_SHIP_PRIMARY_BANKS);
1018 
1019 	num_secondary_banks = other.num_secondary_banks;
1020 	memcpy(secondary_bank_weapons, other.secondary_bank_weapons, sizeof(int) * MAX_SHIP_SECONDARY_BANKS);
1021 	memcpy(secondary_bank_ammo_capacity, other.secondary_bank_ammo_capacity, sizeof(int) * MAX_SHIP_SECONDARY_BANKS);
1022 
1023 	memcpy(draw_primary_models, other.draw_primary_models, sizeof(bool) * MAX_SHIP_PRIMARY_BANKS);
1024 	memcpy(draw_secondary_models, other.draw_secondary_models, sizeof(bool) * MAX_SHIP_SECONDARY_BANKS);
1025 	weapon_model_draw_distance = other.weapon_model_draw_distance;
1026 
1027 	max_hull_strength = other.max_hull_strength;
1028 	ship_recoil_modifier = other.ship_recoil_modifier;
1029 	max_shield_strength = other.max_shield_strength;
1030 	max_shield_recharge = other.max_shield_recharge;
1031 	auto_shield_spread = other.auto_shield_spread;
1032 	auto_shield_spread_bypass = other.auto_shield_spread_bypass;
1033 	auto_shield_spread_from_lod = other.auto_shield_spread_from_lod;
1034 	auto_shield_spread_min_span = other.auto_shield_spread_min_span;
1035 
1036 	// ...Hmm. A memcpy() seems slightly overkill here, but I've settled into the pattern of "array gets memcpy'd", so... -MageKing17
1037 	memcpy(shield_point_augment_ctrls, other.shield_point_augment_ctrls, sizeof(int) * 4);
1038 
1039 	hull_repair_rate = other.hull_repair_rate;
1040 	subsys_repair_rate = other.subsys_repair_rate;
1041 
1042 	sup_hull_repair_rate = other.sup_hull_repair_rate;
1043 	sup_shield_repair_rate = other.sup_shield_repair_rate;
1044 	sup_subsys_repair_rate = other.sup_subsys_repair_rate;
1045 
1046 	closeup_pos = other.closeup_pos;
1047 	closeup_zoom = other.closeup_zoom;
1048 
1049 	closeup_pos_targetbox = other.closeup_pos_targetbox;
1050 	closeup_zoom_targetbox = other.closeup_zoom_targetbox;
1051 
1052 	chase_view_offset = other.chase_view_offset;
1053 	chase_view_rigidity = other.chase_view_rigidity;
1054 
1055 	allowed_weapons = other.allowed_weapons;
1056 
1057 	restricted_loadout_flag = other.restricted_loadout_flag;
1058 	allowed_bank_restricted_weapons = other.allowed_bank_restricted_weapons;
1059 
1060 	shield_icon_index = other.shield_icon_index;
1061 	strcpy_s(icon_filename, other.icon_filename);
1062 	model_icon_angles = other.model_icon_angles;
1063 	strcpy_s(anim_filename, other.anim_filename);
1064 	strcpy_s(overhead_filename, other.overhead_filename);
1065 	selection_effect = other.selection_effect;
1066 
1067 	bii_index_ship = other.bii_index_ship;
1068 	bii_index_ship_with_cargo = other.bii_index_ship_with_cargo;
1069 	bii_index_wing = other.bii_index_wing;
1070 	bii_index_wing_with_cargo = other.bii_index_wing_with_cargo;
1071 
1072 	score = other.score;
1073 
1074 	scan_time = other.scan_time;
1075 	scan_range_normal = other.scan_range_normal;
1076 	scan_range_capital = other.scan_range_capital;
1077 
1078 	ask_help_shield_percent = other.ask_help_shield_percent;
1079 	ask_help_hull_percent = other.ask_help_hull_percent;
1080 
1081 	memcpy(ct_info, other.ct_info, sizeof(trail_info) * MAX_SHIP_CONTRAILS);
1082 	ct_count = other.ct_count;
1083 
1084 	memcpy(shield_color, other.shield_color, sizeof(ubyte) * 3);
1085 
1086 	uses_team_colors = other.uses_team_colors;
1087 	default_team_name = other.default_team_name;
1088 
1089 	afterburner_trail = other.afterburner_trail;
1090 	afterburner_trail_tex_stretch = other.afterburner_trail_tex_stretch;
1091 	afterburner_trail_width_factor = other.afterburner_trail_width_factor;
1092 	afterburner_trail_alpha_factor = other.afterburner_trail_alpha_factor;
1093 	afterburner_trail_alpha_end_factor = other.afterburner_trail_alpha_end_factor;
1094 	afterburner_trail_alpha_decay_exponent = other.afterburner_trail_alpha_decay_exponent;
1095 	afterburner_trail_life = other.afterburner_trail_life;
1096 	afterburner_trail_faded_out_sections = other.afterburner_trail_faded_out_sections;
1097 	afterburner_trail_spread = other.afterburner_trail_spread;
1098 
1099 	normal_thruster_particles = other.normal_thruster_particles;
1100 	afterburner_thruster_particles = other.afterburner_thruster_particles;
1101 
1102 	memcpy(&thruster_flame_info, &other.thruster_flame_info, sizeof(thrust_pair));
1103 	memcpy(&thruster_glow_info, &other.thruster_glow_info, sizeof(thrust_pair));
1104 	memcpy(&thruster_secondary_glow_info, &other.thruster_secondary_glow_info, sizeof(thrust_pair_bitmap));
1105 	memcpy(&thruster_tertiary_glow_info, &other.thruster_tertiary_glow_info, sizeof(thrust_pair_bitmap));
1106 	memcpy(&thruster_distortion_info, &other.thruster_distortion_info, sizeof(thrust_pair_bitmap));
1107 
1108 	thruster01_glow_rad_factor = other.thruster01_glow_rad_factor;
1109 	thruster02_glow_rad_factor = other.thruster02_glow_rad_factor;
1110 	thruster03_glow_rad_factor = other.thruster03_glow_rad_factor;
1111 	thruster02_glow_len_factor = other.thruster02_glow_len_factor;
1112 	thruster_dist_rad_factor = other.thruster_dist_rad_factor;
1113 	thruster_dist_len_factor = other.thruster_dist_len_factor;
1114 	thruster_glow_noise_mult = other.thruster_glow_noise_mult;
1115 
1116 	draw_distortion = other.draw_distortion;
1117 
1118 	splodeing_texture = other.splodeing_texture;
1119 	strcpy_s(splodeing_texture_name, other.splodeing_texture_name);
1120 
1121 	replacement_textures = other.replacement_textures;
1122 
1123 	armor_type_idx = other.armor_type_idx;
1124 	shield_armor_type_idx = other.shield_armor_type_idx;
1125 
1126 	can_glide = other.can_glide;
1127 	glide_cap = other.glide_cap;
1128 	glide_dynamic_cap = other.glide_dynamic_cap;
1129 	glide_accel_mult = other.glide_accel_mult;
1130 	use_newtonian_damp = other.use_newtonian_damp;
1131 	newtonian_damp_override = other.newtonian_damp_override;
1132 
1133 	autoaim_fov = other.autoaim_fov;
1134 	autoaim_lock_snd = other.autoaim_lock_snd;
1135 	autoaim_lost_snd = other.autoaim_lost_snd;
1136 
1137 	topdown_offset_def = other.topdown_offset_def;
1138 	topdown_offset = other.topdown_offset;
1139 
1140 	engine_snd = other.engine_snd;
1141 	min_engine_vol = other.min_engine_vol;
1142 	glide_start_snd = other.glide_start_snd;
1143 	glide_end_snd = other.glide_end_snd;
1144 	flyby_snd = other.flyby_snd;
1145 
1146 	ship_sounds = other.ship_sounds;
1147 
1148 	num_maneuvering = other.num_maneuvering;
1149 
1150     for (int i = 0; i < MAX_MAN_THRUSTERS; ++i)
1151     {
1152         maneuvering[i] = other.maneuvering[i];
1153     }
1154 
1155 	radar_image_2d_idx = other.radar_image_2d_idx;
1156 	radar_color_image_2d_idx = other.radar_color_image_2d_idx;
1157 	radar_image_size = other.radar_image_size;
1158 	radar_projection_size_mult = other.radar_projection_size_mult;
1159 
1160 	memcpy(ship_iff_info, other.ship_iff_info, sizeof(int) * MAX_IFFS * MAX_IFFS);
1161 
1162 	aiming_flags = other.aiming_flags;
1163 	minimum_convergence_distance = other.minimum_convergence_distance;
1164 	convergence_distance = other.convergence_distance;
1165 	convergence_offset = other.convergence_offset;
1166 
1167 	emp_resistance_mod = other.emp_resistance_mod;
1168 
1169 	piercing_damage_draw_limit = other.piercing_damage_draw_limit;
1170 
1171 	damage_lightning_type = other.damage_lightning_type;
1172 
1173 	shield_impact_explosion_anim = other.shield_impact_explosion_anim;
1174 
1175 	// We can't copy the HUD gauge vector here since we can't construct a copy of every HUD gauge since the type
1176 	// information is not available anymore when we are at this point. Since this function is only needed before HUD
1177 	// gauge parsing it should be save to assume that the other HUD gauge vector is empty
1178 	Assertion(other.hud_gauges.empty(), "Ship_info cloning is only possible if there are no HUD gauges in the ship class.");
1179 	hud_gauges.clear();
1180 
1181 	hud_enabled = other.hud_enabled;
1182 	hud_retail = other.hud_retail;
1183 
1184 	displays = other.displays;
1185 
1186 	pathMetadata = other.pathMetadata;
1187 
1188 	glowpoint_bank_override_map = other.glowpoint_bank_override_map;
1189 }
1190 
move(ship_info && other)1191 void ship_info::move(ship_info&& other)
1192 {
1193 	std::swap(name, other.name);
1194 	std::swap(display_name, other.display_name);
1195 	std::swap(short_name, other.short_name);
1196 	species = other.species;
1197 	class_type = other.class_type;
1198 
1199 	std::swap(type_str, other.type_str);
1200 	std::swap(maneuverability_str, other.maneuverability_str);
1201 	std::swap(armor_str, other.armor_str);
1202 	std::swap(manufacturer_str, other.manufacturer_str);
1203 	std::swap(desc, other.desc);
1204 	std::swap(tech_desc, other.tech_desc);
1205 
1206 	std::swap(tech_title, other.tech_title);
1207 
1208 	std::swap(ship_length, other.ship_length);
1209 	std::swap(gun_mounts, other.gun_mounts);
1210 	std::swap(missile_banks, other.missile_banks);
1211 
1212 	std::swap(cockpit_pof_file, other.cockpit_pof_file);
1213 	std::swap(cockpit_offset, other.cockpit_offset);
1214 	std::swap(pof_file, other.pof_file);
1215 	std::swap(pof_file_hud, other.pof_file_hud);
1216 	std::swap(pof_file_tech, other.pof_file_tech);
1217 	num_detail_levels = other.num_detail_levels;
1218 	std::swap(detail_distance, other.detail_distance);
1219 	collision_lod = other.collision_lod;
1220 
1221 	cockpit_model_num = other.cockpit_model_num;
1222 	model_num = other.model_num;
1223 	model_num_hud = other.model_num_hud;
1224 
1225 	hud_target_lod = other.hud_target_lod;
1226 	density = other.density;
1227 	damp = other.damp;
1228 	rotdamp = other.rotdamp;
1229 	delta_bank_const = other.delta_bank_const;
1230 	std::swap(max_vel, other.max_vel);
1231 	std::swap(min_vel, other.min_vel);
1232 	std::swap(max_rotvel, other.max_rotvel);
1233 	std::swap(rotation_time, other.rotation_time);
1234 	srotation_time = other.srotation_time;
1235 	max_rear_vel = other.max_rear_vel;
1236 	forward_accel = other.forward_accel;
1237 	forward_decel = other.forward_decel;
1238 	slide_accel = other.slide_accel;
1239 	slide_decel = other.slide_decel;
1240 
1241 	warpin_params_index = other.warpin_params_index;
1242 	warpout_params_index = other.warpout_params_index;
1243 
1244 	flags = other.flags;
1245 	ai_class = other.ai_class;
1246 	max_speed = other.max_speed;
1247 	min_speed = other.min_speed;
1248 	max_accel = other.max_accel;
1249 
1250 	collision_damage_type_idx = other.collision_damage_type_idx;
1251 	std::swap(collision_physics, other.collision_physics);
1252 
1253 	std::swap(shockwave, other.shockwave);
1254 	explosion_propagates = other.explosion_propagates;
1255 	explosion_splits_ship = other.explosion_splits_ship;
1256 	big_exp_visual_rad = other.big_exp_visual_rad;
1257 	prop_exp_rad_mult = other.prop_exp_rad_mult;
1258 	death_roll_r_mult = other.death_roll_r_mult;
1259 	death_fx_r_mult = other.death_fx_r_mult;
1260 	death_roll_time_mult = other.death_roll_time_mult;
1261 	death_roll_rotation_mult = other.death_roll_rotation_mult;
1262 	death_roll_xrotation_cap = other.death_roll_xrotation_cap;
1263 	death_roll_yrotation_cap = other.death_roll_yrotation_cap;
1264 	death_roll_zrotation_cap = other.death_roll_zrotation_cap;
1265 	death_roll_base_time = other.death_roll_base_time;
1266 	death_fx_count = other.death_fx_count;
1267 	shockwave_count = other.shockwave_count;
1268 	std::swap(explosion_bitmap_anims, other.explosion_bitmap_anims);
1269 	vaporize_chance = other.vaporize_chance;
1270 
1271 	std::swap(impact_spew, other.impact_spew);
1272 	std::swap(damage_spew, other.damage_spew);
1273 	std::swap(split_particles, other.split_particles);
1274 	std::swap(knossos_end_particles, other.knossos_end_particles);
1275 	std::swap(regular_end_particles, other.regular_end_particles);
1276 
1277 	std::swap(death_effect, other.death_effect);
1278 
1279 	debris_min_lifetime = other.debris_min_lifetime;
1280 	debris_max_lifetime = other.debris_max_lifetime;
1281 	debris_min_speed = other.debris_min_speed;
1282 	debris_max_speed = other.debris_max_speed;
1283 	debris_min_rotspeed = other.debris_min_rotspeed;
1284 	debris_max_rotspeed = other.debris_max_rotspeed;
1285 	debris_damage_type_idx = other.debris_damage_type_idx;
1286 	debris_min_hitpoints = other.debris_min_hitpoints;
1287 	debris_max_hitpoints = other.debris_max_hitpoints;
1288 	debris_damage_mult = other.debris_damage_mult;
1289 	debris_arc_percent = other.debris_arc_percent;
1290 	debris_ambient_sound = other.debris_ambient_sound;
1291 	debris_collision_sound_light = other.debris_collision_sound_light;
1292 	debris_collision_sound_heavy = other.debris_collision_sound_heavy;
1293 	debris_explosion_sound = other.debris_explosion_sound;
1294 	strcpy_s(generic_debris_pof_file, other.generic_debris_pof_file);
1295 	generic_debris_model_num = other.generic_debris_model_num;
1296 	generic_debris_num_submodels = other.generic_debris_num_submodels;
1297 	generic_debris_spew_num = other.generic_debris_spew_num;
1298 
1299 	std::swap(subsystems, other.subsystems);
1300 	std::swap(n_subsystems, other.n_subsystems);
1301 
1302 	power_output = other.power_output;
1303 	max_overclocked_speed = other.max_overclocked_speed;
1304 	max_weapon_reserve = other.max_weapon_reserve;
1305 	max_shield_regen_per_second = other.max_shield_regen_per_second;
1306 	shield_regen_hit_delay = other.shield_regen_hit_delay;
1307 	max_weapon_regen_per_second = other.max_weapon_regen_per_second;
1308 
1309 	shield_weap_amount = other.shield_weap_amount;
1310 	shield_weap_efficiency = other.shield_weap_efficiency;
1311 	shield_weap_speed = other.shield_weap_speed;
1312 	weap_shield_amount = other.weap_shield_amount;
1313 	weap_shield_efficiency = other.weap_shield_efficiency;
1314 	weap_shield_speed = other.weap_shield_speed;
1315 
1316 	std::swap(afterburner_max_vel, other.afterburner_max_vel);
1317 	afterburner_forward_accel = other.afterburner_forward_accel;
1318 	afterburner_fuel_capacity = other.afterburner_fuel_capacity;
1319 	afterburner_burn_rate = other.afterburner_burn_rate;
1320 	afterburner_recover_rate = other.afterburner_recover_rate;
1321 	afterburner_max_reverse_vel = other.afterburner_max_reverse_vel;
1322 	afterburner_reverse_accel = other.afterburner_reverse_accel;
1323 	afterburner_min_start_fuel = other.afterburner_min_start_fuel;
1324 	afterburner_min_fuel_to_burn = other.afterburner_min_fuel_to_burn;
1325 	afterburner_cooldown_time = other.afterburner_cooldown_time;
1326 
1327 	cmeasure_type = other.cmeasure_type;
1328 	cmeasure_max = other.cmeasure_max;
1329 
1330 	num_primary_banks = other.num_primary_banks;
1331 	std::swap(primary_bank_weapons, other.primary_bank_weapons);
1332 	std::swap(primary_bank_ammo_capacity, other.primary_bank_ammo_capacity);
1333 
1334 	num_secondary_banks = other.num_secondary_banks;
1335 	std::swap(secondary_bank_weapons, other.secondary_bank_weapons);
1336 	std::swap(secondary_bank_ammo_capacity, other.secondary_bank_ammo_capacity);
1337 
1338 	std::swap(draw_primary_models, other.draw_primary_models);
1339 	std::swap(draw_secondary_models, other.draw_secondary_models);
1340 	weapon_model_draw_distance = other.weapon_model_draw_distance;
1341 
1342 	max_hull_strength = other.max_hull_strength;
1343 	ship_recoil_modifier = other.ship_recoil_modifier;
1344 	max_shield_strength = other.max_shield_strength;
1345 	max_shield_recharge = other.max_shield_recharge;
1346 	auto_shield_spread = other.auto_shield_spread;
1347 	auto_shield_spread_bypass = other.auto_shield_spread_bypass;
1348 	auto_shield_spread_from_lod = other.auto_shield_spread_from_lod;
1349 	auto_shield_spread_min_span = other.auto_shield_spread_min_span;
1350 
1351 	std::swap(shield_point_augment_ctrls, other.shield_point_augment_ctrls);
1352 
1353 	hull_repair_rate = other.hull_repair_rate;
1354 	subsys_repair_rate = other.subsys_repair_rate;
1355 
1356 	sup_hull_repair_rate = other.sup_hull_repair_rate;
1357 	sup_shield_repair_rate = other.sup_shield_repair_rate;
1358 	sup_subsys_repair_rate = other.sup_subsys_repair_rate;
1359 
1360 	std::swap(closeup_pos, other.closeup_pos);
1361 	closeup_zoom = other.closeup_zoom;
1362 
1363 	std::swap(closeup_pos_targetbox, other.closeup_pos_targetbox);
1364 	closeup_zoom_targetbox = other.closeup_zoom_targetbox;
1365 
1366 	std::swap(chase_view_offset, other.chase_view_offset);
1367 	chase_view_rigidity = other.chase_view_rigidity;
1368 
1369 	std::swap(allowed_weapons, other.allowed_weapons);
1370 
1371 	std::swap(restricted_loadout_flag, other.restricted_loadout_flag);
1372 	std::swap(allowed_bank_restricted_weapons, other.allowed_bank_restricted_weapons);
1373 
1374 	shield_icon_index = other.shield_icon_index;
1375 	std::swap(icon_filename, other.icon_filename);
1376 	model_icon_angles = other.model_icon_angles;
1377 	std::swap(anim_filename, other.anim_filename);
1378 	std::swap(overhead_filename, other.overhead_filename);
1379 	selection_effect = other.selection_effect;
1380 
1381 	bii_index_ship = other.bii_index_ship;
1382 	bii_index_ship_with_cargo = other.bii_index_ship_with_cargo;
1383 	bii_index_wing = other.bii_index_wing;
1384 	bii_index_wing_with_cargo = other.bii_index_wing_with_cargo;
1385 
1386 	score = other.score;
1387 
1388 	scan_time = other.scan_time;
1389 	scan_range_normal = other.scan_range_normal;
1390 	scan_range_capital = other.scan_range_capital;
1391 
1392 	ask_help_shield_percent = other.ask_help_shield_percent;
1393 	ask_help_hull_percent = other.ask_help_hull_percent;
1394 
1395 	std::swap(ct_info, other.ct_info);
1396 	ct_count = other.ct_count;
1397 
1398 	std::swap(shield_color, other.shield_color);
1399 
1400 	uses_team_colors = other.uses_team_colors;
1401 	std::swap(default_team_name, other.default_team_name);
1402 
1403 	std::swap(afterburner_trail, other.afterburner_trail);
1404 	afterburner_trail_tex_stretch = other.afterburner_trail_tex_stretch;
1405 	afterburner_trail_width_factor = other.afterburner_trail_width_factor;
1406 	afterburner_trail_alpha_factor = other.afterburner_trail_alpha_factor;
1407 	afterburner_trail_alpha_end_factor = other.afterburner_trail_alpha_end_factor;
1408 	afterburner_trail_alpha_decay_exponent = other.afterburner_trail_alpha_decay_exponent;
1409 	afterburner_trail_life = other.afterburner_trail_life;
1410 	afterburner_trail_faded_out_sections = other.afterburner_trail_faded_out_sections;
1411 	afterburner_trail_spread = other.afterburner_trail_spread;
1412 
1413 	std::swap(normal_thruster_particles, other.normal_thruster_particles);
1414 	std::swap(afterburner_thruster_particles, other.afterburner_thruster_particles);
1415 
1416 	std::swap(thruster_flame_info, other.thruster_flame_info);
1417 	std::swap(thruster_glow_info, other.thruster_glow_info);
1418 	std::swap(thruster_secondary_glow_info, other.thruster_secondary_glow_info);
1419 	std::swap(thruster_tertiary_glow_info, other.thruster_tertiary_glow_info);
1420 	std::swap(thruster_distortion_info, other.thruster_distortion_info);
1421 
1422 	thruster01_glow_rad_factor = other.thruster01_glow_rad_factor;
1423 	thruster02_glow_rad_factor = other.thruster02_glow_rad_factor;
1424 	thruster03_glow_rad_factor = other.thruster03_glow_rad_factor;
1425 	thruster02_glow_len_factor = other.thruster02_glow_len_factor;
1426 	thruster_dist_rad_factor = other.thruster_dist_rad_factor;
1427 	thruster_dist_len_factor = other.thruster_dist_len_factor;
1428 	thruster_glow_noise_mult = other.thruster_glow_noise_mult;
1429 
1430 	draw_distortion = other.draw_distortion;
1431 
1432 	splodeing_texture = other.splodeing_texture;
1433 	std::swap(splodeing_texture_name, other.splodeing_texture_name);
1434 
1435 	std::swap(replacement_textures, other.replacement_textures);
1436 
1437 	armor_type_idx = other.armor_type_idx;
1438 	shield_armor_type_idx = other.shield_armor_type_idx;
1439 
1440 	can_glide = other.can_glide;
1441 	glide_cap = other.glide_cap;
1442 	glide_dynamic_cap = other.glide_dynamic_cap;
1443 	glide_accel_mult = other.glide_accel_mult;
1444 	use_newtonian_damp = other.use_newtonian_damp;
1445 	newtonian_damp_override = other.newtonian_damp_override;
1446 
1447 	autoaim_fov = other.autoaim_fov;
1448 	autoaim_lock_snd = other.autoaim_lock_snd;
1449 	autoaim_lost_snd = other.autoaim_lost_snd;
1450 
1451 	topdown_offset_def = other.topdown_offset_def;
1452 	std::swap(topdown_offset, other.topdown_offset);
1453 
1454 	engine_snd = other.engine_snd;
1455 	min_engine_vol = other.min_engine_vol;
1456 	glide_start_snd = other.glide_start_snd;
1457 	glide_end_snd = other.glide_end_snd;
1458 	flyby_snd  = other.flyby_snd;
1459 
1460 	std::swap(ship_sounds, other.ship_sounds);
1461 
1462 	num_maneuvering = other.num_maneuvering;
1463 	std::swap(maneuvering, other.maneuvering);
1464 
1465 	radar_image_2d_idx = other.radar_image_2d_idx;
1466 	radar_color_image_2d_idx = other.radar_color_image_2d_idx;
1467 	radar_image_size = other.radar_image_size;
1468 	radar_projection_size_mult = other.radar_projection_size_mult;
1469 
1470 	memcpy(ship_iff_info, other.ship_iff_info, sizeof(int) * MAX_IFFS * MAX_IFFS);
1471 
1472 	aiming_flags = other.aiming_flags;
1473 	minimum_convergence_distance = other.minimum_convergence_distance;
1474 	convergence_distance = other.convergence_distance;
1475 	std::swap(convergence_offset, other.convergence_offset);
1476 
1477 	emp_resistance_mod = other.emp_resistance_mod;
1478 
1479 	piercing_damage_draw_limit = other.piercing_damage_draw_limit;
1480 
1481 	damage_lightning_type = other.damage_lightning_type;
1482 
1483 	shield_impact_explosion_anim = other.shield_impact_explosion_anim;
1484 	std::swap(hud_gauges, other.hud_gauges);
1485 	hud_enabled = other.hud_enabled;
1486 	hud_retail = other.hud_retail;
1487 
1488 	std::swap(displays, other.displays);
1489 
1490 	std::swap(pathMetadata, other.pathMetadata);
1491 
1492 	std::swap(glowpoint_bank_override_map, other.glowpoint_bank_override_map);
1493 
1494 	std::swap(animations, other.animations);
1495 }
1496 
1497 #define CHECK_THEN_FREE(attribute) \
1498 do {\
1499 	if (attribute != NULL) {\
1500 		vm_free(attribute);\
1501 		attribute = NULL;\
1502 	}\
1503 } while(false)
1504 
free_strings()1505 void ship_info::free_strings()
1506 {
1507 	CHECK_THEN_FREE(type_str);
1508 	CHECK_THEN_FREE(maneuverability_str);
1509 	CHECK_THEN_FREE(armor_str);
1510 	CHECK_THEN_FREE(manufacturer_str);
1511 	CHECK_THEN_FREE(desc);
1512 	CHECK_THEN_FREE(tech_desc);
1513 	CHECK_THEN_FREE(ship_length);
1514 	CHECK_THEN_FREE(gun_mounts);
1515 	CHECK_THEN_FREE(missile_banks);
1516 }
1517 
operator =(ship_info && other)1518 ship_info &ship_info::operator= (ship_info&& other) noexcept
1519 {
1520 	if (this != &other) {
1521 		move(std::move(other));
1522 	}
1523 	return *this;
1524 }
1525 
ship_info(ship_info && other)1526 ship_info::ship_info(ship_info&& other) noexcept
1527 {
1528 	// MageKing17 - Initialize these pointers to NULL because otherwise move() will leave them uninitialized.
1529 	type_str = NULL;
1530 	maneuverability_str = NULL;
1531 	armor_str = NULL;
1532 	manufacturer_str = NULL;
1533 	desc = NULL;
1534 	tech_desc = NULL;
1535 	ship_length = NULL;
1536 	gun_mounts = NULL;
1537 	missile_banks = NULL;
1538 	subsystems = NULL;
1539 	n_subsystems = 0;
1540 
1541 	// Then we swap everything (well, everything that matters to the deconstructor).
1542 	move(std::move(other));
1543 }
1544 
ship_info()1545 ship_info::ship_info()
1546 {
1547 	int i,j;
1548 
1549 	name[0] = '\0';
1550 	display_name[0] = '\0';
1551 	sprintf(short_name, "ShipClass%d", ship_info_size());
1552 	species = 0;
1553 	class_type = -1;
1554 
1555 	type_str = maneuverability_str = armor_str = manufacturer_str = NULL;
1556 	desc = tech_desc = NULL;
1557 	tech_title[0] = 0;
1558 
1559 	ship_length = NULL;
1560 	gun_mounts = NULL;
1561 	missile_banks = NULL;
1562 
1563 	cockpit_pof_file[0] = '\0';
1564 	vm_vec_zero(&cockpit_offset);
1565 	pof_file[0] = '\0';
1566 	pof_file_hud[0] = '\0';
1567 	pof_file_tech[0] = '\0';
1568 	num_detail_levels = 1;
1569 	detail_distance[0] = 0;
1570 	collision_lod = -1;
1571 	cockpit_model_num = -1;
1572 	model_num = -1;
1573 	model_num_hud = -1;
1574 	hud_target_lod = -1;
1575 
1576 	density = 1.0f;
1577 	damp = 0.0f;
1578 	rotdamp = 0.0f;
1579 	delta_bank_const = DEFAULT_DELTA_BANK_CONST;
1580 	vm_vec_zero(&max_vel);
1581 	vm_vec_zero(&min_vel);
1582 	vm_vec_zero(&max_rotvel);
1583 	vm_vec_zero(&rotation_time);
1584 	srotation_time = 0.0f;
1585 	max_rear_vel = 0.0f;
1586 	forward_accel = 0.0f;
1587 	forward_decel = 0.0f;
1588 	slide_accel = 0.0f;
1589 	slide_decel = 0.0f;
1590 
1591 	warpin_params_index = -1;
1592 	warpout_params_index = -1;
1593 
1594 	flags.reset();
1595 	ai_class = 0;
1596 	max_speed = 0.0f;
1597 	min_speed = 0.0f;
1598 	max_accel = 0.0f;
1599 
1600 	collision_damage_type_idx = -1;
1601 	// Retail default collision physics and default landing parameters
1602 	collision_physics = ship_collision_physics();
1603 	collision_physics.both_small_bounce = 5.0;
1604 	collision_physics.bounce = 5.0;
1605 	collision_physics.friction = COLLISION_FRICTION_FACTOR;
1606 	collision_physics.rotation_factor = COLLISION_ROTATION_FACTOR;
1607 	collision_physics.reorient_mult = 1.0f;
1608 	collision_physics.landing_sound_idx = gamesnd_id();
1609 	collision_physics.collision_sound_light_idx = gamesnd_id();
1610 	collision_physics.collision_sound_heavy_idx = gamesnd_id();
1611 	collision_physics.collision_sound_shielded_idx = gamesnd_id();
1612 
1613 	shockwave_create_info_init(&shockwave);
1614 	explosion_propagates = 0;
1615 	explosion_splits_ship = false;
1616 	big_exp_visual_rad = -1.0f;
1617 	prop_exp_rad_mult = 1.0f;
1618 	death_roll_r_mult = 1.0f;
1619 	death_fx_r_mult = 1.0f;
1620 	death_roll_time_mult = 1.0f;
1621 	death_roll_rotation_mult = 1.0f;
1622 	death_roll_xrotation_cap = 0.75f*DEATHROLL_ROTVEL_CAP;
1623 	death_roll_yrotation_cap = 0.75f*DEATHROLL_ROTVEL_CAP;
1624 	death_roll_zrotation_cap = 0.75f*DEATHROLL_ROTVEL_CAP;
1625 	death_roll_base_time = 3000;
1626 	death_fx_count = 6;
1627 	shockwave_count = 1;
1628 	explosion_bitmap_anims.clear();
1629 	vaporize_chance = 0;
1630 
1631 	// default values from shipfx.cpp
1632 	impact_spew.n_high = 30;
1633 	impact_spew.n_low = 25;
1634 	impact_spew.max_rad = 0.5f;
1635 	impact_spew.min_rad = 0.2f;
1636 	impact_spew.max_life = 0.55f;
1637 	impact_spew.min_life = 0.05f;
1638 	impact_spew.max_vel = 12.0f;
1639 	impact_spew.min_vel = 2.0f;
1640 	impact_spew.variance = 1.0f;
1641 
1642 	// default values from shipfx.cpp
1643 	damage_spew.n_high = 1;						// 1 is used here to trigger retail behaviour
1644 	damage_spew.n_low = 0;
1645 	damage_spew.max_rad = 1.3f;
1646 	damage_spew.min_rad = 0.7f;
1647 	damage_spew.max_life = 0.0f;
1648 	damage_spew.min_life = 0.0f;
1649 	damage_spew.max_vel = 12.0f;
1650 	damage_spew.min_vel = 3.0f;
1651 	damage_spew.variance = 0.0f;
1652 
1653 	split_particles.n_high = 80;
1654 	split_particles.n_low = 40;
1655 	split_particles.max_rad = 0.0f;
1656 	split_particles.min_rad = 0.0f;
1657 	split_particles.max_life = 0.0f;
1658 	split_particles.min_life = 0.0f;
1659 	split_particles.max_vel = 0.0f;
1660 	split_particles.min_vel = 0.0f;
1661 	split_particles.variance = 2.0f;
1662 
1663 	knossos_end_particles.n_high = 30;
1664 	knossos_end_particles.n_low = 15;
1665 	knossos_end_particles.max_rad = 100.0f;
1666 	knossos_end_particles.min_rad = 30.0f;
1667 	knossos_end_particles.max_life = 12.0f;
1668 	knossos_end_particles.min_life = 2.0f;
1669 	knossos_end_particles.max_vel = 350.0f;
1670 	knossos_end_particles.min_vel = 50.0f;
1671 	knossos_end_particles.variance = 2.0f;
1672 
1673 	regular_end_particles.n_high = 100;
1674 	regular_end_particles.n_low = 50;
1675 	regular_end_particles.max_rad = 1.5f;
1676 	regular_end_particles.min_rad = 0.1f;
1677 	regular_end_particles.max_life = 4.0f;
1678 	regular_end_particles.min_life = 0.5f;
1679 	regular_end_particles.max_vel = 20.0f;
1680 	regular_end_particles.min_vel = 0.0f;
1681 	regular_end_particles.variance = 2.0f;
1682 
1683 	death_effect = particle::ParticleEffectHandle::invalid();
1684 
1685 	debris_min_lifetime = -1.0f;
1686 	debris_max_lifetime = -1.0f;
1687 	debris_min_speed = -1.0f;
1688 	debris_max_speed = -1.0f;
1689 	debris_min_rotspeed = -1.0f;
1690 	debris_max_rotspeed = -1.0f;
1691 	debris_damage_type_idx = -1;
1692 	debris_min_hitpoints = -1.0f;
1693 	debris_max_hitpoints = -1.0f;
1694 	debris_damage_mult = 1.0f;
1695 	debris_arc_percent = 0.5f;
1696 	debris_ambient_sound = GameSounds::DEBRIS;
1697 	debris_collision_sound_light = gamesnd_id();
1698 	debris_collision_sound_heavy = gamesnd_id();
1699 	debris_explosion_sound = GameSounds::MISSILE_IMPACT1;
1700 	generic_debris_pof_file[0] = '\0';
1701 	generic_debris_model_num = -1;
1702 	generic_debris_num_submodels = -1;
1703 	generic_debris_spew_num = 20;
1704 
1705 	n_subsystems = 0;
1706 	subsystems = NULL;
1707 
1708 	power_output = 0.0f;
1709 	max_overclocked_speed = 0.0f;
1710 	max_weapon_reserve = 0.0f;
1711 	max_shield_regen_per_second = 0.0f;
1712 	shield_regen_hit_delay = 0.0f;
1713 	max_weapon_regen_per_second = 0.0f;
1714 
1715 	shield_weap_amount = 0.0f;
1716 	shield_weap_efficiency = 0.0f;
1717 	shield_weap_speed = 0.0f;
1718 	weap_shield_amount = 0.0f;
1719 	weap_shield_efficiency = 0.0f;
1720 	weap_shield_speed = 0.0f;
1721 
1722 	vm_vec_zero(&afterburner_max_vel);
1723 	afterburner_forward_accel = 0.0f;
1724 	afterburner_fuel_capacity = 0.0f;
1725 	afterburner_burn_rate = 0.0f;
1726 	afterburner_recover_rate = 0.0f;
1727 	afterburner_max_reverse_vel = 0.0f;
1728 	afterburner_reverse_accel = 0.0f;
1729 	afterburner_min_start_fuel = DEFAULT_MIN_AFTERBURNER_FUEL_TO_ENGAGE;
1730 	afterburner_min_fuel_to_burn = 0.0f;
1731 	afterburner_cooldown_time = 0.0f;
1732 
1733 	cmeasure_type = Default_cmeasure_index;
1734 	cmeasure_max = 0;
1735 
1736 	num_primary_banks = 0;
1737 	for ( i = 0; i < MAX_SHIP_PRIMARY_BANKS; i++ )
1738 	{
1739 		primary_bank_weapons[i] = -1;
1740 		draw_primary_models[i] = false;
1741 		primary_bank_ammo_capacity[i] = 0;
1742 	}
1743 
1744 	num_secondary_banks = 0;
1745 	for ( i = 0; i < MAX_SHIP_SECONDARY_BANKS; i++ )
1746 	{
1747 		secondary_bank_weapons[i] = -1;
1748 		draw_secondary_models[i] = false;
1749 		secondary_bank_ammo_capacity[i] = 0;
1750 	}
1751 
1752 	weapon_model_draw_distance = 200.0f;
1753 
1754 	ship_recoil_modifier = 1.0f;
1755 
1756 	max_hull_strength = 100.0f;
1757 	max_shield_strength = 0.0f;
1758 
1759 	max_shield_recharge = 1.0f;
1760 
1761 	auto_shield_spread = 0.0f;
1762 	auto_shield_spread_bypass = false;
1763 	auto_shield_spread_from_lod = -1;
1764 	auto_shield_spread_min_span = -1.0f;
1765 
1766 	for (i = 0; i < 4; i++)
1767 	{
1768 		shield_point_augment_ctrls[i] = -1;
1769 	}
1770 
1771 	hull_repair_rate = 0.0f;
1772 	//-2 represents not set, in which case the default is used for the ship (if it is small)
1773 	subsys_repair_rate = -2.0f;
1774 
1775 	sup_hull_repair_rate = 0.15f;
1776 	sup_shield_repair_rate = 0.20f;
1777 	sup_subsys_repair_rate = 0.15f;		// The retail default is 0.10, but it actually used the hull rate of 0.15
1778 
1779 	vm_vec_zero(&closeup_pos);
1780 	closeup_zoom = 0.5f;
1781 
1782 	vm_vec_zero(&closeup_pos_targetbox);
1783 	closeup_zoom_targetbox = 0.5f;
1784 
1785 	vm_vec_zero(&chase_view_offset);
1786 	chase_view_rigidity = 5.0f;
1787 
1788 	allowed_weapons.clear();
1789 
1790 	restricted_loadout_flag.clear();
1791 	allowed_bank_restricted_weapons.clear();
1792 
1793 	shield_icon_index = 255;		// stored as ubyte
1794 	icon_filename[0] = '\0';
1795 	memset(&model_icon_angles, 0, sizeof(angles));
1796 	anim_filename[0] = '\0';
1797 	overhead_filename[0] = '\0';
1798 
1799 	selection_effect = Default_ship_select_effect;
1800 
1801 	bii_index_ship = -1;
1802 	bii_index_ship_with_cargo = -1;
1803 	bii_index_wing = -1;
1804 	bii_index_wing_with_cargo = -1;
1805 
1806 	score = 0;
1807 
1808 	scan_time = 2000;
1809 	scan_range_normal = CARGO_REVEAL_MIN_DIST;
1810 	scan_range_capital = CAP_CARGO_REVEAL_MIN_DIST;
1811 
1812 	ask_help_shield_percent = DEFAULT_ASK_HELP_SHIELD_PERCENT;
1813 	ask_help_hull_percent = DEFAULT_ASK_HELP_HULL_PERCENT;
1814 
1815 	memset(&ct_info, 0, sizeof(trail_info) * MAX_SHIP_CONTRAILS);
1816 	ct_count = 0;
1817 
1818 	shield_color[0] = 255;
1819 	shield_color[1] = 255;
1820 	shield_color[2] = 255;
1821 
1822 	// Team colors
1823 	uses_team_colors = false;
1824 	default_team_name = "";
1825 
1826 	generic_bitmap_init(&afterburner_trail, NULL);
1827 	afterburner_trail_tex_stretch = 1.0f;
1828 	afterburner_trail_width_factor = 1.0f;
1829 	afterburner_trail_alpha_factor = 1.0f;
1830 	afterburner_trail_alpha_end_factor = 0.0f;
1831 	afterburner_trail_alpha_decay_exponent = 1.0f;
1832 	afterburner_trail_life = 5.0f;
1833 	afterburner_trail_spread = 0.0f;
1834 	afterburner_trail_faded_out_sections = 0;
1835 
1836 	normal_thruster_particles.clear();
1837 	afterburner_thruster_particles.clear();
1838 
1839 	// Bobboau's thruster stuff
1840 	generic_anim_init( &thruster_flame_info.normal );
1841 	generic_anim_init( &thruster_flame_info.afterburn );
1842 	generic_anim_init( &thruster_glow_info.normal );
1843 	generic_anim_init( &thruster_glow_info.afterburn );
1844 	generic_bitmap_init( &thruster_secondary_glow_info.normal );
1845 	generic_bitmap_init( &thruster_secondary_glow_info.afterburn );
1846 	generic_bitmap_init( &thruster_tertiary_glow_info.normal );
1847 	generic_bitmap_init( &thruster_tertiary_glow_info.afterburn );
1848 	generic_bitmap_init( &thruster_distortion_info.normal );
1849 	generic_bitmap_init( &thruster_distortion_info.afterburn );
1850 
1851 	// Bobboau's thruster stuff
1852 	thruster01_glow_rad_factor = 1.0f;
1853 	thruster02_glow_rad_factor = 1.0f;
1854 	thruster03_glow_rad_factor = 1.0f;
1855 	thruster02_glow_len_factor = 1.0f;
1856 	thruster_dist_rad_factor = 2.0f;
1857 	thruster_dist_len_factor = 2.0f;
1858 	thruster_glow_noise_mult = 1.0f;
1859 
1860 	draw_distortion = true;
1861 
1862 	splodeing_texture = -1;
1863 	strcpy_s(splodeing_texture_name, "boom");
1864 
1865 	replacement_textures.clear();
1866 
1867 	armor_type_idx = -1;
1868 	shield_armor_type_idx = -1;
1869 
1870 	can_glide = false;
1871 	glide_cap = 0.0f;
1872 	glide_dynamic_cap = false;
1873 	glide_accel_mult = 0.0f;
1874 	use_newtonian_damp = false;
1875 	newtonian_damp_override = false;
1876 
1877 	autoaim_fov = 0.0f;
1878 	autoaim_lock_snd = gamesnd_id();
1879 	autoaim_lost_snd = gamesnd_id();
1880 
1881 	topdown_offset_def = false;
1882 	vm_vec_zero(&topdown_offset);
1883 
1884 	engine_snd = gamesnd_id();
1885 	min_engine_vol = -1.0f;
1886 	glide_start_snd = gamesnd_id();
1887 	glide_end_snd = gamesnd_id();
1888 	flyby_snd = gamesnd_id();
1889 
1890 	ship_sounds.clear();
1891 
1892 	num_maneuvering = 0;
1893 
1894 	for (i = 0; i < MAX_MAN_THRUSTERS; i++)
1895 	{
1896         maneuvering[i].reset();
1897 	}
1898 
1899 	radar_image_2d_idx = -1;
1900 	radar_color_image_2d_idx = -1;
1901 	radar_image_size = -1;
1902 	radar_projection_size_mult = 1.0f;
1903 
1904 	for (i=0;i<MAX_IFFS;i++)
1905 	{
1906 		for (j=0;j<MAX_IFFS;j++)
1907 			ship_iff_info[i][j] = -1;
1908 	}
1909 
1910 	aiming_flags.reset();
1911 	minimum_convergence_distance = 0.0f;
1912 	convergence_distance = 100.0f;
1913 	vm_vec_zero(&convergence_offset);
1914 
1915 	emp_resistance_mod = 0.0f;
1916 
1917 	piercing_damage_draw_limit = 0.10f;
1918 
1919 	damage_lightning_type = SLT_DEFAULT;
1920 
1921 	shield_impact_explosion_anim = -1;
1922 	hud_gauges.clear();
1923 	hud_enabled = false;
1924 	hud_retail = false;
1925 
1926 	displays.clear();
1927 
1928 	pathMetadata.clear();
1929 
1930 	glowpoint_bank_override_map.clear();
1931 }
1932 
~ship_info()1933 ship_info::~ship_info()
1934 {
1935 	if ( subsystems != NULL ) {
1936 		for(int n = 0; n < n_subsystems; n++) {
1937 			if (subsystems[n].triggers != NULL) {
1938 				vm_free(subsystems[n].triggers);
1939 				subsystems[n].triggers = NULL;
1940 			}
1941 		}
1942 
1943 		delete[] subsystems;
1944 		subsystems = nullptr;
1945 	}
1946 
1947 	free_strings();
1948 }
1949 
get_display_name() const1950 const char* ship_info::get_display_name() const
1951 {
1952 	if (has_display_name())
1953 		return display_name;
1954 	else
1955 		return name;
1956 }
1957 
has_display_name() const1958 bool ship_info::has_display_name() const
1959 {
1960 	return flags[Ship::Info_Flags::Has_display_name];
1961 }
1962 
find_flags(int weapon_info_index) const1963 ubyte allowed_weapon_bank::find_flags(int weapon_info_index) const
1964 {
1965 	for (auto &wf : weapon_and_flags)
1966 		if (wf.first == weapon_info_index)
1967 			return wf.second;
1968 
1969 	// since we can return 0 here, we can't make the return type 'const ubyte &'
1970 	return 0;
1971 }
1972 
set_flag(int weapon_info_index,ubyte flag)1973 void allowed_weapon_bank::set_flag(int weapon_info_index, ubyte flag)
1974 {
1975 	for (auto &wf : weapon_and_flags)
1976 	{
1977 		if (wf.first == weapon_info_index)
1978 		{
1979 			wf.second |= flag;
1980 			return;
1981 		}
1982 	}
1983 
1984 	// if we couldn't find it, add a new entry with that flag
1985 	weapon_and_flags.emplace_back(weapon_info_index, flag);
1986 }
1987 
clear_flag(int weapon_info_index,ubyte flag)1988 void allowed_weapon_bank::clear_flag(int weapon_info_index, ubyte flag)
1989 {
1990 	// clear the flag from all matching weapons, and remove any that end up with a flag of 0
1991 	weapon_and_flags.erase(
1992 		std::remove_if(weapon_and_flags.begin(), weapon_and_flags.end(), [weapon_info_index, flag](std::pair<int, ubyte> &wf)
1993 		{
1994 			if (weapon_info_index < 0 || weapon_info_index == wf.first)
1995 			{
1996 				wf.second &= ~flag;
1997 				return (wf.second == 0);
1998 			}
1999 			else
2000 				return false;
2001 		}),
2002 		weapon_and_flags.end()
2003 	);
2004 }
2005 
clear_flag(ubyte flag)2006 void allowed_weapon_bank::clear_flag(ubyte flag)
2007 {
2008 	clear_flag(-1, flag);
2009 }
2010 
clear()2011 void allowed_weapon_bank::clear()
2012 {
2013 	weapon_and_flags.clear();
2014 }
2015 
2016 // read-only!
2017 // (because we don't want to fill up our member vector with a bunch of zeros)
operator [](int index) const2018 ubyte allowed_weapon_bank::operator[](int index) const
2019 {
2020 	return find_flags(index);
2021 }
operator [](size_t index) const2022 ubyte allowed_weapon_bank::operator[](size_t index) const
2023 {
2024 	return find_flags(static_cast<int>(index));
2025 }
2026 
2027 
2028 static SCP_vector<SCP_string> Removed_ships;
2029 
2030 /**
2031  * Parse the information for a specific ship type.
2032  */
parse_ship(const char * filename,bool replace)2033 static void parse_ship(const char *filename, bool replace)
2034 {
2035 	char fname[NAME_LENGTH];
2036 	ship_info *sip = nullptr;
2037 	bool first_time = false;
2038 	bool create_if_not_found = true;
2039 	bool remove_ship = false;
2040 	bool new_name = false;
2041 
2042 	required_string("$Name:");
2043 	stuff_string(fname, F_NAME, NAME_LENGTH);
2044 	diag_printf("Ship name -- %s\n", fname);
2045 
2046 	if (optional_string("+nocreate")) {
2047 		if (!replace) {
2048 			Warning(LOCATION, "+nocreate flag used for ship in non-modular table");
2049 		}
2050 		create_if_not_found = false;
2051 	}
2052 
2053 	// check first if this is on the remove blacklist
2054 	auto it = std::find(Removed_ships.begin(), Removed_ships.end(), fname);
2055 	if (it != Removed_ships.end()) {
2056 		remove_ship = true;
2057 	}
2058 
2059 	// we might have a remove tag
2060 	if (optional_string("+remove")) {
2061 		if (!replace) {
2062 			Warning(LOCATION, "+remove flag used for ship in non-modular table");
2063 		}
2064 		if (!remove_ship) {
2065 			Removed_ships.push_back(fname);
2066 			remove_ship = true;
2067 		}
2068 	}
2069 
2070 	//Remove @ symbol
2071 	//these used to denote weapons that would
2072 	//only be parsed in demo builds
2073 	if ( fname[0] == '@' ) {
2074 		backspace(fname);
2075 	}
2076 
2077 	//Check if ship exists already
2078 	int ship_id = ship_info_lookup_sub(fname);
2079 
2080 	// maybe remove it
2081 	if (remove_ship)
2082 	{
2083 		if (ship_id >= 0) {
2084 			mprintf(("Removing previously parsed ship '%s'\n", fname));
2085 			Ship_info.erase(Ship_info.begin() + ship_id);
2086 		}
2087 
2088 		if (!skip_to_start_of_string_either("$Name:", "#End")) {
2089 			error_display(1, "Missing [#End] or [$Name] after ship class %s", fname);
2090 		}
2091 		return;
2092 	}
2093 
2094 	// an entry for this ship exists
2095 	if (ship_id >= 0)
2096 	{
2097 		sip = &Ship_info[ship_id];
2098 		if (!replace)
2099 		{
2100 			Warning(LOCATION, "Error:  Ship name %s already exists in %s.  All ship class names must be unique.", fname, filename);
2101 			if (!skip_to_start_of_string_either("$Name:", "#End")) {
2102 				error_display(1, "Missing [#End] or [$Name] after duplicate ship class %s", fname);
2103 			}
2104 			return;
2105 		}
2106 	}
2107 	// an entry does not exist
2108 	else
2109 	{
2110 		// Don't create ship if it has +nocreate and is in a modular table.
2111 		if (!create_if_not_found && replace)
2112 		{
2113 			if (!skip_to_start_of_string_either("$Name:", "#End")) {
2114 				error_display(1, "Missing [#End] or [$Name] after ship class %s", fname);
2115 			}
2116 			return;
2117 		}
2118 
2119 		// Check if there are too many ship classes
2120 		if (Ship_info.size() >= MAX_SHIP_CLASSES) {
2121 			Error(LOCATION, "Too many ship classes before '%s'; maximum is %d.\n", fname, MAX_SHIP_CLASSES);
2122 		}
2123 
2124 		Ship_info.push_back(ship_info());
2125 		sip = &Ship_info.back();
2126 		first_time = true;
2127 
2128 		strcpy_s(sip->name, fname);
2129 		new_name = true;
2130 	}
2131 
2132 	// Use a template for this ship.
2133 	if( optional_string( "+Use Template:" ) ) {
2134 		if ( !create_if_not_found ) {
2135 			Warning(LOCATION, "Both '+nocreate' and '+Use Template:' were specified for ship class '%s', ignoring '+Use Template:'\n", fname);
2136 		}
2137 		else {
2138 			char template_name[SHIP_MULTITEXT_LENGTH];
2139 			stuff_string(template_name, F_NAME, SHIP_MULTITEXT_LENGTH);
2140 			int template_id = ship_template_lookup(template_name);
2141 			if ( template_id != -1 ) {
2142 				first_time = false;
2143 				sip->clone(Ship_templates[template_id]);
2144 				strcpy_s(sip->name, fname);
2145 				new_name = true;
2146 			}
2147 			else {
2148 				Warning(LOCATION, "Unable to find ship template '%s' requested by ship class '%s', ignoring template request...", template_name, fname);
2149 			}
2150 		}
2151 	}
2152 
2153 	if (new_name && !sip->flags[Ship::Info_Flags::Has_display_name]) {
2154 		// if this name has a hash, create a default display name
2155 		if (get_pointer_to_first_hash_symbol(sip->name)) {
2156 			strcpy_s(sip->display_name, sip->name);
2157 			end_string_at_first_hash_symbol(sip->display_name);
2158 			sip->flags.set(Ship::Info_Flags::Has_display_name);
2159 		}
2160 
2161 		sip->animations.changeShipName(sip->name);
2162 	}
2163 
2164 	parse_ship_values(sip, false, first_time, replace);
2165 }
2166 
2167 /**
2168  * Parse the information for a specific ship type template.
2169  */
parse_ship_template()2170 static void parse_ship_template()
2171 {
2172 	char buf[SHIP_MULTITEXT_LENGTH];
2173 	ship_info *sip;
2174 	bool first_time = true;
2175 
2176 	required_string("$Template:");
2177 	stuff_string(buf, F_NAME, SHIP_MULTITEXT_LENGTH);
2178 
2179 	if( optional_string("+nocreate") ) {
2180 		Warning(LOCATION, "+nocreate flag used on ship template. Ship templates can not be modified. Ignoring +nocreate.");
2181 	}
2182 
2183 	diag_printf ("Ship template name -- %s\n", buf);
2184 	//Check if the template exists already
2185 	int template_id;
2186 	template_id = ship_template_lookup( buf );
2187 
2188 	if( template_id != -1 ) {
2189 		sip = &Ship_templates[template_id];
2190 		Warning(LOCATION, "WARNING: Ship template %s already exists. All ship template names must be unique.", sip->name);
2191 		if (!skip_to_start_of_string_either("$Template:", "#End")) {
2192 			error_display(1, "Missing [#End] or [$Template] after duplicate ship template %s", sip->name);
2193 		}
2194 		return;
2195 	}
2196 	else {
2197 
2198 		Ship_templates.push_back(ship_info());
2199 		sip = &Ship_templates.back();
2200 
2201 		strcpy_s(sip->name, buf);
2202 		//Use another template for this template. This allows for template hierarchies. - Turey
2203 		if( optional_string("+Use Template:") ) {
2204 			char template_name[SHIP_MULTITEXT_LENGTH];
2205 			stuff_string(template_name, F_NAME, SHIP_MULTITEXT_LENGTH);
2206 			template_id = ship_template_lookup( template_name);
2207 
2208 			if ( template_id != -1 ) {
2209 				first_time = false;
2210 				sip->clone(Ship_templates[template_id]);
2211 				strcpy_s(sip->name, buf);
2212 				sip->animations.changeShipName(sip->name);
2213 			}
2214 			else {
2215 				Warning(LOCATION, "Unable to find ship template '%s' requested by ship template '%s', ignoring template request...", template_name, buf);
2216 			}
2217 		}
2218 	}
2219 
2220 	parse_ship_values( sip, true, first_time, false );
2221 }
2222 
parse_ship_sound(const char * name,GameSounds id,ship_info * sip)2223 static void parse_ship_sound(const char *name, GameSounds id, ship_info *sip)
2224 {
2225 	Assert( name != NULL );
2226 
2227 	gamesnd_id temp_index;
2228 
2229 	if (parse_game_sound(name, &temp_index))
2230 		sip->ship_sounds.insert(std::make_pair(id, temp_index));
2231 }
2232 
parse_ship_sounds(ship_info * sip)2233 static void parse_ship_sounds(ship_info *sip)
2234 {
2235 	parse_ship_sound("$CockpitEngineSnd:",                GameSounds::ENGINE, sip);
2236 	parse_ship_sound("$FullThrottleSnd:",                 GameSounds::FULL_THROTTLE, sip);
2237 	parse_ship_sound("$ZeroThrottleSnd:",                 GameSounds::ZERO_THROTTLE, sip);
2238 	parse_ship_sound("$ThrottleUpSnd:",                   GameSounds::THROTTLE_UP, sip);
2239 	parse_ship_sound("$ThrottleDownSnd:",                 GameSounds::THROTTLE_DOWN, sip);
2240 	parse_ship_sound("$AfterburnerSnd:",                  GameSounds::ABURN_LOOP, sip);
2241 	parse_ship_sound("$AfterburnerEngageSnd:",            GameSounds::ABURN_ENGAGE, sip);
2242 	parse_ship_sound("$AfterburnerFailedSnd:",            GameSounds::ABURN_FAIL, sip);
2243 	parse_ship_sound("$MissileTrackingSnd:",              GameSounds::MISSILE_TRACKING, sip);
2244 	parse_ship_sound("$MissileLockedSnd:",                GameSounds::MISSILE_LOCK, sip);
2245 	parse_ship_sound("$PrimaryCycleSnd:",                 GameSounds::PRIMARY_CYCLE, sip);
2246 	parse_ship_sound("$SecondaryCycleSnd:",               GameSounds::SECONDARY_CYCLE, sip);
2247 	parse_ship_sound("$TargetAcquiredSnd:",               GameSounds::TARGET_ACQUIRE, sip);
2248 	parse_ship_sound("$PrimaryFireFailedSnd:",            GameSounds::OUT_OF_WEAPON_ENERGY, sip);
2249 	parse_ship_sound("$SecondaryFireFailedSnd:",          GameSounds::OUT_OF_MISSLES, sip);
2250 	parse_ship_sound("$HeatSeekerLaunchWarningSnd:",      GameSounds::HEATLOCK_WARN, sip);
2251 	parse_ship_sound("$AspectSeekerLaunchWarningSnd:",    GameSounds::ASPECTLOCK_WARN, sip);
2252 	parse_ship_sound("$MissileLockWarningSnd:",           GameSounds::THREAT_FLASH, sip);
2253 	parse_ship_sound("$HeatSeekerProximityWarningSnd:",   GameSounds::PROXIMITY_WARNING, sip);
2254 	parse_ship_sound("$AspectSeekerProximityWarningSnd:", GameSounds::PROXIMITY_ASPECT_WARNING, sip);
2255 	parse_ship_sound("$MissileEvadedSnd:",                GameSounds::MISSILE_EVADED_POPUP, sip);
2256 	parse_ship_sound("$CargoScanningSnd:",                GameSounds::CARGO_SCAN, sip);
2257 
2258 	parse_ship_sound("$DeathRollSnd:",                    GameSounds::DEATH_ROLL, sip);
2259 	parse_ship_sound("$ExplosionSnd:",                    GameSounds::SHIP_EXPLODE_1, sip);
2260 	parse_ship_sound("$SubsysExplosionSnd:",              GameSounds::SUBSYS_EXPLODE, sip);
2261 }
2262 
parse_ship_particle_effect(ship_info * sip,particle_effect * pe,const char * id_string)2263 static void parse_ship_particle_effect(ship_info* sip, particle_effect* pe, const char *id_string)
2264 {
2265 	float tempf;
2266 	int temp;
2267 	if(optional_string("+Max particles:"))
2268 	{
2269 		stuff_int(&temp);
2270 		if (temp < 0) {
2271 			Warning(LOCATION,"Bad value %i, defined as %s particle number (max) in ship '%s'.\nValue should be a non-negative integer.\n", temp, id_string, sip->name);
2272 		} else {
2273 			pe->n_high = temp;
2274 			if (pe->n_high == 0) {
2275 				// notification for disabling the particles
2276 				mprintf(("Particle effect for %s disabled on ship '%s'.\n", id_string, sip->name));
2277 			}
2278 		}
2279 	}
2280 	if(optional_string("+Min particles:"))
2281 	{
2282 		stuff_int(&temp);
2283 		if (temp < 0) {
2284 			Warning(LOCATION,"Bad value %i, defined as %s particle number (min) in ship '%s'.\nValue should be a non-negative integer.\n", temp, id_string, sip->name);
2285 		} else {
2286 			pe->n_low = temp;
2287 		}
2288 	}
2289 	if (pe->n_low > pe->n_high)
2290 		pe->n_low = pe->n_high;
2291 
2292 	if(optional_string("+Max Radius:"))
2293 	{
2294 		stuff_float(&tempf);
2295 		if (tempf <= 0.0f) {
2296 			Warning(LOCATION,"Bad value %f, defined as %s particle radius (max) in ship '%s'.\nValue should be a positive float.\n", tempf, id_string, sip->name);
2297 		} else {
2298 			pe->max_rad = tempf;
2299 		}
2300 	}
2301 	if(optional_string("+Min Radius:"))
2302 	{
2303 		stuff_float(&tempf);
2304 		if (tempf < 0.0f) {
2305 			Warning(LOCATION,"Bad value %f, defined as %s particle radius (min) in ship '%s'.\nValue should be a non-negative float.\n", tempf, id_string, sip->name);
2306 		} else {
2307 			pe->min_rad = tempf;
2308 		}
2309 	}
2310 	if (pe->min_rad > pe->max_rad)
2311 		pe->min_rad = pe->max_rad;
2312 
2313 	if(optional_string("+Max Lifetime:"))
2314 	{
2315 		stuff_float(&tempf);
2316 		if (tempf <= 0.0f) {
2317 			Warning(LOCATION,"Bad value %f, defined as %s particle lifetime (max) in ship '%s'.\nValue should be a positive float.\n", tempf, id_string, sip->name);
2318 		} else {
2319 			pe->max_life = tempf;
2320 		}
2321 	}
2322 	if(optional_string("+Min Lifetime:"))
2323 	{
2324 		stuff_float(&tempf);
2325 		if (tempf < 0.0f) {
2326 			Warning(LOCATION,"Bad value %f, defined as %s particle lifetime (min) in ship '%s'.\nValue should be a non-negative float.\n", tempf, id_string, sip->name);
2327 		} else {
2328 			pe->min_life = tempf;
2329 		}
2330 	}
2331 	if (pe->min_life > pe->max_life)
2332 		pe->min_life = pe->max_life;
2333 
2334 	if(optional_string("+Max Velocity:"))
2335 	{
2336 		stuff_float(&tempf);
2337 		if (tempf < 0.0f) {
2338 			Warning(LOCATION,"Bad value %f, defined as %s particle velocity (max) in ship '%s'.\nValue should be a non-negative float.\n", tempf, id_string, sip->name);
2339 		} else {
2340 			pe->max_vel = tempf;
2341 		}
2342 	}
2343 	if(optional_string("+Min Velocity:"))
2344 	{
2345 		stuff_float(&tempf);
2346 		if (tempf < 0.0f) {
2347 			Warning(LOCATION,"Bad value %f, defined as %s particle velocity (min) in ship '%s'.\nValue should be a non-negative float.\n", tempf, id_string, sip->name);
2348 		} else {
2349 			pe->min_vel = tempf;
2350 		}
2351 	}
2352 	if (pe->min_vel > pe->max_vel)
2353 		pe->min_vel = pe->max_vel;
2354 
2355 	if(optional_string("+Normal Variance:"))
2356 	{
2357 		stuff_float(&tempf);
2358 		if ((tempf >= 0.0f) && (tempf <= 2.0f)) {
2359 			pe->variance = tempf;
2360 		} else {
2361 			Warning(LOCATION,"Bad value %f, defined as %s particle normal variance in ship '%s'.\nValue should be a float from 0.0 to 2.0.\n", tempf, id_string, sip->name);
2362 		}
2363 	}
2364 }
2365 
parse_allowed_weapons(ship_info * sip,const bool is_primary,const bool is_dogfight,const bool first_time)2366 static void parse_allowed_weapons(ship_info *sip, const bool is_primary, const bool is_dogfight, const bool first_time)
2367 {
2368 	int i, num_allowed;
2369 	int allowed_weapons[MAX_WEAPON_TYPES];
2370 	const int max_banks = (is_primary ? MAX_SHIP_PRIMARY_BANKS : MAX_SHIP_SECONDARY_BANKS);
2371 	const ubyte weapon_type = (is_dogfight ? DOGFIGHT_WEAPON : REGULAR_WEAPON);
2372 	const int offset = (is_primary ? 0 : MAX_SHIP_PRIMARY_BANKS);
2373 	const char *allowed_banks_str = is_primary ? (is_dogfight ? "$Allowed Dogfight PBanks:" : "$Allowed PBanks:")
2374 		: (is_dogfight ? "$Allowed Dogfight SBanks:" : "$Allowed SBanks:");
2375 	const char *bank_type_str = is_primary ? "primary" : "secondary";
2376 
2377 	// Goober5000 - fixed Bobboau's implementation of restricted banks
2378 	int bank;
2379 
2380 	// Set the weapons filter used in weapons loadout
2381 	if (optional_string(allowed_banks_str))
2382 	{
2383 		// this ship has loadout stuff, so make room
2384 		if (sip->restricted_loadout_flag.empty())
2385 			sip->restricted_loadout_flag.resize(MAX_SHIP_WEAPONS);
2386 		if (sip->allowed_bank_restricted_weapons.empty())
2387 			sip->allowed_bank_restricted_weapons.resize(MAX_SHIP_WEAPONS);
2388 
2389 		// MageKing17 - We need to make modular tables replace bank restrictions by default, instead of adding to them.
2390 		if (!first_time && !(optional_string("+noreplace"))) {	// Only makes sense for modular tables.
2391 			// clear allowed weapons so the modular table can define new ones
2392 			for (bank = 0; bank < max_banks; bank++) {
2393 				sip->allowed_bank_restricted_weapons[offset+bank].clear_flag(weapon_type);
2394 				sip->restricted_loadout_flag[offset+bank] &= ~weapon_type;
2395 			}
2396 		}
2397 
2398 		bank = -1;
2399 
2400 		while (check_for_string("("))
2401 		{
2402 			bank++;
2403 
2404 			// make sure we don't specify more than we have banks for
2405 			if (bank >= max_banks)
2406 			{
2407 				Warning(LOCATION, "%s bank-specific loadout for %s exceeds permissible number of %s banks.  Ignoring the rest...", allowed_banks_str, sip->name, bank_type_str);
2408 				bank--;
2409 				break;
2410 			}
2411 
2412 			num_allowed = (int)stuff_int_list(allowed_weapons, MAX_WEAPON_TYPES, WEAPON_LIST_TYPE);
2413 
2414 			// actually say which weapons are allowed
2415 			for ( i = 0; i < num_allowed; i++ )
2416 			{
2417 				if ( allowed_weapons[i] >= 0 )		// MK, Bug fix, 9/6/99.  Used to be "allowed_weapons" not "allowed_weapons[i]".
2418 				{
2419 					sip->allowed_bank_restricted_weapons[offset+bank].set_flag(allowed_weapons[i], weapon_type);
2420 				}
2421 			}
2422 		}
2423 
2424 		// set flags if need be
2425 		if (bank > 0)	// meaning there was a restricted bank table entry
2426 		{
2427 			for (i=0; i<=bank; i++)
2428 			{
2429 				sip->restricted_loadout_flag[offset+i] |= weapon_type;
2430 			}
2431 		}
2432 	}
2433 }
2434 
2435 /**
2436  * Common method for parsing ship/subsystem primary/secondary weapons so that the parser doesn't flip out in the event of a problem.
2437  *
2438  */
parse_weapon_bank(ship_info * sip,bool is_primary,int * num_banks,int * bank_default_weapons,int * bank_capacities)2439 static void parse_weapon_bank(ship_info *sip, bool is_primary, int *num_banks, int *bank_default_weapons, int *bank_capacities)
2440 {
2441 	Assert(sip != NULL);
2442 	Assert(bank_default_weapons != NULL);
2443 	Assert(bank_capacities != NULL);
2444 	const int max_banks = is_primary ? MAX_SHIP_PRIMARY_BANKS : MAX_SHIP_SECONDARY_BANKS;
2445 	const char *default_banks_str = is_primary ? "$Default PBanks:" : "$Default SBanks:";
2446 	const char *bank_capacities_str = is_primary ? "$PBank Capacity:" : "$SBank Capacity:";
2447 
2448 	// we initialize to the previous parse, which presumably worked
2449 	int num_bank_capacities = num_banks != NULL ? *num_banks : 0;
2450 
2451 	if (optional_string(default_banks_str))
2452 	{
2453 		// get weapon list
2454 		if (num_banks != NULL)
2455 			*num_banks = (int)stuff_int_list(bank_default_weapons, max_banks, WEAPON_LIST_TYPE);
2456 		else
2457 			stuff_int_list(bank_default_weapons, max_banks, WEAPON_LIST_TYPE);
2458 	}
2459 
2460 	if (optional_string(bank_capacities_str))
2461 	{
2462 		// get capacity list
2463 		num_bank_capacities = (int)stuff_int_list(bank_capacities, max_banks, RAW_INTEGER_TYPE);
2464 	}
2465 
2466 	// num_banks can be null if we're parsing weapons for a turret
2467 	if ((num_banks != NULL) && (*num_banks != num_bank_capacities))
2468 	{
2469 		// okay for a ship to have 0 primary capacities, since it won't be ammo-enabled
2470 		if (is_primary && num_bank_capacities != 0)
2471 		{
2472 			Warning(LOCATION, "Ship class '%s' has %d primary banks, but %d primary capacities... fix this!!", sip->name, *num_banks, num_bank_capacities);
2473 		}
2474 
2475 		// secondaries have no excuse!
2476 		if (!is_primary)
2477 		{
2478 			Warning(LOCATION, "Ship class '%s' has %d secondary banks, but %d secondary capacities... fix this!!", sip->name, *num_banks, num_bank_capacities);
2479 		}
2480 	}
2481 }
2482 
2483 /**
2484  * Common method for parsing briefing icon info.
2485  */
parse_and_add_briefing_icon_info()2486 static int parse_and_add_briefing_icon_info()
2487 {
2488 	int bii_index = -1;
2489 	size_t icon;
2490 	char regular_temp[MAX_FILENAME_LEN];
2491 	char fade_temp[MAX_FILENAME_LEN];
2492 	char highlight_temp[MAX_FILENAME_LEN];
2493 
2494 	required_string("+Regular:");
2495 	stuff_string(regular_temp, F_NAME, MAX_FILENAME_LEN);
2496 	required_string("+Fade:");
2497 	stuff_string(fade_temp, F_NAME, MAX_FILENAME_LEN);
2498 	required_string("+Highlight:");
2499 	stuff_string(highlight_temp, F_NAME, MAX_FILENAME_LEN);
2500 
2501 	// search among our existing icons
2502 	for (icon = 0; icon < Briefing_icon_info.size(); icon++)
2503 	{
2504 		if (   !stricmp(regular_temp, Briefing_icon_info[icon].regular.filename)
2505 			&& !stricmp(fade_temp, Briefing_icon_info[icon].fade.filename)
2506 			&& !stricmp(highlight_temp, Briefing_icon_info[icon].highlight.filename) )
2507 		{
2508 			bii_index = (int) icon;
2509 			break;
2510 		}
2511 	}
2512 
2513 	// icon not found: create new one
2514 	if (bii_index < 0)
2515 	{
2516 		briefing_icon_info bii;
2517 		generic_anim_init(&bii.regular, regular_temp);
2518 		hud_anim_init(&bii.fade, 0, 0, fade_temp);
2519 		hud_anim_init(&bii.highlight, 0, 0, highlight_temp);
2520 
2521 		bii_index = (int) Briefing_icon_info.size();
2522 		Briefing_icon_info.push_back(bii);
2523 	}
2524 
2525 	return bii_index;
2526 }
2527 
2528 /**
2529  * Determines the warp parameters for this ship class (or ship).
2530  *
2531  * If we are creating a ship, we want to inherit the parameters of the ship class, then override on a field-by-field basis.
2532  */
parse_warp_params(const WarpParams * inherit_from,WarpDirection direction,const char * info_type_name,const char * info_name)2533 int parse_warp_params(const WarpParams *inherit_from, WarpDirection direction, const char *info_type_name, const char *info_name)
2534 {
2535 	Assert(info_type_name != nullptr);
2536 	Assert(info_name != nullptr);
2537 
2538 	// for parsing
2539 	const char *prefix = (direction == WarpDirection::WARP_IN) ? "$Warpin" : "$Warpout";
2540 	char str[NAME_LENGTH];
2541 
2542 	WarpParams params;
2543 	if (inherit_from != nullptr)
2544 		params = *inherit_from;
2545 	params.direction = direction;
2546 
2547 	sprintf(str, "%s type:", prefix);
2548 	if (optional_string(str))
2549 	{
2550 		char buf[NAME_LENGTH];
2551 
2552 		stuff_string(buf, F_NAME, NAME_LENGTH);
2553 		int j = warptype_match(buf);
2554 		if (j >= 0) {
2555 			params.warp_type = j;
2556 		}
2557 		else {
2558 			// try to match the warp type with one of our fireballs
2559 			j = fireball_info_lookup(buf);
2560 			if (j >= 0) {
2561 				params.warp_type = j | WT_DEFAULT_WITH_FIREBALL;
2562 			}
2563 			else {
2564 				Warning(LOCATION, "Invalid %s '%s' specified for %s '%s'", str, buf, info_type_name, info_name);
2565 				params.warp_type = WT_DEFAULT;
2566 			}
2567 		}
2568 	}
2569 
2570 	sprintf(str, "%s Start Sound:", prefix);
2571 	parse_game_sound(str, &params.snd_start);
2572 
2573 	sprintf(str, "%s End Sound:", prefix);
2574 	parse_game_sound(str, &params.snd_end);
2575 
2576 	if (direction == WarpDirection::WARP_OUT)
2577 	{
2578 		sprintf(str, "%s engage time:", prefix);
2579 		if (optional_string(str))
2580 		{
2581 			float t_time;
2582 			stuff_float(&t_time);
2583 			if (t_time > 0.0f)
2584 				params.warpout_engage_time = fl2i(t_time*1000.0f);
2585 			else
2586 				Warning(LOCATION, "%s specified as 0 or less on %s '%s'; value ignored", str, info_type_name, info_name);
2587 		}
2588 	}
2589 
2590 	sprintf(str, "%s speed:", prefix);
2591 	if (optional_string(str))
2592 	{
2593 		float speed;
2594 		stuff_float(&speed);
2595 		if (speed > 0.0f)
2596 			params.speed = speed;
2597 		else
2598 			Warning(LOCATION, "%s specified as 0 or less on %s '%s'; value ignored", str, info_type_name, info_name);
2599 	}
2600 
2601 	sprintf(str, "%s time:", prefix);
2602 	if (optional_string(str))
2603 	{
2604 		float t_time;
2605 		stuff_float(&t_time);
2606 		if (t_time > 0.0f)
2607 			params.time = fl2i(t_time*1000.0f);
2608 		else
2609 			Warning(LOCATION, "%s specified as 0 or less on %s '%s'; value ignored", str, info_type_name, info_name);
2610 	}
2611 
2612 	sprintf(str, "%s %s exp:", prefix, direction == WarpDirection::WARP_IN ? "decel" : "accel");
2613 	if (optional_string(str))
2614 	{
2615 		float accel_exp;
2616 		stuff_float(&accel_exp);
2617 		if (accel_exp >= 0.0f)
2618 			params.accel_exp = accel_exp;
2619 		else
2620 			Warning(LOCATION, "%s specified as less than 0 on %s '%s'; value ignored", str, info_type_name, info_name);
2621 	}
2622 
2623 	sprintf(str, "%s radius:", prefix);
2624 	if (optional_string(str))
2625 	{
2626 		float rad;
2627 		stuff_float(&rad);
2628 		if (rad > 0.0f)
2629 			params.radius = rad;
2630 		else
2631 			Warning(LOCATION, "%s specified as 0 or less on %s '%s'; value ignored", str, info_type_name, info_name);
2632 	}
2633 
2634 	sprintf(str, "%s animation:", prefix);
2635 	if (optional_string(str))
2636 	{
2637 		stuff_string(params.anim, F_NAME, MAX_FILENAME_LEN);
2638 	}
2639 
2640 	if (direction == WarpDirection::WARP_OUT)
2641 	{
2642 		sprintf(str, "$Player warpout speed:");
2643 		if (optional_string(str))
2644 		{
2645 			float speed;
2646 			stuff_float(&speed);
2647 			if (speed > 0.0f)
2648 				params.warpout_player_speed = speed;
2649 			else
2650 				Warning(LOCATION, "%s specified as 0 or less on %s '%s'; value ignored", str, info_type_name, info_name);
2651 		}
2652 	}
2653 
2654 	// get the index of these warp params, which may be a new index
2655 	return find_or_add_warp_params(params);
2656 }
2657 
2658 /**
2659  * Puts values into a ship_info.
2660  */
parse_ship_values(ship_info * sip,const bool is_template,const bool first_time,const bool replace)2661 static void parse_ship_values(ship_info* sip, const bool is_template, const bool first_time, const bool replace)
2662 {
2663 	char buf[SHIP_MULTITEXT_LENGTH];
2664 	const char* info_type_name;
2665 	const char* type_name;
2666 	char name_tmp[NAME_LENGTH];
2667 
2668 	if ( ! is_template ) {
2669 		info_type_name = "Ship Class";
2670 		type_name = "$Name";
2671 	}
2672 	else {
2673 		info_type_name = "Ship Template";
2674 		type_name = "$Template";
2675 	}
2676 
2677 	if (optional_string("$Alt name:") || optional_string("$Display Name:"))
2678 	{
2679 		stuff_string(sip->display_name, F_NAME, NAME_LENGTH);
2680 		end_string_at_first_hash_symbol(sip->display_name, true);
2681 		consolidate_double_characters(sip->display_name, '#');
2682 		sip->flags.set(Ship::Info_Flags::Has_display_name);
2683 	}
2684 
2685 	if(optional_string("$Short name:"))
2686 		stuff_string(sip->short_name, F_NAME, NAME_LENGTH);
2687 	else if (first_time)
2688 	{
2689 		char *srcpos, *srcend, *destpos;
2690 		srcpos = sip->name;
2691 		destpos = sip->short_name;
2692 		srcend = srcpos + strlen(sip->name);
2693 		while(srcpos <= srcend)
2694 		{
2695 			if(*srcpos != ' ')
2696 				*destpos++ = *srcpos++;
2697 			else
2698 				srcpos++;
2699 		}
2700 	}
2701 	diag_printf ("Ship short name -- %s\n", sip->short_name);
2702 
2703 	if (optional_string("$Species:")) {
2704 		char temp[NAME_LENGTH];
2705 		stuff_string(temp, F_NAME, NAME_LENGTH);
2706 		int i_species = 0;
2707 
2708 		bool found = false;
2709 		for (SCP_vector<species_info>::iterator sii = Species_info.begin(); sii != Species_info.end(); ++sii, ++i_species) {
2710 			if (!stricmp(temp, sii->species_name)) {
2711 				sip->species = i_species;
2712 				found = true;
2713 				break;
2714 			}
2715 		}
2716 
2717 		if (!found) {
2718 			Error(LOCATION, "Invalid Species %s defined in table entry for ship %s.\n", temp, sip->name);
2719 		}
2720 	}
2721 
2722 	diag_printf ("Ship species -- %s\n", Species_info[sip->species].species_name);
2723 
2724 	if (optional_string("+Type:")) {
2725 		stuff_malloc_string(&sip->type_str, F_MESSAGE);
2726 	}
2727 
2728 	if (optional_string("+Maneuverability:")) {
2729 		stuff_malloc_string(&sip->maneuverability_str, F_MESSAGE);
2730 	}
2731 
2732 	if (optional_string("+Armor:")) {
2733 		stuff_malloc_string(&sip->armor_str, F_MESSAGE);
2734 	}
2735 
2736 	if (optional_string("+Manufacturer:")) {
2737 		stuff_malloc_string(&sip->manufacturer_str, F_MESSAGE);
2738 	}
2739 
2740 	if (optional_string("+Description:")) {
2741 		stuff_malloc_string(&sip->desc, F_MULTITEXT, NULL);
2742 	}
2743 
2744 	if (optional_string("+Tech Title:")) {
2745 		stuff_string(sip->tech_title, F_NAME, NAME_LENGTH);
2746 	}
2747 
2748 	if (optional_string("+Tech Description:")) {
2749 		stuff_malloc_string(&sip->tech_desc, F_MULTITEXT, NULL);
2750 	}
2751 
2752 	if (optional_string("+Length:")) {
2753 		stuff_malloc_string(&sip->ship_length, F_MESSAGE);
2754 	}
2755 
2756 	if (optional_string("+Gun Mounts:")) {
2757 		stuff_malloc_string(&sip->gun_mounts, F_MESSAGE);
2758 	}
2759 
2760 	if (optional_string("+Missile Banks:")) {
2761 		stuff_malloc_string(&sip->missile_banks, F_MESSAGE);
2762 	}
2763 
2764 	// Ship fadein effect, used when no ani is specified or ship_select_3d is active
2765 	if(optional_string("$Selection Effect:")) {
2766 		char effect[NAME_LENGTH];
2767 		stuff_string(effect, F_NAME, NAME_LENGTH);
2768 		if (!stricmp(effect, "FS2"))
2769 			sip->selection_effect = 2;
2770 		else if (!stricmp(effect, "FS1"))
2771 			sip->selection_effect = 1;
2772 		else if (!stricmp(effect, "off"))
2773 			sip->selection_effect = 0;
2774 	}
2775 
2776 	if(optional_string( "$Cockpit POF file:" ))
2777 	{
2778 		char temp[MAX_FILENAME_LEN];
2779 		stuff_string(temp, F_NAME, MAX_FILENAME_LEN);
2780 
2781 		// assume we're using this file name
2782 		bool valid = true;
2783 
2784 		// Goober5000 - if this is a modular table, and we're replacing an existing file name, and the file doesn't exist, don't replace it
2785 		if (replace)
2786 			if (sip->cockpit_pof_file[0] != '\0')
2787 				if (!cf_exists_full(temp, CF_TYPE_MODELS))
2788 					valid = false;
2789 
2790 		if (valid)
2791 			strcpy_s(sip->cockpit_pof_file, temp);
2792 		else
2793 			WarningEx(LOCATION, "Ship %s\nCockpit POF file \"%s\" invalid!", sip->name, temp);
2794 	}
2795 	if(optional_string( "+Cockpit offset:" ))
2796 	{
2797 		stuff_vec3d(&sip->cockpit_offset);
2798 	}
2799 	while(optional_string( "$Cockpit Display:" ))
2800 	{
2801 		cockpit_display_info display;
2802 
2803 		display.bg_filename[0] = 0;
2804 		display.fg_filename[0] = 0;
2805 		display.filename[0] = 0;
2806 		display.name[0] = 0;
2807 		display.offset[0] = 0;
2808 		display.offset[1] = 0;
2809 
2810 		required_string("+Texture:");
2811 		stuff_string(display.filename, F_NAME, MAX_FILENAME_LEN);
2812 
2813 		if ( optional_string("+Offsets:") ) {
2814 			stuff_int_list(display.offset, 2);
2815 		}
2816 
2817 		required_string("+Size:");
2818 		stuff_int_list(display.size, 2);
2819 
2820 		if ( optional_string("+Background:") ) {
2821 			stuff_string(display.bg_filename, F_NAME, MAX_FILENAME_LEN);
2822 		}
2823 		if ( optional_string("+Foreground:") ) {
2824 			stuff_string(display.fg_filename, F_NAME, MAX_FILENAME_LEN);
2825 		}
2826 
2827 		required_string("+Display Name:");
2828 		stuff_string(display.name, F_NAME, MAX_FILENAME_LEN);
2829 
2830 		if ( display.offset[0] < 0 || display.offset[1] < 0 ) {
2831 			Warning(LOCATION, "Negative display offsets given for cockpit display on %s, skipping entry", sip->name);
2832 			continue;
2833 		}
2834 
2835 		if( display.size[0] <= 0 || display.size[1] <= 0 ) {
2836 			Warning(LOCATION, "Negative or zero display size given for cockpit display on %s, skipping entry", sip->name);
2837 			continue;
2838 		}
2839 
2840 		sip->displays.push_back(display);
2841 	}
2842 
2843 	if(optional_string( "$POF file:" ))
2844 	{
2845 		char temp[MAX_FILENAME_LEN];
2846 		stuff_string(temp, F_NAME, MAX_FILENAME_LEN);
2847 
2848 		// assume we're using this file name
2849 		bool valid = true;
2850 
2851 		// Goober5000 - if this is a modular table, and we're replacing an existing file name, and the file doesn't exist, don't replace it
2852 		if (replace)
2853 			if (sip->pof_file[0] != '\0')
2854 				if (!cf_exists_full(temp, CF_TYPE_MODELS))
2855 					valid = false;
2856 
2857 		if (valid)
2858 			strcpy_s(sip->pof_file, temp);
2859 		else
2860 			WarningEx(LOCATION, "Ship %s\nPOF file \"%s\" invalid!", sip->name, temp);
2861 	}
2862 
2863 	if(optional_string( "$POF file Techroom:" ))
2864 	{
2865 		char temp[MAX_FILENAME_LEN];
2866 		stuff_string(temp, F_NAME, MAX_FILENAME_LEN);
2867 
2868 		// assume we're using this file name
2869 		bool valid = true;
2870 
2871 		// if this is a modular table, and we're replacing an existing file name, and the file doesn't exist, don't replace it
2872 		if (replace)
2873 			if (sip->pof_file_tech[0] != '\0')
2874 				if (!cf_exists_full(temp, CF_TYPE_MODELS))
2875 					valid = false;
2876 
2877 		if (valid)
2878 			strcpy_s(sip->pof_file_tech, temp);
2879 		else
2880 			WarningEx(LOCATION, "Ship %s\nTechroom POF file \"%s\" invalid!", sip->name, temp);
2881 	}
2882 
2883 	// ship class texture replacement - Goober5000
2884 	// don't clear the vector because we could be parsing a TBM
2885 	if (optional_string("$Texture Replace:"))
2886 	{
2887 		texture_replace tr;
2888 		char *p;
2889 
2890 		tr.from_table = true;
2891 
2892 		while (optional_string("+old:"))
2893 		{
2894 			strcpy_s(tr.ship_name, sip->name);
2895 			tr.new_texture_id = -1;
2896 
2897 			stuff_string(tr.old_texture, F_NAME, MAX_FILENAME_LEN);
2898 			required_string("+new:");
2899 			stuff_string(tr.new_texture, F_NAME, MAX_FILENAME_LEN);
2900 
2901 			// get rid of extensions
2902 			p = strchr(tr.old_texture, '.');
2903 			if (p)
2904 			{
2905 				mprintf(("Extraneous extension found on replacement texture %s!\n", tr.old_texture));
2906 				*p = 0;
2907 			}
2908 			p = strchr(tr.new_texture, '.');
2909 			if (p)
2910 			{
2911 				mprintf(("Extraneous extension found on replacement texture %s!\n", tr.new_texture));
2912 				*p = 0;
2913 			}
2914 
2915 			// add it if we aren't over the limit
2916 			if (sip->replacement_textures.size() < MAX_MODEL_TEXTURES)
2917 				sip->replacement_textures.push_back(tr);
2918 			else
2919 				mprintf(("Too many replacement textures specified for ship '%s'!\n", sip->name));
2920 		}
2921 	}
2922 
2923 	// optional hud targeting model
2924 	if(optional_string( "$POF target file:"))
2925 	{
2926 		char temp[MAX_FILENAME_LEN];
2927 		stuff_string(temp, F_NAME, MAX_FILENAME_LEN);
2928 
2929 		// assume we're using this file name
2930 		bool valid = true;
2931 
2932 		// Goober5000 - if this is a modular table, and we're replacing an existing file name, and the file doesn't exist, don't replace it
2933 		if (replace)
2934 			if (sip->pof_file_hud[0] != '\0')
2935 				if (!cf_exists_full(temp, CF_TYPE_MODELS))
2936 					valid = false;
2937 
2938 		if (valid)
2939 			strcpy_s(sip->pof_file_hud, temp);
2940 		else
2941 			WarningEx(LOCATION, "Ship \"%s\" POF target file \"%s\" invalid!", sip->name, temp);
2942 	}
2943 
2944 	// optional hud target LOD if not using special hud model
2945 	if (optional_string( "$POF target LOD:" )) {
2946 		stuff_int(&sip->hud_target_lod);
2947 	}
2948 
2949 	if(optional_string("$Detail distance:")) {
2950 		sip->num_detail_levels = (int)stuff_int_list(sip->detail_distance, MAX_SHIP_DETAIL_LEVELS, RAW_INTEGER_TYPE);
2951 	}
2952 
2953 	if(optional_string("$Collision LOD:")) {
2954 		stuff_int(&sip->collision_lod);
2955 
2956 		// Cap to sane values, just in case
2957 		sip->collision_lod = MAX(0, MIN(sip->collision_lod, sip->num_detail_levels));
2958 	}
2959 
2960 	// check for optional pixel colors
2961 	// This is dummied out; we no longer support palettized rendering modes
2962 	while(optional_string("$ND:")){
2963 		ubyte nr, ng, nb;
2964 		stuff_ubyte(&nr);
2965 		stuff_ubyte(&ng);
2966 		stuff_ubyte(&nb);
2967 
2968 	}
2969 
2970 	if (optional_string("$Enable Team Colors:")) {
2971 		stuff_boolean(&sip->uses_team_colors);
2972 		sip->default_team_name = "None";
2973 	}
2974 
2975 	if (optional_string("$Default Team:")) {
2976 		char temp[NAME_LENGTH];
2977 		stuff_string(temp, F_NAME, NAME_LENGTH);
2978 		SCP_string name = temp;
2979 		if (!stricmp(temp, "none")) {
2980 			sip->uses_team_colors = true;
2981 		} else {
2982 			if (Team_Colors.find(name) != Team_Colors.end()) {
2983 				sip->default_team_name = name;
2984 				sip->uses_team_colors = true;
2985 			} else {
2986 				Warning(LOCATION, "Team name %s is invalid. Teams must be defined in colors.tbl.\n", temp);
2987 			}
2988 		}
2989 	}
2990 
2991 	if(optional_string("$Show damage:"))
2992 	{
2993 		int bogus_bool;
2994 		stuff_boolean(&bogus_bool);
2995 	}
2996 
2997 	if(optional_string("$Damage Lightning Type:"))
2998 	{
2999 		stuff_string(buf, F_NAME, SHIP_MULTITEXT_LENGTH);
3000 		auto j = lightningtype_match(buf);
3001 		if(j >= 0) {
3002 			sip->damage_lightning_type = j;
3003 		} else {
3004 			Warning(LOCATION, "Invalid lightning type '%s' specified for ship '%s'", buf, sip->name);
3005 			sip->damage_lightning_type = SLT_DEFAULT;
3006 		}
3007 	}
3008 
3009 	if(optional_string("$Impact:"))
3010 	{
3011 		if(optional_string("+Damage Type:"))
3012 		{
3013 			stuff_string(buf, F_NAME, NAME_LENGTH);
3014 			sip->collision_damage_type_idx = damage_type_add(buf);
3015 		}
3016 	}
3017 
3018 	//HACK -
3019 	//This should really be reworked so that all particle fields
3020 	//are settable, but erg, just not happening right now -C
3021 	if(optional_string("$Impact Spew:"))
3022 	{
3023 		parse_ship_particle_effect(sip, &sip->impact_spew, "impact spew");
3024 	}
3025 	if(optional_string("$Damage Spew:"))
3026 	{
3027 		parse_ship_particle_effect(sip, &sip->damage_spew, "damage spew");
3028 	}
3029 
3030 	if(optional_string("$Collision Physics:"))
3031 	{
3032 		if(optional_string("+Bounce:"))	{
3033 			stuff_float(&sip->collision_physics.bounce);
3034 		}
3035 		if(optional_string("+Both Small Bounce:")) {
3036 			stuff_float(&sip->collision_physics.both_small_bounce);
3037 		}
3038 		if(optional_string("+Friction:")) {
3039 			stuff_float(&sip->collision_physics.friction);
3040 		}
3041 		if(optional_string("+Rotation Factor:")) {
3042 			stuff_float(&sip->collision_physics.rotation_factor);
3043 		}
3044 		if(optional_string("+Landing Max Forward Vel:")) {
3045 			stuff_float(&sip->collision_physics.landing_max_z);
3046 		}
3047 		if(optional_string("+Landing Min Forward Vel:")) {
3048 			stuff_float(&sip->collision_physics.landing_min_z);
3049 		}
3050 		if(optional_string("+Landing Max Descent Vel:")) {
3051 			stuff_float(&sip->collision_physics.landing_min_y);
3052 			sip->collision_physics.landing_min_y *= -1;
3053 		}
3054 		if(optional_string("+Landing Max Horizontal Vel:")) {
3055 			stuff_float(&sip->collision_physics.landing_max_x);
3056 		}
3057 		if(optional_string("+Landing Max Angle:")) {
3058 			float degrees;
3059 			stuff_float(&degrees);
3060 			sip->collision_physics.landing_max_angle = cosf(fl_radians(90.0f - degrees));
3061 		}
3062 		if(optional_string("+Landing Min Angle:")) {
3063 			float degrees;
3064 			stuff_float(&degrees);
3065 			sip->collision_physics.landing_min_angle = cosf(fl_radians(90.0f - degrees));
3066 		}
3067 		if(optional_string("+Landing Max Rotate Angle:")) {
3068 			float degrees;
3069 			stuff_float(&degrees);
3070 			sip->collision_physics.landing_max_rot_angle = cosf(fl_radians(90.0f - degrees));
3071 		}
3072 		if(optional_string("+Reorient Max Forward Vel:")) {
3073 			stuff_float(&sip->collision_physics.reorient_max_z);
3074 		}
3075 		if(optional_string("+Reorient Min Forward Vel:")) {
3076 			stuff_float(&sip->collision_physics.reorient_min_z);
3077 		}
3078 		if(optional_string("+Reorient Max Descent Vel:")) {
3079 			stuff_float(&sip->collision_physics.reorient_min_y);
3080 			sip->collision_physics.reorient_min_y *= -1;
3081 		}
3082 		if(optional_string("+Reorient Max Horizontal Vel:")) {
3083 			stuff_float(&sip->collision_physics.reorient_max_x);
3084 		}
3085 		if(optional_string("+Reorient Max Angle:")) {
3086 			float degrees;
3087 			stuff_float(&degrees);
3088 			sip->collision_physics.reorient_max_angle = cosf(fl_radians(90.0f - degrees));
3089 		}
3090 		if(optional_string("+Reorient Min Angle:")) {
3091 			float degrees;
3092 			stuff_float(&degrees);
3093 			sip->collision_physics.reorient_min_angle = cosf(fl_radians(90.0f - degrees));
3094 		}
3095 		if(optional_string("+Reorient Max Rotate Angle:")) {
3096 			float degrees;
3097 			stuff_float(&degrees);
3098 			sip->collision_physics.reorient_max_rot_angle = cosf(fl_radians(90.0f - degrees));
3099 		}
3100 		if(optional_string("+Reorient Speed Mult:")) {
3101 			stuff_float(&sip->collision_physics.reorient_mult);
3102 		}
3103 		if(optional_string("+Landing Rest Angle:")) {
3104 			float degrees;
3105 			stuff_float(&degrees);
3106 			sip->collision_physics.landing_rest_angle = cosf(fl_radians(90.0f - degrees));
3107 		}
3108 		parse_game_sound("+Landing Sound:", &sip->collision_physics.landing_sound_idx);
3109 
3110 		parse_game_sound("+Collision Sound Light:", &sip->collision_physics.collision_sound_light_idx);
3111 		parse_game_sound("+Collision Sound Heavy:", &sip->collision_physics.collision_sound_heavy_idx);
3112 		parse_game_sound("+Collision Sound Shielded:", &sip->collision_physics.collision_sound_shielded_idx);
3113 	}
3114 
3115 
3116 	if(optional_string("$Debris:"))
3117 	{
3118 		if(optional_string("+Min Lifetime:"))	{
3119 			stuff_float(&sip->debris_min_lifetime);
3120 			if(sip->debris_min_lifetime < 0.0f)
3121 				Warning(LOCATION, "Debris min lifetime on %s '%s' is below 0 and will be ignored", info_type_name, sip->name);
3122 		}
3123 		if(optional_string("+Max Lifetime:"))	{
3124 			stuff_float(&sip->debris_max_lifetime);
3125 			if(sip->debris_max_lifetime < 0.0f)
3126 				Warning(LOCATION, "Debris max lifetime on %s '%s' is below 0 and will be ignored", info_type_name, sip->name);
3127 		}
3128 		if(optional_string("+Min Speed:"))	{
3129 			stuff_float(&sip->debris_min_speed);
3130 			if(sip->debris_min_speed < 0.0f)
3131 				Warning(LOCATION, "Debris min speed on %s '%s' is below 0 and will be ignored", info_type_name, sip->name);
3132 		}
3133 		if(optional_string("+Max Speed:"))	{
3134 			stuff_float(&sip->debris_max_speed);
3135 			if(sip->debris_max_speed < 0.0f)
3136 				Warning(LOCATION, "Debris max speed on %s '%s' is below 0 and will be ignored", info_type_name, sip->name);
3137 		}
3138 		if(optional_string("+Min Rotation speed:"))	{
3139 			stuff_float(&sip->debris_min_rotspeed);
3140 			if(sip->debris_min_rotspeed < 0.0f)
3141 				Warning(LOCATION, "Debris min speed on %s '%s' is below 0 and will be ignored", info_type_name, sip->name);
3142 		}
3143 		if(optional_string("+Max Rotation speed:"))	{
3144 			stuff_float(&sip->debris_max_rotspeed);
3145 			if(sip->debris_max_rotspeed < 0.0f)
3146 				Warning(LOCATION, "Debris max speed on %s '%s' is below 0 and will be ignored", info_type_name, sip->name);
3147 		}
3148 		if(optional_string("+Damage Type:")) {
3149 			stuff_string(buf, F_NAME, NAME_LENGTH);
3150 			sip->debris_damage_type_idx = damage_type_add(buf);
3151 		}
3152 		if(optional_string("+Min Hitpoints:")) {
3153 			stuff_float(&sip->debris_min_hitpoints);
3154 			if(sip->debris_min_hitpoints < 0.0f)
3155 				Warning(LOCATION, "Debris min hitpoints on %s '%s' is below 0 and will be ignored", info_type_name, sip->name);
3156 		}
3157 		if(optional_string("+Max Hitpoints:")) {
3158 			stuff_float(&sip->debris_max_hitpoints);
3159 			if(sip->debris_max_hitpoints < 0.0f)
3160 				Warning(LOCATION, "Debris max hitpoints on %s '%s' is below 0 and will be ignored", info_type_name, sip->name);
3161 		}
3162 		if(optional_string("+Damage Multiplier:")) {
3163 			stuff_float(&sip->debris_damage_mult);
3164 			if(sip->debris_damage_mult < 0.0f)
3165 				Warning(LOCATION, "Debris damage multiplier on %s '%s' is below 0 and will be ignored", info_type_name, sip->name);
3166 		}
3167 		if(optional_string("+Lightning Arc Percent:")) {
3168 			stuff_float(&sip->debris_arc_percent);
3169 			if(sip->debris_arc_percent < 0.0f || sip->debris_arc_percent > 100.0f) {
3170 				Warning(LOCATION, "Lightning Arc Percent on %s '%s' should be between 0 and 100.0 (read %f). Entry will be ignored.", info_type_name, sip->name, sip->debris_arc_percent);
3171 				sip->debris_arc_percent = 50.0;
3172 			}
3173 			//Percent is nice for modders, but here in the code we want it betwwen 0 and 1.0
3174 			sip->debris_arc_percent /= 100.0;
3175 		}
3176 		gamesnd_id ambient_snd, collision_snd_light, collision_snd_heavy, explosion_snd;
3177 		if (parse_game_sound("+Ambient Sound:", &ambient_snd))
3178 			sip->debris_ambient_sound = ambient_snd;
3179 		if (parse_game_sound("+Collision Sound Light:", &collision_snd_light))
3180 			sip->debris_collision_sound_light = collision_snd_light;
3181 		if (parse_game_sound("+Collision Sound Heavy:", &collision_snd_heavy))
3182 			sip->debris_collision_sound_heavy = collision_snd_heavy;
3183 		if (parse_game_sound("+Explosion Sound:", &explosion_snd))
3184 			sip->debris_explosion_sound = explosion_snd;
3185 
3186 		if (optional_string("+Generic Debris POF file:"))
3187 		{
3188 			char temp[MAX_FILENAME_LEN];
3189 			stuff_string(temp, F_NAME, MAX_FILENAME_LEN);
3190 
3191 			bool valid = true;
3192 
3193 			if (replace)
3194 				if (sip->generic_debris_pof_file[0] != '\0')
3195 					if (!cf_exists_full(temp, CF_TYPE_MODELS))
3196 						valid = false;
3197 
3198 			if (valid)
3199 				strcpy_s(sip->generic_debris_pof_file, temp);
3200 			else
3201 				WarningEx(LOCATION, "Ship %s\nGeneric Debris POF file \"%s\" invalid!", sip->name, temp);
3202 		}
3203 
3204 		if (optional_string("+Generic Debris Spew Num:")) {
3205 			stuff_int(&(sip->generic_debris_spew_num));
3206 			if (sip->generic_debris_spew_num < 0) {
3207 				Warning(LOCATION, "Generic Debris Spew Num for %s '%s' cannot be below 0.", info_type_name, sip->name);
3208 				sip->generic_debris_spew_num = 0;
3209 			}
3210 		}
3211 
3212 	}
3213 	//WMC - sanity checking
3214 	if(sip->debris_min_speed > sip->debris_max_speed && sip->debris_max_speed >= 0.0f) {
3215 		Warning(LOCATION, "Debris min speed (%f) on %s '%s' is greater than debris max speed (%f), and will be set to debris max speed.", sip->debris_min_speed, info_type_name, sip->name, sip->debris_max_speed);
3216 		sip->debris_min_speed = sip->debris_max_speed;
3217 	}
3218 	if(sip->debris_min_rotspeed > sip->debris_max_rotspeed && sip->debris_max_rotspeed >= 0.0f) {
3219 		Warning(LOCATION, "Debris min rotation speed (%f) on %s '%s' is greater than debris max rotation speed (%f), and will be set to debris max rotation speed.", sip->debris_min_rotspeed, info_type_name, sip->name, sip->debris_max_rotspeed);
3220 		sip->debris_min_rotspeed = sip->debris_max_rotspeed;
3221 	}
3222 	if(sip->debris_min_lifetime > sip->debris_max_lifetime && sip->debris_max_lifetime >= 0.0f) {
3223 		Warning(LOCATION, "Debris min lifetime (%f) on %s '%s' is greater than debris max lifetime (%f), and will be set to debris max lifetime.", sip->debris_min_lifetime, info_type_name, sip->name, sip->debris_max_lifetime);
3224 		sip->debris_min_lifetime = sip->debris_max_lifetime;
3225 	}
3226 	if(sip->debris_min_hitpoints > sip->debris_max_hitpoints && sip->debris_max_hitpoints >= 0.0f) {
3227 		Warning(LOCATION, "Debris min hitpoints (%f) on %s '%s' is greater than debris max hitpoints (%f), and will be set to debris max hitpoints.", sip->debris_min_hitpoints, info_type_name, sip->name, sip->debris_max_hitpoints);
3228 		sip->debris_min_hitpoints = sip->debris_max_hitpoints;
3229 	}
3230 
3231 	if(optional_string("$Density:"))
3232 		stuff_float( &(sip->density) );
3233 	diag_printf ("Ship density -- %7.3f\n", sip->density);
3234 
3235 	if(optional_string("$Damp:"))
3236 		stuff_float( &(sip->damp) );
3237 	diag_printf ("Ship damp -- %7.3f\n", sip->damp);
3238 
3239 	if(optional_string("$Rotdamp:"))
3240 		stuff_float( &(sip->rotdamp) );
3241 	diag_printf ("Ship rotdamp -- %7.3f\n", sip->rotdamp);
3242 
3243 	if(optional_string("$Banking Constant:"))
3244 		stuff_float( &(sip->delta_bank_const) );
3245 	diag_printf ("%s '%s' delta_bank_const -- %7.3f\n", info_type_name, sip->name, sip->delta_bank_const);
3246 
3247 	if(optional_string("$Max Velocity:"))
3248 	{
3249 		stuff_vec3d(&sip->max_vel);
3250 		sip->max_accel = sip->max_vel.xyz.z;
3251 	}
3252 
3253 	// calculate the max speed from max_velocity
3254 	sip->max_speed = sip->max_vel.xyz.z;
3255 
3256 	if(optional_string("$Player Minimum Velocity:"))
3257 		stuff_vec3d(&sip->min_vel);
3258 
3259 	if(optional_string("$Rotation Time:"))
3260 	{
3261 		stuff_vec3d(&sip->rotation_time);
3262 
3263 		// div/0 safety check.
3264 		if ((sip->rotation_time.xyz.x == 0) || (sip->rotation_time.xyz.y == 0) || (sip->rotation_time.xyz.z == 0))
3265 			Warning(LOCATION, "Rotation time must have non-zero values in each of the three variables.\nFix this in %s %s\n", info_type_name, sip->name);
3266 
3267 		sip->srotation_time = (sip->rotation_time.xyz.x + sip->rotation_time.xyz.y)/2.0f;
3268 
3269 		sip->max_rotvel.xyz.x = (2 * PI) / sip->rotation_time.xyz.x;
3270 		sip->max_rotvel.xyz.y = (2 * PI) / sip->rotation_time.xyz.y;
3271 		sip->max_rotvel.xyz.z = (2 * PI) / sip->rotation_time.xyz.z;
3272 	}
3273 
3274 	// get the backwards velocity;
3275 	if(optional_string("$Rear Velocity:"))
3276 	{
3277 		stuff_float(&sip->max_rear_vel);
3278 		sip->min_speed = -sip->max_rear_vel;
3279 	}
3280 
3281 	// get the accelerations
3282 	if(optional_string("$Forward accel:"))
3283 		stuff_float(&sip->forward_accel );
3284 
3285 	if(optional_string("$Forward decel:"))
3286 		stuff_float(&sip->forward_decel );
3287 
3288 	if(optional_string("$Slide accel:"))
3289 		stuff_float(&sip->slide_accel );
3290 
3291 	if(optional_string("$Slide decel:"))
3292 		stuff_float(&sip->slide_decel );
3293 
3294 	if(optional_string("$Glide:"))
3295 	{
3296 		stuff_boolean(&sip->can_glide);
3297 	}
3298 
3299 	if(sip->can_glide == true)
3300 	{
3301 		if(optional_string("+Dynamic Glide Cap:"))
3302 			stuff_boolean(&sip->glide_dynamic_cap);
3303 		if(optional_string("+Max Glide Speed:"))
3304 			stuff_float(&sip->glide_cap );
3305 		if(optional_string("+Glide Accel Mult:"))
3306 			stuff_float(&sip->glide_accel_mult);
3307 	}
3308 
3309 	if(optional_string("$Use Newtonian Dampening:")) {
3310 			sip->newtonian_damp_override = true;
3311 			stuff_boolean(&sip->use_newtonian_damp);
3312 	}
3313 
3314 	if(optional_string("$Autoaim FOV:"))
3315 	{
3316 		float fov_temp;
3317 		stuff_float(&fov_temp);
3318 
3319 		// Make sure it is a reasonable value
3320 		if (fov_temp < 0.0f)
3321 			fov_temp = 0.0f;
3322 
3323 		if (fov_temp > 180.0f)
3324 			fov_temp = 180.0f;
3325 
3326 		sip->aiming_flags.set(Ship::Aiming_Flags::Autoaim);
3327 		sip->autoaim_fov = fov_temp * PI / 180.0f;
3328 
3329 		if(optional_string("+Converging Autoaim"))
3330 			sip->aiming_flags.set(Ship::Aiming_Flags::Autoaim_convergence);
3331 
3332 		if(optional_string("+Minimum Distance:"))
3333 			stuff_float(&sip->minimum_convergence_distance);
3334 
3335 		parse_game_sound("+Autoaim Lock Snd:", &sip->autoaim_lock_snd);
3336 		parse_game_sound("+Autoaim Lost Snd:", &sip->autoaim_lost_snd);
3337 	}
3338 
3339 	if(optional_string("$Convergence:"))
3340 	{
3341 		if(optional_string("+Automatic"))
3342 		{
3343 			sip->aiming_flags.set(Ship::Aiming_Flags::Auto_convergence);
3344 			if(optional_string("+Minimum Distance:"))
3345 				stuff_float(&sip->minimum_convergence_distance);
3346 		}
3347 		if(optional_string("+Standard"))
3348 		{
3349             sip->aiming_flags.set(Ship::Aiming_Flags::Std_convergence);
3350 			if(required_string("+Distance:"))
3351 				stuff_float(&sip->convergence_distance);
3352 		}
3353 		if(optional_string("+Offset:")) {
3354 			stuff_vec3d(&sip->convergence_offset);
3355 
3356             sip->aiming_flags.set(Ship::Aiming_Flags::Convergence_offset, !IS_VEC_NULL(&sip->convergence_offset));
3357 		}
3358 	}
3359 
3360 	// get ship parameters for warpin and warpout
3361 	// Note: if the index is not -1, we must have already assigned warp parameters, probably because we are now
3362 	// parsing a TBM.  In that case, inherit from ourselves.
3363 	sip->warpin_params_index = parse_warp_params(sip->warpin_params_index >= 0 ? &Warp_params[sip->warpin_params_index] : nullptr, WarpDirection::WARP_IN, info_type_name, sip->name);
3364 	sip->warpout_params_index = parse_warp_params(sip->warpout_params_index >= 0 ? &Warp_params[sip->warpout_params_index] : nullptr, WarpDirection::WARP_OUT, info_type_name, sip->name);
3365 
3366 	// get ship explosion info
3367 	shockwave_create_info *sci = &sip->shockwave;
3368 	if(optional_string("$Expl inner rad:")){
3369 		stuff_float(&sci->inner_rad);
3370 	}
3371 
3372 	if(optional_string("$Expl outer rad:")){
3373 		stuff_float(&sci->outer_rad);
3374 	}
3375 
3376 	if(optional_string("$Expl damage:")){
3377 		stuff_float(&sci->damage);
3378 	}
3379 
3380 	if(optional_string("$Expl blast:")){
3381 		stuff_float(&sci->blast);
3382 	}
3383 
3384 	if(optional_string("$Expl Propagates:")){
3385 		stuff_boolean(&sip->explosion_propagates);
3386 	}
3387 
3388 	if(optional_string("$Expl Splits Ship:")){
3389 		stuff_boolean(&sip->explosion_splits_ship);
3390 	}
3391 	else if (first_time) {
3392 		sip->explosion_splits_ship = sip->explosion_propagates == 1;
3393 	}
3394 
3395 	if(optional_string("$Propagating Expl Radius Multiplier:")){
3396 		stuff_float(&sip->prop_exp_rad_mult);
3397 		if(sip->prop_exp_rad_mult <= 0) {
3398 			// on invalid value return to default setting
3399 			Warning(LOCATION, "Propagating explosion radius multiplier was set to non-positive value.\nDefaulting multiplier to 1.0 on %s '%s'.\n", info_type_name, sip->name);
3400 			sip->prop_exp_rad_mult = 1.0f;
3401 		}
3402 	}
3403 
3404 	if(optional_string("$Expl Visual Rad:")){
3405 		stuff_float(&sip->big_exp_visual_rad);
3406 	}
3407 
3408 	if(optional_string("$Base Death-Roll Time:")){
3409 		stuff_int(&sip->death_roll_base_time);
3410 		if (sip->death_roll_base_time < 2)
3411 			sip->death_roll_base_time = 2;
3412 	}
3413 
3414 	if(optional_string("$Death-Roll Explosion Radius Mult:")){
3415 		stuff_float(&sip->death_roll_r_mult);
3416 		if (sip->death_roll_r_mult < 0)
3417 			sip->death_roll_r_mult = 0;
3418 	}
3419 
3420 	if(optional_string("$Death-Roll Explosion Intensity Mult:")){
3421 		stuff_float(&sip->death_roll_time_mult);
3422 		if (sip->death_roll_time_mult <= 0)
3423 			sip->death_roll_time_mult = 1.0f;
3424 	}
3425 
3426 	if(optional_string("$Death FX Explosion Radius Mult:")){
3427 		stuff_float(&sip->death_fx_r_mult);
3428 		if (sip->death_fx_r_mult < 0)
3429 			sip->death_fx_r_mult = 0;
3430 	}
3431 
3432 	if(optional_string("$Death FX Explosion Count:")){
3433 		stuff_int(&sip->death_fx_count);
3434 		if (sip->death_fx_count < 0)
3435 			sip->death_fx_count = 0;
3436 	}
3437 
3438 	if (optional_string("$Death Roll Rotation Multiplier:")) {
3439 		stuff_float(&sip->death_roll_rotation_mult);
3440 	}
3441 
3442 	if(optional_string("$Death Roll X rotation Cap:")){
3443 		stuff_float(&sip->death_roll_xrotation_cap);
3444 		if (sip->death_roll_xrotation_cap < 0.0)
3445 			sip->death_roll_xrotation_cap = 0.0;
3446 	}
3447 
3448 	if(optional_string("$Death Roll Y rotation Cap:")){
3449 		stuff_float(&sip->death_roll_yrotation_cap);
3450 		if (sip->death_roll_yrotation_cap < 0.0)
3451 			sip->death_roll_yrotation_cap = 0.0;
3452 	}
3453 
3454 	if(optional_string("$Death Roll Z rotation Cap:")){
3455 		stuff_float(&sip->death_roll_zrotation_cap);
3456 		if (sip->death_roll_zrotation_cap < 0.0)
3457 			sip->death_roll_zrotation_cap = 0.0;
3458 	}
3459 
3460 	if(optional_string("$Ship Splitting Particles:"))
3461 	{
3462 		parse_ship_particle_effect(sip, &sip->split_particles, "ship split spew");
3463 	}
3464 
3465 	if (optional_string("$Ship Death Effect:"))
3466 	{
3467 		sip->death_effect = particle::util::parseEffect(sip->name);
3468 	}
3469 	else if(optional_string("$Ship Death Particles:"))
3470 	{
3471 		parse_ship_particle_effect(sip, &sip->regular_end_particles, "normal death spew");
3472 	}
3473 
3474 	if(optional_string("$Alternate Death Particles:"))
3475 	{
3476 		parse_ship_particle_effect(sip, &sip->knossos_end_particles, "knossos death spew");
3477 	}
3478 
3479 	if(optional_string("$Vaporize Percent Chance:")){
3480 		stuff_float(&sip->vaporize_chance);
3481 		if (sip->vaporize_chance < 0.0f || sip->vaporize_chance > 100.0f) {
3482 			sip->vaporize_chance = 0.0f;
3483 			Warning(LOCATION, "$Vaporize Percent Chance should be between 0 and 100.0 (read %f). Setting to 0.", sip->vaporize_chance);
3484 		}
3485 		//Percent is nice for modders, but here in the code we want it betwwen 0 and 1.0
3486 		sip->vaporize_chance /= 100.0;
3487 	}
3488 
3489 	if(optional_string("$Shockwave Damage Type:")) {
3490 		stuff_string(buf, F_NAME, SHIP_MULTITEXT_LENGTH);
3491 		sci->damage_type_idx_sav = damage_type_add(buf);
3492 		sci->damage_type_idx = sci->damage_type_idx_sav;
3493 	}
3494 
3495 	if(optional_string("$Shockwave Speed:")){
3496 		stuff_float( &sci->speed );
3497 	}
3498 
3499 	if(optional_string("$Shockwave Count:")){
3500 		stuff_int(&sip->shockwave_count);
3501 	}
3502 
3503 	if(optional_string("$Shockwave model:")){
3504 		stuff_string( sci->pof_name, F_NAME, MAX_FILENAME_LEN);
3505 	}
3506 
3507 	if(optional_string("$Shockwave name:")) {
3508 		stuff_string( sci->name, F_NAME, NAME_LENGTH);
3509 	}
3510 
3511 	if(optional_string("$Explosion Animations:")){
3512 		int temp[MAX_FIREBALL_TYPES];
3513 		auto parsed_ints = stuff_int_list(temp, MAX_FIREBALL_TYPES, RAW_INTEGER_TYPE);
3514 		sip->explosion_bitmap_anims.clear();
3515 		sip->explosion_bitmap_anims.insert(sip->explosion_bitmap_anims.begin(), temp, temp+parsed_ints);
3516 	}
3517 
3518 	if (optional_string("$Weapon Model Draw Distance:")) {
3519 		stuff_float( &sip->weapon_model_draw_distance );
3520 	}
3521 
3522 	// Set the weapons filter used in weapons loadout (for primary weapons)
3523 	parse_allowed_weapons(sip, true, false, first_time);
3524 	parse_allowed_weapons(sip, true, true, first_time);
3525 
3526 	// Get primary bank weapons
3527 	parse_weapon_bank(sip, true, &sip->num_primary_banks, sip->primary_bank_weapons, sip->primary_bank_ammo_capacity);
3528 
3529 	if(optional_string("$Show Primary Models:"))
3530 	{
3531 		sip->flags.set(Ship::Info_Flags::Draw_weapon_models);
3532 		stuff_bool_list(sip->draw_primary_models, sip->num_primary_banks);
3533 	}
3534 
3535 	// Set the weapons filter used in weapons loadout (for secondary weapons)
3536 	parse_allowed_weapons(sip, false, false, first_time);
3537 	parse_allowed_weapons(sip, false, true, first_time);
3538 
3539 	// Get secondary bank weapons
3540 	parse_weapon_bank(sip, false, &sip->num_secondary_banks, sip->secondary_bank_weapons, sip->secondary_bank_ammo_capacity);
3541 
3542 	if(optional_string("$Show Secondary Models:"))
3543 	{
3544 		sip->flags.set(Ship::Info_Flags::Draw_weapon_models);
3545 		stuff_bool_list(sip->draw_secondary_models, sip->num_secondary_banks);
3546 	}
3547 
3548 	if (optional_string("$Ship Recoil Modifier:")){
3549 		stuff_float(&sip->ship_recoil_modifier);
3550 	}
3551 
3552 	if(optional_string("$Shields:")) {
3553 		stuff_float(&sip->max_shield_strength);
3554 
3555 		if(optional_string("+Auto Spread:")) {
3556 			stuff_float(&sip->auto_shield_spread);
3557 		}
3558 		if(optional_string("+Minimum Weapon Span:")) {
3559 			stuff_float(&sip->auto_shield_spread_min_span);
3560 		}
3561 		if(optional_string("+Allow Bypass:")) {
3562 			stuff_boolean(&sip->auto_shield_spread_bypass);
3563 		}
3564 		if(optional_string("+Spread From LOD:")) {
3565 			int temp;
3566 			stuff_int(&temp);
3567 
3568 			if (temp > sip->num_detail_levels)
3569 				Warning(LOCATION, "+Spread From LOD for %s was %i whereas ship only has %i detail levels, ignoring...", sip->name, temp, sip->num_detail_levels);
3570 			else
3571 				sip->auto_shield_spread_from_lod = temp;
3572 		}
3573 	}
3574 
3575 	if(optional_string("$Model Point Shield Controls:")) {
3576 		SCP_vector<SCP_string> ctrl_strings;
3577 		stuff_string_list(ctrl_strings);
3578 
3579 		// Init all to -1 in case some aren't supplied...
3580 		sip->shield_point_augment_ctrls[FRONT_QUAD] = -1;
3581 		sip->shield_point_augment_ctrls[REAR_QUAD] = -1;
3582 		sip->shield_point_augment_ctrls[LEFT_QUAD] = -1;
3583 		sip->shield_point_augment_ctrls[RIGHT_QUAD] = -1;
3584 
3585 		for (int i = 0; i < (int)ctrl_strings.size(); i++) {
3586 			const char *str = ctrl_strings[i].c_str();
3587 
3588 			if (!stricmp(str, "front"))
3589 				sip->shield_point_augment_ctrls[FRONT_QUAD] = i;
3590 			else if (!stricmp(str, "rear"))
3591 				sip->shield_point_augment_ctrls[REAR_QUAD] = i;
3592 			else if (!stricmp(str, "left"))
3593 				sip->shield_point_augment_ctrls[LEFT_QUAD] = i;
3594 			else if (!stricmp(str, "right"))
3595 				sip->shield_point_augment_ctrls[RIGHT_QUAD] = i;
3596 			else if (!stricmp(str, "none"))
3597 				;
3598 			else
3599 				Warning(LOCATION, "Unrecognized value \"%s\" passed to $Model Point Shield Controls, ignoring...", str);
3600 		}
3601 	}
3602 
3603 	// optional shield color
3604 	if(optional_string("$Shield Color:")){
3605 		stuff_ubyte(&sip->shield_color[0]);
3606 		stuff_ubyte(&sip->shield_color[1]);
3607 		stuff_ubyte(&sip->shield_color[2]);
3608 	}
3609 
3610 	if(optional_string("$Shield Impact Explosion:")) {
3611 		char fname[MAX_NAME_LEN];
3612 		stuff_string(fname, F_NAME, NAME_LENGTH);
3613 
3614 		if ( VALID_FNAME(fname) )
3615 			sip->shield_impact_explosion_anim = Weapon_explosions.Load(fname);
3616 	}
3617 
3618 	if(optional_string("$Max Shield Recharge:")){
3619 		stuff_float(&sip->max_shield_recharge);
3620 		CLAMP(sip->max_shield_recharge, 0.0f, 1.0f);
3621 	}
3622 
3623 	// The next five fields are used for the ETS
3624 	if (optional_string("$Power Output:"))
3625 		stuff_float(&sip->power_output);
3626 
3627 	// Goober5000
3628 	if (optional_string("$Shield Regeneration Rate:"))
3629 		stuff_float(&sip->max_shield_regen_per_second);
3630 	else if (first_time)
3631 		sip->max_shield_regen_per_second = 0.02f;
3632 
3633 	if (optional_string("+Shield Regen Hit Delay:")) {
3634 		stuff_float(&sip->shield_regen_hit_delay);
3635 		if (sip->shield_regen_hit_delay < 0.0f) {
3636 			Warning(LOCATION, "Shield Regen Hit Delay on ship '%s' cannot be less than 0.\n", sip->name);
3637 			sip->shield_regen_hit_delay = 0.0f;
3638 		}
3639 	}
3640 
3641 	// Support ship hull shield rate - if allowed
3642 	if(optional_string("$Support Shield Repair Rate:"))
3643 	{
3644 		stuff_float(&sip->sup_shield_repair_rate);
3645 		sip->sup_shield_repair_rate *= 0.01f;
3646 		CLAMP(sip->sup_shield_repair_rate, 0.0f, 1.0f);
3647 	}
3648 
3649 	// Goober5000
3650 	if (optional_string("$Weapon Regeneration Rate:"))
3651 		stuff_float(&sip->max_weapon_regen_per_second);
3652 	else if (first_time)
3653 		sip->max_weapon_regen_per_second = 0.04f;
3654 
3655 	if (optional_string("$Shield to Weapon Transfer Quantity:"))
3656 		stuff_float(&sip->shield_weap_amount);
3657 	else if (first_time)
3658 		sip->shield_weap_amount = 0.2f;
3659 
3660 	if (optional_string("$Shield to Weapon Transfer Efficiency:"))
3661 		stuff_float(&sip->shield_weap_efficiency);
3662 	else if (first_time)
3663 		sip->shield_weap_efficiency = 1.0f;
3664 
3665 	if (optional_string("$Shield to Weapon Transfer Speed:"))
3666 		stuff_float(&sip->shield_weap_speed);
3667 	else if (first_time)
3668 		sip->shield_weap_speed = 0.04f;
3669 
3670 	if (optional_string("$Weapon to Shield Transfer Quantity:"))
3671 		stuff_float(&sip->weap_shield_amount);
3672 	else if (first_time)
3673 		sip->weap_shield_amount = 0.1f;
3674 
3675 	if (optional_string("$Weapon to Shield Transfer Efficiency:"))
3676 		stuff_float(&sip->weap_shield_efficiency);
3677 	else if (first_time)
3678 		sip->weap_shield_efficiency = 1.0f;
3679 
3680 	if (optional_string("$Weapon to Shield Transfer Speed:"))
3681 		stuff_float(&sip->weap_shield_speed);
3682 	else if (first_time)
3683 		sip->weap_shield_speed = 0.04f;
3684 
3685 	if (optional_string("$Max Oclk Speed:") || optional_string("$Max Overclock Speed:"))
3686 		stuff_float(&sip->max_overclocked_speed);
3687 	else if (first_time)
3688 		sip->max_overclocked_speed = sip->max_vel.xyz.z * 1.5f;
3689 
3690 	if (optional_string("$Max Weapon Eng:") || optional_string("$Max Weapon Energy:"))
3691 		stuff_float(&sip->max_weapon_reserve);
3692 
3693 	if(optional_string("$Hitpoints:"))
3694 	{
3695 		stuff_float(&sip->max_hull_strength);
3696 		if (sip->max_hull_strength < 0.0f)
3697 		{
3698 			Warning(LOCATION, "Max hull strength on %s '%s' cannot be less than 0.  Defaulting to 100.\n", info_type_name, sip->name);
3699 			sip->max_hull_strength = 100.0f;
3700 		}
3701 	}
3702 
3703 	//Hull rep rate
3704 
3705 	if(optional_string("$Hull Repair Rate:"))
3706 	{
3707 		stuff_float(&sip->hull_repair_rate);
3708 		sip->hull_repair_rate *= 0.01f;
3709 
3710 		//Sanity checking
3711 		if(sip->hull_repair_rate > 1.0f)
3712 			sip->hull_repair_rate = 1.0f;
3713 		else if(sip->hull_repair_rate < -1.0f)
3714 			sip->hull_repair_rate = -1.0f;
3715 	}
3716 
3717 	// Support ship hull repair rate - if allowed
3718 	if(optional_string("$Support Hull Repair Rate:"))
3719 	{
3720 		stuff_float(&sip->sup_hull_repair_rate);
3721 		sip->sup_hull_repair_rate *= 0.01f;
3722 		CLAMP(sip->sup_hull_repair_rate, 0.0f, 1.0f);
3723 	}
3724 
3725 	//Subsys rep rate
3726 	if(optional_string("$Subsystem Repair Rate:"))
3727 	{
3728 		stuff_float(&sip->subsys_repair_rate);
3729 		sip->subsys_repair_rate *= 0.01f;
3730 
3731 		//Sanity checking
3732 		if(sip->subsys_repair_rate > 1.0f)
3733 			sip->subsys_repair_rate = 1.0f;
3734 		else if(sip->subsys_repair_rate < -1.0f)
3735 			sip->subsys_repair_rate = -1.0f;
3736 	}
3737 
3738 	// Support ship hull repair rate
3739 	if(optional_string("$Support Subsystem Repair Rate:"))
3740 	{
3741 		stuff_float(&sip->sup_subsys_repair_rate);
3742 		sip->sup_subsys_repair_rate *= 0.01f;
3743 		CLAMP(sip->sup_subsys_repair_rate, 0.0f, 1.0f);
3744 	}
3745 
3746 	if(optional_string("$Armor Type:"))
3747 	{
3748 		stuff_string(buf, F_NAME, SHIP_MULTITEXT_LENGTH);
3749 		sip->armor_type_idx = armor_type_get_idx(buf);
3750 
3751 		if(sip->armor_type_idx == -1)
3752 			Warning(LOCATION,"Invalid armor name %s specified for hull in %s '%s'", buf, info_type_name, sip->name);
3753 	}
3754 
3755 	if(optional_string("$Shield Armor Type:"))
3756 	{
3757 		stuff_string(buf, F_NAME, SHIP_MULTITEXT_LENGTH);
3758 		sip->shield_armor_type_idx = armor_type_get_idx(buf);
3759 
3760 		if(sip->shield_armor_type_idx == -1)
3761 			Warning(LOCATION,"Invalid armor name %s specified for shield in %s '%s'", buf, info_type_name, sip->name);
3762 	}
3763 
3764 	if (optional_string("$Flags:"))
3765 	{
3766 		// we'll assume the list will contain no more than 20 distinct tokens
3767 		char ship_strings[20][NAME_LENGTH];
3768 		int num_strings = (int)stuff_string_list(ship_strings, 20);
3769 
3770 		int ship_type_index = -1;
3771 
3772 		if (!optional_string("+noreplace")) {
3773 			// clear flags since we might have a modular table
3774 			// clear only those which are actually set in the flags
3775 			bool has_afterburner = sip->flags[Ship::Info_Flags::Afterburner];
3776 			bool draw_weapon_models = sip->flags[Ship::Info_Flags::Draw_weapon_models];
3777 			bool has_display_name = sip->flags[Ship::Info_Flags::Has_display_name];
3778 			sip->flags.reset();
3779 			sip->flags.set(Ship::Info_Flags::Afterburner, has_afterburner);
3780 			sip->flags.set(Ship::Info_Flags::Draw_weapon_models, draw_weapon_models);
3781 			sip->flags.set(Ship::Info_Flags::Has_display_name, has_display_name);
3782 		}
3783 
3784 		for (auto i = 0; i < num_strings; i++)
3785 		{
3786 			// get ship type from ship flags
3787 			const char *ship_type = ship_strings[i];
3788 			bool flag_found = false;
3789 
3790 			// look it up in the object types table
3791 			ship_type_index = ship_type_name_lookup(ship_type);
3792 
3793 			// set ship class type
3794 			if ((ship_type_index >= 0) && (sip->class_type < 0))
3795 				sip->class_type = ship_type_index;
3796 
3797 			// check various ship flags
3798 			for (size_t idx = 0; idx < Num_ship_flags; idx++) {
3799 				if ( !stricmp(Ship_flags[idx].name, ship_strings[i]) ) {
3800 					flag_found = true;
3801 
3802 					if (!Ship_flags[idx].in_use)
3803 						Warning(LOCATION, "Use of '%s' flag for %s '%s' - this flag is no longer needed.", Ship_flags[idx].name, info_type_name, sip->name);
3804 					else
3805 						sip->flags.set(Ship_flags[idx].def);
3806 
3807 					break;
3808 				}
3809 			}
3810 
3811 			// catch typos or deprecations
3812 			if (!stricmp(ship_strings[i], "no-collide") || !stricmp(ship_strings[i], "no_collide")) {
3813 				flag_found = true;
3814 				sip->flags.set(Ship::Info_Flags::No_collide);
3815 			}
3816 			if (!stricmp(ship_strings[i], "dont collide invisible")) {
3817 				flag_found = true;
3818 				sip->flags.set(Ship::Info_Flags::Ship_class_dont_collide_invis);
3819 			}
3820 			if (!stricmp(ship_strings[i], "dont bank when turning")) {
3821 				flag_found = true;
3822 				sip->flags.set(Ship::Info_Flags::Dont_bank_when_turning);
3823 			}
3824 			if (!stricmp(ship_strings[i], "dont clamp max velocity")) {
3825 				flag_found = true;
3826 				sip->flags.set(Ship::Info_Flags::Dont_clamp_max_velocity);
3827 			}
3828 
3829 			if ( !flag_found && (ship_type_index < 0) )
3830 				Warning(LOCATION, "Bogus string in ship flags: %s\n", ship_strings[i]);
3831 		}
3832 
3833 		// set original status of tech database flags - Goober5000
3834 		if (sip->flags[Info_Flags::In_tech_database])
3835 			sip->flags.set(Info_Flags::Default_in_tech_database);
3836 		if (sip->flags[Info_Flags::In_tech_database_m])
3837 			sip->flags.set(Info_Flags::Default_in_tech_database_m);
3838 	}
3839 
3840 	// Goober5000 - ensure number of banks checks out
3841 	if (sip->num_primary_banks > MAX_SHIP_PRIMARY_BANKS)
3842 	{
3843 		Error(LOCATION, "%s '%s' has too many primary banks (%d).  Maximum for ships is currently %d.\n", info_type_name, sip->name, sip->num_primary_banks, MAX_SHIP_PRIMARY_BANKS);
3844 	}
3845 
3846 	sip->allowed_weapons.clear();
3847 
3848 	// copy to regular allowed_weapons array
3849 	if (!sip->allowed_bank_restricted_weapons.empty())
3850 	{
3851 		for (auto i = 0; i < MAX_SHIP_WEAPONS; i++)
3852 		{
3853 			for (auto &wf : sip->allowed_bank_restricted_weapons[i].weapon_and_flags)
3854 			{
3855 				if (wf.second & REGULAR_WEAPON)
3856 					sip->allowed_weapons.set_flag(wf.first, REGULAR_WEAPON);
3857 
3858 				if (wf.second & DOGFIGHT_WEAPON)
3859 					sip->allowed_weapons.set_flag(wf.first, DOGFIGHT_WEAPON);
3860 			}
3861 		}
3862 	}
3863 
3864 	find_and_stuff_optional("$AI Class:", &sip->ai_class, F_NAME, Ai_class_names, Num_ai_classes, "AI class names");
3865 
3866 	// Get Afterburner information
3867 	// Be aware that if $Afterburner is not 1, the other Afterburner fields are not read in
3868 	int has_afterburner = 0;
3869 
3870 	if(optional_string("$Afterburner:"))
3871 		stuff_boolean(&has_afterburner);
3872 
3873 	if ( has_afterburner == 1 )
3874 	{
3875 		sip->flags.set(Ship::Info_Flags::Afterburner);
3876 
3877 		if(optional_string("+Aburn Max Vel:")) {
3878 			stuff_vec3d(&sip->afterburner_max_vel);
3879 		}
3880 
3881 		if(optional_string("+Aburn For accel:")) {
3882 			stuff_float(&sip->afterburner_forward_accel);
3883 		}
3884 
3885 		// SparK: added reverse burner capability
3886 		if(optional_string("+Aburn Max Reverse Vel:")) {
3887 			stuff_float(&sip->afterburner_max_reverse_vel);
3888 		}
3889 		if(optional_string("+Aburn Rev accel:")) {
3890 			stuff_float(&sip->afterburner_reverse_accel);
3891 		}
3892 
3893 		if(optional_string("+Aburn Fuel:")) {
3894 			stuff_float(&sip->afterburner_fuel_capacity);
3895 		}
3896 
3897 		if(optional_string("+Aburn Burn Rate:")) {
3898 			stuff_float(&sip->afterburner_burn_rate);
3899 		}
3900 
3901 		if(optional_string("+Aburn Rec Rate:")) {
3902 			stuff_float(&sip->afterburner_recover_rate);
3903 		}
3904 
3905 		if (optional_string("+Aburn Minimum Start Fuel:")) {
3906 			stuff_float(&sip->afterburner_min_start_fuel);
3907 		}
3908 
3909 		if (optional_string("+Aburn Minimum Fuel to Burn:")) {
3910 			stuff_float(&sip->afterburner_min_fuel_to_burn);
3911 		}
3912 
3913 		if (optional_string("+Aburn Cooldown Time:")) {
3914 			stuff_float(&sip->afterburner_cooldown_time);
3915 		}
3916 
3917 		if (!(sip->afterburner_fuel_capacity) ) {
3918 			Warning(LOCATION, "%s '%s' has an afterburner but has no afterburner fuel. Setting fuel to 1", info_type_name, sip->name);
3919 			sip->afterburner_fuel_capacity = 1.0f;
3920 		}
3921 	}
3922 
3923 	if ( optional_string("$Trails:") ) {
3924 		bool trails_warning = true;
3925 
3926 		if (optional_string("+Bitmap:") ) {
3927 			trails_warning = false;
3928 			generic_bitmap_init(&sip->afterburner_trail, NULL);
3929 			stuff_string(sip->afterburner_trail.filename, F_NAME, MAX_FILENAME_LEN);
3930 		}
3931 
3932 		if (optional_string("+Bitmap Stretch:")) {
3933 			trails_warning = false;
3934 			stuff_float(&sip->afterburner_trail_tex_stretch);
3935 			if (sip->afterburner_trail_tex_stretch == 0.0f) {
3936 				Warning(LOCATION, "Trail bitmap stretch of ship %s cannot be 0.  Setting to 1.\n", sip->name);
3937 				sip->afterburner_trail_tex_stretch = 1.0f;
3938 			}
3939 		}
3940 
3941 		if ( optional_string("+Width:") ) {
3942 			trails_warning = false;
3943 			stuff_float(&sip->afterburner_trail_width_factor);
3944 		}
3945 
3946 		if ( optional_string("+Alpha:") ) {
3947 			trails_warning = false;
3948 			stuff_float(&sip->afterburner_trail_alpha_factor);
3949 		}
3950 
3951 		if (optional_string("+Alpha End:")) {
3952 			trails_warning = false;
3953 			stuff_float(&sip->afterburner_trail_alpha_end_factor);
3954 		}
3955 
3956 		if (optional_string("+Alpha Decay Exponent:")) {
3957 			trails_warning = false;
3958 			stuff_float(&sip->afterburner_trail_alpha_decay_exponent);
3959 			if (sip->afterburner_trail_alpha_decay_exponent < 0.0f) {
3960 				Warning(LOCATION, "Trail Alpha Decay Exponent of ship %s cannot be negative. Resetting to 1.\n", sip->name);
3961 				sip->afterburner_trail_alpha_decay_exponent = 1.0f;
3962 			}
3963 		}
3964 
3965 		if ( optional_string("+Life:") ) {
3966 			trails_warning = false;
3967 			stuff_float(&sip->afterburner_trail_life);
3968 		}
3969 
3970 		if (optional_string("+Spread:")) {
3971 			trails_warning = false;
3972 			stuff_float(&sip->afterburner_trail_spread);
3973 		}
3974 
3975 		if ( optional_string("+Faded Out Sections:") ) {
3976 			trails_warning = false;
3977 			stuff_int(&sip->afterburner_trail_faded_out_sections);
3978 		}
3979 
3980 		if (trails_warning)
3981 			Warning(LOCATION, "%s %s entry has $Trails field specified, but no properties given.", info_type_name, sip->name);
3982 	}
3983 
3984 	if (optional_string("$Countermeasure type:")) {
3985 		stuff_string(buf, F_NAME, SHIP_MULTITEXT_LENGTH);
3986 		int res = weapon_info_lookup(buf);
3987 		if (res < 0) {
3988 			Warning(LOCATION, "Could not find weapon type '%s' to use as countermeasure on %s '%s'", buf, info_type_name, sip->name);
3989 		} else if (Weapon_info[res].wi_flags[Weapon::Info_Flags::Beam]) {
3990 			Warning(LOCATION, "Attempt made to set a beam weapon as a countermeasure on %s '%s'", info_type_name, sip->name);
3991 		} else {
3992 			sip->cmeasure_type = res;
3993 		}
3994 	} else if (first_time && Species_info[sip->species].cmeasure_index >= 0) {
3995 		sip->cmeasure_type = Species_info[sip->species].cmeasure_index;
3996 	}
3997 
3998 	if(optional_string("$Countermeasures:"))
3999 		stuff_int(&sip->cmeasure_max);
4000 
4001 	if(optional_string("$Scan time:"))
4002 		stuff_int(&sip->scan_time);
4003 
4004 	if(optional_string("$Scan range Normal:"))
4005 		stuff_float(&sip->scan_range_normal);
4006 
4007 	if(optional_string("$Scan range Capital:"))
4008 		stuff_float(&sip->scan_range_capital);
4009 
4010 	if (optional_string("$Ask Help Shield Percent:")) {
4011 		float help_shield_val;
4012 		stuff_float(&help_shield_val);
4013 		if (help_shield_val > 0.0f && help_shield_val <= 1.0f) {
4014 			sip->ask_help_shield_percent = help_shield_val;
4015 		} else {
4016 			error_display(0,"Ask Help Shield Percent for ship class %s is %f. This value is not within range of 0-1.0."
4017 			              "Assuming default value of %f.", sip->name, help_shield_val, DEFAULT_ASK_HELP_SHIELD_PERCENT);
4018 		}
4019 	}
4020 
4021 	if (optional_string("$Ask Help Hull Percent:")) {
4022 		float help_hull_val;
4023 		stuff_float(&help_hull_val);
4024 		if (help_hull_val > 0.0f && help_hull_val <= 1.0f) {
4025 			sip->ask_help_shield_percent = help_hull_val;
4026 		} else {
4027 			error_display(0,"Ask Help Hull Percent for ship class %s is %f. This value is not within range of 0-1.0."
4028 			              "Assuming default value of %f.", sip->name, help_hull_val, DEFAULT_ASK_HELP_HULL_PERCENT);
4029 		}
4030 	}
4031 
4032 	//Parse the engine sound
4033 	parse_game_sound("$EngineSnd:", &sip->engine_snd);
4034 
4035 	if(optional_string("$Minimum Engine Volume:"))
4036 		stuff_float(&sip->min_engine_vol);
4037 
4038 	//Parse optional sound to be used for beginning of a glide
4039 	parse_game_sound("$GlideStartSnd:", &sip->glide_start_snd);
4040 
4041 	//Parse optional sound to be used for end of a glide
4042 	parse_game_sound("$GlideEndSnd:", &sip->glide_end_snd);
4043 
4044 	// Parse optional sound to be used for bfly-by sound
4045 	parse_game_sound("$Flyby Sound:", &sip->flyby_snd);
4046 
4047 	parse_ship_sounds(sip);
4048 
4049 	if(optional_string("$Closeup_pos:"))
4050 	{
4051 		stuff_vec3d(&sip->closeup_pos);
4052 	}
4053 	else if (first_time && strlen(sip->pof_file))
4054 	{
4055 		//Calculate from the model file. This is inefficient, but whatever
4056 		int model_idx = model_load(sip->pof_file, 0, NULL);
4057 		polymodel *pm = model_get(model_idx);
4058 
4059 		//Go through, find best
4060 		sip->closeup_pos.xyz.z = fabsf(pm->maxs.xyz.z);
4061 
4062 		float temp = fabsf(pm->mins.xyz.z);
4063 		if(temp > sip->closeup_pos.xyz.z)
4064 			sip->closeup_pos.xyz.z = temp;
4065 
4066 		//Now multiply by 2
4067 		sip->closeup_pos.xyz.z *= -2.0f;
4068 
4069 		//We're done with the model.
4070 		model_unload(model_idx);
4071 	}
4072 
4073 	if (optional_string("$Closeup_zoom:")) {
4074 		stuff_float(&sip->closeup_zoom);
4075 
4076 		if (sip->closeup_zoom <= 0.0f) {
4077 			mprintf(("Warning!  Ship '%s' has a $Closeup_zoom value that is less than or equal to 0 (%f). Setting to default value.\n", sip->name, sip->closeup_zoom));
4078 			sip->closeup_zoom = 0.5f;
4079 		}
4080 	}
4081 
4082 	if(optional_string("$Closeup_pos_targetbox:"))
4083 	{
4084 		stuff_vec3d(&sip->closeup_pos_targetbox);
4085 	}
4086 	else if (first_time)
4087 	{
4088 		sip->closeup_pos_targetbox = sip->closeup_pos;
4089 	}
4090 
4091 	if (optional_string("$Closeup_zoom_targetbox:")) {
4092 		stuff_float(&sip->closeup_zoom_targetbox);
4093 
4094 		if (sip->closeup_zoom_targetbox <= 0.0f) {
4095 			mprintf(("Warning!  Ship '%s' has a $Closeup_zoom_targetbox value that is less than or equal to 0 (%f). Setting to default value.\n", sip->name, sip->closeup_zoom_targetbox));
4096 			sip->closeup_zoom_targetbox = 0.5f;
4097 		}
4098 	}
4099 	else if (first_time)
4100 	{
4101 		sip->closeup_zoom_targetbox = sip->closeup_zoom;
4102 	}
4103 
4104 	if(optional_string("$Topdown offset:")) {
4105 		sip->topdown_offset_def = true;
4106 		stuff_vec3d(&sip->topdown_offset);
4107 	}
4108 
4109 	if (optional_string("$Chase View Offset:"))	{
4110 		stuff_vec3d(&sip->chase_view_offset);
4111 	}
4112 
4113 	if (optional_string("$Chase View Rigidity:")) {
4114 		stuff_float(&sip->chase_view_rigidity);
4115 		if (sip->chase_view_rigidity <= 0) {
4116 			Warning(LOCATION, "Ship \'%s\' must have a Chase View Rigidity greater than 0.", sip->name);
4117 			sip->chase_view_rigidity = 5.0f;
4118 		}
4119 	}
4120 
4121 	if (optional_string("$Shield_icon:")) {
4122 		stuff_string(name_tmp, F_NAME, sizeof(name_tmp));
4123 		hud_shield_assign_info(sip, name_tmp);
4124 	}
4125 
4126 	// read in filename for icon that is used in ship selection
4127 	if ( optional_string("$Ship_icon:") ) {
4128 		stuff_string(sip->icon_filename, F_NAME, MAX_FILENAME_LEN);
4129 	}
4130 
4131 	if ( optional_string("$Model Icon Direction:") ) {
4132 		char str[NAME_LENGTH];
4133 		stuff_string(str, F_NAME, NAME_LENGTH);
4134 
4135 		angles model_icon_angles = vmd_zero_angles;
4136 
4137 		if (!stricmp(str, "top")) {
4138 			model_icon_angles.p = -PI_2;
4139 		} else if (!stricmp(str, "bottom")) {
4140 			model_icon_angles.p = -PI_2;
4141 			model_icon_angles.b = 2 * PI_2;
4142 		} else if (!stricmp(str, "front")) {
4143 			model_icon_angles.h = 2 * PI_2;
4144 		} else if (!stricmp(str, "back")) {
4145 			model_icon_angles.h = 4 * PI_2;
4146 		} else if (!stricmp(str, "left")) {
4147 			model_icon_angles.h = -PI_2;
4148 		} else if (!stricmp(str, "right")) {
4149 			model_icon_angles.h = PI_2;
4150 		} else {
4151 			Warning(LOCATION, "Unrecognized value \"%s\" passed to $Model Icon Direction, ignoring...", str);
4152 		}
4153 
4154 		sip->model_icon_angles = model_icon_angles;
4155 	}
4156 
4157 	// read in filename for animation that is used in ship selection
4158 	if ( optional_string("$Ship_anim:") ) {
4159 		stuff_string(sip->anim_filename, F_NAME, MAX_FILENAME_LEN);
4160 	}
4161 
4162 	// read in filename for animation that is used in ship selection
4163 	if ( optional_string("$Ship_overhead:") ) {
4164 		stuff_string(sip->overhead_filename, F_NAME, MAX_FILENAME_LEN);
4165 	}
4166 
4167 	// read in briefing stuff
4168 	if ( optional_string("$Briefing icon:") )
4169 		sip->bii_index_ship = parse_and_add_briefing_icon_info();
4170 	if ( optional_string("$Briefing icon with cargo:") )
4171 		sip->bii_index_ship_with_cargo = parse_and_add_briefing_icon_info();
4172 	if ( optional_string("$Briefing wing icon:") )
4173 		sip->bii_index_wing = parse_and_add_briefing_icon_info();
4174 	if ( optional_string("$Briefing wing icon with cargo:") )
4175 		sip->bii_index_wing_with_cargo = parse_and_add_briefing_icon_info();
4176 
4177 	// check for inconsistencies
4178 	if ((sip->bii_index_wing_with_cargo >= 0) && (sip->bii_index_wing < 0 || sip->bii_index_ship_with_cargo < 0))
4179 		Warning(LOCATION, "%s '%s' has a wing-with-cargo briefing icon but is missing a wing briefing icon or a ship-with-cargo briefing icon!", info_type_name, sip->name);
4180 	if ((sip->bii_index_wing_with_cargo < 0) && (sip->bii_index_wing >= 0) && (sip->bii_index_ship_with_cargo >= 0))
4181 		Warning(LOCATION, "%s '%s' has both a wing briefing icon and a ship-with-cargo briefing icon but does not have a wing-with-cargo briefing icon!", info_type_name, sip->name);
4182 
4183 	if ( optional_string("$Score:") ){
4184 		stuff_int( &sip->score );
4185 	}
4186 
4187 	if (first_time)
4188 	{
4189 		species_info *species = &Species_info[sip->species];
4190 
4191 		sip->thruster_flame_info = species->thruster_info.flames;
4192 		sip->thruster_glow_info = species->thruster_info.glow;
4193 		sip->thruster_secondary_glow_info = species->thruster_secondary_glow_info;
4194 		sip->thruster_tertiary_glow_info = species->thruster_tertiary_glow_info;
4195 		sip->thruster_distortion_info = species->thruster_distortion_info;
4196 	}
4197 
4198 	if ( optional_string("$Thruster Normal Flame:") ) {
4199 		stuff_string( name_tmp, F_NAME, sizeof(name_tmp) );
4200 
4201 		if ( VALID_FNAME(name_tmp) )
4202 			generic_anim_init( &sip->thruster_flame_info.normal, name_tmp );
4203 	}
4204 
4205 	if ( optional_string("$Thruster Afterburner Flame:") ) {
4206 		stuff_string( name_tmp, F_NAME, sizeof(name_tmp) );
4207 
4208 		if ( VALID_FNAME(name_tmp) )
4209 			generic_anim_init( &sip->thruster_flame_info.afterburn, name_tmp );
4210 	}
4211 
4212 	if ( optional_string("$Thruster Bitmap 1:") ) {
4213 		stuff_string( name_tmp, F_NAME, sizeof(name_tmp) );
4214 
4215 		if ( VALID_FNAME(name_tmp) ) {
4216 			strcpy_s(sip->thruster_glow_info.normal.filename, name_tmp);
4217 			thruster_glow_anim_load( &sip->thruster_glow_info.normal );
4218 		}
4219 	}
4220 
4221 	if ( optional_string("$Thruster Bitmap 1a:") ) {
4222 		stuff_string( name_tmp, F_NAME, sizeof(name_tmp) );
4223 
4224 		if ( VALID_FNAME(name_tmp) ) {
4225 			strcpy_s(sip->thruster_glow_info.afterburn.filename, name_tmp);
4226 			thruster_glow_anim_load( &sip->thruster_glow_info.afterburn );
4227 		}
4228 	}
4229 
4230 	if ( optional_string("$Thruster01 Radius factor:") ) {
4231 		stuff_float(&sip->thruster01_glow_rad_factor);
4232 	}
4233 
4234 	if ( optional_string("$Thruster Bitmap 2:") ) {
4235 		stuff_string( name_tmp, F_NAME, sizeof(name_tmp) );
4236 
4237 		if ( VALID_FNAME(name_tmp) )
4238 			generic_bitmap_init( &sip->thruster_secondary_glow_info.normal, name_tmp );
4239 	}
4240 
4241 	if ( optional_string("$Thruster Bitmap 2a:") ) {
4242 		stuff_string( name_tmp, F_NAME, sizeof(name_tmp) );
4243 
4244 		if ( VALID_FNAME(name_tmp) )
4245 			generic_bitmap_init( &sip->thruster_secondary_glow_info.afterburn, name_tmp );
4246 	}
4247 
4248 	if ( optional_string("$Thruster02 Radius factor:") ) {
4249 		stuff_float(&sip->thruster02_glow_rad_factor);
4250 	}
4251 
4252 	if ( optional_string("$Thruster01 Length factor:") ) {
4253 		stuff_float(&sip->thruster02_glow_len_factor);
4254 		Warning(LOCATION, "Deprecated spelling: \"$Thruster01 Length factor:\".  Use \"$Thruster02 Length factor:\" instead.");
4255 	}
4256 
4257 	if ( optional_string("$Thruster02 Length factor:") ) {
4258 		stuff_float(&sip->thruster02_glow_len_factor);
4259 	}
4260 
4261 	if ( optional_string("$Thruster Bitmap 3:") ) {
4262 		stuff_string( name_tmp, F_NAME, sizeof(name_tmp) );
4263 
4264 		if ( VALID_FNAME(name_tmp) )
4265 			generic_bitmap_init( &sip->thruster_tertiary_glow_info.normal, name_tmp );
4266 	}
4267 
4268 	if ( optional_string("$Thruster Bitmap 3a:") ) {
4269 		stuff_string( name_tmp, F_NAME, sizeof(name_tmp) );
4270 
4271 		if ( VALID_FNAME(name_tmp) )
4272 			generic_bitmap_init( &sip->thruster_tertiary_glow_info.afterburn, name_tmp );
4273 	}
4274 
4275 	if ( optional_string("$Thruster03 Radius factor:") ) {
4276 		stuff_float(&sip->thruster03_glow_rad_factor);
4277 	}
4278 
4279 	// Valathil - Custom Thruster Distortion
4280 	if ( optional_string("$Thruster Bitmap Distortion:") ) {
4281 		stuff_string( name_tmp, F_NAME, sizeof(name_tmp) );
4282 
4283 		if ( VALID_FNAME(name_tmp) )
4284 			generic_bitmap_init( &sip->thruster_distortion_info.normal, name_tmp );
4285 	}
4286 
4287 	if ( optional_string("$Thruster Bitmap Distortion a:") ) {
4288 		stuff_string( name_tmp, F_NAME, sizeof(name_tmp) );
4289 
4290 		if ( VALID_FNAME(name_tmp) )
4291 			generic_bitmap_init( &sip->thruster_distortion_info.afterburn, name_tmp );
4292 	}
4293 
4294 	if ( optional_string("$Thruster Distortion Radius factor:") ) {
4295 		stuff_float(&sip->thruster_dist_rad_factor);
4296 	}
4297 
4298 	if ( optional_string("$Thruster Distortion Length factor:") ) {
4299 		stuff_float(&sip->thruster_dist_len_factor);
4300 	}
4301 
4302 	if ( optional_string("$Thruster Distortion:") ) {
4303 		stuff_boolean(&sip->draw_distortion);
4304 	}
4305 
4306 	if ( optional_string("$Thruster Glow Noise Mult:") ) {
4307 		stuff_float(&sip->thruster_glow_noise_mult);
4308 	}
4309 
4310 	while ( optional_string("$Thruster Particles:") ) {
4311 		bool afterburner = false;
4312 		thruster_particles tpart;
4313 
4314 		if ( optional_string("$Thruster Particle Bitmap:") )
4315 			afterburner = false;
4316 		else if ( optional_string("$Afterburner Particle Bitmap:") )
4317 			afterburner = true;
4318 		else
4319 			Error( LOCATION, "formatting error in the thruster's particle section for %s '%s'\n", info_type_name, sip->name );
4320 
4321 		generic_anim_init(&tpart.thruster_bitmap, NULL);
4322 		stuff_string(tpart.thruster_bitmap.filename, F_NAME, MAX_FILENAME_LEN);
4323 
4324 		required_string("$Min Radius:");
4325 		stuff_float(&tpart.min_rad);
4326 
4327 		required_string("$Max Radius:");
4328 		stuff_float(&tpart.max_rad);
4329 
4330 		required_string("$Min created:");
4331 		stuff_int(&tpart.n_low);
4332 
4333 		required_string("$Max created:");
4334 		stuff_int(&tpart.n_high);
4335 
4336 		required_string("$Variance:");
4337 		stuff_float(&tpart.variance);
4338 
4339 		if (afterburner) {
4340 			sip->afterburner_thruster_particles.push_back( tpart );
4341 		} else {
4342 			sip->normal_thruster_particles.push_back( tpart );
4343 		}
4344 	}
4345 
4346 	// if the ship is a stealth ship
4347 	if ( optional_string("$Stealth:") ) {
4348 		sip->flags.set(Ship::Info_Flags::Stealth);
4349 	}
4350 	else if ( optional_string("$Stealth") ) {
4351 		Warning(LOCATION, "%s '%s' is missing the colon after \"$Stealth\". Note that you may also use the ship flag \"stealth\".", info_type_name, sip->name);
4352 		sip->flags.set(Ship::Info_Flags::Stealth);
4353 	}
4354 
4355 	if ( optional_string("$max decals:") ){
4356 		int bogus;
4357 		stuff_int(&bogus);
4358 		WarningEx(LOCATION, "The decal system has been deactivated in FSO builds. Entries will be discarded.\n");
4359 		mprintf(("WARNING: The decal system has been deactivated in FSO builds. Entries will be discarded.\n"));
4360 		//Do nothing, left in for compatibility.
4361 	}
4362 
4363 	// parse contrail info
4364 	while ( optional_string("$Trail:") ) {
4365 		// setting '+ClearAll' resets the trails
4366 		if ( optional_string("+ClearAll")) {
4367 			memset(&sip->ct_info, 0, sizeof(trail_info) * MAX_SHIP_CONTRAILS);
4368 			sip->ct_count = 0;
4369 		}
4370 
4371 		// this means you've reached the max # of contrails for a ship
4372 		if (sip->ct_count >= MAX_SHIP_CONTRAILS) {
4373 			Warning(LOCATION, "%s '%s' has more contrails than the max of %d", info_type_name, sip->name, MAX_SHIP_CONTRAILS);
4374 			break;
4375 		}
4376 
4377 		trail_info *ci = &sip->ct_info[sip->ct_count++];
4378 
4379 		required_string("+Offset:");
4380 		stuff_vec3d(&ci->pt);
4381 
4382 		required_string("+Start Width:");
4383 		stuff_float(&ci->w_start);
4384 
4385 		required_string("+End Width:");
4386 		stuff_float(&ci->w_end);
4387 
4388 		required_string("+Start Alpha:");
4389 		stuff_float(&ci->a_start);
4390 
4391 		required_string("+End Alpha:");
4392 		stuff_float(&ci->a_end);
4393 
4394 		if (optional_string("+Alpha Decay Exponent:")) {
4395 			stuff_float(&ci->a_decay_exponent);
4396 			if (ci->a_decay_exponent < 0.0f) {
4397 				Warning(LOCATION, "Trail Alpha Decay Exponent of ship %s cannot be negative. Resetting to 1.\n", sip->name);
4398 				ci->a_decay_exponent = 1.0f;
4399 			}
4400 		}
4401 		else if (first_time)
4402 			ci->a_decay_exponent = 1.0f;
4403 
4404 		required_string("+Max Life:");
4405 		stuff_float(&ci->max_life);
4406 
4407 		if (optional_string("+Spread:"))
4408 			stuff_float(&ci->spread);
4409 
4410 		required_string("+Spew Time:");
4411 		stuff_int(&ci->stamp);
4412 
4413 		required_string("+Bitmap:");
4414 		stuff_string(name_tmp, F_NAME, NAME_LENGTH);
4415 		generic_bitmap_init(&ci->texture, name_tmp);
4416 		generic_bitmap_load(&ci->texture);
4417 
4418 		if (optional_string("+Bitmap Stretch:")) {
4419 			stuff_float(&ci->texture_stretch);
4420 			if (ci->texture_stretch == 0.0f) {
4421 				Warning(LOCATION, "Trail bitmap stretch of ship %s cannot be 0.  Setting to 1.\n", sip->name);
4422 				ci->texture_stretch = 1.0f;
4423 			}
4424 		}
4425 		else if (first_time)
4426 			ci->texture_stretch = 1.0f;
4427 
4428 		if (optional_string("+Faded Out Sections:") ) {
4429 			stuff_int(&ci->n_fade_out_sections);
4430 		}
4431 	}
4432 
4433 	man_thruster *mtp = NULL;
4434 	man_thruster manwich;
4435 	while(optional_string("$Thruster:"))
4436 	{
4437 		int idx = -1;
4438 		if(optional_string("+index:")) {
4439 			stuff_int(&idx);
4440 		}
4441 
4442 		if(idx >= 0 && idx < sip->num_maneuvering) {
4443 			mtp = &sip->maneuvering[idx];
4444 		} else if(idx < 0) {
4445 			if(sip->num_maneuvering < MAX_MAN_THRUSTERS) {
4446 				mtp = &sip->maneuvering[sip->num_maneuvering++];
4447 			} else {
4448 				Warning(LOCATION, "Too many maneuvering thrusters on %s '%s'; maximum is %d", info_type_name, sip->name, MAX_MAN_THRUSTERS);
4449 			}
4450 		} else {
4451 			mtp = &manwich;
4452 			Warning(LOCATION, "Invalid index (%d) specified for maneuvering thruster on %s '%s'", idx, info_type_name, sip->name);
4453 		}
4454 
4455 		if(optional_string("+Used for:")) {
4456 			parse_string_flag_list(mtp->use_flags, Man_types, Num_man_types, NULL);
4457 		}
4458 
4459 		if(optional_string("+Position:")) {
4460 			stuff_float_list(mtp->pos.a1d, 3);
4461 		}
4462 
4463 		if(optional_string("+Normal:")) {
4464 			stuff_float_list(mtp->norm.a1d, 3);
4465 		}
4466 
4467 		if(optional_string("+Texture:"))
4468 		{
4469 			stuff_string(name_tmp, F_NAME, sizeof(name_tmp));
4470 			int tex_fps=0, tex_nframes=0, tex_id=-1;;
4471 			tex_id = bm_load_animation(name_tmp, &tex_nframes, &tex_fps, nullptr, nullptr, true);
4472 			if(tex_id < 0)
4473 				tex_id = bm_load(name_tmp);
4474 			if(tex_id >= 0)
4475 			{
4476 				if(mtp->tex_id >= 0) {
4477 					bm_unload(mtp->tex_id);
4478 				}
4479 
4480 				mtp->tex_id = tex_id;
4481 				mtp->tex_fps = tex_fps;
4482 				mtp->tex_nframes = tex_nframes;
4483 			}
4484 		}
4485 
4486 		if(optional_string("+Radius:")) {
4487 			stuff_float(&mtp->radius);
4488 		}
4489 
4490 		if(optional_string("+Length:")) {
4491 			stuff_float(&mtp->length);
4492 		}
4493 
4494 		parse_game_sound("+StartSnd:", &mtp->start_snd);
4495 		parse_game_sound("+LoopSnd:", &mtp->loop_snd);
4496 		parse_game_sound("+StopSnd:", &mtp->stop_snd);
4497 	}
4498 
4499 	if (optional_string("$Glowpoint overrides:")) {
4500 		SCP_vector<SCP_string> tokens;
4501 		tokens.clear();
4502 		stuff_string_list(tokens);
4503 		for(SCP_vector<SCP_string>::iterator token = tokens.begin(); token != tokens.end(); ++token) {
4504 			SCP_string name, banks;
4505 			size_t seppos;
4506 			seppos = token->find_first_of(':');
4507 			if(seppos == SCP_string::npos) {
4508 				Warning(LOCATION, "Couldn't find ':' seperator in Glowpoint override for ship %s ignoring token", sip->name);
4509 				continue;
4510 			}
4511 			name = token->substr(0, seppos);
4512 			banks = token->substr(seppos+1);
4513 			SCP_vector<glow_point_bank_override>::iterator gpo = get_glowpoint_bank_override_by_name(name.data());
4514 			if(gpo == glowpoint_bank_overrides.end()){
4515 				Warning(LOCATION, "Couldn't find preset %s in glowpoints.tbl when parsing ship: %s", name.data(), sip->name);
4516 				continue;
4517 			}
4518 			if(banks == "*") {
4519 				sip->glowpoint_bank_override_map[-1] = (void*)(&(*gpo));
4520 				continue;
4521 			}
4522 			SCP_string banktoken;
4523 			size_t start = 0;
4524 			size_t end;
4525 			do {
4526 				end = banks.find_first_of(',', start);
4527 				banktoken = banks.substr(start, end);
4528 				start = end + 1;
4529 
4530 				size_t fromtopos;
4531 				fromtopos = banktoken.find_first_of('-');
4532 				if(fromtopos != SCP_string::npos) {
4533 					SCP_string from, to;
4534 					int ifrom, ito;
4535 					from = banktoken.substr(0, fromtopos);
4536 					to = banktoken.substr(fromtopos+1);
4537 					ifrom = atoi(from.data()) - 1;
4538 					ito = atoi(to.data()) - 1;
4539 					for(int bank = ifrom; bank <= ito; ++bank) {
4540 						sip->glowpoint_bank_override_map[bank] = (void*)(&(*gpo));
4541 					}
4542 				} else {
4543 					int bank = atoi(banktoken.data()) - 1;
4544 					sip->glowpoint_bank_override_map[bank] = (void*)(&(*gpo));
4545 				}
4546 			} while(end !=SCP_string::npos);
4547 		}
4548 	}
4549 
4550 	if (optional_string("$Radar Image 2D:"))
4551 	{
4552 		stuff_string(name_tmp, F_NAME, NAME_LENGTH);
4553 		sip->radar_image_2d_idx = bm_load(name_tmp);
4554 
4555 		if ( optional_string("$Radar Color Image 2D:") ) {
4556 			stuff_string(name_tmp, F_NAME, NAME_LENGTH);
4557 			sip->radar_color_image_2d_idx = bm_load(name_tmp);
4558 		}
4559 
4560 		if (optional_string("$Radar Image Size:"))
4561 			stuff_int(&sip->radar_image_size);
4562 
4563 		if (optional_string("$3D Radar Blip Size Multiplier:"))
4564 			stuff_float(&sip->radar_projection_size_mult);
4565 	}
4566 
4567 	// Alternate - per ship class - IFF colors
4568 	while((optional_string("$Ship IFF Colors:")) || (optional_string("$Ship IFF Colours:")))
4569 	{
4570 		char iff_1[NAME_LENGTH];
4571 		char iff_2[NAME_LENGTH];
4572 		int iff_color_data[3];
4573 		int iff_data[2];
4574 
4575 		// Get the iff strings and get the iff indexes
4576 		required_string("+Seen By:");
4577 		stuff_string(iff_1, F_NAME, NAME_LENGTH);
4578 
4579 		required_string("+When IFF Is:");
4580 		stuff_string(iff_2, F_NAME, NAME_LENGTH);
4581 		iff_data[0] = iff_lookup(iff_1);
4582 		iff_data[1] = iff_lookup(iff_2);
4583 
4584 		if (iff_data[0] == -1)
4585 			WarningEx(LOCATION, "%s '%s'\nIFF colour seen by \"%s\" invalid!", info_type_name, sip->name, iff_1);
4586 
4587 		if (iff_data[1] == -1)
4588 			WarningEx(LOCATION, "%s '%s'\nIFF colour when IFF is \"%s\" invalid!", info_type_name, sip->name, iff_2);
4589 
4590 		// Set the color
4591 		required_string("+As Color:");
4592 		stuff_int_list(iff_color_data, 3, RAW_INTEGER_TYPE);
4593 		sip->ship_iff_info[iff_data[0]][iff_data[1]] = iff_init_color(iff_color_data[0],iff_color_data[1],iff_color_data[2]);
4594 	}
4595 
4596 	if (optional_string("$Target Priority Groups:") ) {
4597 		SCP_vector<SCP_string> target_group_strings;
4598 		stuff_string_list(target_group_strings);
4599 		size_t num_groups = Ai_tp_list.size();
4600 		bool override_strings = false;
4601 
4602 		if (optional_string("+Override")) {
4603 			override_strings = true;
4604 		}
4605 
4606 		for(size_t j = 0; j < target_group_strings.size(); j++) {
4607 			size_t i;
4608 			for(i = 0; i < num_groups; i++) {
4609 				if ( !stricmp(target_group_strings[j].c_str(), Ai_tp_list[i].name) ) {
4610 					//so now the string from the list above as well as the ai priority group name match
4611 					//clear it if override has been set
4612 					if (override_strings) {
4613 						Ai_tp_list[i].ship_class.clear();
4614 						override_strings = false;
4615 					}
4616 					for (auto it = Ship_info.cbegin(); it != Ship_info.cend(); ++it) {
4617 						//find the index number of the current ship info type
4618 						if (it->name == sip->name) {
4619 							Ai_tp_list[i].ship_class.push_back((int)std::distance(Ship_info.cbegin(), it));
4620 							break;
4621 						}
4622 					}
4623 					// found something, try next string
4624 					break;
4625 				}
4626 			}
4627 			if (i == num_groups) {
4628 				Warning(LOCATION,"Unidentified priority group '%s' set for %s '%s'\n", target_group_strings[j].c_str(), info_type_name, sip->name);
4629 			}
4630 		}
4631 	}
4632 
4633 	if (optional_string("$EMP Resistance Modifier:")) {
4634 		stuff_float(&sip->emp_resistance_mod);
4635 	}
4636 
4637 	if (optional_string("$Piercing Damage Draw Limit:")) {
4638 		float tempf;
4639 		stuff_float(&tempf);
4640 		sip->piercing_damage_draw_limit = tempf / 100.0f;
4641 	}
4642 
4643 	while(optional_string("$Path Metadata:"))
4644 	{
4645 		char path_name[64];
4646 		stuff_string(path_name, F_NAME, sizeof(path_name));
4647 
4648 		path_metadata metadata;
4649 		init_path_metadata(metadata);
4650 
4651 		//Get +departure rvec and store on the path_metadata object
4652 		if (optional_string("+departure rvec:"))
4653 		{
4654 			stuff_vec3d(&metadata.departure_rvec);
4655 		}
4656 
4657 		if (optional_string("+arrive speed multiplier:"))
4658 		{
4659 			stuff_float(&metadata.arrive_speed_mult);
4660 		}
4661 		if (optional_string("+depart speed multiplier:"))
4662 		{
4663 			stuff_float(&metadata.depart_speed_mult);
4664 		}
4665 
4666 		//Add the new path_metadata to sip->pathMetadata keyed by path name
4667 		SCP_string pathName(path_name);
4668 		sip->pathMetadata[pathName] = metadata;
4669 	}
4670 
4671 	int n_subsystems = 0;
4672 	int cont_flag = 1;
4673 	model_subsystem subsystems[MAX_MODEL_SUBSYSTEMS] = {}; // see model.h for max_model_subsystems
4674 	for (auto i=0; i<MAX_MODEL_SUBSYSTEMS; i++) {
4675 		subsystems[i].reset();
4676 	}
4677 
4678 	float	hull_percentage_of_hits = 100.0f;
4679 	//If the ship already has subsystem entries (ie this is a modular table)
4680 	//make sure hull_percentage_of_hits is set properly
4681 	for(auto i=0; i < sip->n_subsystems; i++) {
4682 		hull_percentage_of_hits -= sip->subsystems[i].max_subsys_strength / sip->max_hull_strength;
4683 	}
4684 
4685 	while (cont_flag) {
4686 		int r = required_string_one_of(3, "#End", "$Subsystem:", type_name);
4687 		switch (r) {
4688 		case 0:
4689 			cont_flag = 0;
4690 			break;
4691 		case 1:
4692 		{
4693 			float	turning_rate;
4694 			float	percentage_of_hits;
4695 			bool turret_has_base_fov = false;
4696 			model_subsystem *sp = NULL;			// to append on the ships list of subsystems
4697 
4698 			int sfo_return;
4699 			required_string("$Subsystem:");
4700 			stuff_string(name_tmp, F_NAME, sizeof(name_tmp), ",");
4701 			Mp++;
4702 			for(auto i = 0;i < sip->n_subsystems; i++)
4703 			{
4704 				if(!subsystem_stricmp(sip->subsystems[i].subobj_name, name_tmp))
4705 					sp = &sip->subsystems[i];
4706 			}
4707 
4708 			if(sp == NULL)
4709 			{
4710 				if( sip->n_subsystems + n_subsystems >= MAX_MODEL_SUBSYSTEMS )
4711 				{
4712 					Warning(LOCATION, "Number of subsystems for %s '%s' (%d) exceeds max of %d; only the first %d will be used", info_type_name, sip->name, sip->n_subsystems, n_subsystems, MAX_MODEL_SUBSYSTEMS);
4713 					break;
4714 				}
4715 				sp = &subsystems[n_subsystems++];			// subsystems a local -- when done, we will malloc and copy
4716 				strcpy_s(sp->subobj_name, name_tmp);
4717 
4718 				//Init blank values
4719 				sp->max_subsys_strength = 0.0f;
4720 				sp->turret_turning_rate = 0.0f;
4721 				sp->weapon_rotation_pbank = -1;
4722 
4723                 memset(sp->alt_sub_name, 0, sizeof(sp->alt_sub_name));
4724                 memset(sp->alt_dmg_sub_name, 0, sizeof(sp->alt_dmg_sub_name));
4725 
4726 				for (auto i=0; i<MAX_SHIP_PRIMARY_BANKS; i++) {
4727 					sp->primary_banks[i] = -1;
4728 					sp->primary_bank_capacity[i] = 0;
4729 				}
4730 
4731 				for (auto i=0; i<MAX_SHIP_SECONDARY_BANKS; i++) {
4732 					sp->secondary_banks[i] = -1;
4733 					sp->secondary_bank_capacity[i] = 0;
4734 				}
4735 
4736 				sp->engine_wash_pointer = NULL;
4737 
4738 				sp->alive_snd = gamesnd_id();
4739 				sp->dead_snd = gamesnd_id();
4740 				sp->rotation_snd = gamesnd_id();
4741 				sp->turret_gun_rotation_snd = gamesnd_id();
4742 				sp->turret_gun_rotation_snd_mult = 1.0f;
4743 				sp->turret_base_rotation_snd = gamesnd_id();
4744 				sp->turret_base_rotation_snd_mult = 1.0f;
4745 
4746                 sp->flags.reset();
4747 
4748 				sp->n_triggers = 0;
4749 				sp->triggers = NULL;
4750 
4751 				sp->model_num = -1;		// init value for later sanity checking!!
4752 				sp->armor_type_idx = -1;
4753 				sp->path_num = -1;
4754 				sp->turret_max_fov = 1.0f;
4755 
4756 				sp->turret_reset_delay = 2000;
4757 
4758 				sp->num_target_priorities = 0;
4759 				for (auto i = 0; i < 32; i++) {
4760 					sp->target_priority[i] = -1;
4761 				}
4762 				sp->optimum_range = 0.0f;
4763 				sp->favor_current_facing = 0.0f;
4764 
4765 				sp->turret_rof_scaler = 1.0f;
4766 
4767 				sp->turret_max_bomb_ownage = -1;
4768 				sp->turret_max_target_ownage = -1;
4769 			}
4770 			sfo_return = stuff_float_optional(&percentage_of_hits);
4771 			if(sfo_return==2)
4772 			{
4773 				hull_percentage_of_hits -= percentage_of_hits;
4774 				sp->max_subsys_strength = sip->max_hull_strength * (percentage_of_hits / 100.0f);
4775 				sp->type = SUBSYSTEM_UNKNOWN;
4776 			}
4777 			if(sfo_return > 0)
4778 			{
4779 				if(stuff_float_optional(&turning_rate)==2)
4780 				{
4781 					// specified as how long to turn 360 degrees in ships.tbl
4782 					if ( turning_rate > 0.0f ){
4783 						sp->turret_turning_rate = PI2 / turning_rate;
4784 					} else {
4785 						sp->turret_turning_rate = 0.0f;
4786 					}
4787 				}
4788 				else
4789 				{
4790 					Error(LOCATION, "Malformed $Subsystem entry '%s' in %s '%s'.\n\n"
4791 						"Specify a turning rate or remove the trailing comma.",
4792 						sp->subobj_name, info_type_name, sip->name);
4793 				}
4794 			}
4795 
4796 			if(optional_string("$Alt Subsystem Name:")) {
4797 				stuff_string(buf, F_NAME, SHIP_MULTITEXT_LENGTH);
4798 				strcpy_s(sp->alt_sub_name,  buf);
4799 			}
4800 
4801 			if(optional_string("$Alt Damage Popup Subsystem Name:")) {
4802 				stuff_string(buf, F_NAME, SHIP_MULTITEXT_LENGTH);
4803                 strcpy_s(sp->alt_dmg_sub_name, buf);
4804 			}
4805 
4806 			if(optional_string("$Armor Type:")) {
4807 				stuff_string(buf, F_NAME, SHIP_MULTITEXT_LENGTH);
4808 				sp->armor_type_idx = armor_type_get_idx(buf);
4809 
4810 				if (sp->armor_type_idx == -1)
4811 					WarningEx(LOCATION, "%s '%s', subsystem %s\nInvalid armor type %s!", info_type_name, sip->name, sp->subobj_name, buf);
4812 			}
4813 
4814 			//	Get primary bank weapons
4815 			parse_weapon_bank(sip, true, NULL, sp->primary_banks, sp->primary_bank_capacity);
4816 
4817 			//	Get secondary bank weapons
4818 			parse_weapon_bank(sip, false, NULL, sp->secondary_banks, sp->secondary_bank_capacity);
4819 
4820 			// Get optional engine wake info
4821 			if (optional_string("$Engine Wash:")) {
4822 				stuff_string(name_tmp, F_NAME, sizeof(name_tmp));
4823 				// get and set index
4824 				sp->engine_wash_pointer = get_engine_wash_pointer(name_tmp);
4825 
4826 				if(sp->engine_wash_pointer == NULL)
4827 					WarningEx(LOCATION,"Invalid engine wash name %s specified for subsystem %s in %s '%s'", name_tmp, sp->subobj_name, info_type_name, sip->name);
4828 			}
4829 
4830 			parse_game_sound("$AliveSnd:", &sp->alive_snd);
4831 			parse_game_sound("$DeadSnd:", &sp->dead_snd);
4832 			parse_game_sound("$RotationSnd:", &sp->rotation_snd);
4833 			parse_game_sound("$Turret Base RotationSnd:", &sp->turret_base_rotation_snd);
4834 			parse_game_sound("$Turret Gun RotationSnd:", &sp->turret_gun_rotation_snd);
4835 
4836 			if (optional_string("$Turret BaseSnd Volume:"))
4837 				stuff_float(&sp->turret_base_rotation_snd_mult);
4838 
4839 			if (optional_string("$Turret GunSnd Volume:"))
4840 				stuff_float(&sp->turret_gun_rotation_snd_mult);
4841 
4842 			// Get any AWACS info
4843 			sp->awacs_intensity = 0.0f;
4844 			if(optional_string("$AWACS:")){
4845 				sfo_return = stuff_float_optional(&sp->awacs_intensity);
4846 				if(sfo_return > 0)
4847 					stuff_float_optional(&sp->awacs_radius);
4848 				sip->flags.set(Ship::Info_Flags::Has_awacs);
4849 			}
4850 
4851 			if(optional_string("$Maximum Barrel Elevation:")){
4852 				int value;
4853 				stuff_int(&value);
4854 				CAP(value, 0, 90);
4855 				float angle = fl_radians(90 - value);
4856 				sp->turret_max_fov = cosf(angle);
4857 			}
4858 
4859 			if(optional_string("$Turret Base FOV:")) {
4860 				int value;
4861 				stuff_int(&value);
4862 				CAP(value, 0, 359);
4863 				float angle = fl_radians(value)/2.0f;
4864 				sp->turret_base_fov = cosf(angle);
4865 				turret_has_base_fov = true;
4866 			}
4867 
4868 			if (optional_string("$Turret Reset Delay:"))
4869 				stuff_int(&sp->turret_reset_delay);
4870 
4871 			if (optional_string("$Turret Optimum Range:"))
4872 				stuff_float(&sp->optimum_range);
4873 
4874 			if (optional_string("$Turret Direction Preference:")) {
4875 				int temp;
4876 				stuff_int(&temp);
4877 				if (temp == 0) {
4878 					sp->favor_current_facing = 0.0f;
4879 				} else {
4880 					CAP(temp, 1, 100);
4881 					sp->favor_current_facing = 1.0f + (((float) (100 - temp)) / 10.0f);
4882 				}
4883 			}
4884 
4885 			if (optional_string("$Target Priority:")) {
4886 				SCP_vector <SCP_string> tgt_priorities;
4887 				stuff_string_list(tgt_priorities);
4888 				sp->num_target_priorities = 0;
4889 
4890 				if (tgt_priorities.size() > 32)
4891 					tgt_priorities.resize(32);
4892 
4893 				size_t num_groups = Ai_tp_list.size();
4894 
4895 				for (size_t i = 0; i < tgt_priorities.size(); ++i) {
4896 					size_t j;
4897 					for(j = 0; j < num_groups; j++) {
4898 						if ( !stricmp(Ai_tp_list[j].name, tgt_priorities[i].c_str()))  {
4899 							sp->target_priority[i] = (int)j;
4900 							sp->num_target_priorities++;
4901 							break;
4902 						}
4903 					}
4904 					if (j == num_groups) {
4905 						Warning(LOCATION, "Unidentified target priority '%s' set for\nsubsystem '%s' in %s '%s'.", tgt_priorities[i].c_str(), sp->subobj_name, info_type_name, sip->name);
4906 					}
4907 				}
4908 			}
4909 
4910 			if (optional_string("$Max Turrets per Bomb:")) {
4911 				stuff_int(&sp->turret_max_bomb_ownage);
4912 			}
4913 
4914 			if (optional_string("$Max Turrets per Target:")) {
4915 				stuff_int(&sp->turret_max_target_ownage);
4916 			}
4917 
4918 			if (optional_string("$ROF:")) {
4919 
4920 				if (optional_string("+Use firingpoints")) {
4921 					sp->turret_rof_scaler = 0;
4922 				} else {
4923 					if (optional_string("+Multiplier:")) {
4924 						float tempf;
4925 						stuff_float(&tempf);
4926 
4927 						if (tempf < 0) {
4928 							mprintf(("RoF multiplier clamped to 0 for subsystem '%s' in %s '%s'.\n", sp->subobj_name, info_type_name, sip->name));
4929 							sp->turret_rof_scaler = 0;
4930 						} else {
4931 							sp->turret_rof_scaler = tempf;
4932 						}
4933 					} else {
4934 						Warning(LOCATION, "RoF multiplier not set for subsystem\n'%s' in %s '%s'.", sp->subobj_name, info_type_name, sip->name);
4935 					}
4936 				}
4937 			}
4938 
4939 			if (optional_string("$Flags:")) {
4940                 SCP_vector<SCP_string> errors;
4941                 flagset<Model::Subsystem_Flags> tmp_flags;
4942                 parse_string_flag_list(tmp_flags, Subsystem_flags, Num_subsystem_flags, &errors);
4943 
4944                 if (optional_string("+noreplace")) {
4945                     sp->flags |= tmp_flags;
4946                 }
4947                 else {
4948                     sp->flags = tmp_flags;
4949                 }
4950 
4951                 if (!errors.empty()) {
4952                     for (auto const &error : errors) {
4953                         Warning(LOCATION, "Bogus string in subsystem flags: %s\n", error.c_str());
4954                     }
4955                 }
4956 
4957 				//If we've set any subsystem as landable, set a ship-info flag as a shortcut for later
4958 				if (sp->flags[Model::Subsystem_Flags::Allow_landing])
4959 					sip->flags.set(Ship::Info_Flags::Allow_landings);
4960 			}
4961 
4962             if (turret_has_base_fov)
4963                 sp->flags.set(Model::Subsystem_Flags::Turret_restricted_fov);
4964 
4965 			if (optional_string("+non-targetable")) {
4966 				Warning(LOCATION, "Grammar error in table file.  Please change \"+non-targetable\" to \"+untargetable\".");
4967 				sp->flags.set(Model::Subsystem_Flags::Untargetable);
4968 			}
4969 
4970 			bool old_flags = false;
4971 			if (optional_string("+untargetable")) {
4972 				sp->flags.set(Model::Subsystem_Flags::Untargetable);
4973 				old_flags = true;
4974 			}
4975 
4976 			if (optional_string("+carry-no-damage")) {
4977 				sp->flags.set(Model::Subsystem_Flags::Carry_no_damage);
4978 				old_flags = true;
4979 			}
4980 
4981 			if (optional_string("+use-multiple-guns")) {
4982 				sp->flags.set(Model::Subsystem_Flags::Use_multiple_guns);
4983 				old_flags = true;
4984 			}
4985 
4986 			if (optional_string("+fire-down-normals")) {
4987 				sp->flags.set(Model::Subsystem_Flags::Fire_on_normal);
4988 				old_flags = true;
4989 			}
4990 
4991 			if ((sp->flags[Model::Subsystem_Flags::Turret_fixed_fp]) && !(sp->flags[Model::Subsystem_Flags::Use_multiple_guns])) {
4992 				Warning(LOCATION, "\"fixed firingpoints\" flag used without \"use multiple guns\" flag on a subsystem on %s '%s'.\n\"use multiple guns\" flags added by default\n", info_type_name, sip->name);
4993 				sp->flags.set(Model::Subsystem_Flags::Use_multiple_guns);
4994 			}
4995 
4996 			if ((sp->flags[Model::Subsystem_Flags::Autorepair_if_disabled]) && (sp->flags[Model::Subsystem_Flags::No_autorepair_if_disabled])) {
4997 				Warning(LOCATION, "\"autorepair if disabled\" flag used with \"don't autorepair if disabled\" flag on a subsystem on %s '%s'.\nWhichever flag would be default behavior anyway for this ship has been removed.\n", info_type_name, sip->name);
4998 				if (sip->flags[Ship::Info_Flags::Subsys_repair_when_disabled]){
4999                     sp->flags.remove(Model::Subsystem_Flags::Autorepair_if_disabled);
5000 				} else {
5001                     sp->flags.remove(Model::Subsystem_Flags::No_autorepair_if_disabled);
5002 				}
5003 			}
5004 
5005 			if (old_flags) {
5006 				mprintf(("Use of deprecated subsystem syntax.  Please use the $Flags: field for subsystem flags.\n\n" \
5007 				"At least one of the following tags was used on %s '%s', subsystem %s:\n" \
5008 				"\t+untargetable\n" \
5009 				"\t+carry-no-damage\n" \
5010 				"\t+use-multiple-guns\n" \
5011 				"\t+fire-down-normals\n", info_type_name, sip->name, sp->subobj_name));
5012 			}
5013 
5014 			while(optional_string("$animation:"))
5015 			{
5016 				stuff_string(name_tmp, F_NAME, sizeof(name_tmp));
5017 				if(!stricmp(name_tmp, "triggered"))
5018 				{
5019 					queued_animation current_trigger;
5020 					bool applyNewTrigger = true;
5021 
5022 					//add a new trigger
5023 					queued_animation_init(&current_trigger);
5024 
5025 					required_string("$type:");
5026 					char atype[NAME_LENGTH];
5027 					stuff_string(atype, F_NAME, NAME_LENGTH);
5028 					current_trigger.type = model_anim_match_type(atype);
5029 
5030 					if(optional_string("+sub_type:")){
5031 						stuff_int(&current_trigger.subtype);
5032 					}else{
5033 						current_trigger.subtype = ANIMATION_SUBTYPE_ALL;
5034 					}
5035 
5036 					if(optional_string("+sub_name:")) {
5037 						stuff_string(current_trigger.sub_name, F_NAME, NAME_LENGTH);
5038 					} else {
5039 						strcpy_s(current_trigger.sub_name, "<none>");
5040 					}
5041 
5042 
5043 					if(current_trigger.type == AnimationTriggerType::Initial){
5044 						animation::ModelAnimation::parseLegacyAnimationTable(sp, sip);
5045 
5046 						applyNewTrigger = false;
5047 					}else{
5048 
5049 						if(optional_string("+delay:"))
5050 							stuff_int(&current_trigger.start);
5051 						else
5052 							current_trigger.start = 0;
5053 
5054 						if ( optional_string("+reverse_delay:") )
5055 							stuff_int(&current_trigger.reverse_start);
5056 						else
5057 							current_trigger.reverse_start = -1; //have some code figure this out for us
5058 
5059 						if(optional_string("+absolute_angle:")){
5060 							current_trigger.absolute = true;
5061 							stuff_vec3d(&current_trigger.angle );
5062 
5063 							current_trigger.angle.xyz.x = fl_radians(current_trigger.angle.xyz.x);
5064 							current_trigger.angle.xyz.y = fl_radians(current_trigger.angle.xyz.y);
5065 							current_trigger.angle.xyz.z = fl_radians(current_trigger.angle.xyz.z);
5066 						}else{
5067 							current_trigger.absolute = false;
5068 							required_string("+relative_angle:");
5069 							stuff_vec3d(&current_trigger.angle );
5070 
5071 							current_trigger.angle.xyz.x = fl_radians(current_trigger.angle.xyz.x);
5072 							current_trigger.angle.xyz.y = fl_radians(current_trigger.angle.xyz.y);
5073 							current_trigger.angle.xyz.z = fl_radians(current_trigger.angle.xyz.z);
5074 						}
5075 
5076 						required_string("+velocity:");
5077 						stuff_vec3d(&current_trigger.vel );
5078 						current_trigger.vel.xyz.x = fl_radians(current_trigger.vel.xyz.x);
5079 						current_trigger.vel.xyz.y = fl_radians(current_trigger.vel.xyz.y);
5080 						current_trigger.vel.xyz.z = fl_radians(current_trigger.vel.xyz.z);
5081 
5082 						if (optional_string("+acceleration:")){
5083 							stuff_vec3d(&current_trigger.accel );
5084 							current_trigger.accel.xyz.x = fl_radians(current_trigger.accel.xyz.x);
5085 							current_trigger.accel.xyz.y = fl_radians(current_trigger.accel.xyz.y);
5086 							current_trigger.accel.xyz.z = fl_radians(current_trigger.accel.xyz.z);
5087 						} else {
5088 							current_trigger.accel.xyz.x = 0.0f;
5089 							current_trigger.accel.xyz.y = 0.0f;
5090 							current_trigger.accel.xyz.z = 0.0f;
5091 						}
5092 
5093 						if(optional_string("+time:"))
5094 							stuff_int(&current_trigger.end );
5095 						else
5096 							current_trigger.end = 0;
5097 
5098 						if(optional_string("$Sound:")){
5099 							parse_game_sound("+Start:", &current_trigger.start_sound);
5100 
5101 							parse_game_sound("+Loop:", &current_trigger.loop_sound);
5102 
5103 							parse_game_sound("+End:", &current_trigger.end_sound);
5104 
5105 							required_string("+Radius:");
5106 							stuff_float(&current_trigger.snd_rad );
5107 						}else{
5108 							current_trigger.start_sound = gamesnd_id();
5109 							current_trigger.loop_sound = gamesnd_id();
5110 							current_trigger.end_sound = gamesnd_id();
5111 							current_trigger.snd_rad = 0;
5112 						}
5113 					}
5114 
5115 					if (applyNewTrigger) {
5116 						sp->triggers = (queued_animation*)vm_realloc(sp->triggers, sizeof(queued_animation) * (sp->n_triggers + 1));
5117 						Verify(sp->triggers != nullptr);
5118 
5119 						queued_animation* actual = &sp->triggers[sp->n_triggers];
5120 						sp->n_triggers++;
5121 
5122 						*actual = current_trigger;
5123 
5124 						//make sure that the amount of time it takes to accelerate up and down doesn't make it go farther than the angle
5125 						queued_animation_correct(actual);
5126 					}
5127 				}
5128 				else if(!stricmp(name_tmp, "linked"))
5129 				{
5130 					mprintf(("TODO: set up linked animation\n"));
5131 				}
5132 			}
5133 
5134 			sp->beam_warmdown_program = actions::ProgramSet::parseProgramSet("$On Beam Warmdown:",
5135 				{actions::ProgramContextFlags::HasObject, actions::ProgramContextFlags::HasSubobject});
5136 		}
5137 		break;
5138 		case 2:
5139 			cont_flag = 0;
5140 			break;
5141 		case -1:	// Possible return value if -noparseerrors is used
5142 			break;
5143 		default:
5144 			UNREACHABLE("This should never happen.\n");	// Impossible return value from required_string_one_of.
5145 		}
5146 	}
5147 
5148 	// must be > 0//no it doesn't :P -Bobboau
5149 	// yes it does! - Goober5000
5150 	// (we don't want a div-0 error)
5151 	if (hull_percentage_of_hits <= 0.0f )
5152 	{
5153 		//Warning(LOCATION, "The subsystems defined for the %s can take more (or the same) combined damage than the ship itself. Adjust the tables so that the percentages add up to less than 100", sip->name);
5154 	}
5155 	// when done reading subsystems, malloc and copy the subsystem data to the ship info structure
5156 	int orig_n_subsystems = sip->n_subsystems;
5157 	if ( n_subsystems > 0 ) {
5158 		// Let's make sure that n_subsystems is not negative
5159 		Assertion(sip->n_subsystems >= 0, "Invalid n_subsystems detected!");
5160 		auto new_n = sip->n_subsystems + n_subsystems;
5161 
5162 		std::unique_ptr<model_subsystem[]> subsys_storage(new model_subsystem[new_n]);
5163 
5164 		if(sip->n_subsystems <= 0) {
5165 			sip->n_subsystems = n_subsystems;
5166 		} else {
5167 			// This used realloc originally so we need to copy the existing subsystems to the new storage
5168 			std::copy(sip->subsystems, sip->subsystems + sip->n_subsystems, subsys_storage.get());
5169 
5170 			sip->n_subsystems += n_subsystems;
5171 		}
5172 		sip->subsystems = subsys_storage.release();
5173 
5174 	    Assert(sip->subsystems != NULL);
5175     }
5176 
5177 	for ( int i = 0; i < n_subsystems; i++ ){
5178 		sip->subsystems[orig_n_subsystems+i] = subsystems[i];
5179 	}
5180 
5181 	model_anim_fix_reverse_times(sip);
5182 }
5183 
get_engine_wash_pointer(char * engine_wash_name)5184 static engine_wash_info *get_engine_wash_pointer(char *engine_wash_name)
5185 {
5186 	for(int i = 0; i < Num_engine_wash_types; i++)
5187 	{
5188 		if(!stricmp(engine_wash_name, Engine_wash_info[i].name))
5189 		{
5190 			return &Engine_wash_info[i];
5191 		}
5192 	}
5193 
5194 	//Didn't find anything.
5195 	return NULL;
5196 }
5197 
parse_ship_type(const char * filename,const bool replace)5198 static void parse_ship_type(const char *filename, const bool replace)
5199 {
5200 	char name_buf[NAME_LENGTH];
5201 	bool create_if_not_found = true;
5202 	ship_type_info *stp = nullptr;
5203 
5204 	required_string("$Name:");
5205 	stuff_string(name_buf, F_NAME, NAME_LENGTH);
5206 
5207 	if(optional_string("+nocreate")) {
5208 		if(!replace) {
5209 			Warning(LOCATION, "+nocreate flag used for ship type '%s' in non-modular table", name_buf);
5210 		}
5211 		create_if_not_found = false;
5212 	}
5213 
5214 	bool first_time;
5215 	int idx = ship_type_name_lookup(name_buf);
5216 	if (idx >= 0)
5217 	{
5218 		if (!replace)
5219 		{
5220 			Warning(LOCATION, "ship type '%s' already exists in %s; ship type names must be unique.", name_buf, filename);
5221 			if (!skip_to_start_of_string_either("$Name:", "#End")) {
5222 				error_display(1, "Missing [#End] or [$Name] after duplicate ship type %s", name_buf);
5223 			}
5224 			return;
5225 		}
5226 		stp = &Ship_types[idx];
5227 		first_time = false;
5228 	}
5229 	else
5230 	{
5231 		if (!create_if_not_found && replace)
5232 		{
5233 			if (!skip_to_start_of_string_either("$Name:", "#End")) {
5234 				error_display(1, "Missing [#End] or [$Name] after ship type %s", name_buf);
5235 			}
5236 			return;
5237 		}
5238 		Ship_types.push_back(ship_type_info());
5239 		stp = &Ship_types.back();
5240 		strcpy_s(stp->name, name_buf);
5241 		first_time = true;
5242 	}
5243 
5244 	const char *ship_type = NULL;
5245 	if (!stricmp(stp->name, "sentrygun")) {
5246 		ship_type = "sentry gun";
5247 	} else if (!stricmp(stp->name, "escapepod")) {
5248 		ship_type = "escape pod";
5249 	} else if (!stricmp(stp->name, "repair_rearm")) {
5250 		ship_type = "support";
5251 	} else if (!stricmp(stp->name, "supercap")) {
5252 		ship_type = "super cap";
5253 	} else if (!stricmp(stp->name, "knossos")) {
5254 		ship_type = "knossos device";
5255 	}
5256 
5257 	if (ship_type != NULL) {
5258 		Warning(LOCATION, "Bad ship type name in %s\n\nUsed ship type is redirected to another ship type.\nReplace \"%s\" with \"%s\"\nin %s to fix this.\n", filename, stp->name, ship_type, filename);
5259 	}
5260 
5261 	bool big_ship = false;
5262 	bool huge_ship = false;
5263 	if (stricmp(stp->name, "cruiser") == 0 || stricmp(stp->name, "freighter") == 0 || stricmp(stp->name, "transport") == 0 ||
5264 		stricmp(stp->name, "corvette") == 0 || stricmp(stp->name, "gas miner") == 0 || stricmp(stp->name, "awacs") == 0)
5265 		big_ship = true;
5266 
5267 	if (stricmp(stp->name, "capital") == 0 || stricmp(stp->name, "super cap") == 0 || stricmp(stp->name, "knossos device") == 0 || stricmp(stp->name, "drydock") == 0)
5268 		huge_ship = true;
5269 
5270 	//Okay, now we should have the values to parse
5271 	//But they aren't here!! :O
5272 	//Now they are!! Whee fogging!!
5273 
5274 	//AI turret targeting priority setup
5275 	if (optional_string("$Target Priority Groups:") ) {
5276 		SCP_vector <SCP_string> target_group_strings;
5277 		stuff_string_list(target_group_strings);
5278 		auto num_strings = target_group_strings.size();
5279 		auto num_groups = Ai_tp_list.size();
5280 		bool override_strings = false;
5281 
5282 		if (optional_string("+Override")) {
5283 			override_strings = true;
5284 		}
5285 
5286 		for(size_t j = 0; j < num_strings; j++) {
5287 			size_t i;
5288 			for(i = 0; i < num_groups; i++) {
5289 				if ( !stricmp(target_group_strings[j].c_str(), Ai_tp_list[i].name) ) {
5290 					//so now the string from the list above as well as the ai priority group name match
5291 					//clear it if override has been set
5292 					if (override_strings) {
5293 						Ai_tp_list[i].ship_type.clear();
5294 						override_strings = false;
5295 					}
5296 					//find the index number of the current ship info type
5297 					Ai_tp_list[i].ship_type.push_back(ship_type_name_lookup(name_buf));
5298 					break;
5299 				}
5300 			}
5301 			if (i == num_groups) {
5302 				Warning(LOCATION,"Unidentified priority group '%s' set for ship type '%s' in %s\n", target_group_strings[j].c_str(), stp->name, filename);
5303 			}
5304 		}
5305 	}
5306 
5307 	if(optional_string("$Counts for Alone:")) {
5308 		stuff_boolean_flag(stp->flags, Ship::Type_Info_Flags::Counts_for_alone);
5309 	}
5310 
5311 	if(optional_string("$Praise Destruction:")) {
5312         stuff_boolean_flag(stp->flags, Ship::Type_Info_Flags::Praise_destruction);
5313 	}
5314 
5315 	if(optional_string("$On Hotkey list:")) {
5316 		stuff_boolean_flag(stp->flags, Ship::Type_Info_Flags::Hotkey_on_list);
5317 	}
5318 
5319 	if(optional_string("$Target as Threat:")) {
5320 		stuff_boolean_flag(stp->flags, Ship::Type_Info_Flags::Target_as_threat);
5321 	}
5322 
5323 	if(optional_string("$Show Attack Direction:")) {
5324 		stuff_boolean_flag(stp->flags, Ship::Type_Info_Flags::Show_attack_direction);
5325 	}
5326 
5327 	if(optional_string("$Scannable:")) {
5328 		stuff_boolean_flag(stp->flags, Ship::Type_Info_Flags::Scannable);
5329 	}
5330 
5331 	if(optional_string("$Warp Pushes:")) {
5332 		stuff_boolean_flag(stp->flags, Ship::Type_Info_Flags::Warp_pushes);
5333 	}
5334 
5335 	if(optional_string("$Warp Pushable:")) {
5336 		stuff_boolean_flag(stp->flags, Ship::Type_Info_Flags::Warp_pushable);
5337 	}
5338 
5339 	if(optional_string("$Turrets prioritize ship target:")) {
5340 		stuff_boolean_flag(stp->flags, Ship::Type_Info_Flags::Turret_tgt_ship_tgt);
5341 	}
5342 
5343 	if(optional_string("$Max Debris Speed:")) {
5344 		stuff_float(&stp->debris_max_speed);
5345 	}
5346 
5347 	if(optional_string("$FF Multiplier:")) {
5348 		stuff_float(&stp->ff_multiplier);
5349 	}
5350 
5351 	if(optional_string("$EMP Multiplier:")) {
5352 		stuff_float(&stp->emp_multiplier);
5353 	}
5354 
5355 	if(optional_string("$Beams Easily Hit:")) {
5356 		stuff_boolean_flag(stp->flags, Ship::Type_Info_Flags::Beams_easily_hit);
5357 	}
5358 
5359 	if(optional_string("$Protected on cripple:")) {
5360 		stuff_boolean_flag(stp->flags, Ship::Type_Info_Flags::AI_protected_on_cripple);
5361 	}
5362 
5363 	if(optional_string("$No Huge Beam Impact Effects:")) {
5364 		stuff_boolean_flag(stp->flags, Ship::Type_Info_Flags::No_huge_impact_eff);
5365 	}
5366 
5367 	if(optional_string("$Don't display class in briefing:")) {
5368 		stuff_boolean_flag(stp->flags, Ship::Type_Info_Flags::No_class_display);
5369 	}
5370 
5371 	if(optional_string("$Fog:"))
5372 	{
5373 		if(optional_string("+Start dist:")) {
5374 			stuff_float(&stp->fog_start_dist);
5375 		}
5376 
5377 		if(optional_string("+Compl dist:")) {
5378 			stuff_float(&stp->fog_complete_dist);
5379 		}
5380 	}
5381 
5382 	if(optional_string("$AI:"))
5383 	{
5384 		if(optional_string("+Valid goals:")) {
5385 			parse_string_flag_list(&stp->ai_valid_goals, Ai_goal_names, Num_ai_goals);
5386 		}
5387 
5388 		if(optional_string("+Accept Player Orders:")) {
5389 			stuff_boolean_flag(stp->flags, Ship::Type_Info_Flags::AI_accept_player_orders);
5390 		}
5391 
5392 		if(optional_string("+Player Orders:")) {
5393 			parse_string_flag_list(&stp->ai_player_orders, Player_orders, Num_player_orders);
5394 		}
5395 
5396 		if(optional_string("+Auto attacks:")) {
5397             stuff_boolean_flag(stp->flags, Ship::Type_Info_Flags::AI_auto_attacks);
5398 		}
5399 
5400 		if(optional_string("+Attempt broadside:")) {
5401             stuff_boolean_flag(stp->flags, Ship::Type_Info_Flags::AI_attempt_broadside);
5402 		}
5403 
5404 		if(optional_string("+Actively Pursues:")) {
5405 			stuff_string_list(stp->ai_actively_pursues_temp);
5406 		}
5407 
5408 		if(optional_string("+Guards attack this:")) {
5409             stuff_boolean_flag(stp->flags, Ship::Type_Info_Flags::AI_guards_attack);
5410 		}
5411 
5412 		if(optional_string("+Turrets attack this:")) {
5413             stuff_boolean_flag(stp->flags, Ship::Type_Info_Flags::AI_turrets_attack);
5414 		}
5415 
5416 		if(optional_string("+Can form wing:")) {
5417             stuff_boolean_flag(stp->flags, Ship::Type_Info_Flags::AI_can_form_wing);
5418 		}
5419 
5420 		if(optional_string("+Active docks:")) {
5421 			parse_string_flag_list(&stp->ai_active_dock, Dock_type_names, Num_dock_type_names);
5422 		}
5423 
5424 		if(optional_string("+Passive docks:")) {
5425 			parse_string_flag_list(&stp->ai_passive_dock, Dock_type_names, Num_dock_type_names);
5426 		}
5427 
5428 		if(optional_string("+Ignored on cripple by:")) {
5429 			stuff_string_list(stp->ai_cripple_ignores_temp);
5430 		}
5431 
5432 		if (optional_string("+Targeted by 'Huge' weapons and Ignored by 'small only' weapons:")) {
5433 			stuff_boolean_flag(stp->flags, Ship::Type_Info_Flags::Targeted_by_huge_Ignored_by_small_only);
5434 		} else if (first_time && (big_ship || huge_ship)) {
5435 			stp->flags.set(Ship::Type_Info_Flags::Targeted_by_huge_Ignored_by_small_only);
5436 		}
5437 	}
5438 
5439 	if(optional_string("$Explosion Animations:"))
5440 	{
5441 		int temp[MAX_FIREBALL_TYPES];
5442 		auto parsed_ints = stuff_int_list(temp, MAX_FIREBALL_TYPES, RAW_INTEGER_TYPE);
5443 		stp->explosion_bitmap_anims.clear();
5444 		stp->explosion_bitmap_anims.insert(stp->explosion_bitmap_anims.begin(), temp, temp+parsed_ints);
5445 	}
5446 
5447 	if(optional_string("$Vaporize Percent Chance:")) {
5448 		stuff_float(&stp->vaporize_chance);
5449 		if (stp->vaporize_chance < 0.0f || stp->vaporize_chance > 100.0f) {
5450 			stp->vaporize_chance = 0.0f;
5451 			Warning(LOCATION, "$Vaporize Percent Chance should be between 0 and 100.0 (read %f) for ship type '%s' in %s. Setting to 0.", stp->vaporize_chance, stp->name, filename);
5452 		}
5453 		//Percent is nice for modders, but here in the code we want it betwwen 0 and 1.0
5454 		stp->vaporize_chance /= 100.0;
5455 	}
5456 }
5457 
parse_shiptype_tbl(const char * filename)5458 static void parse_shiptype_tbl(const char *filename)
5459 {
5460 	try
5461 	{
5462 		if (filename != NULL)
5463 			read_file_text(filename, CF_TYPE_TABLES);
5464 		else
5465 			read_file_text_from_default(defaults_get_file("objecttypes.tbl"));
5466 
5467 		reset_parse();
5468 
5469 		if (optional_string("#Target Priorities"))
5470 		{
5471 			while (required_string_either("#End", "$Name:"))
5472 				parse_ai_target_priorities();
5473 
5474 			required_string("#End");
5475 		}
5476 
5477 		if (optional_string("#Weapon Targeting Priorities"))
5478 		{
5479 			while (required_string_either("#End", "$Name:"))
5480 				parse_weapon_targeting_priorities();
5481 
5482 			required_string("#End");
5483 		}
5484 
5485 		if (optional_string("#Ship Types"))
5486 		{
5487 			while (required_string_either("#End", "$Name:"))
5488 				parse_ship_type(filename ? filename : "built-in objecttypes.tbl", Parsing_modular_table);
5489 
5490 			required_string("#End");
5491 		}
5492 	}
5493 	catch (const parse::ParseException& e)
5494 	{
5495 		mprintf(("TABLES: Unable to parse '%s'!  Error message = %s.\n", filename, e.what()));
5496 		return;
5497 	}
5498 }
5499 
5500 // The E - Simple lookup function for FRED.
get_default_player_ship_index()5501 int get_default_player_ship_index()
5502 {
5503 	if (strlen(default_player_ship))
5504 	{
5505 		for (auto it = Ship_info.cbegin(); it != Ship_info.cend(); ++it)
5506 		{
5507 			if (stricmp(default_player_ship, it->name) == 0)
5508 				return (int)std::distance(Ship_info.cbegin(), it);
5509 		}
5510 		return 0;
5511 	} else
5512 		return 0;
5513 }
5514 
5515 // Goober5000 - this works better in its own function
ship_set_default_player_ship()5516 static void ship_set_default_player_ship()
5517 {
5518 	// already have one
5519 	if(strlen(default_player_ship))
5520 		return;
5521 
5522 	// find the first with the default flag
5523 	for (auto it = Ship_info.cbegin(); it != Ship_info.end(); ++it)
5524 	{
5525 		if(it->flags[Ship::Info_Flags::Default_player_ship])
5526 		{
5527 			strcpy_s(default_player_ship, it->name);
5528 			return;
5529 		}
5530 	}
5531 
5532 	// find the first player ship
5533 	for (auto it = Ship_info.cbegin(); it != Ship_info.end(); ++it)
5534 	{
5535 		if(it->flags[Ship::Info_Flags::Player_ship])
5536 		{
5537 			strcpy_s(default_player_ship, it->name);
5538 			return;
5539 		}
5540 	}
5541 
5542 	// find the first ship
5543 	if(!Ship_info.empty())
5544 	{
5545 		strcpy_s(default_player_ship, Ship_info[0].name);
5546 	}
5547 }
5548 
parse_shiptbl(const char * filename)5549 static void parse_shiptbl(const char *filename)
5550 {
5551 	try
5552 	{
5553 		read_file_text(filename, CF_TYPE_TABLES);
5554 		reset_parse();
5555 
5556 		// parse default ship
5557 		//Override default player ship
5558 		if (optional_string("#Default Player Ship"))
5559 		{
5560 			required_string("$Name:");
5561 			stuff_string(default_player_ship, F_NAME, sizeof(default_player_ship));
5562 			required_string("#End");
5563 		}
5564 		//Add engine washes
5565 		//This will override if they already exist
5566 		if (optional_string("#Engine Wash Info"))
5567 		{
5568 			while (required_string_either("#End", "$Name:"))
5569 				parse_engine_wash(Parsing_modular_table);
5570 
5571 			required_string("#End");
5572 		}
5573 
5574 		if (optional_string("#Ship Templates"))
5575 		{
5576 			while (required_string_either("#End", "$Template:"))
5577 				parse_ship_template();
5578 
5579 			required_string("#End");
5580 		}
5581 
5582 		//Add ship classes
5583 		if (optional_string("#Ship Classes"))
5584 		{
5585 			while (required_string_either("#End", "$Name:"))
5586 				parse_ship(filename, Parsing_modular_table);
5587 
5588 			required_string("#End");
5589 		}
5590 
5591 		//Add formations
5592 		if (optional_string("#Wing Formations"))
5593 		{
5594 			while (required_string_either("#End", "$Name:"))
5595 				parse_wing_formation(Parsing_modular_table);
5596 
5597 			required_string("#End");
5598 		}
5599 
5600 		//Set default player ship
5601 		ship_set_default_player_ship();
5602 	}
5603 	catch (const parse::ParseException& e)
5604 	{
5605 		mprintf(("TABLES: Unable to parse '%s'!  Error message = %s.\n", filename, e.what()));
5606 		return;
5607 	}
5608 }
5609 
5610 int ship_show_velocity_dot = 0;
5611 
5612 
DCF_BOOL(show_velocity_dot,ship_show_velocity_dot)5613 DCF_BOOL( show_velocity_dot, ship_show_velocity_dot )
5614 
5615 static bool ballistic_possible_for_this_ship(const ship_info *sip)
5616 {
5617 	// has no weapons!
5618 	if (sip->num_primary_banks < 1)
5619 		return false;
5620 
5621 	for (int i = 0; i < MAX_SHIP_PRIMARY_BANKS; i++)
5622 	{
5623 		// check default weapons
5624 		if (sip->primary_bank_weapons[i] >= 0 && Weapon_info[sip->primary_bank_weapons[i]].wi_flags[Weapon::Info_Flags::Ballistic])
5625 			return true;
5626 
5627 		// check allowed weapons
5628 		if (!sip->allowed_bank_restricted_weapons.empty()) {
5629 			for (int j = 0; j < weapon_info_size(); ++j)
5630 			{
5631 				if (sip->allowed_bank_restricted_weapons[i][j] && (Weapon_info[j].wi_flags[Weapon::Info_Flags::Ballistic]))
5632 					return true;
5633 			}
5634 		}
5635 	}
5636 
5637 	return false;
5638 }
5639 
5640 /**
5641  * Clean up ship entries, making sure various flags and settings are correct
5642  */
ship_parse_post_cleanup()5643 static void ship_parse_post_cleanup()
5644 {
5645 	int j;
5646 	char name_tmp[NAME_LENGTH];
5647 
5648 	for (auto sip = Ship_info.begin(); sip != Ship_info.end(); ++sip)
5649 	{
5650 		// ballistic primary fixage...
5651 		{
5652 			bool pbank_capacity_specified = false;
5653 
5654 			// determine whether this ship had primary capacities specified for it
5655 			for (j = 0; j < sip->num_primary_banks; j++) {
5656 				if (sip->primary_bank_ammo_capacity[j] > 0) {
5657 					pbank_capacity_specified = true;
5658 					break;
5659 				}
5660 			}
5661 
5662 			// be friendly; ensure ballistic flags check out
5663 			if (!pbank_capacity_specified) {
5664 				if ( ballistic_possible_for_this_ship(&(*sip)) ) {
5665 					Warning(LOCATION, "Pbank capacity not specified for ballistic-primary-enabled ship %s.\nDefaulting to capacity of 1 per bank.\n", sip->name);
5666 
5667 					for (j = 0; j < MAX_SHIP_PRIMARY_BANKS; j++) {
5668 						sip->primary_bank_ammo_capacity[j] = 1;
5669 					}
5670 				}
5671 			}
5672 		}
5673 
5674 		// ultra stupid compatbility handling for the once broken "generate hud" flag.
5675 		// it was previously testing the afterburner flag, so that's what we check for that
5676 		if ( (sip->shield_icon_index == 255) && (sip->flags[Ship::Info_Flags::Afterburner])
5677 				&& !(sip->flags[Ship::Info_Flags::Generate_hud_icon]) && (sip->flags[Ship::Info_Flags::Player_ship]) )
5678 		{
5679 			Warning(LOCATION, "Compatibility warning:\nNo shield icon specified for '%s' but the \"generate icon\" flag is not specified.\nEnabling flag by default.\n", sip->name);
5680 			sip->flags.set(Ship::Info_Flags::Generate_hud_icon);
5681 		}
5682 
5683 		// if we have a ship copy, then check to be sure that our base ship exists
5684 		if (sip->flags[Ship::Info_Flags::Ship_copy])
5685 		{
5686 			strcpy_s(name_tmp, sip->name);
5687 
5688 			if (end_string_at_first_hash_symbol(name_tmp))
5689 			{
5690 				if (ship_info_lookup(name_tmp) < 0)
5691 				{
5692 					Warning(LOCATION, "Ship %s is a copy, but base ship %s couldn't be found.", sip->name, name_tmp);
5693 					sip->flags.remove(Ship::Info_Flags::Ship_copy);
5694 				}
5695 			}
5696 			else
5697 			{
5698 				Warning(LOCATION, "Ship %s is defined as a copy (ship flag 'ship copy' is set), but is not named like one (no '#').\n", sip->name);
5699 				sip->flags.remove(Ship::Info_Flags::Ship_copy);
5700 			}
5701 		}
5702 
5703 		// very low rotational velocity values disable rotational collisions
5704 		// warn early rather than ambush the modder @ runtime (unless the ship is also no-collide!)
5705 		// the 2nd part of this check is duplicated from collideshipship.cpp:ship_ship_check_collision()
5706 		if (!(sip->flags[Ship::Info_Flags::No_collide]) && (vm_vec_mag_squared( &sip->max_rotvel ) * .04) >= (PI*PI/4))
5707 		{
5708 			Warning(LOCATION, "$Rotation time: too low; this will disable rotational collisions. All three variables should be >= 1.39.\nFix this in ship '%s'\n", sip->name);
5709 		}
5710 
5711 		// ensure player min velocity makes sense
5712 		if (sip->min_vel.xyz.x > sip->max_vel.xyz.x || sip->min_vel.xyz.x < 0.0f)
5713 		{
5714 			error_display(0, "$Player Minimum Velocity X-value (%f) is negative or greater than max velocity X-value (%f), setting to zero\nFix for ship '%s'\n",
5715 					sip->min_vel.xyz.x, sip->max_vel.xyz.x, sip->name);
5716 			sip->min_vel.xyz.x = 0.0f;
5717 		}
5718 		if (sip->min_vel.xyz.y > sip->max_vel.xyz.y || sip->min_vel.xyz.y < 0.0f)
5719 		{
5720 			error_display(0, "$Player Minimum Velocity Y-value (%f) is negative or greater than max velocity Y-value (%f), setting to zero\nFix for ship '%s'\n",
5721 					sip->min_vel.xyz.y, sip->max_vel.xyz.y, sip->name);
5722 			sip->min_vel.xyz.y = 0.0f;
5723 		}
5724 		if (sip->min_vel.xyz.z > sip->max_vel.xyz.z || sip->min_vel.xyz.z < 0.0f)
5725 		{
5726 			error_display(0, "$Player Minimum Velocity Z-value (%f) is negative or greater than max velocity Z-value (%f), setting to zero\nFix for ship '%s'\n",
5727 					sip->min_vel.xyz.z, sip->max_vel.xyz.z, sip->name);
5728 			sip->min_vel.xyz.z = 0.0f;
5729 		}
5730 	}
5731 
5732 	// check also target groups here
5733 	size_t n_tgt_groups = Ai_tp_list.size();
5734 
5735 	if (n_tgt_groups > 0) {
5736 		for(size_t i = 0; i < n_tgt_groups; i++) {
5737 			if (!(Ai_tp_list[i].obj_flags.any_set() || Ai_tp_list[i].sif_flags.any_set() || Ai_tp_list[i].wif_flags.any_set())) {
5738 				//had none of these, check next
5739 				if (Ai_tp_list[i].obj_type == -1) {
5740 					//didn't have this one
5741 					if (!(Ai_tp_list[i].ship_class.size() || Ai_tp_list[i].ship_type.size() || Ai_tp_list[i].weapon_class.size())) {
5742 						// had nothing - time to issue a warning
5743 						Warning(LOCATION, "Target priority group '%s' had no targeting rules issued for it.\n", Ai_tp_list[i].name);
5744 					}
5745 				}
5746 			}
5747 		}
5748 	}
5749 
5750 	// Clear out ship templates, since they're no longer needed. -MageKing17
5751 	Ship_templates.clear();
5752 }
5753 
5754 /**
5755  * Called once at the beginning of the game to parse ships.tbl and stuff the ::Ship_info
5756  * vector
5757  */
ship_init()5758 void ship_init()
5759 {
5760 	if ( !Ships_inited )
5761 	{
5762 		//Initialize Ignore_List for targeting
5763 		set_default_ignore_list();
5764 
5765 		//Parse main TBL first
5766 		if (cf_exists_full("objecttypes.tbl", CF_TYPE_TABLES))
5767 			parse_shiptype_tbl("objecttypes.tbl");
5768 		else
5769 			parse_shiptype_tbl(NULL);
5770 
5771 		//Then other ones
5772 		parse_modular_table(NOX("*-obt.tbm"), parse_shiptype_tbl);
5773 
5774 		// DO ALL THE STUFF WE NEED TO DO AFTER LOADING Ship_types
5775 		ship_type_info *stp;
5776 
5777 		uint i,j;
5778 		int idx;
5779 		for(i = 0; i < Ship_types.size(); i++)
5780 		{
5781 			stp = &Ship_types[i];
5782 
5783 			//Handle active pursuit
5784 			for(j = 0; j < stp->ai_actively_pursues_temp.size(); j++)
5785 			{
5786 				idx = ship_type_name_lookup((char*)stp->ai_actively_pursues_temp[j].c_str());
5787 				if(idx >= 0) {
5788 					stp->ai_actively_pursues.push_back(idx);
5789 				}
5790 			}
5791 			stp->ai_actively_pursues_temp.clear();
5792 
5793 			//Handle disabled/disarmed behaviour
5794 			for(j = 0; j < stp->ai_cripple_ignores_temp.size(); j++) {
5795 				idx = ship_type_name_lookup((char*)stp->ai_cripple_ignores_temp[j].c_str());
5796 				if(idx >= 0) {
5797 					stp->ai_cripple_ignores.push_back(idx);
5798 				}
5799 			}
5800 			stp->ai_cripple_ignores_temp.clear();
5801 		}
5802 
5803 		//ships.tbl
5804 		{
5805 			Num_engine_wash_types = 0;
5806 			strcpy_s(default_player_ship, "");
5807 
5808 			//Parse main TBL first
5809 			Removed_ships.clear();
5810 			Ship_info.clear();
5811 			parse_shiptbl("ships.tbl");
5812 
5813 			//Then other ones
5814 			parse_modular_table(NOX("*-shp.tbm"), parse_shiptbl);
5815 
5816 			ship_parse_post_cleanup();
5817 
5818 			Ships_inited = true;
5819 		}
5820 
5821 		// We shouldn't already have any subsystem pointers at this point.
5822 		Assertion(Ship_subsystems.empty(), "Some pre-allocated subsystems didn't get cleared out: " SIZE_T_ARG " batches present during ship_init(); get a coder!\n", Ship_subsystems.size());
5823 	}
5824 
5825 	ship_level_init();	// needed for FRED
5826 }
5827 
5828 static int Man_thruster_reset_timestamp = 0;
5829 
ship_clear_subsystems()5830 static void ship_clear_subsystems()
5831 {
5832 	for (auto it = Ship_subsystems.begin(); it != Ship_subsystems.end(); ++it) {
5833 		delete[] *it;
5834 	}
5835 	Ship_subsystems.clear();
5836 
5837 	Num_ship_subsystems = 0;
5838 	Num_ship_subsystems_allocated = 0;
5839 
5840 	Triggered_rotations.clear();
5841 }
5842 
ship_allocate_subsystems(int num_so,bool page_in=false)5843 static int ship_allocate_subsystems(int num_so, bool page_in = false)
5844 {
5845 	int i;
5846 	int num_subsystems_save = 0;
5847 
5848 	// "0" itself is safe
5849 	if (num_so < 0) {
5850 		Int3();
5851 		return 0;
5852 	}
5853 
5854 	// allow a page-in thingy, so that we can grab as much as possible before mission
5855 	// start, but without messing up our count for future things
5856 	if (page_in)
5857 		num_subsystems_save = Num_ship_subsystems;
5858 
5859 	Num_ship_subsystems += num_so;
5860 
5861 	// bail if we don't actually need any more
5862 	if ( Num_ship_subsystems < Num_ship_subsystems_allocated )
5863 		return 1;
5864 
5865 	mprintf(("Allocating space for at least %i new ship subsystems ... ", num_so));
5866 
5867 	// we might need more than one set worth of new subsystems, so make as many as required
5868 	do {
5869 		ship_subsys* new_batch = new ship_subsys[NUM_SHIP_SUBSYSTEMS_PER_SET];
5870 		Ship_subsystems.push_back(new_batch);
5871 
5872 		// append the new set to our free list
5873 		for (i = 0; i < NUM_SHIP_SUBSYSTEMS_PER_SET; i++)
5874 			list_append( &ship_subsys_free_list, &new_batch[i] );
5875 
5876 		Num_ship_subsystems_allocated += NUM_SHIP_SUBSYSTEMS_PER_SET;
5877 	} while ( (Num_ship_subsystems - Num_ship_subsystems_allocated) > 0 );
5878 
5879 	if (page_in)
5880 		Num_ship_subsystems = num_subsystems_save;
5881 
5882 	mprintf(("a total of %i is now available (%i in-use).\n", Num_ship_subsystems_allocated, Num_ship_subsystems));
5883 	return 1;
5884 }
5885 
5886 /**
5887  * This will get called at the start of each level.
5888  */
ship_level_init()5889 void ship_level_init()
5890 {
5891 	int i;
5892 
5893 	// Reset everything between levels
5894 	Ships_exited.clear();
5895 	Ships_exited.reserve(100);
5896 	for (i=0; i<MAX_SHIPS; i++ )
5897 	{
5898 		Ships[i].ship_name[0] = '\0';
5899 		Ships[i].objnum = -1;
5900 	}
5901 
5902 	Num_wings = 0;
5903 	for (i = 0; i < MAX_WINGS; i++ )
5904 	{
5905 		Wings[i].num_waves = -1;
5906 		Wings[i].wing_squad_filename[0] = '\0';
5907 		Wings[i].wing_insignia_texture = -1;	// Goober5000 - default to no wing insignia
5908 												// don't worry about releasing textures because
5909 												// they are released automatically when the model
5910 												// is unloaded (because they are part of the model)
5911 	}
5912 
5913 	for (i=0; i<MAX_STARTING_WINGS; i++)
5914 		Starting_wings[i] = -1;
5915 
5916 	for (i=0; i<MAX_SQUADRON_WINGS; i++)
5917 		Squadron_wings[i] = -1;
5918 
5919 	for (i=0; i<MAX_TVT_WINGS; i++)
5920 		TVT_wings[i] = -1;
5921 
5922 	// Goober5000
5923 
5924 	// set starting wing names to default
5925 	strcpy_s(Starting_wing_names[0], "Alpha");
5926 	strcpy_s(Starting_wing_names[1], "Beta");
5927 	strcpy_s(Starting_wing_names[2], "Gamma");
5928 
5929 	// set squadron wing names to default
5930 	strcpy_s(Squadron_wing_names[0], "Alpha");
5931 	strcpy_s(Squadron_wing_names[1], "Beta");
5932 	strcpy_s(Squadron_wing_names[2], "Gamma");
5933 	strcpy_s(Squadron_wing_names[3], "Delta");
5934 	strcpy_s(Squadron_wing_names[4], "Epsilon");
5935 
5936 	// set tvt wing names to default
5937 	strcpy_s(TVT_wing_names[0], "Alpha");
5938 	strcpy_s(TVT_wing_names[1], "Zeta");
5939 
5940 	// clear out ship registry
5941 	Ship_registry.clear();
5942 	Ship_registry_map.clear();
5943 
5944 
5945 	// Empty the subsys list
5946 	ship_clear_subsystems();
5947 	list_init( &ship_subsys_free_list );
5948 
5949 	Laser_energy_out_snd_timer = 1;
5950 	Missile_out_snd_timer		= 1;
5951 
5952 	ship_obj_list_init();
5953 
5954 	Ship_cargo_check_timer = 1;
5955 
5956 	shipfx_large_blowup_level_init();
5957 
5958 	Man_thruster_reset_timestamp = timestamp(0);
5959 }
5960 
5961 /**
5962  * Add a ship onto the exited ships list.
5963  *
5964  * The reason parameter tells us why the ship left the mission (i.e. departed or destroyed)
5965  */
ship_add_exited_ship(ship * sp,Ship::Exit_Flags reason)5966 void ship_add_exited_ship( ship *sp, Ship::Exit_Flags reason )
5967 {
5968 	exited_ship entry;
5969 
5970 	strcpy_s(entry.ship_name, sp->ship_name );
5971 	entry.display_name = sp->get_display_name();
5972 	entry.obj_signature = Objects[sp->objnum].signature;
5973 	entry.ship_class = sp->ship_info_index;
5974 	entry.team = sp->team;
5975 	entry.flags += reason;
5976 	// if ship is red alert, flag as such
5977 	if (sp->flags[Ship_Flags::Red_alert_store_status]) {
5978         entry.flags.set(Ship::Exit_Flags::Red_alert_carry);
5979 	}
5980 	entry.time = Missiontime;
5981 	entry.hull_strength = int(Objects[sp->objnum].hull_strength);
5982 
5983 	entry.cargo1 = sp->cargo1;
5984 
5985 	entry.time_cargo_revealed = (fix)0;
5986 	if ( sp->flags[Ship_Flags::Cargo_revealed] )
5987 	{
5988         entry.flags.set(Ship::Exit_Flags::Cargo_known);
5989 		entry.time_cargo_revealed = sp->time_cargo_revealed;
5990 	}
5991 
5992     if (sp->time_first_tagged > 0) {
5993 		entry.flags.set(Ship::Exit_Flags::Been_tagged);
5994 	}
5995 
5996 	//copy across the damage_ship arrays
5997 	for (int i = 0; i < MAX_DAMAGE_SLOTS ; i++) {
5998 		entry.damage_ship_id[i] = sp->damage_ship_id[i] ;
5999 		entry.damage_ship[i] = sp->damage_ship[i] ;
6000 	}
6001 
6002 	// record this in the ship registry
6003 	auto ship_it = Ship_registry_map.find(sp->ship_name);
6004 	if (ship_it != Ship_registry_map.end())
6005 		Ship_registry[ship_it->second].exited_index = static_cast<int>(Ships_exited.size());
6006 
6007 	Ships_exited.push_back(entry);
6008 }
6009 
6010 /**
6011  * Attempt to find information about an exited ship based on shipname
6012  */
ship_find_exited_ship_by_name(const char * name)6013 int ship_find_exited_ship_by_name( const char *name )
6014 {
6015 	int i;
6016 
6017 	for (i = 0; i < (int)Ships_exited.size(); i++) {
6018 		if ( !stricmp(name, Ships_exited[i].ship_name) )
6019 			return i;
6020 	}
6021 
6022 	return -1;
6023 }
6024 
6025 /**
6026  * Attempt to find information about an exited ship based on signature
6027  */
ship_find_exited_ship_by_signature(int signature)6028 int ship_find_exited_ship_by_signature( int signature )
6029 {
6030 	int i;
6031 
6032 	for (i = 0; i < (int)Ships_exited.size(); i++) {
6033 		if ( signature == Ships_exited[i].obj_signature )
6034 			return i;
6035 	}
6036 
6037 	return -1;
6038 }
6039 
6040 
physics_ship_init(object * objp)6041 void physics_ship_init(object *objp)
6042 {
6043 	ship_info	*sinfo = &Ship_info[Ships[objp->instance].ship_info_index];
6044 	physics_info	*pi = &objp->phys_info;
6045 	polymodel *pm = model_get(sinfo->model_num);
6046 
6047 	// use mass and I_body_inv from POF read into polymodel
6048 	physics_init(pi);
6049 
6050 	if (sinfo->density == 0) {
6051 		sinfo->density = 1;
6052 		nprintf(("Physics", "pi->density==0.0f. setting to 1\n"));
6053 		Warning(LOCATION, "%s has 0 density! setting to 1", sinfo->name);
6054 	}
6055 
6056 	if (pm->mass==0.0f)
6057 	{
6058 		// make a guess for the ship's mass
6059 		vec3d size;
6060 		vm_vec_sub(&size,&pm->maxs,&pm->mins);
6061 		float vmass=size.xyz.x*size.xyz.y*size.xyz.z;
6062 		float amass=4.65f*(float)pow(vmass,(2.0f/3.0f));
6063 
6064 		nprintf(("Physics", "pi->mass==0.0f. setting to %f\n",amass));
6065 		Warning(LOCATION, "%s (%s) has no mass! setting to %f", sinfo->name, sinfo->pof_file, amass);
6066 		pm->mass=amass;
6067 		pi->mass=amass * sinfo->density;
6068 	}
6069 	else
6070 		pi->mass = pm->mass * sinfo->density;
6071 
6072 	// it was print-worthy back in read_model_file() in modelread.cpp, but now that its being used for an actual ship the user should be warned.
6073 	if (IS_VEC_NULL(&pm->moment_of_inertia.vec.fvec)
6074 		&& IS_VEC_NULL(&pm->moment_of_inertia.vec.uvec)
6075 		&& IS_VEC_NULL(&pm->moment_of_inertia.vec.rvec))
6076 		Warning(LOCATION, "%s (%s) has a null moment of inertia!", sinfo->name, sinfo->pof_file);
6077 
6078 	// if invalid they were already warned about this in read_model_file() in modelread.cpp, so now we just need to try and sweep it under the rug
6079 	if (!is_valid_matrix(&pm->moment_of_inertia))
6080 	{
6081 		// TODO: generate MOI properly
6082 		vm_mat_zero(&pi->I_body_inv);
6083 	}
6084 	// it's valid, so we can use it
6085 	else
6086 		pi->I_body_inv = pm->moment_of_inertia;
6087 
6088 	// scale inverse moment of inertia value by inverse density
6089 	vm_vec_scale( &pi->I_body_inv.vec.rvec, 1/sinfo->density );
6090 	vm_vec_scale( &pi->I_body_inv.vec.uvec, 1/sinfo->density );
6091 	vm_vec_scale( &pi->I_body_inv.vec.fvec, 1/sinfo->density );
6092 
6093 	pi->center_of_mass = pm->center_of_mass;
6094 	pi->side_slip_time_const = sinfo->damp;
6095 	pi->delta_bank_const = sinfo->delta_bank_const;
6096 	pi->rotdamp = sinfo->rotdamp;
6097 	pi->max_vel = sinfo->max_vel;
6098 	pi->afterburner_max_vel = sinfo->afterburner_max_vel;
6099 	pi->max_rotvel = sinfo->max_rotvel;
6100 	pi->max_rear_vel = sinfo->max_rear_vel;
6101 	pi->flags |= PF_ACCELERATES;
6102 	pi->flags &= ~PF_GLIDING; //Turn off glide
6103 	pi->flags &= ~PF_FORCE_GLIDE;
6104 
6105 	pi->forward_accel_time_const=sinfo->forward_accel;
6106 	pi->afterburner_forward_accel_time_const=sinfo->afterburner_forward_accel;
6107 	pi->forward_decel_time_const=sinfo->forward_decel;
6108 	pi->slide_accel_time_const=sinfo->slide_accel;
6109 	pi->slide_decel_time_const=sinfo->slide_decel;
6110 
6111 	if ( (pi->max_vel.xyz.x > 0.000001f) || (pi->max_vel.xyz.y > 0.000001f) )
6112 		pi->flags |= PF_SLIDE_ENABLED;
6113 
6114 	pi->cur_glide_cap = pi->max_vel.xyz.z; //Init dynamic glide cap stuff to the max vel.
6115 	if ( sinfo->glide_cap > 0.000001f || sinfo->glide_cap < -0.000001f )		//Backslash
6116 		pi->glide_cap = sinfo->glide_cap;
6117 	else
6118 		pi->glide_cap = MAX(MAX(pi->max_vel.xyz.z, sinfo->max_overclocked_speed), pi->afterburner_max_vel.xyz.z);
6119 	// If there's not a value for +Max Glide Speed set in the table, we want this cap to default to the fastest speed the ship can go.
6120 	// However, a negative value means we want no cap, thus allowing nearly infinite maximum gliding speeds.
6121 
6122 	//SUSHI: If we are using dynamic glide capping, force the glide cap to 0 (understood by physics.cpp to mean the cap should be dynamic)
6123 	if (sinfo->glide_dynamic_cap)
6124 		pi->glide_cap = 0;
6125 
6126 	pi->glide_accel_mult = sinfo->glide_accel_mult;
6127 
6128 	//SUSHI: This defaults to the AI_Profile value, and is only optionally overridden
6129 	if (The_mission.ai_profile->flags[AI::Profile_Flags::Use_newtonian_dampening])
6130 		pi->flags |= PF_NEWTONIAN_DAMP;
6131 	if (sinfo->newtonian_damp_override)
6132 	{
6133 		if (sinfo->use_newtonian_damp)
6134 			pi->flags |= PF_NEWTONIAN_DAMP;
6135 		else
6136 			pi->flags &= ~PF_NEWTONIAN_DAMP;
6137 	}
6138 
6139 	vm_vec_zero(&pi->vel);
6140 	vm_vec_zero(&pi->rotvel);
6141 	pi->speed = 0.0f;
6142 	pi->heading = 0.0f;
6143 	vm_set_identity(&pi->last_rotmat);
6144 
6145 	//SparK: setting the reverse burners
6146 	pi->afterburner_max_reverse_vel = sinfo->afterburner_max_reverse_vel;
6147 	pi->afterburner_reverse_accel = sinfo->afterburner_reverse_accel;
6148 }
6149 
6150 /**
6151  * Get the type of the given ship as a string
6152  */
ship_get_type(char * output,ship_info * sip)6153 int ship_get_type(char* output, ship_info *sip)
6154 {
6155 	if(sip->class_type < 0) {
6156 		strcpy(output, "Unknown");
6157 		return 0;
6158 	}
6159 
6160 	strcpy(output, Ship_types[sip->class_type].name);
6161 	return 1;
6162 }
6163 
6164 /**
6165  * Set the orders allowed for a ship -- based on ship type.
6166  *
6167  * This value might get overridden by a value in the mission file.
6168  */
ship_get_default_orders_accepted(ship_info * sip)6169 int ship_get_default_orders_accepted( ship_info *sip )
6170 {
6171 	if(sip->class_type >= 0) {
6172 		return Ship_types[sip->class_type].ai_player_orders;
6173 	} else {
6174 		return 0;
6175 	}
6176 }
6177 
get_submodel_offset(int model,int submodel)6178 vec3d get_submodel_offset(int model, int submodel){
6179 	polymodel*pm = model_get(model);
6180 	if(pm->submodel[submodel].parent == -1)
6181 		return pm->submodel[submodel].offset;
6182 	vec3d ret = pm->submodel[submodel].offset;
6183 	vec3d v = get_submodel_offset(model,pm->submodel[submodel].parent);
6184 	vm_vec_add2(&ret, &v);
6185 	return ret;
6186 
6187 }
6188 
6189 // Reset all ship values to empty/unused.
clear()6190 void ship::clear()
6191 {
6192 	int i, j;
6193 
6194 	objnum = -1;
6195 	ai_index = -1;
6196 	ship_info_index = -1;
6197 	hotkey = -1;
6198 	escort_priority = 0;
6199 	score = 0;
6200 	assist_score_pct = 0.0f;
6201 	respawn_priority = 0;
6202 
6203 	pre_death_explosion_happened = 0;
6204 	wash_killed = 0;	// serenity lies
6205 	cargo1 = 0;							// "Nothing"
6206 
6207 	wing_status_wing_index = -1;
6208 	wing_status_wing_pos = -1;
6209 
6210 	alt_type_index = -1;
6211 	callsign_index = -1;
6212 
6213 	targeting_laser_bank = -1;
6214 	targeting_laser_objnum = -1;
6215 
6216 	num_corkscrew_to_fire = 0;
6217 	corkscrew_missile_bank = -1;
6218 	next_corkscrew_fire = timestamp(0);
6219 
6220 	final_death_time = timestamp(-1);
6221 	death_time = timestamp(-1);
6222 	end_death_time = timestamp(-1);
6223 	really_final_death_time = timestamp(-1);
6224 	deathroll_rotvel = vmd_zero_vector;
6225 
6226 	if (warpin_effect != nullptr)
6227 		delete warpin_effect;
6228 	if (warpout_effect != nullptr)
6229 		delete warpout_effect;
6230 	warpin_effect = nullptr;
6231 	warpout_effect = nullptr;
6232 
6233 	warpin_params_index = -1;
6234 	warpout_params_index = -1;
6235 
6236 	next_fireball = timestamp(-1);
6237 	next_hit_spark = timestamp(-1);
6238 	num_hits = 0;
6239 	memset(sparks, 0, MAX_SHIP_HITS * sizeof(ship_spark));
6240 
6241 	use_special_explosion = false;
6242 	special_exp_damage = -1;
6243 	special_exp_blast = -1;
6244 	special_exp_inner = -1;
6245 	special_exp_outer = -1;
6246 	use_shockwave = false;
6247 	special_exp_shockwave_speed = 0;
6248 	special_exp_deathroll_time = 0;
6249 
6250 	special_hitpoints = 0;
6251 	special_shield = -1;
6252 
6253 	shield_points.clear();
6254 
6255 	ship_max_shield_strength = 0.0f;
6256 	ship_max_hull_strength = 0.0f;
6257 
6258 	ship_guardian_threshold = 0;
6259 
6260 	ship_name[0] = 0;
6261 	display_name.clear();
6262 	team = 0;
6263 
6264 	time_cargo_revealed = 0;
6265 
6266 	arrival_location = 0;
6267 	arrival_distance = 0;
6268 	arrival_anchor = -1;
6269 	arrival_path_mask = 0;
6270 	arrival_cue = -1;
6271 	arrival_delay = 0;
6272 
6273 	departure_location = 0;
6274 	departure_anchor = -1;
6275 	departure_path_mask = 0;
6276 	departure_cue = -1;
6277 	departure_delay = 0;
6278 
6279 	wingnum = -1;
6280 	orders_accepted = 0;
6281 
6282 	subsys_list.clear();
6283 	// since these aren't cleared by clear()
6284 	subsys_list.next = NULL;
6285 	subsys_list.prev = NULL;
6286 
6287 	memset(&subsys_info, 0, SUBSYSTEM_MAX * sizeof(ship_subsys_info));
6288 
6289 	memset(last_targeted_subobject, 0, MAX_PLAYERS * sizeof(ship_subsys *));
6290 
6291 	shield_recharge_index = INTIAL_SHIELD_RECHARGE_INDEX;
6292 	weapon_recharge_index = INTIAL_WEAPON_RECHARGE_INDEX;
6293 	engine_recharge_index = INTIAL_ENGINE_RECHARGE_INDEX;
6294 	weapon_energy = 0;
6295 	current_max_speed = 0.0f;
6296 	next_manage_ets = timestamp(0);
6297 
6298 	flags.reset();
6299 	reinforcement_index = -1;
6300 
6301 	afterburner_fuel = 0.0f;
6302 	afterburner_last_engage_fuel = 0.0f;
6303 	afterburner_last_end_time = 0;
6304 
6305 	cmeasure_count = 0;
6306 	current_cmeasure = -1;
6307 
6308 	cmeasure_fire_stamp = timestamp(0);
6309 
6310 	target_shields_delta = 0.0f;
6311 	target_weapon_energy_delta = 0.0f;
6312 
6313 	weapons.clear();
6314 
6315 	// ---------- special weapons init that isn't setting things to 0
6316 	for (i = 0; i < MAX_SHIP_PRIMARY_BANKS; i++)
6317 	{
6318 		// not part of weapons!
6319 		primary_rotate_rate[i] = 0.0f;
6320 		primary_rotate_ang[i] = 0.0f;
6321 		last_fired_point[i] = 0;
6322 		// for fighter beams
6323 		was_firing_last_frame[i] = 0;
6324 	}
6325 
6326 	secondary_point_reload_pct.init(0, 0, 0.0f);
6327 	// ---------- done with weapons init
6328 
6329 	shield_hits = 0;
6330 
6331 	wash_intensity = 0.0f;
6332 	wash_rot_axis = vmd_zero_vector;
6333 	wash_timestamp = timestamp(0);
6334 
6335 	num_swarm_missiles_to_fire = 0;
6336 	next_swarm_fire = timestamp(0);
6337 	num_turret_swarm_info = 0;
6338 	swarm_missile_bank = -1;
6339 
6340 	group = -1;
6341 	death_roll_snd  = sound_handle::invalid();
6342 	ship_list_index = -1;
6343 
6344 	thruster_bitmap = -1;
6345 	thruster_frame = 0.0f;
6346 
6347 	thruster_glow_bitmap = -1;
6348 	thruster_glow_frame = 0.0f;
6349 	thruster_glow_noise = 1.0f;
6350 
6351 	thruster_secondary_glow_bitmap = -1;
6352 	thruster_tertiary_glow_bitmap = -1;
6353 	thruster_distortion_bitmap = -1;
6354 
6355 	next_engine_stutter = timestamp(0);
6356 
6357 	base_texture_anim_frametime = 0;
6358 
6359 	total_damage_received = 0.0f;
6360 	memset(&damage_ship, 0, MAX_DAMAGE_SLOTS * sizeof(float));
6361 	for(i = 0; i < MAX_DAMAGE_SLOTS; i++)
6362 		damage_ship_id[i] = -1;
6363 
6364 	persona_index = -1;
6365 
6366 	subsys_disrupted_flags = 0;
6367 	subsys_disrupted_check_timestamp = timestamp(0);
6368 
6369 	create_time = 0;
6370 
6371 	ts_index = -1;
6372 
6373 	large_ship_blowup_index = -1;
6374 	for (i = 0; i < NUM_SUB_EXPL_HANDLES; i++)
6375 		sub_expl_sound_handle[i] = sound_handle::invalid();
6376 
6377 	memset(&arc_pts, 0, MAX_SHIP_ARCS * 2 * sizeof(vec3d));
6378 	for (i = 0; i < MAX_SHIP_ARCS; i++)
6379 		arc_timestamp[i] = timestamp(-1);
6380 	memset(&arc_type, 0, MAX_SHIP_ARCS * sizeof(ubyte));
6381 	arc_next_time = timestamp(-1);
6382 
6383 	emp_intensity = -1.0f;
6384 	emp_decr = 0.0f;
6385 
6386 	memset(trail_ptr, 0, MAX_SHIP_CONTRAILS * sizeof(trail *));
6387 
6388 	tag_total = 0.0f;
6389 	tag_left = -1.0f;
6390 	time_first_tagged = 0;
6391 	level2_tag_total = 0.0f;
6392 	level2_tag_left = -1.0f;
6393 
6394 	lightning_stamp = timestamp(-1);
6395 
6396 	// set awacs warning flags so awacs ship only asks for help once at each level
6397     awacs_warning_flag.reset();
6398 
6399 	special_warpin_objnum = -1;
6400 	special_warpout_objnum = -1;
6401 
6402 	fighter_beam_turret_data.clear();
6403 	beam_sys_info.reset();
6404 
6405 	primitive_sensor_range = DEFAULT_SHIP_PRIMITIVE_SENSOR_RANGE;
6406 
6407 	ship_replacement_textures = NULL;
6408 
6409 	current_viewpoint = -1;
6410 
6411 	for (i = 0; i < MAX_SHIP_CONTRAILS; i++)
6412 		ABtrail_ptr[i] = NULL;
6413 	memset(&ab_info, 0, MAX_SHIP_CONTRAILS * sizeof(trail_info));
6414 	ab_count = 0;
6415 
6416 	glow_point_bank_active.clear();
6417 
6418 	shader_effect_num = 0;
6419 	shader_effect_duration = 0;
6420 	shader_effect_start_time = 0;
6421 	shader_effect_active = false;
6422 
6423 	last_fired_turret = NULL;
6424 
6425 	bay_doors_anim_done_time = 0;
6426 	bay_doors_status = MA_POS_NOT_SET;
6427 	bay_doors_wanting_open = 0;
6428 	bay_doors_launched_from = 0;
6429 	bay_doors_need_open = false;
6430 	bay_doors_parent_shipnum = -1;
6431 
6432 	for(i = 0; i < MAX_MAN_THRUSTERS; i++)
6433 	{
6434 		thrusters_start[i] = timestamp(-1);
6435 		thrusters_sounds[i] = -1;
6436 	}
6437 
6438 	s_alt_classes.clear();
6439 
6440 	for(i=0;i<MAX_IFFS;i++)
6441 		for(j=0;j<MAX_IFFS;j++)
6442 			ship_iff_color[i][j] = -1;
6443 
6444 	ammo_low_complaint_count = 0;
6445 
6446 	armor_type_idx = -1;
6447 	shield_armor_type_idx = -1;
6448 	collision_damage_type_idx = -1;
6449 	debris_damage_type_idx = -1;
6450 	debris_net_sig = 0;
6451 
6452 	model_instance_num = -1;
6453 
6454 	time_created = 0;
6455 
6456 	radar_visible_since = -1;
6457 	radar_last_contact = -1;
6458 
6459 	radar_last_status = VISIBLE;
6460 	radar_current_status = VISIBLE;
6461 
6462 	team_name = "";
6463 	secondary_team_name = "";
6464 	team_change_timestamp = timestamp(-1);
6465 	team_change_time = 0;
6466 
6467 	autoaim_fov = 0.0f;
6468 }
has_display_name() const6469 bool ship::has_display_name() const {
6470 	return flags[Ship::Ship_Flags::Has_display_name];
6471 }
get_display_name() const6472 const char* ship::get_display_name() const {
6473 	if (has_display_name()) {
6474 		return display_name.c_str();
6475 	} else {
6476 		return ship_name;
6477 	}
6478 }
6479 
apply_replacement_textures(SCP_vector<texture_replace> & replacements)6480 void ship::apply_replacement_textures(SCP_vector<texture_replace> &replacements)
6481 {
6482 	if (!replacements.empty())
6483 	{
6484 		ship_replacement_textures = (int *) vm_malloc( MAX_REPLACEMENT_TEXTURES * sizeof(int));
6485 
6486 		for (auto i = 0; i < MAX_REPLACEMENT_TEXTURES; i++)
6487 			ship_replacement_textures[i] = -1;
6488 	}
6489 
6490 	// now fill them in
6491 	for (auto tr : replacements)
6492 	{
6493 		auto pm = model_get(Ship_info[ship_info_index].model_num);
6494 
6495 		// look for textures
6496 		for (auto j = 0; j < pm->n_textures; j++)
6497 		{
6498 			texture_map *tmap = &pm->maps[j];
6499 
6500 			int tnum = tmap->FindTexture(tr.old_texture);
6501 			if(tnum > -1)
6502 				ship_replacement_textures[j * TM_NUM_TYPES + tnum] = tr.new_texture_id;
6503 		}
6504 	}
6505 }
6506 
clear()6507 void ship_weapon::clear()
6508 {
6509     flags.reset();
6510 
6511     num_primary_banks = 0;
6512     num_secondary_banks = 0;
6513     num_tertiary_banks = 0;
6514 
6515     current_primary_bank = 0;
6516     current_secondary_bank = 0;
6517     current_tertiary_bank = 0;
6518 
6519     previous_primary_bank = 0;
6520     previous_secondary_bank = 0;
6521 
6522     next_tertiary_fire_stamp = timestamp(0);
6523 
6524     for (int i = 0; i < MAX_SHIP_PRIMARY_BANKS; i++)
6525     {
6526         primary_bank_weapons[i] = -1;
6527 
6528 		primary_bank_external_model_instance[i] = -1;
6529 		primary_bank_model_instance_check[i] = false;
6530 
6531         next_primary_fire_stamp[i] = timestamp(0);
6532         last_primary_fire_stamp[i] = timestamp(-1);
6533         last_primary_fire_sound_stamp[i] = timestamp(0);
6534 
6535         primary_bank_slot_count[i] = 1;
6536 
6537         primary_bank_rearm_time[i] = timestamp(0);
6538         primary_animation_done_time[i] = timestamp(0);
6539         primary_bank_ammo[i] = 0;
6540         primary_bank_start_ammo[i] = 0;
6541         primary_bank_capacity[i] = 0;
6542         primary_next_slot[i] = 0;
6543         primary_bank_fof_cooldown[i] = 0;
6544 
6545         primary_animation_position[i] = EModelAnimationPosition::MA_POS_NOT_SET;
6546 
6547         primary_bank_pattern_index[i] = 0;
6548 
6549         burst_counter[i] = 0;
6550 		burst_seed[i] = Random::next();
6551         external_model_fp_counter[i] = 0;
6552     }
6553 
6554     for (int i = 0; i < MAX_SHIP_SECONDARY_BANKS; i++)
6555     {
6556         secondary_bank_weapons[i] = -1;
6557 
6558         next_secondary_fire_stamp[i] = timestamp(0);
6559         last_secondary_fire_stamp[i] = timestamp(-1);
6560 
6561         secondary_bank_rearm_time[i] = timestamp(0);
6562         secondary_animation_done_time[i] = timestamp(0);
6563 
6564         secondary_bank_ammo[i] = 0;
6565         secondary_bank_start_ammo[i] = 0;
6566         secondary_bank_capacity[i] = 0;
6567         secondary_next_slot[i] = 0;
6568 
6569 		secondary_animation_position[i] = EModelAnimationPosition::MA_POS_NOT_SET;
6570 
6571 		secondary_bank_pattern_index[i] = 0;
6572 
6573         burst_counter[i + MAX_SHIP_PRIMARY_BANKS] = 0;
6574         external_model_fp_counter[i + MAX_SHIP_PRIMARY_BANKS] = 0;
6575     }
6576 
6577     tertiary_bank_ammo = 0;
6578     tertiary_bank_start_ammo = 0;
6579     tertiary_bank_capacity = 0;
6580     tertiary_bank_rearm_time = timestamp(0);
6581 
6582     detonate_weapon_time = 0;
6583     ai_class = 0;
6584 
6585 	remote_detonaters_active = 0;
6586 
6587 	per_burst_rot = 0.f;
6588 }
6589 
ship_weapon()6590 ship_weapon::ship_weapon() {
6591 	clear();
6592 }
6593 
6594 // NOTE: Now that the clear() member function exists, this function only sets the stuff associated with the object and ship class.
ship_set(int ship_index,int objnum,int ship_type)6595 static void ship_set(int ship_index, int objnum, int ship_type)
6596 {
6597 	int i;
6598 	ship		*shipp = &Ships[ship_index];
6599 	object		*objp = &Objects[objnum];
6600 	ship_info	*sip = &(Ship_info[ship_type]);
6601 	ship_weapon	*swp = &shipp->weapons;
6602 	polymodel *pm = model_get(sip->model_num);
6603 
6604 	Assert(strlen(shipp->ship_name) <= NAME_LENGTH - 1);
6605 	shipp->ship_info_index = ship_type;
6606 	shipp->objnum = objnum;
6607 	shipp->score = sip->score;
6608 
6609 	ai_object_init(objp, shipp->ai_index);
6610 	physics_ship_init(objp);
6611 
6612 	if (Fred_running){
6613 		shipp->ship_max_hull_strength = 100.0f;
6614 	} else {
6615 		shipp->ship_max_hull_strength = sip->max_hull_strength;
6616 	}
6617 	objp->hull_strength = shipp->ship_max_hull_strength;
6618 
6619 	shipp->max_shield_recharge = sip->max_shield_recharge;
6620 
6621 	if (sip->flags[Ship::Info_Flags::Model_point_shields]) {
6622 		objp->n_quadrants = (int)pm->shield_points.size();
6623 		shipp->shield_points = pm->shield_points;
6624 		objp->shield_quadrant.resize(objp->n_quadrants);
6625 	}
6626 
6627 	if (Fred_running) {
6628 		shipp->ship_max_shield_strength = 100.0f;
6629 		objp->shield_quadrant[0] = 100.0f;
6630 	} else {
6631 		shipp->ship_max_shield_strength = sip->max_shield_strength;
6632 		shield_set_strength(objp, shield_get_max_strength(objp));
6633 	}
6634 
6635 	shipp->orders_accepted = ship_get_default_orders_accepted( sip );
6636 
6637 	if (!subsys_set(objnum))
6638 	{
6639 		char err_msg[512];
6640 		sprintf (err_msg, "Unable to allocate ship subsystems, which shouldn't be possible anymore. Current allocation is %d (%d in use). No subsystems have been assigned to %s.", Num_ship_subsystems_allocated, Num_ship_subsystems, shipp->ship_name);
6641 
6642 		if (Fred_running)
6643 			os::dialogs::Message(os::dialogs::MESSAGEBOX_ERROR, err_msg);
6644 		else
6645 			Error(LOCATION, "%s", err_msg);
6646 	}
6647 
6648 	ets_init_ship(objp);	// init ship fields that are used for the ETS
6649 
6650 	shipp->current_max_speed = Ship_info[ship_type].max_speed;
6651 
6652 	shipp->flags.set(Ship_Flags::Engines_on);
6653 
6654 	// set certain flags that used to be in ship_info - Goober5000
6655 	if (sip->flags[Ship::Info_Flags::Stealth])
6656 		shipp->flags.set(Ship_Flags::Stealth);
6657 	if (sip->flags[Ship::Info_Flags::Ship_class_dont_collide_invis])
6658 		shipp->flags.set(Ship_Flags::Dont_collide_invis);
6659 
6660 	auto obj_flags = objp->flags;
6661 	if (sip->flags[Ship::Info_Flags::No_collide])
6662 		obj_flags.remove(Object::Object_Flags::Collides);
6663 	else
6664 		obj_flags.set(Object::Object_Flags::Collides);
6665 
6666 	obj_set_flags(objp, obj_flags);
6667 
6668 	if (sip->flags[Ship::Info_Flags::No_ets])
6669 		shipp->flags.set(Ship_Flags::No_ets);
6670 
6671 	shipp->afterburner_fuel = sip->afterburner_fuel_capacity;
6672 
6673 	shipp->cmeasure_count = sip->cmeasure_max;
6674 	shipp->current_cmeasure = sip->cmeasure_type;
6675 
6676 	if (sip->num_primary_banks == 0 || swp->primary_bank_weapons[0] < 0) {
6677 		swp->current_primary_bank = -1;
6678 		swp->previous_primary_bank = -1;
6679 	}
6680 	if (sip->num_secondary_banks == 0 || swp->secondary_bank_weapons[0] < 0) {
6681 		swp->current_secondary_bank = -1;
6682 		swp->previous_secondary_bank = -1;
6683 	}
6684 	swp->current_tertiary_bank = -1;
6685 
6686 	swp->ai_class = Ai_info[shipp->ai_index].ai_class;
6687 
6688 	// handle ballistic primaries - kinda hackish; is this actually necessary?
6689 	// because I think it's not needed - when I accidentally left this unreachable
6690 	// it didn't cause any problems - Goober5000
6691 	for ( i = 0; i < sip->num_primary_banks; i++ )
6692 	{
6693 		float weapon_size = Weapon_info[sip->primary_bank_weapons[i]].cargo_size;
6694 
6695 		if ( weapon_size > 0.0f )
6696 		{
6697 			if (Fred_running)
6698 				swp->primary_bank_ammo[i] = 100;
6699 			else
6700 				swp->primary_bank_ammo[i] = (int)std::lround(sip->primary_bank_ammo_capacity[i] / weapon_size);
6701 		}
6702 	}
6703 
6704 	for ( i = 0; i < sip->num_secondary_banks; i++ )
6705 	{
6706 		float weapon_size = Weapon_info[sip->secondary_bank_weapons[i]].cargo_size;
6707 		Assertion( weapon_size > 0.0f, "Cargo size for secondary weapon %s is invalid, must be greater than 0.\n", Weapon_info[sip->secondary_bank_weapons[i]].name );
6708 
6709 		if (Fred_running)
6710 			swp->secondary_bank_ammo[i] = 100;
6711 		else
6712 			swp->secondary_bank_ammo[i] = (int)std::lround(sip->secondary_bank_ammo_capacity[i] / weapon_size);
6713 	}
6714 
6715 	// set initial reload percentages... used to be done in ship::clear()
6716 	int max_points_per_bank = 0;
6717 	for (i = 0; i < sip->num_secondary_banks; ++i)
6718 	{
6719 		int slots = pm->missile_banks[i].num_slots;
6720 		if (slots > max_points_per_bank)
6721 			max_points_per_bank = slots;
6722 	}
6723 	shipp->secondary_point_reload_pct.init(sip->num_secondary_banks, max_points_per_bank, 1.0f);
6724 
6725 	shipp->armor_type_idx = sip->armor_type_idx;
6726 	shipp->shield_armor_type_idx = sip->shield_armor_type_idx;
6727 	shipp->collision_damage_type_idx =  sip->collision_damage_type_idx;
6728 	shipp->debris_damage_type_idx = sip->debris_damage_type_idx;
6729 
6730 	shipp->warpin_params_index = sip->warpin_params_index;
6731 	shipp->warpout_params_index = sip->warpout_params_index;
6732 
6733 	if(pm != NULL && pm->n_view_positions > 0)
6734 		ship_set_eye(objp, 0);
6735 	else
6736 		ship_set_eye(objp, -1);
6737 
6738 	sip->shockwave.damage_type_idx = sip->shockwave.damage_type_idx_sav;
6739 
6740 	// Team colors
6741 	shipp->team_name.assign( sip->default_team_name);
6742 	shipp->secondary_team_name = "none";
6743 
6744 	shipp->autoaim_fov = sip->autoaim_fov;
6745 	shipp->max_shield_regen_per_second = sip->max_shield_regen_per_second;
6746 	shipp->max_weapon_regen_per_second = sip->max_weapon_regen_per_second;
6747 }
6748 
6749 /**
6750  * Recalculates the overall strength of subsystems.
6751  *
6752  * Needed because several places in FreeSpace change subsystem strength and all
6753  * this data needs to be kept up to date.
6754  */
ship_recalc_subsys_strength(ship * shipp)6755 void ship_recalc_subsys_strength( ship *shipp )
6756 {
6757 	int i;
6758 	ship_subsys *ship_system;
6759 
6760 	// fill in the subsys_info fields for all particular types of subsystems
6761 	// make the current strength be 1.0.  If there are initial conditions on the ship, then
6762 	// the mission parse code should take care of setting that.
6763 	for (i = 0; i < SUBSYSTEM_MAX; i++) {
6764 		shipp->subsys_info[i].type_count = 0;
6765 		shipp->subsys_info[i].aggregate_max_hits = 0.0f;
6766 		shipp->subsys_info[i].aggregate_current_hits = 0.0f;
6767 	}
6768 
6769 	// count all of the subsystems of a particular type.  For each generic type of subsystem, we store the
6770 	// total count of hits.  (i.e. for 3 engines, we store the sum of the max_hits for each engine)
6771 	for ( ship_system = GET_FIRST(&shipp->subsys_list); ship_system != END_OF_LIST(&shipp->subsys_list); ship_system = GET_NEXT(ship_system) ) {
6772 
6773 		if (!(ship_system->flags[Ship::Subsystem_Flags::No_aggregate])) {
6774 			int type = ship_system->system_info->type;
6775 			Assert ( (type >= 0) && (type < SUBSYSTEM_MAX) );
6776 
6777 			shipp->subsys_info[type].type_count++;
6778 			shipp->subsys_info[type].aggregate_max_hits += ship_system->max_hits;
6779 			shipp->subsys_info[type].aggregate_current_hits += ship_system->current_hits;
6780 		}
6781 
6782 		//Get rid of any persistent sounds on the subsystem
6783 		//This is inefficient + sloppy but there's not really an easy way to handle things
6784 		//if a subsystem is brought back from the dead, other than this
6785 		if(ship_system->current_hits > 0.0f)
6786 		{
6787             if (ship_system->subsys_snd_flags[Ship::Subsys_Sound_Flags::Dead])
6788             {
6789                 obj_snd_delete_type(shipp->objnum, ship_system->system_info->dead_snd, ship_system);
6790                 ship_system->subsys_snd_flags.set(Ship::Subsys_Sound_Flags::Dead, false);
6791             }
6792             if ((ship_system->system_info->alive_snd.isValid()) && !(ship_system->subsys_snd_flags[Ship::Subsys_Sound_Flags::Alive]))
6793             {
6794                 obj_snd_assign(shipp->objnum, ship_system->system_info->alive_snd, &ship_system->system_info->pnt, OS_SUBSYS_ALIVE, ship_system);
6795                 ship_system->subsys_snd_flags.set(Ship::Subsys_Sound_Flags::Alive);
6796             }
6797             if (!(ship_system->subsys_snd_flags[Ship::Subsys_Sound_Flags::Turret_rotation]))
6798             {
6799                 if (ship_system->system_info->turret_base_rotation_snd.isValid())
6800                 {
6801                     obj_snd_assign(shipp->objnum, ship_system->system_info->turret_base_rotation_snd, &ship_system->system_info->pnt, OS_TURRET_BASE_ROTATION, ship_system);
6802                     ship_system->subsys_snd_flags.set(Ship::Subsys_Sound_Flags::Turret_rotation);
6803                 }
6804                 if (ship_system->system_info->turret_gun_rotation_snd.isValid())
6805                 {
6806                     obj_snd_assign(shipp->objnum, ship_system->system_info->turret_gun_rotation_snd, &ship_system->system_info->pnt, OS_TURRET_GUN_ROTATION, ship_system);
6807                     ship_system->subsys_snd_flags.set(Ship::Subsys_Sound_Flags::Turret_rotation);
6808                 }
6809             }
6810             if ((ship_system->flags[Subsystem_Flags::Rotates]) && (ship_system->system_info->rotation_snd.isValid()) && !(ship_system->subsys_snd_flags[Ship::Subsys_Sound_Flags::Rotate]))
6811             {
6812                 obj_snd_assign(shipp->objnum, ship_system->system_info->rotation_snd, &ship_system->system_info->pnt, OS_SUBSYS_ROTATION, ship_system);
6813                 ship_system->subsys_snd_flags.set(Ship::Subsys_Sound_Flags::Rotate);
6814             }
6815 		}
6816 		else
6817 		{
6818             if (ship_system->subsys_snd_flags[Ship::Subsys_Sound_Flags::Alive])
6819             {
6820                 obj_snd_delete_type(shipp->objnum, ship_system->system_info->alive_snd, ship_system);
6821                 ship_system->subsys_snd_flags.set(Ship::Subsys_Sound_Flags::Alive, false);;
6822             }
6823             if (ship_system->subsys_snd_flags[Ship::Subsys_Sound_Flags::Turret_rotation])
6824             {
6825                 obj_snd_delete_type(shipp->objnum, ship_system->system_info->turret_base_rotation_snd, ship_system);
6826                 obj_snd_delete_type(shipp->objnum, ship_system->system_info->turret_gun_rotation_snd, ship_system);
6827                 ship_system->subsys_snd_flags.set(Ship::Subsys_Sound_Flags::Turret_rotation, false);
6828             }
6829             if (ship_system->subsys_snd_flags[Ship::Subsys_Sound_Flags::Rotate])
6830             {
6831                 obj_snd_delete_type(shipp->objnum, ship_system->system_info->rotation_snd, ship_system);
6832                 ship_system->subsys_snd_flags.set(Ship::Subsys_Sound_Flags::Rotate, false);
6833             }
6834             if ((ship_system->system_info->dead_snd.isValid()) && !(ship_system->subsys_snd_flags[Ship::Subsys_Sound_Flags::Dead]))
6835             {
6836                 obj_snd_assign(shipp->objnum, ship_system->system_info->dead_snd, &ship_system->system_info->pnt, OS_SUBSYS_DEAD, ship_system);
6837                 ship_system->subsys_snd_flags.set(Ship::Subsys_Sound_Flags::Dead, false);
6838             }
6839 		}
6840 	}
6841 
6842 	// set any ship flags which should be set.  unset the flags since we might be repairing a subsystem
6843 	// through sexpressions.
6844 	if ( (shipp->subsys_info[SUBSYSTEM_ENGINE].type_count > 0) && (shipp->subsys_info[SUBSYSTEM_ENGINE].aggregate_current_hits <= 0.0f) ) {
6845 		shipp->flags.set(Ship_Flags::Disabled);
6846 	} else {
6847 		shipp->flags.remove(Ship_Flags::Disabled);
6848 		ship_reset_disabled_physics( &Objects[shipp->objnum], shipp->ship_info_index );
6849 	}
6850 }
6851 
6852 /**
6853  * Fixup the model subsystem information for this ship pointer.
6854  * Needed when ships share the same model.
6855  */
ship_copy_subsystem_fixup(ship_info * sip)6856 static void ship_copy_subsystem_fixup(ship_info *sip)
6857 {
6858 	int model_num;
6859 
6860 	model_num = sip->model_num;
6861 
6862 	// no point copying the subsystem data if the ship in question has none...
6863 	// mark that the ship (cargo container) has the path fixup done.
6864 	if (sip->n_subsystems == 0) {
6865 		sip->flags.set(Ship::Info_Flags::Path_fixup);
6866 		return;
6867 	}
6868 
6869 	// if we need to get information for all our subsystems, we need to find another ship with the same model
6870 	// number as our own and that has the model information
6871 	for ( auto it = Ship_info.cbegin(); it != Ship_info.cend(); ++it ) {
6872 		model_subsystem *source_msp, *dest_msp;
6873 
6874 		if ( (it->model_num != model_num) || (&(*it) == sip) ){
6875 			continue;
6876 		}
6877 
6878 		// see if this ship has subsystems and a model for the subsystems.  We only need check the first
6879 		// subsystem since previous error checking would have trapped its loading as an error.
6880 		Assert( it->n_subsystems == sip->n_subsystems );
6881 
6882 		source_msp = &(it->subsystems[0]);
6883 		dest_msp = &(sip->subsystems[0]);
6884 		if (source_msp->model_num != -1) {
6885 			model_copy_subsystems( sip->n_subsystems, dest_msp, source_msp );
6886 		} else if (dest_msp->model_num != -1) {
6887 			model_copy_subsystems( sip->n_subsystems, source_msp, dest_msp );
6888 		} else {
6889 			// if none were found try finding a another ship to copy the data from
6890 			continue;
6891 		}
6892 		sip->flags.set(Ship::Info_Flags::Path_fixup);
6893 		break;
6894 	}
6895 
6896 }
6897 
6898 // as with object, don't set next and prev to NULL because they keep the object on the free and used lists
clear()6899 void ship_subsys::clear()
6900 {
6901 	int i;
6902 
6903 	system_info = NULL;
6904 
6905 	parent_objnum = -1;
6906 
6907 	sub_name[0] = 0;
6908 	current_hits = max_hits = 0.0f;
6909 
6910     flags.reset();
6911 
6912 	subsys_guardian_threshold = 0;
6913 	armor_type_idx = -1;
6914 
6915 	turret_last_fire_direction = vmd_zero_vector;
6916 	turret_next_enemy_check_stamp = timestamp(0);
6917 	turret_next_fire_stamp = timestamp(0);
6918 	turret_enemy_objnum = -1;
6919 	turret_enemy_sig = 0;
6920 	turret_next_fire_pos = 0;
6921 	turret_time_enemy_in_range = 0.0f;
6922 	turret_inaccuracy = 0.0f;
6923 
6924 	for (i = 0; i < NUM_TURRET_ORDER_TYPES; i++)
6925 		turret_targeting_order[i] = -1;
6926 	optimum_range = 0.0f;
6927 	favor_current_facing = 0.0f;
6928 	targeted_subsys = NULL;
6929 	scripting_target_override = false;
6930 	last_fired_weapon_info_index = -1;
6931 
6932 	turret_pick_big_attack_point_timestamp = timestamp(0);
6933 	turret_big_attack_point = vmd_zero_vector;
6934 
6935 	turret_animation_position = MA_POS_NOT_SET;
6936 	turret_animation_done_time = 0;
6937 
6938 	for (i = 0; i < MAX_TFP; i++)
6939 		turret_swarm_info_index[i] = -1;
6940 	turret_swarm_num = 0;
6941 
6942 	awacs_intensity = 0.0f;
6943 	awacs_radius = 0.0f;
6944 
6945     weapons.clear();
6946 
6947 	submodel_instance_1 = nullptr;
6948 	submodel_instance_2 = nullptr;
6949 
6950 	disruption_timestamp = timestamp(0);
6951 
6952 	subsys_cargo_name = 0;
6953 	time_subsys_cargo_revealed = 0;
6954 
6955 	triggered_rotation_index = -1;
6956 
6957 	points_to_target = 0.0f;
6958 	base_rotation_rate_pct = 0.0f;
6959 	gun_rotation_rate_pct = 0.0f;
6960 
6961     subsys_snd_flags.reset();
6962 
6963 	rotation_timestamp = timestamp(0);
6964 
6965 	for (i = 0; i < 32; i++)
6966 		target_priority[i] = -1;
6967 	num_target_priorities = 0;
6968 
6969 	next_aim_pos_time = 0;
6970 	last_aim_enemy_pos = vmd_zero_vector;
6971 	last_aim_enemy_vel = vmd_zero_vector;
6972 
6973 	rof_scaler = 1.0f;
6974 	turn_rate = 0.0f;
6975 
6976 	turret_max_bomb_ownage = -1;
6977 	turret_max_target_ownage = -1;
6978 }
6979 
6980 /**
6981  * Set subsystem
6982  *
6983  * @param objnum				Object number (used as index into Objects[])
6984  * @param ignore_subsys_info	Default parameter with value of 0.  This is only set to 1 by the save/restore code
6985  */
subsys_set(int objnum,int ignore_subsys_info)6986 static int subsys_set(int objnum, int ignore_subsys_info)
6987 {
6988 	ship	*shipp = &Ships[Objects[objnum].instance];
6989 	ship_info	*sinfo = &Ship_info[Ships[Objects[objnum].instance].ship_info_index];
6990 	model_subsystem *model_system;
6991 	ship_subsys *ship_system;
6992 	int i, j, k;
6993 
6994 	// set up the subsystems for this ship.  walk through list of subsystems in the ship-info array.
6995 	// for each subsystem, get a new ship_subsys instance and set up the pointers and other values
6996 	list_init ( &shipp->subsys_list );								// initialize the ship's list of subsystems
6997 
6998 	// make sure to have allocated the number of subsystems we require
6999 	if (!ship_allocate_subsystems( sinfo->n_subsystems )) {
7000 		return 0;
7001 	}
7002 
7003 	// make sure we set up the model instance properly
7004 	// (we need this to have been done already so we can link the submodels with the subsystems)
7005 	Assert(shipp->model_instance_num >= 0);
7006 	polymodel_instance *pmi = model_get_instance(shipp->model_instance_num);
7007 	Assert(pmi->model_num == sinfo->model_num);
7008 
7009 	for ( i = 0; i < sinfo->n_subsystems; i++ )
7010 	{
7011 		model_system = &(sinfo->subsystems[i]);
7012 		if (model_system->model_num < 0) {
7013 			Warning (LOCATION, "Invalid subobj_num or model_num in subsystem '%s' on ship type '%s'.\nNot linking into ship!\n\n(This warning means that a subsystem was present in the table entry and not present in the model\nit should probably be removed from the table or added to the model.)\n", model_system->subobj_name, sinfo->name );
7014 			continue;
7015 		}
7016 
7017 		// set up the linked list
7018 		ship_system = GET_FIRST( &ship_subsys_free_list );		// get a new element from the ship_subsystem array
7019 		Assert ( ship_system != &ship_subsys_free_list );		// shouldn't have the dummy element
7020 		list_remove( ship_subsys_free_list, ship_system );		// remove the element from the array
7021 		list_append( &shipp->subsys_list, ship_system );		// link the element into the ship
7022 		ship_system->clear();									// initialize it to a known blank slate
7023 
7024 		ship_system->system_info = model_system;				// set the system_info pointer to point to the data read in from the model
7025 		ship_system->parent_objnum = objnum;
7026 
7027 		// link the submodel instance info
7028 		if (model_system->subobj_num >= 0) {
7029 			ship_system->submodel_instance_1 = &pmi->submodel[model_system->subobj_num];
7030 		}
7031 		if ((model_system->subobj_num != model_system->turret_gun_sobj) && (model_system->turret_gun_sobj >= 0)) {
7032 			ship_system->submodel_instance_2 = &pmi->submodel[model_system->turret_gun_sobj];
7033 		}
7034 
7035 		// if the table has set an name copy it
7036 		if (ship_system->system_info->alt_sub_name[0] != '\0') {
7037 			strcpy_s(ship_system->sub_name, ship_system->system_info->alt_sub_name);
7038 		}
7039 		else {
7040 			memset(ship_system->sub_name, '\0', sizeof(ship_system->sub_name));
7041 		}
7042 
7043 		// copy subsystem target priorities stuff
7044 		ship_system->num_target_priorities = ship_system->system_info->num_target_priorities;
7045 		for (j = 0; j < 32; j++) {
7046 			ship_system->target_priority[j] = ship_system->system_info->target_priority[j];
7047 		}
7048 
7049 		ship_system->rof_scaler = ship_system->system_info->turret_rof_scaler;
7050 
7051 		// zero flags
7052         ship_system->flags.reset();
7053 		ship_system->weapons.flags.reset();
7054         ship_system->subsys_snd_flags.reset();
7055 
7056 		// Goober5000
7057 		if (model_system->flags[Model::Subsystem_Flags::Untargetable])
7058 			ship_system->flags.set(Ship::Subsystem_Flags::Untargetable);
7059 		// Wanderer
7060 		if (model_system->flags[Model::Subsystem_Flags::No_ss_targeting])
7061 			ship_system->flags.set(Ship::Subsystem_Flags::No_SS_targeting);
7062 		if ((The_mission.ai_profile->flags[AI::Profile_Flags::Advanced_turret_fov_edge_checks]) || (model_system->flags[Model::Subsystem_Flags::Fov_edge_check]))
7063 			ship_system->flags.set(Ship::Subsystem_Flags::FOV_edge_check);
7064 		if ((The_mission.ai_profile->flags[AI::Profile_Flags::Require_turret_to_have_target_in_fov]) || (model_system->flags[Model::Subsystem_Flags::Fov_required]))
7065 			ship_system->flags.set(Ship::Subsystem_Flags::FOV_Required);
7066 
7067 		if (model_system->flags[Model::Subsystem_Flags::No_replace])
7068 			ship_system->flags.set(Ship::Subsystem_Flags::No_replace);
7069 		if (model_system->flags[Model::Subsystem_Flags::No_live_debris])
7070 			ship_system->flags.set(Ship::Subsystem_Flags::No_live_debris);
7071 		if (model_system->flags[Model::Subsystem_Flags::Ignore_if_dead])
7072 			ship_system->flags.set(Ship::Subsystem_Flags::Missiles_ignore_if_dead);
7073 		if (model_system->flags[Model::Subsystem_Flags::Allow_vanishing])
7074 			ship_system->flags.set(Ship::Subsystem_Flags::Vanished);
7075         if (model_system->flags[Model::Subsystem_Flags::Damage_as_hull])
7076 			ship_system->flags.set(Ship::Subsystem_Flags::Damage_as_hull);
7077 		if (model_system->flags[Model::Subsystem_Flags::No_aggregate])
7078 			ship_system->flags.set(Ship::Subsystem_Flags::No_aggregate);
7079 		if (model_system->flags[Model::Subsystem_Flags::Rotates])
7080 			ship_system->flags.set(Ship::Subsystem_Flags::Rotates);
7081 		if (model_system->flags[Model::Subsystem_Flags::Player_turret_sound])
7082 			ship_system->flags.set(Ship::Subsystem_Flags::Play_sound_for_player);
7083 		if (model_system->flags[Model::Subsystem_Flags::No_disappear])
7084 			ship_system->flags.set(Ship::Subsystem_Flags::No_disappear);
7085 		if (model_system->flags[Model::Subsystem_Flags::Autorepair_if_disabled])
7086 			ship_system->flags.set(Ship::Subsystem_Flags::Autorepair_if_disabled);
7087 		if (model_system->flags[Model::Subsystem_Flags::No_autorepair_if_disabled])
7088 			ship_system->flags.set(Ship::Subsystem_Flags::No_autorepair_if_disabled);
7089 		if (model_system->flags[Model::Subsystem_Flags::Turret_locked])
7090 			ship_system->weapons.flags.set(Ship::Weapon_Flags::Turret_Lock);
7091 		// check the mission flag to possibly free all beam weapons - Goober5000, taken from SEXP.CPP, and moved to subsys_set() by Asteroth
7092 		if (The_mission.flags[Mission::Mission_Flags::Beam_free_all_by_default])
7093 			ship_system->weapons.flags.set(Ship::Weapon_Flags::Beam_Free);
7094 
7095 		ship_system->turn_rate = model_system->turn_rate;
7096 
7097 		// Goober5000 - this has to be moved outside back to parse_create_object, because
7098 		// a lot of the ship creation code is duplicated in several points and overwrites
7099 		// previous things... ugh.
7100 		ship_system->max_hits = model_system->max_subsys_strength;	// * shipp->ship_max_hull_strength / sinfo->max_hull_strength;
7101 
7102 		if ( !Fred_running ) {
7103 			ship_system->current_hits = ship_system->max_hits;		// set the current hits
7104 		} else {
7105 			ship_system->current_hits = 0.0f;				// Jason wants this to be 0 in Fred.
7106 		}
7107 
7108 		ship_system->subsys_guardian_threshold = 0;
7109 		ship_system->armor_type_idx = model_system->armor_type_idx;
7110 		ship_system->turret_next_fire_stamp = timestamp(0);
7111 		ship_system->turret_next_enemy_check_stamp = timestamp(0);
7112 		ship_system->turret_enemy_objnum = -1;
7113 		ship_system->turret_next_fire_stamp = timestamp((int) frand_range(1.0f, 500.0f));	// next time this turret can fire
7114 		ship_system->turret_last_fire_direction = model_system->turret_norm;
7115 		ship_system->turret_next_fire_pos = 0;
7116 		ship_system->turret_time_enemy_in_range = 0.0f;
7117 		ship_system->disruption_timestamp=timestamp(0);
7118 		ship_system->turret_pick_big_attack_point_timestamp = timestamp(0);
7119 		ship_system->scripting_target_override = false;
7120 		vm_vec_zero(&ship_system->turret_big_attack_point);
7121 		for(j = 0; j < NUM_TURRET_ORDER_TYPES; j++)
7122 		{
7123 			//WMC - Set targeting order to default.
7124 			ship_system->turret_targeting_order[j] = j;
7125 		}
7126 		ship_system->optimum_range = model_system->optimum_range;
7127 		ship_system->favor_current_facing = model_system->favor_current_facing;
7128 		ship_system->subsys_cargo_name = 0;
7129 		ship_system->time_subsys_cargo_revealed = 0;
7130 
7131 		j = 0;
7132 		int number_of_weapons = 0;
7133 
7134 		for (k=0; k<MAX_SHIP_PRIMARY_BANKS; k++){
7135 			if (model_system->primary_banks[k] != -1) {
7136 				ship_system->weapons.primary_bank_weapons[j] = model_system->primary_banks[k];
7137 				ship_system->weapons.primary_bank_capacity[j] = model_system->primary_bank_capacity[k];	// added by Goober5000
7138 				ship_system->weapons.next_primary_fire_stamp[j] = timestamp(0);
7139 				ship_system->weapons.last_primary_fire_stamp[j++] = -1;
7140 			}
7141 			ship_system->weapons.burst_counter[k] = 0;
7142 		}
7143 
7144 		ship_system->weapons.num_primary_banks = j;
7145 		number_of_weapons += j;
7146 
7147 		j = 0;
7148 		for (k=0; k<MAX_SHIP_SECONDARY_BANKS; k++){
7149 			if (model_system->secondary_banks[k] != -1) {
7150 				ship_system->weapons.secondary_bank_weapons[j] = model_system->secondary_banks[k];
7151 				ship_system->weapons.secondary_bank_capacity[j] = model_system->secondary_bank_capacity[k];
7152 				ship_system->weapons.next_secondary_fire_stamp[j] = timestamp(0);
7153 				ship_system->weapons.last_secondary_fire_stamp[j++] = -1;
7154 			}
7155 			ship_system->weapons.burst_counter[k + MAX_SHIP_PRIMARY_BANKS] = 0;
7156 		}
7157 
7158 		ship_system->weapons.num_secondary_banks = j;
7159 		number_of_weapons += j;
7160 		ship_system->weapons.current_primary_bank = -1;
7161 		ship_system->weapons.current_secondary_bank = -1;
7162 
7163 		ship_system->next_aim_pos_time = 0;
7164 
7165 		ship_system->turret_max_bomb_ownage = model_system->turret_max_bomb_ownage;
7166 		ship_system->turret_max_target_ownage = model_system->turret_max_target_ownage;
7167 
7168 		// Make turret flag checks and warnings
7169 		if ((ship_system->system_info->flags[Model::Subsystem_Flags::Turret_salvo]) && (ship_system->system_info->flags[Model::Subsystem_Flags::Turret_fixed_fp]))
7170 		{
7171 			Warning (LOCATION, "\"salvo mode\" flag used with \"fixed firingpoints\" flag\nsubsystem '%s' on ship type '%s'.\n\"salvo mode\" flag is ignored\n", model_system->subobj_name, sinfo->name );
7172             ship_system->system_info->flags.remove(Model::Subsystem_Flags::Turret_salvo);
7173 		}
7174 
7175 		if ((ship_system->system_info->flags[Model::Subsystem_Flags::Turret_salvo]) && (model_system->turret_num_firing_points < 2))
7176 		{
7177 			Warning (LOCATION, "\"salvo mode\" flag used with turret which has less than two firingpoints\nsubsystem '%s' on ship type '%s'.\n\"salvo mode\" flag is ignored\n", model_system->subobj_name, sinfo->name );
7178 			ship_system->system_info->flags.remove(Model::Subsystem_Flags::Turret_salvo);
7179 		}
7180 
7181 		if ((ship_system->system_info->flags[Model::Subsystem_Flags::Turret_fixed_fp]) && (model_system->turret_num_firing_points < 2))
7182 		{
7183 			Warning (LOCATION, "\"fixed firingpoints\" flag used with turret which has less than two firingpoints\nsubsystem '%s' on ship type '%s'.\n\"fixed firingpoints\" flag is ignored\n", model_system->subobj_name, sinfo->name );
7184 			ship_system->system_info->flags.remove(Model::Subsystem_Flags::Turret_fixed_fp);
7185 		}
7186 
7187 		if ((ship_system->system_info->flags[Model::Subsystem_Flags::Turret_salvo]) && (ship_system->system_info->flags[Model::Subsystem_Flags::Use_multiple_guns]))
7188 		{
7189 			Warning (LOCATION, "\"salvo mode\" flag used with \"use multiple guns\" flag\nsubsystem '%s' on ship type '%s'.\n\"use multiple guns\" flag is ignored\n", model_system->subobj_name, sinfo->name );
7190 			ship_system->system_info->flags.remove(Model::Subsystem_Flags::Use_multiple_guns);
7191 		}
7192 
7193 		if ((ship_system->system_info->flags[Model::Subsystem_Flags::Turret_fixed_fp]) && !(ship_system->system_info->flags[Model::Subsystem_Flags::Use_multiple_guns]))
7194 		{
7195 			Warning (LOCATION, "\"fixed firingpoints\" flag used without \"use multiple guns\" flag\nsubsystem '%s' on ship type '%s'.\n\"use multiple guns\" guns added by default\n", model_system->subobj_name, sinfo->name );
7196             ship_system->system_info->flags.set(Model::Subsystem_Flags::Use_multiple_guns);
7197 		}
7198 
7199 		if ((ship_system->system_info->flags[Model::Subsystem_Flags::Turret_salvo]) && (number_of_weapons > 1))
7200 		{
7201 			mprintf(("\"salvo mode\" flag used with turret which has more than one weapon defined for it\nsubsystem '%s' on ship type '%s'.\nonly single weapon will be used\n", model_system->subobj_name, sinfo->name));
7202 		}
7203 
7204 		if ((ship_system->system_info->flags[Model::Subsystem_Flags::Turret_fixed_fp]) && (number_of_weapons > model_system->turret_num_firing_points))
7205 		{
7206 			mprintf(("\"fixed firingpoint\" flag used with turret which has more weapons defined for it than it has firingpoints\nsubsystem '%s' on ship type '%s'.\nweapons will share firingpoints\n", model_system->subobj_name, sinfo->name));
7207 		}
7208 
7209 		if ((ship_system->system_info->flags[Model::Subsystem_Flags::Turret_fixed_fp]) && (number_of_weapons < model_system->turret_num_firing_points))
7210 		{
7211 			mprintf(("\"fixed firingpoint\" flag used with turret which has less weapons defined for it than it has firingpoints\nsubsystem '%s' on ship type '%s'.\nsome of the firingpoints will be left unused\n", model_system->subobj_name, sinfo->name));
7212 		}
7213 
7214 		if ((ship_system->system_info->flags[Model::Subsystem_Flags::Share_fire_direction]) && (!(ship_system->system_info->flags[Model::Subsystem_Flags::Turret_salvo]) && !(ship_system->system_info->flags[Model::Subsystem_Flags::Use_multiple_guns])))
7215 		{
7216 			Warning(LOCATION, "\"share fire direction\" flag used with turret which does not have the \"salvo mode\" or \"use multiple guns\" flag set\nsubsystem '%s' on ship type '%s'.\n\"share fire direction\" flag is ignored\n", model_system->subobj_name, sinfo->name);
7217 			ship_system->system_info->flags.remove(Model::Subsystem_Flags::Share_fire_direction);
7218 		}
7219 
7220 		for (k=0; k<ship_system->weapons.num_secondary_banks; k++) {
7221 			float weapon_size = Weapon_info[ship_system->weapons.secondary_bank_weapons[k]].cargo_size;
7222 			Assertion( weapon_size > 0.0f, "Cargo size for secondary weapon %s is invalid, must be greater than 0.\n", Weapon_info[ship_system->weapons.secondary_bank_weapons[k]].name );
7223 			ship_system->weapons.secondary_bank_ammo[k] = (Fred_running ? 100 : (int)std::lround(ship_system->weapons.secondary_bank_capacity[k] / weapon_size));
7224 
7225 			ship_system->weapons.secondary_next_slot[k] = 0;
7226 		}
7227 
7228 		// Goober5000
7229 		for (k=0; k<ship_system->weapons.num_primary_banks; k++)
7230 		{
7231 			float weapon_size = Weapon_info[ship_system->weapons.primary_bank_weapons[k]].cargo_size;
7232 
7233 			if (weapon_size > 0.0f) {	// Non-ballistic primaries are supposed to have a cargo_size of 0
7234 				ship_system->weapons.primary_bank_ammo[k] = (Fred_running ? 100 : (int)std::lround(ship_system->weapons.primary_bank_capacity[k] / weapon_size));
7235 			}
7236 		}
7237 
7238 		ship_system->weapons.remote_detonaters_active = 0;
7239 		ship_system->weapons.detonate_weapon_time = -1;
7240 		ship_system->weapons.ai_class = sinfo->ai_class;  // assume ai class of ship for turret
7241 
7242 		// rapid fire (swarm) stuff
7243 		for (k = 0; k < MAX_TFP; k++)
7244 			ship_system->turret_swarm_info_index[k] = -1;
7245 
7246 		ship_system->turret_swarm_num = 0;
7247 
7248 		// AWACS stuff
7249 		ship_system->awacs_intensity = model_system->awacs_intensity;
7250 		ship_system->awacs_radius = model_system->awacs_radius;
7251 		if (ship_system->awacs_intensity > 0) {
7252             ship_system->system_info->flags.set(Model::Subsystem_Flags::Awacs);
7253         }
7254 
7255 		// turn_rate, turn_accel
7256 		float turn_accel = 0.5f;
7257 		if (ship_system->submodel_instance_1 != nullptr)
7258 			model_set_submodel_turn_info(ship_system->submodel_instance_1, model_system->turn_rate, turn_accel);
7259 
7260 		// Allocate a triggered rotation instance if we need it
7261 		if (model_system->flags[Model::Subsystem_Flags::Triggered]) {
7262 			ship_system->triggered_rotation_index = (int)Triggered_rotations.size();
7263 			triggered_rotation tr;
7264 			Triggered_rotations.push_back(tr);
7265 		}
7266 	}
7267 
7268 	if ( !ignore_subsys_info ) {
7269 		ship_recalc_subsys_strength( shipp );
7270 	}
7271 
7272 	// Fix up animation code references
7273 	for (i = 0; i < sinfo->n_subsystems; i++) {
7274 		for (j = 0; j < sinfo->subsystems[i].n_triggers; j++) {
7275 			if (stricmp(sinfo->subsystems[i].triggers[j].sub_name, "<none>") != 0) {
7276 				int idx = ship_get_subobj_model_num(sinfo, sinfo->subsystems[i].triggers[j].sub_name);
7277 				if (idx != -1) {
7278 					sinfo->subsystems[i].triggers[j].subtype = idx;
7279 				} else {
7280 					WarningEx(LOCATION, "Could not find subobject %s in ship class %s. Animation triggers will not work correctly.\n", sinfo->subsystems[i].triggers[j].sub_name, sinfo->name);
7281 				}
7282 			}
7283 		}
7284 	}
7285 
7286 	return 1;
7287 }
7288 
7289 /**
7290  * Modify the matrix orient by the slew angles a.
7291  */
compute_slew_matrix(matrix * orient,angles * a)7292 void compute_slew_matrix(matrix *orient, angles *a)
7293 {
7294 	matrix	tmp, tmp2;
7295 	angles	t1, t2;
7296 
7297 	t1 = t2 = *a;
7298 	t1.h = 0.0f;	t1.b = 0.0f;
7299 	t2.p = 0.0f;	t2.b = 0.0f;
7300 
7301 	// put in p & b like normal
7302 	vm_angles_2_matrix(&tmp, &t2 ); // Changed the order of axis rotations. First pitch, then yaw (Swifty)
7303 	vm_matrix_x_matrix( &tmp2, orient, &tmp);
7304 
7305 	// Put in heading separately
7306 	vm_angles_2_matrix(&tmp, &t1 );
7307 	vm_matrix_x_matrix( orient, &tmp2, &tmp );
7308 
7309 	vm_orthogonalize_matrix(orient);
7310 }
7311 
7312 int Ship_shadows = 0;
7313 
7314 DCF_BOOL( ship_shadows, Ship_shadows )
7315 
7316 MONITOR( NumShipsRend )
7317 
7318 int Show_shield_hits = 0;
7319 DCF_BOOL( show_shield_hits, Show_shield_hits )
7320 
7321 int Show_tnorms = 0;
7322 DCF_BOOL( show_tnorms, Show_tnorms )
7323 
7324 int Show_paths = 0;
7325 DCF_BOOL( show_paths, Show_paths )
7326 
7327 int Show_fpaths = 0;
DCF_BOOL(show_fpaths,Show_fpaths)7328 DCF_BOOL( show_fpaths, Show_fpaths )
7329 
7330 static void ship_find_warping_ship_helper(object *objp, dock_function_info *infop)
7331 {
7332 	// only check ships
7333 	if (objp->type != OBJ_SHIP)
7334 		return;
7335 
7336 	// am I arriving or departing by warp?
7337 	if (Ships[objp->instance].is_arriving(ship::warpstage::BOTH, true) || Ships[objp->instance].flags[Ship_Flags::Depart_warp])
7338 	{
7339 #ifndef NDEBUG
7340 		// in debug builds, make sure only one of the docked objects has these flags set
7341 		if (infop->maintained_variables.bool_value)
7342 		{
7343 			//WMC - This is annoying and triggered in sm2-10
7344 			//Warning(LOCATION, "Ship %s and its docked ship %s are arriving or departing at the same time.\n",
7345 			//Ships[infop->maintained_variables.objp_value->instance].ship_name, Ships[objp->instance].ship_name);
7346 		}
7347 #endif
7348 		// we found someone
7349 		infop->maintained_variables.bool_value = true;
7350 		infop->maintained_variables.objp_value = objp;
7351 
7352 #ifdef NDEBUG
7353 		// return early in release builds
7354 		infop->early_return_condition = true;
7355 #endif
7356 	}
7357 }
7358 
7359 //WMC - used for FTL and maneuvering thrusters
7360 extern bool Rendering_to_shadow_map;
7361 
ship_render_cockpit(object * objp)7362 void ship_render_cockpit(object *objp)
7363 {
7364 	if(objp->type != OBJ_SHIP || objp->instance < 0)
7365 		return;
7366 
7367 	ship *shipp = &Ships[objp->instance];
7368 	ship_info *sip = &Ship_info[shipp->ship_info_index];
7369 
7370 	if(sip->cockpit_model_num < 0)
7371 		return;
7372 
7373 	polymodel *pm = model_get(sip->cockpit_model_num);
7374 	Assert(pm != NULL);
7375 
7376 	//Setup
7377 	gr_reset_clip();
7378 	hud_save_restore_camera_data(1);
7379 
7380 	matrix eye_ori = vmd_identity_matrix;
7381 	vec3d eye_pos = vmd_zero_vector;
7382 	ship_get_eye(&eye_pos, &eye_ori, objp, false, true);
7383 
7384 	vec3d pos = vmd_zero_vector;
7385 
7386 	vm_vec_unrotate(&pos, &sip->cockpit_offset, &eye_ori);
7387 
7388 	bool shadow_override_backup = Shadow_override;
7389 
7390 	//Deal with the model
7391 	model_clear_instance(sip->cockpit_model_num);
7392 	if (Shadow_quality != ShadowQuality::Disabled) {
7393 		gr_reset_clip();
7394 		Shadow_override = false;
7395 
7396 		shadows_start_render(&Eye_matrix, &leaning_position, Proj_fov, gr_screen.clip_aspect, std::get<0>(Shadow_distances_cockpit), std::get<1>(Shadow_distances_cockpit), std::get<2>(Shadow_distances_cockpit), std::get<3>(Shadow_distances_cockpit));
7397 
7398 		model_render_params shadow_render_info;
7399 		shadow_render_info.set_detail_level_lock(0);
7400 		shadow_render_info.set_flags(MR_NO_TEXTURING | MR_NO_LIGHTING);
7401 		model_render_immediate(&shadow_render_info, sip->cockpit_model_num, &eye_ori, &pos);
7402 
7403 		shadows_end_render();
7404 		gr_clear_states();
7405 	}
7406 
7407 	gr_set_proj_matrix(Proj_fov, gr_screen.clip_aspect, 0.02f, 10.0f * pm->rad);
7408 	gr_set_view_matrix(&leaning_position, &Eye_matrix);
7409 
7410 	uint render_flags = MR_NORMAL;
7411 	render_flags |= MR_NO_FOGGING;
7412 
7413 	if (shipp->flags[Ship::Ship_Flags::Glowmaps_disabled]) {
7414 		render_flags |= MR_NO_GLOWMAPS;
7415 	}
7416 
7417 	model_render_params render_info;
7418 	render_info.set_detail_level_lock(0);
7419 	render_info.set_flags(render_flags);
7420 	render_info.set_replacement_textures(Player_cockpit_textures);
7421 
7422 	model_render_immediate(&render_info, sip->cockpit_model_num, &eye_ori, &pos);
7423 
7424 	gr_end_view_matrix();
7425 	gr_end_proj_matrix();
7426 
7427 	hud_save_restore_camera_data(0);
7428 
7429 	//Restore the Shadow_override
7430 	if (Shadow_quality != ShadowQuality::Disabled)
7431 		Shadow_override = shadow_override_backup;
7432 }
7433 
ship_render_show_ship_cockpit(object * objp)7434 void ship_render_show_ship_cockpit(object *objp)
7435 {
7436 	if (objp->type != OBJ_SHIP || objp->instance < 0)
7437 		return;
7438 
7439 	vec3d cockpit_eye_pos;
7440 	matrix dummy;
7441 	ship_get_eye(&cockpit_eye_pos, &dummy, objp, true, false); // Get cockpit eye position
7442 
7443 	gr_end_view_matrix();
7444 	gr_end_proj_matrix();
7445 	gr_set_proj_matrix(Proj_fov, gr_screen.clip_aspect, 0.05f, Max_draw_distance);
7446 	gr_set_view_matrix(&cockpit_eye_pos, &Eye_matrix); // Set Camera to cockpit eye position
7447 
7448 	Glowpoint_override = true; // Turn off glowpoints so they dont get rendered fixed at origin
7449 
7450 	model_render_params render_info;
7451 
7452 	render_info.set_object_number(OBJ_INDEX(objp));
7453 	render_info.set_replacement_textures(Ships[objp->instance].ship_replacement_textures);
7454 	render_info.set_detail_level_lock(0);
7455 
7456 	model_render_immediate(&render_info, Ship_info[Ships[objp->instance].ship_info_index].model_num, &objp->orient,
7457 	                       &objp->pos); // Render ship model with fixed detail level 0 so its not switching LOD when
7458 	                                    // moving away from origin
7459 	Glowpoint_override = false;
7460 
7461 	gr_end_view_matrix();
7462 	gr_end_proj_matrix();
7463 	gr_set_proj_matrix(Proj_fov, gr_screen.clip_aspect, Min_draw_distance, Max_draw_distance);
7464 	gr_set_view_matrix(&Eye_position, &Eye_matrix); // Reset Camera to normal
7465 }
7466 
ship_init_cockpit_displays(ship * shipp)7467 void ship_init_cockpit_displays(ship *shipp)
7468 {
7469 	ship_info *sip = &Ship_info[shipp->ship_info_index];
7470 
7471 	int cockpit_model_num = sip->cockpit_model_num;
7472 
7473 	// don't bother creating cockpit texture replacements if this ship has no cockpit
7474 	if ( cockpit_model_num < 0 ) {
7475 		return;
7476 	}
7477 
7478 	// check if we even have cockpit displays
7479 	if ( sip->displays.empty() ) {
7480 		return;
7481 	}
7482 
7483 	if ( Player_cockpit_textures != NULL) {
7484 		return;
7485 	}
7486 
7487 	// ship's cockpit texture replacements haven't been setup yet, so do it.
7488 	Player_cockpit_textures = (int *) vm_malloc(MAX_REPLACEMENT_TEXTURES * sizeof(int));
7489 
7490 	int i;
7491 
7492 	for ( i = 0; i < MAX_REPLACEMENT_TEXTURES; i++ ) {
7493 		Player_cockpit_textures[i] = -1;
7494 	}
7495 
7496 	for ( i = 0; i < (int)sip->displays.size(); i++ ) {
7497 		ship_add_cockpit_display(&sip->displays[i], cockpit_model_num);
7498 	}
7499 
7500 	ship_set_hud_cockpit_targets();
7501 }
7502 
ship_clear_cockpit_displays()7503 void ship_clear_cockpit_displays()
7504 {
7505 	for ( int i = 0; i < (int)Player_displays.size(); i++ ) {
7506 		if ( Player_displays[i].background >= 0 ) {
7507 			bm_release(Player_displays[i].background);
7508 		}
7509 
7510 		if ( Player_displays[i].foreground >= 0 ) {
7511 			bm_release(Player_displays[i].foreground);
7512 		}
7513 
7514 		if ( Player_displays[i].target >= 0 ) {
7515 			bm_release(Player_displays[i].target);
7516 		}
7517 	}
7518 
7519 	Player_displays.clear();
7520 
7521 	if ( Player_cockpit_textures != NULL ) {
7522 		vm_free(Player_cockpit_textures);
7523 		Player_cockpit_textures = NULL;
7524 	}
7525 }
7526 
ship_add_cockpit_display(cockpit_display_info * display,int cockpit_model_num)7527 static void ship_add_cockpit_display(cockpit_display_info *display, int cockpit_model_num)
7528 {
7529 	if ( strlen(display->filename) <= 0 ) {
7530 		return;
7531 	}
7532 
7533 	if( cockpit_model_num < 0 ) {
7534 		return;
7535 	}
7536 
7537 	int i, tm_num, glow_target = -1, glow_handle = -1, diffuse_handle = -1;
7538 	int w, h;
7539 	cockpit_display new_display;
7540 
7541 	// if no texture target has been found yet, find one.
7542 	polymodel *pm = model_get(cockpit_model_num);
7543 
7544 	for ( i = 0; i < pm->n_textures; i++ )
7545 	{
7546 		tm_num = pm->maps[i].FindTexture(display->filename);
7547 		if ( tm_num >= 0 ) {
7548 			glow_target = i*TM_NUM_TYPES+TM_GLOW_TYPE;
7549 
7550 			diffuse_handle = pm->maps[i].textures[TM_BASE_TYPE].GetTexture();
7551 			glow_handle = pm->maps[i].textures[TM_GLOW_TYPE].GetTexture();
7552 			break;
7553 		}
7554 	}
7555 
7556 	// create a render target for this cockpit texture
7557 	if ( Player_cockpit_textures[glow_target] < 0) {
7558 
7559 		bm_get_info(diffuse_handle, &w, &h);
7560 		Player_cockpit_textures[glow_target] = bm_make_render_target(w, h, BMP_FLAG_RENDER_TARGET_DYNAMIC);
7561 
7562 		// if no render target was made, bail
7563 		if ( Player_cockpit_textures[glow_target] < 0 ) {
7564 			return;
7565 		}
7566 	}
7567 
7568 	new_display.background = -1;
7569 	if ( display->bg_filename[0] != '\0' ) {
7570 		new_display.background = bm_load(display->bg_filename);
7571 
7572 		if ( new_display.background < 0 ) {
7573 			Warning(LOCATION, "Unable to load background %s for cockpit display %s", display->bg_filename, display->name);
7574 		}
7575 	}
7576 
7577 	new_display.foreground = -1;
7578 	if ( display->fg_filename[0] != '\0' ) {
7579 		new_display.foreground = bm_load(display->fg_filename);
7580 
7581 		if ( new_display.foreground < 0 ) {
7582 			Warning(LOCATION, "Unable to load background %s for cockpit display %s", display->fg_filename, display->name);
7583 		}
7584 	}
7585 
7586 	strcpy_s(new_display.name, display->name);
7587 	new_display.offset[0] = display->offset[0];
7588 	new_display.offset[1] = display->offset[1];
7589 	new_display.size[0] = display->size[0];
7590 	new_display.size[1] = display->size[1];
7591 	new_display.source = glow_handle;
7592 	new_display.target = Player_cockpit_textures[glow_target];
7593 
7594 	Player_displays.push_back(new_display);
7595 }
7596 
ship_set_hud_cockpit_targets()7597 static void ship_set_hud_cockpit_targets()
7598 {
7599 	if ( !Ship_info[Player_ship->ship_info_index].hud_enabled ) {
7600 		return;
7601 	}
7602 
7603 	auto& hud = Ship_info[Player_ship->ship_info_index].hud_gauges;
7604 
7605 	for ( int i = 0; i < (int)hud.size(); i++ ) {
7606 		for ( int j = 0; j < (int)Player_displays.size(); j++ ) {
7607 			hud[i]->setCockpitTarget(&Player_displays[j]);
7608 		}
7609 	}
7610 }
7611 
ship_start_render_cockpit_display(size_t cockpit_display_num)7612 int ship_start_render_cockpit_display(size_t cockpit_display_num)
7613 {
7614 	// make sure this thing even has a cockpit
7615 	if ( Ship_info[Player_ship->ship_info_index].cockpit_model_num < 0 ) {
7616 		return -1;
7617 	}
7618 
7619 	if ( Player_cockpit_textures == NULL ) {
7620 		return -1;
7621 	}
7622 
7623 	// check sanity of the cockpit display handle
7624 	if ( cockpit_display_num >= Player_displays.size()) {
7625 		return -1;
7626 	}
7627 
7628 	cockpit_display* display = &Player_displays[cockpit_display_num];
7629 
7630 	if ( display->target < 0 ) {
7631 		return -1;
7632 	}
7633 
7634 	if ( !bm_set_render_target(display->target) ) {
7635 		return -1;
7636 	}
7637 	gr_push_debug_group("Render cockpit display");
7638 
7639 	int cull = gr_set_cull(0);
7640 
7641 	gr_clear();
7642 
7643 	if ( display->source >= 0 ) {
7644 		gr_set_bitmap(display->source);
7645 		gr_bitmap(0, 0, GR_RESIZE_NONE);
7646 	}
7647 
7648 	if ( display->background >= 0 ) {
7649 		gr_set_bitmap(display->background);
7650 		gr_bitmap_ex(display->offset[0], display->offset[1], display->size[0], display->size[1], 0, 0, GR_RESIZE_NONE);
7651 	}
7652 
7653 	gr_set_cull(cull);
7654 
7655 	return display->target;
7656 }
7657 
ship_end_render_cockpit_display(size_t cockpit_display_num)7658 void ship_end_render_cockpit_display(size_t cockpit_display_num)
7659 {
7660 	// make sure this thing even has a cockpit
7661 	if ( Ship_info[Player_ship->ship_info_index].cockpit_model_num < 0 ) {
7662 		return;
7663 	}
7664 
7665 	if ( Player_cockpit_textures == NULL ) {
7666 		return;
7667 	}
7668 
7669 	// check sanity of the cockpit display handle
7670 	if ( cockpit_display_num >= Player_displays.size()) {
7671 		return;
7672 	}
7673 
7674 	cockpit_display* display = &Player_displays[cockpit_display_num];
7675 
7676 	int cull = gr_set_cull(0);
7677 	if ( display->foreground >= 0 ) {
7678 		gr_reset_clip();
7679 		gr_set_bitmap(display->foreground);
7680 		gr_bitmap_ex(display->offset[0], display->offset[1], display->size[0], display->size[1], 0, 0, GR_RESIZE_NONE);
7681 	}
7682 
7683 	gr_set_cull(cull);
7684 	bm_set_render_target(-1);
7685 
7686 	gr_pop_debug_group();
7687 }
7688 
ship_subsystems_delete(ship * shipp)7689 static void ship_subsystems_delete(ship *shipp)
7690 {
7691 	if ( NOT_EMPTY(&shipp->subsys_list) )
7692 	{
7693 		ship_subsys *systemp, *temp;
7694 
7695 		systemp = GET_FIRST( &shipp->subsys_list );
7696 		while ( systemp != END_OF_LIST(&shipp->subsys_list) ) {
7697 			temp = GET_NEXT( systemp );								// use temporary since pointers will get screwed with next operation
7698 			list_remove( shipp->subsys_list, systemp );			// remove the element
7699 			list_append( &ship_subsys_free_list, systemp );		// and place back onto free list
7700 			Num_ship_subsystems--;								// subtract from our in-use total
7701 			systemp = temp;												// use the temp variable to move right along
7702 		}
7703 	}
7704 }
7705 
ship_delete(object * obj)7706 void ship_delete( object * obj )
7707 {
7708 	ship	*shipp;
7709 	int	num, objnum __UNUSED;
7710 
7711 	num = obj->instance;
7712 	Assert( num >= 0);
7713 
7714 	objnum = OBJ_INDEX(obj);
7715 	Assert( Ships[num].objnum == objnum );
7716 
7717 	shipp = &Ships[num];
7718 
7719 	if (shipp->ai_index != -1){
7720 		ai_free_slot(shipp->ai_index);
7721 	}
7722 
7723 	// free up the list of subsystems of this ship.  walk through list and move remaining subsystems
7724 	// on ship back to the free list for other ships to use.
7725 	ship_subsystems_delete(&Ships[num]);
7726 	shipp->objnum = -1;
7727 
7728 	for (const auto& animationList : Ship_info[shipp->ship_info_index].animations.animationSet) {
7729 		for (const auto& animStop : animationList.second) {
7730 			animStop.second->stop(model_get_instance(shipp->model_instance_num), true);
7731 		}
7732 	}
7733 
7734 	if (shipp->ship_replacement_textures != NULL) {
7735 		vm_free(shipp->ship_replacement_textures);
7736 		shipp->ship_replacement_textures = NULL;
7737 	}
7738 
7739 	// glow point banks
7740 	shipp->glow_point_bank_active.clear();
7741 
7742 	if ( shipp->ship_list_index != -1 ) {
7743 		ship_obj_list_remove(shipp->ship_list_index);
7744 		shipp->ship_list_index = -1;
7745 	}
7746 
7747 	free_sexp2(shipp->arrival_cue);
7748 	free_sexp2(shipp->departure_cue);
7749 
7750 	// call the contrail system
7751 	ct_ship_delete(shipp);
7752 
7753 	model_delete_instance(shipp->model_instance_num);
7754 
7755 	// free up any weapon model instances
7756 	for (int i = 0; i < shipp->weapons.num_primary_banks; ++i)
7757 	{
7758 		if (shipp->weapons.primary_bank_external_model_instance[i] >= 0)
7759 			model_delete_instance(shipp->weapons.primary_bank_external_model_instance[i]);
7760 	}
7761 }
7762 
7763 /**
7764  * Used by ::ship_cleanup which is called if the ship is in a wing.
7765  *
7766  * This function updates the ship_index list (i.e. removes its entry in the list)
7767  * and packs the array accordingly.
7768  */
ship_wing_cleanup(int shipnum,wing * wingp)7769 void ship_wing_cleanup( int shipnum, wing *wingp )
7770 {
7771 	int i, index = -1, team = Ships[shipnum].team;
7772 
7773 	// find this ship's position within its wing
7774 	for (i = 0; i < wingp->current_count; i++)
7775 	{
7776 		if (wingp->ship_index[i] == shipnum)
7777 		{
7778 			index = i;
7779 			break;
7780 		}
7781 	}
7782 
7783 	// this can happen in multiplayer (dogfight, ingame join specifically)
7784 	if (index == -1)
7785 		return;
7786 
7787 	// compress the ship_index array and mark the last entry with a -1
7788 	for (i = index; i < wingp->current_count - 1; i++)
7789 		wingp->ship_index[i] = wingp->ship_index[i+1];
7790 
7791 	wingp->current_count--;
7792 	Assert ( wingp->current_count >= 0 );
7793 	wingp->ship_index[wingp->current_count] = -1;
7794 
7795 	// adjust the special ship if necessary
7796 	if (wingp->special_ship > 0 && wingp->special_ship >= index)
7797 		wingp->special_ship--;
7798 
7799 	// if the current count is 0, check to see if the wing departed or was destroyed.
7800 	if (wingp->current_count == 0)
7801 	{
7802 		// if this wing was ordered to depart by the player, set the current_wave equal to the total
7803 		// waves so we can mark the wing as gone and no other ships arrive
7804 		// Goober5000 - also if it's departing... this is sort of, but not exactly, what :V: did;
7805 		// but it seems to be consistent with how it should behave
7806 		if (wingp->flags[Ship::Wing_Flags::Departing, Ship::Wing_Flags::Departure_ordered])
7807 			wingp->current_wave = wingp->num_waves;
7808 
7809 		// Goober5000 - some changes for clarity and closing holes
7810 		// make sure to flag the wing as gone if all of its member ships are gone and no more can arrive
7811 		if ((wingp->current_wave == wingp->num_waves) && (wingp->total_destroyed + wingp->total_departed + wingp->total_vanished == wingp->total_arrived_count))
7812 		{
7813 			// mark the wing as gone
7814 			wingp->flags.set(Ship::Wing_Flags::Gone);
7815 			wingp->time_gone = Missiontime;
7816 
7817 			// if all ships were destroyed, log it as destroyed
7818 			if (wingp->total_destroyed == wingp->total_arrived_count)
7819 			{
7820 				// first, be sure to mark a wing destroyed event if all members of wing were destroyed and on
7821 				// the last wave.  This circumvents a problem where the wing could be marked as departed and
7822 				// destroyed if the last ships were destroyed after the wing's departure cue became true.
7823 				mission_log_add_entry(LOG_WING_DESTROYED, wingp->name, NULL, team);
7824 			}
7825 			// if some ships escaped, log it as departed
7826 			else if (wingp->total_vanished != wingp->total_arrived_count)
7827 			{
7828 				// if the wing wasn't destroyed, and it is departing, then mark it as departed -- in this
7829 				// case, there had better be ships in this wing with departure entries in the log file.  The
7830 				// logfile code checks for this case.
7831 				mission_log_add_entry(LOG_WING_DEPARTED, wingp->name, NULL, team);
7832 			}
7833 
7834 #ifndef NDEBUG
7835 			//WMC - Ships can depart too, besides being destroyed :P
7836 			if ((wingp->total_destroyed + wingp->total_departed + wingp->total_vanished) != wingp->total_arrived_count)
7837 			{
7838 				// apparently, there have been reports of ships still present in the mission when this log
7839 				// entry if written.  Do a sanity check here to find out for sure.
7840 				for (ship_obj *so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so))
7841 				{
7842 					// skip the player -- stupid special case.
7843 					if (&Objects[so->objnum] == Player_obj)
7844 						continue;
7845 
7846 					if ((Game_mode & GM_MULTIPLAYER) && (Net_player->flags & NETINFO_FLAG_INGAME_JOIN))
7847 						continue;
7848 
7849 					if ((Ships[Objects[so->objnum].instance].wingnum == WING_INDEX(wingp)) && !(Ships[Objects[so->objnum].instance].is_dying_or_departing()))
7850 					{
7851 						// TODO: I think this Int3() is triggered when a wing whose ships are all docked to ships of another
7852 						// wing departs.  It can be reliably seen in TVWP chapter 1 mission 7, when Torino and Iota wing depart.
7853 						// Not sure how to fix this. -- Goober5000
7854 						Int3();
7855 					}
7856 				}
7857 			}
7858 #endif
7859 		}
7860 	}
7861 }
7862 
7863 // functions to do management, like log entries and wing cleanup after a ship has been destroyed
7864 
7865 // Goober5000
ship_actually_depart_helper(object * objp,dock_function_info * infop)7866 static void ship_actually_depart_helper(object *objp, dock_function_info *infop)
7867 {
7868 	// do standard departure stuff first
7869 	objp->flags.set(Object::Object_Flags::Should_be_dead);
7870 	if (objp->type == OBJ_SHIP)
7871 		ship_cleanup(objp->instance, infop->parameter_variables.bool_value ? SHIP_VANISHED : SHIP_DEPARTED);
7872 
7873 	// do the end-mission stuff if it's the player ship
7874 	if (objp == Player_obj)
7875 		gameseq_post_event(GS_EVENT_PLAYER_WARPOUT_DONE);
7876 }
7877 
7878 /**
7879  * Used to actually remove a ship, plus all the ships it's docked to, from the mission
7880  */
ship_actually_depart(int shipnum,int method)7881 void ship_actually_depart(int shipnum, int method)
7882 {
7883 	dock_function_info dfi;
7884 	dfi.parameter_variables.bool_value = (method == SHIP_VANISHED ? true:false);
7885 	dock_evaluate_all_docked_objects(&Objects[Ships[shipnum].objnum], &dfi, ship_actually_depart_helper);
7886 
7887 	// in a couple of cases we'll need to send a packet to update clients
7888 	if (MULTIPLAYER_MASTER && ((method == SHIP_DEPARTED_BAY) || (method == SHIP_VANISHED)) ) {
7889 		send_ship_depart_packet(&Objects[Ships[shipnum].objnum], method);
7890 	}
7891 }
7892 
7893 // no destruction effects, not for player destruction and multiplayer, only self-destruction
ship_destroy_instantly(object * ship_objp)7894 void ship_destroy_instantly(object *ship_objp)
7895 {
7896 	Assert(ship_objp->type == OBJ_SHIP);
7897 	Assert(!(ship_objp == Player_obj));
7898 
7899 	// undocking and death preparation
7900 	ship_stop_fire_primary(ship_objp);
7901 	ai_deathroll_start(ship_objp);
7902 
7903 	mission_log_add_entry(LOG_SELF_DESTRUCTED, Ships[ship_objp->instance].ship_name, NULL );
7904 
7905 	// scripting stuff
7906 	if (Script_system.IsActiveAction(CHA_DEATH)) {
7907 		Script_system.SetHookObjects(2, "Self", ship_objp, "Ship", ship_objp);
7908 		Script_system.RunCondition(CHA_DEATH, ship_objp);
7909 		Script_system.RemHookVars({"Self", "Ship"});
7910 	}
7911 
7912 	ship_objp->flags.set(Object::Object_Flags::Should_be_dead);
7913 	ship_cleanup(ship_objp->instance, SHIP_DESTROYED);
7914 }
7915 
7916 // convert the departure int method to a string --wookieejedi
get_departure_name(int method)7917 static const char* get_departure_name(int method)
7918 {
7919 	const char* departname;
7920 	switch (method) {
7921 	case SHIP_DEPARTED_WARP: {
7922 		departname = "SHIP_DEPARTED_WARP";
7923 		break;
7924 	};
7925 	case SHIP_DEPARTED_BAY: {
7926 		departname = "SHIP_DEPARTED_BAY";
7927 		break;
7928 	};
7929 	case SHIP_VANISHED: {
7930 		departname = "SHIP_VANISHED";
7931 		break;
7932 	};
7933 	case SHIP_DEPARTED_REDALERT: {
7934 		departname = "SHIP_DEPARTED_REDALERT";
7935 		break;
7936 	};
7937 	// assume SHIP_DEPARTED
7938 	default:
7939 		departname = "SHIP_DEPARTED";
7940 	};
7941 	return departname;
7942 }
7943 
7944 /**
7945  * Merge ship_destroyed and ship_departed and ship_vanished
7946  */
ship_cleanup(int shipnum,int cleanup_mode)7947 void ship_cleanup(int shipnum, int cleanup_mode)
7948 {
7949 	Assert(shipnum >= 0 && shipnum < MAX_SHIPS);
7950 	Assert(cleanup_mode &
7951 	       (SHIP_DESTROYED | SHIP_DEPARTED | SHIP_VANISHED | SHIP_DESTROYED_REDALERT | SHIP_DEPARTED_REDALERT));
7952 	Assert(Objects[Ships[shipnum].objnum].type == OBJ_SHIP);
7953 	Assert(Objects[Ships[shipnum].objnum].flags[Object::Object_Flags::Should_be_dead]);
7954 
7955 	ship *shipp = &Ships[shipnum];
7956 	object *objp = &Objects[shipp->objnum];
7957 	const char *jumpnode_name = nullptr;
7958 
7959 	// this should never happen
7960 	Assertion(Ship_registry_map.find(shipp->ship_name) != Ship_registry_map.end(), "Ship %s was destroyed, but was never stored in the ship registry!", shipp->ship_name);
7961 
7962 	// Goober5000 - handle ship registry
7963 	auto entry = &Ship_registry[Ship_registry_map[shipp->ship_name]];
7964 	entry->status = ShipStatus::EXITED;
7965 	entry->objp = nullptr;
7966 	entry->shipp = nullptr;
7967 	entry->cleanup_mode = cleanup_mode;
7968 
7969 	// add the information to the exited ship list
7970 	switch (cleanup_mode) {
7971 	case SHIP_DESTROYED:
7972 		ship_add_exited_ship(shipp, Ship::Exit_Flags::Destroyed);
7973 		break;
7974 	case SHIP_DEPARTED:
7975 	case SHIP_DEPARTED_WARP:
7976 	case SHIP_DEPARTED_BAY:
7977 		ship_add_exited_ship(shipp, Ship::Exit_Flags::Departed);
7978 		break;
7979 	case SHIP_DESTROYED_REDALERT:
7980 	case SHIP_DEPARTED_REDALERT:
7981 		// Ship was removed in previous mission. Mark as "player deleted" for this mission
7982 		ship_add_exited_ship(shipp, Ship::Exit_Flags::Player_deleted);
7983 		break;
7984 	case SHIP_VANISHED:
7985 		// Do nothing
7986 		break;
7987 	default:
7988 		// Can't Happen
7989 		UNREACHABLE("Unknown cleanup_mode '%i' passed to ship_cleanup!", cleanup_mode);
7990 		break;
7991 	}
7992 
7993 	// record kill?
7994 	if (cleanup_mode == SHIP_DESTROYED) {
7995 		// determine if we need to count this ship as a kill in counting number of kills per ship type
7996 		// look at the ignore flag for the ship (if not in a wing), or the ignore flag for the wing
7997 		// (if the ship is in a wing), and add to the kill count if the flags are not set
7998 		if ( !(shipp->flags[Ship_Flags::Ignore_count]) || ((shipp->wingnum != -1) && !(Wings[shipp->wingnum].flags[Ship::Wing_Flags::Ignore_count])) )
7999 			ship_add_ship_type_kill_count( shipp->ship_info_index );
8000 
8001 		// let the event music system know an enemy was destroyed (important for deciding when to transition from battle to normal music)
8002 		if (Player_ship != NULL && iff_x_attacks_y(Player_ship->team, shipp->team))
8003 			event_music_hostile_ship_destroyed();
8004 	}
8005 
8006 	// add mission log entry?
8007 	// (vanished ships and red-alert deleted ships have no log, and destroyed ships are logged in ship_hit_kill)
8008 	if ((cleanup_mode == SHIP_DEPARTED_WARP) || (cleanup_mode == SHIP_DEPARTED_BAY) || (cleanup_mode == SHIP_DEPARTED)) {
8009 		// see if this ship departed within the radius of a jump node -- if so, put the node name into
8010 		// the secondary mission log field
8011 		auto jnp = jumpnode_get_which_in(objp);
8012 		if (jnp)
8013 			jumpnode_name = jnp->GetName();
8014 
8015 		mission_log_add_entry(LOG_SHIP_DEPARTED, shipp->ship_name, jumpnode_name, shipp->wingnum);
8016 	}
8017 
8018 	// run "On Ship Depart" conditional hook variable that accounts for all departure types
8019 	// the hook is not limited to only warping --wookieejedi
8020 	if ((cleanup_mode == SHIP_DEPARTED_WARP) || (cleanup_mode == SHIP_DEPARTED_BAY) ||
8021 	    (cleanup_mode == SHIP_DEPARTED) || (cleanup_mode == SHIP_DEPARTED_REDALERT) ||
8022 	    (cleanup_mode == SHIP_VANISHED)) {
8023 		const char* departmethod = get_departure_name(cleanup_mode);
8024 
8025 		if (Script_system.IsActiveAction(CHA_ONSHIPDEPART)) {
8026 			Script_system.SetHookObject("Ship", objp);
8027 			Script_system.SetHookVar("Method", 's', departmethod);
8028 			Script_system.SetHookVar("JumpNode", 's', jumpnode_name);
8029 			Script_system.RunCondition(CHA_ONSHIPDEPART);
8030 			Script_system.RemHookVars({"Ship", "Method", "JumpNode"});
8031 		}
8032 	}
8033 
8034 #ifndef NDEBUG
8035 	// this isn't posted to the mission log, so log it here
8036 	if (cleanup_mode == SHIP_VANISHED) {
8037 		float mission_time = f2fl(Missiontime);
8038 		int minutes = (int)(mission_time / 60);
8039 		int seconds = (int)mission_time % 60;
8040 
8041 		nprintf(("missionlog", "MISSION LOG: %s vanished at %02d:%02d\n", shipp->ship_name, minutes, seconds));
8042 	}
8043 #endif
8044 
8045 	// update wingman status gauge
8046 	if ( (shipp->wing_status_wing_index >= 0) && (shipp->wing_status_wing_pos >= 0) ) {
8047 		switch (cleanup_mode) {
8048 		case SHIP_DESTROYED:
8049 		case SHIP_DESTROYED_REDALERT:
8050 			hud_set_wingman_status_dead(shipp->wing_status_wing_index, shipp->wing_status_wing_pos);
8051 			break;
8052 		case SHIP_DEPARTED:
8053 		case SHIP_DEPARTED_WARP:
8054 		case SHIP_DEPARTED_BAY:
8055 		case SHIP_DEPARTED_REDALERT:
8056 			hud_set_wingman_status_departed(shipp->wing_status_wing_index, shipp->wing_status_wing_pos);
8057 			break;
8058 		case SHIP_VANISHED:
8059 			hud_set_wingman_status_none(shipp->wing_status_wing_index, shipp->wing_status_wing_pos);
8060 			break;
8061 		default:
8062 			// Can't Happen, but we should've already caught this
8063 			UNREACHABLE("Unknown cleanup_mode '%i' passed to ship_cleanup!", cleanup_mode);
8064 			break;
8065 		}
8066 	}
8067 
8068 	// if ship belongs to a wing, do the wing cleanup
8069 	if ( shipp->wingnum != -1 ) {
8070 		wing *wingp = &Wings[shipp->wingnum];
8071 
8072 		switch (cleanup_mode) {
8073 		case SHIP_DESTROYED:
8074 		case SHIP_DESTROYED_REDALERT:
8075 			wingp->total_destroyed++;
8076 			break;
8077 		case SHIP_DEPARTED:
8078 		case SHIP_DEPARTED_WARP:
8079 		case SHIP_DEPARTED_BAY:
8080 		case SHIP_DEPARTED_REDALERT:
8081 			wingp->total_departed++;
8082 			break;
8083 		case SHIP_VANISHED:
8084 			wingp->total_vanished++;
8085 			break;
8086 		default:
8087 			// Can't Happen, but we should've already caught this
8088 			UNREACHABLE("Unknown cleanup_mode '%i' passed to ship_cleanup!", cleanup_mode);
8089 			break;
8090 		}
8091 		ship_wing_cleanup(shipnum, wingp);
8092 	}
8093 
8094 	// Note, this call to ai_ship_destroy() must come after ship_wing_cleanup for guarded wings to
8095 	// properly note the destruction of a ship in their wing.
8096 	ai_ship_destroy(shipnum);	// Do AI stuff for destruction/leave of ship.
8097 
8098 	// Goober5000 - lastly, clear out the dead-docked list, per Mantis #2294
8099 	// (for exploding ships, this list should have already been cleared by now, via
8100 	// do_dying_undock_physics, except in the case of the destroy-instantly sexp)
8101 	dock_dead_undock_all(objp);
8102 }
8103 
8104 /**
8105  * Calculates the blast and damage applied to a ship from another ship blowing up.
8106  *
8107  * @param pos1			ship explosion position
8108  * @param pos2			other ship position
8109  * @param inner_rad		distance from ship center for which full damage is applied
8110  * @param outer_rad		distance from ship center for which no damage is applied
8111  * @param max_damage	maximum damage applied
8112  * @param max_blast		maximum impulse applied from blast
8113  * @param damage		damage applied
8114  * @param blast			impulse applied from blast
8115  */
ship_explode_area_calc_damage(vec3d * pos1,vec3d * pos2,float inner_rad,float outer_rad,float max_damage,float max_blast,float * damage,float * blast)8116 int ship_explode_area_calc_damage( vec3d *pos1, vec3d *pos2, float inner_rad, float outer_rad, float max_damage, float max_blast, float *damage, float *blast )
8117 {
8118 	float dist;
8119 
8120 	dist = vm_vec_dist_quick( pos1, pos2 );
8121 
8122 	// check outside outer radius
8123 	if ( dist > outer_rad )
8124 		return -1;
8125 
8126 	if ( dist < inner_rad ) {
8127 	// check insider inner radius
8128 		*damage = max_damage;
8129 		*blast = max_blast;
8130 	} else {
8131 	// between inner and outer
8132 		float fraction = 1.0f - (dist - inner_rad) / (outer_rad - inner_rad);
8133 		*damage  = fraction * max_damage;
8134 		*blast   = fraction * max_blast;
8135 	}
8136 
8137 	return 1;
8138 }
8139 
8140 static const float MAX_SHOCK_ANGLE_RANGE = 1.99f * PI;
8141 
8142 /**
8143  * Applies damage to ship close to others when a ship dies and blows up
8144  *
8145  * @param exp_objp			ship object pointers
8146  */
ship_blow_up_area_apply_blast(object * exp_objp)8147 static void ship_blow_up_area_apply_blast( object *exp_objp)
8148 {
8149 	ship *shipp;
8150 	ship_info *sip;
8151 	float	inner_rad, outer_rad, max_damage, max_blast, shockwave_speed;
8152 
8153 	//	No area explosion in training missions.
8154 	if (The_mission.game_type & MISSION_TYPE_TRAINING){
8155 		return;
8156 	}
8157 
8158 	Assert( exp_objp != NULL );
8159 	Assert( exp_objp->type == OBJ_SHIP );
8160 	Assert( exp_objp->instance >= 0 );
8161 
8162 	shipp = &Ships[exp_objp->instance];
8163 	sip = &Ship_info[shipp->ship_info_index];
8164 
8165 	Assert( (shipp != NULL) && (sip != NULL) );
8166 
8167 	if ((exp_objp->hull_strength <= KAMIKAZE_HULL_ON_DEATH) && (Ai_info[Ships[exp_objp->instance].ai_index].ai_flags[AI::AI_Flags::Kamikaze]) && (shipp->special_exp_damage == -1)) {
8168 		int override = Ai_info[shipp->ai_index].kamikaze_damage;
8169 
8170 		inner_rad = exp_objp->radius*2.0f;
8171 		outer_rad = exp_objp->radius*4.0f; // + (override * 0.3f);
8172 		max_damage = i2fl(override);
8173 		max_blast = override * 5.0f;
8174 		shockwave_speed = 100.0f;
8175 	} else {
8176 		if (shipp->use_special_explosion) {
8177 			inner_rad = i2fl(shipp->special_exp_inner);
8178 			outer_rad = i2fl(shipp->special_exp_outer);
8179 			max_damage = i2fl(shipp->special_exp_damage);
8180 			max_blast = i2fl(shipp->special_exp_blast);
8181 			shockwave_speed = i2fl(shipp->special_exp_shockwave_speed);
8182 		} else {
8183 			inner_rad = sip->shockwave.inner_rad;
8184 			outer_rad = sip->shockwave.outer_rad;
8185 			max_damage = sip->shockwave.damage;
8186 			max_blast  = sip->shockwave.blast;
8187 			shockwave_speed = sip->shockwave.speed;
8188 		}
8189 	}
8190 
8191 	// account for ships that give no damage when they blow up.
8192 	if ( (max_damage < 0.1f) && (max_blast < 0.1f) ){
8193 		return;
8194 	}
8195 
8196 	if ( shockwave_speed > 0 ) {
8197 		shockwave_create_info sci = sip->shockwave;
8198 
8199 		sci.inner_rad = inner_rad;
8200 		sci.outer_rad = outer_rad;
8201 		sci.blast = max_blast;
8202 		sci.damage = max_damage;
8203 		sci.speed = shockwave_speed;
8204 		sci.rot_angles.p = frand_range(0.0f, MAX_SHOCK_ANGLE_RANGE);
8205 		sci.rot_angles.b = frand_range(0.0f, MAX_SHOCK_ANGLE_RANGE);
8206 		sci.rot_angles.h = frand_range(0.0f, MAX_SHOCK_ANGLE_RANGE);
8207 		shipfx_do_shockwave_stuff(shipp, &sci);
8208 	} else {
8209 		object *objp;
8210 		float blast = 0.0f;
8211 		float damage = 0.0f;
8212 		for ( objp = GET_FIRST(&obj_used_list); objp !=END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) ) {
8213 			if ( (objp->type != OBJ_SHIP) && (objp->type != OBJ_ASTEROID) ) {
8214 				continue;
8215 			}
8216 
8217 			if ( objp == exp_objp ){
8218 				continue;
8219 			}
8220 
8221 			// don't blast navbuoys
8222 			if ( objp->type == OBJ_SHIP ) {
8223 				if ( ship_get_SIF(objp->instance)[Ship::Info_Flags::Navbuoy] ) {
8224 					continue;
8225 				}
8226 			}
8227 
8228 			if ( ship_explode_area_calc_damage( &exp_objp->pos, &objp->pos, inner_rad, outer_rad, max_damage, max_blast, &damage, &blast ) == -1 ){
8229 				continue;
8230 			}
8231 
8232 			switch ( objp->type ) {
8233 			case OBJ_SHIP:
8234 				ship_apply_global_damage( objp, exp_objp, &exp_objp->pos, damage, sip->shockwave.damage_type_idx);
8235 				vec3d force, vec_ship_to_impact;
8236 				vm_vec_sub( &vec_ship_to_impact, &objp->pos, &exp_objp->pos );
8237 				vm_vec_copy_normalize( &force, &vec_ship_to_impact );
8238 				vm_vec_scale( &force, blast );
8239 				ship_apply_whack( &force, &exp_objp->pos, objp );
8240 				break;
8241 			case OBJ_ASTEROID:
8242 				asteroid_hit(objp, NULL, NULL, damage);
8243 				break;
8244 			default:
8245 				Int3();
8246 				break;
8247 			}
8248 		}	// end for
8249 	}
8250 }
8251 
8252 /**
8253  * Only ever called once for any ship that dies
8254  *
8255  * This function relies on the "dead dock" list, which replaces the dock_objnum_when_dead
8256  * used in retail.
8257  */
do_dying_undock_physics(object * dying_objp,ship * dying_shipp)8258 static void do_dying_undock_physics(object *dying_objp, ship *dying_shipp)
8259 {
8260 	// this function should only be called for an object that was docked...
8261 	// no harm in calling it if it wasn't, but we want to enforce this
8262 	Assert(object_is_dead_docked(dying_objp));
8263 
8264 	object *docked_objp;
8265 
8266 	float damage;
8267 	float impulse_mag;
8268 
8269 	vec3d impulse_norm, impulse_vec, pos;
8270 
8271 	// damage applied to each docked object
8272 	damage = 0.2f * dying_shipp->ship_max_hull_strength;
8273 
8274 	// Goober5000 - as with ai_deathroll_start, we can't simply iterate through the dock list while we're
8275 	// unlinking things.  So just repeatedly unlink the first object.
8276 	while (object_is_dead_docked(dying_objp))
8277 	{
8278 		docked_objp = dock_get_first_dead_docked_object(dying_objp);
8279 		ship *docked_shipp = &Ships[docked_objp->instance];
8280 		int dockee_index = dock_find_dead_dockpoint_used_by_object(docked_objp, dying_objp);
8281 
8282 		// undo all the docking animations for the docked ship only
8283 		model_anim_start_type(docked_shipp, AnimationTriggerType::Docked, dockee_index, -1);
8284 		model_anim_start_type(docked_shipp, AnimationTriggerType::Docking_Stage3, dockee_index, -1);
8285 		model_anim_start_type(docked_shipp, AnimationTriggerType::Docking_Stage2, dockee_index, -1);
8286 		model_anim_start_type(docked_shipp, AnimationTriggerType::Docking_Stage1, dockee_index, -1);
8287 
8288 		// only consider the mass of these two objects, not the whole assembly
8289 		// (this is inaccurate, but the alternative is a huge mess of extra code for a very small gain in realism)
8290 		float docked_mass = dying_objp->phys_info.mass + docked_objp->phys_info.mass;
8291 
8292 		// damage this docked object
8293 		ship_apply_global_damage(docked_objp, dying_objp, &dying_objp->pos, damage, -1);
8294 
8295 		// do physics
8296 		vm_vec_sub(&impulse_norm, &docked_objp->pos, &dying_objp->pos);
8297 		vm_vec_normalize(&impulse_norm);
8298 		// set for relative separation velocity of ~30
8299 		impulse_mag = 50.f * docked_objp->phys_info.mass * dying_objp->phys_info.mass / docked_mass;
8300 		vm_vec_copy_scale(&impulse_vec, &impulse_norm, impulse_mag);
8301 		vm_vec_rand_vec_quick(&pos);
8302 		vm_vec_scale(&pos, docked_objp->radius);
8303 		vm_vec_add2(&pos, &docked_objp->pos);
8304 		// apply whack to docked object
8305 		ship_apply_whack(&impulse_vec, &pos, docked_objp);
8306 		// enhance rotation of the docked object
8307 		vm_vec_scale(&docked_objp->phys_info.rotvel, 2.0f);
8308 
8309 		// apply whack to dying object
8310 		vm_vec_negate(&impulse_vec);
8311 		vm_vec_rand_vec_quick(&pos);
8312 		vm_vec_scale(&pos, dying_objp->radius);
8313 		vm_vec_add2(&pos, &dying_objp->pos);
8314 		ship_apply_whack(&impulse_vec, &pos, dying_objp);
8315 
8316 		// unlink the two objects, since dying_objp has blown up
8317 		dock_dead_undock_objects(dying_objp, docked_objp);
8318 	}
8319 }
8320 
8321 /**
8322  * Do the stuff we do in a frame for a ship that's in its death throes.
8323  */
ship_dying_frame(object * objp,int ship_num)8324 static void ship_dying_frame(object *objp, int ship_num)
8325 {
8326 	ship *shipp = &Ships[ship_num];
8327 
8328 	if ( shipp->flags[Ship_Flags::Dying] )	{
8329 		ship_info *sip = &Ship_info[shipp->ship_info_index];
8330 		bool knossos_ship = (sip->flags[Ship::Info_Flags::Knossos_device]);
8331 
8332 		// bash hull value toward 0 (from self destruct)
8333 		if (objp->hull_strength > 0) {
8334 			int time_left = timestamp_until(shipp->final_death_time);
8335 			float hits_left = objp->hull_strength;
8336 
8337 			objp->hull_strength -= hits_left * (1000.0f * flFrametime) / time_left;
8338 		}
8339 
8340 		// special case of VAPORIZE
8341 		if (shipp->flags[Ship_Flags::Vaporize]) {
8342 			if (timestamp_elapsed(shipp->final_death_time)) {
8343 				// play death sound
8344 				snd_play_3d( gamesnd_get_game_sound(GameSounds::VAPORIZED), &objp->pos, &View_position, objp->radius, NULL, 0, 1.0f, SND_PRIORITY_MUST_PLAY  );
8345 
8346 				// do joystick effect
8347 				if (objp == Player_obj) {
8348 					joy_ff_explode();
8349 				}
8350 
8351 				// if dying ship is docked, do damage to docked and physics
8352 				if (object_is_dead_docked(objp))  {
8353 					do_dying_undock_physics(objp, shipp);
8354 				}
8355 
8356 				// do all accounting for respawning client and server side here.
8357 				if (objp == Player_obj) {
8358 					gameseq_post_event(GS_EVENT_DEATH_BLEW_UP);
8359 				}
8360 
8361 				// mark object as dead
8362 				objp->flags.set(Object::Object_Flags::Should_be_dead);
8363 
8364 				// Don't blow up model.  Only use debris shards.
8365 				// call ship function to clean up after the ship is destroyed.
8366 				ship_cleanup(ship_num, SHIP_DESTROYED);
8367 				return;
8368 			} else {
8369 				return;
8370 			}
8371 		}
8372 
8373 		// bash the desired rotvel
8374 		objp->phys_info.desired_rotvel = shipp->deathroll_rotvel;
8375 
8376 		// Do fireballs for Big ship with propagating explostion, but not Kamikaze
8377 		if (!(Ai_info[shipp->ai_index].ai_flags[AI::AI_Flags::Kamikaze]) && ship_get_exp_propagates(shipp) && (sip->death_roll_r_mult > 0.0f)) {
8378 			if ( timestamp_elapsed(shipp->next_fireball))	{
8379 				vec3d outpnt, pnt1, pnt2;
8380 				polymodel *pm = model_get(sip->model_num);
8381 
8382 				// Gets two random points on the surface of a submodel
8383 				submodel_get_two_random_points_better(pm->id, pm->detail[0], &pnt1, &pnt2);
8384 
8385 				model_instance_find_world_point(&outpnt, &pnt1, shipp->model_instance_num, pm->detail[0], &objp->orient, &objp->pos );
8386 
8387 				float rad = objp->radius*0.1f;
8388 
8389 				if (sip->death_roll_r_mult != 1.0f)
8390 					rad *= sip->death_roll_r_mult;
8391 
8392 				int fireball_type = fireball_ship_explosion_type(sip);
8393 				if(fireball_type < 0) {
8394 					fireball_type = FIREBALL_EXPLOSION_LARGE1 + Random::next(FIREBALL_NUM_LARGE_EXPLOSIONS);
8395 				}
8396 				fireball_create( &outpnt, fireball_type, FIREBALL_LARGE_EXPLOSION, OBJ_INDEX(objp), rad, false, &objp->phys_info.vel );
8397 				// start the next fireball up in the next 50 - 200 ms (2-3 per frame)
8398 				int min_time = 333;
8399 				int max_time = 500;
8400 
8401 				if (sip->death_roll_time_mult != 1.0f) {
8402 					min_time = (int) (min_time / sip->death_roll_time_mult);
8403 					max_time = (int) (max_time / sip->death_roll_time_mult);
8404 				}
8405 
8406 				shipp->next_fireball = timestamp_rand(min_time,max_time);
8407 
8408 				// do sound - maybe start a random sound, if it has played far enough.
8409 				do_sub_expl_sound(objp->radius, &outpnt, shipp->sub_expl_sound_handle.data());
8410 			}
8411 		}
8412 
8413 		// create little fireballs for knossos as it dies
8414 		if (knossos_ship) {
8415 			if ( timestamp_elapsed(shipp->next_fireball)) {
8416 				vec3d rand_vec, outpnt; // [0-.7 rad] in plane
8417 				vm_vec_rand_vec_quick(&rand_vec);
8418 				float scale = -vm_vec_dot(&objp->orient.vec.fvec, &rand_vec) * (0.9f + 0.2f * frand());
8419 				vm_vec_scale_add2(&rand_vec, &objp->orient.vec.fvec, scale);
8420 				vm_vec_normalize_quick(&rand_vec);
8421 				scale = objp->radius * frand() * 0.717f;
8422 				vm_vec_scale(&rand_vec, scale);
8423 				vm_vec_add(&outpnt, &objp->pos, &rand_vec);
8424 
8425 				float rad = objp->radius*0.2f;
8426 
8427 				int fireball_type = fireball_ship_explosion_type(sip);
8428 				if(fireball_type < 0) {
8429 					fireball_type = FIREBALL_EXPLOSION_LARGE1 + Random::next(FIREBALL_NUM_LARGE_EXPLOSIONS);
8430 				}
8431 				fireball_create( &outpnt, fireball_type, FIREBALL_LARGE_EXPLOSION, OBJ_INDEX(objp), rad, false, &objp->phys_info.vel );
8432 				// start the next fireball up in the next 50 - 200 ms (2-3 per frame)
8433 				shipp->next_fireball = timestamp_rand(333,500);
8434 
8435 				// emit particles
8436 				particle::particle_emitter	pe;
8437 				particle_effect		pef = sip->knossos_end_particles;
8438 
8439 				pe.num_low = pef.n_low;					// Lowest number of particles to create
8440 				pe.num_high = pef.n_high;				// Highest number of particles to create
8441 				pe.pos = outpnt;				// Where the particles emit from
8442 				pe.vel = objp->phys_info.vel;	// Initial velocity of all the particles
8443 				pe.min_life = pef.min_life;	// How long the particles live
8444 				pe.max_life = pef.max_life;	// How long the particles live
8445 				pe.normal = objp->orient.vec.uvec;	// What normal the particle emit around
8446 				pe.normal_variance = pef.variance;		//	How close they stick to that normal 0=on normal, 1=180, 2=360 degree
8447 				pe.min_vel = pef.min_vel;
8448 				pe.max_vel = pef.max_vel;
8449 				pe.min_rad = pef.min_rad;	// * objp->radius;
8450 				pe.max_rad = pef.max_rad; // * objp->radius;
8451 
8452 				if (pe.num_high > 0) {
8453 					particle::emit( &pe, particle::PARTICLE_SMOKE2, 0, 50 );
8454 				}
8455 
8456 				// do sound - maybe start a random sound, if it has played far enough.
8457 				do_sub_expl_sound(objp->radius, &outpnt, shipp->sub_expl_sound_handle.data());
8458 			}
8459 		}
8460 
8461 		int time_until_minor_explosions = timestamp_until(shipp->final_death_time);
8462 
8463 		// Wait until just before death and set off some explosions
8464 		// If it is less than 1/2 second until large explosion, but there is
8465 		// at least 1/10th of a second left, then create 5 small explosions
8466 		if ( ((time_until_minor_explosions < 500) && (time_until_minor_explosions > 100) && (!shipp->pre_death_explosion_happened))
8467 			// If we're already exploding and missed our chance, then do it anyway; better late than never
8468 			|| ((time_until_minor_explosions <= 0) && (!shipp->pre_death_explosion_happened)) )
8469 		{
8470 			shipp->next_fireball = timestamp(-1);	// never time out again
8471 			shipp->pre_death_explosion_happened=1;		// Mark this event as having occurred
8472 
8473 			polymodel *pm = model_get(sip->model_num);
8474 			polymodel_instance *pmi = model_get_instance(shipp->model_instance_num);
8475 
8476 			// Start shockwave for ship with propagating explosion, do now for timing
8477 			if ( ship_get_exp_propagates(shipp) ) {
8478 				ship_blow_up_area_apply_blast( objp );
8479 			}
8480 
8481 			int zz_max = sip->death_fx_count;
8482 
8483 			for (int zz=0; zz<zz_max; zz++ ) {
8484 				// don't make sequence of fireballs for knossos
8485 				if (knossos_ship) {
8486 					break;
8487 				}
8488 
8489 				if (sip->death_fx_r_mult <= 0.0f) {
8490 					break;
8491 				}
8492 				// Find two random vertices on the model, then average them
8493 				// and make the piece start there.
8494 				vec3d tmp, outpnt, pnt1, pnt2;
8495 
8496 				// Gets two random points on the surface of a submodel [KNOSSOS]
8497 				submodel_get_two_random_points_better(pm->id, pm->detail[0], &pnt1, &pnt2);
8498 
8499 				vm_vec_avg( &tmp, &pnt1, &pnt2 );
8500 				model_instance_find_world_point(&outpnt, &tmp, pm, pmi, pm->detail[0], &objp->orient, &objp->pos );
8501 
8502 				float rad = objp->radius*0.40f;
8503 
8504 				rad *= sip->death_fx_r_mult;
8505 
8506 				int fireball_type = fireball_ship_explosion_type(sip);
8507 				if(fireball_type < 0) {
8508 					fireball_type = FIREBALL_EXPLOSION_MEDIUM;
8509 				}
8510 				fireball_create( &outpnt, fireball_type, FIREBALL_MEDIUM_EXPLOSION, OBJ_INDEX(objp), rad, false, &objp->phys_info.vel );
8511 			}
8512 		}
8513 
8514 		if ( timestamp_elapsed(shipp->final_death_time))	{
8515 			shipp->death_time = shipp->final_death_time;
8516 			shipp->final_death_time = timestamp(-1);	// never time out again
8517 
8518 			// play ship explosion sound effect, pick appropriate explosion sound
8519 			gamesnd_id sound_index;
8520 
8521 			if (ship_has_sound(objp, GameSounds::SHIP_EXPLODE_1))
8522 			{
8523 				sound_index = ship_get_sound(objp, GameSounds::SHIP_EXPLODE_1);
8524 			}
8525 			else
8526 			{
8527 				if (sip->flags[Info_Flags::Capital] || sip->flags[Info_Flags::Knossos_device]) {
8528 					sound_index=GameSounds::CAPSHIP_EXPLODE;
8529 				} else {
8530 					 if ( OBJ_INDEX(objp) & 1 ) {
8531 						sound_index=GameSounds::SHIP_EXPLODE_1;
8532 					} else {
8533 						sound_index=GameSounds::SHIP_EXPLODE_2;
8534 					}
8535 				}
8536 			}
8537 
8538 			if (sound_index.isValid())
8539 				snd_play_3d(gamesnd_get_game_sound(sound_index), &objp->pos, &View_position, objp->radius, nullptr, 0, 1.0f, SND_PRIORITY_MUST_PLAY);
8540 			if (objp == Player_obj)
8541 				joy_ff_explode();
8542 
8543 			if (shipp->death_roll_snd.isValid()) {
8544 				snd_stop(shipp->death_roll_snd);
8545 				shipp->death_roll_snd = sound_handle::invalid();
8546 			}
8547 
8548 			// if dying ship is docked, do damage to docked and physics
8549 			if (object_is_dead_docked(objp))  {
8550 				do_dying_undock_physics(objp, shipp);
8551 			}
8552 
8553 			if (!knossos_ship){
8554 				if (sip->death_effect.isValid()) {
8555 					// Use the new particle effect
8556 					auto source = particle::ParticleManager::get()->createSource(sip->death_effect);
8557 
8558 					// Use the position since the ship is going to be invalid soon
8559 					source.moveTo(&objp->pos);
8560 
8561 					source.finish();
8562 				} else {
8563 					// play a random explosion
8564 					particle::particle_emitter	pe;
8565 					particle_effect		pef = sip->regular_end_particles;
8566 
8567 					pe.num_low = pef.n_low;					// Lowest number of particles to create
8568 					pe.num_high = pef.n_high;				// Highest number of particles to create
8569 					pe.pos = objp->pos;				// Where the particles emit from
8570 					pe.vel = objp->phys_info.vel;	// Initial velocity of all the particles
8571 					pe.min_life = pef.min_life;				// How long the particles live
8572 					pe.max_life = pef.max_life;				// How long the particles live
8573 					pe.normal = objp->orient.vec.uvec;	// What normal the particle emit around
8574 					pe.normal_variance = pef.variance;		//	How close they stick to that normal 0=on normal, 1=180, 2=360 degree
8575 					pe.min_vel = pef.min_vel;				// How fast the slowest particle can move
8576 					pe.max_vel = pef.max_vel;				// How fast the fastest particle can move
8577 					pe.min_rad = pef.min_rad;				// Min radius
8578 					pe.max_rad = pef.max_rad;				// Max radius
8579 
8580 					if (pe.num_high > 0) {
8581 						particle::emit( &pe, particle::PARTICLE_SMOKE2, 0 );
8582 					}
8583 				}
8584 			}
8585 
8586 			// If this is a splitting explosion, set it split up.
8587 			if ( sip->explosion_splits_ship )	{
8588 				if (Ai_info[shipp->ai_index].ai_flags[AI::AI_Flags::Kamikaze]) {
8589 					ship_blow_up_area_apply_blast( objp );
8590 				}
8591 				shipfx_large_blowup_init(shipp);
8592 				// need to timeout immediately to keep physics in sync
8593 				shipp->really_final_death_time = timestamp(0);
8594 				polymodel *pm = model_get(sip->model_num);
8595 				shipp->end_death_time = timestamp((int) pm->core_radius);
8596 			} else {
8597 				// else, just a single big fireball
8598 				float big_rad;
8599 				int fireball_objnum, fireball_type, default_fireball_type;
8600 				float explosion_life;
8601 				big_rad = objp->radius*1.75f;
8602 
8603 				default_fireball_type = FIREBALL_EXPLOSION_LARGE1 + Random::next(FIREBALL_NUM_LARGE_EXPLOSIONS);
8604 				if (knossos_ship) {
8605 					big_rad = objp->radius * 1.2f;
8606 					default_fireball_type = FIREBALL_EXPLOSION_LARGE1;
8607 				}
8608 				//SUSHI: Option to override radius of big fireball
8609 				if (Ship_info[shipp->ship_info_index].big_exp_visual_rad >= 0)
8610 					big_rad = Ship_info[shipp->ship_info_index].big_exp_visual_rad;
8611 
8612 				fireball_type = fireball_ship_explosion_type(sip);
8613 				if(fireball_type < 0) {
8614 					fireball_type = default_fireball_type;
8615 				}
8616 				fireball_objnum = fireball_create( &objp->pos, fireball_type, FIREBALL_LARGE_EXPLOSION, OBJ_INDEX(objp), big_rad, false, &objp->phys_info.vel );
8617 				if ( fireball_objnum >= 0 )	{
8618 					explosion_life = fireball_lifeleft(&Objects[fireball_objnum]);
8619 				} else {
8620 					explosion_life = 0.0f;
8621 				}
8622 
8623 				// JAS:  I put in all this code because of an item on my todo list that
8624 				// said that the ship destroyed debris shouldn't pop in until the
8625 				// big explosion is 30% done.  I did this on Oct24 and me & Adam
8626 				// thought it looked dumb since the explosion didn't move with the
8627 				// ship, so instead of just taking this code out, since we might need
8628 				// it in the future, I disabled it.   You can reenable it by changing
8629 				// the commenting on the following two lines.
8630 				shipp->end_death_time = shipp->really_final_death_time = timestamp( fl2i(explosion_life*1000.0f)/5 );	// Wait till 30% of vclip time before breaking the ship up.
8631 			}
8632 
8633 			shipp->flags.set(Ship_Flags::Exploded);
8634 
8635 			if ( !(ship_get_exp_propagates(shipp)) ) {
8636 				// apply area of effect blast damage from ship explosion
8637 				ship_blow_up_area_apply_blast( objp );
8638 			}
8639 		}
8640 
8641 		if ( timestamp_elapsed(shipp->really_final_death_time))	{
8642 			// Copied from lock all turrets sexp
8643 			// Locks all turrets on ship that is about to split.
8644 			ship_subsys *subsys;
8645 			subsys = GET_FIRST(&shipp->subsys_list);
8646 			while ( subsys != END_OF_LIST(&shipp->subsys_list) )
8647 			{
8648 			// just mark all turrets as locked
8649 				if (subsys->system_info->type == SUBSYSTEM_TURRET) {
8650 					subsys->weapons.flags.set(Ship::Weapon_Flags::Turret_Lock);
8651 				}
8652 				subsys = GET_NEXT(subsys);
8653 			}
8654 
8655 			// do large_ship_split and explosion
8656 			if ( shipp->large_ship_blowup_index >= 0 ) {
8657 				if ( shipfx_large_blowup_do_frame(shipp, flFrametime) )	{
8658 					// do all accounting for respawning client and server side here.
8659 					if(objp == Player_obj) {
8660 						gameseq_post_event(GS_EVENT_DEATH_BLEW_UP);
8661 					}
8662 
8663 					objp->flags.set(Object::Object_Flags::Should_be_dead);
8664 
8665 					ship_cleanup(ship_num, SHIP_DESTROYED);		// call ship function to clean up after the ship is destroyed.
8666 				}
8667 				return;
8668 			}
8669 
8670 			shipfx_blow_up_model(objp, 0, sip->generic_debris_spew_num, &objp->pos);
8671 
8672 			// do all accounting for respawning client and server side here.
8673 			if(objp == Player_obj) {
8674 				gameseq_post_event(GS_EVENT_DEATH_BLEW_UP);
8675 			}
8676 
8677 			objp->flags.set(Object::Object_Flags::Should_be_dead);
8678 
8679 			ship_cleanup(ship_num, SHIP_DESTROYED);		// call ship function to clean up after the ship is destroyed.
8680 			shipp->really_final_death_time = timestamp( -1 );	// Never time out again!
8681 		}
8682 
8683 		// If a ship is dying (and not a capital or big ship) then stutter the engine sound
8684 		if ( timestamp_elapsed(shipp->next_engine_stutter) ) {
8685 			if ( !(sip->is_big_or_huge()) ) {
8686 				shipp->flags.toggle(Ship_Flags::Engines_on);			// toggle state of engines
8687 				shipp->next_engine_stutter = timestamp_rand(50, 250);
8688 			}
8689 		}
8690 	}
8691 }
8692 
8693 // Trickle buffered energy from weapon<->shield transfers into the main banks
ship_move_ets_transfer_buffers(ship * shipp,object * obj,float frametime)8694 static void ship_move_ets_transfer_buffers(ship *shipp, object *obj, float frametime)
8695 {
8696 	float delta;
8697 	ship_info *sip;
8698 
8699 	if (shipp->flags[Ship_Flags::Dying])
8700 		return;
8701 
8702 	sip = &Ship_info[shipp->ship_info_index];
8703 
8704 	delta = frametime * shipp->ship_max_shield_strength * sip->weap_shield_speed;
8705 
8706 	//	Chase target_shields and target_weapon_energy
8707 	if (shipp->target_shields_delta > 0.0f) {
8708 		if (delta > shipp->target_shields_delta)
8709 			delta = shipp->target_shields_delta;
8710 
8711 		shield_add_strength(obj, delta);
8712 		shipp->target_shields_delta -= delta;
8713 	} else if (shipp->target_shields_delta < 0.0f) {
8714 		//if (delta > -shipp->target_shields_delta)
8715 			delta = -shipp->target_shields_delta;
8716 
8717 		shield_add_strength(obj, -delta);
8718 		shipp->target_shields_delta += delta;
8719 	}
8720 
8721 	delta = frametime * sip->max_weapon_reserve * sip->shield_weap_speed;
8722 
8723 	if (shipp->target_weapon_energy_delta > 0.0f) {
8724 		if (delta > shipp->target_weapon_energy_delta)
8725 			delta = shipp->target_weapon_energy_delta;
8726 
8727 		shipp->weapon_energy += delta;
8728 		shipp->target_weapon_energy_delta -= delta;
8729 	} else if (shipp->target_weapon_energy_delta < 0.0f) {
8730 		//if (delta > -shipp->target_weapon_energy_delta)
8731 			delta = -shipp->target_weapon_energy_delta;
8732 
8733 		shipp->weapon_energy -= delta;
8734 		shipp->target_weapon_energy_delta += delta;
8735 	}
8736 
8737 }
8738 
thruster_glow_anim_load(generic_anim * ga)8739 static int thruster_glow_anim_load(generic_anim *ga)
8740 {
8741 	if ( !VALID_FNAME(ga->filename) )
8742 		return -1;
8743 
8744 	int fps = 15;
8745 
8746 	ga->first_frame = bm_load(ga->filename);
8747 	if (ga->first_frame < 0)
8748 	{
8749 		Warning(LOCATION, "Couldn't load thruster glow animation '%s'\nPrimary glow type effect does not accept .EFF or .ANI effects", ga->filename);
8750 		return -1;
8751 	}
8752 	ga->num_frames = NOISE_NUM_FRAMES;
8753 
8754 	Assert(fps != 0);
8755 	ga->total_time = i2fl(ga->num_frames)/fps;
8756 
8757 	return 0;
8758 }
8759 
8760 /**
8761  * Loads the animations for ship's afterburners
8762  */
ship_init_thrusters()8763 static void ship_init_thrusters()
8764 {
8765 	if ( Thrust_anim_inited == 1 )
8766 		return;
8767 
8768 	for (size_t i = 0; i < Species_info.size(); i++)
8769 	{
8770 		species_info *species = &Species_info[i];
8771 
8772 		generic_anim_load(&species->thruster_info.flames.normal);
8773 		generic_anim_load(&species->thruster_info.flames.afterburn);
8774 
8775 		// Bobboau's extra thruster stuff
8776 		{
8777 			generic_bitmap_load(&species->thruster_secondary_glow_info.normal);
8778 			generic_bitmap_load(&species->thruster_secondary_glow_info.afterburn);
8779 			generic_bitmap_load(&species->thruster_tertiary_glow_info.normal);
8780 			generic_bitmap_load(&species->thruster_tertiary_glow_info.afterburn);
8781 			generic_bitmap_load(&species->thruster_distortion_info.normal);
8782 			generic_bitmap_load(&species->thruster_distortion_info.afterburn);
8783 		}
8784 
8785 		// glows are handled a bit strangely
8786 		thruster_glow_anim_load(&species->thruster_info.glow.normal);
8787 		thruster_glow_anim_load(&species->thruster_info.glow.afterburn);
8788 	}
8789 
8790 	Thrust_anim_inited = 1;
8791 }
8792 
8793 
8794 /**
8795  * Figure out which thruster bitmap will get rendered next time around.
8796  *
8797  * ::ship_render() needs to have shipp->thruster_bitmap set to
8798  * a valid bitmap number, or -1 if we shouldn't render thrusters.
8799  */
ship_do_thruster_frame(ship * shipp,object * objp,float frametime)8800 static void ship_do_thruster_frame( ship *shipp, object *objp, float frametime )
8801 {
8802 	float rate;
8803 	int framenum;
8804 	int secondary_glow_bitmap, tertiary_glow_bitmap, distortion_bitmap;
8805 	generic_anim *flame_anim, *glow_anim;
8806 	ship_info	*sinfo = &Ship_info[shipp->ship_info_index];
8807 
8808 	if ( !Thrust_anim_inited ) {
8809 		ship_init_thrusters();
8810 	}
8811 
8812 	if (objp->phys_info.flags & PF_AFTERBURNER_ON) {
8813 		flame_anim = &sinfo->thruster_flame_info.afterburn;		// select afterburner flame
8814 		glow_anim = &sinfo->thruster_glow_info.afterburn;			// select afterburner glow
8815 		secondary_glow_bitmap = sinfo->thruster_secondary_glow_info.afterburn.bitmap_id;
8816 		tertiary_glow_bitmap = sinfo->thruster_tertiary_glow_info.afterburn.bitmap_id;
8817 		distortion_bitmap = sinfo->thruster_distortion_info.afterburn.bitmap_id;
8818 
8819 		rate = 1.5f;		// go at 1.5x faster when afterburners on
8820 	} else if (objp->phys_info.flags & PF_BOOSTER_ON) {
8821 		flame_anim = &sinfo->thruster_flame_info.afterburn;		// select afterburner flame
8822 		glow_anim = &sinfo->thruster_glow_info.afterburn;			// select afterburner glow
8823 		secondary_glow_bitmap = sinfo->thruster_secondary_glow_info.afterburn.bitmap_id;
8824 		tertiary_glow_bitmap = sinfo->thruster_tertiary_glow_info.afterburn.bitmap_id;
8825 		distortion_bitmap = sinfo->thruster_distortion_info.afterburn.bitmap_id;
8826 
8827 		rate = 2.5f;		// go at 2.5x faster when boosters on
8828 	} else {
8829 		flame_anim = &sinfo->thruster_flame_info.normal;			// select normal flame
8830 		glow_anim = &sinfo->thruster_glow_info.normal;				// select normal glow
8831 		secondary_glow_bitmap = sinfo->thruster_secondary_glow_info.normal.bitmap_id;
8832 		tertiary_glow_bitmap = sinfo->thruster_tertiary_glow_info.normal.bitmap_id;
8833 		distortion_bitmap = sinfo->thruster_distortion_info.normal.bitmap_id;
8834 
8835 		// If thrust at 0, go at half as fast, full thrust; full framerate
8836 		// so set rate from 0.67 to 1.67, depending on thrust from 0 to 1
8837 		rate = 0.67f * (1.0f + objp->phys_info.forward_thrust);
8838 	}
8839 
8840 	Assert( frametime > 0.0f );
8841 
8842 	// add primary thruster effects ...
8843 
8844 	if (flame_anim->first_frame >= 0) {
8845 		shipp->thruster_frame += frametime * rate;
8846 
8847 		framenum = bm_get_anim_frame(flame_anim->first_frame, shipp->thruster_frame, flame_anim->total_time, true);
8848 
8849 		// Get the bitmap for this frame
8850 		shipp->thruster_bitmap = flame_anim->first_frame + framenum;
8851 	} else {
8852 		shipp->thruster_frame = 0.0f;
8853 		shipp->thruster_bitmap = -1;
8854 	}
8855 
8856 	// primary glows ...
8857 	if (glow_anim->first_frame >= 0) {
8858 		shipp->thruster_glow_frame += frametime * rate;
8859 
8860 		framenum = bm_get_anim_frame(glow_anim->first_frame, shipp->thruster_glow_frame, glow_anim->total_time, true);
8861 
8862 		// Get the bitmap for this frame
8863 		shipp->thruster_glow_bitmap = glow_anim->first_frame;	// + framenum;
8864 		shipp->thruster_glow_noise = Noise[framenum];
8865 	} else {
8866 		shipp->thruster_glow_frame = 0.0f;
8867 		shipp->thruster_glow_bitmap = -1;
8868 		shipp->thruster_glow_noise = 1.0f;
8869 	}
8870 
8871 	// add extra thruster effects
8872 	shipp->thruster_secondary_glow_bitmap = secondary_glow_bitmap;
8873 	shipp->thruster_tertiary_glow_bitmap = tertiary_glow_bitmap;
8874 	shipp->thruster_distortion_bitmap = distortion_bitmap;
8875 }
8876 
8877 
8878 /**
8879  * Figure out which thruster bitmap will get rendered next time around.
8880  *
8881  * ship_render needs to have shipp->thruster_bitmap set to
8882  * a valid bitmap number, or -1 if we shouldn't render thrusters.
8883  *
8884  * This does basically the same thing as ship_do_thruster_frame, except it
8885  * operates on a weapon. This is in the ship code because it needs
8886  * the same thruster animation info as the ship stuff, and I would
8887  * rather extern this one function than all the thruster animation stuff.
8888  */
ship_do_weapon_thruster_frame(weapon * weaponp,object * objp,float frametime)8889 void ship_do_weapon_thruster_frame( weapon *weaponp, object *objp, float frametime )
8890 {
8891 	float rate;
8892 	int framenum;
8893 	generic_anim *flame_anim, *glow_anim;
8894 
8895 	if (!Thrust_anim_inited)
8896 		ship_init_thrusters();
8897 
8898 	species_info *species = &Species_info[weaponp->species];
8899 	weapon_info *wip = &Weapon_info[weaponp->weapon_info_index];
8900 
8901 	// If thrust at 0, go at half as fast, full thrust; full framerate
8902 	// so set rate from 0.67 to 1.67, depending on thrust from 0 to 1
8903 	rate = 0.67f * (1.0f + objp->phys_info.forward_thrust);
8904 
8905 	if (wip->thruster_flame.first_frame >= 0)
8906 		flame_anim = &wip->thruster_flame;
8907 	else
8908 		flame_anim = &species->thruster_info.flames.normal;
8909 
8910 	if (wip->thruster_glow.first_frame >= 0)
8911 		glow_anim = &wip->thruster_glow;
8912 	else
8913 		glow_anim  = &species->thruster_info.glow.normal;
8914 
8915 	Assert( frametime > 0.0f );
8916 
8917 	if (flame_anim->first_frame >= 0) {
8918 		weaponp->thruster_frame += frametime * rate;
8919 
8920 		framenum = bm_get_anim_frame(flame_anim->first_frame, weaponp->thruster_frame, flame_anim->total_time, true);
8921 
8922 		// Get the bitmap for this frame
8923 		weaponp->thruster_bitmap = flame_anim->first_frame + framenum;
8924 	} else {
8925 		weaponp->thruster_frame = 0.0f;
8926 		weaponp->thruster_bitmap = -1;
8927 	}
8928 
8929 	// Do it for glow bitmaps
8930 	if (glow_anim->first_frame >= 0) {
8931 		weaponp->thruster_glow_frame += frametime * rate;
8932 
8933 		framenum = bm_get_anim_frame(glow_anim->first_frame, weaponp->thruster_glow_frame, glow_anim->total_time, true);
8934 
8935 		// Get the bitmap for this frame
8936 		weaponp->thruster_glow_bitmap = glow_anim->first_frame;	// + framenum;
8937 		weaponp->thruster_glow_noise = Noise[framenum];
8938 	} else {
8939 		weaponp->thruster_glow_frame = 0.0f;
8940 		weaponp->thruster_glow_bitmap = -1;
8941 		weaponp->thruster_glow_noise = 1.0f;
8942 	}
8943 }
8944 
8945 
8946 
8947 // Repair damaged subsystems for a ship, called for each ship once per frame.
8948 // TODO: optimize by only calling ever N seconds and keeping track of elapsed time
8949 //
8950 // NOTE: need to update current_hits in the sp->subsys_list element, and the sp->subsys_info[]
8951 // element.
8952 #define SHIP_REPAIR_SUBSYSTEM_RATE	0.01f	// percent repair per second for a subsystem
8953 #define SUBSYS_REPAIR_THRESHOLD		0.1	// only repair subsystems that have > 10% strength
ship_auto_repair_frame(int shipnum,float frametime)8954 static void ship_auto_repair_frame(int shipnum, float frametime)
8955 {
8956 	ship_subsys		*ssp;
8957 	ship_subsys_info	*ssip;
8958 	ship			*sp;
8959 	ship_info		*sip;
8960 	object			*objp;
8961 	float			real_repair_rate;
8962 
8963 	#ifndef NDEBUG
8964 	if ( !Ship_auto_repair )	// only repair subsystems if Ship_auto_repair flag is set
8965 		return;
8966 	#endif
8967 
8968 	Assert( shipnum >= 0 && shipnum < MAX_SHIPS);
8969 	sp = &Ships[shipnum];
8970 	sip = &Ship_info[sp->ship_info_index];
8971 	objp = &Objects[sp->objnum];
8972 
8973 	if (sp->flags[Ship::Ship_Flags::Dying]) // do not repair if already dead
8974 		return;
8975 
8976 	//Repair the hull...or maybe unrepair?
8977 	if(sip->hull_repair_rate != 0.0f)
8978 	{
8979 		objp->hull_strength += sp->ship_max_hull_strength * sip->hull_repair_rate * frametime;
8980 
8981 		if (objp->hull_strength > sp->ship_max_hull_strength)
8982 			objp->hull_strength = sp->ship_max_hull_strength;
8983 		else if (objp->hull_strength < 0)
8984 			ship_hit_kill(objp, nullptr, nullptr, 0, true);
8985 	}
8986 
8987 	// only allow for the auto-repair of subsystems on small ships
8988 	//...NOT. Check if var has been changed from def -C
8989 	if ( !(sip->is_small_ship()) && sip->subsys_repair_rate == -2.0f)
8990 		return;
8991 
8992 	if(sip->subsys_repair_rate == -2.0f)
8993 		real_repair_rate = SHIP_REPAIR_SUBSYSTEM_RATE;
8994 	else
8995 		real_repair_rate = sip->subsys_repair_rate;
8996 
8997 	// AL 3-14-98: only allow auto-repair if power output not zero
8998 	if (sip->power_output <= 0)
8999 		return;
9000 
9001 	// iterate through subsystems, repair as needed based on elapsed frametime
9002 	for ( ssp = GET_FIRST(&sp->subsys_list); ssp != END_OF_LIST(&sp->subsys_list); ssp = GET_NEXT(ssp) ) {
9003 		Assert(ssp->system_info->type >= 0 && ssp->system_info->type < SUBSYSTEM_MAX);
9004 		ssip = &sp->subsys_info[ssp->system_info->type];
9005 
9006 		if ( ssp->current_hits < ssp->max_hits ) {
9007 
9008 			// only repair those subsystems which are not destroyed
9009 			if ( ssp->max_hits <= 0 )
9010 				continue;
9011 
9012 			if ( ssp->current_hits <= 0 ) {
9013 				if (sip->flags[Ship::Info_Flags::Subsys_repair_when_disabled]) {
9014 					if (ssp->flags[Ship::Subsystem_Flags::No_autorepair_if_disabled]) {
9015 						continue;
9016 					}
9017 				} else if (!(ssp->flags[Ship::Subsystem_Flags::Autorepair_if_disabled])) {
9018 					continue;
9019 				}
9020 			}
9021 
9022 			// do incremental repair on the subsystem
9023 			// check for overflow of current_hits
9024 			ssp->current_hits += ssp->max_hits * real_repair_rate * frametime;
9025 			if ( ssp->current_hits > ssp->max_hits ) {
9026 				ssp->current_hits = ssp->max_hits;
9027 			}
9028 
9029 			// aggregate repair
9030 			if (!(ssp->flags[Ship::Subsystem_Flags::No_aggregate])) {
9031 				ssip->aggregate_current_hits += ssip->aggregate_max_hits * real_repair_rate * frametime;
9032 				if ( ssip->aggregate_current_hits > ssip->aggregate_max_hits ) {
9033 					ssip->aggregate_current_hits = ssip->aggregate_max_hits;
9034 				}
9035 			}
9036 
9037 			// check to see if this subsystem was totally non functional before -- if so, then
9038 			// reset the flags
9039 			if ( (ssp->system_info->type == SUBSYSTEM_ENGINE) && (sp->flags[Ship_Flags::Disabled]) ) {
9040 				sp->flags.remove(Ship_Flags::Disabled);
9041 				ship_reset_disabled_physics(objp, sp->ship_info_index);
9042 			}
9043 		}
9044 	}	// end for
9045 }
9046 
9047 // this function checks to see how far the player has strayed from his starting location (should be
9048 // single player only).  Issues a warning at some distance.  Makes mission end if he keeps flying away
9049 // 3 strikes and you're out or too far away
9050 #define PLAYER_MAX_DIST_WARNING			700000			// distance in KM at which player gets warning to return to battle
9051 #define PLAYER_DISTANCE_MAX_WARNINGS	3				// maximum number of warnings player can receive before mission ends
9052 #define PLAYER_MAX_DIST_END				750000			// distance from starting loc at which we end mission
9053 #define PLAYER_WARN_DELTA_TIME			10000			//ms
9054 #define PLAYER_DEATH_DELTA_TIME			5000			//ms
9055 
ship_check_player_distance_sub(player * p,int multi_target=-1)9056 static void ship_check_player_distance_sub(player *p, int multi_target=-1)
9057 {
9058 	// only check distance for ships
9059 	if ( p->control_mode != PCM_NORMAL )	{
9060 		// already warping out... don't bother checking anymore
9061 		return;
9062 	}
9063 
9064 	float dist = vm_vec_dist_quick(&Objects[p->objnum].pos, &vmd_zero_vector);
9065 
9066 	int give_warning_to_player = 0;
9067 	if ( dist > PLAYER_MAX_DIST_WARNING ) {
9068 		if (p->distance_warning_count == 0) {
9069 			give_warning_to_player = 1;
9070 		} else {
9071 			if (timestamp_until(p->distance_warning_time) < 0) {
9072 				give_warning_to_player = 1;
9073 			}
9074 		}
9075 	}
9076 
9077 	if ( give_warning_to_player ) {
9078 		// increase warning count
9079 		p->distance_warning_count++;
9080 		// set timestamp unless player PLAYER_FLAGS_DIST_TO_BE_KILLED flag is set
9081 		if ( !(p->flags & PLAYER_FLAGS_DIST_TO_BE_KILLED) ) {
9082 			p->distance_warning_time = timestamp(PLAYER_WARN_DELTA_TIME);
9083 		}
9084 		// issue up to max warnings
9085 		if (p->distance_warning_count <= PLAYER_DISTANCE_MAX_WARNINGS) {
9086 			message_send_builtin_to_player( MESSAGE_STRAY_WARNING, NULL, MESSAGE_PRIORITY_HIGH, MESSAGE_TIME_SOON, 0, 0, multi_target, -1 );
9087 		}
9088 
9089 		if (p->distance_warning_count > PLAYER_DISTANCE_MAX_WARNINGS) {
9090 			p->flags |= PLAYER_FLAGS_DIST_WARNING;
9091 		}
9092 	}
9093 
9094 	if ( !(p->flags & PLAYER_FLAGS_FORCE_MISSION_OVER) && ((p->distance_warning_count > PLAYER_DISTANCE_MAX_WARNINGS) || (dist > PLAYER_MAX_DIST_END)) ) {
9095 //		DKA 5/17/99 - DON'T force warpout.  Won't work multiplayer.  Blow up ship.
9096 		if ( !(p->flags & PLAYER_FLAGS_DIST_TO_BE_KILLED) ) {
9097 			message_send_builtin_to_player( MESSAGE_STRAY_WARNING_FINAL, NULL, MESSAGE_PRIORITY_HIGH, MESSAGE_TIME_IMMEDIATE, 0, 0, multi_target, -1 );
9098 			p->flags |= PLAYER_FLAGS_DIST_TO_BE_KILLED;
9099 			p->distance_warning_time = timestamp(PLAYER_DEATH_DELTA_TIME);
9100 		}
9101 
9102 		// get hull strength and blow up
9103 		if ( (p->flags & PLAYER_FLAGS_DIST_TO_BE_KILLED) && (timestamp_until(p->distance_warning_time) < 0) ) {
9104 			p->flags |= PLAYER_FLAGS_FORCE_MISSION_OVER;
9105 			float damage = 10.0f * Objects[p->objnum].hull_strength;
9106 			ship_apply_global_damage(&Objects[p->objnum], &Objects[p->objnum], NULL, damage, -1);
9107 		}
9108 	}
9109 
9110 	// see if player has moved back into "bounds"
9111 	if ( (dist < PLAYER_MAX_DIST_WARNING) && (p->flags & PLAYER_FLAGS_DIST_WARNING) && !(p->flags & PLAYER_FLAGS_DIST_TO_BE_KILLED) ) {
9112 		p->flags &= ~PLAYER_FLAGS_DIST_WARNING;
9113 		p->distance_warning_count = 1;
9114 	}
9115 }
9116 
ship_check_player_distance()9117 static void ship_check_player_distance()
9118 {
9119 	int idx;
9120 
9121 	// multiplayer
9122 	if (Game_mode & GM_MULTIPLAYER) {
9123 		// if I'm the server, check all non-observer players including myself
9124 		if (MULTIPLAYER_MASTER) {
9125 			// warn all players
9126 			for (idx=0; idx<MAX_PLAYERS; idx++) {
9127 				if (MULTI_CONNECTED(Net_players[idx]) && !MULTI_STANDALONE(Net_players[idx]) && !MULTI_OBSERVER(Net_players[idx]) && (Objects[Net_players[idx].m_player->objnum].type != OBJ_GHOST) ) {
9128 					// if bad, blow him up
9129 					ship_check_player_distance_sub(Net_players[idx].m_player, idx);
9130 				}
9131 			}
9132 		}
9133 	}
9134 	// single player
9135 	else {
9136 		// maybe blow him up
9137 		ship_check_player_distance_sub(Player);
9138 	}
9139 }
9140 
observer_process_post(object * objp)9141 void observer_process_post(object *objp)
9142 {
9143 	Assert(objp != NULL);
9144 
9145 	if (objp == NULL)
9146 		return;
9147 
9148 	Assert(objp->type == OBJ_OBSERVER);
9149 
9150 	if (Game_mode & GM_MULTIPLAYER) {
9151 		// if I'm just an observer
9152 		if (MULTI_OBSERVER(Net_players[MY_NET_PLAYER_NUM])) {
9153 			float dist = vm_vec_dist_quick(&Player_obj->pos, &vmd_zero_vector);
9154 			// if beyond max dist, reset to 0
9155 			if (dist > PLAYER_MAX_DIST_END) {
9156 				// set me to zero
9157 				if ((Player_obj != NULL) && (Player_obj->type != OBJ_GHOST)) {
9158 					Player_obj->pos = vmd_zero_vector;
9159 				}
9160 			}
9161 		}
9162 	}
9163 }
9164 
9165 /**
9166  * Reset some physics info when ship's engines goes from disabled->enabled
9167  */
ship_reset_disabled_physics(object * objp,int ship_class)9168 void ship_reset_disabled_physics(object *objp, int ship_class)
9169 {
9170 	Assert(objp != NULL);
9171 
9172 	if (objp == NULL)
9173 		return;
9174 
9175 	objp->phys_info.flags &= ~(PF_REDUCED_DAMP | PF_DEAD_DAMP);
9176 	objp->phys_info.side_slip_time_const = Ship_info[ship_class].damp;
9177 }
9178 
9179 /**
9180  * Clear/set the subsystem disrupted flags
9181  */
ship_subsys_disrupted_check(ship * sp)9182 static void ship_subsys_disrupted_check(ship *sp)
9183 {
9184 	ship_subsys *ss;
9185 	int engines_disabled=0;
9186 
9187 	if ( sp->subsys_disrupted_flags & (1<<SUBSYSTEM_ENGINE) ) {
9188 		engines_disabled=1;
9189 	}
9190 
9191 	sp->subsys_disrupted_flags=0;
9192 
9193 	ss = GET_FIRST(&sp->subsys_list);
9194 	while ( ss != END_OF_LIST( &sp->subsys_list ) ) {
9195 		if ( !timestamp_elapsed(ss->disruption_timestamp) ) {
9196 			sp->subsys_disrupted_flags |= (1<<ss->system_info->type);
9197 		}
9198 		ss = GET_NEXT( ss );
9199 	}
9200 
9201 	if ( engines_disabled ) {
9202 		if ( !(sp->subsys_disrupted_flags & (1<<SUBSYSTEM_ENGINE)) ) {
9203 			if ( !(sp->flags[Ship_Flags::Disabled]) ) {
9204 				ship_reset_disabled_physics(&Objects[sp->objnum], sp->ship_info_index);
9205 			}
9206 		}
9207 	}
9208 }
9209 
9210 /**
9211  * Maybe check ship subsystems for disruption, and set/clear flags
9212  */
ship_subsys_disrupted_maybe_check(ship * shipp)9213 static void ship_subsys_disrupted_maybe_check(ship *shipp)
9214 {
9215 	if ( timestamp_elapsed(shipp->subsys_disrupted_check_timestamp) ) {
9216 		ship_subsys_disrupted_check(shipp);
9217 		shipp->subsys_disrupted_check_timestamp=timestamp(250);
9218 	}
9219 }
9220 
9221 /**
9222  * Determine if a given subsystem is disrupted (ie inoperable)
9223  *
9224  * @param ss	pointer to ship subsystem
9225  * @return		1 if subsystem is disrupted, 0 if subsystem is not disrupted
9226  */
ship_subsys_disrupted(ship_subsys * ss)9227 int ship_subsys_disrupted(ship_subsys *ss)
9228 {
9229 	if ( !ss ) {
9230 		Int3();		// should never happen, get Alan if it does.
9231 		return 0;
9232 	}
9233 
9234 	if ( timestamp_elapsed(ss->disruption_timestamp) ) {
9235 		return 0;
9236 	} else {
9237 		return 1;
9238 	}
9239 }
9240 
9241 /**
9242  * Disrupt a subsystem (ie make it inoperable for a time)
9243  *
9244  * @param ss	pointer to ship subsystem to be disrupted
9245  * @param time	time in ms that subsystem should be disrupted
9246  */
ship_subsys_set_disrupted(ship_subsys * ss,int time)9247 void ship_subsys_set_disrupted(ship_subsys *ss, int time)
9248 {
9249 	int time_left=0;
9250 
9251 	if ( !ss ) {
9252 		Int3();		// should never happen, get Alan if it does.
9253 		return;
9254 	}
9255 
9256 	time_left=timestamp_until(ss->disruption_timestamp);
9257 	if ( time_left < 0 ) {
9258 		time_left=0;
9259 	}
9260 
9261 	ss->disruption_timestamp = timestamp(time+time_left);
9262 }
9263 
9264 /**
9265  * Determine if a given type of subsystem is disrupted (i.e. inoperable)
9266  *
9267  * @param sp	pointer to ship containing subsystem
9268  * @param type	type of subsystem (SUBSYSTEM_*)
9269  * @return		1 if subsystem is disrupted, 0 if subsystem is not disrupted
9270  */
ship_subsys_disrupted(ship * sp,int type)9271 int ship_subsys_disrupted(ship *sp, int type)
9272 {
9273 	Assert ( sp != NULL );
9274 	Assert ( type >= 0 && type < SUBSYSTEM_MAX );
9275 
9276 	// Bogus pointer to ship to check for disrupted subsystem
9277 	if (sp == NULL)
9278 		return 0;
9279 
9280 	if ( sp->subsys_disrupted_flags & (1<<type) ) {
9281 		return 1;
9282 	} else {
9283 		return 0;
9284 	}
9285 }
9286 
9287 float Decay_rate = 1.0f / 120.0f;
9288 DCF(lethality_decay, "Sets ship lethality_decay, or the time in sec to go from 100 to 0 health (default is 1/120)")
9289 {
9290 	if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) {
9291 		dc_printf("Decay rate is currently %f\n", Decay_rate);
9292 		return;
9293 	}
9294 
9295 	dc_stuff_float(&Decay_rate);
9296 }
9297 
lethality_decay(ai_info * aip)9298 static void lethality_decay(ai_info *aip)
9299 {
9300 	float decay_rate = Decay_rate;
9301 	aip->lethality -= 100.0f * decay_rate * flFrametime;
9302 	aip->lethality = MAX(-10.0f, aip->lethality);
9303 
9304 #ifndef NDEBUG
9305 	if (Objects[Ships[aip->shipnum].objnum].flags[Object::Object_Flags::Player_ship]) {
9306 		if (Framecount % 10 == 0) {
9307 			int num_turrets = 0;
9308 			if ((aip->target_objnum != -1) && (Objects[aip->target_objnum].type == OBJ_SHIP)) {
9309 				//TODO: put this where it belongs, this would involve recompiling *everything* right now
9310 				//-WMC
9311 				int num_turrets_attacking(object *turret_parent, int target_objnum);
9312 				num_turrets = num_turrets_attacking(&Objects[aip->target_objnum], Ships[aip->shipnum].objnum);
9313 			}
9314 			nprintf(("lethality", "Player lethality: %.1f, num turrets targeting player: %d\n", aip->lethality, num_turrets));
9315 		}
9316 	}
9317 #endif
9318 }
9319 
9320 // moved out of ship_process_post() so it can be called from either -post() or -pre() depending on Framerate_independent_turning
ship_evaluate_ai(object * obj,float frametime)9321 void ship_evaluate_ai(object* obj, float frametime) {
9322 
9323 	int num = obj->instance;
9324 	Assertion(obj->type == OBJ_SHIP, "Non-ship object passed to ship_evaluate_ai");
9325 	Assertion(num >= 0 && num < MAX_SHIPS, "Invalid ship instance num in ship_evaluate_ai");
9326 	Assertion(Ships[num].objnum == OBJ_INDEX(obj), "Ship objnum does not match its num in OBJ_INDEX in ship_evaluate_ai");
9327 
9328 	ship* shipp = &Ships[num];
9329 
9330 	//rotate player subobjects since its processed by the ai functions
9331 	// AL 2-19-98: Fire turret for player if it exists
9332 	//WMC - changed this to call process_subobjects
9333 	if ((obj->flags[Object::Object_Flags::Player_ship]) && !Player_use_ai)
9334 	{
9335 		ai_info* aip = &Ai_info[Ships[obj->instance].ai_index];
9336 		if (aip->ai_flags[AI::AI_Flags::Being_repaired, AI::AI_Flags::Awaiting_repair])
9337 		{
9338 			if (aip->support_ship_objnum >= 0)
9339 			{
9340 				if (vm_vec_dist_quick(&obj->pos, &Objects[aip->support_ship_objnum].pos) < (obj->radius + Objects[aip->support_ship_objnum].radius) * 1.25f)
9341 					return;
9342 			}
9343 		}
9344 		if (!shipp->flags[Ship::Ship_Flags::Rotators_locked])
9345 			process_subobjects(OBJ_INDEX(obj));
9346 	}
9347 
9348 	// update ship lethality
9349 	if (Ships[num].ai_index >= 0 ){
9350 		if (!physics_paused && !ai_paused) {
9351 			lethality_decay(&Ai_info[Ships[num].ai_index]);
9352 		}
9353 	}
9354 
9355 	// if the ship is an observer ship don't need to do AI
9356 	if ( obj->type == OBJ_OBSERVER)  {
9357 		return;
9358 	}
9359 
9360 	// Goober5000 - player may want to use AI
9361 	if ( (Ships[num].ai_index >= 0) && (!(obj->flags[Object::Object_Flags::Player_ship]) || Player_use_ai) ){
9362 		if (!physics_paused && !ai_paused){
9363 			ai_process( obj, Ships[num].ai_index, frametime );
9364 		}
9365 	}
9366 }
9367 
ship_process_pre(object * obj,float frametime)9368 void ship_process_pre(object *obj, float frametime)
9369 {
9370 	// If Framerate_independent_turning is false everything following is evaluated in ship_process_post()
9371 	// Also only multi masters do ai
9372 	if ( (obj == nullptr) || !frametime || MULTIPLAYER_CLIENT || !Framerate_independent_turning)
9373 		return;
9374 
9375 	if (obj->type != OBJ_SHIP) {
9376 		nprintf(("AI", "Ignoring non-ship object in ship_process_pre()\n"));
9377 		return;
9378 	}
9379 
9380 	int num = obj->instance;
9381 	Assert(num >= 0 && num < MAX_SHIPS);
9382 	Assert(Ships[num].objnum == OBJ_INDEX(obj));
9383 	ship* shipp = &Ships[num];
9384 
9385 	if ((!(shipp->is_arriving()) || (Ai_info[shipp->ai_index].mode == AIM_BAY_EMERGE)
9386 		|| ((Warp_params[shipp->warpin_params_index].warp_type == WT_IN_PLACE_ANIM) && (shipp->flags[Ship_Flags::Arriving_stage_2])))
9387 		&& !(shipp->flags[Ship_Flags::Depart_warp]))
9388 	{
9389 		ship_evaluate_ai(obj, frametime);
9390 	}
9391 }
9392 
MONITOR(NumShips)9393 MONITOR( NumShips )
9394 
9395 static void ship_radar_process( object * obj, ship * shipp, ship_info * sip )
9396 {
9397 	Assert( obj != NULL);
9398 	Assert( shipp != NULL );
9399 	Assert( sip != NULL);
9400 
9401 	shipp->radar_last_status = shipp->radar_current_status;
9402 
9403 	RadarVisibility visibility = radar_is_visible(obj);
9404 
9405 	if (visibility == NOT_VISIBLE)
9406 	{
9407 		if (shipp->radar_last_contact < 0 && shipp->radar_visible_since < 0)
9408 		{
9409 			shipp->radar_visible_since = -1;
9410 			shipp->radar_last_contact = -1;
9411 		}
9412 		else
9413 		{
9414 			shipp->radar_visible_since = -1;
9415 			shipp->radar_last_contact = Missiontime;
9416 		}
9417 	}
9418 	else if (visibility == VISIBLE || visibility == DISTORTED)
9419 	{
9420 		if (shipp->radar_visible_since < 0)
9421 		{
9422 			shipp->radar_visible_since = Missiontime;
9423 		}
9424 
9425 		shipp->radar_last_contact = Missiontime;
9426 	}
9427 
9428 	shipp->radar_current_status = visibility;
9429 }
9430 
9431 
9432 /**
9433  * Player ship uses this code, but does a quick out after doing a few things.
9434  *
9435  * When adding code to this function, decide whether or not a client in a multiplayer game
9436  * needs to execute the code you are adding.  Code which moves things, creates things, etc
9437  * probably doesn't need to be called.  If you don't know -- find Allender!!!
9438  */
ship_process_post(object * obj,float frametime)9439 void ship_process_post(object * obj, float frametime)
9440 {
9441 	int	num;
9442 	ship	*shipp;
9443 	ship_info *sip;
9444 
9445 	if(obj->type != OBJ_SHIP){
9446 		nprintf(("General","Ignoring non-ship object in ship_process_post()\n"));
9447 		return;
9448 	}
9449 
9450 	MONITOR_INC( NumShips, 1 );
9451 
9452 	num = obj->instance;
9453 	Assert( num >= 0 && num < MAX_SHIPS);
9454 	Assert( obj->type == OBJ_SHIP );
9455 	Assert( Ships[num].objnum == OBJ_INDEX(obj));
9456 
9457 	shipp = &Ships[num];
9458 
9459 	sip = &Ship_info[shipp->ship_info_index];
9460 
9461 	shipp->shield_hits = 0;
9462 
9463 	update_ets(obj, frametime);
9464 
9465 	afterburners_update(obj, frametime);
9466 
9467 	ship_subsys_disrupted_maybe_check(shipp);
9468 
9469 	ship_dying_frame(obj, num);
9470 
9471 	ship_move_ets_transfer_buffers(shipp, obj, frametime);
9472 
9473 	// AL 1-6-98: record the initial ammo counts for ships, which is used as the max limit for rearming
9474 	// Goober5000 - added ballistics support
9475 	if ( !(shipp->flags[Ship_Flags::Ammo_count_recorded]) )
9476 	{
9477 		int max_missiles;
9478 		for ( int i=0; i<MAX_SHIP_SECONDARY_BANKS; i++ ) {
9479 			if ( red_alert_mission() )
9480 			{
9481 				max_missiles = get_max_ammo_count_for_bank(shipp->ship_info_index, i, shipp->weapons.secondary_bank_weapons[i]);
9482 				shipp->weapons.secondary_bank_start_ammo[i] = max_missiles;
9483 			}
9484 			else
9485 			{
9486 				shipp->weapons.secondary_bank_start_ammo[i] = shipp->weapons.secondary_bank_ammo[i];
9487 			}
9488 		}
9489 
9490 		for ( int i=0; i<MAX_SHIP_PRIMARY_BANKS; i++ )
9491 		{
9492 			if ( red_alert_mission() )
9493 			{
9494 				max_missiles = get_max_ammo_count_for_primary_bank(shipp->ship_info_index, i, shipp->weapons.primary_bank_weapons[i]);
9495 				shipp->weapons.primary_bank_start_ammo[i] = max_missiles;
9496 			}
9497 			else
9498 			{
9499 				shipp->weapons.primary_bank_start_ammo[i] = shipp->weapons.primary_bank_ammo[i];
9500 			}
9501 		}
9502 
9503 		shipp->flags.set(Ship_Flags::Ammo_count_recorded);
9504 	}
9505 
9506 	if(!(Game_mode & GM_STANDALONE_SERVER)) {
9507 		// Plot ship on the radar.  What about multiplayer ships?
9508 		if ( obj != Player_obj && Game_mode & GM_IN_MISSION )			// don't plot myself.
9509 			radar_plot_object( obj );
9510 
9511 		// MWA -- move the spark code to before the check for multiplayer master
9512 		//	Do ship sparks.  Don't do sparks on my ship (since I cannot see it).  This
9513 		// code will do sparks on other ships in multiplayer though.
9514 		// JAS: Actually in external view, you can see sparks, so I don't do sparks
9515 		// on the Viewer_obj, not Player_obj.
9516 		if ( (obj != Viewer_obj) && timestamp_elapsed(Ships[num].next_hit_spark) )	{
9517 			shipfx_emit_spark(num,-1);	// -1 means choose random spark location
9518 		}
9519 
9520 		if ( obj != Viewer_obj )	{
9521 			shipfx_do_damaged_arcs_frame( shipp );
9522 		}
9523 
9524 		// JAS - flicker the thruster bitmaps
9525 		ship_do_thruster_frame(shipp,obj,frametime);
9526 	}
9527 
9528 	ship_auto_repair_frame(num, frametime);
9529 
9530 	shipfx_do_lightning_frame(shipp);
9531 
9532 	// if the ship has an EMP effect active, process it
9533 	emp_process_ship(shipp);
9534 
9535 	// call the contrail system
9536 	ct_ship_process(shipp);
9537 
9538 	// process engine wash
9539 	void engine_wash_ship_process(ship *shipp);
9540 	engine_wash_ship_process(shipp);
9541 
9542 	// update TAG info
9543 	if(shipp->tag_left > 0.0f){
9544 		shipp->tag_left -= flFrametime;
9545 		if(shipp->tag_left <= 0.000001f){
9546 			shipp->tag_left = -1.0f;
9547 
9548 			mprintf(("Killing TAG for %s\n", shipp->ship_name));
9549 		}
9550 	}
9551 
9552 	// update level 2 TAG info
9553 	if(shipp->level2_tag_left > 0.0f){
9554 		shipp->level2_tag_left -= flFrametime;
9555 		if(shipp->level2_tag_left <= 0.000001f){
9556 			shipp->level2_tag_left = -1.0f;
9557 
9558 			mprintf(("Killing level 2 TAG for %s\n", shipp->ship_name));
9559 		}
9560 	}
9561 
9562 	if ( shipp->is_arriving(ship::warpstage::BOTH, true) && Ai_info[shipp->ai_index].mode != AIM_BAY_EMERGE )	{
9563 		// JAS -- if the ship is warping in, just move it forward at a speed
9564 		// fast enough to move 2x its radius in SHIP_WARP_TIME seconds.
9565 		shipfx_warpin_frame( obj, frametime );
9566 	} else if ( shipp->flags[Ship_Flags::Depart_warp] ) {
9567 		// JAS -- if the ship is warping out, just move it forward at a speed
9568 		// fast enough to move 2x its radius in SHIP_WARP_TIME seconds.
9569 		shipfx_warpout_frame( obj, frametime );
9570 	}
9571 
9572 	// update radar status of the ship
9573 	ship_radar_process(obj, shipp, sip);
9574 
9575 	if ( (!(shipp->is_arriving()) || (Ai_info[shipp->ai_index].mode == AIM_BAY_EMERGE)
9576 		|| ((Warp_params[shipp->warpin_params_index].warp_type == WT_IN_PLACE_ANIM) && (shipp->flags[Ship_Flags::Arriving_stage_2])) )
9577 		&&	!(shipp->flags[Ship_Flags::Depart_warp]))
9578 	{
9579 		//	Do AI.
9580 
9581 		// for multiplayer people.  return here if in multiplay and not the host
9582 		if ( MULTIPLAYER_CLIENT ) {
9583 			model_anim_handle_multiplayer( &Ships[num] );
9584 			return;
9585 		}
9586 
9587 		// MWA -- moved the code to maybe fire swarm missiles to after the check for
9588 		// multiplayer master.  Only single player and multi server needs to do this code
9589 		// this code might call ship_fire_secondary which will send the fire packets
9590 		swarm_maybe_fire_missile(num);
9591 
9592 		// maybe fire turret swarm missiles
9593 		void turret_swarm_maybe_fire_missile(int num);
9594 		turret_swarm_maybe_fire_missile(num);
9595 
9596 		// maybe fire a corkscrew missile (just like swarmers)
9597 		cscrew_maybe_fire_missile(num);
9598 
9599 		if (obj == Player_obj) {
9600 			ship_check_player_distance();
9601 		}
9602 
9603 		// If Framerate_independent_turning is true this is evaluated in ship_process_pre()
9604 		if (!Framerate_independent_turning)
9605 			ship_evaluate_ai(obj, frametime);
9606 	}
9607 }
9608 
9609 
9610 /**
9611  * Set the ship level weapons based on the information contained in the ship info.
9612  *
9613  * Weapon assignments are checked against the model to ensure the models
9614  * and the ship info weapon data are in synch.
9615  */
ship_set_default_weapons(ship * shipp,ship_info * sip)9616 static void ship_set_default_weapons(ship *shipp, ship_info *sip)
9617 {
9618 	int			i, j;
9619 	polymodel	*pm;
9620 	ship_weapon *swp = &shipp->weapons;
9621 	weapon_info *wip;
9622 
9623 	//	Copy primary and secondary weapons from ship_info to ship.
9624 	//	Later, this will happen in the weapon loadout screen.
9625 	for (i=0; i < MAX_SHIP_PRIMARY_BANKS; i++){
9626 		swp->primary_bank_weapons[i] = sip->primary_bank_weapons[i];
9627 		swp->primary_bank_slot_count[i] = 1; // RSAXVC DYN LINK CODE
9628 	}
9629 
9630 	for (i=0; i < MAX_SHIP_SECONDARY_BANKS; i++){
9631 		swp->secondary_bank_weapons[i] = sip->secondary_bank_weapons[i];
9632 	}
9633 
9634 	// Copy the number of primary and secondary banks to ship, and verify that
9635 	// model is in synch
9636 	pm = model_get( sip->model_num );
9637 
9638 	// Primary banks
9639 	if ( pm->n_guns > sip->num_primary_banks ) {
9640 		Assert(pm->n_guns <= MAX_SHIP_PRIMARY_BANKS);
9641 		Error(LOCATION, "There are %d primary banks in the model file,\nbut only %d primary banks specified for %s.\nThis must be fixed, as it will cause crashes.\n", pm->n_guns, sip->num_primary_banks, sip->name);
9642 		for ( i = sip->num_primary_banks; i < pm->n_guns; i++ ) {
9643 			// Make unspecified weapon for bank be a laser
9644 			for ( j = 0; j < Num_player_weapon_precedence; j++ ) {
9645 				Assertion((Player_weapon_precedence[j] > 0), "Error reading player weapon precedence list. Check weapons.tbl for $Player Weapon Precedence entry, and correct as necessary.\n");
9646 				int weapon_id = Player_weapon_precedence[j];
9647 				if ( (Weapon_info[weapon_id].subtype == WP_LASER) || (Weapon_info[weapon_id].subtype == WP_BEAM) ) {
9648 					swp->primary_bank_weapons[i] = weapon_id;
9649 					break;
9650 				}
9651 			}
9652 			Assert(swp->primary_bank_weapons[i] >= 0);
9653 		}
9654 		sip->num_primary_banks = pm->n_guns;
9655 	}
9656 	else if ( pm->n_guns < sip->num_primary_banks ) {
9657 		Warning(LOCATION, "There are %d primary banks specified for %s\nbut only %d primary banks in the model\n", sip->num_primary_banks, sip->name, pm->n_guns);
9658 		sip->num_primary_banks = pm->n_guns;
9659 	}
9660 
9661 	// Secondary banks
9662 	if ( pm->n_missiles > sip->num_secondary_banks ) {
9663 		Assert(pm->n_missiles <= MAX_SHIP_SECONDARY_BANKS);
9664 		Error(LOCATION, "There are %d secondary banks in the model file,\nbut only %d secondary banks specified for %s.\nThis must be fixed, as it will cause crashes.\n", pm->n_missiles, sip->num_secondary_banks, sip->name);
9665 		for ( i = sip->num_secondary_banks; i < pm->n_missiles; i++ ) {
9666 			// Make unspecified weapon for bank be a missile
9667 			for ( j = 0; j < Num_player_weapon_precedence; j++ ) {
9668 				Assertion((Player_weapon_precedence[j] > 0), "Error reading player weapon precedence list. Check weapons.tbl for $Player Weapon Precedence entry, and correct as necessary.\n");
9669 				int weapon_id = Player_weapon_precedence[j];
9670 				if (Weapon_info[weapon_id].subtype == WP_MISSILE) {
9671 					swp->secondary_bank_weapons[i] = weapon_id;
9672 					break;
9673 				}
9674 			}
9675 			Assert(swp->secondary_bank_weapons[i] >= 0);
9676 		}
9677 		sip->num_secondary_banks = pm->n_missiles;
9678 	}
9679 	else if ( pm->n_missiles < sip->num_secondary_banks ) {
9680 		Warning(LOCATION, "There are %d secondary banks specified for %s,\n but only %d secondary banks in the model.\n", sip->num_secondary_banks, sip->name, pm->n_missiles);
9681 		sip->num_secondary_banks = pm->n_missiles;
9682 	}
9683 
9684 	// added ballistic primary support - Goober5000
9685 	swp->num_primary_banks = sip->num_primary_banks;
9686 	for ( i = 0; i < swp->num_primary_banks; i++ )
9687 	{
9688 		wip = &Weapon_info[swp->primary_bank_weapons[i]];
9689 
9690 		if ( wip->wi_flags[Weapon::Info_Flags::Ballistic] )
9691 		{
9692 			if (Fred_running){
9693 				swp->primary_bank_ammo[i] = 100;
9694 			}
9695 			else
9696 			{
9697 				float capacity, size;
9698 				capacity = (float) sip->primary_bank_ammo_capacity[i];
9699 				size = (float) wip->cargo_size;
9700 				swp->primary_bank_ammo[i] = (int)std::lround(capacity / size);
9701 				swp->primary_bank_start_ammo[i] = swp->primary_bank_ammo[i];
9702 			}
9703 
9704 			swp->primary_bank_capacity[i] = sip->primary_bank_ammo_capacity[i];
9705 		}
9706 	}
9707 
9708 	swp->num_secondary_banks = sip->num_secondary_banks;
9709 	for ( i = 0; i < swp->num_secondary_banks; i++ ) {
9710 		if (Fred_running){
9711 			swp->secondary_bank_ammo[i] = 100;
9712 		} else {
9713 			wip = &Weapon_info[swp->secondary_bank_weapons[i]];
9714 			float size = (float) wip->cargo_size;
9715 			swp->secondary_bank_ammo[i] = fl2i(sip->secondary_bank_ammo_capacity[i]/size);
9716 			// Karajorma - Support ships will use the wrong values if we don't set this.
9717 			swp->secondary_bank_start_ammo[i] = swp->secondary_bank_ammo[i];
9718 		}
9719 
9720 		swp->secondary_bank_capacity[i] = sip->secondary_bank_ammo_capacity[i];
9721 	}
9722 
9723 	for ( i = 0; i < MAX_SHIP_PRIMARY_BANKS; i++ ){
9724 		swp->next_primary_fire_stamp[i] = timestamp(0);
9725 		swp->last_primary_fire_stamp[i] = -1;
9726 		swp->burst_counter[i] = 0;
9727 		swp->burst_seed[i] = Random::next();
9728 		swp->last_primary_fire_sound_stamp[i] = timestamp(0);
9729 	}
9730 
9731 	for ( i = 0; i < MAX_SHIP_SECONDARY_BANKS; i++ ){
9732 		swp->next_secondary_fire_stamp[i] = timestamp(0);
9733 		swp->last_secondary_fire_stamp[i] = -1;
9734 		swp->burst_counter[i + MAX_SHIP_PRIMARY_BANKS] = 0;
9735 		swp->burst_seed[i + MAX_SHIP_PRIMARY_BANKS] = Random::next();
9736 	}
9737 
9738 	//Countermeasures
9739 	shipp->current_cmeasure = sip->cmeasure_type;
9740 }
9741 
9742 
9743 /**
9744  * Faster version of ship_check_collision that does not do checking at the polygon
9745  * level.  Just checks to see if a vector will intersect a sphere.
9746  */
ship_check_collision_fast(object * obj,object * other_obj,vec3d * hitpos)9747 int ship_check_collision_fast( object * obj, object * other_obj, vec3d * hitpos)
9748 {
9749 	int num;
9750 	mc_info mc;
9751 
9752 	Assert( obj->type == OBJ_SHIP );
9753 	Assert( obj->instance >= 0 );
9754 
9755 	num = obj->instance;
9756 
9757 	mc_info_init(&mc);
9758 	mc.model_instance_num = Ships[num].model_instance_num;
9759 	mc.model_num = Ship_info[Ships[num].ship_info_index].model_num;	// Fill in the model to check
9760 	mc.orient = &obj->orient;					// The object's orient
9761 	mc.pos = &obj->pos;							// The object's position
9762 	mc.p0 = &other_obj->last_pos;			// Point 1 of ray to check
9763 	mc.p1 = &other_obj->pos;					// Point 2 of ray to check
9764 	mc.flags = MC_ONLY_SPHERE;				// flags
9765 
9766 	model_collide(&mc);
9767 	if (mc.num_hits)
9768 		*hitpos = mc.hit_point_world;
9769 
9770 	return mc.num_hits;
9771 }
9772 
9773 /**
9774  * Ensure create time for ship is unique
9775  */
ship_make_create_time_unique(ship * shipp)9776 static void ship_make_create_time_unique(ship *shipp)
9777 {
9778 	static int last_smctu_initial_time = -1;
9779 	static int last_smctu_final_time = -1;
9780 	int		sanity_counter = 0, collision;
9781 	ship		*compare_shipp;
9782 	ship_obj	*so;
9783 	uint		new_create_time;
9784 
9785 	new_create_time = shipp->create_time;
9786 
9787 	while (1) {
9788 
9789 		collision = 0;
9790 
9791 		for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) {
9792 			compare_shipp = &Ships[Objects[so->objnum].instance];
9793 
9794 			if ( compare_shipp == shipp ) {
9795 				continue;
9796 			}
9797 
9798 			if ( compare_shipp->create_time == new_create_time )
9799 			{
9800 				if((unsigned int)sanity_counter == 0 && (unsigned int)last_smctu_initial_time == shipp->create_time)
9801 				{
9802 					//WMC: If we're creating a whole bunch of ships at once, we can
9803 					//shortcut this process by looking at the last call to this function
9804 					//This fixes a bug when more than 50 ships are created at once.
9805 					new_create_time = last_smctu_final_time + 1;
9806 				}
9807 				else
9808 				{
9809 					new_create_time++;
9810 				}
9811 				collision = 1;
9812 				break;
9813 			}
9814 		}
9815 
9816 		if ( !collision ) {
9817 			last_smctu_initial_time = shipp->create_time;
9818 			last_smctu_final_time = new_create_time;
9819 			shipp->create_time = new_create_time;
9820 			break;
9821 		}
9822 
9823 		if ( sanity_counter++ > MAX_SHIPS ) {
9824 			Int3();
9825 			break;
9826 		}
9827 	}
9828 }
9829 
9830 int	Ship_subsys_hwm = 0;
9831 
show_ship_subsys_count()9832 static void show_ship_subsys_count()
9833 {
9834 	object	*objp;
9835 	int		count = 0;
9836 	int		o_type = 0;
9837 
9838 	for ( objp = GET_FIRST(&obj_used_list); objp != END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) ) {
9839 		o_type = (int)objp->type;
9840 		if (o_type == OBJ_SHIP) {
9841 			count += Ship_info[Ships[objp->instance].ship_info_index].n_subsystems;
9842 		}
9843 	}
9844 
9845 	if (count > Ship_subsys_hwm) {
9846 		Ship_subsys_hwm = count;
9847 	}
9848 }
9849 
ship_init_afterburners(ship * shipp)9850 static void ship_init_afterburners(ship *shipp)
9851 {
9852 	Assert( shipp );
9853 
9854 	shipp->ab_count = 0;
9855 
9856 	if (shipp->ship_info_index < 0) {
9857 		Int3();
9858 		return;
9859 	}
9860 
9861 	ship_info *sip = &Ship_info[shipp->ship_info_index];
9862 	Assert( sip->model_num >= 0 );
9863 	polymodel *pm = model_get(sip->model_num);
9864 	Assert( pm != NULL );
9865 
9866 	if ( !(sip->flags[Ship::Info_Flags::Afterburner]) ) {
9867 		return;
9868 	}
9869 
9870 	if (sip->afterburner_trail.bitmap_id < 0) {
9871 		return;
9872 	}
9873 
9874 	for (int i = 0; i < pm->n_thrusters; i++) {
9875 		thruster_bank *bank = &pm->thrusters[i];
9876 
9877 		for (int j = 0; j < bank->num_points; j++) {
9878 			// this means you've reached the max # of AB trails for a ship
9879 			if (shipp->ab_count >= MAX_SHIP_CONTRAILS) {
9880 				Int3();
9881 				break;
9882 			}
9883 
9884 			trail_info *ci = &shipp->ab_info[shipp->ab_count];
9885 
9886 			if (bank->points[j].norm.xyz.z > -0.5f) {
9887 				continue; // only make ab trails for thrusters that are pointing backwards
9888 			}
9889 
9890 			ci->pt = bank->points[j].pnt; //offset
9891 
9892 			ci->w_start = bank->points[j].radius * sip->afterburner_trail_width_factor;	// width * table loaded width factor
9893 			ci->w_end = 0.05f; //end width
9894 
9895 			ci->a_start = sip->afterburner_trail_alpha_factor; // start alpha  * table loaded alpha factor
9896 			ci->a_end = sip->afterburner_trail_alpha_end_factor; //end alpha
9897 			ci->a_decay_exponent = sip->afterburner_trail_alpha_decay_exponent;
9898 
9899 			ci->max_life = sip->afterburner_trail_life;	// table loaded max life
9900 			ci->stamp = 60;	//spew time???
9901 			ci->spread = sip->afterburner_trail_spread; // table loaded spread speed
9902 
9903 			ci->n_fade_out_sections = sip->afterburner_trail_faded_out_sections; // initial fade out
9904 
9905 			ci->texture.bitmap_id = sip->afterburner_trail.bitmap_id; // table loaded bitmap used on this ships burner trails
9906 			ci->texture_stretch = sip->afterburner_trail_tex_stretch;
9907 
9908 			nprintf(("AB TRAIL", "AB trail point #%d made for '%s'\n", shipp->ab_count, shipp->ship_name));
9909 
9910 			shipp->ab_count++;
9911 		}
9912 	}
9913 }
9914 
9915 /**
9916  * Returns object index of ship.
9917  * @return -1 means failed.
9918  */
ship_create(matrix * orient,vec3d * pos,int ship_type,const char * ship_name)9919 int ship_create(matrix* orient, vec3d* pos, int ship_type, const char* ship_name)
9920 {
9921 	int			i, n, objnum, j, k, t;
9922 	ship_info	*sip;
9923 	ship			*shipp;
9924 
9925 	t = ship_get_num_ships();
9926 
9927 	// The following check caps the number of ships that can be created.  Because Fred needs
9928 	// to create all the ships, regardless of when they arrive/depart, it needs a higher
9929 	// limit than FreeSpace.  On release, however, we will reduce it, thus FreeSpace needs
9930 	// to check against what this limit will be, otherwise testing the missions before
9931 	// release could work fine, yet not work anymore once a release build is made.
9932 	if (Fred_running) {
9933 		if (t >= MAX_SHIPS)
9934 			return -1;
9935 
9936 	} else {
9937 		if (t >= SHIPS_LIMIT) {
9938 			Error(LOCATION, XSTR("There is a limit of %d ships in the mission at once.  Please be sure that you do not have more than %d ships present in the mission at the same time.", 1495), SHIPS_LIMIT, SHIPS_LIMIT );
9939 			return -1;
9940 		}
9941 	}
9942 
9943 	for (n=0; n<MAX_SHIPS; n++){
9944 		if (Ships[n].objnum == -1){
9945 			break;
9946 		}
9947 	}
9948 
9949 	if (n == MAX_SHIPS){
9950 		return -1;
9951 	}
9952 
9953 	Assertion((ship_type >= 0) && (ship_type < ship_info_size()), "Invalid ship_type %d passed to ship_create() (expected value in the range 0-%d)\n", ship_type, ship_info_size()-1);
9954 	sip = &(Ship_info[ship_type]);
9955 	shipp = &Ships[n];
9956 	shipp->clear();
9957 
9958 	sip->model_num = model_load(sip->pof_file, sip->n_subsystems, &sip->subsystems[0]);		// use the highest detail level
9959 
9960 	shipp->model_instance_num = model_create_instance(true, sip->model_num);
9961 
9962 	if(strlen(sip->cockpit_pof_file))
9963 	{
9964 		sip->cockpit_model_num = model_load(sip->cockpit_pof_file, 0, NULL);
9965 	}
9966 
9967 	// maybe load an optional hud target model
9968 	if(strlen(sip->pof_file_hud)){
9969 		// check to see if a "real" ship uses this model. if so, load it up for him so that subsystems are setup properly
9970 		for(auto it = Ship_info.begin(); it != Ship_info.end(); ++it){
9971 			if(!stricmp(it->pof_file, sip->pof_file_hud)){
9972 				it->model_num = model_load(it->pof_file, it->n_subsystems, &it->subsystems[0]);
9973 			}
9974 		}
9975 
9976 		// mow load it for me with no subsystems
9977 		sip->model_num_hud = model_load(sip->pof_file_hud, 0, NULL);
9978 	}
9979 
9980 	if (strlen(sip->generic_debris_pof_file)) {
9981 		sip->generic_debris_model_num = model_load(sip->generic_debris_pof_file, 0, nullptr);
9982 		if (sip->generic_debris_model_num >= 0) {
9983 			polymodel* pm = model_get(sip->generic_debris_model_num);
9984 			sip->generic_debris_num_submodels = pm->n_models;
9985 		}
9986 	}
9987 
9988 	polymodel *pm = model_get(sip->model_num);
9989 
9990 	ship_copy_subsystem_fixup(sip);
9991 	show_ship_subsys_count();
9992 
9993 	if ( sip->num_detail_levels != pm->n_detail_levels )
9994 	{
9995 		if ( !Is_standalone )
9996 		{
9997 			// just log to file for standalone servers
9998 			Warning(LOCATION, "For ship '%s', detail level\nmismatch. Table has %d,\nPOF has %d.", sip->name, sip->num_detail_levels, pm->n_detail_levels );
9999 		}
10000 		else
10001 		{
10002 			nprintf(("Warning",  "For ship '%s', detail level mismatch. Table has %d, POF has %d.", sip->name, sip->num_detail_levels, pm->n_detail_levels ));
10003 		}
10004 	}
10005 	for ( i=0; i<pm->n_detail_levels; i++ )
10006 		pm->detail_depth[i] = (i < sip->num_detail_levels) ? i2fl(sip->detail_distance[i]) : 0.0f;
10007 
10008 	flagset<Object::Object_Flags> default_ship_object_flags;
10009 	default_ship_object_flags.set(Object::Object_Flags::Renders);
10010 	default_ship_object_flags.set(Object::Object_Flags::Physics);
10011 	// JAS: Nav buoys don't need to do collisions!
10012 	// G5K: Corrected to apply specifically for ships with the no-collide flag.  (In retail, navbuoys already have this flag, so this doesn't break anything.)
10013 	default_ship_object_flags.set(Object::Object_Flags::Collides, !sip->flags[Ship::Info_Flags::No_collide]);
10014 
10015 	objnum = obj_create(OBJ_SHIP, -1, n, orient, pos, model_get_radius(sip->model_num), default_ship_object_flags);
10016 	Assert( objnum >= 0 );
10017 
10018 	shipp->ai_index = ai_get_slot(n);
10019 	Assert( shipp->ai_index >= 0 );
10020 
10021 	// Goober5000 - if no ship name specified, or if specified ship already exists,
10022 	// or if specified ship has exited, use a default name
10023 	// Cyborg17 - The final check here was supposed to prevent duplicate names from being on the mission log and causing chaos,
10024 	// but it breaks multi, so there will just be a warning on debug instead.
10025 	if ((ship_name == nullptr) || (ship_name_lookup(ship_name) >= 0) /*|| (ship_find_exited_ship_by_name(ship_name) >= 0)*/) {
10026 		char suffix[NAME_LENGTH];
10027 		sprintf(suffix, NOX(" %d"), n);
10028 
10029 		// ensure complete ship name doesn't overflow the buffer
10030 		auto name_len = std::min(NAME_LENGTH - strlen(suffix) - 1, strlen(Ship_info[ship_type].name));
10031 		Assert(name_len > 0);
10032 
10033 		strncpy(shipp->ship_name, Ship_info[ship_type].name, name_len);
10034 		strcpy(shipp->ship_name + name_len, suffix);
10035 	} else {
10036 		if (ship_find_exited_ship_by_name(ship_name) >= 0 && !(Game_mode & GM_MULTIPLAYER)) {
10037 			Warning(LOCATION, "Newly-arrived ship %s has been given the same name as a ship previously destroyed in-mission. This can cause unpredictable SEXP behavior. Correct your mission file or scripts to prevent duplicates.", ship_name);
10038 		}
10039 		strcpy_s(shipp->ship_name, ship_name);
10040 	}
10041 
10042 	ship_set_default_weapons(shipp, sip);	//	Moved up here because ship_set requires that weapon info be valid.  MK, 4/28/98
10043 	ship_set(n, objnum, ship_type);
10044 
10045 	init_ai_object(objnum);
10046 	ai_clear_ship_goals( &Ai_info[Ships[n].ai_index] );		// only do this one here.  Can't do it in init_ai because it might wipe out goals in mission file
10047 
10048 	// Bump the object radius to ensure that collision detection works right
10049 	// even when spread shields extend outside the model's natural radius
10050 	if (sip->flags[Ship::Info_Flags::Auto_spread_shields]) {
10051 		Objects[objnum].radius += sip->auto_shield_spread;
10052 	}
10053 
10054 	// allocate memory for keeping glow point bank status (enabled/disabled)
10055 	{
10056 		bool val = true; // default value, enabled
10057 
10058 		if (pm->n_glow_point_banks)
10059 			shipp->glow_point_bank_active.resize( pm->n_glow_point_banks, val );
10060 	}
10061 
10062 	// fix up references into paths for this ship's model to point to a ship_subsys entry instead
10063 	// of a submodel index.  The ship_subsys entry should be the same for *all* instances of the
10064 	// same ship.
10065 	if (!(sip->flags[Ship::Info_Flags::Path_fixup]))
10066 	{
10067 		for ( i = 0; i < pm->n_paths; i++ )
10068 		{
10069 			for ( j = 0; j < pm->paths[i].nverts; j++ )
10070 			{
10071 				for ( k = 0; k < pm->paths[i].verts[j].nturrets; k++ )
10072 				{
10073 					int ptindex = pm->paths[i].verts[j].turret_ids[k];		// this index is a submodel number (ala bspgen)
10074 					int index;
10075 					ship_subsys *ss;
10076 
10077 					// iterate through the ship_subsystems looking for an id that matches
10078 					index = 0;
10079 					ss = GET_FIRST(&Ships[n].subsys_list);
10080 					while ( ss != END_OF_LIST( &Ships[n].subsys_list ) ) {
10081 						if ( ss->system_info->subobj_num == ptindex ) {			// when these are equal, fix up the ref
10082 							pm->paths[i].verts[j].turret_ids[k] = index;				// in path structure to index a ship_subsys
10083 							break;
10084 						}
10085 						index++;
10086 						ss = GET_NEXT( ss );
10087 					}
10088 
10089 					if ( ss == END_OF_LIST(&Ships[n].subsys_list) )
10090 						Warning(LOCATION, "Couldn't fix up turret indices in spline path\n\nModel: %s\nPath: %s\nVertex: %d\nTurret model id:%d\n\nThis probably means that the turret was not specified in the ship table(s).", sip->pof_file, pm->paths[i].name, j, ptindex );
10091 				}
10092 			}
10093 		}
10094 		sip->flags.set(Ship::Info_Flags::Path_fixup);
10095 	}
10096 
10097 	// this used to be done in parse_create_object_sub
10098 	if (!Fred_running)
10099 		ship_assign_sound(shipp);
10100 
10101 	// first try at ABtrails -Bobboau
10102 	ship_init_afterburners(shipp);
10103 
10104 	// call the contrail system
10105 	ct_ship_create(shipp);
10106 
10107 	model_anim_set_initial_states(shipp);
10108 	animation::anim_set_initial_states(shipp);
10109 
10110 	// Add this ship to Ship_obj_list
10111 	shipp->ship_list_index = ship_obj_list_add(objnum);
10112 
10113 	// Goober5000 - update the ship registry
10114 	// (since scripts and sexps can create ships, the entry may not yet exist)
10115 	auto ship_it = Ship_registry_map.find(shipp->ship_name);
10116 	if (ship_it == Ship_registry_map.end())
10117 	{
10118 		ship_registry_entry entry(shipp->ship_name);
10119 		entry.status = ShipStatus::PRESENT;
10120 		entry.objp = &Objects[objnum];
10121 		entry.shipp = shipp;
10122 
10123 		Ship_registry.push_back(entry);
10124 		Ship_registry_map[shipp->ship_name] = static_cast<int>(Ship_registry.size() - 1);
10125 	}
10126 	else
10127 	{
10128 		auto entry = &Ship_registry[ship_it->second];
10129 		entry->status = ShipStatus::PRESENT;
10130 		entry->objp = &Objects[objnum];
10131 		entry->shipp = shipp;
10132 	}
10133 
10134 	// Start up stracking for this ship in multi.
10135 	if (Game_mode & (GM_MULTIPLAYER)) {
10136 		multi_ship_record_add_ship(objnum);
10137 	}
10138 
10139 	// Set time when ship is created
10140 	shipp->create_time = timer_get_milliseconds();
10141 
10142 	ship_make_create_time_unique(shipp);
10143 
10144 	shipp->time_created = Missiontime;
10145 
10146 	return objnum;
10147 }
10148 
10149 /**
10150  * Change the ship model for a ship to that for ship class 'ship_type'
10151  *
10152  * @param n			index of ship in ::Ships[] array
10153  * @param ship_type	ship class (index into ::Ship_info vector)
10154  */
ship_model_change(int n,int ship_type)10155 static void ship_model_change(int n, int ship_type)
10156 {
10157 	int			i;
10158 	ship_info	*sip;
10159 	ship			*sp;
10160 	polymodel * pm;
10161 	object *objp;
10162 
10163 	Assert( n >= 0 && n < MAX_SHIPS );
10164 	sp = &Ships[n];
10165 	sip = &(Ship_info[ship_type]);
10166 	objp = &Objects[sp->objnum];
10167 
10168 	// get new model
10169 	if (sip->model_num == -1) {
10170 		sip->model_num = model_load(sip->pof_file, sip->n_subsystems, &sip->subsystems[0]);
10171 	}
10172 
10173 	if ( sip->cockpit_model_num == -1 ) {
10174 		if ( strlen(sip->cockpit_pof_file) ) {
10175 			sip->cockpit_model_num = model_load(sip->cockpit_pof_file, 0, NULL);
10176 		}
10177 	}
10178 
10179 	pm = model_get(sip->model_num);
10180 	Objects[sp->objnum].radius = model_get_radius(pm->id);
10181 
10182 	// page in nondims in game
10183 	if ( !Fred_running )
10184 		model_page_in_textures(sip->model_num, ship_type);
10185 
10186 	// allocate memory for keeping glow point bank status (enabled/disabled)
10187 	{
10188 		bool val = true; // default value, enabled
10189 
10190 		// clear out any old gpb's first, then add new ones if needed
10191 		sp->glow_point_bank_active.clear();
10192 
10193 		if (pm->n_glow_point_banks)
10194 			sp->glow_point_bank_active.resize( pm->n_glow_point_banks, val );
10195 	}
10196 
10197 	ship_copy_subsystem_fixup(sip);
10198 
10199 	if ( sip->num_detail_levels != pm->n_detail_levels )
10200 	{
10201 		if ( !Is_standalone )
10202 		{
10203 			// just log to file for standalone servers
10204 			Warning(LOCATION, "For ship '%s', detail level\nmismatch. Table has %d,\nPOF has %d.", sip->name, sip->num_detail_levels, pm->n_detail_levels );
10205 		}
10206 		else
10207 		{
10208 			nprintf(("Warning",  "For ship '%s', detail level mismatch. Table has %d, POF has %d.", sip->name, sip->num_detail_levels, pm->n_detail_levels ));
10209 		}
10210 	}
10211 	for ( i=0; i<pm->n_detail_levels; i++ )
10212 		pm->detail_depth[i] = (i < sip->num_detail_levels) ? i2fl(sip->detail_distance[i]) : 0.0f;
10213 
10214 	if (sip->flags[Ship::Info_Flags::Model_point_shields]) {
10215 		objp->n_quadrants = (int)pm->shield_points.size();
10216 		sp->shield_points = pm->shield_points;
10217 	} else {
10218 		objp->n_quadrants = DEFAULT_SHIELD_SECTIONS;
10219 	}
10220 	objp->shield_quadrant.resize(objp->n_quadrants);
10221 
10222 	// reset texture animations
10223 	sp->base_texture_anim_frametime = game_get_overall_frametime();
10224 }
10225 
10226 /**
10227  * Change the ship class on a ship, and changing all required information
10228  * for consistency (ie textures, subsystems, weapons, physics)
10229  *
10230  * @param n			index of ship in ::Ships[] array
10231  * @param ship_type	ship class (index into ::Ship_info vector)
10232  * @param by_sexp	SEXP reference
10233  */
change_ship_type(int n,int ship_type,int by_sexp)10234 void change_ship_type(int n, int ship_type, int by_sexp)
10235 {
10236 	int i;
10237 	ship_info	*sip;
10238 	ship_info	*sip_orig;
10239 	ship			*sp;
10240 	ship_weapon *swp;
10241 	ship_subsys *ss;
10242 	object		*objp;
10243 	p_object	*p_objp;
10244 	float hull_pct, shield_pct;
10245 	physics_info ph_inf;
10246 
10247 	Assert( n >= 0 && n < MAX_SHIPS );
10248 	sp = &Ships[n];
10249 
10250 	// do a quick out if we're already using the new ship class
10251 	if (sp->ship_info_index == ship_type)
10252 		return;
10253 
10254 	int objnum = sp->objnum;
10255 
10256 	swp = &sp->weapons;
10257 	sip = &(Ship_info[ship_type]);
10258 	sip_orig = &Ship_info[sp->ship_info_index];
10259 	objp = &Objects[objnum];
10260 	p_objp = mission_parse_get_parse_object(sp->ship_name);
10261 	ph_inf = objp->phys_info;
10262 
10263 	// MageKing17 - See if any AIs are doing anything with subsystems of this ship (targeting, goal to destroy)
10264 	// keep track of those subsystems and transfer the target/goal if the subsystem still exists, delete otherwise
10265 
10266 	SCP_list<int> target_matches;	// tells us to check this Ai_info index during subsystem traversal
10267 
10268 	SCP_list<ivec3> subsystem_matches;	// Ai_info index, goal index (or -1 for targeted), subsystem index
10269 	// not having a subsystem index at first is why we have the target_matches list:
10270 	// while traversing the subsystem list, a match can be compared directly to the
10271 	// subsystem object, and its index added to subsystem_matches.
10272 
10273 	SCP_list<weapon*> homing_matches;	// any missiles locked on to a subsystem from this ship
10274 
10275 	SCP_list<std::pair<weapon*, int>> homing_subsystem_matches;	// When homing_matches find matches, they put them here
10276 
10277 	SCP_list<weapon*> weapon_turret_matches;	// projectiles that were fired from turrets on this ship
10278 
10279 	SCP_list<std::pair<weapon*, int>> weapon_turret_subsystem_matches;
10280 
10281 	SCP_list<int> last_targeted_matches;
10282 
10283 	SCP_list<std::pair<int, int>> last_targeted_subsystem_matches;
10284 
10285 	if (!(Fred_running) && (Game_mode & GM_IN_MISSION)) {	// Doing this effort only makes sense in the middle of a mission.
10286 		// Delete ship sparks if the model changed
10287 		if (sip_orig->model_num != sip->model_num) {
10288 			memset(sp->sparks, 0, MAX_SHIP_HITS * sizeof(ship_spark));
10289 			sp->num_hits = 0;
10290 		}
10291 
10292 		for (i = 0; i < MAX_AI_INFO; i++) {
10293 			if (Ai_info[i].shipnum > -1) {
10294 				if (Ai_info[i].targeted_subsys && Ai_info[i].targeted_subsys->parent_objnum == objnum) {
10295 					target_matches.push_back(i);
10296 				}
10297 				ai_goal* goals = Ai_info[i].goals;
10298 				for (int j = 0; j < MAX_AI_GOALS; j++) {
10299 					// POSSIBLE OPTIMIZATION: goals[0] should be the active goal, so we might be able to reuse target_subsys_parent for that instead of doing ship_name_lookup()
10300 					if (goals[j].ai_mode == AI_GOAL_DESTROY_SUBSYSTEM && !(goals[j].flags[AI::Goal_Flags::Subsys_needs_fixup])) {	// If the subsystem name hasn't been parsed yet, we're fine.
10301 						int sindex = ship_name_lookup(goals[j].target_name);
10302 						if (sindex > -1 && Ships[sindex].objnum == objnum) {
10303 							ivec3 temp = {i, j, goals[j].ai_submode};
10304 							subsystem_matches.push_back(temp);
10305 						}
10306 					}
10307 				}
10308 			}
10309 		}
10310 
10311 		for (i = 0; i < MAX_WEAPONS; i++) {
10312 			if (Weapons[i].objnum == -1) {
10313 				continue;
10314 			}
10315 			weapon* wp = &Weapons[i];
10316 			if (wp->homing_subsys && wp->homing_subsys->parent_objnum == objnum) {
10317 				homing_matches.push_back(wp);
10318 			}
10319 			if (wp->turret_subsys && wp->turret_subsys->parent_objnum == objnum) {
10320 				weapon_turret_matches.push_back(wp);
10321 			}
10322 		}
10323 
10324 		for (i = 0; i < MAX_PLAYERS; i++) {
10325 			if (sp->last_targeted_subobject[i]) {
10326 				last_targeted_matches.push_back(i);
10327 			}
10328 		}
10329 	}
10330 
10331 	// Goober5000 - we can't copy the ship object because the tree of structs contains at least
10332 	// one class without a copy constructor.  So let's just save the information we need.
10333 
10334 	// these are wiped by ets_init_ship
10335 	int orig_wep_rechg_idx = sp->weapon_recharge_index;
10336 	int orig_shd_rechg_idx = sp->shield_recharge_index;
10337 	int orig_eng_rechg_idx = sp->engine_recharge_index;
10338 
10339 
10340 	// Goober5000 - maintain the original hull, shield, and subsystem percentages... gah
10341 	// ...except when in FRED, because this stuff is handled in the missionparse/missionsave part. The E
10342 
10343 	if (!Fred_running) {
10344 		// hull
10345 		if (sp->special_hitpoints) {
10346 			hull_pct = objp->hull_strength / sp->ship_max_hull_strength;
10347 		} else {
10348 			Assert( Ship_info[sp->ship_info_index].max_hull_strength > 0.0f );
10349 			hull_pct = objp->hull_strength / Ship_info[sp->ship_info_index].max_hull_strength;
10350 		}
10351 
10352 		// extra check
10353 		CLAMP(hull_pct, 0.01f, 1.0f);
10354 
10355 		// shield
10356 		if (sp->special_shield > 0) {
10357 			shield_pct = shield_get_strength(objp) / shield_get_max_strength(objp);
10358 		} else if (Ship_info[sp->ship_info_index].max_shield_strength > 0.0f) {
10359 			shield_pct = shield_get_strength(objp) / (sip_orig->max_shield_strength * sip_orig->max_shield_recharge);
10360 		} else {
10361 			shield_pct = 0.0f;
10362 		}
10363 
10364 		// extra check
10365 		Assert(shield_pct >= 0.0f && shield_pct <= 1.0f);
10366 		CLAMP(shield_pct, 0.0f, 1.0f);
10367 	} else {
10368 		shield_pct = hull_pct = 1.0f;
10369 	}
10370 
10371 	// subsystems
10372 	int num_saved_subsystems = 0;
10373 	char **subsys_names = new char *[sip_orig->n_subsystems];
10374 	float *subsys_pcts = new float[sip_orig->n_subsystems];
10375 
10376 	ss = GET_FIRST(&sp->subsys_list);
10377 	while ( ss != END_OF_LIST(&sp->subsys_list) )
10378 	{
10379 		if (num_saved_subsystems == sip_orig->n_subsystems)
10380 		{
10381 			Error(LOCATION, "Subsystem mismatch while changing ship class from '%s' to '%s'!", sip_orig->name, sip->name);
10382 			break;
10383 		}
10384 
10385 		// MageKing17 - Update subsystem pointers if changing classes mid-mission
10386 		if (!(Fred_running) && (Game_mode & GM_IN_MISSION)) {
10387 			// If any of our AI info objects targeting a subsystem on this ship are targeting this specific
10388 			// subsystem, add them to the subsystem_matches vector and remove them from our "to be checked" list.
10389 			{
10390 				auto it = target_matches.begin();
10391 				while (it != target_matches.end()) {
10392 					ai_info* aip = &Ai_info[*it];
10393 					if (aip->targeted_subsys == ss) {
10394 						ivec3 temp = {*it, -1, num_saved_subsystems};	// -1 for the "goal" index means targeted, not actually a goal
10395 						subsystem_matches.push_back(temp);
10396 						aip->targeted_subsys = NULL;	// Clear this so that aip->last_subsys_target won't point to a subsystem from the old list later
10397 						aip->targeted_subsys_parent = -1;
10398 						auto erasor = it;
10399 						++it;
10400 						target_matches.erase(erasor);
10401 					} else {
10402 						++it;
10403 					}
10404 				}
10405 			}
10406 
10407 			// Fix any weapons homing in on this subsystem.
10408 			{
10409 				auto it = homing_matches.begin();
10410 				while (it != homing_matches.end()) {
10411 					if ((*it)->homing_subsys == ss) {
10412 						homing_subsystem_matches.push_back(std::make_pair(*it, num_saved_subsystems));
10413 						auto erasor = it;
10414 						++it;
10415 						homing_matches.erase(erasor);
10416 					} else {
10417 						++it;
10418 					}
10419 				}
10420 			}
10421 
10422 			// Fix any projectiles fired from this subsystem.
10423 			{
10424 				auto it = weapon_turret_matches.begin();
10425 				while (it != weapon_turret_matches.end()) {
10426 					if ((*it)->turret_subsys == ss) {
10427 						weapon_turret_subsystem_matches.push_back(std::make_pair(*it, num_saved_subsystems));
10428 						auto erasor = it;
10429 						++it;
10430 						weapon_turret_matches.erase(erasor);
10431 					} else {
10432 						++it;
10433 					}
10434 				}
10435 			}
10436 
10437 			// Fix any subsystems in last_targeted_matches[].
10438 			{
10439 				auto it = last_targeted_matches.begin();
10440 				while (it != last_targeted_matches.end()) {
10441 					if (sp->last_targeted_subobject[*it] == ss) {
10442 						last_targeted_subsystem_matches.push_back(std::make_pair(*it, num_saved_subsystems));
10443 						auto erasor = it;
10444 						++it;
10445 						last_targeted_matches.erase(erasor);
10446 					} else {
10447 						++it;
10448 					}
10449 				}
10450 			}
10451 		}
10452 
10453 		// save subsys information
10454 		subsys_names[num_saved_subsystems] = new char[NAME_LENGTH];
10455 		strcpy(subsys_names[num_saved_subsystems], ss->system_info->subobj_name);
10456 
10457 		if (ss->max_hits > 0.0f)
10458 			subsys_pcts[num_saved_subsystems] = ss->current_hits / ss->max_hits;
10459 		else
10460 			subsys_pcts[num_saved_subsystems] = ss->max_hits;
10461 
10462 		// extra check
10463 		Assert(subsys_pcts[num_saved_subsystems] >= 0.0f && subsys_pcts[num_saved_subsystems] <= 1.0f);
10464 		CLAMP(subsys_pcts[num_saved_subsystems], 0.0f, 1.0f);
10465 
10466 		num_saved_subsystems++;
10467 		ss = GET_NEXT(ss);
10468 	}
10469 	Assertion(target_matches.empty(), "Failed to find matches for every currently-targeted subsystem on ship %s in change_ship_type(); get a coder!\n", sp->ship_name);
10470 	Assertion(homing_matches.empty(), "Failed to find matches for every subsystem being homed in on ship %s in change_ship_type(); get a coder!\n", sp->ship_name);
10471 	Assertion(weapon_turret_matches.empty(), "Failed to find matches for every turret a projectile was fired from on ship %s in change_ship_type(); get a coder!\n", sp->ship_name);
10472 	Assertion(last_targeted_matches.empty(), "Somehow failed to find every subsystem a player was previously targeting on ship %s in change_ship_type(); get a coder!\n", sp->ship_name);
10473 
10474 	// point to new ship data
10475 	ship_model_change(n, ship_type);
10476 	sp->ship_info_index = ship_type;
10477 
10478 	// create new model instance data
10479 	// note: this is needed for both subsystem stuff and submodel animation stuff
10480 	sp->model_instance_num = model_create_instance(true, sip->model_num);
10481 
10482 	// if we have the same warp parameters as the ship class, we will need to update them to point to the new class
10483 	if (sp->warpin_params_index == sip_orig->warpin_params_index) {
10484 		sp->warpin_params_index = sip->warpin_params_index;
10485 	}
10486 	if (sp->warpout_params_index == sip_orig->warpout_params_index) {
10487 		sp->warpout_params_index = sip->warpout_params_index;
10488 	}
10489 
10490 	if (!Fred_running) {
10491 		//WMC - set warp effects
10492 		ship_set_warp_effects(objp);
10493 	}
10494 
10495 	// set the correct hull strength
10496 	if (Fred_running) {
10497 		sp->ship_max_hull_strength = 100.0f;
10498 		objp->hull_strength = 100.0f;
10499 	} else {
10500 		if (sp->special_hitpoints > 0) {
10501 			sp->ship_max_hull_strength = (float)sp->special_hitpoints;
10502 		} else {
10503 			sp->ship_max_hull_strength = sip->max_hull_strength;
10504 		}
10505 
10506 		objp->hull_strength = hull_pct * sp->ship_max_hull_strength;
10507 	}
10508 
10509 	sp->max_shield_recharge = sip->max_shield_recharge;
10510 
10511 	// set the correct shield strength
10512 	if (Fred_running) {
10513 		if (sp->ship_max_shield_strength)
10514 			sp->ship_max_shield_strength = 100.0f;
10515 		objp->shield_quadrant[0] = 100.0f;
10516 	} else {
10517 		if (sp->special_shield >= 0) {
10518 			sp->ship_max_shield_strength = (float)sp->special_shield;
10519 		} else {
10520 			sp->ship_max_shield_strength = sip->max_shield_strength;
10521 		}
10522 
10523 		shield_set_strength(objp, shield_pct * shield_get_max_strength(objp));
10524 	}
10525 
10526 	// Goober5000: div-0 checks
10527 	Assert(sp->ship_max_hull_strength > 0.0f);
10528 	Assert(objp->hull_strength > 0.0f);
10529 
10530 	// Mantis 2763: moved down to have access to the right ship_max_shield_strength value
10531 	// make sure that shields are disabled/enabled if they need to be - Chief1983
10532 	if (!Fred_running) {
10533 		if ((p_objp != nullptr && p_objp->flags[Mission::Parse_Object_Flags::OF_Force_shields_on]) && (sp->ship_max_shield_strength > 0.0f)) {
10534 			objp->flags.remove(Object::Object_Flags::No_shields);
10535 		}
10536 		else if ((p_objp != nullptr && p_objp->flags[Mission::Parse_Object_Flags::OF_No_shields]) || (sp->ship_max_shield_strength == 0.0f)) {
10537 			objp->flags.set(Object::Object_Flags::No_shields);
10538 			// Since there's not a mission flag set to be adjusting this, see if there was a change from a ship that normally has shields to one that doesn't, and vice versa
10539 		}
10540 		else if (!(sip_orig->flags[Info_Flags::Intrinsic_no_shields]) && (sip->flags[Info_Flags::Intrinsic_no_shields])) {
10541 			objp->flags.set(Object::Object_Flags::No_shields);
10542 		}
10543 		else if ((sip_orig->flags[Info_Flags::Intrinsic_no_shields]) && !(sip->flags[Info_Flags::Intrinsic_no_shields]) && (sp->ship_max_shield_strength > 0.0f)) {
10544 			objp->flags.remove(Object::Object_Flags::No_shields);
10545 		}
10546 	}
10547 
10548 	// niffiwan: set new armor types
10549 	sp->armor_type_idx = sip->armor_type_idx;
10550 	sp->shield_armor_type_idx = sip->shield_armor_type_idx;
10551 	sp->collision_damage_type_idx = sip->collision_damage_type_idx;
10552 	sp->debris_damage_type_idx = sip->debris_damage_type_idx;
10553 
10554 	// subsys stuff done only after hull stuff is set
10555 	// if the subsystem list is not currently empty, then we need to clear it out first.
10556 	ship_subsystems_delete(sp);
10557 
10558 	// fix up the subsystems
10559 	subsys_set( sp->objnum );
10560 
10561 	// Goober5000 - restore the subsystem percentages
10562 	ss = GET_FIRST(&sp->subsys_list);
10563 	int ss_index = 0;
10564 	while ( ss != END_OF_LIST(&sp->subsys_list) )
10565 	{
10566 		for (i = 0; i < num_saved_subsystems; i++)
10567 		{
10568 			if (!subsystem_stricmp(ss->system_info->subobj_name, subsys_names[i]))
10569 			{
10570 				ss->current_hits = ss->max_hits * subsys_pcts[i];
10571 
10572 				// MageKing17 - Every AI doing something with this subsystem must transfer to the new one.
10573 				{
10574 					auto it = subsystem_matches.begin();
10575 					while (it != subsystem_matches.end()) {
10576 						if (it->z == i) {	// the subsystem is a match
10577 							int goalnum = it->y;
10578 							ai_info* aip = &Ai_info[it->x];
10579 							if (goalnum == -1) {
10580 								set_targeted_subsys(aip, ss, n);
10581 							} else {
10582 								aip->goals[goalnum].ai_submode = ss_index;
10583 							}
10584 							auto erasor = it;
10585 							++it;
10586 							subsystem_matches.erase(erasor);
10587 						} else {
10588 							++it;
10589 						}
10590 					}
10591 				}
10592 
10593 				// also weapons homing in on this subsystem
10594 				{
10595 					auto it = homing_subsystem_matches.begin();
10596 					while (it != homing_subsystem_matches.end()) {
10597 						if (it->second == i) {	// the subsystem is a match
10598 							it->first->homing_subsys = ss;
10599 							auto erasor = it;
10600 							++it;
10601 							homing_subsystem_matches.erase(erasor);
10602 						} else {
10603 							++it;
10604 						}
10605 					}
10606 				}
10607 
10608 				// also weapons fired from this subsystem
10609 				{
10610 					auto it = weapon_turret_subsystem_matches.begin();
10611 					while (it != weapon_turret_subsystem_matches.end()) {
10612 						if (it->second == i) {
10613 							it->first->turret_subsys = ss;
10614 							auto erasor = it;
10615 							++it;
10616 							weapon_turret_subsystem_matches.erase(erasor);
10617 						} else {
10618 							++it;
10619 						}
10620 					}
10621 				}
10622 
10623 				// and finally, fix previously-targeted subsystems
10624 				{
10625 					auto it = last_targeted_subsystem_matches.begin();
10626 					while (it != last_targeted_subsystem_matches.end()) {
10627 						if (it->second == i) {	// the subsystem is a match
10628 							sp->last_targeted_subobject[it->first] = ss;
10629 							auto erasor = it;
10630 							++it;
10631 							last_targeted_subsystem_matches.erase(erasor);
10632 						} else {
10633 							++it;
10634 						}
10635 					}
10636 				}
10637 
10638 				break;
10639 			}
10640 		}
10641 
10642 		ss_index++;
10643 		ss = GET_NEXT(ss);
10644 	}
10645 	ship_recalc_subsys_strength(sp);
10646 
10647 	for (auto cit = subsystem_matches.cbegin(); cit != subsystem_matches.cend(); ++cit) {
10648 		int goalnum = cit->y;
10649 		ai_info* aip = &Ai_info[cit->x];
10650 		if (goalnum == -1) {
10651 			if (aip == Player_ai) {
10652 				hud_cease_subsystem_targeting(0);
10653 			} else {
10654 				set_targeted_subsys(aip, nullptr, -1);
10655 			}
10656 		} else {
10657 			ai_remove_ship_goal(aip, goalnum);
10658 		}
10659 	}
10660 
10661 	subsystem_matches.clear();	// We don't need these anymore, so may as well clear them now.
10662 
10663 	for (auto cit = homing_subsystem_matches.cbegin(); cit != homing_subsystem_matches.cend(); ++cit) {
10664 		cit->first->homing_subsys = nullptr;
10665 	}
10666 
10667 	homing_subsystem_matches.clear();
10668 
10669 	for (auto cit = weapon_turret_subsystem_matches.cbegin(); cit != weapon_turret_subsystem_matches.cend(); ++cit) {
10670 		cit->first->turret_subsys = nullptr;
10671 	}
10672 
10673 	weapon_turret_subsystem_matches.clear();
10674 
10675 	for (auto cit = last_targeted_subsystem_matches.cbegin(); cit != last_targeted_subsystem_matches.cend(); ++cit) {
10676 		sp->last_targeted_subobject[cit->first] = nullptr;
10677 	}
10678 
10679 	last_targeted_subsystem_matches.clear();
10680 
10681 	// now free the memory
10682 	for (i = 0; i < sip_orig->n_subsystems; i++)
10683 		delete[] subsys_names[i];
10684 
10685 	delete [] subsys_names;
10686 	delete [] subsys_pcts;
10687 
10688 	sp->afterburner_fuel = MAX(0, sip->afterburner_fuel_capacity - (sip_orig->afterburner_fuel_capacity - sp->afterburner_fuel));
10689 	sp->cmeasure_count = MAX(0, sip->cmeasure_max - (sip_orig->cmeasure_max - sp->cmeasure_count));
10690 
10691 	// avoid cases where either of these are 0
10692 	if (sp->current_max_speed != 0 && sip_orig->max_speed != 0) {
10693 		sp->current_max_speed = sip->max_speed * (sp->current_max_speed / sip_orig->max_speed);
10694 	}
10695 	else {
10696 		sp->current_max_speed = sip->max_speed;
10697 	}
10698 
10699 	ship_set_default_weapons(sp, sip);
10700 	physics_ship_init(&Objects[sp->objnum]);
10701 	ets_init_ship(&Objects[sp->objnum]);
10702 
10703 	// Reset physics to previous values
10704 	if (by_sexp) {
10705 		Objects[sp->objnum].phys_info.desired_rotvel = ph_inf.desired_rotvel;
10706 		Objects[sp->objnum].phys_info.desired_vel = ph_inf.desired_vel;
10707 		Objects[sp->objnum].phys_info.forward_thrust = ph_inf.forward_thrust;
10708 		Objects[sp->objnum].phys_info.fspeed = ph_inf.fspeed;
10709 		Objects[sp->objnum].phys_info.heading = ph_inf.heading;
10710 		Objects[sp->objnum].phys_info.last_rotmat = ph_inf.last_rotmat;
10711 		Objects[sp->objnum].phys_info.prev_fvec = ph_inf.prev_fvec;
10712 		Objects[sp->objnum].phys_info.prev_ramp_vel = ph_inf.prev_ramp_vel;
10713 		Objects[sp->objnum].phys_info.reduced_damp_decay = ph_inf.reduced_damp_decay;
10714 		Objects[sp->objnum].phys_info.rotvel = ph_inf.rotvel;
10715 		Objects[sp->objnum].phys_info.shockwave_decay = ph_inf.shockwave_decay;
10716 		Objects[sp->objnum].phys_info.shockwave_shake_amp = ph_inf.shockwave_shake_amp;
10717 		Objects[sp->objnum].phys_info.side_thrust = ph_inf.side_thrust;
10718 		Objects[sp->objnum].phys_info.speed = ph_inf.speed;
10719 		Objects[sp->objnum].phys_info.vel = ph_inf.vel;
10720 		Objects[sp->objnum].phys_info.vert_thrust = ph_inf.vert_thrust;
10721 		Objects[sp->objnum].phys_info.ai_desired_orient = ph_inf.ai_desired_orient;
10722 	}
10723 
10724 	ship_set_new_ai_class(sp, sip->ai_class);
10725 
10726 	//======================================================
10727 
10728 	// Bobboau's thruster stuff again
10729 	if (sip->afterburner_trail.bitmap_id < 0)
10730 		generic_bitmap_load(&sip->afterburner_trail);
10731 
10732 	sp->ab_count = 0;
10733 	if (sip->flags[Ship::Info_Flags::Afterburner])
10734 	{
10735 		polymodel *pm = model_get(sip->model_num);
10736 
10737 		for (int h = 0; h < pm->n_thrusters; h++)
10738 		{
10739 			for (int j = 0; j < pm->thrusters[h].num_points; j++)
10740 			{
10741 				// this means you've reached the max # of AB trails for a ship
10742 				Assert(sip->ct_count <= MAX_SHIP_CONTRAILS);
10743 
10744 				trail_info *ci = &sp->ab_info[sp->ab_count];
10745 
10746 				// only make ab trails for thrusters that are pointing backwards
10747 				if (pm->thrusters[h].points[j].norm.xyz.z > -0.5)
10748 					continue;
10749 
10750 				ci->pt = pm->thrusters[h].points[j].pnt;	//offset
10751 				ci->w_start = pm->thrusters[h].points[j].radius * sip->afterburner_trail_width_factor;	// width * table loaded width factor
10752 
10753 				ci->w_end = 0.05f;//end width
10754 
10755 				ci->a_start = sip->afterburner_trail_alpha_factor;	// start alpha  * table loaded alpha factor
10756 
10757 				ci->a_end = sip->afterburner_trail_alpha_end_factor;//end alpha
10758 
10759 				ci->a_decay_exponent = sip->afterburner_trail_alpha_decay_exponent;
10760 
10761 				ci->max_life = sip->afterburner_trail_life;	// table loaded max life
10762 
10763 				ci->spread = sip->afterburner_trail_spread; // table loaded spread speed
10764 
10765 				ci->stamp = 60;	//spew time???
10766 
10767 				ci->n_fade_out_sections = sip->afterburner_trail_faded_out_sections; // table loaded n sections to be faded out
10768 
10769 				ci->texture.bitmap_id = sip->afterburner_trail.bitmap_id; // table loaded bitmap used on this ships burner trails
10770 
10771 				ci->texture_stretch = sip->afterburner_trail_tex_stretch;
10772 				nprintf(("AB TRAIL", "AB trail point #%d made for '%s'\n", sp->ab_count, sp->ship_name));
10773 				sp->ab_count++;
10774 				Assert(MAX_SHIP_CONTRAILS > sp->ab_count);
10775 			}
10776 		}
10777 	}//end AB trails -Bobboau
10778 
10779 	// Goober5000 - check other class-specific flags too
10780 
10781 	if (sip->flags[Ship::Info_Flags::Stealth])			// changing TO a stealthy ship class
10782 		sp->flags.set(Ship_Flags::Stealth);
10783 	else if (sip_orig->flags[Ship::Info_Flags::Stealth])	// changing FROM a stealthy ship class
10784 		sp->flags.remove(Ship_Flags::Stealth);
10785 
10786 	if (sip->flags[Ship::Info_Flags::Ship_class_dont_collide_invis])				// changing TO a don't-collide-invisible ship class
10787 		sp->flags.set(Ship_Flags::Dont_collide_invis);
10788 	else if (sip_orig->flags[Ship::Info_Flags::Ship_class_dont_collide_invis])	// changing FROM a don't-collide-invisible ship class
10789 		sp->flags.remove(Ship_Flags::Dont_collide_invis);
10790 
10791 	if (sip->flags[Ship::Info_Flags::No_collide])								// changing TO a no-collision ship class
10792 		obj_set_flags(objp, objp->flags - Object::Object_Flags::Collides);
10793 	else if (sip_orig->flags[Ship::Info_Flags::No_collide])						// changing FROM a no-collision ship class
10794 		obj_set_flags(objp, objp->flags + Object::Object_Flags::Collides);
10795 
10796 	if (sip->flags[Ship::Info_Flags::No_ets])
10797 		sp->flags.set(Ship_Flags::No_ets);
10798 	else if (sip_orig->flags[Ship::Info_Flags::No_ets])
10799 		sp->flags.remove(Ship_Flags::No_ets);
10800 
10801 
10802 	// Chief1983: Make sure that when changing to a new ship with secondaries, you switch to bank 0.  They still won't
10803 	// fire if the SF2_SECONDARIES_LOCKED flag is on as this should have carried over.
10804 	if ( swp->num_secondary_banks > 0 && swp->current_secondary_bank == -1 ){
10805 		swp->current_secondary_bank = 0;
10806 	}
10807 
10808 	// Bobboau's animation fixup
10809 	for( i = 0; i<MAX_SHIP_PRIMARY_BANKS;i++){
10810 			swp->primary_animation_position[i] = MA_POS_NOT_SET;
10811 	}
10812 	for( i = 0; i<MAX_SHIP_SECONDARY_BANKS;i++){
10813 			swp->secondary_animation_position[i] = MA_POS_NOT_SET;
10814 	}
10815 	model_anim_set_initial_states(sp);
10816 	animation::anim_set_initial_states(sp);
10817 
10818 	//Reassign sound stuff
10819 	if (!Fred_running)
10820 		ship_assign_sound(sp);
10821 
10822 	// Valathil - Reinitialize collision checks
10823 	obj_remove_collider(OBJ_INDEX(objp));
10824 	obj_add_collider(OBJ_INDEX(objp));
10825 
10826 	// The E - If we're switching during gameplay, make sure we get valid primary/secondary selections
10827 	if ( by_sexp ) {
10828 		if (sip_orig->num_primary_banks > sip->num_primary_banks) {
10829 			sp->weapons.current_primary_bank = 0;
10830 			sp->weapons.previous_primary_bank = 0;
10831 		}
10832 
10833 		if (sip_orig->num_secondary_banks > sip->num_secondary_banks) {
10834 			sp->weapons.current_secondary_bank = 0;
10835 			sp->weapons.previous_secondary_bank = 0;
10836 		}
10837 
10838 		// While we're at it, let's copy over the ETS settings too
10839 		sp->weapon_recharge_index = orig_wep_rechg_idx;
10840 		sp->shield_recharge_index = orig_shd_rechg_idx;
10841 		sp->engine_recharge_index = orig_eng_rechg_idx;
10842 	}
10843 
10844 	// zookeeper - If we're switching in the loadout screen, make sure we retain initial velocity set in FRED
10845 	if (!(Game_mode & GM_IN_MISSION) && !(Fred_running)) {
10846 		Objects[sp->objnum].phys_info.speed = (float) p_objp->initial_velocity * sip->max_speed / 100.0f;
10847 		// prev_ramp_vel needs to be in local coordinates
10848 		// set z of prev_ramp_vel to initial velocity
10849 		vm_vec_zero(&Objects[sp->objnum].phys_info.prev_ramp_vel);
10850 		Objects[sp->objnum].phys_info.prev_ramp_vel.xyz.z = Objects[sp->objnum].phys_info.speed;
10851 		// convert to global coordinates and set to ship velocity and desired velocity
10852 		vm_vec_unrotate(&Objects[sp->objnum].phys_info.vel, &Objects[sp->objnum].phys_info.prev_ramp_vel, &Objects[sp->objnum].orient);
10853 		Objects[sp->objnum].phys_info.desired_vel = Objects[sp->objnum].phys_info.vel;
10854 	}
10855 
10856 	// Goober5000 - if we're changing to a ship class that has a different default set of orders, update the orders
10857 	// (this avoids wiping the orders if we're e.g. changing between fighter classes)
10858 	if (Fred_running)
10859 	{
10860 		int old_defaults = ship_get_default_orders_accepted(sip_orig);
10861 		int new_defaults = ship_get_default_orders_accepted(sip);
10862 
10863 		if (old_defaults != new_defaults)
10864 			sp->orders_accepted = new_defaults;
10865 	}
10866 
10867 	// Goober5000 - deal with texture replacement by re-applying the same code we used during parsing
10868 	if (sp->ship_replacement_textures != nullptr)
10869 	{
10870 		// clear them out because the new positions may be different
10871 		for (i = 0; i < MAX_REPLACEMENT_TEXTURES; i++)
10872 			sp->ship_replacement_textures[i] = -1;
10873 
10874 		if (p_objp != nullptr)
10875 		{
10876 			// now fill them in according to texture name
10877 			for (const auto &tr : p_objp->replacement_textures)
10878 			{
10879 				int j;
10880 				polymodel* pm = model_get(sip->model_num);
10881 
10882 				// look for textures
10883 				for (j = 0; j < pm->n_textures; j++)
10884 				{
10885 					texture_map* tmap = &pm->maps[j];
10886 
10887 					int tnum = tmap->FindTexture(tr.old_texture);
10888 					if (tnum > -1)
10889 						sp->ship_replacement_textures[j * TM_NUM_TYPES + tnum] = tr.new_texture_id;
10890 				}
10891 			}
10892 		}
10893 	}
10894 
10895 	if (sip->uses_team_colors)
10896 	{
10897 		sp->team_name = sip->default_team_name;
10898 	}
10899 }
10900 
10901 /**
10902  * Launch countermeasures from object *objp.
10903  *
10904  * @param objp object from which to launch countermeasure
10905  * @param rand_val is used in multiplayer to ensure that all clients in the game fire countermeasure the same way
10906  */
ship_launch_countermeasure(object * objp,int rand_val)10907 int ship_launch_countermeasure(object *objp, int rand_val)
10908 {
10909 	if(!Countermeasures_enabled) {
10910 		return 0;
10911 	}
10912 
10913 	int	check_count, cmeasure_count;
10914 	int cobjnum=-1;
10915 	vec3d	pos;
10916 	ship	*shipp;
10917 	ship_info *sip;
10918 
10919 	shipp = &Ships[objp->instance];
10920 	sip = &Ship_info[shipp->ship_info_index];
10921 
10922 	int arand;
10923 	if(rand_val < 0) {
10924 		arand = Random::next();
10925 	} else {
10926 		arand = rand_val;
10927 	}
10928 
10929 	// in the case where the server is an observer, he can launch countermeasures unless we do this.
10930 	if( objp->type == OBJ_OBSERVER){
10931 		return 0;
10932 	}
10933 
10934 	if ( !timestamp_elapsed(shipp->cmeasure_fire_stamp) ){
10935 		return 0;
10936 	}
10937 
10938 	shipp->cmeasure_fire_stamp = timestamp(Weapon_info[shipp->current_cmeasure].cmeasure_firewait);	//	Can launch every cmeasure wait
10939 #ifndef NDEBUG
10940 	if (Weapon_energy_cheat) {
10941 		shipp->cmeasure_count++;
10942 	}
10943 #endif
10944 
10945 	// we might check the count of countermeasures left depending on game state.  Multiplayer clients
10946 	// do not need to check any objects other than themselves for the count
10947 	check_count = 1;
10948 
10949 	if ( MULTIPLAYER_CLIENT && (objp != Player_obj) ){
10950 		check_count = 0;
10951 	}
10952 
10953 	if ( check_count && ((shipp->cmeasure_count <= 0) || (sip->cmeasure_type < 0)) ) {
10954 		if ( objp == Player_obj ) {
10955 			if(sip->cmeasure_max < 1 || sip->cmeasure_type < 0) {
10956 				//TODO: multi-lingual support
10957 				HUD_sourced_printf(HUD_SOURCE_HIDDEN, "%s", XSTR( "Not equipped with countermeasures", 1633));
10958 			} else if(shipp->current_cmeasure < 0) {
10959 				//TODO: multi-lingual support
10960 				HUD_sourced_printf(HUD_SOURCE_HIDDEN, "%s", XSTR( "No countermeasures selected", 1634));
10961 			} else if(shipp->cmeasure_count <= 0) {
10962 				HUD_sourced_printf(HUD_SOURCE_HIDDEN, "%s", XSTR( "No more countermeasure charges.", 485));
10963 			}
10964 			snd_play( gamesnd_get_game_sound(ship_get_sound(Player_obj, GameSounds::OUT_OF_MISSLES)), 0.0f );
10965 		}
10966 
10967 		// if we have a player ship, then send the fired packet anyway so that the player
10968 		// who fired will get his 'out of countermeasures' sound
10969 		cmeasure_count = 0;
10970 		if (objp->flags[Object::Object_Flags::Player_ship]){
10971 			// the new way of doing things
10972 			if (Game_mode & GM_MULTIPLAYER){
10973 				send_NEW_countermeasure_fired_packet(objp, cmeasure_count, -1);
10974 			}
10975 			return 0;
10976 		}
10977 
10978 		return 0;
10979 	}
10980 
10981 	cmeasure_count = shipp->cmeasure_count;
10982 	shipp->cmeasure_count--;
10983 
10984 	vm_vec_scale_add(&pos, &objp->pos, &objp->orient.vec.fvec, -objp->radius/2.0f);
10985 
10986 	cobjnum = weapon_create(&pos, &objp->orient, shipp->current_cmeasure, OBJ_INDEX(objp));
10987 	if (cobjnum >= 0)
10988 	{
10989 		cmeasure_set_ship_launch_vel(&Objects[cobjnum], objp, arand);
10990 		nprintf(("Network", "Cmeasure created by %s\n", shipp->ship_name));
10991 
10992 		// Play sound effect for counter measure launch
10993 		Assert(shipp->current_cmeasure < weapon_info_size());
10994 		if ( Weapon_info[shipp->current_cmeasure].launch_snd.isValid() ) {
10995 			snd_play_3d( gamesnd_get_game_sound(Weapon_info[shipp->current_cmeasure].launch_snd), &pos, &View_position );
10996 		}
10997 
10998 		// the new way of doing things
10999 		if(Game_mode & GM_MULTIPLAYER){
11000 			send_NEW_countermeasure_fired_packet(objp, cmeasure_count, Objects[cobjnum].net_signature);
11001 		}
11002 
11003 		// add scripting hook for 'On Countermeasure Fire' --wookieejedi
11004 		OnCountermeasureFireHook->run(scripting::hook_param_list(scripting::hook_param("Ship", 'o', objp),
11005 			scripting::hook_param("CountermeasuresLeft", 'i', shipp->cmeasure_count),
11006 			scripting::hook_param("Countermeasure", 'o', &Objects[cobjnum])));
11007 	}
11008 
11009 	return (cobjnum >= 0);		// return 0 if not fired, 1 otherwise
11010 }
11011 
11012 /**
11013  * See if enough time has elapsed to play fail sound again
11014  */
ship_maybe_play_primary_fail_sound()11015 void ship_maybe_play_primary_fail_sound()
11016 {
11017 	ship_weapon *swp = &Player_ship->weapons;
11018 	int stampval;
11019 
11020 	hud_start_flash_weapon(swp->current_primary_bank);
11021 
11022 	if ( timestamp_elapsed(Laser_energy_out_snd_timer) )
11023 	{
11024 		// check timestamp according to ballistics
11025 		if (Weapon_info[swp->primary_bank_weapons[swp->current_primary_bank]].wi_flags[Weapon::Info_Flags::Ballistic])
11026 		{
11027 			stampval = 500;
11028 		}
11029 		else
11030 		{
11031 			stampval = 50;
11032 		}
11033 		Laser_energy_out_snd_timer = timestamp(stampval);
11034 		snd_play( gamesnd_get_game_sound(ship_get_sound(Player_obj, GameSounds::OUT_OF_WEAPON_ENERGY)));
11035 	}
11036 }
11037 
11038 /**
11039  * See if enough time has elapsed to play secondary fail sound again
11040  */
ship_maybe_play_secondary_fail_sound(weapon_info * wip)11041 static int ship_maybe_play_secondary_fail_sound(weapon_info *wip)
11042 {
11043 	hud_start_flash_weapon(Player_ship->weapons.num_primary_banks + Player_ship->weapons.current_secondary_bank);
11044 
11045 	if ( timestamp_elapsed(Missile_out_snd_timer) ) {
11046 
11047 		if ( wip->wi_flags[Weapon::Info_Flags::Swarm] ) {
11048 			Missile_out_snd_timer = timestamp(500);
11049 		} else {
11050 			Missile_out_snd_timer = timestamp(50);
11051 		}
11052 		snd_play( gamesnd_get_game_sound(ship_get_sound(Player_obj, GameSounds::OUT_OF_MISSLES)) );
11053 		return 1;
11054 	}
11055 	return 0;
11056 }
11057 
11058 /**
11059  * See if weapon for ship can fire based on weapons subystem strength.
11060  *
11061  * @return 1 if weapon failed to fire, 0 if weapon can fire
11062  */
ship_weapon_maybe_fail(ship * sp)11063 static int ship_weapon_maybe_fail(ship *sp)
11064 {
11065 	int	rval;
11066 	float	weapons_subsys_str;
11067 
11068 	// If playing on lowest skill level, weapons will not fail due to subsystem damage
11069 	if ( Game_skill_level == 0 ){
11070 		return 0;
11071 	}
11072 
11073 	rval = 0;
11074 	weapons_subsys_str = ship_get_subsystem_strength( sp, SUBSYSTEM_WEAPONS );
11075 	if ( weapons_subsys_str < SUBSYS_WEAPONS_STR_FIRE_FAIL ) {
11076 		rval = 1;
11077 	}
11078 	else if ( weapons_subsys_str < SUBSYS_WEAPONS_STR_FIRE_OK ) {
11079 		// chance to fire depends on weapons subsystem strength
11080 		if ( (frand()-0.2f) > weapons_subsys_str )
11081 			rval = 1;
11082 	}
11083 
11084 	if (!rval) {
11085 		// is subsystem disrupted?
11086 		if ( ship_subsys_disrupted(sp, SUBSYSTEM_WEAPONS) ) {
11087 			rval=1;
11088 		}
11089 	}
11090 
11091 	return rval;
11092 }
11093 
11094 // create a moving tracer based upon a weapon which just fired
11095 float t_rad = 0.5f;
11096 float t_len = 10.0f;
11097 float t_vel = 0.2f;
11098 float t_min = 150.0f;
11099 float t_max = 300.0f;
11100 DCF(t_rad, "Sets weapon tracer radius")
11101 {
11102 	if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) {
11103 		dc_printf("t_rad : %f\n", t_rad);
11104 		return;
11105 	}
11106 
11107 	dc_stuff_float(&t_rad);
11108 }
11109 DCF(t_len, "Sets weapon tracer length")
11110 {
11111 	if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) {
11112 		dc_printf("t_len : %f\n", t_len);
11113 		return;
11114 	}
11115 
11116 	dc_stuff_float(&t_len);
11117 }
11118 DCF(t_vel, "Sets weapon tracer velocity")
11119 {
11120 	if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) {
11121 		dc_printf("t_vel : %f\n", t_vel);
11122 		return;
11123 	}
11124 
11125 	dc_stuff_float(&t_vel);
11126 }
11127 /*
11128  TODO: These two DCF's (and variables) are unused
11129 DCF(t_min, "")
11130 {
11131 	dc_stuff_float(&t_min);
11132 }
11133 DCF(t_max, "")
11134 {
11135 	dc_stuff_float(&t_max);
11136 }
11137 */
11138 
11139 /**
11140  * Stops a single primary bank
11141  */
ship_stop_fire_primary_bank(object * obj,int bank_to_stop)11142 static int ship_stop_fire_primary_bank(object * obj, int bank_to_stop)
11143 {
11144 	ship			*shipp;
11145 	ship_weapon	*swp;
11146 
11147 	if(obj == NULL){
11148 		return 0;
11149 	}
11150 
11151 	if(obj->type != OBJ_SHIP){
11152 		return 0;
11153 	}
11154 
11155 	shipp = &Ships[obj->instance];
11156 	swp = &shipp->weapons;
11157 
11158 	if(shipp->primary_rotate_rate[bank_to_stop] > 0.0f)
11159 		shipp->primary_rotate_rate[bank_to_stop] -= Weapon_info[swp->primary_bank_weapons[bank_to_stop]].weapon_submodel_rotate_accell*flFrametime;
11160 	if(shipp->primary_rotate_rate[bank_to_stop] < 0.0f)
11161 		shipp->primary_rotate_rate[bank_to_stop] = 0.0f;
11162 	if(Ship_info[shipp->ship_info_index].draw_primary_models[bank_to_stop]){
11163 		shipp->primary_rotate_ang[bank_to_stop] += shipp->primary_rotate_rate[bank_to_stop]*flFrametime;
11164 		if(shipp->primary_rotate_ang[bank_to_stop] > PI2)
11165 			shipp->primary_rotate_ang[bank_to_stop] -= PI2;
11166 		if(shipp->primary_rotate_ang[bank_to_stop] < 0.0f)
11167 			shipp->primary_rotate_ang[bank_to_stop] += PI2;
11168 	}
11169 
11170 	if(shipp->was_firing_last_frame[bank_to_stop] == 0)
11171 		return 0;
11172 
11173 	shipp->was_firing_last_frame[bank_to_stop] = 0;
11174 
11175 	return 1;
11176 }
11177 
11178 
11179 /**
11180  * Stuff to do when the ship has stoped fireing all primary weapons
11181  */
ship_stop_fire_primary(object * obj)11182 int ship_stop_fire_primary(object * obj)
11183 {
11184 	int i, num_primary_banks = 0, bank_to_stop = 0;
11185 	ship			*shipp;
11186 	ship_weapon	*swp;
11187 
11188 	if(obj == NULL){
11189 		return 0;
11190 	}
11191 
11192 	if(obj->type != OBJ_SHIP){
11193 		return 0;
11194 	}
11195 
11196 	shipp = &Ships[obj->instance];
11197 	swp = &shipp->weapons;
11198 
11199 	bank_to_stop = swp->current_primary_bank;
11200 
11201 	if ( shipp->flags[Ship_Flags::Primary_linked] ) {
11202 		num_primary_banks = swp->num_primary_banks;
11203 	} else {
11204 		num_primary_banks = MIN(1, swp->num_primary_banks);
11205 	}
11206 
11207 	for ( i = 0; i < num_primary_banks; i++ ) {
11208 		// Goober5000 - allow more than two banks
11209 		bank_to_stop = (swp->current_primary_bank+i) % swp->num_primary_banks;
11210 		//only stop if it was fireing last frame
11211 		ship_stop_fire_primary_bank(obj, bank_to_stop);
11212 	}
11213 	for(i = 0; i<swp->num_primary_banks+num_primary_banks;i++)
11214 		ship_stop_fire_primary_bank(obj, i%swp->num_primary_banks);
11215 
11216 	return 1;
11217 }
11218 
11219 
11220 
11221 int tracers[MAX_SHIPS][4][4];
11222 
11223 /**
11224  * Checks whether a ship would use autoaim if it were to fire its primaries at
11225  * the given object, taking lead into account.
11226  *
11227  * @param shipp the ship to check
11228  * @param bank_to_fire the assumed primary bank
11229  * @param obj the object to check; must be the ship's current target
11230  */
in_autoaim_fov(ship * shipp,int bank_to_fire,object * obj)11231 bool in_autoaim_fov(ship *shipp, int bank_to_fire, object *obj)
11232 {
11233 	// Most of the code of this function has been duplicated from
11234 	// ship_fire_primary(), because it is not easy to encapsulate cleanly.
11235 
11236 	ship_info *sip;
11237 	ai_info *aip;
11238 	ship_weapon *swp;
11239 
11240 	bool has_autoaim, has_converging_autoaim;
11241 	float autoaim_fov = 0;
11242 	float dist_to_target = 0;
11243 
11244 	vec3d plr_to_target_vec;
11245 	vec3d player_forward_vec = Objects[shipp->objnum].orient.vec.fvec;
11246 
11247 	vec3d target_position;
11248 
11249 	sip = &Ship_info[shipp->ship_info_index];
11250 	aip = &Ai_info[shipp->ai_index];
11251 
11252 	swp = &shipp->weapons;
11253 	int weapon_idx = swp->primary_bank_weapons[bank_to_fire];
11254 	weapon_info* winfo_p = &Weapon_info[weapon_idx];
11255 
11256 	has_converging_autoaim = ((sip->aiming_flags[Ship::Aiming_Flags::Autoaim_convergence] || (The_mission.ai_profile->player_autoaim_fov[Game_skill_level] > 0.0f && !( Game_mode & GM_MULTIPLAYER ))) && aip->target_objnum != -1);
11257 	has_autoaim = ((has_converging_autoaim || (sip->aiming_flags[Ship::Aiming_Flags::Autoaim])) && aip->target_objnum != -1);
11258 
11259 	if (!has_autoaim) {
11260 		return false;
11261 	}
11262 
11263 	autoaim_fov = MAX(shipp->autoaim_fov, The_mission.ai_profile->player_autoaim_fov[Game_skill_level]);
11264 
11265 	if (aip->targeted_subsys != nullptr) {
11266 		get_subsystem_world_pos(&Objects[aip->target_objnum], aip->targeted_subsys, &target_position);
11267 	}
11268 	else {
11269 		target_position = obj->pos;
11270 	}
11271 
11272 	dist_to_target = vm_vec_dist_quick(&Objects[shipp->objnum].pos, &target_position);
11273 
11274 	hud_calculate_lead_pos(&plr_to_target_vec, &target_position, obj, winfo_p, dist_to_target);
11275 	vm_vec_sub2(&plr_to_target_vec, &Objects[shipp->objnum].pos);
11276 
11277 	float angle_to_target = vm_vec_delta_ang(&player_forward_vec, &plr_to_target_vec, nullptr);
11278 
11279 	if (angle_to_target <= autoaim_fov) {
11280 		return true;
11281 	}
11282 	else {
11283 		return false;
11284 	}
11285 }
11286 
11287 
11288 // fires a primary weapon for the given object.  It also handles multiplayer cases.
11289 // in multiplayer, the starting network signature, and number of banks fired are sent
11290 // to all the clients in the game. All the info is passed to send_primary at the end of
11291 // the function.  The check_energy parameter (defaults to 1) tells us whether or not
11292 // we should check the energy.  It will be 0 when a multiplayer client is firing an AI
11293 // primary.
ship_fire_primary(object * obj,int stream_weapons,int force,bool rollback_shot)11294 int ship_fire_primary(object * obj, int stream_weapons, int force, bool rollback_shot)
11295 {
11296 	vec3d		gun_point, pnt, firing_pos, target_position, target_velocity_vec;
11297 	int			n = obj->instance;
11298 	ship			*shipp;
11299 	ship_weapon	*swp;
11300 	ship_info	*sip;
11301 	ai_info		*aip;
11302 	int			weapon_idx, i, j, w, v, weapon_objnum;
11303 	int			bank_to_fire, num_fired = 0;
11304 	int			banks_fired;				// used for multiplayer to help determine whether or not to send packet
11305 	banks_fired = 0;			// used in multiplayer -- bitfield of banks that were fired
11306 	bool has_fired = false;		// used to determine whether we should fire the scripting hook
11307 	bool has_autoaim, has_converging_autoaim, needs_target_pos;	// used to flag weapon/ship as having autoaim
11308 	float autoaim_fov = 0;			// autoaim limit
11309 	float dist_to_target = 0;		// distance to target, for autoaim & automatic convergence
11310 
11311 	gamesnd_id		sound_played;	// used to track what sound is played.  If the player is firing two banks
11312 										// of the same laser, we only want to play one sound
11313 	Assert( obj != NULL );
11314 
11315 	if(obj == NULL){
11316 		return 0;
11317 	}
11318 
11319 	// in the case where the server is an observer, he can fire (which) would be bad - unless we do this.
11320 	if( obj->type == OBJ_OBSERVER){
11321 		return 0;
11322 	}
11323 
11324 	Assert( obj->type == OBJ_SHIP );
11325 	Assert( n >= 0 );
11326 	Assert( Ships[n].objnum == OBJ_INDEX(obj));
11327 	if((obj->type != OBJ_SHIP) || (n < 0) || (n >= MAX_SHIPS) || (Ships[n].objnum != OBJ_INDEX(obj))){
11328 		return 0;
11329 	}
11330 
11331 	shipp = &Ships[n];
11332 	swp = &shipp->weapons;
11333 
11334 	// bogus
11335 	if((shipp->ship_info_index < 0) || (shipp->ship_info_index >= ship_info_size())){
11336 		return 0;
11337 	}
11338 	if((shipp->ai_index < 0) || (shipp->ai_index >= MAX_AI_INFO)){
11339 		return 0;
11340 	}
11341 	sip = &Ship_info[shipp->ship_info_index];
11342 	aip = &Ai_info[shipp->ai_index];
11343 
11344 	if ( swp->num_primary_banks <= 0 ) {
11345 		return 0;
11346 	}
11347 
11348 	if ( swp->current_primary_bank < 0 ){
11349 		return 0;
11350 	}
11351 
11352 	// If the primaries have been locked, bail
11353 	if (shipp->flags[Ship_Flags::Primaries_locked])
11354 	{
11355 		return 0;
11356 	}
11357 
11358 	sound_played = gamesnd_id();
11359 
11360 	// Fire the correct primary bank.  If primaries are linked (SF_PRIMARY_LINKED set), then fire
11361 	// both primary banks.
11362 	int	num_primary_banks;
11363 
11364 	if ( shipp->flags[Ship_Flags::Primary_linked] ) {
11365 		num_primary_banks = swp->num_primary_banks;
11366 	} else {
11367 		num_primary_banks = MIN(1, swp->num_primary_banks);
11368 	}
11369 
11370 	Assert(num_primary_banks > 0);
11371 	if (num_primary_banks < 1){
11372 		return 0;
11373 	}
11374 
11375 	// if we're firing stream weapons, but the trigger is not down, do nothing
11376 	if(stream_weapons && !(shipp->flags[Ship_Flags::Trigger_down])){
11377 		return 0;
11378 	}
11379 
11380 	if(num_primary_banks == 1)
11381 		for(i = 0; i<swp->num_primary_banks; i++){
11382 			if(i!=swp->current_primary_bank)ship_stop_fire_primary_bank(obj, i);
11383 		}
11384 
11385 	// lets start gun convergence / autoaim code from here - Wanderer
11386 	has_converging_autoaim = ((sip->aiming_flags[Ship::Aiming_Flags::Autoaim_convergence] || (The_mission.ai_profile->player_autoaim_fov[Game_skill_level] > 0.0f && !( Game_mode & GM_MULTIPLAYER ))) && aip->target_objnum != -1);
11387 	has_autoaim = ((has_converging_autoaim || (sip->aiming_flags[Ship::Aiming_Flags::Autoaim])) && aip->target_objnum != -1);
11388 	needs_target_pos = ((has_autoaim || (sip->aiming_flags[Ship::Aiming_Flags::Auto_convergence])) && aip->target_objnum != -1);
11389 
11390 	if (needs_target_pos) {
11391 		if (has_autoaim) {
11392 			autoaim_fov = MAX(shipp->autoaim_fov, The_mission.ai_profile->player_autoaim_fov[Game_skill_level]);
11393 		}
11394 
11395 		// If a subsystem is targeted, fire in that direction instead
11396 		if (aip->targeted_subsys != NULL)
11397 		{
11398 			get_subsystem_world_pos(&Objects[aip->target_objnum], aip->targeted_subsys, &target_position);
11399 		}
11400 		else
11401 		{
11402 			target_position = Objects[aip->target_objnum].pos;
11403 		}
11404 
11405 		dist_to_target = vm_vec_dist_quick(&target_position, &obj->pos);
11406 	}
11407 
11408 	polymodel* pm = model_get(sip->model_num);
11409 
11410 	for ( i = 0; i < num_primary_banks; i++ ) {
11411 		// Goober5000 - allow more than two banks
11412 		bank_to_fire = (swp->current_primary_bank+i) % swp->num_primary_banks;
11413 
11414 
11415 		weapon_idx = swp->primary_bank_weapons[bank_to_fire];
11416 
11417 		// why would a ship try to fire a weapon that doesn't exist?
11418 		Assert( weapon_idx >= 0 && weapon_idx < weapon_info_size());
11419 		if ( (weapon_idx < 0) || (weapon_idx >= weapon_info_size()) ) {
11420 			continue;
11421 		}
11422 
11423 		if (swp->primary_animation_position[bank_to_fire] == MA_POS_SET) {
11424 			if ( timestamp_elapsed(swp->primary_animation_done_time[bank_to_fire]) )
11425 				swp->primary_animation_position[bank_to_fire] = MA_POS_READY;
11426 			else
11427 				continue;
11428 		}
11429 
11430 		weapon_info* winfo_p = &Weapon_info[weapon_idx];
11431 
11432 		if (needs_target_pos) {
11433 			target_velocity_vec = Objects[aip->target_objnum].phys_info.vel;
11434 			if (The_mission.ai_profile->flags[AI::Profile_Flags::Use_additive_weapon_velocity])
11435 				vm_vec_scale_sub2(&target_velocity_vec, &obj->phys_info.vel, winfo_p->vel_inherit_amount);
11436 		}
11437 
11438 		if (winfo_p->weapon_submodel_rotate_vel > 0.0f) {
11439 			if (shipp->primary_rotate_rate[bank_to_fire] < winfo_p->weapon_submodel_rotate_vel)
11440 				shipp->primary_rotate_rate[bank_to_fire] += winfo_p->weapon_submodel_rotate_accell*flFrametime;
11441 			if (shipp->primary_rotate_rate[bank_to_fire] > winfo_p->weapon_submodel_rotate_vel)
11442 				shipp->primary_rotate_rate[bank_to_fire] = winfo_p->weapon_submodel_rotate_vel;
11443 			if (sip->draw_primary_models[bank_to_fire]) {
11444 				shipp->primary_rotate_ang[bank_to_fire] += shipp->primary_rotate_rate[bank_to_fire]*flFrametime;
11445 				if (shipp->primary_rotate_ang[bank_to_fire] > PI2)
11446 					shipp->primary_rotate_ang[bank_to_fire] -= PI2;
11447 				if (shipp->primary_rotate_ang[bank_to_fire] < 0.0f)
11448 					shipp->primary_rotate_ang[bank_to_fire] += PI2;
11449 			}
11450 			if (shipp->primary_rotate_rate[bank_to_fire] < winfo_p->weapon_submodel_rotate_vel)
11451 				continue;
11452 		}
11453 		// if this is a targeting laser, start it up   ///- only targeting laser if it is tag-c, otherwise it's a fighter beam -Bobboau
11454 		if((winfo_p->wi_flags[Weapon::Info_Flags::Beam]) && (winfo_p->tag_level == 3) && (shipp->flags[Ship_Flags::Trigger_down]) && (winfo_p->b_info.beam_type == BEAM_TYPE_C) ){
11455 			ship_start_targeting_laser(shipp);
11456 			continue;
11457 		}
11458 
11459 		// Cyborg17 - In rollback mode we don't need to worry about stream weapons or timestamps, because we are recreating an exact shot, anywway.
11460 		if (!rollback_shot) {
11461 			// if we're firing stream weapons and this is a non stream weapon, skip it
11462 			if (stream_weapons && !(winfo_p->wi_flags[Weapon::Info_Flags::Stream])) {
11463 				continue;
11464 			}
11465 			// if we're firing non stream weapons and this is a stream weapon, skip it
11466 			if (!stream_weapons && (winfo_p->wi_flags[Weapon::Info_Flags::Stream])) {
11467 				continue;
11468 			}
11469 			// only non-multiplayer clients (single, multi-host) need to do timestamp checking
11470 			if ( !timestamp_elapsed(swp->next_primary_fire_stamp[bank_to_fire]) ) {
11471 				continue;
11472 			}
11473 		}
11474 
11475 		// if weapons are linked and this is a nolink weapon, skip it
11476 		if (shipp->flags[Ship_Flags::Primary_linked] && winfo_p->wi_flags[Weapon::Info_Flags::Nolink]) {
11477 			continue;
11478 		}
11479 
11480 		int num_slots = pm->gun_banks[bank_to_fire].num_slots;
11481 
11482 		// do timestamp stuff for next firing time
11483 		float next_fire_delay;
11484 		bool fast_firing = false;
11485 		// reset the burst if applicable
11486 		if (winfo_p->burst_flags[Weapon::Burst_Flags::Resets]) {
11487 			// a bit of an oversimplification but the reset time doesnt have to be super accurate
11488 			int reset_time = (int)(swp->last_primary_fire_stamp[bank_to_fire] + (winfo_p->fire_wait * 1000.f));
11489 			if (timestamp_elapsed(reset_time)) {
11490 				swp->burst_counter[bank_to_fire] = 0;
11491 			}
11492 		}
11493 
11494 		int old_burst_counter = swp->burst_counter[bank_to_fire];
11495 		int old_burst_seed = swp->burst_seed[bank_to_fire];
11496 
11497 		// should subtract 1 from num_slots to match behavior of winfo_p->burst_shots
11498 		int burst_shots = (winfo_p->burst_flags[Weapon::Burst_Flags::Num_firepoints_burst_shots] ? num_slots - 1 : winfo_p->burst_shots);
11499 		if (burst_shots > swp->burst_counter[bank_to_fire]) {
11500 			next_fire_delay = winfo_p->burst_delay * 1000.0f;
11501 			swp->burst_counter[bank_to_fire]++;
11502 			if (winfo_p->burst_flags[Weapon::Burst_Flags::Fast_firing])
11503 				fast_firing = true;
11504 		} else {
11505 			if (winfo_p->max_delay != 0.0f && winfo_p->min_delay != 0.0f) // Random fire delay (DahBlount)
11506 				next_fire_delay = frand_range(winfo_p->min_delay, winfo_p->max_delay) * 1000.0f;
11507 			else
11508 				next_fire_delay = winfo_p->fire_wait * 1000.0f;
11509 
11510 			swp->burst_counter[bank_to_fire] = 0;
11511 			swp->burst_seed[bank_to_fire] = Random::next();
11512 			// only used by type 5 beams
11513 			if (swp->burst_counter[bank_to_fire] == 0) {
11514 				swp->per_burst_rot += winfo_p->b_info.t5info.per_burst_rot;
11515 				if (swp->per_burst_rot > PI2)
11516 					swp->per_burst_rot -= PI2;
11517 				else if (swp->per_burst_rot < -PI2)
11518 					swp->per_burst_rot += PI2;
11519 			}
11520 		}
11521 		//doing that time scale thing on enemy fighter is just ugly with beams, especaly ones that have careful timeing
11522 		if (!((obj->flags[Object::Object_Flags::Player_ship]) || fast_firing || winfo_p->wi_flags[Weapon::Info_Flags::Beam])) {
11523 			// When testing weapons fire in the lab, we do not have a player object available.
11524 			if ((Game_mode & GM_LAB) || shipp->team == Ships[Player_obj->instance].team){
11525 				next_fire_delay *= aip->ai_ship_fire_delay_scale_friendly;
11526 			} else {
11527 				next_fire_delay *= aip->ai_ship_fire_delay_scale_hostile;
11528 			}
11529 		}
11530 
11531 		// Goober5000 (thanks to _argv[-1] for the original idea)
11532 		if ( (num_primary_banks > 1) &&  !(winfo_p->wi_flags[Weapon::Info_Flags::No_linked_penalty]) && !(The_mission.ai_profile->flags[AI::Profile_Flags::Disable_linked_fire_penalty]) )
11533 		{
11534 			int effective_primary_banks = 0;
11535 			for (int it = 0; it < num_primary_banks; it++)
11536 			{
11537 				if ((it == bank_to_fire) || !(Weapon_info[swp->primary_bank_weapons[it]].wi_flags[Weapon::Info_Flags::Nolink, Weapon::Info_Flags::No_linked_penalty]))
11538 					effective_primary_banks++;
11539 			}
11540 			Assert(effective_primary_banks >= 1);
11541 
11542 			next_fire_delay *= 1.0f + (effective_primary_banks - 1) * 0.5f;		//	50% time penalty if banks linked
11543 		}
11544 
11545 		if (winfo_p->fof_spread_rate > 0.0f)
11546 		{
11547 			//Adjust the primary_bank_fof_cooldown based on how long it's been since the last shot.
11548 			float reset_amount = (timestamp_until(swp->last_primary_fire_stamp[bank_to_fire]) / 1000.0f) * winfo_p->fof_reset_rate;
11549 			swp->primary_bank_fof_cooldown[bank_to_fire] += winfo_p->fof_spread_rate + reset_amount;
11550 			CLAMP(swp->primary_bank_fof_cooldown[bank_to_fire], 0.0f, 1.0f);
11551 		}
11552 
11553 		//	MK, 2/4/98: Since you probably were allowed to fire earlier, but couldn't fire until your frame interval
11554 		//	rolled around, subtract out up to half the previous frametime.
11555 		//	Note, unless we track whether the fire button has been held down, and not tapped, it's hard to
11556 		//	know how much time to subtract off.  It could be this fire is "late" because the user didn't want to fire.
11557 		if ((next_fire_delay > 0.0f)) {
11558 			if (obj->flags[Object::Object_Flags::Player_ship]) {
11559 				int	t = timestamp_until(swp->next_primary_fire_stamp[bank_to_fire]);
11560 				if (t < 0) {
11561 					float	tx;
11562 
11563 					tx = (float) t/-1000.0f;
11564 					if (tx > flFrametime/2.0f){
11565 						tx = 1000.0f * flFrametime * 0.7f;
11566 					}
11567 					next_fire_delay -= tx;
11568 				}
11569 
11570 				if ((int) next_fire_delay < 1){
11571 					next_fire_delay = 1.0f;
11572 				}
11573 			}
11574 
11575 			swp->next_primary_fire_stamp[bank_to_fire] = timestamp((int)(next_fire_delay));
11576 			swp->last_primary_fire_stamp[bank_to_fire] = timestamp();
11577 		}
11578 
11579 		if (sip->flags[Ship::Info_Flags::Dyn_primary_linking] ) {
11580 			Assert(pm->gun_banks[bank_to_fire].num_slots != 0);
11581 			swp->next_primary_fire_stamp[bank_to_fire] = timestamp((int)(next_fire_delay * ( swp->primary_bank_slot_count[ bank_to_fire ] ) / pm->gun_banks[bank_to_fire].num_slots ) );
11582 			swp->last_primary_fire_stamp[bank_to_fire] = timestamp();
11583 		} else if (winfo_p->wi_flags[Weapon::Info_Flags::Cycle]) {
11584 			Assert(pm->gun_banks[bank_to_fire].num_slots != 0);
11585 			swp->next_primary_fire_stamp[bank_to_fire] = timestamp((int)(next_fire_delay / pm->gun_banks[bank_to_fire].num_slots));
11586 			swp->last_primary_fire_stamp[bank_to_fire] = timestamp();
11587 			//to maintain balance of fighters with more fire points they will fire faster than ships with fewer points
11588 		}else{
11589 			swp->next_primary_fire_stamp[bank_to_fire] = timestamp((int)(next_fire_delay));
11590 			swp->last_primary_fire_stamp[bank_to_fire] = timestamp();
11591 		}
11592 
11593 		// Here is where we check if weapons subsystem is capable of firing the weapon.
11594 		// Note that we can have partial bank firing, if the weapons subsystem is partially
11595 		// functional, which should be cool.
11596 		if ( ship_weapon_maybe_fail(shipp) && !force) {
11597 			if ( obj == Player_obj ) {
11598 				ship_maybe_play_primary_fail_sound();
11599 			}
11600 			ship_stop_fire_primary_bank(obj, bank_to_fire);
11601 			continue;
11602 		}
11603 
11604 		if ( pm->n_guns > 0 ) {
11605 			vec3d predicted_target_pos, plr_to_target_vec;
11606 			vec3d player_forward_vec = obj->orient.vec.fvec;
11607 			bool in_automatic_aim_fov = false;
11608 			float dist_to_aim = 0;
11609 
11610 			// more autoaim stuff here - Wanderer
11611 			// needs weapon speed
11612 			if (needs_target_pos) {
11613 				float time_to_target, angle_to_target;
11614 				vec3d last_delta_vec;
11615 
11616 				time_to_target = 0.0f;
11617 
11618 				if (winfo_p->max_speed != 0)
11619 				{
11620 					time_to_target = dist_to_target / winfo_p->max_speed;
11621 				}
11622 
11623 				// This is to circumvent a nine-year-old bug that prevents autoaim from working in multi. In most cases polish_predicted_target_pos needs
11624 				// the Player object, but in multi it needs the shooting object.  Also, additional conditions would need to be added in the future if you
11625 				// want to apply autoaim to AI, for whatever reason.
11626 				object* object_to_send = (obj->flags[Object::Object_Flags::Player_ship]) ? obj : Player_obj;
11627 
11628 				vm_vec_scale_add(&predicted_target_pos, &target_position, &target_velocity_vec, time_to_target);
11629 				polish_predicted_target_pos(winfo_p, &Objects[aip->target_objnum], &target_position, &predicted_target_pos, dist_to_target, &last_delta_vec, 1, object_to_send);
11630 				vm_vec_sub(&plr_to_target_vec, &predicted_target_pos, &obj->pos);
11631 
11632 				if (has_autoaim) {
11633 					angle_to_target = vm_vec_delta_ang(&player_forward_vec, &plr_to_target_vec, NULL);
11634 					if (angle_to_target < autoaim_fov)
11635 						in_automatic_aim_fov = true;
11636 				}
11637 
11638 				dist_to_aim = vm_vec_mag_quick(&plr_to_target_vec);
11639 
11640 				// minimum convergence distance
11641 				if (sip->minimum_convergence_distance > dist_to_aim) {
11642 					float dist_mult;
11643 					dist_mult = sip->minimum_convergence_distance / dist_to_aim;
11644 					vm_vec_scale_add(&predicted_target_pos, &obj->pos, &plr_to_target_vec, dist_mult);
11645 					dist_to_aim = sip->minimum_convergence_distance;
11646 				}
11647 			}
11648 
11649 			if(winfo_p->wi_flags[Weapon::Info_Flags::Beam]){		// the big change I made for fighter beams, if there beams fill out the Fire_Info for a targeting laser then fire it, for each point in the weapon bank -Bobboau
11650 				beam_fire_info fbfire_info;
11651 
11652 				int points;
11653 				if (winfo_p->b_info.beam_shots){
11654 					if (winfo_p->b_info.beam_shots > num_slots){
11655 						points = num_slots;
11656 					}else{
11657 						points = winfo_p->b_info.beam_shots;
11658 					}
11659 				} else if (winfo_p->wi_flags[Weapon::Info_Flags::Cycle]) {
11660 					points = 1;
11661 				} else {
11662 					points = num_slots;
11663 				}
11664 
11665 				if ( shipp->weapon_energy < points*winfo_p->energy_consumed*flFrametime ||
11666 					(winfo_p->wi_flags[Weapon::Info_Flags::Ballistic] && shipp->weapons.primary_bank_ammo[bank_to_fire] <= 0))
11667 				{
11668 					swp->next_primary_fire_stamp[bank_to_fire] = timestamp((int)(next_fire_delay));
11669 					if ( obj == Player_obj )
11670 					{
11671 						ship_maybe_play_primary_fail_sound();
11672 					}
11673 					ship_stop_fire_primary_bank(obj, bank_to_fire);
11674 					continue;
11675 				}
11676 
11677 				shipp->beam_sys_info.turret_norm.xyz.x = 0.0f;
11678 				shipp->beam_sys_info.turret_norm.xyz.y = 0.0f;
11679 				shipp->beam_sys_info.turret_norm.xyz.z = 1.0f;
11680 				shipp->beam_sys_info.model_num = sip->model_num;
11681 				shipp->beam_sys_info.turret_gun_sobj = pm->detail[0];
11682 				shipp->beam_sys_info.turret_num_firing_points = 1;  // dummy turret info is used per firepoint
11683 				shipp->beam_sys_info.turret_fov = cosf((winfo_p->field_of_fire != 0.0f)?winfo_p->field_of_fire:180);
11684 
11685 				shipp->fighter_beam_turret_data.disruption_timestamp = timestamp(0);
11686 				shipp->fighter_beam_turret_data.turret_next_fire_pos = 0;
11687 				shipp->fighter_beam_turret_data.current_hits = 1.0;
11688 				shipp->fighter_beam_turret_data.system_info = &shipp->beam_sys_info;
11689 
11690 				fbfire_info.target_subsys = Ai_info[shipp->ai_index].targeted_subsys;
11691 				fbfire_info.beam_info_index = shipp->weapons.primary_bank_weapons[bank_to_fire];
11692 				fbfire_info.beam_info_override = NULL;
11693 				fbfire_info.shooter = &Objects[shipp->objnum];
11694 
11695 				if (aip->target_objnum >= 0) {
11696 					fbfire_info.target = &Objects[aip->target_objnum];
11697 				} else {
11698 					fbfire_info.target = NULL;
11699 				}
11700 				fbfire_info.turret = &shipp->fighter_beam_turret_data;
11701 				fbfire_info.bfi_flags = BFIF_IS_FIGHTER_BEAM;
11702 				fbfire_info.bank = bank_to_fire;
11703 				fbfire_info.burst_index = old_burst_counter;
11704 				fbfire_info.burst_seed = old_burst_seed;
11705 				fbfire_info.per_burst_rotation = swp->per_burst_rot;
11706 
11707 				for ( v = 0; v < points; v++ ){
11708 					if(winfo_p->b_info.beam_shots || winfo_p->wi_flags[Weapon::Info_Flags::Cycle]){
11709 						j = (shipp->last_fired_point[bank_to_fire]+1)%num_slots;
11710 						shipp->last_fired_point[bank_to_fire] = j;
11711 					}else{
11712 						j=v;
11713 					}
11714 
11715 					fbfire_info.local_fire_postion = pm->gun_banks[bank_to_fire].pnt[j];
11716 					shipp->beam_sys_info.pnt = pm->gun_banks[bank_to_fire].pnt[j];
11717 					shipp->beam_sys_info.turret_firing_point[0] = pm->gun_banks[bank_to_fire].pnt[j];
11718 					fbfire_info.point = j;
11719 					fbfire_info.fire_method = BFM_FIGHTER_FIRED;
11720 
11721 					beam_fire(&fbfire_info);
11722 					has_fired = true;
11723 					num_fired++;
11724 				}
11725 			}
11726 			else	//if this isn't a fighter beam, do it normally -Bobboau
11727 			{
11728 				int points = 0, numtimes = 1;
11729 
11730 				// ok if this is a cycling weapon use shots as the number of points to fire from at a time
11731 				// otherwise shots is the number of times all points will be fired (used mostly for the 'shotgun' effect)
11732 				if ( sip->flags[Ship::Info_Flags::Dyn_primary_linking] ) {
11733 					numtimes = 1;
11734 					points = MIN( num_slots, swp->primary_bank_slot_count[ bank_to_fire ] );
11735 				} else if ( winfo_p->wi_flags[Weapon::Info_Flags::Cycle] ) {
11736 					numtimes = 1;
11737 					points = MIN(num_slots, winfo_p->shots);
11738 				} else {
11739 					numtimes = winfo_p->shots;
11740 					points = num_slots;
11741 				}
11742 
11743 				// The energy-consumption code executes even for ballistic primaries, because
11744 				// there may be a reason why you want to have ballistics consume energy.  Perhaps
11745 				// you can't fire too many too quickly or they'll overheat.  If not, just set
11746 				// the weapon's energy_consumed to 0 and it'll work just fine. - Goober5000
11747 
11748 				// fail unless we're forcing (energy based primaries)
11749 				if ( (shipp->weapon_energy < points*numtimes * winfo_p->energy_consumed)			//was num_slots
11750 				 && !force ) {
11751 
11752 					swp->next_primary_fire_stamp[bank_to_fire] = timestamp((int)(next_fire_delay));
11753 					if ( obj == Player_obj )
11754 					{
11755 						ship_maybe_play_primary_fail_sound();
11756 					}
11757 					ship_stop_fire_primary_bank(obj, bank_to_fire);
11758 					continue;
11759 				}
11760 				// moved the above to here to use points instead of num_slots for energy consumption check
11761 
11762 				// ballistics support for primaries - Goober5000
11763 				if ( winfo_p->wi_flags[Weapon::Info_Flags::Ballistic] )
11764 				{
11765 					// If ship is being repaired/rearmed, it cannot fire ballistics
11766 					if ( aip->ai_flags[AI::AI_Flags::Being_repaired] )
11767 					{
11768 						continue;
11769 					}
11770 
11771 					// duplicated from the secondaries firing routine...
11772 					// determine if there is enough ammo left to fire weapons on this bank.  As with primary
11773 					// weapons, we might or might not check ammo counts depending on game mode, who is firing,
11774 					// and if I am a client in multiplayer
11775 					int check_ammo = 1;
11776 
11777 					if ( MULTIPLAYER_CLIENT && (obj != Player_obj) )
11778 					{
11779 						check_ammo = 0;
11780 					}
11781 
11782 					// not enough ammo
11783 					if ( check_ammo && ( swp->primary_bank_ammo[bank_to_fire] <= 0) )
11784 					{
11785 						if ( obj == Player_obj )
11786 						{
11787 							ship_maybe_play_primary_fail_sound();
11788 						}
11789 						else
11790 						{
11791 							// TODO:  AI switch primary weapon / re-arm?
11792 						}
11793 						continue;
11794 					}
11795 
11796 					// deplete ammo
11797 					if ( !Weapon_energy_cheat )
11798 					{
11799 						swp->primary_bank_ammo[bank_to_fire] -= points*numtimes;
11800 
11801 						// make sure we don't go below zero; any such error is excusable
11802 						// because it only happens when the bank is depleted in one shot
11803 						if (swp->primary_bank_ammo[bank_to_fire] < 0)
11804 						{
11805 							swp->primary_bank_ammo[bank_to_fire] = 0;
11806 						}
11807 					}
11808 				}
11809 
11810 				// now handle the energy as usual
11811 				// deplete the weapon reserve energy by the amount of energy used to fire the weapon
11812 				// Only subtract the energy amount required for equipment operation once
11813 				shipp->weapon_energy -= points*numtimes * winfo_p->energy_consumed;
11814 				// note for later: option for fuel!
11815 
11816 				// Mark all these weapons as in the same group
11817 				int new_group_id = weapon_create_group_id();
11818 
11819 				vec3d total_impulse;
11820 				vec3d *firepoint_list;
11821 				size_t current_firepoint = 0;
11822 
11823 				if (winfo_p->wi_flags[Weapon::Info_Flags::Apply_Recoil]){
11824 					firepoint_list = new vec3d[numtimes * points];
11825 					vm_vec_zero(&total_impulse);
11826 				} else {
11827 					firepoint_list = nullptr;
11828 				}
11829 
11830 				for ( w = 0; w < numtimes; w++ ) {
11831 					polymodel *weapon_model = NULL;
11832 					if(winfo_p->external_model_num >= 0)
11833 						weapon_model = model_get(winfo_p->external_model_num);
11834 
11835 					if (weapon_model)
11836 						if ((weapon_model->n_guns <= swp->external_model_fp_counter[bank_to_fire]) || (swp->external_model_fp_counter[bank_to_fire] < 0))
11837 							swp->external_model_fp_counter[bank_to_fire] = 0;
11838 
11839 					for ( j = 0; j < points; j++ ) {
11840 						int pt; //point
11841 						if ( (winfo_p->wi_flags[Weapon::Info_Flags::Cycle]) || (sip->flags[Ship::Info_Flags::Dyn_primary_linking]) ){
11842 							pt = (shipp->last_fired_point[bank_to_fire]+1)%num_slots;
11843 						}else{
11844 							pt = j;
11845 						}
11846 
11847 						int sub_shots = 1;
11848 						// Use 0 instead of bank_to_fire as index when checking the number of external weapon model firingpoints
11849 						if (weapon_model && weapon_model->n_guns)
11850 							if (!(winfo_p->wi_flags[Weapon::Info_Flags::External_weapon_fp]))
11851 								sub_shots = weapon_model->gun_banks[0].num_slots;
11852 
11853 						for(int s = 0; s<sub_shots; s++){
11854 							pnt = pm->gun_banks[bank_to_fire].pnt[pt];
11855 							// Use 0 instead of bank_to_fire as index to external weapon model firingpoints
11856 							if (weapon_model && weapon_model->n_guns) {
11857 								if (winfo_p->wi_flags[Weapon::Info_Flags::External_weapon_fp]) {
11858 									vm_vec_add2(&pnt, &weapon_model->gun_banks[0].pnt[swp->external_model_fp_counter[bank_to_fire]]);
11859 								} else {
11860 									vm_vec_add2(&pnt, &weapon_model->gun_banks[0].pnt[s]);
11861 								}
11862 							}
11863 
11864 							vm_vec_unrotate(&gun_point, &pnt, &obj->orient);
11865 							vm_vec_add(&firing_pos, &gun_point, &obj->pos);
11866 
11867 							matrix firing_orient;
11868 
11869 							/*	I AIM autoaim convergence
11870 								II AIM autoaim
11871 								III AIM auto convergence
11872 								IV AIM std convergence
11873 								V SIF convergence
11874 								no convergence or autoaim
11875 							*/
11876 							if (has_autoaim && in_automatic_aim_fov) {
11877 								vec3d firing_vec;
11878 
11879 								if (has_converging_autoaim) {
11880 									// converging autoaim
11881 									vm_vec_sub(&firing_vec, &predicted_target_pos, &firing_pos);
11882 								} else {
11883 									// autoaim
11884 									vm_vec_sub(&firing_vec, &predicted_target_pos, &obj->pos);
11885 								}
11886 
11887 								vm_vector_2_matrix(&firing_orient, &firing_vec, NULL, NULL);
11888 							} else if ((sip->aiming_flags[Ship::Aiming_Flags::Std_convergence]) || ((sip->aiming_flags[Ship::Aiming_Flags::Auto_convergence]) && (aip->target_objnum != -1))) {
11889 								// std & auto convergence
11890 								vec3d target_vec, firing_vec, convergence_offset;
11891 
11892 								// make sure vector is of the set length
11893 								vm_vec_copy_normalize(&target_vec, &player_forward_vec);
11894 								if ((sip->aiming_flags[Ship::Aiming_Flags::Auto_convergence]) && (aip->target_objnum != -1)) {
11895 									// auto convergence
11896 									vm_vec_scale(&target_vec, dist_to_aim);
11897 								} else {
11898 									// std convergence
11899 									vm_vec_scale(&target_vec, sip->convergence_distance);
11900 								}
11901 
11902 								// if there is convergence offset then make use of it)
11903 								if (sip->aiming_flags[Ship::Aiming_Flags::Convergence_offset]) {
11904 									vm_vec_unrotate(&convergence_offset, &sip->convergence_offset, &obj->orient);
11905 									vm_vec_add2(&target_vec, &convergence_offset);
11906 								}
11907 
11908 								vm_vec_add2(&target_vec, &obj->pos);
11909 								vm_vec_sub(&firing_vec, &target_vec, &firing_pos);
11910 
11911 								// set orientation
11912 								vm_vector_2_matrix(&firing_orient, &firing_vec, NULL, NULL);
11913 							} else if (sip->flags[Ship::Info_Flags::Gun_convergence]) {
11914 								// model file defined convergence
11915 								vec3d firing_vec;
11916 								vm_vec_unrotate(&firing_vec, &pm->gun_banks[bank_to_fire].norm[pt], &obj->orient);
11917 								vm_vector_2_matrix(&firing_orient, &firing_vec, NULL, NULL);
11918 							} else {
11919 								// no autoaim or convergence
11920 								firing_orient = obj->orient;
11921 							}
11922 
11923 							if (winfo_p->wi_flags[Weapon::Info_Flags::Apply_Recoil]){	// Function to add recoil functionality - DahBlount
11924 								vec3d local_impulse = firing_orient.vec.fvec;
11925 
11926 								float recoil_force = (winfo_p->mass * winfo_p->max_speed * winfo_p->recoil_modifier * sip->ship_recoil_modifier);
11927 
11928 								firepoint_list[current_firepoint++] = firing_pos;
11929 
11930 								vm_vec_scale(&local_impulse, (-1 * recoil_force));
11931 								vm_vec_add2(&total_impulse, &local_impulse);
11932 							}
11933 
11934 							// create the weapon -- the network signature for multiplayer is created inside
11935 							// of weapon_create
11936 							weapon_objnum = weapon_create( &firing_pos, &firing_orient, weapon_idx, OBJ_INDEX(obj), new_group_id,
11937 								0, 0, swp->primary_bank_fof_cooldown[bank_to_fire] );
11938 
11939 							if (weapon_objnum == -1) {
11940 								// Weapon most likely failed to fire
11941 								if (obj == Player_obj) {
11942 									ship_maybe_play_primary_fail_sound();
11943 								}
11944 								continue;
11945 							}
11946 
11947 							winfo_p = &Weapon_info[Weapons[Objects[weapon_objnum].instance].weapon_info_index];
11948 							has_fired = true;
11949 
11950 							weapon_set_tracking_info(weapon_objnum, OBJ_INDEX(obj), aip->target_objnum, aip->current_target_is_locked, aip->targeted_subsys);
11951 
11952 							if (winfo_p->wi_flags[Weapon::Info_Flags::Flak])
11953 							{
11954 								object *target;
11955 								vec3d predicted_pos;
11956 								float flak_range=(winfo_p->lifetime)*(winfo_p->max_speed);
11957 								float range_to_target = flak_range;
11958 								float wepstr=ship_get_subsystem_strength(shipp, SUBSYSTEM_WEAPONS);
11959 
11960 								if (aip->target_objnum != -1) {
11961 									target = &Objects[aip->target_objnum];
11962 								} else {
11963 									target = NULL;
11964 								}
11965 
11966 								if (target != NULL) {
11967 									set_predicted_enemy_pos(&predicted_pos, obj, &target->pos, &target->phys_info.vel, aip);
11968 									range_to_target=vm_vec_dist(&predicted_pos, &obj->pos);
11969 								}
11970 
11971 								//if we have a target and its in range
11972 								if ( (target != NULL) && (range_to_target < flak_range) )
11973 								{
11974 									//set flak range to range of ship
11975 									flak_pick_range(&Objects[weapon_objnum], &firing_pos, &predicted_pos,wepstr);
11976 								}
11977 								else
11978 								{
11979 									flak_set_range(&Objects[weapon_objnum], flak_range - winfo_p->untargeted_flak_range_penalty);
11980 								}
11981 							}
11982 							// create the muzzle flash effect
11983 							shipfx_flash_create( obj, sip->model_num, &pnt, &obj->orient.vec.fvec, 1, weapon_idx );
11984 
11985 							// maybe shudder the ship - if its me
11986 							if((winfo_p->wi_flags[Weapon::Info_Flags::Shudder]) && (obj == Player_obj) && !(Game_mode & GM_STANDALONE_SERVER)){
11987 								// calculate some arbitrary value between 100
11988 								// (mass * velocity) / 10
11989 								game_shudder_apply(500, (winfo_p->mass * winfo_p->max_speed) * 0.1f);
11990 							}
11991 
11992 							num_fired++;
11993 							shipp->last_fired_point[bank_to_fire] = (shipp->last_fired_point[bank_to_fire] + 1) % num_slots;
11994 
11995 							// maybe add this weapon to the list of those we need to roll forward
11996 							if ((Game_mode & (GM_MULTIPLAYER | GM_STANDALONE_SERVER )) && rollback_shot) {
11997 								multi_ship_record_add_rollback_wep(weapon_objnum);
11998 							}
11999 						}
12000 					}
12001 					swp->external_model_fp_counter[bank_to_fire]++;
12002 				}
12003 				if (winfo_p->wi_flags[Weapon::Info_Flags::Apply_Recoil]){
12004 					vec3d avg_firepoint;
12005 
12006 					vm_vec_avg_n(&avg_firepoint, (int)current_firepoint, firepoint_list);
12007 
12008 					ship_apply_whack(&total_impulse, &avg_firepoint, obj);
12009 					delete[] firepoint_list;
12010 				}
12011 			}
12012 
12013 			CLAMP(shipp->weapon_energy, 0.0f, sip->max_weapon_reserve);
12014 
12015 			banks_fired |= (1<<bank_to_fire);				// mark this bank as fired.
12016 		}
12017 
12018 
12019 		// Only play the weapon fired sound if it hasn't been played yet.  This is to
12020 		// avoid playing the same sound multiple times when banks are linked with the
12021 		// same weapon.
12022 
12023 		if (!(winfo_p->wi_flags[Weapon::Info_Flags::Beam])){	// not a beam weapon?
12024 			if ( sound_played != winfo_p->launch_snd ) {
12025 				sound_played = winfo_p->launch_snd;
12026 				if ( obj == Player_obj ) {
12027 					if ( winfo_p->launch_snd.isValid() ) {
12028 						weapon_info *wip;
12029 						ship_weapon *sw_pl;
12030 
12031 						//Update the last timestamp until continous fire is over, so we have the timestamp of the cease-fire.
12032 						if (shipp->was_firing_last_frame[bank_to_fire] == 1) {
12033 							swp->last_primary_fire_sound_stamp[bank_to_fire] = timestamp();
12034 						}
12035 
12036 						//Check for pre-launch sound and play if relevant
12037 						if( (winfo_p->pre_launch_snd.isValid())									//If this weapon type has a pre-fire sound
12038 							&& ((timestamp() - swp->last_primary_fire_sound_stamp[bank_to_fire]) >= winfo_p->pre_launch_snd_min_interval)	//and if we're past our minimum delay from the last cease-fire
12039 							&& (shipp->was_firing_last_frame[bank_to_fire] == 0)				//and if we are at the beginning of a firing stream
12040 						){
12041 							snd_play( gamesnd_get_game_sound(winfo_p->pre_launch_snd), 0.0f, 1.0f, SND_PRIORITY_MUST_PLAY); //play it
12042 						} else { //Otherwise, play normal firing sounds
12043 							// HACK
12044 							if(winfo_p->launch_snd == gamesnd_id(GameSounds::AUTOCANNON_SHOT)){
12045 								snd_play( gamesnd_get_game_sound(winfo_p->launch_snd), 0.0f, 1.0f, SND_PRIORITY_TRIPLE_INSTANCE );
12046 							} else {
12047 								snd_play( gamesnd_get_game_sound(winfo_p->launch_snd), 0.0f, 1.0f, SND_PRIORITY_MUST_PLAY );
12048 							}
12049 						}
12050 
12051 						sw_pl = &Player_ship->weapons;
12052 						if (sw_pl->current_primary_bank >= 0)
12053 						{
12054 							wip = &Weapon_info[sw_pl->primary_bank_weapons[sw_pl->current_primary_bank]];
12055 							int force_level = (int) ((wip->armor_factor + wip->shield_factor * 0.2f) * (wip->damage * wip->damage - 7.5f) * 0.45f + 0.6f) * 10 + 2000;
12056 
12057 							// modify force feedback for ballistics: make it stronger
12058 							if (wip->wi_flags[Weapon::Info_Flags::Ballistic])
12059 								joy_ff_play_primary_shoot(force_level * 2);
12060 							// no ballistics
12061 							else
12062 								joy_ff_play_primary_shoot(force_level);
12063 						}
12064 					}
12065 				}else {
12066 					if ( winfo_p->launch_snd.isValid() ) {
12067 						snd_play_3d( gamesnd_get_game_sound(winfo_p->launch_snd), &obj->pos, &View_position );
12068 					}
12069 				}
12070 			}
12071 		}
12072 
12073 		shipp->was_firing_last_frame[bank_to_fire] = 1;
12074 	}	// end for (go to next primary bank)
12075 
12076 	// if multiplayer and we're client-side firing, send the packet
12077 	if(Game_mode & GM_MULTIPLAYER){
12078 		// if I'm a Host send a primary fired packet packet if it's brand new
12079 		if(MULTIPLAYER_MASTER && !rollback_shot) {
12080 			send_NEW_primary_fired_packet(shipp, banks_fired);
12081 		// or if I'm a client, and it is my ship send it for rollback on the server.
12082 		} else if (MULTIPLAYER_CLIENT && (shipp == Player_ship)) {
12083 				send_non_homing_fired_packet(shipp, banks_fired);
12084 		}
12085 	}
12086 
12087    // STATS
12088    if (obj->flags[Object::Object_Flags::Player_ship]) {
12089 		// in multiplayer -- only the server needs to keep track of the stats.  Call the cool
12090 		// function to find the player given the object *.  It had better return a valid player
12091 		// or our internal structure as messed up.
12092 		if( Game_mode & GM_MULTIPLAYER ) {
12093 			if ( Net_player->flags & NETINFO_FLAG_AM_MASTER ) {
12094 				int player_num;
12095 
12096 				player_num = multi_find_player_by_object ( obj );
12097 				Assert ( player_num != -1 );
12098 
12099 				Net_players[player_num].m_player->stats.mp_shots_fired += num_fired;
12100 			}
12101 		} else {
12102 			Player->stats.mp_shots_fired += num_fired;
12103 		}
12104 	}
12105 
12106 	if (has_fired) {
12107 		object *objp = &Objects[shipp->objnum];
12108 		object* target;
12109 		if (Ai_info[shipp->ai_index].target_objnum != -1)
12110 			target = &Objects[Ai_info[shipp->ai_index].target_objnum];
12111 		else
12112 			target = NULL;
12113 		if (objp == Player_obj && Player_ai->target_objnum != -1)
12114 			target = &Objects[Player_ai->target_objnum];
12115 
12116 		if (Script_system.IsActiveAction(CHA_ONWPFIRED) || Script_system.IsActiveAction(CHA_PRIMARYFIRE)) {
12117 			Script_system.SetHookObjects(2, "User", objp, "Target", target);
12118 			Script_system.RunCondition(CHA_ONWPFIRED, objp, 1);
12119 			Script_system.RunCondition(CHA_PRIMARYFIRE, objp);
12120 			Script_system.RemHookVars({"User", "Target"});
12121 		}
12122 	}
12123 
12124 	return num_fired;
12125 }
12126 
ship_start_targeting_laser(ship * shipp)12127 static void ship_start_targeting_laser(ship *shipp)
12128 {
12129 	int bank0_laser = 0;
12130 	int bank1_laser = 0;
12131 
12132 	// determine if either of our banks have a targeting laser
12133 	if((shipp->weapons.primary_bank_weapons[0] >= 0) && (Weapon_info[shipp->weapons.primary_bank_weapons[0]].wi_flags[Weapon::Info_Flags::Beam]) && (Weapon_info[shipp->weapons.primary_bank_weapons[0]].b_info.beam_type == BEAM_TYPE_C)){
12134 		bank0_laser = 1;
12135 	}
12136 	if((shipp->weapons.primary_bank_weapons[1] >= 0) && (Weapon_info[shipp->weapons.primary_bank_weapons[1]].wi_flags[Weapon::Info_Flags::Beam]) && (Weapon_info[shipp->weapons.primary_bank_weapons[1]].b_info.beam_type == BEAM_TYPE_C)){
12137 		bank1_laser = 1;
12138 	}
12139 
12140 	// if primary banks are linked
12141 	if(shipp->flags[Ship_Flags::Primary_linked]){
12142 		if(bank0_laser){
12143 			shipp->targeting_laser_bank = 0;
12144 			return;
12145 		}
12146 		if(bank1_laser){
12147 			shipp->targeting_laser_bank = 1;
12148 			return;
12149 		}
12150 	}
12151 	// if we only have 1 bank selected
12152 	else {
12153 		if(bank0_laser && (shipp->weapons.current_primary_bank == 0)){
12154 			shipp->targeting_laser_bank = 0;
12155 			return;
12156 		}
12157 		if(bank1_laser && (shipp->weapons.current_primary_bank == 1)){
12158 			shipp->targeting_laser_bank = 1;
12159 			return;
12160 		}
12161 	}
12162 }
12163 
ship_stop_targeting_laser(ship * shipp)12164 static void ship_stop_targeting_laser(ship *shipp)
12165 {
12166 	shipp->targeting_laser_bank = -1;
12167 	shipp->targeting_laser_objnum = -1; // erase old laser obj num if it has any -Bobboau
12168 }
12169 
ship_process_targeting_lasers()12170 void ship_process_targeting_lasers()
12171 {
12172 	fighter_beam_fire_info fire_info;
12173 	ship_obj *so;
12174 	ship *shipp;
12175 	polymodel *m;
12176 
12177 	// interate over all ships
12178 	for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) {
12179 		// sanity checks
12180 		if(so->objnum < 0){
12181 			continue;
12182 		}
12183 		if(Objects[so->objnum].type != OBJ_SHIP){
12184 			continue;
12185 		}
12186 		if(Objects[so->objnum].instance < 0){
12187 			continue;
12188 		}
12189 		shipp = &Ships[Objects[so->objnum].instance];
12190 
12191 		// if our trigger is no longer down, switch it off
12192 		if(!(shipp->flags[Ship_Flags::Trigger_down])){
12193 			ship_stop_targeting_laser(shipp);
12194 			continue;
12195 		}
12196 
12197 		// if we have a bank to fire - fire it
12198 		if((shipp->targeting_laser_bank >= 0) && (shipp->targeting_laser_bank < 2)){
12199 			// try and get the model
12200 			m = model_get(Ship_info[shipp->ship_info_index].model_num);
12201 			if(m == NULL){
12202 				continue;
12203 			}
12204 
12205 			// fire a targeting laser
12206 			fire_info.life_left = 0.0;					//for fighter beams
12207 			fire_info.life_total = 0.0f;					//for fighter beams
12208 			fire_info.warmdown_stamp = -1;				//for fighter beams
12209 			fire_info.warmup_stamp = -1;				//for fighter beams
12210 			fire_info.accuracy = 0.0f;
12211 			fire_info.beam_info_index = shipp->weapons.primary_bank_weapons[(int)shipp->targeting_laser_bank];
12212 			fire_info.beam_info_override = NULL;
12213 			fire_info.shooter = &Objects[shipp->objnum];
12214 			fire_info.target = NULL;
12215 			fire_info.target_subsys = NULL;
12216 			fire_info.turret = NULL;
12217 			fire_info.local_fire_postion = m->gun_banks[shipp->targeting_laser_bank].pnt[0];
12218 			shipp->targeting_laser_objnum = beam_fire_targeting(&fire_info);
12219 
12220 			// hmm, why didn't it fire?
12221 			if(shipp->targeting_laser_objnum < 0){
12222 				Int3();
12223 				ship_stop_targeting_laser(shipp);
12224 			}
12225 		}
12226 	}}
12227 
12228 /**
12229  * Maybe detonate secondary weapon that's already out.
12230  * @return Return true if we detonate it, false if not.
12231  */
ship_fire_secondary_detonate(object * obj,ship_weapon * swp)12232 static bool ship_fire_secondary_detonate(object *obj, ship_weapon *swp)
12233 {
12234 	if (swp->remote_detonaters_active > 0) {
12235 		if (timestamp_elapsed(swp->detonate_weapon_time)) {
12236 			missile_obj	*mo;
12237 
12238 			// check for currently locked missiles (highest precedence)
12239 			for ( mo = GET_FIRST(&Missile_obj_list); mo != END_OF_LIST(&Missile_obj_list); mo = GET_NEXT(mo) ) {
12240 				object	*mobjp;
12241 				Assert(mo->objnum >= 0 && mo->objnum < MAX_OBJECTS);
12242 				mobjp = &Objects[mo->objnum];
12243 				if (mobjp->parent_sig == obj->parent_sig && Weapon_info[Weapons[mobjp->instance].weapon_info_index].wi_flags[Weapon::Info_Flags::Remote]) {
12244 					// dont detonate if this guy just spawned i.e. we just spawned him in a previous iteration of this loop
12245 					if (Missiontime - Weapons[mobjp->instance].creation_time > fl2f(0.01)) {
12246 						weapon_detonate(mobjp);
12247 					}
12248 				}
12249 			}
12250 
12251 			return true;
12252 		}
12253 	}
12254 
12255 	return false;
12256 }
12257 
12258 extern void ai_maybe_announce_shockwave_weapon(object *firing_objp, int weapon_index);
12259 
12260 //	Object *obj fires its secondary weapon, if it can.
12261 //	If its most recently fired weapon is a remotely detonatable weapon, detonate it.
12262 //	Returns number of weapons fired.  Note, for swarmers, returns 1 if it is allowed
12263 //	to fire the missiles when allow_swarm is NOT set.  They don't actually get fired on a call here unless allow_swarm is set.
12264 //	When you want to fire swarmers, you call this function with allow_swarm NOT set and frame interval
12265 //	code comes aruond and fires it.
12266 // allow_swarm -> default value is 0... since swarm missiles are fired over several frames,
12267 //                need to avoid firing when normally called
ship_fire_secondary(object * obj,int allow_swarm,bool rollback_shot)12268 int ship_fire_secondary( object *obj, int allow_swarm, bool rollback_shot )
12269 {
12270 	int			n, weapon_idx, j, bank, bank_adjusted, starting_bank_count = -1, num_fired;
12271 	ushort		starting_sig = 0;
12272 	ship			*shipp;
12273 	ship_weapon *swp;
12274 	ship_info	*sip;
12275 	weapon_info	*wip;
12276 	ai_info		*aip;
12277 	polymodel	*pm;
12278 	vec3d		missile_point, pnt, firing_pos;
12279 	bool has_fired = false;		// Used to determine whether to fire the scripting hook
12280 
12281 	Assert( obj != NULL );
12282 
12283 	// in the case where the server is an observer, he can fire (which would be bad) - unless we do this.
12284 	if( obj->type == OBJ_OBSERVER ){
12285 		return 0;
12286 	}
12287 
12288 	// in the case where the object is a ghost (a delayed fire packet from right before he died, for instance)
12289 	if( (obj->type == OBJ_GHOST) || (obj->type == OBJ_NONE) ){
12290 		return 0;
12291 	}
12292 
12293 	Assert( obj->type == OBJ_SHIP );
12294 	if(obj->type != OBJ_SHIP){
12295 		return 0;
12296 	}
12297 	n = obj->instance;
12298 	Assert( n >= 0 && n < MAX_SHIPS );
12299 	if((n < 0) || (n >= MAX_SHIPS)){
12300 		return 0;
12301 	}
12302 	Assert( Ships[n].objnum == OBJ_INDEX(obj));
12303 	if(Ships[n].objnum != OBJ_INDEX(obj)){
12304 		return 0;
12305 	}
12306 
12307 	shipp = &Ships[n];
12308 	swp = &shipp->weapons;
12309 	sip = &Ship_info[shipp->ship_info_index];
12310 	aip = &Ai_info[shipp->ai_index];
12311 
12312 	// if no secondary weapons are present on ship, return
12313 	if ( swp->num_secondary_banks <= 0 ){
12314 		return 0;
12315 	}
12316 
12317 	// If the secondaries have been locked, bail
12318 	if (shipp->flags[Ship_Flags::Secondaries_locked])
12319 	{
12320 		return 0;
12321 	}
12322 
12323 	// If ship is being repaired/rearmed, it cannot fire missiles
12324 	if ( aip->ai_flags[AI::AI_Flags::Being_repaired] ) {
12325 		return 0;
12326 	}
12327 
12328 	num_fired = 0;		// tracks how many missiles actually fired
12329 
12330 	// niffiwan: allow swarm/corkscrew bank to keep firing if current bank changes
12331 	if (shipp->swarm_missile_bank != -1 && allow_swarm) {
12332 		bank = shipp->swarm_missile_bank;
12333 	} else if (shipp->corkscrew_missile_bank != -1 && allow_swarm) {
12334 		bank = shipp->corkscrew_missile_bank;
12335 	} else {
12336 		bank = swp->current_secondary_bank;
12337 	}
12338 
12339 	if ( bank < 0 || bank >= sip->num_secondary_banks ) {
12340 		return 0;
12341 	}
12342 	bank_adjusted = MAX_SHIP_PRIMARY_BANKS + bank;
12343 
12344 	if (swp->secondary_animation_position[bank] == MA_POS_SET) {
12345 		if ( timestamp_elapsed(swp->secondary_animation_done_time[bank]) )
12346 			swp->secondary_animation_position[bank] = MA_POS_READY;
12347 		else
12348 			return 0;
12349 	}
12350 
12351 	weapon_idx = swp->secondary_bank_weapons[bank];
12352 
12353 	// It's possible for banks to be empty without issue
12354 	// but indices outside the weapon_info range are a problem
12355 	Assert(swp->secondary_bank_weapons[bank] < weapon_info_size());
12356 	if((swp->secondary_bank_weapons[bank] < 0) || (swp->secondary_bank_weapons[bank] >= weapon_info_size())){
12357 		return 0;
12358 	}
12359 
12360 	wip = &Weapon_info[weapon_idx];
12361 
12362 	if ( MULTIPLAYER_MASTER ) {
12363 		starting_sig = multi_get_next_network_signature( MULTI_SIG_NON_PERMANENT );
12364 		starting_bank_count = swp->secondary_bank_ammo[bank];
12365 	}
12366 
12367 	if (ship_fire_secondary_detonate(obj, swp)) {
12368 		// in multiplayer, master sends a secondary fired packet with starting signature of -1 -- indicates
12369 		// to client code to set the detonate timer to 0.
12370 		if ( MULTIPLAYER_MASTER ) {
12371 			send_secondary_fired_packet( shipp, 0, starting_bank_count, 1, allow_swarm );
12372 		}
12373 
12374 		//	For all banks, if ok to fire a weapon, make it wait a bit.
12375 		//	Solves problem of fire button likely being down next frame and
12376 		//	firing weapon despite fire causing detonation of existing weapon.
12377 		if (swp->current_secondary_bank >= 0) {
12378 			if (timestamp_elapsed(swp->next_secondary_fire_stamp[bank])){
12379 				swp->next_secondary_fire_stamp[bank] = timestamp(MAX((int) flFrametime*3000, 250));
12380 			}
12381 		}
12382 		return 0;
12383 	}
12384 
12385 	// niffiwan: 04/03/12: duplicate of a check approx 100 lines above - not needed?
12386 	if ( bank < 0 ){
12387 		return 0;
12388 	}
12389 
12390 	if ( !timestamp_elapsed(swp->next_secondary_fire_stamp[bank]) && !allow_swarm) {
12391 		if (timestamp_until(swp->next_secondary_fire_stamp[bank]) > 60000){
12392 			swp->next_secondary_fire_stamp[bank] = timestamp(1000);
12393 		}
12394 		goto done_secondary;
12395 	}
12396 
12397 	// Ensure if this is a "require-lock" missile, that a lock actually exists
12398 	if ( wip->wi_flags[Weapon::Info_Flags::No_dumbfire] ) {
12399 		if (!aip->current_target_is_locked && !ship_lock_present(shipp) && shipp->missile_locks_firing.empty() && aip->ai_missile_locks_firing.empty()) {
12400 			if (obj == Player_obj) {
12401 				if (!Weapon_energy_cheat) {
12402 					float max_dist;
12403 
12404 					max_dist = wip->lifetime * wip->max_speed;
12405 					if (wip->wi_flags[Weapon::Info_Flags::Local_ssm]) {
12406 						max_dist = wip->lssm_lock_range;
12407 					}
12408 
12409 					if ((aip->target_objnum != -1) &&
12410 						(vm_vec_dist_quick(&obj->pos, &Objects[aip->target_objnum].pos) > max_dist)) {
12411 						HUD_sourced_printf(HUD_SOURCE_HIDDEN, "%s", XSTR("Too far from target to acquire lock", 487));
12412 					} else {
12413 						HUD_sourced_printf(HUD_SOURCE_HIDDEN, XSTR("Cannot fire %s without a lock", 488), wip->get_display_name());
12414 					}
12415 
12416 					snd_play(gamesnd_get_game_sound(ship_get_sound(Player_obj, GameSounds::OUT_OF_MISSLES)));
12417 					swp->next_secondary_fire_stamp[bank] = timestamp(800); // to avoid repeating messages
12418 					return 0;
12419 				}
12420 			} else {
12421 				// multiplayer clients should always fire the weapon here, so return only if not
12422 				// a multiplayer client.
12423 				if (!MULTIPLAYER_CLIENT) {
12424 					return 0;
12425 				}
12426 			}
12427 		}
12428 	}
12429 
12430 	if (wip->wi_flags[Weapon::Info_Flags::Tagged_only])
12431 	{
12432 		if (!ship_is_tagged(&Objects[aip->target_objnum]))
12433 		{
12434 			if (obj==Player_obj || (MULTIPLAYER_MASTER && obj->flags[Object::Object_Flags::Player_ship]))
12435 			{
12436 				if ( !Weapon_energy_cheat )
12437 				{
12438 					HUD_sourced_printf(HUD_SOURCE_HIDDEN, NOX("Cannot fire %s if target is not tagged"), wip->get_display_name());
12439 					snd_play( gamesnd_get_game_sound(ship_get_sound(Player_obj, GameSounds::OUT_OF_MISSLES)) );
12440 					swp->next_secondary_fire_stamp[bank] = timestamp(800);	// to avoid repeating messages
12441 					return 0;
12442 				}
12443 			}
12444 			else
12445 			{
12446 				if ( !MULTIPLAYER_CLIENT )
12447 				{
12448 					return 0;
12449 				}
12450 			}
12451 		}
12452 	}
12453 
12454 	if ( !allow_swarm && (obj == Player_obj || (MULTIPLAYER_MASTER && obj->flags[Object::Object_Flags::Player_ship] ))) {
12455 		ship_queue_missile_locks(shipp);
12456 	}
12457 
12458 	// if trying to fire a swarm missile, make sure being called from right place
12459 	if ( (wip->wi_flags[Weapon::Info_Flags::Swarm]) && !allow_swarm ) {
12460 		Assert(wip->swarm_count > 0);
12461 		if (wip->multi_lock) {
12462 			if(obj == Player_obj || (MULTIPLAYER_MASTER && obj->flags[Object::Object_Flags::Player_ship]))
12463 				shipp->num_swarm_missiles_to_fire = (int)shipp->missile_locks_firing.size();
12464 			else // AI ships
12465 				shipp->num_swarm_missiles_to_fire = (int)aip->ai_missile_locks_firing.size();
12466 		} else if(wip->swarm_count <= 0){
12467 			shipp->num_swarm_missiles_to_fire = SWARM_DEFAULT_NUM_MISSILES_FIRED;
12468 		} else {
12469 			shipp->num_swarm_missiles_to_fire = wip->swarm_count;
12470 		}
12471 
12472 		shipp->swarm_missile_bank = bank;
12473 		return 1;		//	Note: Missiles didn't get fired, but the frame interval code will fire them.
12474 	}
12475 
12476 	// if trying to fire a corkscrew missile, make sure being called from right place
12477 	if ( (wip->wi_flags[Weapon::Info_Flags::Corkscrew]) && !allow_swarm ) {
12478 		//phreak 11-9-02
12479 		//changed this from 4 to custom number defined in tables
12480 		if (wip->multi_lock) {
12481 			if (obj == Player_obj || (MULTIPLAYER_MASTER && obj->flags[Object::Object_Flags::Player_ship]))
12482 				shipp->num_corkscrew_to_fire = (ubyte)shipp->missile_locks_firing.size();
12483 			else // AI ships
12484 				shipp->num_corkscrew_to_fire = (ubyte)aip->ai_missile_locks_firing.size();
12485 		} else {
12486 			shipp->num_corkscrew_to_fire = (ubyte)(shipp->num_corkscrew_to_fire + (ubyte)wip->cs_num_fired);
12487 		}
12488 		shipp->corkscrew_missile_bank = bank;
12489 		return 1;		//	Note: Missiles didn't get fired, but the frame interval code will fire them.
12490 	}
12491 
12492 	float t;
12493 
12494 	if (Weapon_info[weapon_idx].burst_shots > swp->burst_counter[bank_adjusted]) {
12495 		t = Weapon_info[weapon_idx].burst_delay;
12496 		swp->burst_counter[bank_adjusted]++;
12497 	} else {
12498 		t = Weapon_info[weapon_idx].fire_wait;	// They can fire 5 times a second
12499 		swp->burst_counter[bank_adjusted] = 0;
12500 		swp->burst_seed[bank_adjusted] = Random::next();
12501 	}
12502 	swp->next_secondary_fire_stamp[bank] = timestamp((int)(t * 1000.0f));
12503 	swp->last_secondary_fire_stamp[bank] = timestamp();
12504 
12505 	// Here is where we check if weapons subsystem is capable of firing the weapon.
12506 	// do only in single player or if I am the server of a multiplayer game
12507 	if ( !(Game_mode & GM_MULTIPLAYER) || MULTIPLAYER_MASTER ) {
12508 		if ( ship_weapon_maybe_fail(shipp) ) {
12509 			if ( obj == Player_obj )
12510 				if ( ship_maybe_play_secondary_fail_sound(wip) ) {
12511 					HUD_sourced_printf(HUD_SOURCE_HIDDEN, XSTR( "Cannot fire %s due to weapons system damage", 489), Weapon_info[weapon_idx].get_display_name());
12512 				}
12513 			goto done_secondary;
12514 		}
12515 	}
12516 
12517 	pm = model_get( sip->model_num );
12518 	if ( pm->n_missiles > 0 ) {
12519 		int check_ammo;		// used to tell if we should check ammo counts or not
12520 		int num_slots;
12521 
12522 		if ( bank > pm->n_missiles ) {
12523 			nprintf(("WARNING","WARNING ==> Tried to fire bank %d, but ship has only %d banks\n", bank+1, pm->n_missiles));
12524 			return 0;		// we can make a quick out here!!!
12525 		}
12526 
12527 		int target_objnum;
12528 		ship_subsys *target_subsys;
12529 		int locked;
12530 
12531 		if ( obj == Player_obj || ( MULTIPLAYER_MASTER && obj->flags[Object::Object_Flags::Player_ship] ) ) {
12532 			// use missile lock slots
12533 			if ( !shipp->missile_locks_firing.empty() ) {
12534 				lock_info lock_data = shipp->missile_locks_firing.back();
12535 
12536 				/* The conditional here might seem complicated but it is required to ensure that each use case is handled correctly.
12537 				 * While all missiles need to have their locks cleared, swarms and corkscrews need them cleared only after the last missile is going to be
12538 				 * or has been fired. (DahBlount)
12539 				**/
12540 				if ( wip->multi_lock ||
12541 					 !(wip->wi_flags[Weapon::Info_Flags::Corkscrew] || wip->wi_flags[Weapon::Info_Flags::Swarm]) ||
12542 					 (shipp->num_corkscrew_to_fire <= 1 && shipp->num_swarm_missiles_to_fire <= 1) ) {
12543 					shipp->missile_locks_firing.pop_back();
12544 				}
12545 
12546 				target_objnum = OBJ_INDEX(lock_data.obj);
12547 				target_subsys = lock_data.subsys;
12548 				locked = 1;
12549 			} else if (wip->wi_flags[Weapon::Info_Flags::Homing_heat]) {
12550 				target_objnum = aip->target_objnum;
12551 				target_subsys = aip->targeted_subsys;
12552 				locked = aip->current_target_is_locked;
12553 			} else {
12554 				target_objnum = -1;
12555 				target_subsys = nullptr;
12556 				locked = 0;
12557 			}
12558 		} else if (wip->multi_lock && !aip->ai_missile_locks_firing.empty()) {
12559 			target_objnum = aip->ai_missile_locks_firing.back().first;
12560 			target_subsys = aip->ai_missile_locks_firing.back().second;
12561 			locked = 1;
12562 			aip->ai_missile_locks_firing.pop_back();
12563 		} else {
12564 			target_objnum = aip->target_objnum;
12565 			target_subsys = aip->targeted_subsys;
12566 			locked = aip->current_target_is_locked;
12567 		}
12568 
12569 		num_slots = pm->missile_banks[bank].num_slots;
12570 
12571 		// determine if there is enough ammo left to fire weapons on this bank.  As with primary
12572 		// weapons, we might or might not check ammo counts depending on game mode, who is firing,
12573 		// and if I am a client in multiplayer
12574 		check_ammo = 1;
12575 
12576 		if ( MULTIPLAYER_CLIENT && (obj != Player_obj) ){
12577 			check_ammo = 0;
12578 		}
12579 
12580 		if ( check_ammo && ( swp->secondary_bank_ammo[bank] <= 0) ) {
12581 			if ( shipp->objnum == OBJ_INDEX(Player_obj) ) {
12582 				if ( ship_maybe_play_secondary_fail_sound(wip) ) {
12583 //					HUD_sourced_printf(HUD_SOURCE_HIDDEN, "No %s missiles left in bank", Weapon_info[swp->secondary_bank_weapons[bank]].name);
12584 				}
12585 			}
12586 			else {
12587 				// TODO:  AI switch secondary weapon / re-arm?
12588 			}
12589 			goto done_secondary;
12590 		}
12591 
12592 		// Handle the optional disabling of dual fire
12593 		// dual fire/doublefire can be disabled for individual weapons, for players, or for AIs
12594 		// if any of these apply to the current weapon, unset the dual fire flag on the ship
12595 		// then proceed as normal.
12596 		// This is only handled at firing time so dualfire isn't lost when cycling through weapons
12597 		if (shipp->flags[Ship_Flags::Secondary_dual_fire] &&
12598 			( wip->wi_flags[Weapon::Info_Flags::No_doublefire] ||
12599 			( The_mission.ai_profile->flags[AI::Profile_Flags::Disable_ai_secondary_doublefire] &&
12600 				shipp->objnum != OBJ_INDEX(Player_obj) ) ||
12601 			( The_mission.ai_profile->flags[AI::Profile_Flags::Disable_player_secondary_doublefire] &&
12602 				shipp->objnum == OBJ_INDEX(Player_obj) ))
12603 			) {
12604 			shipp->flags.remove(Ship_Flags::Secondary_dual_fire);
12605 		}
12606 
12607 		int start_slot, end_slot;
12608 
12609 		if ( shipp->flags[Ship_Flags::Secondary_dual_fire] && num_slots > 1) {
12610 			start_slot = swp->secondary_next_slot[bank];
12611 			// AL 11-19-97: Ensure enough ammo remains when firing linked secondary weapons
12612 			if ( check_ammo && (swp->secondary_bank_ammo[bank] < 2) ) {
12613 				end_slot = start_slot;
12614 			} else {
12615 				end_slot = start_slot+1;
12616 			}
12617 		} else {
12618 			// de-set the flag just in case dual-fire was set but couldn't be used
12619 			// because there's less than two firepoints
12620 			shipp->flags.remove(Ship_Flags::Secondary_dual_fire);
12621 			start_slot = swp->secondary_next_slot[bank];
12622 			end_slot = start_slot;
12623 		}
12624 
12625 		int pnt_index=start_slot;
12626 		//If this is a tertiary weapon, only subtract one piece of ammo
12627 		for ( j = start_slot; j <= end_slot; j++ ) {
12628 			int	weapon_num;
12629 
12630 			swp->secondary_next_slot[bank]++;
12631 			if ( swp->secondary_next_slot[bank] > (num_slots-1) ){
12632 				swp->secondary_next_slot[bank] = 0;
12633 			}
12634 
12635 			if ( pnt_index >= num_slots ){
12636 				pnt_index = 0;
12637 			}
12638 			shipp->secondary_point_reload_pct.set(bank, pnt_index, 0.0f);
12639 			pnt = pm->missile_banks[bank].pnt[pnt_index++];
12640 
12641 			polymodel *weapon_model = NULL;
12642 			if(wip->external_model_num >= 0){
12643 				weapon_model = model_get(wip->external_model_num);
12644 			}
12645 
12646 			if (weapon_model && weapon_model->n_guns) {
12647 				int external_bank = bank + MAX_SHIP_PRIMARY_BANKS;
12648 				if (wip->wi_flags[Weapon::Info_Flags::External_weapon_fp]) {
12649 					if ((weapon_model->n_guns <= swp->external_model_fp_counter[external_bank]) || (swp->external_model_fp_counter[external_bank] < 0))
12650 						swp->external_model_fp_counter[external_bank] = 0;
12651 					vm_vec_add2(&pnt, &weapon_model->gun_banks[0].pnt[swp->external_model_fp_counter[external_bank]]);
12652 					swp->external_model_fp_counter[external_bank]++;
12653 				} else {
12654 					// make it use the 0 index slot
12655 					vm_vec_add2(&pnt, &weapon_model->gun_banks[0].pnt[0]);
12656 				}
12657 			}
12658 			vm_vec_unrotate(&missile_point, &pnt, &obj->orient);
12659 			vm_vec_add(&firing_pos, &missile_point, &obj->pos);
12660 
12661 			if ( Game_mode & GM_MULTIPLAYER ) {
12662 				Assert( Weapon_info[weapon_idx].subtype == WP_MISSILE );
12663 			}
12664 
12665 			matrix firing_orient;
12666 			if(!(sip->flags[Ship::Info_Flags::Gun_convergence]))
12667 			{
12668 				firing_orient = obj->orient;
12669 			}
12670 			else
12671 			{
12672 				vec3d firing_vec;
12673 				vm_vec_unrotate(&firing_vec, &pm->missile_banks[bank].norm[pnt_index-1], &obj->orient);
12674 				vm_vector_2_matrix(&firing_orient, &firing_vec, NULL, NULL);
12675 			}
12676 
12677 			// create the weapon -- for multiplayer, the net_signature is assigned inside
12678 			// of weapon_create
12679 			weapon_num = weapon_create( &firing_pos, &firing_orient, weapon_idx, OBJ_INDEX(obj), -1, locked);
12680 
12681 			if (weapon_num == -1) {
12682 				// Weapon most likely failed to fire
12683 				if (obj == Player_obj) {
12684 					ship_maybe_play_secondary_fail_sound(wip);
12685 				}
12686 				continue;
12687 			}
12688 
12689 			if (weapon_num >= 0) {
12690 				weapon_idx = Weapons[Objects[weapon_num].instance].weapon_info_index;
12691 				weapon_set_tracking_info(weapon_num, OBJ_INDEX(obj), target_objnum, locked, target_subsys);
12692 				has_fired = true;
12693 
12694 				// create the muzzle flash effect
12695 				shipfx_flash_create(obj, sip->model_num, &pnt, &obj->orient.vec.fvec, 0, weapon_idx);
12696 
12697 				if((wip->wi_flags[Weapon::Info_Flags::Shudder]) && (obj == Player_obj) && !(Game_mode & GM_STANDALONE_SERVER)){
12698 					// calculate some arbitrary value between 100
12699 					// (mass * velocity) / 10
12700 					game_shudder_apply(500, (wip->mass * wip->max_speed) * 0.1f);
12701 				}
12702 
12703 				num_fired++;
12704 				swp->detonate_weapon_time = timestamp((int)(DEFAULT_REMOTE_DETONATE_TRIGGER_WAIT * 1000));;		//	Can detonate 1/2 second later.
12705 				if (Weapon_info[weapon_idx].wi_flags[Weapon::Info_Flags::Remote])
12706 					swp->remote_detonaters_active++;
12707 
12708 				// possibly add this to the rollback vector
12709 				if ((Game_mode & (GM_MULTIPLAYER | GM_STANDALONE_SERVER)) && rollback_shot){
12710 					multi_ship_record_add_rollback_wep(weapon_num);
12711 				}
12712 
12713 				// subtract the number of missiles fired
12714 				if ( !Weapon_energy_cheat ){
12715 					swp->secondary_bank_ammo[bank]--;
12716 				}
12717 			}
12718 		}
12719 	}
12720 
12721 	if ( obj == Player_obj ) {
12722 		if ( Weapon_info[weapon_idx].launch_snd.isValid() ) {
12723 			snd_play( gamesnd_get_game_sound(Weapon_info[weapon_idx].launch_snd), 0.0f, 1.0f, SND_PRIORITY_MUST_PLAY );
12724 			swp = &Player_ship->weapons;
12725 			if (bank >= 0) {
12726 				wip = &Weapon_info[swp->secondary_bank_weapons[bank]];
12727 				if (Player_ship->flags[Ship_Flags::Secondary_dual_fire]){
12728 					joy_ff_play_secondary_shoot((int) (wip->cargo_size * 2.0f));
12729 				} else {
12730 					joy_ff_play_secondary_shoot((int) wip->cargo_size);
12731 				}
12732 			}
12733 		}
12734 
12735 	} else {
12736 		if ( Weapon_info[weapon_idx].launch_snd.isValid() ) {
12737 			snd_play_3d( gamesnd_get_game_sound(Weapon_info[weapon_idx].launch_snd), &obj->pos, &View_position );
12738 		}
12739 	}
12740 
12741 done_secondary:
12742 
12743 	if(num_fired > 0){
12744 		// if I am the master of a multiplayer game, send a secondary fired packet along with the
12745 		// first network signatures for the newly created weapons.
12746 		// Cyborg17 - If this is a rollback shot, the server will let the player know within the packet.
12747 		if ( MULTIPLAYER_MASTER ) {
12748 			Assert(starting_sig != 0);
12749 			send_secondary_fired_packet( shipp, starting_sig, starting_bank_count, num_fired, allow_swarm );
12750 		}
12751 
12752 		// Handle Player only stuff, including stats and client secondary packets
12753 		if (obj->flags[Object::Object_Flags::Player_ship]) {
12754 			// in multiplayer -- only the server needs to keep track of the stats.  Call the cool
12755 			// function to find the player given the object *.  It had better return a valid player
12756 			// or our internal structure as messed up.
12757 			if( Game_mode & GM_MULTIPLAYER ) {
12758 				if ( Net_player->flags & NETINFO_FLAG_AM_MASTER ) {
12759 					int player_num;
12760 
12761 					player_num = multi_find_player_by_object ( obj );
12762 					Assert ( player_num != -1 );
12763 
12764 					Net_players[player_num].m_player->stats.ms_shots_fired += num_fired;
12765 				} else if (MULTIPLAYER_CLIENT) {
12766 					if (!wip->is_homing())
12767 					send_non_homing_fired_packet(shipp, num_fired, true);
12768 				}
12769 			} else {
12770 				Player->stats.ms_shots_fired += num_fired;
12771 			}
12772 		}
12773 
12774 		// maybe announce a shockwave weapon
12775 		ai_maybe_announce_shockwave_weapon(obj, weapon_idx);
12776 	}
12777 
12778 	// if we are out of ammo in this bank then don't carry over firing swarm/corkscrew
12779 	// missiles to a new bank
12780 	if (swp->secondary_bank_ammo[bank] <= 0) {
12781 		// NOTE: these are set to 1 since they will get reduced by 1 in the
12782 		//       swarm/corkscrew code once this function returns
12783 
12784 		if (shipp->num_swarm_missiles_to_fire > 1) {
12785 			shipp->num_swarm_missiles_to_fire = 1;
12786 			shipp->swarm_missile_bank = -1;
12787 		}
12788 
12789 		if (shipp->num_corkscrew_to_fire > 1) {
12790 			shipp->num_corkscrew_to_fire = 1;
12791 			shipp->corkscrew_missile_bank = -1;
12792 		}
12793 	}
12794 
12795 	if (has_fired) {
12796 		object *objp = &Objects[shipp->objnum];
12797 		object* target;
12798 		if (Ai_info[shipp->ai_index].target_objnum != -1)
12799 			target = &Objects[Ai_info[shipp->ai_index].target_objnum];
12800 		else
12801 			target = NULL;
12802 		if (objp == Player_obj && Player_ai->target_objnum != -1)
12803 			target = &Objects[Player_ai->target_objnum];
12804 
12805 		if (Script_system.IsActiveAction(CHA_ONWPFIRED) || Script_system.IsActiveAction(CHA_SECONDARYFIRE)) {
12806 			Script_system.SetHookObjects(2, "User", objp, "Target", target);
12807 			Script_system.RunCondition(CHA_ONWPFIRED, objp);
12808 			Script_system.RunCondition(CHA_SECONDARYFIRE, objp);
12809 			Script_system.RemHookVars({"User", "Target"});
12810 		}
12811 	}
12812 
12813 	// AL 3-7-98: Move to next valid secondary bank if out of ammo
12814 	//
12815 
12816 	//21-07-02 01:24 DTP; COMMENTED OUT some of the mistakes
12817 	//this bug was made by AL, when he assumed he had to take the next fire_wait time remaining and add 250 ms of delay to it,
12818 	//and put it in the next valid bank. for the player to have a 250 ms of penalty
12819 	//
12820 	//what that caused was that the next valid bank inherited the current valid banks FULL fire delay. since he used the Weapon_info struct that has
12821 	// no information / member that stores the next valids banks remaning fire_wait delay.
12822 	//
12823 	//what he should have done was to check of the next valid bank had any fire delay that had elapsed, if it had elapsed,
12824 	//then it would have no firedelay. and then add 250 ms of delay. in effect, this way there is no penalty if there is any firedelay remaning in
12825 	//the next valid bank. the delay is there to prevent things like Trible/Quad Fire Trebuchets.
12826 	//
12827 	// niffiwan: only try to switch banks if object has multiple banks, and firing bank is the current bank
12828 	if ( (obj->flags[Object::Object_Flags::Player_ship]) && (swp->secondary_bank_ammo[bank] <= 0) && (swp->num_secondary_banks >= 2) && (bank == swp->current_secondary_bank) ) {
12829 		if (ship_select_next_secondary(obj) ) {			//DTP here we switch to the next valid bank, but we can't call weapon_info on next fire_wait
12830 
12831 			if ( timestamp_elapsed(shipp->weapons.next_secondary_fire_stamp[shipp->weapons.current_secondary_bank]) ) {	//DTP, this is simply a copy of the manual cycle functions
12832 				shipp->weapons.next_secondary_fire_stamp[shipp->weapons.current_secondary_bank] = timestamp(1000);	//Bumped from 250 to 1000 because some people seem to be to triggerhappy :).
12833 				shipp->weapons.last_secondary_fire_stamp[shipp->weapons.current_secondary_bank] = timestamp();
12834 			}
12835 
12836 			if ( obj == Player_obj ) {
12837 				snd_play( gamesnd_get_game_sound(ship_get_sound(Player_obj, GameSounds::SECONDARY_CYCLE)) );
12838 			}
12839 		}
12840 	}
12841 
12842 	return num_fired;
12843 }
12844 
12845 // Goober5000
primary_out_of_ammo(ship_weapon * swp,int bank)12846 static int primary_out_of_ammo(ship_weapon *swp, int bank)
12847 {
12848 	// true if both ballistic and ammo <= 0,
12849 	// false if not ballistic or if ballistic and ammo > 0
12850 
12851 	if ( Weapon_info[swp->primary_bank_weapons[bank]].wi_flags[Weapon::Info_Flags::Ballistic] )
12852 	{
12853 		if (swp->primary_bank_ammo[bank] <= 0)
12854 		{
12855 			return 1;
12856 		}
12857 	}
12858 
12859 	// note: never out of ammo if not ballistic
12860 	return 0;
12861 }
12862 
ship_has_a_ballistic_primary(const ship * shipp)12863 static bool ship_has_a_ballistic_primary(const ship *shipp)
12864 {
12865 	const ship_weapon *swp = &shipp->weapons;
12866 
12867 	for (int i = 0; i < swp->num_primary_banks; i++)
12868 	{
12869 		if (Weapon_info[swp->primary_bank_weapons[i]].wi_flags[Weapon::Info_Flags::Ballistic])
12870 			return true;
12871 	}
12872 
12873 	return false;
12874 }
12875 
12876 /**
12877  * Return true if a new index gets selected.
12878  *
12879  * @param objp      pointer to object for ship cycling primary
12880  * @param direction forward == CYCLE_PRIMARY_NEXT, backward == CYCLE_PRIMARY_PREV
12881  *
12882  * NOTE: This code can be called for any arbitrary ship.  HUD messages and sounds are only used
12883  *       for the player ship.
12884  */
ship_select_next_primary(object * objp,int direction)12885 int ship_select_next_primary(object *objp, int direction)
12886 {
12887 	ship	*shipp;
12888 	ship_info *sip;
12889 	ship_weapon *swp;
12890 	int new_bank;
12891 	int original_bank;
12892 	int i;
12893 
12894 	Assert(objp != NULL);
12895 	Assert(objp->type == OBJ_SHIP);
12896 	Assert(objp->instance >= 0 && objp->instance < MAX_SHIPS);
12897 
12898 	shipp = &Ships[objp->instance];
12899 	sip = &Ship_info[shipp->ship_info_index];
12900 	swp = &shipp->weapons;
12901 
12902 	Assert(direction == CYCLE_PRIMARY_NEXT || direction == CYCLE_PRIMARY_PREV);
12903 
12904 	original_bank = swp->current_primary_bank;
12905 	auto original_link_flag = shipp->flags[Ship_Flags::Primary_linked];
12906 
12907 	// redid case structure to possibly add more primaries in the future - Goober5000
12908 	if ( swp->num_primary_banks == 0 )
12909 	{
12910 		if ( objp == Player_obj )
12911 		{
12912 			HUD_sourced_printf(HUD_SOURCE_HIDDEN, "%s", XSTR( "This ship has no primary weapons", 490));
12913 			gamesnd_play_error_beep();
12914 		}
12915 		return 0;
12916 	}
12917 	else if ( swp->num_primary_banks == 1 )
12918 	{
12919 		if ( objp == Player_obj )
12920 		{
12921 			HUD_sourced_printf(HUD_SOURCE_HIDDEN, XSTR( "This ship has only one primary weapon: %s", 491),Weapon_info[swp->primary_bank_weapons[swp->current_primary_bank]].get_display_name(), swp->current_primary_bank + 1);
12922 			gamesnd_play_error_beep();
12923 		}
12924 		return 0;
12925 	}
12926 	else if ( swp->num_primary_banks > MAX_SHIP_PRIMARY_BANKS )
12927 	{
12928 		Int3();
12929 		return 0;
12930 	}
12931 	else
12932 	{
12933 		Assert((swp->current_primary_bank >= 0) && (swp->current_primary_bank < swp->num_primary_banks));
12934 
12935 		// first check if linked
12936 		if ( shipp->flags[Ship_Flags::Ship_selective_linking] )
12937 		{
12938 			printf("npb:%i\n", swp->num_primary_banks );
12939 		}
12940 
12941 		if ( shipp->flags[Ship_Flags::Primary_linked] )
12942 		{
12943 			shipp->flags.remove(Ship_Flags::Primary_linked);
12944 			if ( direction == CYCLE_PRIMARY_NEXT )
12945 			{
12946 				swp->current_primary_bank = 0;
12947 			}
12948 			else
12949 			{
12950 				swp->current_primary_bank = swp->num_primary_banks - 1;
12951 			}
12952 		}
12953 		// now handle when not linked: cycle and constrain
12954 		else
12955 		{
12956 			if ( direction == CYCLE_PRIMARY_NEXT )
12957 			{
12958 				if ( swp->current_primary_bank < swp->num_primary_banks - 1 )
12959 				{
12960 					swp->current_primary_bank++;
12961 				}
12962 				else if( sip->flags[Ship::Info_Flags::No_primary_linking] )
12963 				{
12964 					swp->current_primary_bank = 0;
12965 				}
12966 				else
12967 				{
12968 					shipp->flags.set(Ship_Flags::Primary_linked);
12969 				}
12970 			}
12971 			else
12972 			{
12973 				if ( swp->current_primary_bank > 0 )
12974 				{
12975 					swp->current_primary_bank--;
12976 				}
12977 				else if( sip->flags[Ship::Info_Flags::No_primary_linking] )
12978 				{
12979 					swp->current_primary_bank = swp->num_primary_banks - 1;
12980 				}
12981 				else
12982 				{
12983 					shipp->flags.set(Ship_Flags::Primary_linked);
12984 				}
12985 			}
12986 		}
12987 	}
12988 
12989 	// test for ballistics - Goober5000
12990 	if (ship_has_a_ballistic_primary(shipp))
12991 	{
12992 		// if we can't link, disengage primary linking and change to next available bank
12993 		if (shipp->flags[Ship_Flags::Primary_linked])
12994 		{
12995 			for (i = 0; i < swp->num_primary_banks; i++)
12996 			{
12997 				if (!Weapon_info[swp->primary_bank_weapons[i]].wi_flags[Weapon::Info_Flags::Nolink] && primary_out_of_ammo(swp, i)) // We only care if linkable primaries are out of ammo.
12998 				{
12999 					shipp->flags.remove(Ship_Flags::Primary_linked);
13000 
13001 					if (direction == CYCLE_PRIMARY_NEXT)
13002 					{
13003 						swp->current_primary_bank = 0;
13004 					}
13005 					else
13006 					{
13007 						swp->current_primary_bank = shipp->weapons.num_primary_banks-1;
13008 					}
13009 					break;
13010 				}
13011 			}
13012 		}
13013 
13014 		// check to see if we keep cycling...we have to if we're out of ammo in the current bank
13015 		if ( primary_out_of_ammo(swp, swp->current_primary_bank) )
13016 		{
13017 			// cycle around until we find ammunition...
13018 			// we land on the original bank if all banks fail
13019 			Assert(swp->current_primary_bank < swp->num_primary_banks);
13020 			new_bank = swp->current_primary_bank;
13021 
13022 			for (i = 1; i < swp->num_primary_banks; i++)
13023 			{
13024 				// cycle in the proper direction
13025 				if ( direction == CYCLE_PRIMARY_NEXT )
13026 				{
13027 					new_bank = (swp->current_primary_bank + i) % swp->num_primary_banks;
13028 				}
13029 				else
13030 				{
13031 					new_bank = (swp->current_primary_bank + swp->num_primary_banks - i) % swp->num_primary_banks;
13032 				}
13033 
13034 				// check to see if this is a valid bank
13035 				if (!primary_out_of_ammo(swp, new_bank))
13036 				{
13037 					break;
13038 				}
13039 			}
13040 			// set the new bank; defaults to resetting to the old bank if we completed a full iteration
13041 			swp->current_primary_bank = new_bank;
13042 		}
13043 
13044 		// make sure we're okay
13045 		Assert((swp->current_primary_bank >= 0) && (swp->current_primary_bank < swp->num_primary_banks));
13046 	}	// end of ballistics implementation
13047 
13048 	swp->previous_primary_bank = original_bank;
13049 
13050 	// check to make sure we actually changed banks (which can fail to happen if e.g. ballistic weapons ran out of ammo)
13051 	if ( (swp->current_primary_bank != original_bank) || ((shipp->flags[Ship_Flags::Primary_linked]) != original_link_flag) )
13052 	{
13053 		if ( objp == Player_obj )
13054 		{
13055 			snd_play( gamesnd_get_game_sound(ship_get_sound(objp, GameSounds::PRIMARY_CYCLE)), 0.0f );
13056 		}
13057 		ship_primary_changed(shipp);
13058 		objp = &Objects[shipp->objnum];
13059 		object* target;
13060 		if (Ai_info[shipp->ai_index].target_objnum != -1)
13061 			target = &Objects[Ai_info[shipp->ai_index].target_objnum];
13062 		else
13063 			target = NULL;
13064 		if (objp == Player_obj && Player_ai->target_objnum != -1)
13065 			target = &Objects[Player_ai->target_objnum];
13066 
13067 		if (Script_system.IsActiveAction(CHA_ONWPSELECTED) || Script_system.IsActiveAction(CHA_ONWPDESELECTED)) {
13068 			Script_system.SetHookObjects(2, "User", objp, "Target", target);
13069 			Script_system.RunCondition(CHA_ONWPSELECTED, objp);
13070 			Script_system.RunCondition(CHA_ONWPDESELECTED, objp);
13071 			Script_system.RemHookVars({"User", "Target"});
13072 		}
13073 
13074 		return 1;
13075 	}
13076 
13077 	// could not select new weapon:
13078 	if ( objp == Player_obj )
13079 	{
13080 		gamesnd_play_error_beep();
13081 	}
13082 	return 0;
13083 }
13084 
13085 // ------------------------------------------------------------------------------
13086 // ship_select_next_secondary() selects the next secondary bank with missles
13087 //
13088 //	returns:		1	=> The secondary bank was switched
13089 //					0	=> The secondary bank stayed the same
13090 //
13091 // If a secondary bank has no missles left, it is skipped.
13092 //
13093 // NOTE: This can be called for an arbitrary ship.  HUD messages and sounds are only used
13094 //			for the player ship.
ship_select_next_secondary(object * objp)13095 int ship_select_next_secondary(object *objp)
13096 {
13097 	Assert(objp != NULL);
13098 	Assert(objp->type == OBJ_SHIP);
13099 	Assert(objp->instance >= 0 && objp->instance < MAX_SHIPS);
13100 
13101 	int	original_bank, new_bank, i;
13102 	ship	*shipp;
13103 	ship_weapon *swp;
13104 
13105 	shipp = &Ships[objp->instance];
13106 	swp = &shipp->weapons;
13107 
13108 	// redid the switch structure to allow additional seconary banks if added later - Goober5000
13109 	if ( swp->num_secondary_banks == 0 )
13110 	{
13111 		if ( objp == Player_obj )
13112 		{
13113 			HUD_sourced_printf(HUD_SOURCE_HIDDEN, "%s", XSTR( "This ship has no secondary weapons", 492));
13114 			gamesnd_play_error_beep();
13115 		}
13116 		return 0;
13117 	}
13118 	else if ( swp->num_secondary_banks == 1 )
13119 	{
13120 		if ( objp == Player_obj )
13121 		{
13122 			HUD_sourced_printf(HUD_SOURCE_HIDDEN, XSTR( "This ship has only one secondary weapon: %s", 493), Weapon_info[swp->secondary_bank_weapons[swp->current_secondary_bank]].get_display_name(), swp->current_secondary_bank + 1);
13123 			gamesnd_play_error_beep();
13124 		}
13125 		return 0;
13126 	}
13127 	else if ( swp->num_secondary_banks > MAX_SHIP_SECONDARY_BANKS )
13128 	{
13129 		Int3();
13130 		return 0;
13131 	}
13132 	else
13133 	{
13134 		Assert((swp->current_secondary_bank >= 0) && (swp->current_secondary_bank < swp->num_secondary_banks));
13135 
13136 		original_bank = swp->current_secondary_bank;
13137 
13138 		for ( i = 1; i < swp->num_secondary_banks; i++ ) {
13139 			new_bank = (swp->current_secondary_bank+i) % swp->num_secondary_banks;
13140 			if ( swp->secondary_bank_ammo[new_bank] <= 0 )
13141 				continue;
13142 			swp->current_secondary_bank = new_bank;
13143 			break;
13144 		}
13145 
13146 		if ( swp->current_secondary_bank != original_bank )
13147 		{
13148 			swp->previous_secondary_bank = original_bank;
13149 			if ( objp == Player_obj )
13150 			{
13151 				snd_play( gamesnd_get_game_sound(ship_get_sound(Player_obj, GameSounds::SECONDARY_CYCLE)), 0.0f );
13152 			}
13153 			ship_secondary_changed(shipp);
13154 
13155 			// Clear missile locks when banks are switched
13156 			for (auto& missile_lock : shipp->missile_locks) {
13157 				ship_clear_lock(&missile_lock);
13158 			}
13159 
13160 			shipp->missile_locks_firing.clear();
13161 
13162 			objp = &Objects[shipp->objnum];
13163 			object* target;
13164 			if (Ai_info[shipp->ai_index].target_objnum != -1)
13165 				target = &Objects[Ai_info[shipp->ai_index].target_objnum];
13166 			else
13167 				target = NULL;
13168 			if (objp == Player_obj && Player_ai->target_objnum != -1)
13169 				target = &Objects[Player_ai->target_objnum];
13170 
13171 			if (Script_system.IsActiveAction(CHA_ONWPSELECTED) || Script_system.IsActiveAction(CHA_ONWPDESELECTED)) {
13172 				Script_system.SetHookObjects(2, "User", objp, "Target", target);
13173 				Script_system.RunCondition(CHA_ONWPSELECTED, objp);
13174 				Script_system.RunCondition(CHA_ONWPDESELECTED, objp);
13175 				Script_system.RemHookVars({"User", "Target"});
13176 			}
13177 
13178 			return 1;
13179 		}
13180 	} // end if
13181 
13182 	// If we've reached this point, must have failed
13183 	if ( objp == Player_obj )
13184 	{
13185 		gamesnd_play_error_beep();
13186 	}
13187 	return 0;
13188 }
13189 
13190 // Goober5000 - copied from secondary routine
13191 //	Stuff list of weapon indices for object *objp in list *outlist.
13192 //	Return number of weapons in list.
get_available_primary_weapons(object * objp,int * outlist,int * outbanklist)13193 int get_available_primary_weapons(object *objp, int *outlist, int *outbanklist)
13194 {
13195 	int	count = 0;
13196 	int	i;
13197 	ship	*shipp;
13198 
13199 	Assert(objp->type == OBJ_SHIP);
13200 	Assert((objp->instance >= 0) && (objp->instance < MAX_SHIPS));
13201 	shipp = &Ships[objp->instance];
13202 
13203 	for (i=0; i<shipp->weapons.num_primary_banks; i++)
13204 	{
13205 		if (!primary_out_of_ammo(&(shipp->weapons), i))
13206 		{
13207 			outbanklist[count] = i;
13208 			outlist[count++] = shipp->weapons.primary_bank_weapons[i];
13209 		}
13210 	}
13211 
13212 	return count;
13213 }
13214 
13215 /**
13216  * Stuff list of weapon indices for object *objp in list *outlist.
13217  * @return number of weapons in list.
13218  */
get_available_secondary_weapons(object * objp,int * outlist,int * outbanklist)13219 int get_available_secondary_weapons(object *objp, int *outlist, int *outbanklist)
13220 {
13221 	int	count = 0;
13222 	int	i;
13223 	ship	*shipp;
13224 	weapon_info *wepp;
13225 	Assert(objp->type == OBJ_SHIP);
13226 	Assert((objp->instance >= 0) && (objp->instance < MAX_SHIPS));
13227 	shipp = &Ships[objp->instance];
13228 	Assert(shipp->ai_index >= 0 && shipp->ai_index < MAX_AI_INFO);
13229 	ai_info* aip = &Ai_info[shipp->ai_index];
13230 
13231 	float target_range, weapon_range_max, weapon_range_min;
13232 	target_range = 0.0f;
13233 
13234 	if (The_mission.ai_profile->ai_range_aware_secondary_select_mode!= AI_RANGE_AWARE_SEC_SEL_MODE_RETAIL) {
13235 		vec3d our_position = objp->pos;
13236 		vec3d target_position;
13237 		object *target = &Objects[Ai_info[shipp->ai_index].target_objnum];
13238 		if (target->type == OBJ_SHIP) {
13239 			if (aip->targeted_subsys != nullptr) {
13240 				get_subsystem_pos(&target_position, target, aip->targeted_subsys);
13241 			}
13242 			else if (Ship_info[shipp->ship_info_index].is_big_or_huge() ){
13243 				ai_big_pick_attack_point(target, objp, &target_position, 0.8f);
13244 			}
13245 			else {
13246 				target_position = target->pos;
13247 			}
13248 		}
13249 		else {
13250 			target_position = target->pos;
13251 		}
13252 		target_range = vm_vec_dist_quick(&our_position, &target_position);
13253 	}
13254 	for (i=0; i<shipp->weapons.num_secondary_banks; i++)
13255 		if (shipp->weapons.secondary_bank_ammo[i]) {
13256 			if (The_mission.ai_profile->ai_range_aware_secondary_select_mode != AI_RANGE_AWARE_SEC_SEL_MODE_RETAIL) {
13257 				wepp = &Weapon_info[shipp->weapons.secondary_bank_weapons[i]];
13258 				weapon_range_min = wepp->weapon_min_range;
13259 				weapon_range_max = wepp->weapon_range;
13260 				//If weapon range is not set in the weapon info, derive it
13261 				if (weapon_range_max >= WEAPON_DEFAULT_TABLED_MAX_RANGE) {
13262 					if (wepp->subtype == WP_BEAM) {
13263 						weapon_range_max = wepp->b_info.range;
13264 					}
13265 					else {
13266 						weapon_range_max = wepp->lifetime * wepp->max_speed;
13267 					}
13268 				}
13269 				if (target_range <= weapon_range_max && target_range >= weapon_range_min) {
13270 					outbanklist[count] = i;
13271 					outlist[count++] = shipp->weapons.secondary_bank_weapons[i];
13272 				}
13273 			}
13274 			else {
13275 				outbanklist[count] = i;
13276 				outlist[count++] = shipp->weapons.secondary_bank_weapons[i];
13277 			}
13278 		}
13279 
13280 	return count;
13281 }
13282 
wing_bash_ship_name(char * ship_name,const char * wing_name,int index,bool * needs_display_name)13283 void wing_bash_ship_name(char *ship_name, const char *wing_name, int index, bool *needs_display_name)
13284 {
13285 	if (needs_display_name)
13286 		*needs_display_name = false;
13287 
13288 	// if wing name has a hash symbol, create the ship name a particular way
13289 	// (but don't do this for names that have the hash as the first or last character)
13290 	const char *p = get_pointer_to_first_hash_symbol(wing_name);
13291 	if ((p != NULL) && (p != wing_name) && (*(p+1) != '\0'))
13292 	{
13293 		size_t len = (p - wing_name);
13294 		strncpy(ship_name, wing_name, len);
13295 		sprintf(ship_name + len, NOX(" %d"), index);
13296 		strcat(ship_name, p);
13297 
13298 		if (needs_display_name)
13299 			*needs_display_name = true;
13300 	}
13301 	// most of the time we should create the name the standard retail way
13302 	else
13303 		sprintf(ship_name, NOX("%s %d"), wing_name, index);
13304 }
13305 
13306 /**
13307  * Return the object index of the ship with name *name.
13308  */
wing_name_lookup(const char * name,int ignore_count)13309 int wing_name_lookup(const char *name, int ignore_count)
13310 {
13311 	int i, wing_limit;
13312 
13313 	Assertion(name != nullptr, "NULL name passed to wing_name_lookup");
13314 
13315 	if ( Fred_running )
13316 		wing_limit = MAX_WINGS;
13317 	else
13318 		wing_limit = Num_wings;
13319 
13320 	if (Fred_running || ignore_count ) {  // current_count not used for Fred..
13321 		for (i=0; i<wing_limit; i++)
13322 			if (Wings[i].wave_count && !stricmp(Wings[i].name, name))
13323 				return i;
13324 
13325 	} else {
13326 		for (i=0; i<wing_limit; i++)
13327 			if (Wings[i].current_count && !stricmp(Wings[i].name, name))
13328 				return i;
13329 	}
13330 
13331 	return -1;
13332 }
13333 
wing_has_yet_to_arrive(const wing * wingp)13334 bool wing_has_yet_to_arrive(const wing *wingp)
13335 {
13336 	return (wingp != nullptr) && (wingp->num_waves >= 0) && (wingp->total_arrived_count == 0);
13337 }
13338 
13339 /**
13340  * Needed in addition to wing_name_lookup because it does a straight lookup without
13341  * caring about how many ships are in the wing, etc.
13342  */
wing_lookup(const char * name)13343 int wing_lookup(const char *name)
13344 {
13345 	Assertion(name != nullptr, "NULL name passed to wing_lookup");
13346 
13347 	for(int idx=0;idx<Num_wings;idx++)
13348 		if(stricmp(Wings[idx].name,name)==0)
13349 		   return idx;
13350 
13351 	return -1;
13352 }
13353 
wing_formation_lookup(const char * formation_name)13354 int wing_formation_lookup(const char *formation_name)
13355 {
13356 	Assertion(formation_name != nullptr, "NULL formation name passed to wing_formation_lookup");
13357 
13358 	for (int idx = 0; idx < (int)Wing_formations.size(); ++idx)
13359 		if (stricmp(Wing_formations[idx].name, formation_name) == 0)
13360 			return idx;
13361 
13362 	return -1;
13363 }
13364 
13365 /**
13366  * Return the index of Ship_info[].name that is *token.
13367  */
ship_info_lookup_sub(const char * token)13368 static int ship_info_lookup_sub(const char *token)
13369 {
13370 	Assertion(token != nullptr, "NULL token passed to ship_info_lookup_sub");
13371 
13372 	for (auto it = Ship_info.cbegin(); it != Ship_info.cend(); ++it)
13373 		if (!stricmp(token, it->name))
13374 			return (int)std::distance(Ship_info.cbegin(), it);
13375 
13376 	return -1;
13377 }
13378 
13379 /**
13380  * Return the index of Ship_templates[].name that is *token.
13381  */
ship_template_lookup(const char * token)13382 static int ship_template_lookup(const char *token)
13383 {
13384 	Assertion(token != nullptr, "NULL token passed to ship_template_lookup");
13385 
13386 	for ( auto it = Ship_templates.cbegin(); it != Ship_templates.cend(); ++it ) {
13387 		if ( !stricmp(token, it->name) ) {
13388 			return (int)std::distance(Ship_templates.cbegin(), it);
13389 		}
13390 	}
13391 	return -1;
13392 }
13393 
13394 // Goober5000
ship_info_lookup(const char * token)13395 int ship_info_lookup(const char *token)
13396 {
13397 	int idx;
13398 	const char *p;
13399 	char name[NAME_LENGTH], temp1[NAME_LENGTH], temp2[NAME_LENGTH];
13400 
13401 	Assertion(token != nullptr, "NULL token passed to ship_info_lookup");
13402 
13403 	// first try a straightforward lookup
13404 	idx = ship_info_lookup_sub(token);
13405 	if (idx >= 0)
13406 		return idx;
13407 
13408 	// ship copy types might be mismatched
13409 	p = get_pointer_to_first_hash_symbol(token);
13410 	if (p == NULL)
13411 		return -1;
13412 
13413 	// conversion from FS1 missions
13414 	if (!stricmp(token, "GTD Orion#1 (Galatea)"))
13415 	{
13416 		idx = ship_info_lookup_sub("GTD Orion#Galatea");
13417 		if (idx >= 0)
13418 			return idx;
13419 
13420 		idx = ship_info_lookup_sub("GTD Orion (Galatea)");
13421 		if (idx >= 0)
13422 			return idx;
13423 
13424 		return -1;
13425 	}
13426 	else if (!stricmp(token, "GTD Orion#2 (Bastion)"))
13427 	{
13428 		idx = ship_info_lookup_sub("GTD Orion#Bastion");
13429 		if (idx >= 0)
13430 			return idx;
13431 
13432 		idx = ship_info_lookup_sub("GTD Orion (Bastion)");
13433 		if (idx >= 0)
13434 			return idx;
13435 
13436 		return -1;
13437 	}
13438 	else if (!stricmp(token, "SF Dragon#2 (weakened)"))
13439 	{
13440 		idx = ship_info_lookup_sub("SF Dragon#weakened");
13441 		if (idx >= 0)
13442 			return idx;
13443 
13444 		idx = ship_info_lookup_sub("SF Dragon (weakened)");
13445 		if (idx >= 0)
13446 			return idx;
13447 
13448 		return -1;
13449 	}
13450 	else if (!stricmp(token, "SF Dragon#3 (Player)"))
13451 	{
13452 		idx = ship_info_lookup_sub("SF Dragon#Terrans");
13453 		if (idx >= 0)
13454 			return idx;
13455 
13456 		idx = ship_info_lookup_sub("SF Dragon (Terrans)");
13457 		if (idx >= 0)
13458 			return idx;
13459 
13460 		return -1;
13461 	}
13462 	else if (!stricmp(token, "GTSC Faustus#2 (big blast)"))
13463 	{
13464 		idx = ship_info_lookup_sub("GTSC Faustus#bigblast");
13465 		if (idx >= 0)
13466 			return idx;
13467 
13468 		return -1;
13469 	}
13470 	else if (!stricmp(token, "GTF Loki (stealth)"))
13471 	{
13472 		idx = ship_info_lookup_sub("GTF Loki#stealth");
13473 		if (idx >= 0)
13474 			return idx;
13475 
13476 		return -1;
13477 	}
13478 
13479 	// get first part of new string
13480 	strcpy_s(temp1, token);
13481 	end_string_at_first_hash_symbol(temp1);
13482 
13483 	// get second part
13484 	strcpy_s(temp2, p + 1);
13485 
13486 	// found a hash
13487 	if (*p == '#')
13488 	{
13489 		if (strlen(token) > NAME_LENGTH-3) {
13490 			// If the below sprintf would exceed NAME_LENGTH (taking \0 terminator into account), give a warning and return.
13491 			Warning(LOCATION, "Token [%s] is too long to be parenthesized by ship_info_lookup()!\n", token);
13492 			return -1;
13493 		}
13494 		// assemble using parentheses
13495 		sprintf_safe(name, "%s (%s)", temp1, temp2);
13496 	}
13497 	// found a parenthesis
13498 	else if (*p == '(')
13499 	{
13500 		// chop off right parenthesis (it exists because otherwise the left wouldn't have been flagged)
13501 		char *p2 = strchr(temp2, ')');
13502 		*p2 = '\0';
13503 
13504 		// assemble using hash
13505 		sprintf_safe(name, "%s#%s", temp1, temp2);
13506 	}
13507 	// oops
13508 	else
13509 	{
13510 		Warning(LOCATION, "Unrecognized hash symbol.  Contact a programmer!");
13511 		return -1;
13512 	}
13513 
13514 	// finally check the new name
13515 	return ship_info_lookup_sub(name);
13516 }
13517 
13518 /**
13519  * Return the ship index of the ship with name *name.
13520  */
ship_name_lookup(const char * name,int inc_players)13521 int ship_name_lookup(const char *name, int inc_players)
13522 {
13523 	Assertion(name != nullptr, "NULL name passed to ship_name_lookup");
13524 
13525 	for (int i=0; i<MAX_SHIPS; i++){
13526 		if (Ships[i].objnum >= 0){
13527 			if (Objects[Ships[i].objnum].type == OBJ_SHIP || (Objects[Ships[i].objnum].type == OBJ_START && inc_players)){
13528 				if (!stricmp(name, Ships[i].ship_name)){
13529 					return i;
13530 				}
13531 			}
13532 		}
13533 	}
13534 
13535 	// couldn't find it
13536 	return -1;
13537 }
13538 
ship_type_name_lookup_sub(const char * name)13539 int ship_type_name_lookup_sub(const char *name)
13540 {
13541 	Assertion(name != nullptr, "NULL name passed to ship_type_name_lookup");
13542 
13543 	//Look through Ship_types array
13544 	for (size_t idx = 0; idx < Ship_types.size(); ++idx)
13545 		if (!stricmp(name, Ship_types[idx].name))
13546 			return (int)idx;
13547 
13548 	// couldn't find it
13549 	return -1;
13550 }
13551 
ship_type_name_lookup(const char * name)13552 int ship_type_name_lookup(const char *name)
13553 {
13554 	// try the normal lookup
13555 	auto idx = ship_type_name_lookup_sub(name);
13556 	if (idx >= 0)
13557 		return idx;
13558 
13559 	// Goober5000 - in retail FreeSpace, some ship classes were specified differently
13560 	// in ships.tbl and the ship type array; this patches those differences so that
13561 	// the ship type lookup will work properly
13562 	if (!stricmp(name, "sentrygun"))
13563 		name = "sentry gun";
13564 	else if (!stricmp(name, "escapepod"))
13565 		name = "escape pod";
13566 	else if (!stricmp(name, "repair_rearm"))
13567 		name = "support";
13568 	else if (!stricmp(name, "supercap"))
13569 		name = "super cap";
13570 	else if (!stricmp(name, "knossos"))
13571 		name = "knossos device";
13572 
13573 	// try it again
13574 	return ship_type_name_lookup_sub(name);
13575 }
13576 
13577 // Finds the world position of a subsystem.
13578 // Return true/false for subsystem found/not found.
13579 // Stuff vector *pos with absolute position.
13580 // subsysp is a pointer to the subsystem.
get_subsystem_pos(vec3d * pos,object * objp,ship_subsys * subsysp)13581 int get_subsystem_pos(vec3d* pos, object* objp, ship_subsys* subsysp)
13582 {
13583 	if (subsysp == NULL) {
13584 		*pos = objp->pos;
13585 		return 0;
13586 	}
13587 	Assertion(objp->type == OBJ_SHIP, "Only ships can have subsystems!");
13588 
13589 	model_subsystem* mss = subsysp->system_info;
13590 
13591 	if (mss->subobj_num == -1) {
13592 		// If it's a special point subsys, we can use its offset directly
13593 
13594 		vm_vec_unrotate(pos, &subsysp->system_info->pnt, &objp->orient);
13595 		vm_vec_add2(pos, &objp->pos);
13596 	} else {
13597 		// Submodel subsystems may require a more complicated calculation
13598 
13599 		auto pmi = model_get_instance(Ships[objp->instance].model_instance_num);
13600 		auto pm = model_get(pmi->model_num);
13601 		find_submodel_instance_world_point(pos, pm, pmi, mss->subobj_num, &objp->orient, &objp->pos);
13602 	}
13603 
13604 	return 1;
13605 }
13606 
13607 /**
13608  * Like ship_model_start but fills submodel instances instead of the submodels themselves
13609  */
ship_model_update_instance(object * objp)13610 void ship_model_update_instance(object *objp)
13611 {
13612 	model_subsystem	*psub;
13613 	ship		*shipp;
13614 	ship_subsys	*pss;
13615 
13616 	Assert(objp != NULL);
13617 	Assert(objp->instance >= 0);
13618 	Assert(objp->type == OBJ_SHIP);
13619 
13620 	shipp = &Ships[objp->instance];
13621 	polymodel_instance *pmi = model_get_instance(shipp->model_instance_num);
13622 	polymodel *pm = model_get(pmi->model_num);
13623 
13624 	// Handle subsystem rotations for this ship
13625 	for ( pss = GET_FIRST(&shipp->subsys_list); pss != END_OF_LIST(&shipp->subsys_list); pss = GET_NEXT(pss) ) {
13626 		psub = pss->system_info;
13627 		switch (psub->type) {
13628 			case SUBSYSTEM_RADAR:
13629 			case SUBSYSTEM_NAVIGATION:
13630 			case SUBSYSTEM_COMMUNICATION:
13631 			case SUBSYSTEM_UNKNOWN:
13632 			case SUBSYSTEM_ENGINE:
13633 			case SUBSYSTEM_SENSORS:
13634 			case SUBSYSTEM_WEAPONS:
13635 			case SUBSYSTEM_SOLAR:
13636 			case SUBSYSTEM_GAS_COLLECT:
13637 			case SUBSYSTEM_ACTIVATION:
13638 			case SUBSYSTEM_TURRET:
13639 				break;
13640 			default:
13641 				Error(LOCATION, "Illegal subsystem type.\n");
13642 		}
13643 
13644 		if ( psub->subobj_num >= 0 )	{
13645 			model_update_instance(pm, pmi, psub->subobj_num, pss->flags );
13646 		}
13647 
13648 		if ( (psub->subobj_num != psub->turret_gun_sobj) && (psub->turret_gun_sobj >= 0) )		{
13649 			model_update_instance(pm, pmi, psub->turret_gun_sobj, pss->flags );
13650 		}
13651 	}
13652 
13653 	// Handle intrinsic rotations for this ship
13654 	model_do_intrinsic_rotations(shipp->model_instance_num);
13655 }
13656 
13657 /**
13658  * Finds the number of crew points in a ship
13659  */
ship_find_num_crewpoints(object * objp)13660 int ship_find_num_crewpoints(object *objp)
13661 {
13662 	int n = 0;
13663 	model_subsystem	*psub;
13664 	ship		*shipp;
13665 	ship_subsys	*pss;
13666 
13667 	shipp = &Ships[objp->instance];
13668 
13669 	// Go through all subsystems and record the model angles for all
13670 	// the subsystems that need it.
13671 	for ( pss = GET_FIRST(&shipp->subsys_list); pss != END_OF_LIST(&shipp->subsys_list); pss = GET_NEXT(pss) ) {
13672 		psub = pss->system_info;
13673 		switch (psub->type) {
13674 		case SUBSYSTEM_TURRET:
13675 			if ( psub->flags[Model::Subsystem_Flags::Crewpoint] )
13676 				n++; // fall through
13677 
13678 		case SUBSYSTEM_RADAR:
13679 		case SUBSYSTEM_NAVIGATION:
13680 		case SUBSYSTEM_COMMUNICATION:
13681 		case SUBSYSTEM_UNKNOWN:
13682 		case SUBSYSTEM_ENGINE:
13683 		case SUBSYSTEM_GAS_COLLECT:
13684 		case SUBSYSTEM_ACTIVATION:
13685 			break;
13686 		default:
13687 			Error(LOCATION, "Illegal subsystem type.\n");
13688 		}
13689 	}
13690 	return n;
13691 }
13692 
13693 /**
13694  * Finds the number of turrets in a ship
13695  */
ship_find_num_turrets(object * objp)13696 int ship_find_num_turrets(object *objp)
13697 {
13698 	int n = 0;
13699 	model_subsystem	*psub;
13700 	ship		*shipp;
13701 	ship_subsys	*pss;
13702 
13703 	shipp = &Ships[objp->instance];
13704 
13705 	// Go through all subsystems and record the model angles for all
13706 	// the subsystems that need it.
13707 	for ( pss = GET_FIRST(&shipp->subsys_list); pss != END_OF_LIST(&shipp->subsys_list); pss = GET_NEXT(pss) ) {
13708 		psub = pss->system_info;
13709 		switch (psub->type) {
13710 		case SUBSYSTEM_TURRET:
13711 			n++; // drop through
13712 
13713 		case SUBSYSTEM_RADAR:
13714 		case SUBSYSTEM_NAVIGATION:
13715 		case SUBSYSTEM_COMMUNICATION:
13716 		case SUBSYSTEM_UNKNOWN:
13717 		case SUBSYSTEM_ENGINE:
13718 		case SUBSYSTEM_GAS_COLLECT:
13719 		case SUBSYSTEM_ACTIVATION:
13720 			break;
13721 		default:
13722 			Error(LOCATION, "Illegal subsystem type.\n");
13723 		}
13724 	}
13725 	return n;
13726 }
13727 
13728 //WMC
ship_set_eye(object * obj,int eye_index)13729 static void ship_set_eye( object *obj, int eye_index)
13730 {
13731 	if(obj->type != OBJ_SHIP)
13732 		return;
13733 
13734 	ship *shipp = &Ships[obj->instance];
13735 
13736 	if(eye_index < 0)
13737 	{
13738 		shipp->current_viewpoint = -1;
13739 		return;
13740 	}
13741 
13742 	ship_info *sip = &Ship_info[shipp->ship_info_index];
13743 	if(sip->model_num < 0)
13744 		return;
13745 
13746 	polymodel *pm = model_get(sip->model_num);
13747 
13748 	if(pm == NULL || eye_index > pm->n_view_positions)
13749 		return;
13750 
13751 	shipp->current_viewpoint = eye_index;
13752 }
13753 
13754 // calculates the eye position for this ship in the global reference frame.  Uses the
13755 // view_positions array in the model.  The 0th element is the normal viewing position.
13756 // the vector of the eye is returned in the parameter 'eye'.  The orientation of the
13757 // eye is returned in orient.  (NOTE: this is kind of bogus for now since non 0th element
13758 // eyes have no defined up vector)
ship_get_eye(vec3d * eye_pos,matrix * eye_orient,object * obj,bool do_slew,bool from_origin)13759 void ship_get_eye( vec3d *eye_pos, matrix *eye_orient, object *obj, bool do_slew , bool from_origin)
13760 {
13761 	Assertion(obj->type == OBJ_SHIP, "Only ships can have eye positions!");
13762 
13763 	ship *shipp = &Ships[obj->instance];
13764 	auto pmi = model_get_instance(shipp->model_instance_num);
13765 	auto pm = model_get(pmi->model_num);
13766 
13767 	// check to be sure that we have a view eye to look at.....spit out nasty debug message
13768 	if ( shipp->current_viewpoint < 0 || pm->n_view_positions == 0 || shipp->current_viewpoint > pm->n_view_positions) {
13769 		*eye_pos = obj->pos;
13770 		*eye_orient = obj->orient;
13771 		return;
13772 	}
13773 
13774 	// eye points are stored in an array -- the normal viewing position for a ship is the current_eye_index
13775 	// element.
13776 	eye *ep = &(pm->view_positions[shipp->current_viewpoint]);
13777 
13778 	if (ep->parent >= 0 && pm->submodel[ep->parent].can_move) {
13779 		find_submodel_instance_point_orient(eye_pos, eye_orient, pm, pmi, ep->parent, &ep->pnt, &vmd_identity_matrix);
13780 		vec3d tvec = *eye_pos;
13781 		vm_vec_unrotate(eye_pos, &tvec, &obj->orient);
13782 		vm_vec_add2(eye_pos, &obj->pos);
13783 
13784 		matrix tempmat = *eye_orient;
13785 		vm_matrix_x_matrix(eye_orient, &obj->orient, &tempmat);
13786 	} else {
13787 		model_instance_find_world_point( eye_pos, &ep->pnt, shipp->model_instance_num, ep->parent, &obj->orient, from_origin ? &vmd_zero_vector : &obj->pos );
13788 		*eye_orient = obj->orient;
13789 	}
13790 
13791 	//	Modify the orientation based on head orientation.
13792 	if ( Viewer_obj == obj && do_slew) {
13793 		// Add the cockpit leaning translation offset
13794 		vm_vec_add2(eye_pos,&leaning_position);
13795 		compute_slew_matrix(eye_orient, &Viewer_slew_angles);
13796 	}
13797 }
13798 
13799 // of attackers to make this decision.
13800 //
13801 // NOTE: This function takes into account how many ships are attacking a subsystem, and will
13802 //			prefer an ignored subsystem over a subsystem that is in line of sight, if the in-sight
13803 //			subsystem is attacked by more than MAX_SUBSYS_ATTACKERS
13804 // input:
13805 //				sp					=>		ship pointer to parent of subsystem
13806 //				subsys_type		=>		what kind of subsystem this is
13807 //				attacker_pos	=>		the world coords of the attacker of this subsystem
13808 //
13809 // returns: pointer to subsystem if one found, NULL otherwise
13810 #define MAX_SUBSYS_ATTACKERS 3
ship_get_best_subsys_to_attack(ship * sp,int subsys_type,vec3d * attacker_pos)13811 ship_subsys *ship_get_best_subsys_to_attack(ship *sp, int subsys_type, vec3d *attacker_pos)
13812 {
13813 	ship_subsys	*ss;
13814 	ship_subsys *best_in_sight_subsys, *lowest_attacker_subsys, *ss_return;
13815 	int			lowest_num_attackers, lowest_in_sight_attackers, num_attackers;
13816 	vec3d		gsubpos;
13817 	ship_obj		*sop;
13818 
13819 	lowest_in_sight_attackers = lowest_num_attackers = 1000;
13820 	ss_return = best_in_sight_subsys = lowest_attacker_subsys = NULL;
13821 
13822 	for (ss = GET_FIRST(&sp->subsys_list); ss != END_OF_LIST(&sp->subsys_list); ss = GET_NEXT(ss) ) {
13823 		if ( (ss->system_info->type == subsys_type) && (ss->current_hits > 0) ) {
13824 
13825 			// get world pos of subsystem
13826 			vm_vec_unrotate(&gsubpos, &ss->system_info->pnt, &Objects[sp->objnum].orient);
13827 			vm_vec_add2(&gsubpos, &Objects[sp->objnum].pos);
13828 
13829 			// now find the number of ships attacking this subsystem by iterating through the ships list,
13830 			// and checking if aip->targeted_subsys matches the subsystem we're checking
13831 			num_attackers = 0;
13832 			sop = GET_FIRST(&Ship_obj_list);
13833 			while(sop != END_OF_LIST(&Ship_obj_list)){
13834 				if ( Ai_info[Ships[Objects[sop->objnum].instance].ai_index].targeted_subsys == ss ) {
13835 					num_attackers++;
13836 				}
13837 				sop = GET_NEXT(sop);
13838 			}
13839 
13840 			if ( num_attackers < lowest_num_attackers ) {
13841 				lowest_num_attackers = num_attackers;
13842 				lowest_attacker_subsys = ss;
13843 			}
13844 
13845 			if ( ship_subsystem_in_sight(&Objects[sp->objnum], ss, attacker_pos, &gsubpos) ) {
13846 				if ( num_attackers < lowest_in_sight_attackers ) {
13847 					lowest_in_sight_attackers = num_attackers;
13848 					best_in_sight_subsys = ss;
13849 				}
13850 			}
13851 		}
13852 	}
13853 
13854 	if ( best_in_sight_subsys == NULL ) {
13855 		// no subsystems are in sight, so return the subsystem with the lowest # of attackers
13856 		ss_return =  lowest_attacker_subsys;
13857 	} else {
13858 		if ( lowest_in_sight_attackers > MAX_SUBSYS_ATTACKERS ) {
13859 			ss_return = lowest_attacker_subsys;
13860 		} else {
13861 			ss_return =  best_in_sight_subsys;
13862 		}
13863 	}
13864 
13865 	return ss_return;
13866 }
13867 
13868 // function to return a pointer to the 'nth' ship_subsys structure in a ship's linked list
13869 // of ship_subsys'.
13870 // attacker_pos	=>	world pos of attacker (default value NULL).  If value is non-NULL, try
13871 //							to select the best subsystem to attack of that type (using line-of-sight)
13872 //							and based on the number of ships already attacking the subsystem
ship_get_indexed_subsys(ship * sp,int index,vec3d * attacker_pos)13873 ship_subsys *ship_get_indexed_subsys( ship *sp, int index, vec3d *attacker_pos )
13874 {
13875 	int count;
13876 	ship_subsys *ss;
13877 
13878 	// first, special code to see if the index < 0.  If so, we are looking for one of several possible
13879 	// engines or one of several possible turrets.  If we enter this if statement, we will always return
13880 	// something.
13881 	if ( index < 0 ) {
13882 		int subsys_type;
13883 
13884 		subsys_type = -index;
13885 		if ( sp->subsys_info[subsys_type].aggregate_current_hits <= 0.0f )		// if there are no hits, no subsystem to attack.
13886 			return NULL;
13887 
13888 		if ( attacker_pos != NULL ) {
13889 			ss = ship_get_best_subsys_to_attack(sp, subsys_type, attacker_pos);
13890 			return ss;
13891 		} else {
13892 			// next, scan the list of subsystems and search for the first subsystem of the particular
13893 			// type which has > 0 hits remaining.
13894 			for (ss = GET_FIRST(&sp->subsys_list); ss != END_OF_LIST(&sp->subsys_list); ss = GET_NEXT(ss) ) {
13895 				if ( (ss->system_info->type == subsys_type) && (ss->current_hits > 0) )
13896 					return ss;
13897 			}
13898 		}
13899 
13900 		// maybe we shouldn't get here, but with possible floating point rounding, I suppose we could
13901 		Warning(LOCATION, "Unable to get a nonspecific subsystem of index %d on ship %s!", index, sp->ship_name);
13902 		return NULL;
13903 	}
13904 
13905 
13906 	count = 0;
13907 	ss = GET_FIRST(&sp->subsys_list);
13908 	while ( ss != END_OF_LIST( &sp->subsys_list ) ) {
13909 		if ( count == index )
13910 			return ss;
13911 		count++;
13912 		ss = GET_NEXT( ss );
13913 	}
13914 
13915 	// get allender -- turret ref didn't fixup correctly!!!!
13916 	Warning(LOCATION, "In ship_get_indexed_subsys, unable to get a subsystem of index %d on ship %s, due to a broken subsystem reference!  This is most likely due to a table/model mismatch.", index, sp->ship_name);
13917 	return NULL;
13918 }
13919 
13920 /**
13921  * Given a pointer to a subsystem and an associated object, return the index.
13922  */
ship_get_index_from_subsys(ship_subsys * ssp,int objnum)13923 int ship_get_index_from_subsys(ship_subsys *ssp, int objnum)
13924 {
13925 	if (ssp == NULL)
13926 		return -1;
13927 	else {
13928 		int	count;
13929 		ship	*shipp;
13930 		ship_subsys	*ss;
13931 
13932 		Assert(objnum >= 0);
13933 		Assert(Objects[objnum].instance >= 0);
13934 
13935 		shipp = &Ships[Objects[objnum].instance];
13936 
13937 		count = 0;
13938 		ss = GET_FIRST(&shipp->subsys_list);
13939 		while ( ss != END_OF_LIST( &shipp->subsys_list ) ) {
13940 			if ( ss == ssp)
13941 				return count;
13942 			count++;
13943 			ss = GET_NEXT( ss );
13944 		}
13945 
13946 		return -1;
13947 	}
13948 }
13949 
13950 /**
13951  * Returns the index number of the ship_subsys parameter
13952  */
ship_get_subsys_index(ship * sp,const char * ss_name)13953 int ship_get_subsys_index(ship *sp, const char* ss_name)
13954 {
13955 	int count;
13956 	ship_subsys *ss;
13957 
13958 	count = 0;
13959 	ss = GET_FIRST(&sp->subsys_list);
13960 	while ( ss != END_OF_LIST( &sp->subsys_list ) ) {
13961 		if ( !subsystem_stricmp(ss->system_info->subobj_name, ss_name) )
13962 			return count;
13963 		count++;
13964 		ss = GET_NEXT( ss );
13965 	}
13966 
13967 	return -1;
13968 }
13969 
13970 /**
13971 * Returns the index number of the ship_subsys parameter
13972 */
ship_get_subsys_index(ship * shipp,ship_subsys * subsys)13973 int ship_get_subsys_index(ship *shipp, ship_subsys *subsys)
13974 {
13975 	int count;
13976 	ship_subsys *ss;
13977 
13978 	count = 0;
13979 	ss = GET_FIRST(&shipp->subsys_list);
13980 	while (ss != END_OF_LIST(&shipp->subsys_list)) {
13981 		if (ss == subsys)
13982 			return count;
13983 		count++;
13984 		ss = GET_NEXT(ss);
13985 	}
13986 
13987 	return -1;
13988 }
13989 
13990 // routine to return the strength of a subsystem.  We keep a total hit tally for all subsystems
13991 // which are similar (i.e. a total for all engines).  These routines will return a number between
13992 // 0.0 and 1.0 which is the relative combined strength of the given subsystem type.  The number
13993 // calculated for the engines is slightly different.  Once an engine reaches < 15% of its hits, its
13994 // output drops to that %.  A dead engine has no output.
ship_get_subsystem_strength(ship * shipp,int type,bool skip_dying_check)13995 float ship_get_subsystem_strength( ship *shipp, int type, bool skip_dying_check )
13996 {
13997 	float strength;
13998 	ship_subsys *ssp;
13999 
14000 	Assert ( (type >= 0) && (type < SUBSYSTEM_MAX) );
14001 
14002 	//	For a dying ship, all subsystem strengths are zero.
14003 	if (Objects[shipp->objnum].hull_strength <= 0.0f && !skip_dying_check)
14004 		return 0.0f;
14005 
14006 	// short circuit 1
14007 	if (shipp->subsys_info[type].aggregate_max_hits <= 0.0f)
14008 		return 1.0f;
14009 
14010 	// short circuit 0
14011 	if (shipp->subsys_info[type].aggregate_current_hits <= 0.0f)
14012 		return 0.0f;
14013 
14014 	strength = shipp->subsys_info[type].aggregate_current_hits / shipp->subsys_info[type].aggregate_max_hits;
14015 	Assert( strength != 0.0f );
14016 
14017 	if ( (type == SUBSYSTEM_ENGINE) && (strength < 1.0f) ) {
14018 		float percent;
14019 
14020 		percent = 0.0f;
14021 		ssp = GET_FIRST(&shipp->subsys_list);
14022 		while ( ssp != END_OF_LIST( &shipp->subsys_list ) ) {
14023 
14024 			if ( ssp->system_info->type == SUBSYSTEM_ENGINE ) {
14025 				float ratio;
14026 
14027 				ratio = ssp->current_hits / ssp->max_hits;
14028 				if ( ratio < ENGINE_MIN_STR )
14029 					ratio = ENGINE_MIN_STR;
14030 
14031 				percent += ratio;
14032 			}
14033 			ssp = GET_NEXT( ssp );
14034 		}
14035 		strength = percent / (float)shipp->subsys_info[type].type_count;
14036 	}
14037 
14038 	return strength;
14039 }
14040 
14041 /**
14042  * Set the strength of a subsystem on a given ship.
14043  *
14044  * The strength passed as a parameter is between 0.0 and 1.0
14045  *
14046  * NOTE: this function was made to be called by the debug function dcf_set_subsys().  If
14047  * you want to use this, be sure that you test it for all cases.
14048  */
ship_set_subsystem_strength(ship * shipp,int type,float strength)14049 void ship_set_subsystem_strength( ship *shipp, int type, float strength )
14050 {
14051 	ship_subsys *ssp;
14052 
14053 	Assert ( (type >= 0) && (type < SUBSYSTEM_MAX) );
14054 	CLAMP(strength, 0.0f, 1.0f);
14055 
14056 	ssp = GET_FIRST(&shipp->subsys_list);
14057 	while ( ssp != END_OF_LIST( &shipp->subsys_list ) ) {
14058 
14059 		if ( (ssp->system_info->type == type) && !(ssp->flags[Ship::Subsystem_Flags::No_aggregate]) ) {
14060 			ssp->current_hits = strength * ssp->max_hits;
14061 
14062 			// maybe blow up subsys
14063 			if (ssp->current_hits <= 0) {
14064 				do_subobj_destroyed_stuff(shipp, ssp, nullptr);
14065 			}
14066 		}
14067 		ssp = GET_NEXT( ssp );
14068 	}
14069 
14070 	// fix up the overall ship subsys status
14071 	ship_recalc_subsys_strength(shipp);
14072 }
14073 
14074 
14075 
14076 
14077 /**
14078  * Calculates approximate time in seconds it would take to rearm and repair object.
14079  */
ship_calculate_rearm_duration(object * objp)14080 float ship_calculate_rearm_duration( object *objp )
14081 {
14082 	ship* sp;
14083 	ship_info* sip;
14084 	ship_subsys* ssp;
14085 	ship_weapon* swp;
14086 	weapon_info* wip;
14087 
14088 	float shield_rep_time = 0;
14089 	float subsys_rep_time = 0;
14090 	float hull_rep_time = 0;
14091 	float prim_rearm_time = 0;
14092 	float sec_rearm_time = 0;
14093 
14094 	float max_hull_repair;
14095 	float max_subsys_repair;
14096 
14097 	int i;
14098 	int num_reloads;
14099 
14100 	bool found_first_empty;
14101 
14102 	Assert(objp->type == OBJ_SHIP);
14103 
14104 	sp = &Ships[objp->instance];
14105 	swp = &sp->weapons;
14106 	sip = &Ship_info[sp->ship_info_index];
14107 
14108 	//find out time to repair shields
14109 	float max_shields = shield_get_max_strength(objp);
14110 	if(sip->sup_shield_repair_rate > 0.0f && max_shields > 0.0f)
14111 		shield_rep_time = (max_shields - shield_get_strength(objp)) / (max_shields * sip->sup_shield_repair_rate);
14112 
14113 	max_hull_repair = sp->ship_max_hull_strength * (The_mission.support_ships.max_hull_repair_val * 0.01f);
14114 	if ((The_mission.flags[Mission::Mission_Flags::Support_repairs_hull]) && (max_hull_repair > objp->hull_strength) && (sip->sup_hull_repair_rate > 0.0f))
14115 	{
14116 		hull_rep_time = (max_hull_repair - objp->hull_strength) / (sp->ship_max_hull_strength * sip->sup_hull_repair_rate);
14117 	}
14118 
14119 	//caluclate subsystem repair time
14120 	ssp = GET_FIRST(&sp->subsys_list);
14121 	while (ssp != END_OF_LIST(&sp->subsys_list))
14122 	{
14123 		max_subsys_repair = ssp->max_hits * (The_mission.support_ships.max_subsys_repair_val * 0.01f);
14124 		if ((max_subsys_repair > ssp->current_hits) && (sip->sup_subsys_repair_rate > 0.0f))
14125 		{
14126 			subsys_rep_time += (max_subsys_repair - ssp->current_hits) / (ssp->max_hits * sip->sup_subsys_repair_rate);
14127 		}
14128 
14129 		ssp = GET_NEXT( ssp );
14130 	}
14131 
14132 	//now do the primary rearm time
14133 	found_first_empty = false;
14134 	for (i = 0; i < swp->num_primary_banks; i++)
14135 	{
14136 		wip = &Weapon_info[swp->primary_bank_weapons[i]];
14137 		if (wip->wi_flags[Weapon::Info_Flags::Ballistic])
14138 		{
14139 			//check how many full reloads we need
14140 			num_reloads = (swp->primary_bank_start_ammo[i] - swp->primary_bank_ammo[i]) / wip->reloaded_per_batch;
14141 
14142 			//take into account a fractional reload
14143 			if ((swp->primary_bank_start_ammo[i] - swp->primary_bank_ammo[i]) % wip->reloaded_per_batch != 0)
14144 			{
14145 				num_reloads++;
14146 			}
14147 
14148 			//don't factor in the time it takes for the first reload, since that is loaded instantly
14149 			num_reloads--;
14150 
14151 			if (num_reloads < 0) continue;
14152 
14153 			if (!found_first_empty && (swp->primary_bank_start_ammo[i] - swp->primary_bank_ammo[i]))
14154 			{
14155 				found_first_empty = true;
14156 				prim_rearm_time += gamesnd_get_max_duration(gamesnd_get_game_sound(GameSounds::MISSILE_START_LOAD)) / 1000.0f;
14157 			}
14158 
14159 			prim_rearm_time += num_reloads * wip->rearm_rate;
14160 		}
14161 	}
14162 
14163 	//and on to secondary rearm time
14164 	found_first_empty = false;
14165 	for (i = 0; i < swp->num_secondary_banks; i++)
14166 	{
14167 			wip = &Weapon_info[swp->secondary_bank_weapons[i]];
14168 
14169 			//check how many full reloads we need
14170 		    num_reloads = (swp->secondary_bank_start_ammo[i] - swp->secondary_bank_ammo[i]) / wip->reloaded_per_batch;
14171 
14172 			//take into account a fractional reload
14173 		    if ((swp->secondary_bank_start_ammo[i] - swp->secondary_bank_ammo[i]) % wip->reloaded_per_batch != 0)
14174 			{
14175 				num_reloads++;
14176 			}
14177 
14178 			//don't factor in the time it takes for the first reload, since that is loaded instantly
14179 			num_reloads--;
14180 
14181 			if (num_reloads < 0) continue;
14182 
14183 			if (!found_first_empty && (swp->secondary_bank_start_ammo[i] - swp->secondary_bank_ammo[i]))
14184 			{
14185 				found_first_empty = true;
14186 				sec_rearm_time += gamesnd_get_max_duration(gamesnd_get_game_sound(GameSounds::MISSILE_START_LOAD)) / 1000.0f;
14187 			}
14188 
14189 			sec_rearm_time += num_reloads * wip->rearm_rate;
14190 	}
14191 
14192 	//sum them up and you've got an estimated rearm time.
14193 	//add 1.2 to compensate for release delay
14194 	return shield_rep_time + hull_rep_time + subsys_rep_time + prim_rearm_time + sec_rearm_time + 1.2f;
14195 }
14196 
14197 
14198 
14199 // ==================================================================================
14200 // ship_do_rearm_frame()
14201 //
14202 // function to rearm a ship.  This function gets called from the ai code ai_do_rearm_frame (or
14203 // some function of a similar name).  Returns 1 when ship is fully repaired and rearmed, 0 otherwise
14204 //
ship_do_rearm_frame(object * objp,float frametime)14205 int ship_do_rearm_frame( object *objp, float frametime )
14206 {
14207 	int			i, banks_full, primary_banks_full, subsys_type, last_ballistic_idx = -1;
14208 	float			shield_str, max_shield_str = 0.0f, repair_delta, repair_allocated, max_hull_repair = 0, max_subsys_repair;
14209 	ship			*shipp;
14210 	ship_weapon	*swp;
14211 	ship_info	*sip;
14212 	ship_subsys	*ssp;
14213 	ai_info		*aip;
14214 
14215 	shipp = &Ships[objp->instance];
14216 	swp = &shipp->weapons;
14217 	sip = &Ship_info[shipp->ship_info_index];
14218 	aip = &Ai_info[shipp->ai_index];
14219 
14220 	// AL 10-31-97: Add missing primary weapons to the ship.  This is required since designers
14221 	//              want to have ships that start with no primaries, but can get them through
14222 	//					 rearm/repair
14223 	// plieblang - skip this if the ai profile flag is set
14224 	if ( swp->num_primary_banks < sip->num_primary_banks && !aip->ai_profile_flags[AI::Profile_Flags::Support_dont_add_primaries] ) {
14225 		for ( i = swp->num_primary_banks; i < sip->num_primary_banks; i++ ) {
14226 			swp->primary_bank_weapons[i] = sip->primary_bank_weapons[i];
14227 		}
14228 		swp->num_primary_banks = sip->num_primary_banks;
14229 	}
14230 
14231 	// AL 12-30-97: Repair broken warp drive
14232 	if ( shipp->flags[Ship_Flags::Warp_broken] ) {
14233 		// TODO: maybe do something here like informing player warp is fixed?
14234 		// like this? -- Goober5000
14235 		HUD_sourced_printf(HUD_SOURCE_HIDDEN, "%s", XSTR( "Subspace drive repaired.", 1635));
14236 		shipp->flags.remove(Ship_Flags::Warp_broken);
14237 	}
14238 
14239 	// AL 1-16-98: Replenish countermeasures
14240 	shipp->cmeasure_count = sip->cmeasure_max;
14241 
14242 	// Do shield repair here
14243 	if ( !(objp->flags[Object::Object_Flags::No_shields]) )
14244 	{
14245 		shield_str = shield_get_strength(objp);
14246 		max_shield_str = shield_get_max_strength(objp);
14247 		if ( shield_str < (max_shield_str) ) {
14248 			if ( objp == Player_obj ) {
14249 				player_maybe_start_repair_sound();
14250 			}
14251 			shield_str += shipp->ship_max_shield_strength * frametime * sip->sup_shield_repair_rate; // repair rate is unaffected by $Max Shield Recharge
14252 			if ( shield_str > max_shield_str ) {
14253 				 shield_str = max_shield_str;
14254 			}
14255 			shield_set_strength(objp, shield_str);
14256 		}
14257 	}
14258 
14259 	// Repair the ship integrity (subsystems + hull).  This works by applying the repair points
14260 	// to the subsystems.  Ships integrity is stored in objp->hull_strength, so that always is
14261 	// incremented by repair_allocated
14262 
14263 	//	AL 11-24-97: remove increase to hull integrity
14264 	//	Comments removed by PhReAk; Note that this is toggled on/off with a mission flag
14265 	if (The_mission.flags[Mission::Mission_Flags::Support_repairs_hull])
14266 	{
14267 		//Figure out how much of the ship's hull we can repair
14268 		//Don't "reverse-repair" the hull if it's already above the max repair threshold
14269 		max_hull_repair = shipp->ship_max_hull_strength * (The_mission.support_ships.max_hull_repair_val * 0.01f);
14270 		if (objp->hull_strength < max_hull_repair)
14271 		{
14272 			objp->hull_strength += shipp->ship_max_hull_strength * frametime * sip->sup_hull_repair_rate;
14273 			if (objp->hull_strength > max_hull_repair)
14274 				objp->hull_strength = max_hull_repair;
14275 		}
14276 	}
14277 
14278 	// figure out repairs for subsystems
14279 	repair_allocated = shipp->ship_max_hull_strength * frametime * sip->sup_subsys_repair_rate;
14280 
14281 	// check the subsystems of the ship.
14282 	bool subsys_all_ok = true;
14283 	ssp = GET_FIRST(&shipp->subsys_list);
14284 	while ( ssp != END_OF_LIST( &shipp->subsys_list ) ) {
14285 		//Figure out how much we *can* repair the current subsystem -C
14286 		max_subsys_repair = ssp->max_hits * (The_mission.support_ships.max_subsys_repair_val * 0.01f);
14287 
14288 		if ( ssp->current_hits < max_subsys_repair && repair_allocated > 0 ) {
14289 			subsys_all_ok = false;
14290 			subsys_type = ssp->system_info->type;
14291 
14292 			if ( objp == Player_obj ) {
14293 				player_maybe_start_repair_sound();
14294 			}
14295 
14296 			repair_delta = max_subsys_repair - ssp->current_hits;
14297 			if ( repair_delta > repair_allocated ) {
14298 				repair_delta = repair_allocated;
14299 			}
14300 			repair_allocated -= repair_delta;
14301 			Assert(repair_allocated >= 0.0f);
14302 
14303 			// add repair to current strength of single subsystem
14304 			ssp->current_hits += repair_delta;
14305 			if ( ssp->current_hits > max_subsys_repair ) {
14306 				ssp->current_hits = max_subsys_repair;
14307 			}
14308 
14309 			// add repair to aggregate strength of subsystems of that type
14310 			if (!(ssp->flags[Ship::Subsystem_Flags::No_aggregate])) {
14311 				shipp->subsys_info[subsys_type].aggregate_current_hits += repair_delta;
14312 				if ( shipp->subsys_info[subsys_type].aggregate_current_hits > shipp->subsys_info[subsys_type].aggregate_max_hits )
14313 					shipp->subsys_info[subsys_type].aggregate_current_hits = shipp->subsys_info[subsys_type].aggregate_max_hits;
14314 			}
14315 
14316 			// check to see if this subsystem was totally non functional before -- if so, then
14317 			// reset the flags
14318 			if ( (ssp->system_info->type == SUBSYSTEM_ENGINE) && (shipp->flags[Ship_Flags::Disabled]) ) {
14319 				shipp->flags.remove(Ship_Flags::Disabled);
14320 				ship_reset_disabled_physics(objp, shipp->ship_info_index);
14321 			}
14322 			break;
14323 		}
14324 		ssp = GET_NEXT( ssp );
14325 	}
14326 
14327 	// now deal with rearming the player.  All secondary weapons have a certain rate at which
14328 	// they can be rearmed.  We can rearm multiple banks at once.
14329 	banks_full = 0;
14330 	primary_banks_full = 0;
14331 	if ( subsys_all_ok )
14332 	{
14333 		for (i = 0; i < swp->num_secondary_banks; i++ )
14334 		{
14335 			// Actual loading of missiles is preceded by a sound effect which is the missile
14336 			// loading equipment moving into place
14337 			if ( aip->rearm_first_missile == TRUE )
14338 			{
14339 				swp->secondary_bank_rearm_time[i] = timestamp((int)gamesnd_get_max_duration(gamesnd_get_game_sound(GameSounds::MISSILE_START_LOAD)));
14340 			}
14341 
14342 			if ( swp->secondary_bank_ammo[i] < swp->secondary_bank_start_ammo[i] )
14343 			{
14344 				float rearm_time;
14345 
14346 				if ( objp == Player_obj )
14347 				{
14348 					hud_gauge_popup_start(HUD_WEAPONS_GAUGE);
14349 				}
14350 
14351 				if ( timestamp_elapsed(swp->secondary_bank_rearm_time[i]) )
14352 				{
14353 					rearm_time = Weapon_info[swp->secondary_bank_weapons[i]].rearm_rate;
14354 					swp->secondary_bank_rearm_time[i] = timestamp((int)(rearm_time * 1000.0f));
14355 
14356 					snd_play_3d( gamesnd_get_game_sound(GameSounds::MISSILE_LOAD), &objp->pos, &View_position );
14357 					if (objp == Player_obj)
14358 						joy_ff_play_reload_effect();
14359 
14360 					swp->secondary_bank_ammo[i] += Weapon_info[swp->secondary_bank_weapons[i]].reloaded_per_batch;
14361 					if ( swp->secondary_bank_ammo[i] > swp->secondary_bank_start_ammo[i] )
14362 					{
14363 						swp->secondary_bank_ammo[i] = swp->secondary_bank_start_ammo[i];
14364 					}
14365 				}
14366 				else
14367 				{
14368 				}
14369 			}
14370 			else
14371 			{
14372 				banks_full++;
14373 			}
14374 
14375 			if ((aip->rearm_first_missile == TRUE) && (i == swp->num_secondary_banks - 1))
14376 			{
14377 				if ((banks_full != swp->num_secondary_banks))
14378 					snd_play_3d( gamesnd_get_game_sound(GameSounds::MISSILE_START_LOAD), &objp->pos, &View_position );
14379 
14380 				aip->rearm_first_missile = FALSE;
14381 			}
14382 		}	// end for
14383 
14384 		// rearm ballistic primaries - Goober5000
14385 		if ( aip->rearm_first_ballistic_primary == TRUE)
14386 		{
14387 			for (i = 0; i < swp->num_primary_banks; i++ )
14388 			{
14389 				if ( Weapon_info[swp->primary_bank_weapons[i]].wi_flags[Weapon::Info_Flags::Ballistic] )
14390 					last_ballistic_idx = i;
14391 			}
14392 		}
14393 
14394 		for (i = 0; i < swp->num_primary_banks; i++ )
14395 		{
14396 			if (Weapon_info[swp->primary_bank_weapons[i]].wi_flags[Weapon::Info_Flags::Ballistic])
14397 			{
14398 				// Actual loading of bullets is preceded by a sound effect which is the bullet
14399 				// loading equipment moving into place
14400 				if ( aip->rearm_first_ballistic_primary == TRUE )
14401 				{
14402 					// Goober5000
14403 					gamesnd_id sound_index;
14404 					if (gamesnd_game_sound_valid(GameSounds::BALLISTIC_START_LOAD))
14405 						sound_index = GameSounds::BALLISTIC_START_LOAD;
14406 					else
14407 						sound_index = GameSounds::MISSILE_START_LOAD;
14408 
14409 					if (sound_index.isValid())
14410 						swp->primary_bank_rearm_time[i] = timestamp((int)gamesnd_get_max_duration(gamesnd_get_game_sound(sound_index)));
14411 					else
14412 						swp->primary_bank_rearm_time[i] = timestamp(0);
14413 				}
14414 
14415 				if ( swp->primary_bank_ammo[i] < swp->primary_bank_start_ammo[i] )
14416 				{
14417 					float rearm_time;
14418 
14419 					if ( objp == Player_obj )
14420 					{
14421 						hud_gauge_popup_start(HUD_WEAPONS_GAUGE);
14422 					}
14423 
14424 					if ( timestamp_elapsed(swp->primary_bank_rearm_time[i]) )
14425 					{
14426 						rearm_time = Weapon_info[swp->primary_bank_weapons[i]].rearm_rate;
14427 						swp->primary_bank_rearm_time[i] = timestamp( (int)(rearm_time * 1000.f) );
14428 
14429 						// Goober5000
14430 						gamesnd_id sound_index;
14431 						if (gamesnd_game_sound_valid(GameSounds::BALLISTIC_LOAD))
14432 							sound_index = GameSounds::BALLISTIC_LOAD;
14433 						else
14434 							sound_index = GameSounds::MISSILE_LOAD;
14435 
14436 						if (sound_index.isValid())
14437 							snd_play_3d( gamesnd_get_game_sound(sound_index), &objp->pos, &View_position );
14438 
14439 						swp->primary_bank_ammo[i] += Weapon_info[swp->primary_bank_weapons[i]].reloaded_per_batch;
14440 						if ( swp->primary_bank_ammo[i] > swp->primary_bank_start_ammo[i] )
14441 						{
14442 							swp->primary_bank_ammo[i] = swp->primary_bank_start_ammo[i];
14443 						}
14444 					}
14445 				}
14446 				else
14447 				{
14448 					primary_banks_full++;
14449 				}
14450 			}
14451 			// if the bank is not a ballistic
14452 			else
14453 			{
14454 				primary_banks_full++;
14455 			}
14456 
14457 			if ((aip->rearm_first_ballistic_primary == TRUE) && (i == last_ballistic_idx))
14458 			{
14459 				if (primary_banks_full != swp->num_primary_banks)
14460 				{
14461 					// Goober5000
14462 					gamesnd_id sound_index;
14463 					if (gamesnd_game_sound_valid(GameSounds::BALLISTIC_START_LOAD))
14464 						sound_index = GameSounds::BALLISTIC_START_LOAD;
14465 					else
14466 						sound_index = GameSounds::MISSILE_START_LOAD;
14467 
14468 					if (sound_index.isValid())
14469 						snd_play_3d( gamesnd_get_game_sound(sound_index), &objp->pos, &View_position );
14470 				}
14471 
14472 				aip->rearm_first_ballistic_primary = FALSE;
14473 			}
14474 		}	// end for
14475 	} // end if (subsys_all_ok)
14476 
14477 	if ( banks_full == swp->num_secondary_banks )
14478 	{
14479 		aip->rearm_first_missile = TRUE;
14480 	}
14481 
14482 	if ( primary_banks_full == swp->num_primary_banks )
14483 	{
14484 		aip->rearm_first_ballistic_primary = TRUE;
14485 	}
14486 
14487 	int shields_full = false;
14488 	if ( (objp->flags[Object::Object_Flags::No_shields]) ) {
14489 		shields_full = true;
14490 	} else {
14491 		if ( shield_get_strength(objp) >= max_shield_str )
14492 			shields_full = true;
14493 		if (sip->sup_shield_repair_rate == 0.0f)
14494 			shields_full = true;
14495 	}
14496 
14497 	bool hull_ok = false;
14498 	if (!(The_mission.flags[Mission::Mission_Flags::Support_repairs_hull])) {
14499 		hull_ok = true;
14500 	} else {
14501 		if (objp->hull_strength >= max_hull_repair)
14502 			hull_ok = true;
14503 		if (sip->sup_hull_repair_rate == 0.0f)
14504 			hull_ok = true;
14505 	}
14506 
14507 	// return 1 if at end of subsystem list, hull damage at 0, and shields full and all secondary banks full.
14508 	if ( (subsys_all_ok && shields_full && (The_mission.flags[Mission::Mission_Flags::Support_repairs_hull]) && hull_ok ) || (subsys_all_ok && shields_full && !(The_mission.flags[Mission::Mission_Flags::Support_repairs_hull]) ) )
14509 	{
14510 		if ( objp == Player_obj ) {
14511 			player_stop_repair_sound();
14512 		}
14513 
14514 		if (!aip->rearm_release_delay)
14515 			aip->rearm_release_delay = timestamp(1200);
14516 
14517 		// check both primary and secondary banks are full
14518 		if ( (banks_full == swp->num_secondary_banks) && (primary_banks_full == swp->num_primary_banks) )
14519 		{
14520 			if ( timestamp_elapsed(aip->rearm_release_delay) )
14521 				return 1;
14522 		}
14523 		else
14524 		{
14525 			aip->rearm_release_delay = timestamp(1200);
14526 		}
14527 	}
14528 
14529 	if (objp == Player_obj)
14530 		Player_rearm_eta -= frametime;
14531 
14532 	return 0;
14533 }
14534 
14535 // Goober5000 - modified the logic to clarify the various states
14536 // function which is used to find a repair ship to repair requester_obj.  the way repair ships will work is:
14537 // if no ships in the mission at all, return 0
14538 // if a ship can immediately satisfy a repair request, return 1 and fill in the pointer
14539 // if no ships can satisfy a request, but we haven't reached either the concurrent or cumulative limit, return 2
14540 // if no ships can satisfy a request, and we've reached the limits, but a request can be queued, return 3 and fill in the pointer
14541 // if no ships can satisfy a request, we've reached the limits, and we can't queue anything, we're out of luck -- return 4
ship_find_repair_ship(object * requester_obj,object ** ship_we_found)14542 int ship_find_repair_ship( object *requester_obj, object **ship_we_found )
14543 {
14544 	object *objp;
14545 	int num_support_ships = 0;
14546 	float min_dist = -1.0f;
14547 	object *nearest_support_ship = NULL;
14548 	float min_time_till_available = -1.0f;
14549 	object *soonest_available_support_ship = NULL;
14550 
14551 	Assertion(requester_obj->type == OBJ_SHIP, "requester_obj not a ship. Has type of %08x", requester_obj->type);
14552 	Assertion((requester_obj->instance >= 0) && (requester_obj->instance < MAX_SHIPS),
14553 		"requester_obj does not have a valid pointer to a ship. Pointer is %d, which is smaller than 0 or bigger than %d",
14554 		requester_obj->instance, MAX_SHIPS);
14555 
14556 	ship *requester_ship = &Ships[requester_obj->instance];
14557 	for ( objp = GET_FIRST(&obj_used_list); objp !=END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) )
14558 	{
14559 		if ((objp->type == OBJ_SHIP) && !(objp->flags[Object::Object_Flags::Should_be_dead]))
14560 		{
14561 			ship *shipp;
14562 			ship_info *sip;
14563 			float dist;
14564 
14565 			Assertion((objp->instance >= 0) && (objp->instance < MAX_SHIPS),
14566 				"objp does not have a valid pointer to a ship. Pointer is %d, which is smaller than 0 or bigger than %d",
14567 				objp->instance, MAX_SHIPS);
14568 
14569 			shipp = &Ships[objp->instance];
14570 
14571 			if ( shipp->team != requester_ship->team ) {
14572 				continue;
14573 			}
14574 
14575 			Assertion((shipp->ship_info_index >= 0) && (shipp->ship_info_index < ship_info_size()),
14576 				"Ship '%s' does not have a valid pointer to a ship class. Pointer is %d, which is smaller than 0 or bigger than %d",
14577 				shipp->ship_name, shipp->ship_info_index, ship_info_size());
14578 
14579 			sip = &Ship_info[shipp->ship_info_index];
14580 
14581 			if ( !(sip->flags[Ship::Info_Flags::Support]) ) {
14582 				continue;
14583 			}
14584 
14585 			// tally how many support ships actually exist
14586 			num_support_ships++;
14587 
14588 			// don't deal with dying or departing support ships
14589 			if ( shipp->is_dying_or_departing() ) {
14590 				continue;
14591 			}
14592 
14593 			// Ship has been ordered to warpout but has not had a chance to process the order.
14594 			Assertion( (shipp->ai_index >= 0) && (shipp->ai_index < MAX_AI_INFO),
14595 				"Ship '%s' doesn't have a valid ai pointer. Pointer is %d, which is smaller than 0 or larger than %d",
14596 				shipp->ship_name, shipp->ai_index, MAX_AI_INFO);
14597 			ai_info* aip = &(Ai_info[shipp->ai_index]);
14598 			if ( ai_find_goal_index( aip->goals, AI_GOAL_WARP ) != -1 ) {
14599 				continue;
14600 			}
14601 
14602 			dist = vm_vec_dist_quick(&objp->pos, &requester_obj->pos);
14603 
14604 			if (aip->ai_flags[AI::AI_Flags::Repairing, AI::AI_Flags::Awaiting_repair, AI::AI_Flags::Being_repaired])
14605 			{
14606 				// support ship is already busy, track the one that will be
14607 				// done soonest by estimating how many seconds it will take for the support ship
14608 				// to reach the requester.
14609 				// The estimate is calculated by calculating how many seconds it will take the
14610 				// support ship to travel from its current location to the requester at max velocity
14611 				// We assume that every leg of the support ships journey will take the amount of time
14612 				// for the support ship to fly from its current location to the requester.  This is
14613 				// a bit hacky but it penalizes further away support ships, so a futher support ship
14614 				// will only be called if the closer ones are really busy.  This is just a craps shoot
14615 				// anyway because everything is moving around.
14616 				float howlong = 0;
14617 				for( int i = 0; i < MAX_AI_GOALS; i++ ) {
14618 					if ( aip->goals[i].ai_mode == AI_GOAL_REARM_REPAIR ) {
14619 						howlong += dist * objp->phys_info.max_vel.xyz.z;
14620 					}
14621 				}
14622 				if ( min_time_till_available < 0.0f || howlong < min_time_till_available ) {
14623 					min_time_till_available = howlong;
14624 					soonest_available_support_ship = objp;
14625 				}
14626 			}
14627 			else
14628 			{
14629 				// support ship not already busy, find the closest
14630 				if ( min_dist < 0.0f || dist < min_dist )
14631 				{
14632 					min_dist = dist;
14633 					nearest_support_ship = objp;
14634 				}
14635 			}
14636 		}
14637 	}
14638 
14639 	// no ships present?
14640 	// (be advised we may have an Arriving_support_ship in this case)
14641 	if (num_support_ships == 0) {
14642 		return 0;
14643 	}
14644 	// ship available?
14645 	else if (nearest_support_ship != NULL) {
14646 		// the nearest non-busy support ship is to service request
14647 		if (ship_we_found != NULL)
14648 			*ship_we_found = nearest_support_ship;
14649 		return 1;
14650 	}
14651 	// no ships available; are we below the limits?  (can we bring another ship in? -- and btw an Arriving_support_ship counts as being able to bring one in)
14652 	else if ((num_support_ships < The_mission.support_ships.max_concurrent_ships)
14653 		&& (	(Arriving_support_ship == NULL && The_mission.support_ships.tally < The_mission.support_ships.max_support_ships)
14654 			 || (Arriving_support_ship != NULL && The_mission.support_ships.tally <= The_mission.support_ships.max_support_ships) ))
14655 	{
14656 		// We are allowed more support ships in the mission; request another ship
14657 		// to service this request.
14658 		return 2;
14659 	}
14660 	// we're at the limit, but maybe a ship will become available
14661 	else if (soonest_available_support_ship != NULL) {
14662 		// found more support ships than should be in mission, so I can't ask for more,
14663 		// instead I will give the player the ship that will be done soonest
14664 		if (ship_we_found != NULL)
14665 			*ship_we_found = soonest_available_support_ship;
14666 		return 3;
14667 	}
14668 	// none of the above; we're out of luck
14669 	else {
14670 		return 4;
14671 	}
14672 }
14673 
14674 /**
14675  * Called in game_shutdown() to free malloced memory
14676  *
14677  * NOTE: do not call this function.  It is only called from ::game_shutdown()
14678  */
ship_close()14679 void ship_close()
14680 {
14681 	int i;
14682 
14683 	for (i=0; i<MAX_SHIPS; i++ )	{
14684 		ship *shipp = &Ships[i];
14685 
14686 		if (shipp->ship_replacement_textures != NULL) {
14687 			vm_free(shipp->ship_replacement_textures);
14688 			shipp->ship_replacement_textures = NULL;
14689 		}
14690 
14691 		if(shipp->warpin_effect != NULL)
14692 			delete shipp->warpin_effect;
14693 		shipp->warpin_effect = NULL;
14694 
14695 		if(shipp->warpout_effect != NULL)
14696 			delete shipp->warpout_effect;
14697 		shipp->warpout_effect = NULL;
14698 	}
14699 
14700 	// free this too! -- Goober5000
14701 	ship_clear_subsystems();
14702 
14703 	// free info from parsed table data
14704 	Ship_info.clear();
14705 
14706 	for (i = 0; i < (int)Ship_types.size(); i++) {
14707 		Ship_types[i].ai_actively_pursues.clear();
14708 		Ship_types[i].ai_actively_pursues_temp.clear();
14709 	}
14710 	Ship_types.clear();
14711 }
14712 
14713 /**
14714  * Assign object-linked sound to a particular ship
14715  */
ship_assign_sound(ship * sp)14716 void ship_assign_sound(ship *sp)
14717 {
14718 	ship_info	*sip;
14719 	object *objp;
14720 	ship_subsys *moveup;
14721 	bool has_engine = false;
14722 
14723 	Assert( sp->objnum >= 0 );
14724 	if(sp->objnum < 0){
14725 		return;
14726 	}
14727 
14728 	objp = &Objects[sp->objnum];
14729 	sip = &Ship_info[sp->ship_info_index];
14730 
14731 	// Do subsystem sounds
14732 	moveup = GET_FIRST(&sp->subsys_list);
14733 	while(moveup != END_OF_LIST(&sp->subsys_list)) {
14734 		if (!strnicmp(moveup->system_info->name, "engine", 6)) {
14735 			has_engine = true;
14736 		}
14737 
14738 		// Check for any engine sounds
14739 		if(strstr(moveup->system_info->name, "enginelarge")){
14740 			obj_snd_assign(sp->objnum, GameSounds::ENGINE_LOOP_LARGE, &moveup->system_info->pnt);
14741 		} else if(strstr(moveup->system_info->name, "enginehuge")){
14742 			obj_snd_assign(sp->objnum, GameSounds::ENGINE_LOOP_HUGE, &moveup->system_info->pnt);
14743 		}
14744 
14745 		//Do any normal subsystem sounds
14746 		if(moveup->current_hits > 0.0f)
14747 		{
14748 			if(moveup->system_info->alive_snd.isValid())
14749 			{
14750 				obj_snd_assign(sp->objnum, moveup->system_info->alive_snd, &moveup->system_info->pnt, OS_SUBSYS_ALIVE, moveup);
14751                 moveup->subsys_snd_flags.set(Ship::Subsys_Sound_Flags::Alive);
14752 			}
14753 			if(moveup->system_info->turret_base_rotation_snd.isValid())
14754 			{
14755 				obj_snd_assign(sp->objnum, moveup->system_info->turret_base_rotation_snd, &moveup->system_info->pnt, OS_TURRET_BASE_ROTATION, moveup);
14756 				moveup->subsys_snd_flags.set(Ship::Subsys_Sound_Flags::Turret_rotation);
14757 			}
14758 			if(moveup->system_info->turret_gun_rotation_snd.isValid())
14759 			{
14760 				obj_snd_assign(sp->objnum, moveup->system_info->turret_gun_rotation_snd, &moveup->system_info->pnt, OS_TURRET_GUN_ROTATION, moveup);
14761 				moveup->subsys_snd_flags.set(Ship::Subsys_Sound_Flags::Turret_rotation);
14762 			}
14763 			if((moveup->system_info->rotation_snd.isValid()) && (moveup->flags[Ship::Subsystem_Flags::Rotates]))
14764 			{
14765 				obj_snd_assign(sp->objnum, moveup->system_info->rotation_snd, &moveup->system_info->pnt, OS_SUBSYS_ROTATION, moveup);
14766 				moveup->subsys_snd_flags.set(Ship::Subsys_Sound_Flags::Rotate);
14767 			}
14768 		}
14769 		else
14770 		{
14771 			if(moveup->system_info->dead_snd.isValid())
14772 			{
14773 				obj_snd_assign(sp->objnum, moveup->system_info->dead_snd, &moveup->system_info->pnt, OS_SUBSYS_DEAD, moveup);
14774 				moveup->subsys_snd_flags.set(Ship::Subsys_Sound_Flags::Dead);
14775 			}
14776 		}
14777 
14778 		// next
14779 		moveup = GET_NEXT(moveup);
14780 	}
14781 
14782 	if (sip->engine_snd.isValid()) {
14783 		vec3d engine_pos;
14784 
14785 		// Only put the engine sound near the back of the ship if it has an engine.  Otherwise put it in the center.
14786 		if (has_engine) {
14787 			vm_vec_copy_scale(&engine_pos, &objp->orient.vec.fvec, -objp->radius / 2.0f);
14788 		} else {
14789 			engine_pos = vmd_zero_vector;
14790 		}
14791 
14792 		obj_snd_assign(sp->objnum, sip->engine_snd, &engine_pos, OS_MAIN);
14793 	}
14794 }
14795 
14796 /**
14797  * Assign object-linked sounds to all ships currently in the obj_used_list
14798  */
ship_assign_sound_all()14799 void ship_assign_sound_all()
14800 {
14801 	object *objp;
14802 	size_t idx;
14803 	int has_sounds;
14804 
14805 	if ( !Sound_enabled )
14806 		return;
14807 
14808 	for ( objp = GET_FIRST(&obj_used_list); objp !=END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) ) {
14809 		if ( objp->type == OBJ_SHIP && Player_obj != objp) {
14810 			has_sounds = 0;
14811 
14812 			// check to make sure this guy hasn't got sounds already assigned to him
14813 			for(idx=0; idx<objp->objsnd_num.size(); idx++){
14814 				if(objp->objsnd_num[idx] != -1){
14815 					// skip
14816 					has_sounds = 1;
14817 					break;
14818 				}
14819 			}
14820 
14821 			// actually assign the sound
14822 			if(!has_sounds){
14823 				ship_assign_sound(&Ships[objp->instance]);
14824 			}
14825 		}
14826 	}
14827 }
14828 
14829 
14830 /**
14831  * Debug console function to set the shield for the player ship
14832  */
14833 DCF(set_shield,"Change player ship shield strength")
14834 {
14835 	float value;
14836 
14837 	if (dc_optional_string_either("help", "--help")) {
14838 		dc_printf ("Usage: set_shield [num]\n");
14839 		dc_printf ("[num] --  shield percentage 0.0 -> 1.0 of max\n");
14840 		return;
14841 	}
14842 
14843 	if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) {
14844 		dc_printf( "Shields are currently %.2f", shield_get_strength(Player_obj) );
14845 		return;
14846 	}
14847 
14848 	dc_stuff_float(&value);
14849 
14850 	CLAMP(value, 0.0f, 1.0f);
14851 
14852 	shield_set_strength(Player_obj, value * shield_get_max_strength(Player_obj));
14853 	dc_printf("Shields set to %.2f\n", shield_get_strength(Player_obj) );
14854 }
14855 
14856 /**
14857  * Debug console function to set the hull for the player ship
14858  */
14859 DCF(set_hull, "Change player ship hull strength")
14860 {
14861 	float value;
14862 
14863 	if (dc_optional_string_either("help", "--help")) {
14864 		dc_printf ("Usage: set_hull [num]\n");
14865 		dc_printf ("[num] --  hull percentage 0.0 -> 1.0 of max\n");
14866 		return;
14867 	}
14868 
14869 	if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) {
14870 		dc_printf( "Hull is currently %.2f", Player_obj->hull_strength );
14871 		return;
14872 	}
14873 
14874 	dc_stuff_float(&value);
14875 
14876 	CLAMP(value, 0.0f, 1.0f);
14877 	Player_obj->hull_strength = value * Player_ship->ship_max_hull_strength;
14878 	dc_printf("Hull set to %.2f\n", Player_obj->hull_strength );
14879 }
14880 
14881 /**
14882  * Debug console function to set the strength of a particular subsystem
14883  */
14884 //XSTR:OFF
14885 DCF(set_subsys, "Set the strength of a particular subsystem on player ship" )
14886 {
14887 	SCP_string arg;
14888 	int subsystem = SUBSYSTEM_NONE;
14889 	float val_f;
14890 
14891 	if (dc_optional_string_either("help", "--help")) {
14892 		dc_printf( "Usage: set_subsys <type> [--status] <strength>\n");
14893 		dc_printf("<type> is any of the following:\n");
14894 		dc_printf("\tweapons\n");
14895 		dc_printf("\tengine\n");
14896 		dc_printf("\tsensors\n");
14897 		dc_printf("\tcommunication\n");
14898 		dc_printf("\tnavigation\n");
14899 		dc_printf("\tradar\n\n");
14900 
14901 		dc_printf("[--status] will display status of that subsystem\n\n");
14902 
14903 		dc_printf("<strength> is any value between 0 and 1.0\n");
14904 		return;
14905 	}
14906 
14907 	dc_stuff_string_white(arg);
14908 
14909 	if (arg == "weapons") {
14910 		subsystem = SUBSYSTEM_WEAPONS;
14911 
14912 	} else if (arg == "engine") {
14913 		subsystem = SUBSYSTEM_ENGINE;
14914 
14915 	} else if (arg == "sensors") {
14916 		subsystem = SUBSYSTEM_SENSORS;
14917 
14918 	} else if (arg == "communication") {
14919 		subsystem = SUBSYSTEM_COMMUNICATION;
14920 
14921 	} else if (arg == "navigation") {
14922 		subsystem = SUBSYSTEM_NAVIGATION;
14923 
14924 	} else if (arg == "radar") {
14925 		subsystem = SUBSYSTEM_RADAR;
14926 
14927 	} else if ((arg == "status") || (arg == "--status") || (arg == "?") || (arg == "--?")) {
14928 		dc_printf("Error: Must specify a subsystem.\n");
14929 		return;
14930 
14931 	} else {
14932 		dc_printf("Error: Unknown argument '%s'\n", arg.c_str());
14933 		return;
14934 	}
14935 
14936 	if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) {
14937 		dc_printf("Subsystem '%s' is at %f strength\n", arg.c_str(), ship_get_subsystem_strength(Player_ship, subsystem));
14938 
14939 	} else {
14940 		// Set the subsystem strength
14941 		dc_stuff_float(&val_f);
14942 
14943 		CLAMP(val_f, 0.0, 1.0);
14944 		ship_set_subsystem_strength( Player_ship, subsystem, val_f );
14945 
14946 		if (subsystem == SUBSYSTEM_ENGINE) {
14947 			// If subsystem is an engine, set/clear the disabled flag
14948 			Player_ship->flags.set(Ship_Flags::Disabled, (val_f < ENGINE_MIN_STR));
14949 		}
14950 	}
14951 }
14952 //XSTR:ON
14953 
14954 // console function to toggle whether auto-repair for subsystems is active
14955 #ifndef NDEBUG
DCF_BOOL(auto_repair,Ship_auto_repair)14956 DCF_BOOL( auto_repair, Ship_auto_repair )
14957 #endif
14958 
14959 // two functions to keep track of counting ships of particular types.  Maybe we should be rolling this
14960 // thing into the stats section??  The first function adds a ship of a particular type to the overall
14961 // count of ships of that type (called from MissionParse.cpp).  The second function adds to the kill total
14962 // of ships of a particular type.  Note that we use the ship_info flags structure member to determine
14963 // what is happening.
14964 
14965 //WMC - ALERT!!!!!!!!!!!
14966 //These two functions did something weird with fighters/bombers. I don't
14967 //think that not doing this will break anything, but it might.
14968 //If it does, get me. OR someone smart.
14969 //G5K - Someone smart to the rescue!  Fixed the functions so they don't
14970 //accidentally overwrite all the information.
14971 
14972 void ship_clear_ship_type_counts()
14973 {
14974 	// resize if we need to
14975 	Ship_type_counts.resize(Ship_types.size());
14976 
14977 	// clear all the stats
14978 	for (size_t i = 0; i < Ship_type_counts.size(); i++)
14979 	{
14980 		Ship_type_counts[i].killed = 0;
14981 		Ship_type_counts[i].total = 0;
14982 	}
14983 }
14984 
ship_add_ship_type_count(int ship_info_index,int num)14985 void ship_add_ship_type_count( int ship_info_index, int num )
14986 {
14987 	int type = ship_class_query_general_type(ship_info_index);
14988 
14989 	//Ship has no type or something
14990 	if(type < 0) {
14991 		return;
14992 	}
14993 
14994 	//Add it
14995 	Ship_type_counts[type].total += num;
14996 }
14997 
ship_add_ship_type_kill_count(int ship_info_index)14998 static void ship_add_ship_type_kill_count( int ship_info_index )
14999 {
15000 	int type = ship_class_query_general_type(ship_info_index);
15001 
15002 	//Ship has no type or something
15003 	if(type < 0) {
15004 		return;
15005 	}
15006 
15007 	//Add it if we are actually in gameplay
15008 	if (Ship_type_counts.size() > static_cast<size_t>(type))
15009 		Ship_type_counts[type].killed++;
15010 }
15011 
ship_query_general_type(int ship)15012 int ship_query_general_type(int ship)
15013 {
15014 	return ship_query_general_type(&Ships[ship]);
15015 }
15016 
ship_query_general_type(ship * shipp)15017 int ship_query_general_type(ship *shipp)
15018 {
15019 	return ship_class_query_general_type(shipp->ship_info_index);
15020 }
15021 
ship_class_query_general_type(int ship_class)15022 int ship_class_query_general_type(int ship_class)
15023 {
15024 	//This is quick
15025 	return Ship_info[ship_class].class_type;
15026 }
15027 
15028 /**
15029  * Returns true
15030  */
ship_docking_valid(int,int)15031 int ship_docking_valid(int  /*docker*/, int  /*dockee*/)
15032 {
15033 	// Goober5000
15034 	// So many people have asked for this function to be extended that it's making less
15035 	// and less sense to keep it around.  We should probably just let any ship type
15036 	// dock with any other ship type and assume the mission designer is smart enough not to
15037 	// mess things up.
15038 	return 1;
15039 }
15040 
15041 // function to return a random ship in a starting player wing.  Returns -1 if a suitable
15042 // one cannot be found
15043 // input:	max_dist	=>	OPTIONAL PARAMETER (default value 0.0f) max range ship can be from player
15044 // input:   persona  => OPTIONAL PARAMETER (default to -1) which persona to get
ship_get_random_player_wing_ship(int flags,float max_dist,int persona_index,int get_first,int multi_team)15045 int ship_get_random_player_wing_ship( int flags, float max_dist, int persona_index, int get_first, int multi_team )
15046 {
15047 	const int MAX_SIZE = MAX_SHIPS_PER_WING * MAX_SQUADRON_WINGS;
15048 
15049 	int i, j, ship_index, count;
15050 	int slist[MAX_SIZE], which_one;
15051 
15052 	// iterate through starting wings of player.  Add ship indices of ships which meet
15053 	// given criteria
15054 	count = 0;
15055 	for (i = 0; i < Num_wings; i++ ) {
15056 		if (count >= MAX_SIZE)
15057 			break;
15058 
15059 		int wingnum = -1;
15060 
15061 		// multi-team?
15062 		if(multi_team >= 0){
15063 			if( i == TVT_wings[multi_team] ) {
15064 				wingnum = i;
15065 			} else {
15066 				continue;
15067 			}
15068 		} else {
15069 			// first check for a player starting wing
15070 			for ( j = 0; j < MAX_STARTING_WINGS; j++ ) {
15071 				if ( i == Starting_wings[j] ) {
15072 					wingnum = i;
15073 					break;
15074 				}
15075 			}
15076 
15077 			// if not found, then check all squad wings (Goober5000)
15078 			if ( wingnum == -1 ) {
15079 				for ( j = 0; j < MAX_SQUADRON_WINGS; j++ ) {
15080 					if ( i == Squadron_wings[j] ) {
15081 						wingnum = i;
15082 						break;
15083 					}
15084 				}
15085 			}
15086 
15087 			if ( wingnum == -1 ){
15088 				continue;
15089 			}
15090 		}
15091 
15092 		for ( j = 0; j < Wings[wingnum].current_count; j++ ) {
15093 			if (count >= MAX_SIZE)
15094 				break;
15095 
15096 			ship_index = Wings[wingnum].ship_index[j];
15097 			Assert( ship_index != -1 );
15098 
15099 			if ( Ships[ship_index].flags[Ship_Flags::Dying] ) {
15100 				continue;
15101 			}
15102 
15103 			// see if ship meets our criteria
15104 			if ( (flags == SHIP_GET_NO_PLAYERS || flags == SHIP_GET_UNSILENCED) && (Objects[Ships[ship_index].objnum].flags[Object::Object_Flags::Player_ship]) ){
15105 				continue;
15106 			}
15107 			if (flags == SHIP_GET_UNSILENCED) {
15108 				if (Ships[ship_index].flags[Ship_Flags::No_builtin_messages])
15109 					continue;
15110 				if (The_mission.ai_profile->flags[AI::Profile_Flags::Check_comms_for_non_player_ships] && hud_communications_state(&Ships[ship_index]) <= COMM_DAMAGED)
15111 					continue;
15112 			}
15113 
15114 			// don't process ships on a different team
15115 			if(multi_team < 0){
15116 				if ( Player_ship->team != Ships[ship_index].team ){
15117 					continue;
15118 				}
15119 			}
15120 
15121 			// see if ship is within max_dist units
15122 			if ( (max_dist > 1.0f) && (multi_team < 0) ) {
15123 				float dist;
15124 				dist = vm_vec_dist_quick(&Objects[Ships[ship_index].objnum].pos, &Player_obj->pos);
15125 				if ( dist > max_dist ) {
15126 					continue;
15127 				}
15128 			}
15129 
15130 			// if we should be checking persona's, then don't add ships that don't have the proper persona
15131 			if ( persona_index != -1 ) {
15132 				if ( Ships[ship_index].persona_index != persona_index ){
15133 					continue;
15134 				}
15135 			}
15136 
15137 			// return the first ship with correct persona
15138 			if (get_first) {
15139 				return ship_index;
15140 			}
15141 
15142 			slist[count] = ship_index;
15143 			count++;
15144 		}
15145 	}
15146 
15147 	if ( count == 0 ){
15148 		return -1;
15149 	}
15150 
15151 	// now get a random one from the list
15152 	which_one = Random::next(count);
15153 	ship_index = slist[which_one];
15154 
15155 	Assert ( Ships[ship_index].objnum != -1 );
15156 
15157 	return ship_index;
15158 }
15159 
15160 // like above function, but returns a random ship in the given wing -- no restrictions
15161 // input:	max_dist	=>	OPTIONAL PARAMETER (default value 0.0f) max range ship can be from player
ship_get_random_ship_in_wing(int wingnum,int flags,float max_dist,int get_first)15162 int ship_get_random_ship_in_wing(int wingnum, int flags, float max_dist, int get_first)
15163 {
15164 	int i, ship_index, slist[MAX_SHIPS_PER_WING], count, which_one;
15165 
15166 	count = 0;
15167 	for ( i = 0; i < Wings[wingnum].current_count; i++ ) {
15168 		ship_index = Wings[wingnum].ship_index[i];
15169 		Assert( ship_index != -1 );
15170 
15171 		if ( Ships[ship_index].flags[Ship_Flags::Dying] ) {
15172 			continue;
15173 		}
15174 
15175 		// see if ship meets our criterea
15176 		if ( (flags == SHIP_GET_NO_PLAYERS || flags == SHIP_GET_UNSILENCED) && (Objects[Ships[ship_index].objnum].flags[Object::Object_Flags::Player_ship]) )
15177 			continue;
15178 
15179 		if ( (flags == SHIP_GET_UNSILENCED) && (Ships[ship_index].flags[Ship_Flags::No_builtin_messages]) )
15180 		{
15181 			continue;
15182 		}
15183 
15184 		// see if ship is within max_dist units
15185 		if ( max_dist > 0 ) {
15186 			float dist;
15187 			dist = vm_vec_dist_quick(&Objects[Ships[ship_index].objnum].pos, &Player_obj->pos);
15188 			if ( dist > max_dist ) {
15189 				continue;
15190 			}
15191 		}
15192 
15193 		// return the first ship in wing
15194 		if (get_first) {
15195 			return ship_index;
15196 		}
15197 
15198 		slist[count] = ship_index;
15199 		count++;
15200 	}
15201 
15202 	if ( count == 0 ) {
15203 		return -1;
15204 	}
15205 
15206 	// now get a random one from the list
15207 	which_one = Random::next(count);
15208 	ship_index = slist[which_one];
15209 
15210 	Assert ( Ships[ship_index].objnum != -1 );
15211 
15212 	return ship_index;
15213 }
15214 
15215 
15216 // this function returns a random index into the Ship array of a ship of the given team
15217 // cargo containers are not counted as ships for the purposes of this function.  Why???
15218 // because now it is only used for getting a random ship for a message and cargo containers
15219 // can't send mesages.  This function is an example of kind of bad coding :-(
15220 // input:	max_dist	=>	OPTIONAL PARAMETER (default value 0.0f) max range ship can be from player
ship_get_random_team_ship(int team_mask,int flags,float max_dist)15221 int ship_get_random_team_ship(int team_mask, int flags, float max_dist )
15222 {
15223 	int num, which_one;
15224 	object *objp, *obj_list[MAX_SHIPS];
15225 
15226 	// for any allied, go through the ships list and find all of the ships on that team
15227 	num = 0;
15228 	for ( objp = GET_FIRST(&obj_used_list); objp != END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) ) {
15229 		if ( objp->type != OBJ_SHIP )
15230 			continue;
15231 
15232 		// series of conditionals one per line for easy reading
15233 		// don't process ships on wrong team
15234 		// don't process cargo's or navbuoys
15235 		// don't process player ships if flags are set
15236 		if (!iff_matches_mask(Ships[objp->instance].team, team_mask))
15237 			continue;
15238 		else if ( !Ship_info[Ships[objp->instance].ship_info_index].is_flyable() )
15239 			continue;
15240 		else if ( (flags == SHIP_GET_NO_PLAYERS) && (objp->flags[Object::Object_Flags::Player_ship]) )
15241 			continue;
15242 		else if ( (flags == SHIP_GET_ONLY_PLAYERS) && !(objp->flags[Object::Object_Flags::Player_ship]) )
15243 			continue;
15244 
15245 		if ( Ships[objp->instance].flags[Ship_Flags::Dying] ) {
15246 			continue;
15247 		}
15248 
15249 		// see if ship is within max_dist units
15250 		if ( max_dist > 0 ) {
15251 			float dist;
15252 			dist = vm_vec_dist_quick(&objp->pos, &Player_obj->pos);
15253 			if ( dist > max_dist ) {
15254 				continue;
15255 			}
15256 		}
15257 
15258 		obj_list[num] = objp;
15259 		num++;
15260 	}
15261 
15262 	if ( num == 0 )
15263 		return -1;
15264 
15265 	which_one = Random::next(num);
15266 	objp = obj_list[which_one];
15267 
15268 	Assert ( objp->instance != -1 );
15269 
15270 	return objp->instance;
15271 }
15272 
15273 // -----------------------------------------------------------------------
15274 // ship_secondary_bank_has_ammo()
15275 //
15276 // check if currently selected secondary bank has ammo
15277 //
15278 // input:	shipnum	=>	index into Ships[] array for ship to check
15279 //
ship_secondary_bank_has_ammo(int shipnum)15280 int ship_secondary_bank_has_ammo(int shipnum)
15281 {
15282 	ship_weapon	*swp;
15283 
15284 	Assert(shipnum >= 0 && shipnum < MAX_SHIPS);
15285 	swp = &Ships[shipnum].weapons;
15286 
15287 	if ( swp->current_secondary_bank == -1 )
15288 		return 0;
15289 
15290 	Assert(swp->current_secondary_bank >= 0 && swp->current_secondary_bank < MAX_SHIP_SECONDARY_BANKS );
15291 	if ( swp->secondary_bank_ammo[swp->current_secondary_bank] <= 0 )
15292 		return 0;
15293 
15294 	return 1;
15295 }
15296 
15297 // see if there is enough engine power to allow the ship to warp
15298 // returns 1 if ship is able to warp, otherwise return 0
ship_engine_ok_to_warp(ship * sp)15299 int ship_engine_ok_to_warp(ship *sp)
15300 {
15301 	// disabled ships can't warp
15302 	if (sp->flags[Ship_Flags::Disabled])
15303 		return 0;
15304 
15305 	if (sp->flags[Ship_Flags::Warp_broken] || sp->flags[Ship_Flags::Warp_never])
15306 		return 0;
15307 
15308 	float engine_strength = ship_get_subsystem_strength(sp, SUBSYSTEM_ENGINE);
15309 
15310 	// if at 0% strength, can't warp
15311 	if (engine_strength <= 0.0f)
15312 		return 0;
15313 
15314 	// player ships playing above Very Easy can't warp when below a threshold
15315 	if ((sp == Player_ship) && (Game_skill_level > 0) && (engine_strength < SHIP_MIN_ENGINES_TO_WARP))
15316 		return 0;
15317 
15318 	// otherwise, warp is allowed
15319 	return 1;
15320 }
15321 
15322 // Goober5000
15323 // see if there is enough navigation power to allow the ship to warp
15324 // returns 1 if ship is able to warp, otherwise return 0
ship_navigation_ok_to_warp(ship * sp)15325 int ship_navigation_ok_to_warp(ship *sp)
15326 {
15327 	// if not using the special flag, warp is always allowed
15328 	if (!(The_mission.ai_profile->flags[AI::Profile_Flags::Navigation_subsys_governs_warp]))
15329 		return 1;
15330 
15331 	float navigation_strength = ship_get_subsystem_strength(sp, SUBSYSTEM_NAVIGATION);
15332 
15333 	// if at 0% strength, can't warp
15334 	if (navigation_strength <= 0.0f)
15335 		return 0;
15336 
15337 	// player ships playing above Very Easy can't warp when below a threshold
15338 	if ((sp == Player_ship) && (Game_skill_level > 0) && (navigation_strength < SHIP_MIN_NAV_TO_WARP))
15339 		return 0;
15340 
15341 	// otherwise, warp is allowed
15342 	return 1;
15343 }
15344 
15345 // wookieejedi
15346 // checks both the warp flags and ship_engine_ok_to_warp() and ship_navigation_ok_to_warp() --wookieejedi
15347 // returns true if ship is able to warp, otherwise return false
ship_can_warp_full_check(ship * sp)15348 bool ship_can_warp_full_check(ship* sp)
15349 {
15350 	if (!(sp->cannot_warp_flags()) && ship_navigation_ok_to_warp(sp) && ship_engine_ok_to_warp(sp)) {
15351 		// there are no warp flags that are preventing us from warping and the nav and engines are good
15352 		// thus warp is allowed
15353 		return true;
15354 	} else {
15355 		// warp is not allowed since one of those conditions was not met
15356 		return false;
15357 	}
15358 }
15359 
15360 // wookieejedi
15361 // check to see if a ship can depart via bay
15362 // takes into account if the ship is in a wing
15363 // returns true if the ship has a bay departure and the mothership is present, false otherwise
ship_can_bay_depart(ship * sp)15364 bool ship_can_bay_depart(ship* sp)
15365 {
15366 	// if this ship belongs to a wing, then use the wing departure information
15367 	int departure_location;
15368 	int departure_anchor;
15369 	int departure_path_mask;
15370 	if (sp->wingnum >= 0)
15371 	{
15372 		wing *wingp = &Wings[sp->wingnum];
15373 		departure_location = wingp->departure_location;
15374 		departure_anchor = wingp->departure_anchor;
15375 		departure_path_mask = wingp->departure_path_mask;
15376 	} else {
15377 		departure_location = sp->departure_location;
15378 		departure_anchor = sp->departure_anchor;
15379 		departure_path_mask = sp->departure_path_mask;
15380 	}
15381 
15382 	if ( departure_location == DEPART_AT_DOCK_BAY )
15383 	{
15384 		Assertion( departure_anchor >= 0, "Ship %s must have a valid departure anchor", sp->ship_name );
15385 		int anchor_shipnum = ship_name_lookup(Parse_names[departure_anchor]);
15386 		if (anchor_shipnum >= 0 && ship_useful_for_departure(anchor_shipnum, departure_path_mask)) {
15387 			// can bay depart at this time
15388 			return true;
15389 		}
15390 	}
15391 
15392 	// cannot bay depart at this time
15393 	return false;
15394 }
15395 
15396 // Calculate the normal vector from a subsystem position and its first path point
15397 // input:	sp	=>	pointer to ship that is parent of subsystem
15398 //				ss =>	pointer to subsystem of interest
15399 //				norm	=> output parameter... vector from subsys to first path point
15400 //
15401 //	exit:		0	=>	a valid vector was placed in norm
15402 //				!0	=> an path normal could not be calculated
15403 //
ship_return_subsys_path_normal(ship * shipp,ship_subsys * ss,vec3d * gsubpos,vec3d * norm)15404 int ship_return_subsys_path_normal(ship *shipp, ship_subsys *ss, vec3d *gsubpos, vec3d *norm)
15405 {
15406 	if ( ss->system_info->path_num >= 0 ) {
15407 		polymodel	*pm = NULL;
15408 		model_path	*mp;
15409 		vec3d		*path_point;
15410 		vec3d		gpath_point;
15411 		pm = model_get(Ship_info[shipp->ship_info_index].model_num);
15412 		Assert( pm != NULL );
15413 
15414 		// possibly a bad model?
15415 		Assertion(ss->system_info->path_num <= pm->n_paths, "Too many paths in '%s'!  Max is %i and the requested path was %i for subsystem '%s'!\n", pm->filename, pm->n_paths, ss->system_info->path_num, ss->system_info->subobj_name);
15416 		if (ss->system_info->path_num > pm->n_paths)
15417 			return 1;
15418 
15419 		mp = &pm->paths[ss->system_info->path_num];
15420 		if ( mp->nverts >= 2 ) {
15421 			path_point = &mp->verts[0].pos;
15422 			// get path point in world coords
15423 			vm_vec_unrotate(&gpath_point, path_point, &Objects[shipp->objnum].orient);
15424 			vm_vec_add2(&gpath_point, &Objects[shipp->objnum].pos);
15425 			// get unit vector pointing from subsys pos to first path point
15426 			vm_vec_normalized_dir(norm, &gpath_point, gsubpos);
15427 			return 0;
15428 		}
15429 	}
15430 	return 1;
15431 }
15432 
15433 
15434 //	Determine if the subsystem can be viewed from eye_pos.  The method is to check where the
15435 // vector from eye_pos to the subsystem hits the ship.  If distance from the hit position and
15436 // the center of the subsystem is within a range (currently the subsystem radius) it is considered
15437 // in view (return true).  If not in view, return false.
15438 //
15439 // input:	objp		=>		object that is the ship with the subsystem on it
15440 //				subsys	=>		pointer to the subsystem of interest
15441 //				eye_pos	=>		world coord for the eye looking at the subsystem
15442 //				subsys_pos			=>	world coord for the center of the subsystem of interest
15443 //				do_facing_check	=>	OPTIONAL PARAMETER (default value is 1), do a dot product check to see if subsystem fvec is facing
15444 //											towards the eye position
15445 //				dot_out	=>		OPTIONAL PARAMETER, output parameter, will return dot between subsys fvec and subsys_to_eye_vec
15446 //									(only filled in if do_facing_check is true)
15447 //				vec_out	=>		OPTIONAL PARAMETER, vector from eye_pos to absolute subsys_pos.  (only filled in if do_facing_check is true)
ship_subsystem_in_sight(object * objp,ship_subsys * subsys,vec3d * eye_pos,vec3d * subsys_pos,int do_facing_check,float * dot_out,vec3d * vec_out)15448 int ship_subsystem_in_sight(object* objp, ship_subsys* subsys, vec3d *eye_pos, vec3d* subsys_pos, int do_facing_check, float *dot_out, vec3d *vec_out)
15449 {
15450 	float		dist, dot;
15451 	mc_info	mc;
15452 	vec3d	terminus, eye_to_pos, subsys_fvec, subsys_to_eye_vec;
15453 
15454 	if (objp->type != OBJ_SHIP)
15455 		return 0;
15456 
15457 	// See if we are at least facing the subsystem
15458 	if ( do_facing_check ) {
15459 		if ( ship_return_subsys_path_normal(&Ships[objp->instance], subsys, subsys_pos, &subsys_fvec) ) {
15460 			// non-zero return value means that we couldn't generate a normal from path info... so use inaccurate method
15461 			vm_vec_normalized_dir(&subsys_fvec, subsys_pos, &objp->pos);
15462 		}
15463 
15464 		vm_vec_normalized_dir(&subsys_to_eye_vec, eye_pos, subsys_pos);
15465 		dot = vm_vec_dot(&subsys_fvec, &subsys_to_eye_vec);
15466 		if ( dot_out ) {
15467 			*dot_out = dot;
15468 		}
15469 
15470 		if (vec_out) {
15471 			*vec_out = subsys_to_eye_vec;
15472 			vm_vec_negate(vec_out);
15473 		}
15474 
15475 		if ( dot < 0 )
15476 			return 0;
15477 	}
15478 
15479 	// See if ray from eye to subsystem actually hits close enough to the subsystem position
15480 	vm_vec_normalized_dir(&eye_to_pos, subsys_pos, eye_pos);
15481 	vm_vec_scale_add(&terminus, eye_pos, &eye_to_pos, 100000.0f);
15482 
15483 	mc_info_init(&mc);
15484 	mc.model_instance_num = Ships[objp->instance].model_instance_num;
15485 	mc.model_num = Ship_info[Ships[objp->instance].ship_info_index].model_num;			// Fill in the model to check
15486 	mc.orient = &objp->orient;										// The object's orientation
15487 	mc.pos = &objp->pos;												// The object's position
15488 	mc.p0 = eye_pos;													// Point 1 of ray to check
15489 	mc.p1 = &terminus;												// Point 2 of ray to check
15490 	mc.flags = MC_CHECK_MODEL;
15491 
15492 	model_collide(&mc);
15493 
15494 	if ( !mc.num_hits ) {
15495 		return 0;
15496 	}
15497 
15498 	// determine if hitpos is close enough to subsystem
15499 	dist = vm_vec_dist(&mc.hit_point_world, subsys_pos);
15500 
15501 	if ( dist <= subsys->system_info->radius ) {
15502 		return 1;
15503 	}
15504 
15505 	return 0;
15506 }
15507 
15508 /**
15509  * Find a subsystem matching 'type' inside the ship, and that is not destroyed.
15510  * @return If cannot find one, return NULL.
15511  */
ship_return_next_subsys(ship * shipp,int type,vec3d * attacker_pos)15512 ship_subsys *ship_return_next_subsys(ship *shipp, int type, vec3d *attacker_pos)
15513 {
15514 	ship_subsys	*ssp;
15515 
15516 	Assert ( type >= 0 && type < SUBSYSTEM_MAX );
15517 
15518 	// If aggregate total is 0, that means no subsystem is alive of that type
15519 	if ( shipp->subsys_info[type].aggregate_max_hits <= 0.0f )
15520 		return NULL;
15521 
15522 	// loop through all the subsystems, if we find a match that has some strength, return it
15523 	ssp = ship_get_best_subsys_to_attack(shipp, type, attacker_pos);
15524 
15525 	return ssp;
15526 }
15527 
15528 // Returns the closest subsystem of specified type that is in line of sight.
15529 // Returns null if all subsystems of that type are destroyed or none is in sight.
ship_get_closest_subsys_in_sight(ship * sp,int subsys_type,vec3d * attacker_pos)15530 ship_subsys *ship_get_closest_subsys_in_sight(ship *sp, int subsys_type, vec3d *attacker_pos)
15531 {
15532 	Assert ( subsys_type >= 0 && subsys_type < SUBSYSTEM_MAX );
15533 
15534 	// If aggregate total is 0, that means no subsystem is alive of that type
15535 	if ( sp->subsys_info[subsys_type].aggregate_max_hits <= 0.0f )
15536 		return NULL;
15537 
15538 	ship_subsys	*closest_in_sight_subsys;
15539 	ship_subsys	*ss;
15540 	vec3d		gsubpos;
15541 	float		closest_dist;
15542 	float		ss_dist;
15543 
15544 	closest_in_sight_subsys = NULL;
15545 	closest_dist = FLT_MAX;
15546 
15547 	for (ss = GET_FIRST(&sp->subsys_list); ss != END_OF_LIST(&sp->subsys_list); ss = GET_NEXT(ss) ) {
15548 		if ( (ss->system_info->type == subsys_type) && (ss->current_hits > 0) ) {
15549 
15550 			// get world pos of subsystem
15551 			vm_vec_unrotate(&gsubpos, &ss->system_info->pnt, &Objects[sp->objnum].orient);
15552 			vm_vec_add2(&gsubpos, &Objects[sp->objnum].pos);
15553 
15554 			if ( ship_subsystem_in_sight(&Objects[sp->objnum], ss, attacker_pos, &gsubpos) ) {
15555 				ss_dist = vm_vec_dist_squared(attacker_pos, &gsubpos);
15556 
15557 				if ( ss_dist < closest_dist ) {
15558 					closest_dist = ss_dist;
15559 					closest_in_sight_subsys = ss;
15560 				}
15561 			}
15562 		}
15563 	}
15564 
15565 	return closest_in_sight_subsys;
15566 }
15567 
ship_subsys_get_name(ship_subsys * ss)15568 char *ship_subsys_get_name(ship_subsys *ss)
15569 {
15570 	if( ss->sub_name[0] != '\0' )
15571 		return ss->sub_name;
15572 	else
15573 		return ss->system_info->name;
15574 }
15575 
ship_subsys_has_instance_name(ship_subsys * ss)15576 bool ship_subsys_has_instance_name(ship_subsys *ss)
15577 {
15578 	if( ss->sub_name[0] != '\0' )
15579 		return true;
15580 	else
15581 		return false;
15582 }
15583 
ship_subsys_set_name(ship_subsys * ss,const char * n_name)15584 void ship_subsys_set_name(ship_subsys* ss, const char* n_name) { strncpy(ss->sub_name, n_name, NAME_LENGTH - 1); }
15585 
15586 /**
15587  * Return the shield strength of the specified quadrant on hit_objp
15588  *
15589  * @param hit_objp object pointer to ship getting hit
15590  * @param quadrant_num shield quadrant that was hit
15591  * @return strength of shields in the quadrant that was hit as a percentage, between 0 and 1.0
15592  */
ship_quadrant_shield_strength(object * hit_objp,int quadrant_num)15593 float ship_quadrant_shield_strength(object *hit_objp, int quadrant_num)
15594 {
15595 	float			max_quadrant;
15596 
15597 	// If ship doesn't have shield mesh, then return
15598 	if ( hit_objp->flags[Object::Object_Flags::No_shields] ) {
15599 		return 0.0f;
15600 	}
15601 
15602 	// If shields weren't hit, return 0
15603 	if ( quadrant_num < 0 )
15604 		return 0.0f;
15605 
15606 	max_quadrant = shield_get_max_quad(hit_objp);
15607 	if ( max_quadrant <= 0 ) {
15608 		return 0.0f;
15609 	}
15610 
15611 	Assertion(quadrant_num < hit_objp->n_quadrants, "ship_quadrant_shield_strength() called with a quadrant of %d on a ship with %d quadrants; get a coder!\n", quadrant_num, hit_objp->n_quadrants);
15612 
15613 	if(hit_objp->shield_quadrant[quadrant_num] > max_quadrant)
15614 		mprintf(("Warning: \"%s\" has shield quadrant strength of %f out of %f\n",
15615 				Ships[hit_objp->instance].ship_name, hit_objp->shield_quadrant[quadrant_num], max_quadrant));
15616 
15617 	return hit_objp->shield_quadrant[quadrant_num]/max_quadrant;
15618 }
15619 
15620 // Determine if a ship is threatened by any dumbfire projectiles (laser or missile)
15621 // input:	sp	=>	pointer to ship that might be threatened
15622 // exit:		0 =>	no dumbfire threats
15623 //				1 =>	at least one dumbfire threat
15624 //
15625 // NOTE: Currently this function is only called periodically from the HUD code for the
15626 //       player ship.
ship_dumbfire_threat(ship * sp)15627 int ship_dumbfire_threat(ship *sp)
15628 {
15629 	if ( (Game_mode & GM_MULTIPLAYER) && (Net_player->flags & NETINFO_FLAG_OBSERVER) ) {
15630 		return 0;
15631 	}
15632 
15633 	if (ai_endangered_by_weapon(&Ai_info[sp->ai_index]) > 0) {
15634 		return 1;
15635 	}
15636 
15637 	return 0;
15638 }
15639 
15640 // Return !0 if there is a missile in the air homing on shipp
ship_has_homing_missile_locked(ship * shipp)15641 static int ship_has_homing_missile_locked(ship *shipp)
15642 {
15643 	object		*locked_objp, *A;
15644 	weapon		*wp;
15645 	weapon_info	*wip;
15646 	missile_obj	*mo;
15647 
15648 	Assert(shipp->objnum >= 0 && shipp->objnum < MAX_OBJECTS);
15649 	locked_objp = &Objects[shipp->objnum];
15650 
15651 	// check for currently locked missiles (highest precedence)
15652 	for ( mo = GET_NEXT(&Missile_obj_list); mo != END_OF_LIST(&Missile_obj_list); mo = GET_NEXT(mo) ) {
15653 		Assert(mo->objnum >= 0 && mo->objnum < MAX_OBJECTS);
15654 		A = &Objects[mo->objnum];
15655 
15656 		if (A->type != OBJ_WEAPON)
15657 			continue;
15658 
15659 		Assert((A->instance >= 0) && (A->instance < MAX_WEAPONS));
15660 		wp = &Weapons[A->instance];
15661 		wip = &Weapon_info[wp->weapon_info_index];
15662 
15663 		if ( wip->subtype != WP_MISSILE )
15664 			continue;
15665 
15666 		if ( !(wip->is_homing() ) )
15667 			continue;
15668 
15669 		if (wp->homing_object == locked_objp) {
15670 			return 1;
15671 		}
15672 	}	// end for
15673 
15674 	return 0;
15675 }
15676 
15677 // Return !0 if there is some ship attempting to lock onto shipp
ship_is_getting_locked(ship * shipp)15678 static int ship_is_getting_locked(ship *shipp)
15679 {
15680 	ship_obj	*so;
15681 	object	*objp;
15682 	ai_info	*aip;
15683 
15684 	for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) {
15685 		objp = &Objects[so->objnum];
15686 		aip = &Ai_info[Ships[objp->instance].ai_index];
15687 
15688 		if ( aip->target_objnum == shipp->objnum ) {
15689 			if ( aip->aspect_locked_time > 0.1f ) {
15690 				float dist, wep_range;
15691 				dist = vm_vec_dist_quick(&objp->pos, &Objects[shipp->objnum].pos);
15692 				wep_range = ship_get_secondary_weapon_range(&Ships[objp->instance]);
15693 				if ( wep_range > dist ) {
15694 					nprintf(("Alan","AI ship is seeking lock\n"));
15695 					return 1;
15696 				}
15697 			}
15698 		}
15699 	}
15700 
15701 	return 0;
15702 }
15703 
15704 // Determine if a ship is threatened by attempted lock or actual lock
15705 // input:	sp	=>	pointer to ship that might be threatened
15706 // exit:		0 =>	no lock threats of any kind
15707 //				1 =>	at least one attempting lock (no actual locks)
15708 //				2 =>	at least one lock (possible other attempting locks)
15709 //
15710 // NOTE: Currently this function is only called periodically from the HUD code for the
15711 //       player ship.
ship_lock_threat(ship * sp)15712 int ship_lock_threat(ship *sp)
15713 {
15714 	if ( ship_has_homing_missile_locked(sp) ) {
15715 		return 2;
15716 	}
15717 
15718 	if ( ship_is_getting_locked(sp) ) {
15719 		return 1;
15720 	}
15721 
15722 	return 0;
15723 }
15724 
15725 // converts a bitmask, such as 0x08, into the bit number this would be (3 in this case)
15726 // NOTE: Should move file to something like Math_utils.
bitmask_2_bitnum(int num)15727 int bitmask_2_bitnum(int num)
15728 {
15729 	int i;
15730 
15731 	for (i=0; i<32; i++)
15732 		if (num & (1 << i))
15733 			return i;
15734 
15735 	return -1;
15736 }
15737 
ship_get_ai_target_display_name(int goal,const char * name)15738 static const char* ship_get_ai_target_display_name(int goal, const char* name)
15739 {
15740 	switch (goal) {
15741 	// These goals refer to ships so we need to retrieve their display name
15742 	case AI_GOAL_FORM_ON_WING:
15743 	case AI_GOAL_CHASE:
15744 	case AI_GOAL_DOCK:
15745 	case AI_GOAL_UNDOCK:
15746 	case AI_GOAL_GUARD:
15747 	case AI_GOAL_DISABLE_SHIP:
15748 	case AI_GOAL_DISARM_SHIP:
15749 	case AI_GOAL_EVADE_SHIP:
15750 	case AI_GOAL_REARM_REPAIR:
15751 	case AI_GOAL_FLY_TO_SHIP:
15752 	case AI_GOAL_DESTROY_SUBSYSTEM: {
15753 		auto ship = ship_name_lookup(name);
15754 		if (ship < 0) {
15755 			return name;
15756 		}
15757 		return Ships[ship].get_display_name();
15758 	}
15759 
15760 		// These goals need no special handling
15761 	case AI_GOAL_CHASE_WING:
15762 	case AI_GOAL_CHASE_SHIP_CLASS:
15763 	case AI_GOAL_GUARD_WING:
15764 	case AI_GOAL_WAYPOINTS:
15765 	case AI_GOAL_WAYPOINTS_ONCE:
15766 	default:
15767 		return name;
15768 	}
15769 }
15770 
15771 // Get a text description of a ships orders.
15772 //
15773 //	input:	outbuf	=>		buffer to hold orders string
15774 //				sp			=>		ship pointer to extract orders from
15775 //
15776 // exit:		NULL		=>		printable orders are not applicable
15777 //				non-NULL	=>		pointer to string that was passed in originally
15778 //
15779 // This function is called from HUD code to get a text description
15780 // of what a ship's orders are.  Feel free to use this function if
15781 // it suits your needs for something.
15782 //
ship_return_orders(ship * sp)15783 SCP_string ship_return_orders(ship* sp)
15784 {
15785 	ai_info* aip;
15786 	ai_goal* aigp;
15787 
15788 	Assert(sp->ai_index >= 0);
15789 	aip = &Ai_info[sp->ai_index];
15790 
15791 	// The active goal is always in the first element of aip->goals[]
15792 	aigp = &aip->goals[0];
15793 
15794 	if (aigp->ai_mode < 0)
15795 		return SCP_string();
15796 
15797 	auto order_text = Ai_goal_text(aigp->ai_mode);
15798 	if (order_text == nullptr)
15799 		return SCP_string();
15800 
15801 	SCP_string outbuf = order_text;
15802 
15803 	const char *target_name;
15804 	if (aigp->target_name) {
15805 		target_name = ship_get_ai_target_display_name(aigp->ai_mode, aigp->target_name);
15806 	} else {
15807 		target_name = "";
15808 	}
15809 
15810 	switch (aigp->ai_mode) {
15811 	case AI_GOAL_FORM_ON_WING:
15812 	case AI_GOAL_GUARD_WING:
15813 	case AI_GOAL_CHASE_WING:
15814 		if (aigp->target_name) {
15815 			outbuf += target_name;
15816 			outbuf += XSTR(" wing", 494);
15817 		} else {
15818 			outbuf += XSTR("no orders", 495);
15819 		}
15820 		break;
15821 
15822 	case AI_GOAL_CHASE_SHIP_CLASS:
15823 		if (aigp->target_name) {
15824 			outbuf += XSTR("any ", -1);
15825 			outbuf += target_name;
15826 		} else {
15827 			outbuf += XSTR("no orders", 495);
15828 		}
15829 		break;
15830 
15831 	case AI_GOAL_CHASE:
15832 	case AI_GOAL_DOCK:
15833 	case AI_GOAL_UNDOCK:
15834 	case AI_GOAL_GUARD:
15835 	case AI_GOAL_DISABLE_SHIP:
15836 	case AI_GOAL_DISARM_SHIP:
15837 	case AI_GOAL_EVADE_SHIP:
15838 	case AI_GOAL_REARM_REPAIR:
15839 	case AI_GOAL_FLY_TO_SHIP:
15840 		if (aigp->target_name) {
15841 			outbuf += target_name;
15842 		} else {
15843 			outbuf += XSTR("no orders", 495);
15844 		}
15845 		break;
15846 
15847 	case AI_GOAL_DESTROY_SUBSYSTEM: {
15848 		if (aip->targeted_subsys != nullptr) {
15849 			char subsys_name[NAME_LENGTH];
15850 			strcpy_s(subsys_name, aip->targeted_subsys->system_info->subobj_name);
15851 			hud_targetbox_truncate_subsys_name(subsys_name);
15852 			sprintf(outbuf, XSTR("atk %s %s", 496), target_name, subsys_name);
15853 		} else {
15854 			outbuf += XSTR("no orders", 495);
15855 		}
15856 		break;
15857 	}
15858 
15859 	case AI_GOAL_WAYPOINTS:
15860 	case AI_GOAL_WAYPOINTS_ONCE:
15861 		// don't do anything, all info is in order_text
15862 		break;
15863 
15864 	default:
15865 		return SCP_string();
15866 	}
15867 
15868 	return outbuf;
15869 }
15870 
15871 // return the amount of time until ship reaches its goal (in MM:SS format)
15872 //	input:	outbuf	=>		buffer to hold orders string
15873 //				sp			=>		ship pointer to extract orders from
15874 //
15875 // exit:		NULL		=>		printable orders are not applicable
15876 //				non-NULL	=>		pointer to string that was passed in originally
15877 //
15878 // This function is called from HUD code to get a text description
15879 // of what a ship's orders are.  Feel free to use this function if
15880 // it suits your needs for something.
ship_return_time_to_goal(char * outbuf,ship * sp)15881 char *ship_return_time_to_goal(char *outbuf, ship *sp)
15882 {
15883 	int	minutes, seconds;
15884 	int time = ship_return_seconds_to_goal(sp);
15885 	if ( time >= 0 ) {
15886 		minutes = time/60;
15887 		seconds = time%60;
15888 		if ( minutes > 99 ) {
15889 			minutes = 99;
15890 			seconds = 99;
15891 		}
15892 		sprintf(outbuf, NOX("%02d:%02d"), minutes, seconds);
15893 
15894 	} else if ( time == -1 ) {
15895 		strcpy( outbuf, XSTR( "Unknown", 497) );
15896 
15897 	} else {
15898 		// we don't want to display anything on the HUD
15899 		return nullptr;
15900 	}
15901 
15902 	return outbuf;
15903 }
15904 
15905 // Returns the estimated time, in seconds, of the given ship to reach its goal
15906 //
15907 // @param[in] sp  Pointer to ship to check
15908 //
15909 // @returns Time of the ship to reach its goal, or
15910 // @returns  0, if it has met its goal, or
15911 // @returns -1, if the ship is not moving or can not move, or
15912 // @returns -2, if the ship does not have a valid goal
15913 //
15914 // @note tcrayford: split out of ship_return_time_to_goal
ship_return_seconds_to_goal(ship * sp)15915 int ship_return_seconds_to_goal(ship *sp)
15916 {
15917 	ai_info	*aip;
15918 	int		time;
15919 	float		dist = 0.0f;
15920 	object	*objp;
15921 	float		min_speed, max_speed;
15922 
15923 	objp = &Objects[sp->objnum];
15924 	aip = &Ai_info[sp->ai_index];
15925 
15926 	min_speed = objp->phys_info.speed;
15927 
15928 	// Goober5000 - handle cap
15929 	if (aip->waypoint_speed_cap > 0)
15930 		max_speed = MIN(sp->current_max_speed, aip->waypoint_speed_cap);
15931 	else
15932 		max_speed = sp->current_max_speed;
15933 
15934 	if ( aip->mode == AIM_WAYPOINTS ) {
15935 		// Is traveling a waypoint path
15936 		min_speed = 0.9f * max_speed;
15937 		if (aip->wp_list != NULL) {
15938 			Assert(aip->wp_index != INVALID_WAYPOINT_POSITION);
15939 			dist += vm_vec_dist_quick(&objp->pos, aip->wp_list->get_waypoints()[aip->wp_index].get_pos());
15940 
15941 			SCP_vector<waypoint>::iterator ii;
15942 			vec3d *prev_vec = NULL;
15943 			for (ii = (aip->wp_list->get_waypoints().begin() + aip->wp_index); ii != aip->wp_list->get_waypoints().end(); ++ii) {
15944 				if (prev_vec != NULL) {
15945 					dist += vm_vec_dist_quick(ii->get_pos(), prev_vec);
15946 				}
15947 				prev_vec = ii->get_pos();
15948 			}
15949 		}
15950 
15951 		if ( dist < 1.0f) {
15952 			// Already there
15953 			time = 0;
15954 
15955 		} else if ( (Objects[sp->objnum].phys_info.speed <= 0) || (max_speed <= 0.0f) ) {
15956 			// Is not moving or can not move
15957 			time = -1;
15958 
15959 		} else {
15960 			// On the way!
15961 			float	speed;
15962 
15963 			speed = objp->phys_info.speed;
15964 
15965 			if (speed < min_speed)
15966 				speed = min_speed;
15967 			time = fl2i(dist/speed);
15968 		}
15969 
15970 	} else if ( (aip->mode == AIM_DOCK) && (aip->submode < AIS_DOCK_4) ) {
15971 		// Is traveling a docking path
15972 		time = hud_get_dock_time( objp );
15973 
15974 	} else {
15975 		// Not a waypoint or docking goal
15976 		time = -2;
15977 	}
15978 
15979 	return time;
15980 }
15981 
15982 /* Karajorma - V decided not to use this function so I've commented it out so it isn't confused with code
15983 +that is actually in use. Someone might want to get it working using AI_Profiles at some point so I didn't
15984 +simply delete it.
15985 
15986 // Called to check if any AI ships might reveal the cargo of any cargo containers.
15987 //
15988 // This is called once a frame, but a global timer 'Ship_cargo_check_timer' will limit this
15989 // function to being called every SHIP_CARGO_CHECK_INTERVAL ms.  I think that should be sufficient.
15990 //
15991 // NOTE: This function uses CARGO_REVEAL_DISTANCE from the HUD code... which is a multiple of
15992 //       the ship radius that is used to determine when cargo is detected.  AI ships do not
15993 //       have to have the ship targeted to reveal cargo.  The player is ignored in this function.
15994 #define SHIP_CARGO_CHECK_INTERVAL	1000
15995 void ship_check_cargo_all()
15996 {
15997 	object	*cargo_objp;
15998 	ship_obj	*cargo_so, *ship_so;
15999 	ship		*cargo_sp, *ship_sp;
16000 	float		dist_squared, limit_squared;
16001 
16002 	// I don't want to do this check every frame, so I made a global timer to limit check to
16003 	// every SHIP_CARGO_CHECK_INTERVAL ms.
16004 	if ( !timestamp_elapsed(Ship_cargo_check_timer) ) {
16005 		return;
16006 	} else {
16007 		Ship_cargo_check_timer = timestamp(SHIP_CARGO_CHECK_INTERVAL);
16008 	}
16009 
16010 	// Check all friendly fighter/bombers against all non-friendly cargo containers that don't have
16011 	// cargo revealed
16012 
16013 	// for now just locate a capital ship on the same team:
16014 	cargo_so = GET_FIRST(&Ship_obj_list);
16015 	while(cargo_so != END_OF_LIST(&Ship_obj_list)){
16016 		cargo_sp = &Ships[Objects[cargo_so->objnum].instance];
16017 		if ( (Ship_info[cargo_sp->ship_info_index].flags[Ship::Info_Flags::Cargo]) && (cargo_sp->team != Player_ship->team) ) {
16018 
16019 			// If the cargo is revealed, continue on to next hostile cargo
16020 			if ( cargo_sp->flags[Ship::Ship_Flags::Cargo_revealed] ) {
16021 				goto next_cargo;
16022 			}
16023 
16024 			// check against friendly fighter/bombers + cruiser/freighter/transport
16025 			// IDEA: could cull down to fighter/bomber if we want this to run a bit quicker
16026 			for ( ship_so=GET_FIRST(&Ship_obj_list); ship_so != END_OF_LIST(&Ship_obj_list); ship_so=GET_NEXT(ship_so) )
16027 			{
16028 				ship_sp = &Ships[Objects[ship_so->objnum].instance];
16029 				// only consider friendly ships
16030 				if (ship_sp->team != Player_ship->team) {
16031 					continue;
16032 				}
16033 
16034 				// ignore the player
16035 				if ( ship_so->objnum == OBJ_INDEX(Player_obj) ) {
16036 					continue;
16037 				}
16038 
16039 				// if this ship is a small or big ship
16040 				if ( Ship_info[ship_sp->ship_info_index].flags & (SIF_SMALL_SHIP|SIF_BIG_SHIP) ) {
16041 					cargo_objp = &Objects[cargo_sp->objnum];
16042 					// use square of distance, faster than getting real distance (which will use sqrt)
16043 					dist_squared = vm_vec_dist_squared(&cargo_objp->pos, &Objects[ship_sp->objnum].pos);
16044 					limit_squared = (cargo_objp->radius+CARGO_RADIUS_DELTA)*(cargo_objp->radius+CARGO_RADIUS_DELTA);
16045 					if ( dist_squared <= MAX(limit_squared, CARGO_REVEAL_MIN_DIST*CARGO_REVEAL_MIN_DIST) ) {
16046 						ship_do_cargo_revealed( cargo_sp );
16047 						break;	// break out of for loop, move on to next hostile cargo
16048 					}
16049 				}
16050 			} // end for
16051 		}
16052 next_cargo:
16053 		cargo_so = GET_NEXT(cargo_so);
16054 	} // end while
16055 }
16056 */
16057 
16058 
16059 // Maybe warn player about this attacking ship.  This is called once per frame, and the
16060 // information about the closest attacking ship comes for free, since this function is called
16061 // from HUD code which has already determined the closest enemy attacker and the distance.
16062 //
16063 // input:	enemy_sp	=>	ship pointer to the TEAM_ENEMY ship attacking the player
16064 //				dist		=>	the distance of the enemy to the player
16065 //
16066 // NOTE: there are no filters on enemy_sp, so it could be any ship type
16067 //
16068 #define PLAYER_CHECK_WARN_INTERVAL		300		// how often we check for warnings
16069 #define PLAYER_MIN_WARN_DIST				100		// minimum distance attacking ship can be from player and still allow warning
16070 #define PLAYER_MAX_WARN_DIST				1000		// maximum distance attacking ship can be from plyaer and still allow warning
16071 
ship_maybe_warn_player(ship * enemy_sp,float dist)16072 void ship_maybe_warn_player(ship *enemy_sp, float dist)
16073 {
16074 	float		fdot; //, rdot, udot;
16075 	vec3d	vec_to_target;
16076 	int		msg_type; //, on_right;
16077 
16078 	// First check if the player has reached the maximum number of warnings for a mission
16079 	if ((Builtin_messages[MESSAGE_CHECK_6].max_count > -1) && ( Player->warn_count >= Builtin_messages[MESSAGE_CHECK_6].max_count )) {
16080 		return;
16081 	}
16082 
16083 	// Check if enough time has elapsed since last warning, if not - leave
16084 	if ( !timestamp_elapsed(Player->allow_warn_timestamp) ) {
16085 		return;
16086 	}
16087 
16088 	// Check to see if check timer has elapsed.  Necessary, since we don't want to check each frame
16089 	if ( !timestamp_elapsed(Player->check_warn_timestamp ) ) {
16090 		return;
16091 	}
16092 	Player->check_warn_timestamp = timestamp(PLAYER_CHECK_WARN_INTERVAL);
16093 
16094 	// only allow warnings if within a certain distance range
16095 	if ( dist < PLAYER_MIN_WARN_DIST || dist > PLAYER_MAX_WARN_DIST ) {
16096 		return;
16097 	}
16098 
16099 	// only warn if a fighter or bomber is attacking the player
16100 	if ( !(Ship_info[enemy_sp->ship_info_index].is_small_ship()) ) {
16101 		return;
16102 	}
16103 
16104 	// get vector from player to target
16105 	vm_vec_normalized_dir(&vec_to_target, &Objects[enemy_sp->objnum].pos, &Eye_position);
16106 
16107 	// ensure that enemy fighter is oriented towards player
16108 	fdot = vm_vec_dot(&Objects[enemy_sp->objnum].orient.vec.fvec, &vec_to_target);
16109 	if ( fdot > -0.7 ) {
16110 		return;
16111 	}
16112 
16113 	fdot = vm_vec_dot(&Player_obj->orient.vec.fvec, &vec_to_target);
16114 
16115 	msg_type = -1;
16116 
16117 	// check if attacking ship is on six.  return if not far enough behind player.
16118 	if ( fdot > -0.7 )
16119 		return;
16120 
16121 	msg_type = MESSAGE_CHECK_6;
16122 
16123 	if ( msg_type != -1 ) {
16124 		int ship_index;
16125 
16126 		// multiplayer tvt - this is client side.
16127 		if(MULTI_TEAM && (Net_player != NULL)){
16128 			ship_index = ship_get_random_player_wing_ship( SHIP_GET_UNSILENCED, 0.0f, -1, 0, Net_player->p_info.team );
16129 		} else {
16130 			ship_index = ship_get_random_player_wing_ship( SHIP_GET_UNSILENCED );
16131 		}
16132 
16133 		if ( ship_index >= 0 ) {
16134 			// multiplayer - make sure I just send to myself
16135 			if(Game_mode & GM_MULTIPLAYER){
16136 				message_send_builtin_to_player(msg_type, &Ships[ship_index], MESSAGE_PRIORITY_HIGH, MESSAGE_TIME_IMMEDIATE, 0, 0, MY_NET_PLAYER_NUM, -1);
16137 			} else {
16138 				message_send_builtin_to_player(msg_type, &Ships[ship_index], MESSAGE_PRIORITY_HIGH, MESSAGE_TIME_IMMEDIATE, 0, 0, -1, -1);
16139 			}
16140 			Player->allow_warn_timestamp = timestamp(Builtin_messages[MESSAGE_CHECK_6].min_delay);
16141 			Player->warn_count++;
16142 		}
16143 	}
16144 }
16145 
16146 // player has just killed a ship, maybe offer send a 'good job' message
ship_maybe_praise_player(ship * deader_sp)16147 void ship_maybe_praise_player(ship *deader_sp)
16148 {
16149 	if (Random::flip_coin()) {
16150 		return;
16151 	}
16152 
16153 	// First check if the player has reached the maximum number of praises for a mission
16154 	if ((Builtin_messages[MESSAGE_PRAISE].max_count > -1) && (Player->praise_count >= Builtin_messages[MESSAGE_PRAISE].max_count )) {
16155 		return;
16156 	}
16157 
16158 	// Check if enough time has elapsed since last praise, if not - leave
16159 	if ( !timestamp_elapsed(Player->allow_praise_timestamp) ) {
16160 		return;
16161 	}
16162 
16163 	// make sure player is not a traitor
16164 	if (Player_ship->team == Iff_traitor) {
16165 		return;
16166 	}
16167 
16168 	// only praise if killing an enemy!
16169 	if ( deader_sp->team == Player_ship->team ) {
16170 		return;
16171 	}
16172 
16173 	// don't praise the destruction of navbuoys, cargo or other non-flyable ship types
16174     if ((Ship_info[deader_sp->ship_info_index].class_type > 0) && !(Ship_types[Ship_info[deader_sp->ship_info_index].class_type].flags[Ship::Type_Info_Flags::Praise_destruction])) {
16175 		return;
16176 	}
16177 
16178 	// There is already a praise pending
16179 	if ( Player->praise_delay_timestamp ) {
16180 		return;
16181 	}
16182 
16183 	// We don't want to praise the player right away.. it is more realistic to wait a moment
16184 	Player->praise_delay_timestamp = timestamp_rand(1000, 2000);
16185 }
16186 
ship_maybe_praise_self(ship * deader_sp,ship * killer_sp)16187 void ship_maybe_praise_self(ship *deader_sp, ship *killer_sp)
16188 {
16189 	int j;
16190 	bool wingman = false;
16191 
16192 	if ( (int)(frand()*100) > Builtin_messages[MESSAGE_PRAISE_SELF].occurrence_chance ) {
16193 		return;
16194 	}
16195 
16196 	if (Game_mode & GM_MULTIPLAYER) {
16197 		return;
16198 	}
16199 
16200 	if ((Builtin_messages[MESSAGE_PRAISE_SELF].max_count > -1) && (Player->praise_self_count >= Builtin_messages[MESSAGE_PRAISE_SELF].max_count)) {
16201 		return;
16202 	}
16203 
16204 	if (!timestamp_elapsed(Player->praise_self_timestamp)) {
16205 		return;
16206 	}
16207 
16208 	// only praise if killing an enemy so check they both attack each other!
16209 	if (!((iff_x_attacks_y(deader_sp->team, killer_sp->team)) && (iff_x_attacks_y(killer_sp->team, deader_sp->team ))) ) {
16210 		return;
16211 	}
16212 
16213 
16214 	// only send messages from the player's wingmen
16215 	if (killer_sp->wingnum == -1) {
16216 		return;
16217 	}
16218 	for ( j = 0; j < MAX_STARTING_WINGS; j++ ) {
16219 		if ( Starting_wings[j] == killer_sp->wingnum) {
16220 			wingman = true;
16221 			break;
16222 		}
16223 	}
16224 
16225 	if (!wingman) {
16226 		return;
16227 	}
16228 
16229 	// don't praise the destruction of navbuoys, cargo or other non-flyable ship types
16230 	if ( (Ship_info[deader_sp->ship_info_index].class_type > 0) && !(Ship_types[Ship_info[deader_sp->ship_info_index].class_type].flags[Ship::Type_Info_Flags::Praise_destruction]) ) {
16231 		return;
16232 	}
16233 
16234 	// ensure the ship isn't silenced
16235 	if ( killer_sp->flags[Ship_Flags::No_builtin_messages] ) {
16236 		return;
16237 	}
16238 	if (The_mission.ai_profile->flags[AI::Profile_Flags::Check_comms_for_non_player_ships] && hud_communications_state(killer_sp) != COMM_OK) {
16239 		return;
16240 	}
16241 
16242 	message_send_builtin_to_player(MESSAGE_PRAISE_SELF, killer_sp, MESSAGE_PRIORITY_HIGH, MESSAGE_TIME_SOON, 0, 0, -1, -1);
16243 	Player->praise_self_timestamp = timestamp(Builtin_messages[MESSAGE_PRAISE_SELF].min_delay);
16244 	Player->praise_self_count++;
16245 }
16246 
16247 // -----------------------------------------------------------------------------
awacs_maybe_ask_for_help(ship * sp,int multi_team_filter)16248 static void awacs_maybe_ask_for_help(ship *sp, int multi_team_filter)
16249 {
16250 	// Goober5000 - bail if not in main fs2 campaign
16251 	// (stupid coders... it's the FREDder's responsibility to add this message)
16252 	if (stricmp(Campaign.filename, "freespace2") != 0 || !(Game_mode & GM_CAMPAIGN_MODE))
16253 		return;
16254 
16255 	object *objp;
16256 	int message = -1;
16257 	objp = &Objects[sp->objnum];
16258 
16259 	if ( objp->hull_strength < ( (AWACS_HELP_HULL_LOW + 0.01f *(static_rand(OBJ_INDEX(objp)) & 5)) * sp->ship_max_hull_strength) ) {
16260 		// awacs ship below 25 + (0-4) %
16261 		if (!(sp->awacs_warning_flag[Ship::Awacs_Warning_Flags::Warn_25])) {
16262 			message = MESSAGE_AWACS_25;
16263             sp->awacs_warning_flag.set(Ship::Awacs_Warning_Flags::Warn_25);
16264 		}
16265 	} else if ( objp->hull_strength < ( (AWACS_HELP_HULL_HI + 0.01f*(static_rand(OBJ_INDEX(objp)) & 5)) * sp->ship_max_hull_strength) ) {
16266 		// awacs ship below 75 + (0-4) %
16267 		if (!(sp->awacs_warning_flag[Ship::Awacs_Warning_Flags::Warn_75])) {
16268 			message = MESSAGE_AWACS_75;
16269             sp->awacs_warning_flag.set(Ship::Awacs_Warning_Flags::Warn_75);
16270 		}
16271 	}
16272 
16273 	if (message >= 0) {
16274 		message_send_builtin_to_player(message, sp, MESSAGE_PRIORITY_HIGH, MESSAGE_TIME_IMMEDIATE, 0, 0, -1, multi_team_filter);
16275 		Player->allow_ask_help_timestamp = timestamp(Builtin_messages[MESSAGE_HELP].min_delay);
16276 		Player->ask_help_count++;
16277 	}
16278 }
16279 
16280 // -----------------------------------------------------------------------------
ship_maybe_ask_for_help(ship * sp)16281 void ship_maybe_ask_for_help(ship *sp)
16282 {
16283 	object *objp;
16284 	int multi_team_filter = -1;
16285 	ship_info* sip = &Ship_info[sp->ship_info_index];
16286 
16287 	// First check if the player has reached the maximum number of ask_help's for a mission
16288 	if ((Builtin_messages[MESSAGE_HELP].max_count > -1) && (Player->ask_help_count >= Builtin_messages[MESSAGE_HELP].max_count))
16289 		return;
16290 
16291 	// Check if enough time has elapsed since last help request, if not - leave
16292 	if (!timestamp_elapsed(Player->allow_ask_help_timestamp))
16293 		return;
16294 
16295 	// make sure player is on their team and not a traitor
16296 	if ((Player_ship->team != sp->team) || (Player_ship->team == Iff_traitor))
16297 		return;
16298 
16299 	objp = &Objects[sp->objnum];
16300 
16301 	// don't let the player ask for help!
16302 	if (objp->flags[Object::Object_Flags::Player_ship])
16303 		return;
16304 
16305 	// determine team filter if TvT
16306 	if(MULTI_TEAM)
16307 		multi_team_filter = sp->team;
16308 
16309 	// handle awacs ship as a special case
16310 	if (Ship_info[sp->ship_info_index].flags[Ship::Info_Flags::Has_awacs])
16311 	{
16312 		awacs_maybe_ask_for_help(sp, multi_team_filter);
16313 		return;
16314 	}
16315 
16316 	// for now, only have wingman ships request help
16317 	if (!(sp->flags[Ship_Flags::From_player_wing]))
16318 		return;
16319 
16320 	// first check if hull is at a critical level
16321 	if (objp->hull_strength < sip->ask_help_hull_percent * sp->ship_max_hull_strength)
16322 		goto play_ask_help;
16323 
16324 	// check if shields are near critical level
16325 	if (objp->flags[Object::Object_Flags::No_shields])
16326 		return;	// no shields on ship, no don't check shield levels
16327 
16328 	if (shield_get_strength(objp) > (sip->ask_help_shield_percent * shield_get_max_strength(objp)))
16329 		return;
16330 
16331 play_ask_help:
16332 
16333 	if (!(Ship_info[sp->ship_info_index].is_fighter_bomber())) //If we're still here, only continue if we're a fighter or bomber.
16334 		return;
16335 
16336 	// only unsilenced ships should ask for help
16337 	if (sp->flags[Ship_Flags::No_builtin_messages])
16338 		return;
16339 	if (The_mission.ai_profile->flags[AI::Profile_Flags::Check_comms_for_non_player_ships] && hud_communications_state(&Ships[objp->instance]) <= COMM_DAMAGED)
16340 		return;
16341 
16342 	message_send_builtin_to_player(MESSAGE_HELP, sp, MESSAGE_PRIORITY_HIGH, MESSAGE_TIME_IMMEDIATE, 0, 0, -1, multi_team_filter);
16343 	Player->allow_ask_help_timestamp = timestamp(Builtin_messages[MESSAGE_HELP].min_delay);
16344 
16345 	// prevent overlap with death message
16346 	if (timestamp_until(Player->allow_scream_timestamp) < 15000)
16347 		Player->allow_scream_timestamp = timestamp(15000);
16348 
16349 	Player->ask_help_count++;
16350 }
16351 
16352 /**
16353  * The player has just entered death roll, maybe have wingman mourn the loss of the player
16354  */
ship_maybe_lament()16355 void ship_maybe_lament()
16356 {
16357 	int ship_index;
16358 
16359 	// no. because in multiplayer, its funny
16360 	if (Game_mode & GM_MULTIPLAYER)
16361 		return;
16362 
16363 	if (Random::next(4) == 0)
16364 	{
16365 		ship_index = ship_get_random_player_wing_ship(SHIP_GET_UNSILENCED);
16366 		if (ship_index >= 0)
16367 			message_send_builtin_to_player(MESSAGE_PLAYER_DIED, &Ships[ship_index], MESSAGE_PRIORITY_HIGH, MESSAGE_TIME_IMMEDIATE, 0, 0, -1, -1);
16368 	}
16369 }
16370 
16371 /**
16372  * Play a death scream for a ship
16373  */
ship_scream(ship * sp)16374 void ship_scream(ship *sp)
16375 {
16376 	int multi_team_filter = -1;
16377 
16378 	// bogus
16379 	if (sp == NULL)
16380 		return;
16381 
16382 	// multiplayer tvt
16383 	if (MULTI_TEAM)
16384 		multi_team_filter = sp->team;
16385 
16386 	// Bail if the ship is silenced
16387 	if (sp->flags[Ship_Flags::No_builtin_messages])
16388 		return;
16389 	if (The_mission.ai_profile->flags[AI::Profile_Flags::Check_comms_for_non_player_ships] && hud_communications_state(sp, true) <= COMM_DAMAGED)
16390 		return;
16391 
16392 	message_send_builtin_to_player(MESSAGE_WINGMAN_SCREAM, sp, MESSAGE_PRIORITY_HIGH, MESSAGE_TIME_IMMEDIATE, 0, 0, -1, multi_team_filter);
16393 	Player->allow_scream_timestamp = timestamp(Builtin_messages[MESSAGE_WINGMAN_SCREAM].min_delay);
16394 	Player->scream_count++;
16395 
16396 	sp->flags.set(Ship_Flags::Ship_has_screamed);
16397 
16398 	// prevent overlap with help messages
16399 	if (timestamp_until(Player->allow_ask_help_timestamp) < 15000)
16400 		Player->allow_ask_help_timestamp = timestamp(15000);
16401 }
16402 
16403 /**
16404  * Ship has just died, maybe play a scream.
16405  */
ship_maybe_scream(ship * sp)16406 void ship_maybe_scream(ship *sp)
16407 {
16408 	// bail if screaming is disabled
16409 	if (sp->flags[Ship_Flags::No_death_scream])
16410 		return;
16411 
16412 	// if screaming is enabled, skip all checks
16413 	if (!(sp->flags[Ship_Flags::Always_death_scream]))
16414 	{
16415 		// only scream x% of the time
16416 		if ( (int)(frand()*100) > Builtin_messages[MESSAGE_WINGMAN_SCREAM].occurrence_chance ) {
16417 			return;
16418 		}
16419 
16420 		// check if enough time has elapsed since last scream; if not, leave
16421 		if (!timestamp_elapsed(Player->allow_scream_timestamp))
16422 			return;
16423 
16424 		// for WCSaga, only do a subset of the checks
16425 		if (!(The_mission.ai_profile->flags[AI::Profile_Flags::Perform_fewer_scream_checks]))
16426 		{
16427 			// bail if this ship isn't from the player wing
16428 			if (!(sp->flags[Ship_Flags::From_player_wing]))
16429 				return;
16430 
16431 			// first check if the player has reached the maximum number of screams for a mission
16432 			if ((Builtin_messages[MESSAGE_WINGMAN_SCREAM].max_count > -1) && (Player->scream_count >= Builtin_messages[MESSAGE_WINGMAN_SCREAM].max_count)) {
16433 				return;
16434 			}
16435 
16436 			// if on different teams (i.e. team v. team games in multiplayer), no scream
16437 			if (Player_ship->team != sp->team)
16438 				return;
16439 		}
16440 	}
16441 
16442 	ship_scream(sp);
16443 }
16444 
16445 // maybe tell player that we're running low on ammo
16446 #define PLAYER_LOW_AMMO_MSG_INTERVAL		250000
16447 #define PLAYER_REQUEST_REPAIR_MSG_INTERVAL	240000
16448 #define PLAYER_MAX_LOW_AMMO_MSGS			5
16449 
16450 /**
16451  * This function is only for notifying the player that the ship's ammo is low, without necessarily requesting support (e.g. if support
16452  * is disallowed for this mission).  It is not called if support was successfully requested in maybe_request_support.
16453  */
ship_maybe_tell_about_low_ammo(ship * sp)16454 void ship_maybe_tell_about_low_ammo(ship *sp)
16455 {
16456 	weapon_info *wip;
16457 	int i;
16458 	ship_weapon *swp;
16459 	int multi_team_filter = -1;
16460 
16461 	// we don't want a ship complaining about low ammo after it has just complained about needing support
16462 	if (!timestamp_elapsed(Player->request_repair_timestamp))
16463 		return;
16464 
16465 	if (!timestamp_elapsed(Player->allow_ammo_timestamp))
16466 		return;
16467 
16468 	if (Player_ship->team == Iff_traitor)
16469 		return;
16470 
16471 	// Silent ships should remain just that
16472 	if (sp->flags[Ship_Flags::No_builtin_messages]) {
16473 		return;
16474 	}
16475 	if (The_mission.ai_profile->flags[AI::Profile_Flags::Check_comms_for_non_player_ships] && hud_communications_state(sp) <= COMM_DAMAGED)
16476 		return;
16477 
16478 	// don't mention low ammo if we're docked, for the same reason as in maybe_request_support
16479 	if (object_is_docked(&Objects[sp->objnum]))
16480 		return;
16481 
16482 	// for now, each ship can only complain about low ammo once a mission to stop it getting repetitive
16483 	if (sp->ammo_low_complaint_count) {
16484 		return;
16485 	}
16486 
16487 	if (Player->low_ammo_complaint_count >= PLAYER_MAX_LOW_AMMO_MSGS) {
16488 		return;
16489 	}
16490 
16491 	swp = &sp->weapons;
16492 
16493 	// stole the code for this from ship_maybe_tell_about_rearm()
16494 	for (i = 0; i < swp->num_primary_banks; i++)
16495 	{
16496 		wip = &Weapon_info[swp->primary_bank_weapons[i]];
16497 
16498 		if (wip->wi_flags[Weapon::Info_Flags::Ballistic])
16499 		{
16500 			if (swp->primary_bank_start_ammo[i] > 0)
16501 			{
16502 				if ((float)swp->primary_bank_ammo[i] / (float)swp->primary_bank_start_ammo[i] < 0.3f)
16503 				{
16504 					// multiplayer tvt
16505 					if(MULTI_TEAM) {
16506 						multi_team_filter = sp->team;
16507 					}
16508 
16509 					message_send_builtin_to_player(MESSAGE_PRIMARIES_LOW, sp, MESSAGE_PRIORITY_NORMAL, MESSAGE_TIME_SOON, 0, 0, -1, multi_team_filter);
16510 
16511 					Player->allow_ammo_timestamp = timestamp(PLAYER_LOW_AMMO_MSG_INTERVAL);
16512 
16513 					// better reset this one too
16514 					Player->request_repair_timestamp = timestamp(PLAYER_REQUEST_REPAIR_MSG_INTERVAL);
16515 
16516 					Player->low_ammo_complaint_count++;
16517 					sp->ammo_low_complaint_count++;
16518 					break;
16519 				}
16520 			}
16521 		}
16522 	}
16523 }
16524 
16525 
16526 /**
16527  * Tell player that we've requested a support ship
16528  */
ship_maybe_tell_about_rearm(ship * sp)16529 void ship_maybe_tell_about_rearm(ship *sp)
16530 {
16531 	weapon_info *wip;
16532 
16533 	if (!timestamp_elapsed(Player->request_repair_timestamp))
16534 		return;
16535 
16536 	if (Player_ship->team == Iff_traitor)
16537 		return;
16538 
16539 	// Silent ships should remain just that
16540 	if (sp->flags[Ship_Flags::No_builtin_messages])
16541 		return;
16542 	if (The_mission.ai_profile->flags[AI::Profile_Flags::Check_comms_for_non_player_ships] && hud_communications_state(sp) <= COMM_DAMAGED)
16543 		return;
16544 
16545 	// AL 1-4-98:	If ship integrity is low, tell player you want to get repaired.  Otherwise, tell
16546 	// the player you want to get re-armed.
16547 
16548 	int message_type = -1;
16549 	int heavily_damaged = (get_hull_pct(&Objects[sp->objnum]) < 0.4);
16550 
16551 	if (heavily_damaged || (sp->flags[Ship_Flags::Disabled]))
16552 	{
16553 		message_type = MESSAGE_REPAIR_REQUEST;
16554 	}
16555 	else
16556 	{
16557 		int i;
16558 		ship_weapon *swp;
16559 
16560 		swp = &sp->weapons;
16561 		for (i = 0; i < swp->num_secondary_banks; i++)
16562 		{
16563 			if (swp->secondary_bank_start_ammo[i] > 0)
16564 			{
16565 				if ((float)swp->secondary_bank_ammo[i] / (float)swp->secondary_bank_start_ammo[i] < 0.5f)
16566 				{
16567 					message_type = MESSAGE_REARM_REQUEST;
16568 					break;
16569 				}
16570 			}
16571 		}
16572 
16573 		// also check ballistic primaries - Goober5000
16574 		for (i = 0; i < swp->num_primary_banks; i++)
16575 		{
16576 			wip = &Weapon_info[swp->primary_bank_weapons[i]];
16577 
16578 			if (wip->wi_flags[Weapon::Info_Flags::Ballistic])
16579 			{
16580 				if (swp->primary_bank_start_ammo[i] > 0)
16581 				{
16582 					if ((float)swp->primary_bank_ammo[i] / (float)swp->primary_bank_start_ammo[i] < 0.3f)
16583 					{
16584 						message_type = MESSAGE_REARM_PRIMARIES;
16585 						break;
16586 					}
16587 				}
16588 			}
16589 		}
16590 	}
16591 
16592 	int multi_team_filter = -1;
16593 
16594 	// multiplayer tvt
16595 	if(MULTI_TEAM)
16596 		multi_team_filter = sp->team;
16597 
16598 
16599 	if (message_type >= 0)
16600 	{
16601 		if (Random::flip_coin())
16602 			message_send_builtin_to_player(message_type, sp, MESSAGE_PRIORITY_NORMAL, MESSAGE_TIME_SOON, 0, 0, -1, multi_team_filter);
16603 
16604 		Player->request_repair_timestamp = timestamp(PLAYER_REQUEST_REPAIR_MSG_INTERVAL);
16605 	}
16606 }
16607 
16608 // The current primary weapon or link status for a ship has changed.. notify clients if multiplayer
16609 //
16610 // input:	sp			=>	pointer to ship that modified primaries
ship_primary_changed(ship * sp)16611 void ship_primary_changed(ship *sp)
16612 {
16613 	int i;
16614 	ship_weapon	*swp;
16615 	swp = &sp->weapons;
16616 
16617 
16618 	if (sp->flags[Ship_Flags::Primary_linked]) {
16619 		// if we are linked now find any body who is down and flip them up
16620 		for (i = 0; i < MAX_SHIP_PRIMARY_BANKS; i++) {
16621 			if (swp->primary_animation_position[i] == MA_POS_NOT_SET) {
16622 				if ( model_anim_start_type(sp, AnimationTriggerType::PrimaryBank, i, 1) ) {
16623 					swp->primary_animation_done_time[i] = model_anim_get_time_type(sp, AnimationTriggerType::PrimaryBank, i);
16624 					swp->primary_animation_position[i] = MA_POS_SET;
16625 				} else {
16626 					swp->primary_animation_position[i] = MA_POS_READY;
16627 				}
16628 			}
16629 		}
16630 	} else {
16631 		// find anything that is up that shouldn't be
16632 		for (i = 0; i < MAX_SHIP_PRIMARY_BANKS; i++) {
16633 			if (i == swp->current_primary_bank) {
16634 				// if the current bank is down raise it up
16635 				if (swp->primary_animation_position[i] == MA_POS_NOT_SET) {
16636 					if ( model_anim_start_type(sp, AnimationTriggerType::PrimaryBank, i, 1) ) {
16637 						swp->primary_animation_done_time[i] = model_anim_get_time_type(sp, AnimationTriggerType::PrimaryBank, i);
16638 						swp->primary_animation_position[i] = MA_POS_SET;
16639 					} else {
16640 						swp->primary_animation_position[i] = MA_POS_READY;
16641 					}
16642 				}
16643 			} else {
16644 				// everyone else should be down, if they are not make them so
16645 				if (swp->primary_animation_position[i] != MA_POS_NOT_SET) {
16646 					model_anim_start_type(sp, AnimationTriggerType::PrimaryBank, i, -1);
16647 					swp->primary_animation_position[i] = MA_POS_NOT_SET;
16648 				}
16649 			}
16650 		}
16651 	}
16652 
16653 #if 0
16654 	// we only need to deal with multiplayer issues for now, so bail it not multiplayer
16655 	if ( !(Game_mode & GM_MULTIPLAYER) )
16656 		return;
16657 
16658 	Assert(sp);
16659 
16660 	if ( MULTIPLAYER_MASTER )
16661 		send_ship_weapon_change( sp, MULTI_PRIMARY_CHANGED, swp->current_primary_bank, (sp->flags[Ship::Ship_Flags::Primary_linked])?1:0 );
16662 #endif
16663 }
16664 
16665 // The current secondary weapon or dual-fire status for a ship has changed.. notify clients if multiplayer
16666 //
16667 // input:	sp					=>	pointer to ship that modified secondaries
ship_secondary_changed(ship * sp)16668 void ship_secondary_changed(ship *sp)
16669 {
16670 	Assert( sp != NULL );
16671 
16672 	int i;
16673 	ship_weapon	*swp = &sp->weapons;
16674 
16675 	// find anything that is up that shouldn't be
16676 	if (timestamp() > 10) {
16677 		for (i = 0; i < MAX_SHIP_SECONDARY_BANKS; i++) {
16678 			if (i == swp->current_secondary_bank) {
16679 				// if the current bank is down raise it up
16680 				if (swp->secondary_animation_position[i] == MA_POS_NOT_SET) {
16681 					if ( model_anim_start_type(sp, AnimationTriggerType::SecondaryBank, i, 1) ) {
16682 						swp->secondary_animation_done_time[i] = model_anim_get_time_type(sp, AnimationTriggerType::SecondaryBank, i);
16683 						swp->secondary_animation_position[i] = MA_POS_SET;
16684 					} else {
16685 						swp->secondary_animation_position[i] = MA_POS_READY;
16686 					}
16687 				}
16688 			} else {
16689 				// everyone else should be down, if they are not make them so
16690 				if (swp->secondary_animation_position[i] != MA_POS_NOT_SET) {
16691 					model_anim_start_type(sp, AnimationTriggerType::SecondaryBank, i, -1);
16692 					swp->secondary_animation_position[i] = MA_POS_NOT_SET;
16693 				}
16694 			}
16695 		}
16696 	}
16697 
16698 #if 0
16699 	// we only need to deal with multiplayer issues for now, so bail it not multiplayer
16700 	if ( !(Game_mode & GM_MULTIPLAYER) ){
16701 		return;
16702 	}
16703 
16704 	Assert(sp);
16705 
16706 	if ( MULTIPLAYER_MASTER )
16707 		send_ship_weapon_change( sp, MULTI_SECONDARY_CHANGED, swp->current_secondary_bank, (sp->flags[Ship::Ship_Flags::Secondary_dual_fire])?1:0 );
16708 #endif
16709 }
16710 
ship_get_SIF(ship * shipp)16711 flagset<Ship::Info_Flags> ship_get_SIF(ship *shipp)
16712 {
16713 	return Ship_info[shipp->ship_info_index].flags;
16714 }
16715 
ship_get_SIF(int sh)16716 flagset<Ship::Info_Flags> ship_get_SIF(int sh)
16717 {
16718 	return Ship_info[Ships[sh].ship_info_index].flags;
16719 }
16720 
ship_get_by_signature(int signature)16721 int ship_get_by_signature(int signature)
16722 {
16723 	ship_obj *so;
16724 
16725 	for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) {
16726 		// if we found a matching ship object signature
16727 		if((Objects[so->objnum].signature == signature) && (Objects[so->objnum].type == OBJ_SHIP)){
16728 			return Objects[so->objnum].instance;
16729 		}
16730 	}
16731 
16732 	// couldn't find the ship
16733 	return -1;
16734 }
16735 
ship_get_type_info(object * objp)16736 ship_type_info *ship_get_type_info(object *objp)
16737 {
16738 	Assert(objp != NULL);
16739 	Assert(objp->type == OBJ_SHIP);
16740 	Assert(objp->instance > -1);
16741 	Assert(Ships[objp->instance].ship_info_index > -1);
16742 	Assert(Ship_info[Ships[objp->instance].ship_info_index].class_type > -1);
16743 
16744 	return &Ship_types[Ship_info[Ships[objp->instance].ship_info_index].class_type];
16745 }
16746 
16747 /**
16748  * Called when the cargo of a ship is revealed.
16749  *
16750  * Happens at two different locations (at least when this function was written), one for the player, and one for AI ships.
16751  * Need to send stuff to clients in multiplayer game.
16752  */
ship_do_cargo_revealed(ship * shipp,int from_network)16753 void ship_do_cargo_revealed( ship *shipp, int from_network )
16754 {
16755 	// don't do anything if we already know the cargo
16756 	if ( shipp->flags[Ship_Flags::Cargo_revealed] ){
16757 		return;
16758 	}
16759 
16760 	nprintf(("Network", "Revealing cargo for %s\n", shipp->ship_name));
16761 
16762 	// send the packet if needed
16763 	if ( (Game_mode & GM_MULTIPLAYER) && !from_network ){
16764 		send_cargo_revealed_packet( shipp );
16765 	}
16766 
16767 	shipp->flags.set(Ship_Flags::Cargo_revealed);
16768 	shipp->time_cargo_revealed = Missiontime;
16769 
16770 	// if the cargo is something other than "nothing", then make a log entry
16771 	if ( stricmp(Cargo_names[shipp->cargo1 & CARGO_INDEX_MASK], NOX("nothing")) != 0 ){
16772 		mission_log_add_entry(LOG_CARGO_REVEALED, shipp->ship_name, NULL, (shipp->cargo1 & CARGO_INDEX_MASK) );
16773 	}
16774 }
16775 
ship_do_cap_subsys_cargo_revealed(ship * shipp,ship_subsys * subsys,int from_network)16776 void ship_do_cap_subsys_cargo_revealed( ship *shipp, ship_subsys *subsys, int from_network )
16777 {
16778 	// don't do anything if we already know the cargo
16779 	if (subsys->flags[Ship::Subsystem_Flags::Cargo_revealed]) {
16780 		return;
16781 	}
16782 
16783 	nprintf(("Network", "Revealing cap ship subsys cargo for %s\n", shipp->ship_name));
16784 
16785 	// send the packet if needed
16786 	if ( (Game_mode & GM_MULTIPLAYER) && !from_network ){
16787 		int subsystem_index = ship_get_index_from_subsys(subsys, shipp->objnum);
16788 		send_subsystem_cargo_revealed_packet( shipp, subsystem_index );
16789 	}
16790 
16791     subsys->flags.set(Ship::Subsystem_Flags::Cargo_revealed);
16792 	subsys->time_subsys_cargo_revealed = Missiontime;
16793 
16794 	// if the cargo is something other than "nothing", then make a log entry
16795 	if ( stricmp(Cargo_names[subsys->subsys_cargo_name & CARGO_INDEX_MASK], NOX("nothing")) != 0 ){
16796 		mission_log_add_entry(LOG_CAP_SUBSYS_CARGO_REVEALED, shipp->ship_name, subsys->system_info->subobj_name, (subsys->subsys_cargo_name & CARGO_INDEX_MASK) );
16797 	}
16798 }
16799 
16800 /**
16801  * alled when the cargo of a ship is hidden by the sexp.
16802  *
16803  * Need to send stuff to clients in multiplayer game.
16804  */
ship_do_cargo_hidden(ship * shipp,int from_network)16805 void ship_do_cargo_hidden( ship *shipp, int from_network )
16806 {
16807 	// don't do anything if the cargo is already hidden
16808 	if ( !(shipp->flags[Ship_Flags::Cargo_revealed]) )
16809 	{
16810 		return;
16811 	}
16812 
16813 	nprintf(("Network", "Hiding cargo for %s\n", shipp->ship_name));
16814 
16815 	// send the packet if needed
16816 	if ( (Game_mode & GM_MULTIPLAYER) && !from_network ){
16817 		send_cargo_hidden_packet( shipp );
16818 	}
16819 
16820 	shipp->flags.remove(Ship_Flags::Cargo_revealed);
16821 
16822 	// don't log that the cargo was hidden and don't reset the time cargo revealed
16823 }
16824 
ship_do_cap_subsys_cargo_hidden(ship * shipp,ship_subsys * subsys,int from_network)16825 void ship_do_cap_subsys_cargo_hidden( ship *shipp, ship_subsys *subsys, int from_network )
16826 {
16827 	// don't do anything if the cargo is already hidden
16828 	if (!(subsys->flags[Ship::Subsystem_Flags::Cargo_revealed]))
16829 	{
16830 		return;
16831 	}
16832 
16833 	nprintf(("Network", "Hiding cap ship subsys cargo for %s\n", shipp->ship_name));
16834 
16835 	// send the packet if needed
16836 	if ( (Game_mode & GM_MULTIPLAYER) && !from_network ){
16837 		int subsystem_index = ship_get_index_from_subsys(subsys, shipp->objnum);
16838 		send_subsystem_cargo_hidden_packet( shipp, subsystem_index );
16839 	}
16840 
16841     subsys->flags.remove(Ship::Subsystem_Flags::Cargo_revealed);
16842 
16843 	// don't log that the cargo was hidden and don't reset the time cargo revealed
16844 }
16845 
16846 /**
16847  * Return the range of the currently selected secondary weapon
16848  *
16849  * NOTE: If there is no missiles left in the current bank, range returned is 0
16850  *
16851  * @param shipp Pointer to ship from which currently selected secondary weapon will be ranged
16852  */
ship_get_secondary_weapon_range(ship * shipp)16853 float ship_get_secondary_weapon_range(ship *shipp)
16854 {
16855 	float srange=0.0f;
16856 
16857 	ship_weapon	*swp;
16858 	swp = &shipp->weapons;
16859 	if ( swp->current_secondary_bank >= 0 ) {
16860 		weapon_info	*wip;
16861 		int bank=swp->current_secondary_bank;
16862 		if (swp->secondary_bank_weapons[bank] >= 0) {
16863 			wip = &Weapon_info[swp->secondary_bank_weapons[bank]];
16864 			if ( swp->secondary_bank_ammo[bank] > 0 ) {
16865 				srange = wip->max_speed * wip->lifetime;
16866 			}
16867 		}
16868 	}
16869 
16870 	return srange;
16871 }
16872 
16873 /**
16874  * Determine the number of primary ammo units allowed max for a ship
16875  */
get_max_ammo_count_for_primary_bank(int ship_class,int bank,int ammo_type)16876 int get_max_ammo_count_for_primary_bank(int ship_class, int bank, int ammo_type)
16877 {
16878 	Assertion(ship_class < ship_info_size(), "Invalid ship_class of %d is >= Ship_info.size() (%d); get a coder!\n", ship_class, ship_info_size());
16879 	Assertion(bank < MAX_SHIP_PRIMARY_BANKS, "Invalid primary bank of %d (max is %d); get a coder!\n", bank, MAX_SHIP_PRIMARY_BANKS - 1);
16880 	Assertion(ammo_type < weapon_info_size(), "Invalid ammo_type of %d is >= Weapon_info.size() (%d); get a coder!\n", ammo_type, weapon_info_size());
16881 
16882 	// Invalid and non-existent weapons, and non-ballistic weapons, have capacities of 0, per ship_weapon::clear()
16883 	if (ship_class < 0 || bank < 0 || ammo_type < 0 || !(Weapon_info[ammo_type].wi_flags[Weapon::Info_Flags::Ballistic]))
16884 		return 0;
16885 
16886 	float capacity = (float)Ship_info[ship_class].primary_bank_ammo_capacity[bank];
16887 	float size = (float)Weapon_info[ammo_type].cargo_size;
16888 	Assertion(size > 0.0f, "Weapon cargo size for %s must be greater than 0!", Weapon_info[ammo_type].name);
16889 	return (int)std::lround(capacity / size);
16890 }
16891 
16892 /**
16893  * Determine the number of secondary ammo units (missile/bomb) allowed max for a ship
16894  */
get_max_ammo_count_for_bank(int ship_class,int bank,int ammo_type)16895 int get_max_ammo_count_for_bank(int ship_class, int bank, int ammo_type)
16896 {
16897 	Assertion(ship_class < ship_info_size(), "Invalid ship_class of %d is >= Ship_info.size() (%d); get a coder!\n", ship_class, ship_info_size());
16898 	Assertion(bank < MAX_SHIP_SECONDARY_BANKS, "Invalid secondary bank of %d (max is %d); get a coder!\n", bank, MAX_SHIP_SECONDARY_BANKS - 1);
16899 	Assertion(ammo_type < weapon_info_size(), "Invalid ammo_type of %d is >= Weapon_info.size() (%d); get a coder!\n", ammo_type, weapon_info_size());
16900 
16901 	// Invalid and non-existent weapons have capacities of 0, per ship_weapon::clear()
16902 	if (ship_class < 0 || bank < 0 || ammo_type < 0)
16903 		return 0;
16904 
16905 	float capacity = (float)Ship_info[ship_class].secondary_bank_ammo_capacity[bank];
16906 	float size = (float)Weapon_info[ammo_type].cargo_size;
16907 	Assertion(size > 0.0f, "Weapon cargo size for %s must be greater than 0!", Weapon_info[ammo_type].name);
16908 	return (int)std::lround(capacity / size);
16909 }
16910 
16911 /**
16912  * The same as above, but for a specific turret's bank.
16913  */
get_max_ammo_count_for_turret_bank(ship_weapon * swp,int bank,int ammo_type)16914 int get_max_ammo_count_for_turret_bank(ship_weapon *swp, int bank, int ammo_type)
16915 {
16916 	float capacity, size;
16917 
16918 	Assertion(bank < MAX_SHIP_SECONDARY_BANKS, "Invalid secondary bank of %d (max is %d); get a coder!\n", bank, MAX_SHIP_SECONDARY_BANKS - 1);
16919 	Assertion(ammo_type < weapon_info_size(), "Invalid ammo_type of %d is >= Weapon_info.size() (%d); get a coder!\n", ammo_type, weapon_info_size());
16920 
16921 	if (!swp || bank < 0 || ammo_type < 0) {
16922 		return 0;
16923 	} else {
16924 		capacity = (float) swp->secondary_bank_capacity[bank];
16925 		size = (float) Weapon_info[ammo_type].cargo_size;
16926 		return (int) (capacity / size);
16927 	}
16928 }
16929 
16930 /**
16931  * Page in bitmaps for all the ships in this level
16932  */
ship_page_in()16933 void ship_page_in()
16934 {
16935 	TRACE_SCOPE(tracing::ShipPageIn);
16936 
16937 	int i, j, k;
16938 	int num_subsystems_needed = 0;
16939 
16940 	int *ship_class_used = NULL;
16941 
16942 	ship_class_used = new int[Ship_info.size()];
16943 
16944 	Verify( ship_class_used != NULL );
16945 
16946 	// Mark all ship classes as not used
16947 	memset( ship_class_used, 0, Ship_info.size() * sizeof(int) );
16948 
16949 	// Mark any support ship types as used
16950 	for (auto sip = Ship_info.begin(); sip != Ship_info.end(); ++sip) {
16951 		if (sip->flags[Ship::Info_Flags::Support]) {
16952 			nprintf(( "Paging", "Found support ship '%s'\n", sip->name ));
16953 			i = (int)std::distance(Ship_info.begin(), sip);
16954 			ship_class_used[i]++;
16955 
16956 			num_subsystems_needed += sip->n_subsystems;
16957 
16958 			// load the darn model and page in textures
16959 			sip->model_num = model_load(sip->pof_file, sip->n_subsystems, &sip->subsystems[0]);
16960 
16961 			if (sip->model_num >= 0) {
16962 				model_page_in_textures(sip->model_num, i);
16963 			}
16964 		}
16965 	}
16966 
16967 	// Mark any ships in the mission as used
16968 	for (i = 0; i < MAX_SHIPS; i++)	{
16969 		if (Ships[i].objnum < 0)
16970 			continue;
16971 
16972 		nprintf(( "Paging","Found ship '%s'\n", Ships[i].ship_name ));
16973 		ship_class_used[Ships[i].ship_info_index]++;
16974 
16975 		// check if we are going to use a Knossos device and make sure the special warp ani gets pre-loaded
16976 		if ( Ship_info[Ships[i].ship_info_index].flags[Ship::Info_Flags::Knossos_device] )
16977 			Knossos_warp_ani_used = 1;
16978 
16979 		// mark any weapons as being used, saves memory and time if we don't load them all
16980 		ship_weapon *swp = &Ships[i].weapons;
16981 
16982 		for (j = 0; j < swp->num_primary_banks; j++)
16983 			weapon_mark_as_used(swp->primary_bank_weapons[j]);
16984 
16985 		for (j = 0; j < swp->num_secondary_banks; j++)
16986 			weapon_mark_as_used(swp->secondary_bank_weapons[j]);
16987 
16988 		// get weapons for all capship subsystems (turrets)
16989 		ship_subsys *ptr = GET_FIRST(&Ships[i].subsys_list);
16990 
16991 		while (ptr != END_OF_LIST(&Ships[i].subsys_list)) {
16992 			for (k = 0; k < MAX_SHIP_PRIMARY_BANKS; k++)
16993 				weapon_mark_as_used(ptr->weapons.primary_bank_weapons[k]);
16994 
16995 			for (k = 0; k < MAX_SHIP_SECONDARY_BANKS; k++)
16996 				weapon_mark_as_used(ptr->weapons.secondary_bank_weapons[k]);
16997 
16998 			ptr = GET_NEXT(ptr);
16999 		}
17000 
17001 		ship_info *sip = &Ship_info[Ships[i].ship_info_index];
17002 
17003 		// page in all of the textures if the model is already loaded
17004 		if (sip->model_num >= 0) {
17005 			nprintf(( "Paging", "Paging in textures for ship '%s'\n", Ships[i].ship_name ));
17006 			model_page_in_textures(sip->model_num, Ships[i].ship_info_index);
17007 			// need to make sure and do this again, after we are sure that all of the textures are ready
17008 			ship_init_afterburners( &Ships[i] );
17009 		}
17010 
17011 		//WMC - Since this is already in-mission, ignore the warpin effect.
17012 		Ships[i].warpout_effect->pageIn();
17013 
17014 		// don't need this one anymore, it's already been accounted for
17015 	//	num_subsystems_needed += Ship_info[Ships[i].ship_info_index].n_subsystems;
17016 	}
17017 
17018 	// Mark any ships that might warp in in the future as used
17019 	for (p_object *p_objp = GET_FIRST(&Ship_arrival_list); p_objp != END_OF_LIST(&Ship_arrival_list); p_objp = GET_NEXT(p_objp)) {
17020 		nprintf(( "Paging", "Found future arrival ship '%s'\n", p_objp->name ));
17021 		ship_class_used[p_objp->ship_class]++;
17022 
17023 		// This will go through Subsys_index[] and grab all weapons: primary, secondary, and turrets
17024 		for (i = p_objp->subsys_index; i < (p_objp->subsys_index + p_objp->subsys_count); i++) {
17025 			for (j = 0; j < MAX_SHIP_PRIMARY_BANKS; j++) {
17026 				if (Subsys_status[i].primary_banks[j] >= 0)
17027 					weapon_mark_as_used(Subsys_status[i].primary_banks[j]);
17028 			}
17029 
17030 			for (j = 0; j < MAX_SHIP_SECONDARY_BANKS; j++) {
17031 				if (Subsys_status[i].secondary_banks[j] >= 0)
17032 					weapon_mark_as_used(Subsys_status[i].secondary_banks[j]);
17033 			}
17034 		}
17035 
17036 		// page in any replacement textures
17037 		if (Ship_info[p_objp->ship_class].model_num >= 0) {
17038 			nprintf(( "Paging", "Paging in textures for future arrival ship '%s'\n", p_objp->name ));
17039 			model_page_in_textures(Ship_info[p_objp->ship_class].model_num, p_objp->ship_class);
17040 		}
17041 
17042 		num_subsystems_needed += Ship_info[p_objp->ship_class].n_subsystems;
17043 	}
17044 
17045 	// pre-allocate the subsystems, this really only needs to happen for ships
17046 	// which don't exist yet (ie, ships NOT in Ships[])
17047 	if (!ship_allocate_subsystems(num_subsystems_needed, true)) {
17048 		Error(LOCATION, "Attempt to page in new subsystems subsystems failed, which shouldn't be possible anymore. Currently allocated %d subsystems (%d in use)", Num_ship_subsystems_allocated, Num_ship_subsystems);
17049 	}
17050 
17051 	mprintf(("About to page in ships!\n"));
17052 
17053 	// Page in all the ship classes that are used on this level
17054 	int num_ship_types_used = 0;
17055 	int test_id __UNUSED = -1;
17056 
17057 	memset( fireball_used, 0, sizeof(int) * MAX_FIREBALL_TYPES );
17058 
17059 	i = 0;
17060 	for (auto sip = Ship_info.begin(); sip != Ship_info.end(); i++, ++sip) {
17061 		if ( !ship_class_used[i] )
17062 			continue;
17063 
17064 		int model_previously_loaded = -1;
17065 		int ship_previously_loaded = -1;
17066 
17067 		num_ship_types_used++;
17068 
17069 		// Page in the small hud icons for each ship
17070 		hud_ship_icon_page_in(&(*sip));
17071 
17072 		// See if this model was previously loaded by another ship
17073 		for (auto it = Ship_info.begin(); it != Ship_info.end(); ++it) {
17074 			if ( (it->model_num > -1) && !stricmp(sip->pof_file, it->pof_file) ) {
17075 				// Model already loaded
17076 				model_previously_loaded = it->model_num;
17077 
17078 				if ((sip->n_subsystems > 0) && (sip->subsystems[0].model_num > -1)) {
17079 					ship_previously_loaded = (int)std::distance(Ship_info.begin(), it);
17080 
17081 					// It is possible in some cases for sip->model_num to change, and for subsystems->model_num
17082 					// to still point to the old model index; this makes sure it doesn't happen. -zookeeper
17083 					for (k = 0; k < sip->n_subsystems; k++) {
17084 						if (sip->model_num != sip->subsystems[k].model_num) {
17085 							mprintf(("Ship %s has model_num %i but its subsystem %s has model_num %i, fixing...\n", sip->name, sip->model_num, sip->subsystems[k].name, sip->subsystems[k].model_num));
17086 							sip->subsystems[k].model_num = sip->model_num;
17087 						}
17088 					}
17089 				}
17090 
17091 				// the model should already be loaded so this wouldn't take long, but
17092 				// we need to make sure that the load count for the model is correct
17093 				test_id = model_load(sip->pof_file, sip->n_subsystems, &sip->subsystems[0]);
17094 				Assert( test_id == model_previously_loaded );
17095 
17096 				break;
17097 			}
17098 		}
17099 
17100 		// If the model is previously loaded...
17101 		if (model_previously_loaded >= 0) {
17102 			// If previously loaded model isn't the same ship class...)
17103 			if (ship_previously_loaded != i) {
17104 				// update the model number.
17105 				sip->model_num = model_previously_loaded;
17106 
17107 				for (j = 0; j < sip->n_subsystems; j++)
17108 					sip->subsystems[j].model_num = -1;
17109 
17110 				ship_copy_subsystem_fixup(&(*sip));
17111 
17112 #ifndef NDEBUG
17113 				for (j = 0; j < sip->n_subsystems; j++) {
17114 					if (sip->subsystems[j].model_num != sip->model_num) {
17115 						polymodel *sip_pm = (sip->model_num >= 0) ? model_get(sip->model_num) : NULL;
17116 						polymodel *subsys_pm = (sip->subsystems[j].model_num >= 0) ? model_get(sip->subsystems[j].model_num) : NULL;
17117 						Warning(LOCATION, "After ship_copy_subsystem_fixup, ship '%s' does not have subsystem '%s' linked into the model file, '%s'.\n\n(Ship_info model is '%s' and subsystem model is '%s'.)", sip->name, sip->subsystems[j].subobj_name, sip->pof_file, (sip_pm != NULL) ? sip_pm->filename : "NULL", (subsys_pm != NULL) ? subsys_pm->filename : "NULL");
17118 					}
17119 				}
17120 #endif
17121 			} else {
17122 				// Just to be safe (I mean to check that my code works...)
17123 				Assert( sip->model_num >= 0 );
17124 				Assert( sip->model_num == model_previously_loaded );
17125 
17126 #ifndef NDEBUG
17127 				for (j = 0; j < sip->n_subsystems; j++) {
17128 					if (sip->subsystems[j].model_num != sip->model_num) {
17129 						polymodel *sip_pm = (sip->model_num >= 0) ? model_get(sip->model_num) : NULL;
17130 						polymodel *subsys_pm = (sip->subsystems[j].model_num >= 0) ? model_get(sip->subsystems[j].model_num) : NULL;
17131 						Warning(LOCATION, "Without ship_copy_subsystem_fixup, ship '%s' does not have subsystem '%s' linked into the model file, '%s'.\n\n(Ship_info model is '%s' and subsystem model is '%s'.)", sip->name, sip->subsystems[j].subobj_name, sip->pof_file, (sip_pm != NULL) ? sip_pm->filename : "NULL", (subsys_pm != NULL) ? subsys_pm->filename : "NULL");
17132 					}
17133 				}
17134 #endif
17135 			}
17136 		} else {
17137 			// Model not loaded, so load it
17138 			sip->model_num = model_load(sip->pof_file, sip->n_subsystems, &sip->subsystems[0]);
17139 
17140 			Assert( sip->model_num >= 0 );
17141 
17142 #ifndef NDEBUG
17143 			// Verify that all the subsystem model numbers are updated
17144 			for (j = 0; j < sip->n_subsystems; j++)
17145 				Assertion( sip->subsystems[j].model_num == sip->model_num, "Model reference for subsystem %s (model num: %d) on model %s (model num: %d) is invalid.\n", sip->subsystems[j].name, sip->subsystems[j].model_num, sip->pof_file, sip->model_num );	// JAS
17146 #endif
17147 		}
17148 
17149 		// more weapon marking, the weapon info in Ship_info[] is the default
17150 		// loadout which isn't specified by missionparse unless it's different
17151 		for (j = 0; j < sip->num_primary_banks; j++)
17152 			weapon_mark_as_used(sip->primary_bank_weapons[j]);
17153 
17154 		for (j = 0; j < sip->num_secondary_banks; j++)
17155 			weapon_mark_as_used(sip->secondary_bank_weapons[j]);
17156 
17157 		weapon_mark_as_used(sip->cmeasure_type);
17158 
17159 		for (j = 0; j < sip->n_subsystems; j++) {
17160 			model_subsystem *msp = &sip->subsystems[j];
17161 
17162 			for (k = 0; k < MAX_SHIP_PRIMARY_BANKS; k++)
17163 				weapon_mark_as_used(msp->primary_banks[k]);
17164 
17165 			for (k = 0; k < MAX_SHIP_SECONDARY_BANKS; k++)
17166 				weapon_mark_as_used(msp->secondary_banks[k]);
17167 		}
17168 
17169 		// Page in the shockwave stuff. -C
17170 		shockwave_create_info_load(&sip->shockwave);
17171 		if(!sip->explosion_bitmap_anims.empty()) {
17172 			int num_fireballs = (int)sip->explosion_bitmap_anims.size();
17173 			for(j = 0; j < num_fireballs; j++){
17174 				fireball_used[sip->explosion_bitmap_anims[j]] = 1;
17175 			}
17176 		} else if(sip->class_type >= 0 && !Ship_types[sip->class_type].explosion_bitmap_anims.empty()) {
17177 			int num_fireballs = (int)Ship_types[sip->class_type].explosion_bitmap_anims.size();
17178 			for(j = 0; j < num_fireballs; j++){
17179 				fireball_used[Ship_types[sip->class_type].explosion_bitmap_anims[j]] = 1;
17180 			}
17181 		}
17182 	}
17183 
17184 	nprintf(( "Paging", "There are %d ship classes used in this mission.\n", num_ship_types_used ));
17185 
17186 
17187 	// Page in the thruster effects
17188 	// Make sure thrusters are loaded
17189 	if (!Thrust_anim_inited)
17190 		ship_init_thrusters();
17191 
17192 	thrust_info *thruster;
17193 	for (i = 0; i < (int)Species_info.size(); i++) {
17194 		thruster = &Species_info[i].thruster_info;
17195 
17196 		bm_page_in_texture( thruster->flames.normal.first_frame, thruster->flames.normal.num_frames );
17197 		bm_page_in_texture( thruster->flames.afterburn.first_frame, thruster->flames.afterburn.num_frames );
17198 
17199 		// glows are really not anims
17200 		bm_page_in_texture( thruster->glow.normal.first_frame );
17201 		bm_page_in_texture( thruster->glow.afterburn.first_frame );
17202 	}
17203 
17204 	// page in insignia bitmaps
17205 	if(Game_mode & GM_MULTIPLAYER){
17206 		for(i=0; i<MAX_PLAYERS; i++){
17207 			if(MULTI_CONNECTED(Net_players[i]) && (Net_players[i].m_player != NULL) && (Net_players[i].m_player->insignia_texture >= 0)){
17208 				bm_page_in_xparent_texture(Net_players[i].m_player->insignia_texture);
17209 			}
17210 		}
17211 	} else {
17212 		if((Player != NULL) && (Player->insignia_texture >= 0)){
17213 			bm_page_in_xparent_texture(Player->insignia_texture);
17214 		}
17215 	}
17216 
17217 	// page in wing insignia bitmaps - Goober5000
17218 	for (i = 0; i < MAX_WINGS; i++)
17219 	{
17220 		if (Wings[i].wing_insignia_texture >= 0)
17221 			bm_page_in_xparent_texture(Wings[i].wing_insignia_texture);
17222 	}
17223 
17224 	// page in replacement textures - Goober5000
17225 	for (i = 0; i < MAX_SHIPS; i++)
17226 	{
17227 		// is this a valid ship?
17228 		if (Ships[i].objnum >= 0)
17229 		{
17230 			// do we have any textures?
17231 			if (Ships[i].ship_replacement_textures != NULL)
17232 			{
17233 				// page in replacement textures
17234 				for (j=0; j<MAX_REPLACEMENT_TEXTURES; j++)
17235 				{
17236 					if (Ships[i].ship_replacement_textures[j] > -1)
17237 					{
17238 						bm_page_in_texture( Ships[i].ship_replacement_textures[j] );
17239 					}
17240 				}
17241 			}
17242 		}
17243 	}
17244 
17245 	// should never be NULL, this entire function wouldn't work
17246 	delete[] ship_class_used;
17247 	ship_class_used = NULL;
17248 
17249 }
17250 
17251 // Goober5000 - called from ship_page_in()
ship_page_in_textures(int ship_index)17252 void ship_page_in_textures(int ship_index)
17253 {
17254 	int i;
17255 	ship_info *sip;
17256 
17257 	if ( (ship_index < 0) || (ship_index >= ship_info_size()) )
17258 		return;
17259 
17260 
17261 	sip = &Ship_info[ship_index];
17262 
17263 	// afterburner
17264 	if ( !generic_bitmap_load(&sip->afterburner_trail) )
17265 		bm_page_in_texture(sip->afterburner_trail.bitmap_id);
17266 
17267 	// Wanderer - just copying over Bobboau's code...
17268 	if ( !generic_anim_load(&sip->thruster_flame_info.normal) )
17269 		bm_page_in_texture(sip->thruster_flame_info.normal.first_frame);
17270 
17271 	if ( !generic_anim_load(&sip->thruster_flame_info.afterburn) )
17272 		bm_page_in_texture(sip->thruster_flame_info.afterburn.first_frame);
17273 
17274 	// Bobboau's thruster bitmaps
17275 	// the first set has to be loaded a special way
17276 	if ( !thruster_glow_anim_load(&sip->thruster_glow_info.normal) )
17277 		bm_page_in_texture(sip->thruster_glow_info.normal.first_frame);
17278 
17279 	if ( !thruster_glow_anim_load(&sip->thruster_glow_info.afterburn) )
17280 		bm_page_in_texture(sip->thruster_glow_info.afterburn.first_frame);
17281 
17282 	// everything else is loaded normally
17283 	if ( !generic_bitmap_load(&sip->thruster_secondary_glow_info.normal) )
17284 		bm_page_in_texture(sip->thruster_secondary_glow_info.normal.bitmap_id);
17285 
17286 	if ( !generic_bitmap_load(&sip->thruster_secondary_glow_info.afterburn) )
17287 		bm_page_in_texture(sip->thruster_secondary_glow_info.afterburn.bitmap_id);
17288 
17289 	if ( !generic_bitmap_load(&sip->thruster_tertiary_glow_info.normal) )
17290 		bm_page_in_texture(sip->thruster_tertiary_glow_info.normal.bitmap_id);
17291 
17292 	if ( !generic_bitmap_load(&sip->thruster_tertiary_glow_info.afterburn) )
17293 		bm_page_in_texture(sip->thruster_tertiary_glow_info.afterburn.bitmap_id);
17294 
17295 	// splodeing bitmap
17296 	if ( VALID_FNAME(sip->splodeing_texture_name) ) {
17297 		sip->splodeing_texture = bm_load(sip->splodeing_texture_name);
17298 		bm_page_in_texture(sip->splodeing_texture);
17299 	}
17300 
17301 	// thruster/particle bitmaps
17302 	for (i = 0; i < (int)sip->normal_thruster_particles.size(); i++) {
17303 		generic_anim_load(&sip->normal_thruster_particles[i].thruster_bitmap);
17304 		bm_page_in_texture(sip->normal_thruster_particles[i].thruster_bitmap.first_frame);
17305 	}
17306 
17307 	for (i = 0; i < (int)sip->afterburner_thruster_particles.size(); i++) {
17308 		generic_anim_load(&sip->afterburner_thruster_particles[i].thruster_bitmap);
17309 		bm_page_in_texture(sip->afterburner_thruster_particles[i].thruster_bitmap.first_frame);
17310 	}
17311 }
17312 
17313 
17314 #define PAGE_OUT_TEXTURE(x) {	\
17315 	if ( (x) >= 0 ) {	\
17316 		if (release) {	\
17317 			bm_release( (x) );	\
17318 			(x) = -1;	\
17319 		} else {	\
17320 			bm_unload( (x) );	\
17321 		}	\
17322 	}	\
17323 }
17324 
17325 /**
17326  * Unload all textures for a given ship
17327  */
ship_page_out_textures(int ship_index,bool release)17328 void ship_page_out_textures(int ship_index, bool release)
17329 {
17330 	int i;
17331 	ship_info *sip;
17332 
17333 	if ( (ship_index < 0) || (ship_index >= ship_info_size()) )
17334 		return;
17335 
17336 
17337 	sip = &Ship_info[ship_index];
17338 
17339 	// afterburner
17340 	PAGE_OUT_TEXTURE(sip->afterburner_trail.bitmap_id);
17341 
17342 	// thruster bitmaps
17343 	PAGE_OUT_TEXTURE(sip->thruster_flame_info.normal.first_frame);
17344 	PAGE_OUT_TEXTURE(sip->thruster_flame_info.afterburn.first_frame);
17345 	PAGE_OUT_TEXTURE(sip->thruster_glow_info.normal.first_frame);
17346 	PAGE_OUT_TEXTURE(sip->thruster_glow_info.afterburn.first_frame);
17347 	PAGE_OUT_TEXTURE(sip->thruster_secondary_glow_info.normal.bitmap_id);
17348 	PAGE_OUT_TEXTURE(sip->thruster_secondary_glow_info.afterburn.bitmap_id);
17349 	PAGE_OUT_TEXTURE(sip->thruster_tertiary_glow_info.normal.bitmap_id);
17350 	PAGE_OUT_TEXTURE(sip->thruster_tertiary_glow_info.afterburn.bitmap_id);
17351 
17352 	// slodeing bitmap
17353 	PAGE_OUT_TEXTURE(sip->splodeing_texture);
17354 
17355 	// thruster/particle bitmaps
17356 	for (i = 0; i < (int)sip->normal_thruster_particles.size(); i++)
17357 		PAGE_OUT_TEXTURE(sip->normal_thruster_particles[i].thruster_bitmap.first_frame);
17358 
17359 	for (i = 0; i < (int)sip->afterburner_thruster_particles.size(); i++)
17360 		PAGE_OUT_TEXTURE(sip->afterburner_thruster_particles[i].thruster_bitmap.first_frame);
17361 }
17362 
ship_replace_active_texture(int ship_index,const char * old_name,const char * new_name)17363 void ship_replace_active_texture(int ship_index, const char* old_name, const char* new_name) {
17364 	ship* shipp = &Ships[ship_index];
17365 	polymodel* pm = model_get(Ship_info[shipp->ship_info_index].model_num);
17366 	int final_index = -1;
17367 
17368 	for (int i = 0; i < pm->n_textures; i++)
17369 	{
17370 		int tm_num = pm->maps[i].FindTexture(old_name);
17371 		if (tm_num > -1)
17372 		{
17373 			final_index = i * TM_NUM_TYPES + tm_num;
17374 			break;
17375 		}
17376 	}
17377 
17378 	if (final_index >= 0) {
17379 		int texture = bm_load(new_name);
17380 
17381 		if (shipp->ship_replacement_textures == nullptr) {
17382 			shipp->ship_replacement_textures = (int*)vm_malloc(MAX_REPLACEMENT_TEXTURES * sizeof(int));
17383 
17384 			for (int i = 0; i < MAX_REPLACEMENT_TEXTURES; i++)
17385 				shipp->ship_replacement_textures[i] = -1;
17386 		}
17387 
17388 		shipp->ship_replacement_textures[final_index] = texture;
17389 	} else
17390 		Warning(LOCATION, "Invalid texture '%s' used for replacement texture", old_name);
17391 }
17392 
17393 // function to return true if support ships are allowed in the mission for the given object.
17394 //	In single player, must be friendly and not Shivan. (Goober5000 - Shivans can now have support)
17395 //	In multiplayer -- to be coded by Mark Allender after 5/4/98 -- MK, 5/4/98
is_support_allowed(object * objp,bool do_simple_check)17396 int is_support_allowed(object *objp, bool do_simple_check)
17397 {
17398 	int result = -1;
17399 
17400 	// check updated mission conditions to allow support
17401 
17402 	// If running under autopilot support is not allowed
17403 	if ( AutoPilotEngaged )
17404 		return 0;
17405 
17406 	// none allowed
17407 	if (The_mission.support_ships.max_support_ships == 0)
17408 		return 0;
17409 
17410 	// ship_find_repair_ship is a little expensive, so let's not do it every frame
17411 	if (!do_simple_check)
17412 	{
17413 		// check if all support ships are departing or dying
17414 		result = ship_find_repair_ship(objp);
17415 		if (result == 4) {
17416 			return 0;
17417 		}
17418 
17419 		// restricted number allowed
17420 		if (The_mission.support_ships.max_support_ships > 0)
17421 		{
17422 			// if all the allowed ships have been used up, can't rearm unless something's available in-mission or arriving
17423 			if ((The_mission.support_ships.tally >= The_mission.support_ships.max_support_ships))
17424 			{
17425 				// this shouldn't happen because we've reached one of the limits
17426 				Assert(result != 2);
17427 
17428 				// nothing arriving and no ships available in mission
17429 				if ((Arriving_support_ship == NULL) && (result == 0 || result == 3))
17430 					return 0;
17431 			}
17432 		}
17433 	}
17434 
17435 	ship *shipp = &Ships[objp->instance];
17436 
17437 	// this also looks a little more expensive
17438 	if (!do_simple_check)
17439 	{
17440 		// make sure, if exiting from bay, that parent ship is in the mission!
17441 		if ((result == 0 || result == 2) && (The_mission.support_ships.arrival_location == ARRIVE_FROM_DOCK_BAY))
17442 		{
17443 			Assert(The_mission.support_ships.arrival_anchor != -1);
17444 
17445 			// ensure it's in-mission
17446 			int temp = ship_name_lookup(Parse_names[The_mission.support_ships.arrival_anchor]);
17447 			if (temp < 0)
17448 			{
17449 				return 0;
17450 			}
17451 
17452 			// make sure it's not leaving or blowing up
17453 			if (Ships[temp].is_dying_or_departing())
17454 			{
17455 				return 0;
17456 			}
17457 
17458 			// also make sure that parent ship's fighterbay hasn't been destroyed
17459 			if (ship_fighterbays_all_destroyed(&Ships[temp]))
17460 			{
17461 				return 0;
17462 			}
17463 		}
17464 	}
17465 
17466 	if (Game_mode & GM_NORMAL)
17467 	{
17468 		if ( !(Iff_info[shipp->team].flags & IFFF_SUPPORT_ALLOWED) )
17469 		{
17470 			return 0;
17471 		}
17472 	}
17473 	else
17474 	{
17475 		// multiplayer version behaves differently.  Depending on mode:
17476 		// 1) coop mode -- only available to friendly
17477 		// 2) team v team mode -- availble to either side
17478 		// 3) dogfight -- never
17479 
17480 		if(Netgame.type_flags & NG_TYPE_DOGFIGHT)
17481 		{
17482 			return 0;
17483 		}
17484 
17485 		if (IS_MISSION_MULTI_COOP)
17486 		{
17487 			if ( !(Iff_info[shipp->team].flags & IFFF_SUPPORT_ALLOWED) )
17488 			{
17489 				return 0;
17490 			}
17491 		}
17492 	}
17493 
17494 	// Goober5000 - extra check for existence of support ship
17495 	if ( (The_mission.support_ships.ship_class < 0) &&
17496 		!(The_mission.support_ships.support_available_for_species & (1 << Ship_info[shipp->ship_info_index].species)) )
17497 	{
17498 		return 0;
17499 	}
17500 
17501 	// this is also somewhat expensive
17502 	if (!do_simple_check)
17503 	{
17504 		// Goober5000 - extra check to make sure this guy has a rearming dockpoint
17505 		if (model_find_dock_index(Ship_info[shipp->ship_info_index].model_num, DOCK_TYPE_REARM) < 0)
17506 		{
17507 			static bool warned_about_rearm_dockpoint = false;
17508 			if (!warned_about_rearm_dockpoint)
17509 			{
17510 				Warning(LOCATION, "Support not allowed for %s because its model lacks a rearming dockpoint!", shipp->ship_name);
17511 				warned_about_rearm_dockpoint = true;
17512 			}
17513 			return 0;
17514 		}
17515 	}
17516 
17517 	// Goober5000 - if we got this far, we can request support
17518 	return 1;
17519 }
17520 
17521 // returns random index of a visible ship
17522 // if no visible ships are generated in num_ships iterations, it returns -1
ship_get_random_targetable_ship()17523 int ship_get_random_targetable_ship()
17524 {
17525 	int rand_ship;
17526 	int idx = 0, target_list[MAX_SHIPS];
17527 	ship_obj *so;
17528 
17529 	for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) {
17530 		// make sure the instance is valid
17531 		if ( (Objects[so->objnum].instance < 0) || (Objects[so->objnum].instance >= MAX_SHIPS) )
17532 			continue;
17533 
17534 		// skip if we aren't supposed to target it
17535 		if (should_be_ignored(&Ships[Objects[so->objnum].instance]))
17536 			continue;
17537 
17538 		if (idx >= MAX_SHIPS) {
17539 			idx = MAX_SHIPS;
17540 			break;
17541 		}
17542 
17543 		target_list[idx] = Objects[so->objnum].instance;
17544 		idx++;
17545 	}
17546 
17547 	if (idx == 0)
17548 		return -1;
17549 
17550 	rand_ship = Random::next(idx);
17551 
17552 	return target_list[rand_ship];
17553 }
17554 
17555 /**
17556  * Forcibly jettison cargo from a ship
17557  */
object_jettison_cargo(object * objp,object * cargo_objp,float jettison_speed,bool jettison_new)17558 void object_jettison_cargo(object *objp, object *cargo_objp, float jettison_speed, bool jettison_new)
17559 {
17560 	// make sure we are docked
17561 	Assert((objp != nullptr) && (cargo_objp != nullptr));
17562 	Assert(dock_check_find_direct_docked_object(objp, cargo_objp));
17563 
17564 	vec3d impulse, pos;
17565 	ship *shipp = &Ships[objp->instance];
17566 	ship *cargo_shipp = &Ships[cargo_objp->instance];
17567 	int docker_index = dock_find_dockpoint_used_by_object(objp, cargo_objp);
17568 	int dockee_index = dock_find_dockpoint_used_by_object(cargo_objp, objp);
17569 
17570 	// undo all the docking animations
17571 	model_anim_start_type(shipp, AnimationTriggerType::Docked, docker_index, -1);
17572 	model_anim_start_type(shipp, AnimationTriggerType::Docking_Stage3, docker_index, -1);
17573 	model_anim_start_type(shipp, AnimationTriggerType::Docking_Stage2, docker_index, -1);
17574 	model_anim_start_type(shipp, AnimationTriggerType::Docking_Stage1, docker_index, -1);
17575 	model_anim_start_type(cargo_shipp, AnimationTriggerType::Docked, dockee_index, -1);
17576 	model_anim_start_type(cargo_shipp, AnimationTriggerType::Docking_Stage3, dockee_index, -1);
17577 	model_anim_start_type(cargo_shipp, AnimationTriggerType::Docking_Stage2, dockee_index, -1);
17578 	model_anim_start_type(cargo_shipp, AnimationTriggerType::Docking_Stage1, dockee_index, -1);
17579 
17580 	// undock the objects
17581 	ai_do_objects_undocked_stuff(objp, cargo_objp);
17582 
17583 	// Goober5000 - add log
17584 	mission_log_add_entry(LOG_SHIP_UNDOCKED, shipp->ship_name, cargo_shipp->ship_name);
17585 
17586 	// physics stuff
17587 	if (jettison_new)
17588 	{
17589 		// new method uses dockpoint normals and user-specified force
17590 		extern void find_adjusted_dockpoint_info(vec3d * global_dock_point, matrix * dock_orient, object * objp, polymodel * pm, int submodel, int dock_index);
17591 		extern int find_parent_rotating_submodel(polymodel *pm, int dock_index);
17592 
17593 		int model_num = Ship_info[shipp->ship_info_index].model_num;
17594 		polymodel *pm = model_get(model_num);
17595 		int docker_rotating_submodel = find_parent_rotating_submodel(pm, docker_index);
17596 		matrix dock_orient;
17597 
17598 		find_adjusted_dockpoint_info(&pos, &dock_orient, objp, pm, docker_rotating_submodel, docker_index);
17599 
17600 		// set for relative separation speed (see also do_dying_undock_physics)
17601 		vm_vec_copy_scale(&impulse, &dock_orient.vec.fvec, jettison_speed * cargo_objp->phys_info.mass);
17602 	}
17603 	else
17604 	{
17605 		// the old method sends cargo in the wrong direction and with an impulse that depends on the size of the ship
17606 		vm_vec_sub(&pos, &cargo_objp->pos, &objp->pos);
17607 		impulse = pos;
17608 		vm_vec_scale(&impulse, 100.0f);
17609 		vm_vec_normalize(&pos);
17610 		vm_vec_add2(&pos, &cargo_objp->pos);
17611 	}
17612 
17613 	// whack the ship
17614 	ship_apply_whack(&impulse, &pos, cargo_objp);
17615 }
17616 
ship_get_exp_damage(object * objp)17617 float ship_get_exp_damage(object* objp)
17618 {
17619 	Assert(objp->type == OBJ_SHIP);
17620 	float damage;
17621 
17622 	ship *shipp = &Ships[objp->instance];
17623 
17624 	if (shipp->special_exp_damage >= 0) {
17625 		damage = i2fl(shipp->special_exp_damage);
17626 	} else {
17627 		damage = Ship_info[shipp->ship_info_index].shockwave.damage;
17628 	}
17629 
17630 	return damage;
17631 }
17632 
ship_get_exp_propagates(ship * sp)17633 static int ship_get_exp_propagates(ship *sp)
17634 {
17635 	return Ship_info[sp->ship_info_index].explosion_propagates;
17636 }
17637 
ship_get_exp_outer_rad(object * ship_objp)17638 float ship_get_exp_outer_rad(object *ship_objp)
17639 {
17640 	float outer_rad;
17641 	Assert(ship_objp->type == OBJ_SHIP);
17642 
17643 	if (Ships[ship_objp->instance].special_exp_outer == -1) {
17644 		outer_rad = Ship_info[Ships[ship_objp->instance].ship_info_index].shockwave.outer_rad;
17645 	} else {
17646 		outer_rad = (float)Ships[ship_objp->instance].special_exp_outer;
17647 	}
17648 
17649 	return outer_rad;
17650 }
17651 
17652 /**
17653  * Determine turret status of a given subsystem
17654  *
17655  * @return 0 for no turret, 1 for "fixed turret", 2 for "rotating" turret
17656  */
ship_get_turret_type(ship_subsys * subsys)17657 int ship_get_turret_type(ship_subsys *subsys)
17658 {
17659 	// not a turret at all
17660 	if(subsys->system_info->type != SUBSYSTEM_TURRET){
17661 		return 0;
17662 	}
17663 
17664 	// if it rotates
17665 	if(subsys->system_info->turret_turning_rate > 0.0f){
17666 		return 2;
17667 	}
17668 
17669 	// if its fixed
17670 	return 1;
17671 }
17672 
ship_get_subsys(const ship * shipp,const char * subsys_name)17673 ship_subsys *ship_get_subsys(const ship *shipp, const char *subsys_name)
17674 {
17675 	// sanity checks
17676 	if ((shipp == NULL) || (subsys_name == NULL)) {
17677 		return NULL;
17678 	}
17679 
17680 	ship_subsys *ss = GET_FIRST(&shipp->subsys_list);
17681 	while (ss != END_OF_LIST(&shipp->subsys_list)) {
17682 		// check subsystem name
17683 		if (!subsystem_stricmp(ss->system_info->subobj_name, subsys_name)) {
17684 			return ss;
17685 		}
17686 
17687 		// next
17688 		ss = GET_NEXT(ss);
17689 	}
17690 
17691 	// didn't find it
17692 	return NULL;
17693 }
17694 
ship_get_num_subsys(ship * shipp)17695 int ship_get_num_subsys(ship *shipp)
17696 {
17697 	Assert(shipp != NULL);
17698 
17699 	return Ship_info[shipp->ship_info_index].n_subsystems;
17700 }
17701 
17702 // returns 0 if no conflict, 1 if conflict, -1 on some kind of error with wing struct
wing_has_conflicting_teams(int wing_index)17703 int wing_has_conflicting_teams(int wing_index)
17704 {
17705 	int first_team, idx;
17706 
17707 	// sanity checks
17708 	Assert((wing_index >= 0) && (wing_index < Num_wings) && (Wings[wing_index].wave_count > 0));
17709 	if((wing_index < 0) || (wing_index >= Num_wings) || (Wings[wing_index].wave_count <= 0)){
17710 		return -1;
17711 	}
17712 
17713 	// check teams
17714 	Assert(Wings[wing_index].ship_index[0] >= 0);
17715 	if(Wings[wing_index].ship_index[0] < 0){
17716 		return -1;
17717 	}
17718 	first_team = Ships[Wings[wing_index].ship_index[0]].team;
17719 	for(idx=1; idx<Wings[wing_index].wave_count; idx++){
17720 		// more sanity checks
17721 		Assert(Wings[wing_index].ship_index[idx] >= 0);
17722 		if(Wings[wing_index].ship_index[idx] < 0){
17723 			return -1;
17724 		}
17725 
17726 		// if we've got a team conflict
17727 		if(first_team != Ships[Wings[wing_index].ship_index[idx]].team){
17728 			return 1;
17729 		}
17730 	}
17731 
17732 	// no conflict
17733 	return 0;
17734 }
17735 
17736 /**
17737  * Get the team of a reinforcement item
17738  */
ship_get_reinforcement_team(int r_index)17739 int ship_get_reinforcement_team(int r_index)
17740 {
17741 	int wing_index;
17742 	p_object *p_objp;
17743 
17744 	// sanity checks
17745 	Assert((r_index >= 0) && (r_index < Num_reinforcements));
17746 	if ((r_index < 0) || (r_index >= Num_reinforcements))
17747 		return -1;
17748 
17749 	// if the reinforcement is a ship
17750 	p_objp = mission_parse_get_arrival_ship(Reinforcements[r_index].name);
17751 	if (p_objp != NULL)
17752 		return p_objp->team;
17753 
17754 	// if the reinforcement is a ship
17755 	wing_index = wing_lookup(Reinforcements[r_index].name);
17756 	if (wing_index >= 0)
17757 	{
17758 		// go through the ship arrival list and find any ship in this wing
17759 		for (p_objp = GET_FIRST(&Ship_arrival_list); p_objp != END_OF_LIST(&Ship_arrival_list); p_objp = GET_NEXT(p_objp))
17760 		{
17761 			// check by wingnum
17762 			if (p_objp->wingnum == wing_index)
17763 				return p_objp->team;
17764 		}
17765 	}
17766 
17767 	// no team ?
17768 	return -1;
17769 }
17770 
17771 // update artillery lock info
17772 #define CLEAR_ARTILLERY_AND_CONTINUE()	{ if(aip != NULL){ aip->artillery_objnum = -1; aip->artillery_sig = -1;	aip->artillery_lock_time = 0.0f;} continue; }
17773 float artillery_dist = 10.0f;
17774 DCF(art, "Sets artillery disance")
17775 {
17776 	dc_stuff_float(&artillery_dist);
17777 }
17778 
ship_update_artillery_lock()17779 void ship_update_artillery_lock()
17780 {
17781 	ai_info *aip = NULL;
17782 	mc_info *cinfo = NULL;
17783 	int c_objnum;
17784 	vec3d temp, local_hit;
17785 	ship *shipp;
17786 	ship_obj *so;
17787 
17788 	// update all ships
17789 	for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ){
17790 		// get the ship
17791 		if((so->objnum >= 0) && (Objects[so->objnum].type == OBJ_SHIP) && (Objects[so->objnum].instance >= 0)){
17792 			shipp = &Ships[Objects[so->objnum].instance];
17793 		} else {
17794 			continue;
17795 		}
17796 
17797 		// get ai info
17798 		if(shipp->ai_index >= 0){
17799 			aip = &Ai_info[shipp->ai_index];
17800 		}
17801 
17802 		// if the ship has no targeting laser firing
17803 		if((shipp->targeting_laser_objnum < 0) || (shipp->targeting_laser_bank < 0)){
17804 			CLEAR_ARTILLERY_AND_CONTINUE();
17805 		}
17806 
17807 		// if he didn't hit any objects this frame
17808 		if(beam_get_num_collisions(shipp->targeting_laser_objnum) <= 0){
17809 			CLEAR_ARTILLERY_AND_CONTINUE();
17810 		}
17811 
17812 		// get weapon info for the targeting laser he's firing
17813 		Assert((shipp->weapons.current_primary_bank >= 0) && (shipp->weapons.current_primary_bank < 2));
17814 		if((shipp->weapons.current_primary_bank < 0) || (shipp->weapons.current_primary_bank >= 2)){
17815 			continue;
17816 		}
17817 		Assert(shipp->weapons.primary_bank_weapons[shipp->weapons.current_primary_bank] >= 0);
17818 		if(shipp->weapons.primary_bank_weapons[shipp->weapons.current_primary_bank] < 0){
17819 			continue;
17820 		}
17821 		Assert((Weapon_info[shipp->weapons.primary_bank_weapons[shipp->weapons.current_primary_bank]].wi_flags[Weapon::Info_Flags::Beam]) && (Weapon_info[shipp->weapons.primary_bank_weapons[shipp->weapons.current_primary_bank]].b_info.beam_type == BEAM_TYPE_C));
17822 		if(!(Weapon_info[shipp->weapons.primary_bank_weapons[shipp->weapons.current_primary_bank]].wi_flags[Weapon::Info_Flags::Beam]) || (Weapon_info[shipp->weapons.primary_bank_weapons[shipp->weapons.current_primary_bank]].b_info.beam_type != BEAM_TYPE_C)){
17823 			continue;
17824 		}
17825 
17826 		// get collision info
17827 		if(!beam_get_collision(shipp->targeting_laser_objnum, 0, &c_objnum, &cinfo)){
17828 			CLEAR_ARTILLERY_AND_CONTINUE();
17829 		}
17830 		if((c_objnum < 0) || (cinfo == NULL)){
17831 			CLEAR_ARTILLERY_AND_CONTINUE();
17832 		}
17833 
17834 		// get the position we hit this guy with in his local coords
17835 		vm_vec_sub(&temp, &cinfo->hit_point_world, &Objects[c_objnum].pos);
17836 		vm_vec_rotate(&local_hit, &temp, &Objects[c_objnum].orient);
17837 
17838 		// if we are hitting a different guy now, reset the lock
17839 		if((c_objnum != aip->artillery_objnum) || (Objects[c_objnum].signature != aip->artillery_sig)){
17840 			aip->artillery_objnum = c_objnum;
17841 			aip->artillery_sig = Objects[c_objnum].signature;
17842 			aip->artillery_lock_time = 0.0f;
17843 			aip->artillery_lock_pos = local_hit;
17844 
17845 			// done
17846 			continue;
17847 		}
17848 
17849 		// otherwise we're hitting the same guy. check to see if we've strayed too far
17850 		if(vm_vec_dist_quick(&local_hit, &aip->artillery_lock_pos) > artillery_dist){
17851 			// hmmm. reset lock time, but don't reset the lock itself
17852 			aip->artillery_lock_time = 0.0f;
17853 			continue;
17854 		}
17855 
17856 		// finally - just increment the lock time
17857 		aip->artillery_lock_time += flFrametime;
17858 
17859 		// TEST CODE
17860 		if(aip->artillery_objnum >= 0 && aip->artillery_lock_time >= 2.0f){
17861 			ssm_create(&Objects[aip->artillery_objnum], &cinfo->hit_point_world, 0, NULL, shipp->team);
17862 
17863 			// reset the artillery
17864 			aip->artillery_lock_time = 0.0f;
17865 		}
17866 	}
17867 }
17868 
17869 /**
17870  * Checks if a world point is inside the extended bounding box of a ship
17871  *
17872  * May not work if delta box is large and negative (ie, adjusted box crosses over on itself - min > max)
17873  */
check_world_pt_in_expanded_ship_bbox(vec3d * world_pt,object * objp,float delta_box)17874 int check_world_pt_in_expanded_ship_bbox(vec3d *world_pt, object *objp, float delta_box)
17875 {
17876 	Assert(objp->type == OBJ_SHIP);
17877 
17878 	vec3d temp, ship_pt;
17879 	polymodel *pm;
17880 	vm_vec_sub(&temp, world_pt, &objp->pos);
17881 	vm_vec_rotate(&ship_pt, &temp, &objp->orient);
17882 
17883 	pm = model_get(Ship_info[Ships[objp->instance].ship_info_index].model_num);
17884 
17885 	return (
17886 			(ship_pt.xyz.x > pm->mins.xyz.x - delta_box) && (ship_pt.xyz.x < pm->maxs.xyz.x + delta_box)
17887 		&& (ship_pt.xyz.y > pm->mins.xyz.y - delta_box) && (ship_pt.xyz.y < pm->maxs.xyz.y + delta_box)
17888 		&& (ship_pt.xyz.z > pm->mins.xyz.z - delta_box) && (ship_pt.xyz.z < pm->maxs.xyz.z + delta_box)
17889 	);
17890 }
17891 
17892 
17893 /**
17894  * Returns true when objp is ship and is tagged
17895  */
ship_is_tagged(object * objp)17896 int ship_is_tagged(object *objp)
17897 {
17898 	ship *shipp;
17899 	if (objp->type == OBJ_SHIP) {
17900 		shipp = &Ships[objp->instance];
17901 		if ( (shipp->tag_left > 0) || (shipp->level2_tag_left > 0) ) {
17902 			return 1;
17903 		}
17904 	}
17905 
17906 	return 0;
17907 }
17908 
17909 /**
17910  * Get maximum ship speed (when not warping in or out)
17911  */
ship_get_max_speed(ship * shipp)17912 float ship_get_max_speed(ship *shipp)
17913 {
17914 	float max_speed;
17915 	ship_info *sip = &Ship_info[shipp->ship_info_index];
17916 
17917 	// Goober5000 - maybe we're using cap-waypoint-speed
17918 	ai_info *aip = &Ai_info[shipp->ai_index];
17919 	if ((aip->mode == AIM_WAYPOINTS || aip->mode == AIM_FLY_TO_SHIP) && aip->waypoint_speed_cap > 0)
17920 		return i2fl(aip->waypoint_speed_cap);
17921 
17922 	// max overclock
17923 	max_speed = sip->max_overclocked_speed;
17924 
17925 	// normal max speed
17926 	max_speed = MAX(max_speed, sip->max_vel.xyz.z);
17927 
17928 	// afterburn if not locked
17929 	if (!(shipp->flags[Ship_Flags::Afterburner_locked])) {
17930 		max_speed = MAX(max_speed, sip->afterburner_max_vel.xyz.z);
17931 	}
17932 
17933 	return max_speed;
17934 }
17935 
17936 /**
17937  * Determine warpout speed of ship
17938  */
ship_get_warpout_speed(object * objp,ship_info * sip,float half_length,float warping_dist)17939 float ship_get_warpout_speed(object *objp, ship_info *sip, float half_length, float warping_dist)
17940 {
17941 	Assert(objp != nullptr && objp->type == OBJ_SHIP);
17942 
17943 	// certain places in the code don't precalculate these variables
17944 	if (sip == nullptr)
17945 	{
17946 		sip = &Ship_info[Ships[objp->instance].ship_info_index];
17947 
17948 		// c.f.  WE_Default::warpStart()
17949 		// determine the half-length and the warping distance (which is actually the full length)
17950 		if (object_is_docked(objp))
17951 		{
17952 			// we need to get the longitudinal radius of our ship, so find the semilatus rectum along the Z-axis
17953 			half_length = dock_calc_max_semilatus_rectum_parallel_to_axis(objp, Z_AXIS);
17954 			warping_dist = 2.0f * half_length;
17955 		}
17956 		else
17957 		{
17958 			warping_dist = ship_class_get_length(sip);
17959 			half_length = 0.5f * warping_dist;
17960 		}
17961 	}
17962 
17963 	WarpParams *params = &Warp_params[Ships[objp->instance].warpout_params_index];
17964 
17965 	//WMC - Any speed is good for in place anims (aka BSG FTL effect)
17966 	//Asteroth - or for the sweeper (aka Homeworld FTL effect)
17967 	if ((params->warp_type == WT_IN_PLACE_ANIM || params->warp_type == WT_SWEEPER) && params->speed <= 0.0f)
17968 	{
17969 		return objp->phys_info.speed;
17970 	}
17971 	else if (params->warp_type == WT_SWEEPER || params->warp_type == WT_IN_PLACE_ANIM)
17972 	{
17973 		return params->speed;
17974 	}
17975 	else if (params->warp_type == WT_HYPERSPACE)
17976 	{
17977 		if (objp->phys_info.speed > params->speed)
17978 			return objp->phys_info.speed;
17979 		else
17980 			return params->speed;
17981 	}
17982 
17983 	return warping_dist / shipfx_calculate_warp_time(objp, WarpDirection::WARP_OUT, half_length, warping_dist);
17984 }
17985 
17986 /**
17987  * Returns true if ship is beginning to speed up in warpout
17988  */
ship_is_beginning_warpout_speedup(object * objp)17989 int ship_is_beginning_warpout_speedup(object *objp)
17990 {
17991 	Assert(objp->type == OBJ_SHIP);
17992 
17993 	ai_info *aip;
17994 
17995 	aip = &Ai_info[Ships[objp->instance].ai_index];
17996 
17997 	if (aip->mode == AIM_WARP_OUT) {
17998 		if ( (aip->submode == AIS_WARP_3) || (aip->submode == AIS_WARP_4) || (aip->submode == AIS_WARP_5) ) {
17999 			return 1;
18000 		}
18001 	}
18002 
18003 	return 0;
18004 }
18005 
18006 /**
18007  * Return the length of a ship
18008  */
ship_class_get_length(const ship_info * sip)18009 float ship_class_get_length(const ship_info *sip)
18010 {
18011 	Assert(sip != nullptr);
18012 	Assert(sip->model_num >= 0);
18013 
18014 	polymodel *pm = model_get(sip->model_num);
18015 	Assert(pm != nullptr);
18016 
18017 	float length = pm->maxs.xyz.z - pm->mins.xyz.z;
18018 	Assert(length > 0.0f);
18019 
18020 	return length;
18021 }
18022 
18023 /**
18024  * Get the offset of the actual center of the ship model for the purposes of warping (which may not be the specified center)
18025  */
ship_class_get_actual_center(const ship_info * sip,vec3d * center_pos)18026 void ship_class_get_actual_center(const ship_info *sip, vec3d *center_pos)
18027 {
18028 	Assert(sip != nullptr && center_pos != nullptr);
18029 	Assert(sip->model_num >= 0);
18030 
18031 	polymodel *pm = model_get(sip->model_num);
18032 	center_pos->xyz.x = (pm->maxs.xyz.x + pm->mins.xyz.x) * 0.5f;
18033 	center_pos->xyz.y = (pm->maxs.xyz.y + pm->mins.xyz.y) * 0.5f;
18034 	center_pos->xyz.z = (pm->maxs.xyz.z + pm->mins.xyz.z) * 0.5f;
18035 }
18036 
18037 // Goober5000
ship_set_new_ai_class(ship * shipp,int new_ai_class)18038 void ship_set_new_ai_class(ship *shipp, int new_ai_class)
18039 {
18040 	Assert(shipp);
18041 	Assert(new_ai_class >= 0);
18042 
18043 	ai_info *aip = &Ai_info[shipp->ai_index];
18044 
18045 	// we hafta change a bunch of stuff here...
18046 	aip->ai_class = new_ai_class;
18047 	aip->behavior = AIM_NONE;
18048 	init_aip_from_class_and_profile(aip, &Ai_classes[new_ai_class], The_mission.ai_profile);
18049 
18050 	shipp->weapons.ai_class = new_ai_class;
18051 
18052 	// I think that's everything!
18053 }
18054 
18055 // Goober5000
ship_subsystem_set_new_ai_class(ship * shipp,const char * subsystem,int new_ai_class)18056 void ship_subsystem_set_new_ai_class(ship *shipp, const char *subsystem, int new_ai_class)
18057 {
18058 	Assert(shipp);
18059 	Assert(subsystem);
18060 	Assert(new_ai_class >= 0);
18061 
18062 	ship_subsys *ss;
18063 
18064 	// find the ship subsystem by searching ship's subsys_list
18065 	ss = GET_FIRST( &shipp->subsys_list );
18066 	while ( ss != END_OF_LIST( &shipp->subsys_list ) )
18067 	{
18068 		// if we found the subsystem
18069 		if ( !subsystem_stricmp(ss->system_info->subobj_name, subsystem))
18070 		{
18071 			// set ai class
18072 			ss->weapons.ai_class = new_ai_class;
18073 			return;
18074 		}
18075 
18076 		ss = GET_NEXT( ss );
18077 	}
18078 }
18079 
18080 // Goober5000 - will attempt to load an insignia bitmap and set it as active for the wing
18081 // copied more or less from managepilot.cpp
wing_load_squad_bitmap(wing * w)18082 void wing_load_squad_bitmap(wing *w)
18083 {
18084 	// sanity check
18085 	if(w == NULL)
18086 	{
18087 		return;
18088 	}
18089 
18090 	// make sure one is not already set?!?
18091 	Assert (w->wing_insignia_texture == -1);
18092 
18093 	// try and set the new one
18094 	if( w->wing_squad_filename[0] != '\0' )
18095 	{
18096 		// load duplicate because it might be the same as the player's squad,
18097 		// and we don't want to overlap and breed nasty errors when we unload
18098 		w->wing_insignia_texture = bm_load_duplicate(w->wing_squad_filename);
18099 
18100 		// lock is as a transparent texture
18101 		if(w->wing_insignia_texture != -1)
18102 		{
18103 			bm_lock(w->wing_insignia_texture, 16, BMP_TEX_XPARENT);
18104 			bm_unlock(w->wing_insignia_texture);
18105 		}
18106 	}
18107 }
18108 
18109 // Goober5000 - needed by new hangar depart code
18110 // check whether this ship has a docking bay
ship_has_dock_bay(int shipnum)18111 bool ship_has_dock_bay(int shipnum)
18112 {
18113 	Assert(shipnum >= 0 && shipnum < MAX_SHIPS);
18114 
18115 	polymodel *pm;
18116 
18117 	pm = model_get( Ship_info[Ships[shipnum].ship_info_index].model_num );
18118 	Assert( pm );
18119 
18120 	return ( pm->ship_bay && (pm->ship_bay->num_paths > 0) );
18121 }
18122 
18123 // Goober5000
ship_useful_for_departure(int shipnum,int)18124 bool ship_useful_for_departure(int shipnum, int  /*path_mask*/)
18125 {
18126 	Assert( shipnum >= 0 && shipnum < MAX_SHIPS );
18127 
18128 	// not valid if dying or departing
18129 	if (Ships[shipnum].is_dying_or_departing())
18130 		return false;
18131 
18132 	// no dockbay, can't depart to it
18133 	if (!ship_has_dock_bay(shipnum))
18134 		return false;
18135 
18136 	// make sure that the bays are not all destroyed
18137 	if (ship_fighterbays_all_destroyed(&Ships[shipnum]))
18138 		return false;
18139 
18140 	// in the future, we might want to check bay paths against destroyed fighterbays,
18141 	// but that capability doesn't currently exist
18142 	// (note, a mask of 0 indicates all paths are valid)
18143 	//if (path_mask != 0)
18144 	//{
18145 	//}
18146 
18147 	// ship is valid
18148 	return true;
18149 }
18150 
18151 // Goober5000 - needed by new hangar depart code
18152 // get first ship in ship list with docking bay
ship_get_ship_for_departure(int team)18153 int ship_get_ship_for_departure(int team)
18154 {
18155 	for (ship_obj *so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so))
18156 	{
18157 		Assert(so->objnum >= 0);
18158 		int shipnum = Objects[so->objnum].instance;
18159 		Assert(shipnum >= 0);
18160 
18161 		if ( (Ships[shipnum].team == team) && ship_useful_for_departure(shipnum) )
18162 			return shipnum;
18163 	}
18164 
18165 	// we didn't find anything
18166 	return -1;
18167 }
18168 
18169 // Goober5000 - check if all fighterbays on a ship have been destroyed
ship_fighterbays_all_destroyed(ship * shipp)18170 bool ship_fighterbays_all_destroyed(ship *shipp)
18171 {
18172 	Assert(shipp);
18173 	ship_subsys *subsys;
18174 	int num_fighterbay_subsystems = 0;
18175 
18176 	// check all fighterbay systems
18177 	subsys = GET_FIRST(&shipp->subsys_list);
18178 	while(subsys != END_OF_LIST(&shipp->subsys_list))
18179 	{
18180 		// look for fighterbays
18181 		if (ship_subsys_is_fighterbay(subsys))
18182 		{
18183 			num_fighterbay_subsystems++;
18184 
18185 			// if fighterbay doesn't take damage, we're good
18186 			if (!ship_subsys_takes_damage(subsys))
18187 				return false;
18188 
18189 			// if fighterbay isn't destroyed, we're good
18190 			if (subsys->current_hits > 0)
18191 				return false;
18192 		}
18193 
18194 		// next item
18195 		subsys = GET_NEXT(subsys);
18196 	}
18197 
18198 	// if the ship has no fighterbay subsystems at all, it must be an unusual case,
18199 	// like the Faustus, so pretend it's okay...
18200 	if (num_fighterbay_subsystems == 0)
18201 		return false;
18202 
18203 	// if we got this far, the ship has at least one fighterbay subsystem,
18204 	// and all the ones it has are destroyed
18205 	return true;
18206 }
18207 
18208 // moved here by Goober5000
ship_subsys_is_fighterbay(ship_subsys * ss)18209 static bool ship_subsys_is_fighterbay(ship_subsys *ss)
18210 {
18211 	Assert(ss);
18212 
18213 	if ( !strnicmp(NOX("fighter"), ss->system_info->name, 7) ) {
18214 		return true;
18215 	}
18216 
18217 	return false;
18218 }
18219 
18220 // Goober5000
ship_subsys_takes_damage(ship_subsys * ss)18221 bool ship_subsys_takes_damage(ship_subsys *ss)
18222 {
18223 	Assert(ss);
18224 
18225 	return (ss->max_hits > SUBSYS_MAX_HITS_THRESHOLD);
18226 }
18227 
18228 // Goober5000
ship_do_submodel_rotation(ship * shipp,model_subsystem * psub,ship_subsys * pss)18229 void ship_do_submodel_rotation(ship *shipp, model_subsystem *psub, ship_subsys *pss)
18230 {
18231 	Assert(shipp);
18232 	Assert(psub);
18233 	Assert(pss);
18234 
18235 	// check if we actually can rotate
18236 	if ( !(pss->flags[Ship::Subsystem_Flags::Rotates]) ){
18237 		return;
18238 	}
18239 
18240 	if (psub->flags[Model::Subsystem_Flags::Triggered] && pss->triggered_rotation_index >= 0) {
18241 		Triggered_rotations[pss->triggered_rotation_index].process_queue();
18242 		model_anim_submodel_trigger_rotate(psub, pss );
18243 		return;
18244 
18245 	}
18246 
18247 	// check for rotating artillery
18248 	if ( psub->flags[Model::Subsystem_Flags::Artillery] )
18249 	{
18250 		ship_weapon *swp = &shipp->weapons;
18251 
18252 		// rotate only if trigger is down
18253 		if ( !(shipp->flags[Ship_Flags::Trigger_down]) )
18254 			return;
18255 
18256 		// check linked
18257 		if ( shipp->flags[Ship_Flags::Primary_linked] )
18258 		{
18259 			int i, ammo_tally = 0;
18260 
18261 			// calculate ammo
18262 			for (i=0; i<swp->num_primary_banks; i++)
18263 				ammo_tally += swp->primary_bank_ammo[i];
18264 
18265 			// do not rotate if out of ammo
18266 			if (ammo_tally <= 0)
18267 				return;
18268 		}
18269 		// check unlinked
18270 		else
18271 		{
18272 			// do not rotate if this is not the firing bank or if we have no ammo in this bank
18273 			if ((psub->weapon_rotation_pbank != swp->current_primary_bank) || (swp->primary_bank_ammo[swp->current_primary_bank] <= 0))
18274 				return;
18275 		}
18276 	}
18277 
18278 	// if we got this far, we can rotate - so choose which method to use
18279 	if (psub->flags[Model::Subsystem_Flags::Stepped_rotate]	) {
18280 		submodel_stepped_rotate(psub, pss->submodel_instance_1);
18281 	} else {
18282 		submodel_rotate(psub, pss->submodel_instance_1 );
18283 	}
18284 }
18285 
18286 // Goober5000
ship_has_energy_weapons(ship * shipp)18287 int ship_has_energy_weapons(ship *shipp)
18288 {
18289 	// (to avoid round-off errors, weapon reserve is not tested for zero)
18290 	return (Ship_info[shipp->ship_info_index].max_weapon_reserve > WEAPON_RESERVE_THRESHOLD);
18291 }
18292 
18293 // Goober5000
ship_has_engine_power(ship * shipp)18294 int ship_has_engine_power(ship *shipp)
18295 {
18296 	return (Ship_info[shipp->ship_info_index].max_speed > 0 );
18297 }
18298 
18299 // Goober5000
ship_starting_wing_lookup(const char * wing_name)18300 int ship_starting_wing_lookup(const char *wing_name)
18301 {
18302 	Assertion(wing_name != nullptr, "NULL wing_name passed to ship_starting_wing_lookup");
18303 
18304 	for (int i = 0; i < MAX_STARTING_WINGS; i++)
18305 	{
18306 		if (!stricmp(Starting_wing_names[i], wing_name))
18307 			return i;
18308 	}
18309 
18310 	return -1;
18311 }
18312 
18313 // Goober5000
ship_squadron_wing_lookup(const char * wing_name)18314 int ship_squadron_wing_lookup(const char *wing_name)
18315 {
18316 	Assertion(wing_name != nullptr, "NULL wing_name passed to ship_squadron_wing_lookup");
18317 
18318 	// TvT uses a different set of wing names from everything else
18319 	if (MULTI_TEAM)
18320 	{
18321 		for (int i = 0; i < MAX_TVT_WINGS; i++)
18322 		{
18323 			if (!stricmp(TVT_wing_names[i], wing_name))
18324 				return i;
18325 		}
18326 	}
18327 	else
18328 	{
18329 		// match duplicate wings, such as Epsilon and Epsilon#clone
18330 		auto ch = get_pointer_to_first_hash_symbol(wing_name);
18331 		size_t len = (ch != nullptr) ? (ch - wing_name) : strlen(wing_name);
18332 
18333 		for (int i = 0; i < MAX_SQUADRON_WINGS; i++)
18334 		{
18335 			if (!strnicmp(Squadron_wing_names[i], wing_name, len))
18336 				return i;
18337 		}
18338 	}
18339 
18340 	return -1;
18341 }
18342 
18343 // Goober5000
ship_tvt_wing_lookup(const char * wing_name)18344 int ship_tvt_wing_lookup(const char *wing_name)
18345 {
18346 	Assertion(wing_name != nullptr, "NULL wing_name passed to ship_tvt_wing_lookup");
18347 
18348 	for (int i = 0; i < MAX_TVT_WINGS; i++)
18349 	{
18350 		if (!stricmp(TVT_wing_names[i], wing_name))
18351 			return i;
18352 	}
18353 
18354 	return -1;
18355 }
18356 
18357 // Goober5000
ship_class_get_priority(int ship_class)18358 static int ship_class_get_priority(int ship_class)
18359 {
18360 	ship_info *sip = &Ship_info[ship_class];
18361 
18362 	// biggest to smallest
18363 	if (sip->flags[Info_Flags::Knossos_device])
18364 		return 1;
18365 	else if (sip->flags[Info_Flags::Supercap])
18366 		return 2;
18367 	else if (sip->flags[Info_Flags::Drydock])
18368 		return 3;
18369 	else if (sip->flags[Info_Flags::Capital])
18370 		return 4;
18371 	else if (sip->flags[Info_Flags::Corvette])
18372 		return 5;
18373 	else if (sip->flags[Info_Flags::Cruiser])
18374 		return 6;
18375 	else if (sip->flags[Info_Flags::Gas_miner])
18376 		return 7;
18377 	else if (sip->flags[Info_Flags::Awacs])
18378 		return 8;
18379 	else if (sip->flags[Info_Flags::Freighter])
18380 		return 9;
18381 	else if (sip->flags[Info_Flags::Transport])
18382 		return 10;
18383 	else if (sip->flags[Info_Flags::Bomber])
18384 		return 11;
18385 	else if (sip->flags[Info_Flags::Fighter] || sip->flags[Info_Flags::Stealth])
18386 		return 12;
18387 	else if (sip->flags[Info_Flags::Escapepod])
18388 		return 13;
18389 	else if (sip->flags[Info_Flags::Sentrygun])
18390 		return 14;
18391 	else if (sip->flags[Info_Flags::Cargo])
18392 		return 15;
18393 	else if (sip->flags[Info_Flags::Navbuoy])
18394 		return 16;
18395 
18396 	Warning(LOCATION, "Unknown priority for ship class '%s'!", sip->name);
18397 	return 17 + ship_class;
18398 }
18399 
18400 // Goober5000
ship_class_compare(int ship_class_1,int ship_class_2)18401 int ship_class_compare(int ship_class_1, int ship_class_2)
18402 {
18403 	// grab priorities
18404 	int priority1 = ship_class_get_priority(ship_class_1);
18405 	int priority2 = ship_class_get_priority(ship_class_2);
18406 
18407 	// standard compare
18408 	if (priority1 < priority2)
18409 		return -1;
18410 	else if (priority1 > priority2)
18411 		return 1;
18412 	else
18413 		return 0;
18414 }
18415 
18416 /**
18417  * Gives the index into the Damage_types[] vector of a specified damage type name
18418  * @return -1 if not found
18419  */
damage_type_get_idx(char * name)18420 static int damage_type_get_idx(char *name)
18421 {
18422 	//This should never be bigger than INT_MAX anyway
18423 	for(int i = 0; i < (int)Damage_types.size(); i++)
18424 	{
18425 		if(!stricmp(name, Damage_types[i].name))
18426 			return i;
18427 	}
18428 
18429 	return -1;
18430 }
18431 
18432 /**
18433  * Either loads a new damage type, or returns the index of one with the same name as given
18434  */
damage_type_add(char * name)18435 int damage_type_add(char *name)
18436 {
18437 	int i = damage_type_get_idx(name);
18438 	if(i != -1)
18439 		return i;
18440 
18441 	DamageTypeStruct dts;
18442 
18443 	strncpy(dts.name, name, NAME_LENGTH-1);
18444 
18445 	if(strlen(name) > NAME_LENGTH - 1)
18446 	{
18447 		Warning(LOCATION, "Damage type name '%s' is too long and has been truncated to '%s'", name, dts.name);
18448 	}
18449 
18450 	Damage_types.push_back(dts);
18451 	return (int)(Damage_types.size() - 1);
18452 }
18453 
clear()18454 void ArmorDamageType::clear()
18455 {
18456 	DamageTypeIndex = -1;
18457 
18458 	Calculations.clear();
18459 	Arguments.clear();
18460 	altArguments.clear();  // Nuke: don't forget to delete it
18461 }
18462 
18463 //************
18464 // Wanderer - beam piercing type
18465 //************
18466 
18467 flag_def_list	PiercingTypes[] = {
18468 	{	"none",		SADTF_PIERCING_NONE,		0},
18469 	{	"default",	SADTF_PIERCING_DEFAULT,		0},
18470 	{	"retail",	SADTF_PIERCING_RETAIL,		0},
18471 };
18472 
18473 const int Num_piercing_effect_types = sizeof(PiercingTypes)/sizeof(flag_def_list);
18474 
piercing_type_get(char * str)18475 static int piercing_type_get(char *str)
18476 {
18477 	int i;
18478 	for(i = 0; i < Num_piercing_effect_types; i++)
18479 	{
18480 		if(!stricmp(PiercingTypes[i].name, str))
18481 			return PiercingTypes[i].def;
18482 	}
18483 
18484 	// default to retail
18485 	return SADTF_PIERCING_RETAIL;
18486 }
18487 
18488 // Nuke: handle difficulty scaling type
18489 flag_def_list	DifficultyScaleTypes[] = {
18490 	{	"first",	ADT_DIFF_SCALE_FIRST,	0},
18491 	{	"last",		ADT_DIFF_SCALE_LAST,	0},
18492 	{	"manual",	ADT_DIFF_SCALE_MANUAL,	0},
18493 };
18494 
18495 const int Num_difficulty_scale_types = sizeof(DifficultyScaleTypes)/sizeof(flag_def_list);
18496 
difficulty_scale_type_get(char * str)18497 static int difficulty_scale_type_get(char *str) {
18498 	int i;
18499 	for(i = 0; i < Num_difficulty_scale_types; i++){
18500 		if (!stricmp(DifficultyScaleTypes[i].name, str))
18501 			return DifficultyScaleTypes[i].def;
18502 	}
18503 
18504 	// indicate error
18505 	return ADT_DIFF_SCALE_BAD_VAL;
18506 }
18507 
18508 // Nuke: flag list for +constant: values
18509 flag_def_list	ArmorTypeConstants[] = {
18510 	{	"base damage",			AT_CONSTANT_BASE_DMG,		0},
18511 	{	"current damage",		AT_CONSTANT_CURRENT_DMG,	0},
18512 	{	"difficulty factor",	AT_CONSTANT_DIFF_FACTOR,	0},
18513 	{	"random",				AT_CONSTANT_RANDOM,			0},
18514 	{	"pi",					AT_CONSTANT_PI,				0},
18515 };
18516 
18517 const int Num_armor_type_constants = sizeof(ArmorTypeConstants)/sizeof(flag_def_list);
18518 
armor_type_constants_get(char * str)18519 static int armor_type_constants_get(char *str){
18520 	int i;
18521 	for (i = 0; i < Num_armor_type_constants; i++){
18522 		if (!stricmp(ArmorTypeConstants[i].name, str))
18523 			return ArmorTypeConstants[i].def;
18524 	}
18525 	// this shouldnt happen, but if it does theirs a define for that
18526 	return AT_CONSTANT_BAD_VAL;
18527 }
18528 
18529 
18530 //**************************************************************
18531 //WMC - All the extra armor crap
18532 
18533 //****************************Calculation type addition
18534 //4 steps to add a new one
18535 
18536 //Armor types
18537 //STEP 1: Add a define
18538 #define AT_TYPE_ADDITIVE				0
18539 #define AT_TYPE_MULTIPLICATIVE			1
18540 #define AT_TYPE_EXPONENTIAL				2
18541 #define AT_TYPE_EXPONENTIAL_BASE		3
18542 #define AT_TYPE_CUTOFF					4
18543 #define AT_TYPE_REVERSE_CUTOFF			5
18544 #define AT_TYPE_INSTANT_CUTOFF			6
18545 #define AT_TYPE_INSTANT_REVERSE_CUTOFF	7
18546 // Added by Nuke
18547 #define AT_TYPE_CAP						8
18548 #define AT_TYPE_INSTANT_CAP				9
18549 #define AT_TYPE_SET						10
18550 #define AT_TYPE_STORE					11
18551 #define AT_TYPE_LOAD					12
18552 #define AT_TYPE_RANDOM					13
18553 
18554 // Nuke: this is the number of storage locations load/store calculations are allowed to use
18555 #define AT_NUM_STORAGE_LOCATIONS		8
18556 
18557 //STEP 2: Add the name string to the array
18558 const char *TypeNames[] = {
18559 	"additive",
18560 	"multiplicative",
18561 	"exponential",
18562 	"exponential base",
18563 	"cutoff",
18564 	"reverse cutoff",
18565 	"instant cutoff",
18566 	"instant reverse cutoff",
18567 	// Added by Nuke
18568 	"cap",
18569 	"instant cap",
18570 	"set",
18571 	"load",
18572 	"store",
18573 	"random"
18574 };
18575 
18576 const int Num_armor_calculation_types = sizeof(TypeNames)/sizeof(char*);
18577 
calculation_type_get(char * str)18578 int calculation_type_get(char *str)
18579 {
18580 	for(int i = 0; i < Num_armor_calculation_types; i++)
18581 	{
18582 		if(!stricmp(TypeNames[i], str))
18583 			return i;
18584 	}
18585 
18586 	return -1;
18587 }
18588 
18589 //STEP 3: Add the calculation to the switch statement.
GetDamage(float damage_applied,int in_damage_type_idx,float diff_dmg_scale,int is_beam)18590 float ArmorType::GetDamage(float damage_applied, int in_damage_type_idx, float diff_dmg_scale, int is_beam) {
18591 	// Nuke: If the weapon has no damage type, just return damage
18592 	if (in_damage_type_idx < 0) {
18593 		// multiply by difficulty scaler now, since it is no longer done where this is called
18594 		return (damage_applied * diff_dmg_scale);
18595 	}
18596 
18597 	//Initialize vars
18598 	ArmorDamageType *adtp = NULL;
18599 
18600 	//Find the entry in the weapon that corresponds to the given weapon damage type
18601 	size_t num = DamageTypes.size();
18602 	for(size_t i = 0; i < num; i++)
18603 	{
18604 		if(DamageTypes[i].DamageTypeIndex == in_damage_type_idx)
18605 		{
18606 			adtp = &DamageTypes[i];
18607 			break;
18608 		}
18609 	}
18610 
18611 	//curr_arg is the current calculation type value
18612 	float curr_arg;
18613 
18614 	//Make sure that we _have_ an armor entry for this damage type
18615 	if(adtp != NULL)
18616 	{
18617 		//How many calculations do we have to do?
18618 		num = adtp->Calculations.size();
18619 
18620 		// Used for instant cutoffs/cap, to instantly end the loop
18621 		bool end_now = false;
18622 		// used for load/store operations
18623 		float storage[AT_NUM_STORAGE_LOCATIONS];
18624 		int storage_idx;
18625 		bool using_storage = false;
18626 		// constant related stuff
18627 		float constant_val;
18628 		float base_damage;
18629 		bool using_constant = false;
18630 
18631 		// set storage locations to zero
18632 		for (size_t i = 0; i < AT_NUM_STORAGE_LOCATIONS; i++) {
18633 			storage[i]=0.0f;
18634 		}
18635 
18636 		// check to see if we need to difficulty scale damage first
18637 		if (adtp->difficulty_scale_type == ADT_DIFF_SCALE_FIRST) {
18638 			damage_applied *= diff_dmg_scale;
18639 		}
18640 
18641 		// user may want to use base damage as a constant
18642 		base_damage = damage_applied;
18643 		// LOOP!
18644 		for (size_t i = 0; i < num; i++) {
18645 			storage_idx = adtp->altArguments[i];
18646 			//Set curr_arg
18647 			// use storage index at +Stored Value:
18648 			if ( (storage_idx >= 0) && (storage_idx < AT_NUM_STORAGE_LOCATIONS) ) {
18649 				curr_arg = storage[storage_idx];
18650 				using_storage = true;
18651 			// using +value: (or error cases caught at parse, where this holda a 0.0f)
18652 			} else if (storage_idx == AT_CONSTANT_NOT_USED) { // save time checking all possible constants when most of the time you will be using +value:
18653 				curr_arg = adtp->Arguments[i];
18654 			// maybe handle constants
18655 			} else if (storage_idx == AT_CONSTANT_BASE_DMG) {
18656 				curr_arg = base_damage;
18657 				using_constant = true;
18658 			} else if (storage_idx == AT_CONSTANT_CURRENT_DMG) {
18659 				curr_arg = damage_applied;
18660 				using_constant = true;
18661 			} else if (storage_idx == AT_CONSTANT_DIFF_FACTOR) {
18662 				curr_arg = diff_dmg_scale;
18663 				using_constant = true;
18664 			} else if (storage_idx == AT_CONSTANT_RANDOM) {
18665 				constant_val = frand();
18666 				curr_arg = constant_val;
18667 				using_constant = true;
18668 			} else if (storage_idx == AT_CONSTANT_PI) {
18669 				constant_val = PI;
18670 				curr_arg = constant_val;
18671 				using_constant = true;
18672 			} else { // fail
18673 				constant_val = 0.0f;
18674 				curr_arg = constant_val;
18675 			}
18676 
18677 			//face: terrible hack to work consistently with beams and additive damages
18678 			if (is_beam && ( adtp->Calculations[i] == AT_TYPE_ADDITIVE
18679 				|| adtp->Calculations[i] == AT_TYPE_CUTOFF
18680 				|| adtp->Calculations[i] == AT_TYPE_REVERSE_CUTOFF
18681 				|| adtp->Calculations[i] == AT_TYPE_INSTANT_CUTOFF
18682 				|| adtp->Calculations[i] == AT_TYPE_INSTANT_REVERSE_CUTOFF
18683 				|| adtp->Calculations[i] == AT_TYPE_CAP
18684 				|| adtp->Calculations[i] == AT_TYPE_INSTANT_CAP
18685 				|| adtp->Calculations[i] == AT_TYPE_SET
18686 				|| adtp->Calculations[i] == AT_TYPE_RANDOM)) {
18687 				curr_arg = curr_arg * (flFrametime * 1000.0f) / i2fl(BEAM_DAMAGE_TIME);
18688 			}
18689 
18690 			// new calcs go here
18691 			switch(adtp->Calculations[i])
18692 			{
18693 				case AT_TYPE_ADDITIVE:
18694 					damage_applied += curr_arg;
18695 					break;
18696 				case AT_TYPE_MULTIPLICATIVE:
18697 					damage_applied *= curr_arg;
18698 					break;
18699 				case AT_TYPE_EXPONENTIAL:
18700 					damage_applied = powf(damage_applied, curr_arg);
18701 					break;
18702 				case AT_TYPE_EXPONENTIAL_BASE:
18703 					damage_applied = powf(curr_arg, damage_applied);
18704 					break;
18705 				case AT_TYPE_CUTOFF:
18706 					if(damage_applied < curr_arg)
18707 						damage_applied = 0;
18708 					break;
18709 				case AT_TYPE_REVERSE_CUTOFF:
18710 					if(damage_applied > curr_arg)
18711 						damage_applied = 0;
18712 					break;
18713 				case AT_TYPE_INSTANT_CUTOFF:
18714 					if(damage_applied < curr_arg)
18715 					{
18716 						damage_applied = 0;
18717 						end_now = true;
18718 					}
18719 					break;
18720 				case AT_TYPE_INSTANT_REVERSE_CUTOFF:
18721 					if(damage_applied > curr_arg)
18722 					{
18723 						damage_applied = 0;
18724 						end_now = true;
18725 					}
18726 					break;
18727 				case AT_TYPE_CAP:
18728 					if (damage_applied > curr_arg)
18729 						damage_applied = curr_arg;
18730 					break;
18731 				case AT_TYPE_INSTANT_CAP:
18732 					if (damage_applied > curr_arg) {
18733 						damage_applied = curr_arg;
18734 						end_now = true;
18735 					}
18736 					break;
18737 				case AT_TYPE_SET:
18738 					damage_applied = curr_arg;
18739 					break;
18740 				case AT_TYPE_STORE:
18741 					if (using_storage || using_constant) {
18742 						Warning(LOCATION, "Cannot use +Stored Value: or +Constant: with +Store:, that would be bad. Skipping calculation.");
18743 					} else {
18744 						storage_idx =  int(floorf(curr_arg));
18745 						// Nuke: idiotproof this, no segfault 4 u
18746 						if ( (storage_idx < 0) || (storage_idx >= AT_NUM_STORAGE_LOCATIONS) ) {
18747 							Warning(LOCATION, "+Value: for +Store: calculation out of range. Should be between 0 and %i. Read: %i, Skipping calculation.", AT_NUM_STORAGE_LOCATIONS, storage_idx);
18748 							storage_idx = 0;
18749 						} else {
18750 							storage[storage_idx] = damage_applied;
18751 						}
18752 					}
18753 					break;
18754 				case AT_TYPE_LOAD:
18755 					if (using_storage || using_constant) {
18756 						Warning(LOCATION, "Cannot use +Stored Value: or +Constant: with +Load:, that would be bad. Skipping calculation.");
18757 					} else {
18758 						storage_idx =  int(floorf(curr_arg));
18759 						// Nuke: idiotproof this, no segfault 4 u
18760 						if ( (storage_idx < 0) || (storage_idx >= AT_NUM_STORAGE_LOCATIONS) ) {
18761 							Warning(LOCATION, "+Value: for +Load: calculation out of range. Should be between 0 and %i. Read: %i, Skipping calculation.", AT_NUM_STORAGE_LOCATIONS, storage_idx);
18762 							storage_idx = 0;
18763 						} else {
18764 							damage_applied = storage[storage_idx];
18765 						}
18766 					}
18767 					break;
18768 				case AT_TYPE_RANDOM:  // Nuke: get a random number between damage_applied and +value:
18769 					if (damage_applied > curr_arg) {
18770 						damage_applied = frand_range( curr_arg, damage_applied );
18771 					} else {
18772 						damage_applied = frand_range( damage_applied, curr_arg );
18773 					}
18774 				break;
18775 			}
18776 
18777 			if(end_now)
18778 				break;
18779 		}
18780 		// Nuke: check to see if we need to difficulty scale damage last
18781 		if (adtp->difficulty_scale_type == ADT_DIFF_SCALE_LAST)
18782 			damage_applied *= diff_dmg_scale;
18783 
18784 		// Face: negative damages should not heal you!!!
18785 		if (damage_applied < 0.0f)
18786 			damage_applied = 0.0f;
18787 
18788 		return damage_applied;
18789 	}
18790 	// fail return is fail
18791 	return (damage_applied * diff_dmg_scale);
18792 }
18793 
GetShieldPiercePCT(int damage_type_idx)18794 float ArmorType::GetShieldPiercePCT(int damage_type_idx)
18795 {
18796 	if(damage_type_idx < 0)
18797 		return 0.0f;
18798 
18799 	//Initialize vars
18800 	ArmorDamageType *adtp = NULL;
18801 
18802 	//Find the entry in the weapon that corresponds to the given weapon damage type
18803 	size_t num = DamageTypes.size();
18804 	for(size_t i = 0; i < num; i++)
18805 	{
18806 		if(DamageTypes[i].DamageTypeIndex == damage_type_idx)
18807 		{
18808 			adtp = &DamageTypes[i];
18809 			break;
18810 		}
18811 	}
18812 	if(adtp != NULL){
18813 		return adtp->shieldpierce_pct;
18814 	}
18815 
18816 	return 0.0f;
18817 }
18818 
GetPiercingType(int damage_type_idx)18819 int ArmorType::GetPiercingType(int damage_type_idx)
18820 {
18821 	if(damage_type_idx < 0)
18822 		return 0;
18823 
18824 	//Initialize vars
18825 	ArmorDamageType *adtp = NULL;
18826 
18827 	//Find the entry in the weapon that corresponds to the given weapon damage type
18828 	size_t num = DamageTypes.size();
18829 	for(size_t i = 0; i < num; i++)
18830 	{
18831 		if(DamageTypes[i].DamageTypeIndex == damage_type_idx)
18832 		{
18833 			adtp = &DamageTypes[i];
18834 			break;
18835 		}
18836 	}
18837 	if(adtp != NULL){
18838 		return adtp->piercing_type;
18839 	}
18840 
18841 	return 0;
18842 }
18843 
GetPiercingLimit(int damage_type_idx)18844 float ArmorType::GetPiercingLimit(int damage_type_idx)
18845 {
18846 	if(damage_type_idx < 0)
18847 		return 0.0f;
18848 
18849 	//Initialize vars
18850 	ArmorDamageType *adtp = NULL;
18851 
18852 	//Find the entry in the weapon that corresponds to the given weapon damage type
18853 	size_t num = DamageTypes.size();
18854 	for(size_t i = 0; i < num; i++)
18855 	{
18856 		if(DamageTypes[i].DamageTypeIndex == damage_type_idx)
18857 		{
18858 			adtp = &DamageTypes[i];
18859 			break;
18860 		}
18861 	}
18862 	if(adtp != NULL){
18863 		return adtp->piercing_start_pct;
18864 	}
18865 
18866 	return 0.0f;
18867 }
18868 
18869 //***********************************Member functions
18870 
ArmorType(const char * in_name)18871 ArmorType::ArmorType(const char* in_name)
18872 {
18873 	auto len = strlen(in_name);
18874 	if(len >= NAME_LENGTH) {
18875 		Warning(LOCATION, "Armor name %s is " SIZE_T_ARG " characters too long, and will be truncated", in_name, len - NAME_LENGTH);
18876 	}
18877 
18878 	strncpy(Name, in_name, NAME_LENGTH-1);
18879 }
18880 
ParseData()18881 void ArmorType::ParseData()
18882 {
18883 	ArmorDamageType adt;
18884 	char buf[NAME_LENGTH];
18885 	float temp_float;
18886 	int temp_int;
18887 	int calc_type = -1;
18888 
18889 	//Get the damage types
18890 	required_string("$Damage Type:");
18891 	do
18892 	{
18893 		//Get damage type name
18894 		stuff_string(buf, F_NAME, NAME_LENGTH);
18895 
18896 		//Clear the struct and set the index
18897 		adt.clear();
18898 		adt.DamageTypeIndex = damage_type_add(buf);
18899 		bool no_content = true;
18900 
18901 		//Get calculation and argument
18902 		while (optional_string("+Calculation:"))
18903 		{
18904 			//+Calculation
18905 			stuff_string(buf, F_NAME, NAME_LENGTH);
18906 
18907 			calc_type = calculation_type_get(buf);
18908 
18909 			//Make sure we have a valid calculation type
18910 			if(calc_type == -1)
18911 			{
18912 				Warning(LOCATION, "Armor '%s': Armor calculation type '%s' is invalid, and has been skipped", Name, buf);
18913 				// Nuke: guess we need to add this here too
18914 				if (optional_string("+Stored Value:")) {
18915 					stuff_int(&temp_int);
18916 				} else if (optional_string("+Constant:")) {
18917 					stuff_string(buf, F_NAME, NAME_LENGTH);
18918 				} else {
18919 					required_string("+Value:");
18920 					stuff_float(&temp_float);
18921 				}
18922 			}
18923 			else
18924 			{
18925 				adt.Calculations.push_back(calc_type);
18926 				// Nuke: maybe were using a stored location
18927 				if (optional_string("+Stored Value:")) {
18928 					stuff_int(&temp_int);
18929 					// Nuke: idiot-proof
18930 					if ( (temp_int < 0) || (temp_int >= AT_NUM_STORAGE_LOCATIONS) ) {
18931 						Error(LOCATION, "+Stored Value: is out of range. Should be between 0 and %i. Read: %i, Using value 0.", AT_NUM_STORAGE_LOCATIONS-1, temp_int);
18932 						temp_int = AT_CONSTANT_NOT_USED;
18933 					}
18934 					adt.altArguments.push_back(temp_int);
18935 					adt.Arguments.push_back(0.0f); // this isnt used in this case, just take up space so the indices lign up, also a fallback value in case of bad altArguments
18936 				} else if (optional_string("+Constant:")) { // use one of the pre-defined constants
18937 					stuff_string(buf, F_NAME, NAME_LENGTH);
18938 					temp_int = armor_type_constants_get(buf);
18939 					// Nuke: idiot proof some more
18940 					if (temp_int == AT_CONSTANT_BAD_VAL) {
18941 						Error(LOCATION, "Invalid +Constant: name, '%s'. Using value 0.", buf);
18942 						temp_int = AT_CONSTANT_NOT_USED;
18943 					}
18944 					adt.altArguments.push_back(temp_int);
18945 					adt.Arguments.push_back(0.0f); // this isnt used in this case, just take up space so the indices lign up, also a fallback value in case of bad altArguments
18946 				} else { // Nuke: +Value, only required if storage location or constant is not used -nuke
18947 					required_string("+Value:");
18948 					stuff_float(&temp_float);
18949 					adt.altArguments.push_back(AT_CONSTANT_NOT_USED); // set this to AT_CONSTANT_NOT_USED so we know to just use the value from adt.Arguments instead of constants/storage locations
18950 					adt.Arguments.push_back(temp_float);
18951 				}
18952 				no_content = false;
18953 			}
18954 		}
18955 
18956 		adt.shieldpierce_pct = 0.0f;
18957 
18958 		if(optional_string("+Shield Piercing Percentage:")) {
18959 			stuff_float(&temp_float);
18960 			CLAMP(temp_float, 0.0f, 1.0f);
18961 			adt.shieldpierce_pct = temp_float;
18962 			no_content = false;
18963 		}
18964 
18965 		adt.piercing_start_pct = 0.1f;
18966 		adt.piercing_type = -1;
18967 
18968 		if(optional_string("+Weapon Piercing Effect Start Limit:")) {
18969 			stuff_float(&temp_float);
18970 			CLAMP(temp_float, 0.0f, 100.0f);
18971 			temp_float /= 100.0f;
18972 			adt.piercing_start_pct = temp_float;
18973 			no_content = false;
18974 		}
18975 
18976 		if(optional_string("+Weapon Piercing Type:")) {
18977 			stuff_string(buf, F_NAME, NAME_LENGTH);
18978 			adt.piercing_type = piercing_type_get(buf);
18979 			no_content = false;
18980 		}
18981 
18982 		// Nuke: don't forget to init things
18983 		adt.difficulty_scale_type = ADT_DIFF_SCALE_FIRST;
18984 
18985 		if (optional_string("+Difficulty Scale Type:")) {
18986 			stuff_string(buf, F_NAME, NAME_LENGTH);
18987 			temp_int = difficulty_scale_type_get(buf);
18988 			if (temp_int == ADT_DIFF_SCALE_BAD_VAL) {
18989 				Error(LOCATION, "Invalid +Difficulty Scale Type: name: '%s'. Reverting to default behavior.", buf);
18990 				adt.difficulty_scale_type = ADT_DIFF_SCALE_FIRST;
18991 			} else {
18992 				adt.difficulty_scale_type = temp_int;
18993 			}
18994 			no_content = false;
18995 		}
18996 
18997 		//If we have calculations in this damage type, add it
18998 		if(!no_content)
18999 		{
19000 			if(adt.Calculations.size() != adt.Arguments.size())
19001 			{
19002 				Warning(LOCATION, "Armor '%s', damage type " SIZE_T_ARG ": Armor has a different number of calculation types than arguments (" SIZE_T_ARG ", " SIZE_T_ARG ")",
19003 						Name, DamageTypes.size(), adt.Calculations.size(), adt.Arguments.size());
19004 			}
19005 			DamageTypes.push_back(adt);
19006 		}
19007 	} while(optional_string("$Damage Type:"));
19008 }
19009 
19010 //********************************Global functions
19011 
armor_type_get_idx(const char * name)19012 int armor_type_get_idx(const char* name)
19013 {
19014 	auto num = Armor_types.size();
19015 	for(size_t i = 0; i < num; i++)
19016 	{
19017 		if(Armor_types[i].IsName(name))
19018 			return (int)i;
19019 	}
19020 
19021 	//Didn't find anything.
19022 	return -1;
19023 }
19024 
parse_armor_type()19025 void parse_armor_type()
19026 {
19027 	char name_buf[NAME_LENGTH];
19028 	ArmorType tat("");
19029 
19030 	required_string("$Name:");
19031 	stuff_string(name_buf, F_NAME, NAME_LENGTH);
19032 
19033 	tat = ArmorType(name_buf);
19034 
19035 	//now parse the actual table (damage type/armor type pairs)
19036 	tat.ParseData();
19037 
19038 	//rest of the parse data
19039 	if (optional_string("$Flags:"))
19040 		parse_string_flag_list((int*)&tat.flags, Armor_flags, Num_armor_flags);
19041 
19042 	//Add it to global armor types
19043 	Armor_types.push_back(tat);
19044 }
19045 
armor_parse_table(const char * filename)19046 void armor_parse_table(const char *filename)
19047 {
19048 	try
19049 	{
19050 		read_file_text(filename, CF_TYPE_TABLES);
19051 		reset_parse();
19052 
19053 		//Enumerate through all the armor types and add them.
19054 		while (optional_string("#Armor Type")) {
19055 			while (required_string_either("#End", "$Name:")) {
19056 				parse_armor_type();
19057 				continue;
19058 			}
19059 
19060 			required_string("#End");
19061 		}
19062 	}
19063 	catch (const parse::ParseException& e)
19064 	{
19065 		mprintf(("TABLES: Unable to parse '%s'!  Error message = %s.\n", filename, e.what()));
19066 		return;
19067 	}
19068 }
19069 
armor_init()19070 void armor_init()
19071 {
19072 	if (!Armor_inited) {
19073 		armor_parse_table("armor.tbl");
19074 
19075 		parse_modular_table(NOX("*-amr.tbm"), armor_parse_table);
19076 
19077 		Armor_inited = true;
19078 	}
19079 }
19080 
19081 //**************************************************************
19082 // AI targeting priority functions
19083 //**************************************************************
parse_ai_target_priorities()19084 void parse_ai_target_priorities()
19085 {
19086 	int i, j;
19087 	int n_entries = (int)Ai_tp_list.size();
19088 	SCP_vector <SCP_string> temp_strings;
19089 
19090 	bool first_time = false;
19091 	int already_exists = -1;
19092 
19093 	if (n_entries == 0)
19094 		first_time = true;
19095 
19096 	required_string("$Name:");
19097 	ai_target_priority temp_priority = init_ai_target_priorities();
19098 
19099 	stuff_string(temp_priority.name, F_NAME, NAME_LENGTH);
19100 	if (first_time == false) {
19101 		for (i = 0; i < n_entries; i++) {
19102 			if (!strnicmp(temp_priority.name, Ai_tp_list[i].name, NAME_LENGTH)) {
19103 				already_exists = i;
19104 			}
19105 		}
19106 	}
19107 
19108 	if (optional_string("+Object Type:") ) {
19109 		char tempname[NAME_LENGTH];
19110 		stuff_string(tempname, F_NAME, NAME_LENGTH);
19111 
19112 		for (j = 0; j < num_ai_tgt_objects; j++) {
19113 			if ( !stricmp(ai_tgt_objects[j].name, tempname) ) {
19114 				temp_priority.obj_type = ai_tgt_objects[j].def;
19115 			}
19116 		}
19117 	}
19118 
19119 	if (optional_string("+Weapon Class:") ) {
19120 		temp_strings.clear();
19121 		stuff_string_list(temp_strings);
19122 
19123 		for(i = 0; i < (int)temp_strings.size(); i++) {
19124 			for(j = 0; j < weapon_info_size(); ++j) {
19125 				if ( !stricmp(Weapon_info[j].name, temp_strings[i].c_str()) ) {
19126 					temp_priority.weapon_class.push_back(j);
19127 					break;
19128 				}
19129 			}
19130 			if (j == weapon_info_size()) {
19131 				Warning(LOCATION, "Unidentified weapon class '%s' set for target priority group '%s'\n", temp_strings[i].c_str(), temp_priority.name);
19132 			}
19133 		}
19134 	}
19135 
19136 	if (optional_string("+Object Flags:") ) {
19137 		temp_strings.clear();
19138 		stuff_string_list(temp_strings);
19139 
19140 		for (i = 0; i < (int)temp_strings.size(); i++) {
19141 			for (j = 0; j < num_ai_tgt_obj_flags; j++) {
19142 				if ( !stricmp(ai_tgt_obj_flags[j].name, temp_strings[i].c_str()) ) {
19143 					temp_priority.obj_flags |= ai_tgt_obj_flags[j].def;
19144 					break;
19145 				}
19146 			}
19147 			if (j == num_ai_tgt_obj_flags) {
19148 				Warning(LOCATION, "Unidentified object flag '%s' set for target priority group '%s'\n", temp_strings[i].c_str(), temp_priority.name);
19149 			}
19150 		}
19151 	}
19152 
19153 	if (optional_string("+Ship Class Flags:")) {
19154 		temp_strings.clear();
19155 		stuff_string_list(temp_strings);
19156 
19157 		for (i = 0; i < (int)temp_strings.size(); i++) {
19158 			for (j = 0; j < num_ai_tgt_ship_flags; j++) {
19159 				if (!stricmp(ai_tgt_ship_flags[j].name, temp_strings[i].c_str())) {
19160 					temp_priority.sif_flags.set(ai_tgt_ship_flags[j].def);
19161 					break;
19162 				}
19163 			}
19164 			if (j == num_ai_tgt_ship_flags) {
19165 				Warning(LOCATION, "Unidentified ship class flag '%s' set for target priority group '%s'\n", temp_strings[i].c_str(), temp_priority.name);
19166 			}
19167 		}
19168 	}
19169 
19170 	if (optional_string("+Weapon Class Flags:") ) {
19171 		temp_strings.clear();
19172 		stuff_string_list(temp_strings);
19173 
19174 		for (i = 0; i < (int)temp_strings.size(); i++) {
19175 			for (j = 0; j < num_ai_tgt_weapon_info_flags; j++) {
19176 				if ( !stricmp(ai_tgt_weapon_flags[j].name, temp_strings[i].c_str()) ) {
19177 					temp_priority.wif_flags |= ai_tgt_weapon_flags[j].def;
19178 					break;
19179 				}
19180 			}
19181 			if (j == num_ai_tgt_weapon_info_flags) {
19182 				Warning(LOCATION, "Unidentified weapon class flag '%s' set for target priority group '%s'\n", temp_strings[i].c_str(), temp_priority.name);
19183 			}
19184 		}
19185 	}
19186 
19187 	temp_strings.clear();
19188 
19189 	if (already_exists == -1) {
19190 		Ai_tp_list.push_back(temp_priority);
19191 	} else {
19192 		Ai_tp_list[already_exists] = temp_priority;
19193 	}
19194 }
19195 
init_ai_target_priorities()19196 ai_target_priority init_ai_target_priorities()
19197 {
19198 	ai_target_priority temp_priority;
19199 
19200 	//initialize the entries
19201 	temp_priority.obj_flags.reset();
19202 	temp_priority.obj_type = -1;
19203 	temp_priority.ship_class.clear();
19204 	temp_priority.ship_type.clear();
19205 	temp_priority.sif_flags.reset();
19206 	temp_priority.weapon_class.clear();
19207 	temp_priority.wif_flags.reset();
19208 	temp_priority.name[0] = '\0';
19209 
19210 	//return the initialized
19211 	return temp_priority;
19212 }
19213 
parse_weapon_targeting_priorities()19214 void parse_weapon_targeting_priorities()
19215 {
19216 	char tempname[NAME_LENGTH];
19217 
19218 	if (optional_string("$Name:")) {
19219 		stuff_string(tempname, F_NAME, NAME_LENGTH);
19220 
19221 		int k = weapon_info_lookup(tempname);
19222 		if (k < 0) {
19223 			error_display(0, "Unrecognized weapon '%s' found when setting weapon targeting priorities.\n", tempname);
19224 			if (optional_string("+Target Priority:")) {		// consume the data to avoid parsing errors
19225 				SCP_vector<SCP_string> dummy;
19226 				stuff_string_list(dummy);
19227 			}
19228 		} else {
19229 			// reset the list
19230 
19231 			weapon_info *wip = &Weapon_info[k];
19232 
19233 			wip->num_targeting_priorities = 0;
19234 
19235 			if (optional_string("+Target Priority:")) {
19236 				SCP_vector <SCP_string> tgt_priorities;
19237 				stuff_string_list(tgt_priorities);
19238 
19239 				if (tgt_priorities.size() > 32)
19240 					tgt_priorities.resize(32);
19241 
19242 				auto num_groups = Ai_tp_list.size();
19243 
19244 				for(size_t i = 0; i < tgt_priorities.size(); i++) {
19245 					size_t j;
19246 					for(j = 0; j < num_groups; j++) {
19247 						if ( !stricmp(Ai_tp_list[j].name, tgt_priorities[i].c_str()))  {
19248 							wip->targeting_priorities[i] = (int)j;
19249 							wip->num_targeting_priorities++;
19250 							break;
19251 						}
19252 					}
19253 					if(j == num_groups)
19254 						error_display(0, "Unrecognized string '%s' found when setting weapon targeting priorities.\n", tgt_priorities[i].c_str());
19255 				}
19256 			}
19257 		}
19258 	}
19259 }
19260 
ship_get_subobj_model_num(ship_info * sip,char * subobj_name)19261 static int ship_get_subobj_model_num(ship_info* sip, char* subobj_name)
19262 {
19263 	for (int i = 0; i < sip->n_subsystems; i++) {
19264 		if (!subsystem_stricmp(sip->subsystems[i].subobj_name, subobj_name))
19265 			return sip->subsystems[i].subobj_num;
19266 	}
19267 
19268 	return -1;
19269 }
19270 
init_path_metadata(path_metadata & metadata)19271 void init_path_metadata(path_metadata& metadata)
19272 {
19273 	vm_vec_zero(&metadata.departure_rvec);
19274 	metadata.arrive_speed_mult = FLT_MIN;
19275 	metadata.depart_speed_mult = FLT_MIN;
19276 }
19277 
ship_get_sound(object * objp,GameSounds id)19278 gamesnd_id ship_get_sound(object *objp, GameSounds id)
19279 {
19280 	Assert( objp != NULL );
19281 	Assert( gamesnd_game_sound_valid(gamesnd_id(id)) );
19282 
19283 	// It's possible that this gets called when an object (in most cases the player) is dead or an observer
19284 	if (objp->type == OBJ_OBSERVER || objp->type == OBJ_GHOST)
19285 		return gamesnd_id(id);
19286 
19287 	Assertion(objp->type == OBJ_SHIP, "Expected a ship, got '%s'.", Object_type_names[objp->type]);
19288 
19289 	ship *shipp = &Ships[objp->instance];
19290 	ship_info *sip = &Ship_info[shipp->ship_info_index];
19291 
19292 	SCP_map<GameSounds, gamesnd_id>::iterator element = sip->ship_sounds.find(id);
19293 
19294 	if (element == sip->ship_sounds.end())
19295 		return gamesnd_id(id);
19296 	else
19297 		return (*element).second;
19298 }
19299 
ship_has_sound(object * objp,GameSounds id)19300 bool ship_has_sound(object *objp, GameSounds id)
19301 {
19302 	Assert( objp != NULL );
19303 	Assert( gamesnd_game_sound_valid(id) );
19304 
19305 	// It's possible that this gets called when an object (in most cases the player) is dead or an observer
19306 	if (objp->type == OBJ_OBSERVER || objp->type == OBJ_GHOST)
19307 		return false;
19308 
19309 	Assertion(objp->type == OBJ_SHIP, "Expected a ship, got '%s'.", Object_type_names[objp->type]);
19310 
19311 	ship *shipp = &Ships[objp->instance];
19312 	ship_info *sip = &Ship_info[shipp->ship_info_index];
19313 
19314 	auto element = sip->ship_sounds.find(id);
19315 
19316 	if (element == sip->ship_sounds.end())
19317 		return false;
19318 	else
19319 		return true;
19320 }
19321 
19322 /**
19323  * Given a ship with bounding box and a point, find the closest point on the bbox
19324  *
19325  * @param ship_objp Object that has the bounding box (should be a ship)
19326  * @param start World position of the point being compared
19327  * @param box_pt OUTPUT PARAMETER: closest point on the bbox to start
19328  *
19329  * @return point is inside bbox, TRUE/1
19330  * @return point is outside bbox, FALSE/0
19331  */
get_nearest_bbox_point(object * ship_objp,vec3d * start,vec3d * box_pt)19332 int get_nearest_bbox_point(object *ship_objp, vec3d *start, vec3d *box_pt)
19333 {
19334 	vec3d temp, rf_start;
19335 	polymodel *pm;
19336 	pm = model_get(Ship_info[Ships[ship_objp->instance].ship_info_index].model_num);
19337 
19338 	// get start in ship rf
19339 	vm_vec_sub(&temp, start, &ship_objp->pos);
19340 	vm_vec_rotate(&rf_start, &temp, &ship_objp->orient);
19341 
19342 	// find box_pt
19343 	int inside = project_point_onto_bbox(&pm->mins, &pm->maxs, &rf_start, &temp);
19344 
19345 	// get box_pt in world rf
19346 	vm_vec_unrotate(box_pt, &temp, &ship_objp->orient);
19347 	vm_vec_add2(box_pt, &ship_objp->pos);
19348 
19349 	return inside;
19350 }
19351 
ship_set_thruster_info(mst_info * mst,object * obj,ship * shipp,ship_info * sip)19352 void ship_set_thruster_info(mst_info *mst, object *obj, ship *shipp, ship_info *sip)
19353 {
19354 	mst->length.xyz.z = obj->phys_info.forward_thrust;
19355 	mst->length.xyz.x = obj->phys_info.side_thrust;
19356 	mst->length.xyz.y = obj->phys_info.vert_thrust;
19357 
19358 	//	Maybe add noise to thruster geometry.
19359 	if (!(sip->flags[Ship::Info_Flags::No_thruster_geo_noise])) {
19360 		mst->length.xyz.z *= (1.0f + frand()/5.0f - 0.1f);
19361 		mst->length.xyz.y *= (1.0f + frand()/5.0f - 0.1f);
19362 		mst->length.xyz.x *= (1.0f + frand()/5.0f - 0.1f);
19363 	}
19364 
19365 	CLAMP(mst->length.xyz.z, -1.0f, 1.0f);
19366 	CLAMP(mst->length.xyz.y, -1.0f, 1.0f);
19367 	CLAMP(mst->length.xyz.x, -1.0f, 1.0f);
19368 
19369 	mst->primary_bitmap = shipp->thruster_bitmap;
19370 	mst->primary_glow_bitmap = shipp->thruster_glow_bitmap;
19371 	mst->secondary_glow_bitmap = shipp->thruster_secondary_glow_bitmap;
19372 	mst->tertiary_glow_bitmap = shipp->thruster_tertiary_glow_bitmap;
19373 	mst->distortion_bitmap = shipp->thruster_distortion_bitmap;
19374 
19375 	mst->use_ab = (obj->phys_info.flags & PF_AFTERBURNER_ON) || (obj->phys_info.flags & PF_BOOSTER_ON);
19376 	mst->glow_noise = shipp->thruster_glow_noise * sip->thruster_glow_noise_mult;
19377 	mst->rotvel = Objects[shipp->objnum].phys_info.rotvel;
19378 
19379 	mst->glow_rad_factor = sip->thruster01_glow_rad_factor;
19380 	mst->secondary_glow_rad_factor = sip->thruster02_glow_rad_factor;
19381 	mst->tertiary_glow_rad_factor = sip->thruster03_glow_rad_factor;
19382 	mst->glow_length_factor = sip->thruster02_glow_len_factor;
19383 	mst->distortion_length_factor = sip->thruster_dist_len_factor;
19384 	mst->distortion_rad_factor = sip->thruster_dist_rad_factor;
19385 
19386 	mst->draw_distortion = sip->draw_distortion;
19387 }
19388 
ship_render_batch_thrusters(object * obj)19389 void ship_render_batch_thrusters(object *obj)
19390 {
19391 	int num = obj->instance;
19392 	ship *shipp = &Ships[num];
19393 	ship_info *sip = &Ship_info[Ships[num].ship_info_index];
19394 
19395 	if ( Rendering_to_shadow_map ) return;
19396 
19397 	physics_info *pi = &Objects[shipp->objnum].phys_info;
19398 	float render_amount;
19399 
19400 	for ( int i = 0; i < sip->num_maneuvering; i++ ) {
19401 		man_thruster *mtp = &sip->maneuvering[i];
19402 
19403 		render_amount = 0.0f;
19404 
19405 		//WMC - get us a steady value
19406 		vec3d des_vel;
19407 		vm_vec_rotate(&des_vel, &pi->desired_vel, &obj->orient);
19408 
19409 		if(pi->desired_rotvel.xyz.x < 0 && (mtp->use_flags[Ship::Thruster_Flags::Pitch_up])) {
19410 			render_amount = fl_abs(pi->desired_rotvel.xyz.x) / pi->max_rotvel.xyz.x;
19411 		} else if(pi->desired_rotvel.xyz.x > 0 && (mtp->use_flags[Ship::Thruster_Flags::Pitch_down])) {
19412 			render_amount = fl_abs(pi->desired_rotvel.xyz.x) / pi->max_rotvel.xyz.x;
19413 		} else if(pi->desired_rotvel.xyz.y < 0 && (mtp->use_flags[Ship::Thruster_Flags::Roll_right])) {
19414 			render_amount = fl_abs(pi->desired_rotvel.xyz.y) / pi->max_rotvel.xyz.y;
19415 		} else if(pi->desired_rotvel.xyz.y > 0 && (mtp->use_flags[Ship::Thruster_Flags::Roll_left])) {
19416 			render_amount = fl_abs(pi->desired_rotvel.xyz.y) / pi->max_rotvel.xyz.y;
19417 		} else if(pi->desired_rotvel.xyz.z < 0 && (mtp->use_flags[Ship::Thruster_Flags::Bank_right])) {
19418 			render_amount = fl_abs(pi->desired_rotvel.xyz.z) / pi->max_rotvel.xyz.z;
19419 		} else if(pi->desired_rotvel.xyz.z > 0 && (mtp->use_flags[Ship::Thruster_Flags::Bank_left])) {
19420 			render_amount = fl_abs(pi->desired_rotvel.xyz.z) / pi->max_rotvel.xyz.z;
19421 		}
19422 
19423 		//Backslash - show thrusters according to thrust amount, not speed
19424 		if(pi->side_thrust > 0 && (mtp->use_flags[Ship::Thruster_Flags::Slide_right])) {
19425 			render_amount = pi->side_thrust;
19426 		} else if(pi->side_thrust < 0 && (mtp->use_flags[Ship::Thruster_Flags::Slide_left])) {
19427 			render_amount = -pi->side_thrust;
19428 		} else if(pi->vert_thrust > 0 && (mtp->use_flags[Ship::Thruster_Flags::Slide_up])) {
19429 			render_amount = pi->vert_thrust;
19430 		} else if(pi->vert_thrust < 0 && (mtp->use_flags[Ship::Thruster_Flags::Slide_down])) {
19431 			render_amount = -pi->vert_thrust;
19432 		} else if(pi->forward_thrust > 0 && (mtp->use_flags[Ship::Thruster_Flags::Forward])) {
19433 			render_amount = pi->forward_thrust;
19434 		} else if(pi->forward_thrust < 0 && (mtp->use_flags[Ship::Thruster_Flags::Reverse])) {
19435 			render_amount = -pi->forward_thrust;
19436 		}
19437 
19438 		//Don't render small faraway thrusters (more than 10k * radius away)
19439 		if ( vm_vec_dist(&Eye_position, &obj->pos) > (10000.0f * mtp->radius) ) {
19440 			render_amount = 0.0f;
19441 		}
19442 
19443 		if ( render_amount > 0.0f ) {
19444 			//Handle sounds and stuff
19445 			if ( shipp->thrusters_start[i] <= 0 ) {
19446 				shipp->thrusters_start[i] = timestamp();
19447 				if(mtp->start_snd.isValid())
19448 					snd_play_3d( gamesnd_get_game_sound(mtp->start_snd), &mtp->pos, &Eye_position, 0.0f, &obj->phys_info.vel );
19449 			}
19450 
19451 			//Only assign looping sound if
19452 			//it is specified
19453 			//it isn't assigned already
19454 			//start sound doesn't exist or has finished
19455 			if (!Cmdline_freespace_no_sound) {
19456 				if(mtp->loop_snd.isValid()
19457 					&& shipp->thrusters_sounds[i] < 0
19458 					&& (!mtp->start_snd.isValid() || (gamesnd_get_max_duration(gamesnd_get_game_sound(mtp->start_snd)) < timestamp() - shipp->thrusters_start[i]))
19459 					)
19460 				{
19461 					shipp->thrusters_sounds[i] = obj_snd_assign(OBJ_INDEX(obj), mtp->loop_snd, &mtp->pos, OS_MAIN);
19462 				}
19463 			}
19464 
19465 			//Draw graphics
19466 			//Skip invalid ones
19467 			if ( mtp->tex_id >= 0 ) {
19468 				float rad = mtp->radius;
19469 				if(rad <= 0.0f)
19470 					rad = 1.0f;
19471 
19472 				float len = mtp->length;
19473 				if(len == 0.0f)
19474 					len = rad;
19475 
19476 				vec3d start, tmpend, end;
19477 				//Start
19478 				vm_vec_unrotate(&start, &mtp->pos, &obj->orient);
19479 				vm_vec_add2(&start, &obj->pos);
19480 
19481 				//End
19482 				vm_vec_scale_add(&tmpend, &mtp->pos, &mtp->norm, len * render_amount);
19483 				vm_vec_unrotate(&end, &tmpend, &obj->orient);
19484 				vm_vec_add2(&end, &obj->pos);
19485 
19486 				int bmap_frame = mtp->tex_id;
19487 				if(mtp->tex_nframes > 0)
19488 					bmap_frame += bm_get_anim_frame(mtp->tex_id, i2fl(timestamp() - shipp->thrusters_start[i]) / 1000.0f, 0.0f, true);
19489 
19490 				//man_thruster_renderer *mtr = man_thruster_get_slot(bmap_frame);
19491 				//mtr->man_batcher.add_allocate(1);
19492 				//mtr->man_batcher.draw_beam(&start, &end, rad, 1.0f);
19493 				batching_add_beam(bmap_frame, &start, &end, rad, 1.0f);
19494 			}
19495 		} else if ( shipp->thrusters_start[i] > 0 ) {
19496 			// We've stopped firing a thruster
19497 
19498 			shipp->thrusters_start[i] = 0;
19499 			if(shipp->thrusters_sounds[i] >= 0)
19500 			{
19501 				obj_snd_delete(obj, shipp->thrusters_sounds[i]);
19502 				shipp->thrusters_sounds[i] = -1;
19503 			}
19504 
19505 			if ( mtp->stop_snd.isValid() ) {
19506 				//Get world pos
19507 				vec3d start;
19508 				vm_vec_unrotate(&start, &mtp->pos, &obj->orient);
19509 				vm_vec_add2(&start, &obj->pos);
19510 
19511 				snd_play_3d( gamesnd_get_game_sound(mtp->stop_snd), &mtp->pos, &Eye_position, 0.0f, &obj->phys_info.vel );
19512 			}
19513 		}
19514 	}
19515 }
19516 
ship_render_weapon_models(model_render_params * ship_render_info,model_draw_list * scene,object * obj,int render_flags)19517 void ship_render_weapon_models(model_render_params *ship_render_info, model_draw_list *scene, object *obj, int render_flags)
19518 {
19519 	int num = obj->instance;
19520 	ship *shipp = &Ships[num];
19521 	ship_info *sip = &Ship_info[Ships[num].ship_info_index];
19522 
19523 	if ( !(sip->flags[Ship::Info_Flags::Draw_weapon_models]) || (shipp->flags[Ship_Flags::Cloaked]) || (shipp->flags[Ship_Flags::Render_without_weapons]) ) {
19524 		return;
19525 	}
19526 
19527 	int i,k;
19528 	ship_weapon *swp = &shipp->weapons;
19529 	auto ship_pm = model_get(sip->model_num);
19530 
19531 	scene->push_transform(&obj->pos, &obj->orient);
19532 
19533 	auto ship_render_flags = ship_render_info->get_model_flags();
19534 	render_flags &= ~MR_SHOW_THRUSTERS;
19535 	ship_render_info->set_flags(render_flags);
19536 
19537 	//primary weapons
19538 	for ( i = 0; i < swp->num_primary_banks; i++ ) {
19539 		auto wip = &Weapon_info[swp->primary_bank_weapons[i]];
19540 
19541 		if ( wip->external_model_num < 0 || !sip->draw_primary_models[i] ) {
19542 			continue;
19543 		}
19544 
19545 		// Lazily create the model instance here, if we need to.  The ideal place to put this
19546 		// would be in parse_object_create_sub, but the player can alter the ship loadout
19547 		// after that function runs.
19548 		if (!swp->primary_bank_model_instance_check[i])
19549 		{
19550 			auto pm = model_get(wip->external_model_num);
19551 
19552 			// create a model instance only if at least one submodel has gun rotation
19553 			for (int mn = 0; mn < pm->n_models; mn++)
19554 			{
19555 				if (pm->submodel[mn].gun_rotation)
19556 				{
19557 					swp->primary_bank_external_model_instance[i] = model_create_instance(false, wip->external_model_num);
19558 					break;
19559 				}
19560 			}
19561 
19562 			swp->primary_bank_model_instance_check[i] = true;
19563 		}
19564 
19565 		auto bank = &ship_pm->gun_banks[i];
19566 
19567 		if ( swp->primary_bank_external_model_instance[i] >= 0 )
19568 		{
19569 			auto pmi = model_get_instance(swp->primary_bank_external_model_instance[i]);
19570 			auto pm = model_get(pmi->model_num);
19571 
19572 			// spin the submodels by the gun rotation
19573 			for (int mn = 0; mn < pm->n_models; ++mn)
19574 			{
19575 				if (pm->submodel[mn].gun_rotation)
19576 				{
19577 					angles angs = vmd_zero_angles;
19578 					angs.b = shipp->primary_rotate_ang[i];
19579 					vm_angles_2_matrix(&pmi->submodel[mn].canonical_orient, &angs);
19580 				}
19581 			}
19582 		}
19583 
19584 		for ( k = 0; k < bank->num_slots; k++ ) {
19585 			// "Bank" the external model by the angle offset
19586 			angles angs = { 0.0f, bank->external_model_angle_offset[k], 0.0f };
19587 			matrix model_orient;
19588 			vm_angles_2_matrix(&model_orient, &angs);
19589 
19590 			model_render_queue(ship_render_info, scene, wip->external_model_num, swp->primary_bank_external_model_instance[i], &model_orient, &bank->pnt[k]);
19591 		}
19592 	}
19593 
19594 	//secondary weapons
19595 	int num_secondaries_rendered = 0;
19596 	vec3d secondary_weapon_pos;
19597 
19598 	for (i = 0; i < swp->num_secondary_banks; i++) {
19599 		auto wip = &Weapon_info[swp->secondary_bank_weapons[i]];
19600 
19601 		if ( wip->external_model_num == -1 || !sip->draw_secondary_models[i] ) {
19602 			continue;
19603 		}
19604 
19605 		auto bank = &ship_pm->missile_banks[i];
19606 
19607 		if (wip->wi_flags[Weapon::Info_Flags::External_weapon_lnch]) {
19608 			for(k = 0; k < bank->num_slots; k++) {
19609 				// "Bank" the external model by the angle offset
19610 				angles angs = { 0.0f, bank->external_model_angle_offset[k], 0.0f };
19611 				matrix model_orient;
19612 				vm_angles_2_matrix(&model_orient, &angs);
19613 
19614 				model_render_queue(ship_render_info, scene, wip->external_model_num, &model_orient, &bank->pnt[k]);
19615 			}
19616 		} else {
19617 			num_secondaries_rendered = 0;
19618 
19619 			for ( k = 0; k < bank->num_slots; k++ ) {
19620 				secondary_weapon_pos = bank->pnt[k];
19621 
19622 				if ( num_secondaries_rendered >= shipp->weapons.secondary_bank_ammo[i] ) {
19623 					break;
19624 				}
19625 
19626 				if ( shipp->secondary_point_reload_pct.get(i, k) <= 0.0 ) {
19627 					continue;
19628 				}
19629 
19630 				num_secondaries_rendered++;
19631 
19632 				vm_vec_scale_add2(&secondary_weapon_pos, &vmd_z_vector, -(1.0f-shipp->secondary_point_reload_pct.get(i, k)) * model_get(wip->external_model_num)->rad);
19633 
19634 				// "Bank" the external model by the angle offset
19635 				angles angs = { 0.0f, bank->external_model_angle_offset[k], 0.0f };
19636 				matrix model_orient;
19637 				vm_angles_2_matrix(&model_orient, &angs);
19638 
19639 				model_render_queue(ship_render_info, scene, wip->external_model_num, &model_orient, &secondary_weapon_pos);
19640 			}
19641 		}
19642 	}
19643 
19644 	ship_render_info->set_flags(ship_render_flags);
19645 
19646 	scene->pop_transform();
19647 }
19648 
ship_render_get_insignia(object * obj,ship * shipp)19649 int ship_render_get_insignia(object* obj, ship* shipp)
19650 {
19651 	if ( Rendering_to_shadow_map ) {
19652 		return -1;
19653 	}
19654 
19655 	if ( Game_mode & GM_MULTIPLAYER ) {
19656 		// if its any player's object
19657 		int np_index = multi_find_player_by_object( obj );
19658 		if ( (np_index >= 0) && (np_index < MAX_PLAYERS) && MULTI_CONNECTED(Net_players[np_index]) && (Net_players[np_index].m_player != NULL) ) {
19659 			return Net_players[np_index].m_player->insignia_texture;
19660 		}
19661 	}
19662 
19663 	// in single player, we want to render model insignias on all ships in alpha beta and gamma
19664 	// Goober5000 - and also on wings that have their logos set
19665 
19666 	// if its an object in my squadron
19667 	if ( ship_in_my_squadron(shipp) ) {
19668 		return Player->insignia_texture;
19669 	}
19670 
19671 	// maybe it has a wing squad logo - Goober5000
19672 	if ( shipp->wingnum >= 0 ) {
19673 		// don't override the player's wing
19674 		if ( shipp->wingnum != Player_ship->wingnum ) {
19675 			// if we have a logo texture
19676 			if ( Wings[shipp->wingnum].wing_insignia_texture >= 0 ) {
19677 				return Wings[shipp->wingnum].wing_insignia_texture;
19678 			}
19679 		}
19680 	}
19681 
19682 	return -1;
19683 }
19684 
ship_render_set_animated_effect(model_render_params * render_info,ship * shipp,uint *)19685 void ship_render_set_animated_effect(model_render_params *render_info, ship *shipp, uint * /*render_flags*/)
19686 {
19687 	if ( !shipp->shader_effect_active || Rendering_to_shadow_map ) {
19688 		return;
19689 	}
19690 
19691 	float timer;
19692 
19693 	ship_effect* sep = &Ship_effects[shipp->shader_effect_num];
19694 
19695 	if ( sep->invert_timer ) {
19696 		timer = 1.0f - ((timer_get_milliseconds() - shipp->shader_effect_start_time) / (float)shipp->shader_effect_duration);
19697 		timer = MAX(timer,0.0f);
19698 	} else {
19699 		timer = ((timer_get_milliseconds() - shipp->shader_effect_start_time) / (float)shipp->shader_effect_duration);
19700 	}
19701 
19702 	render_info->set_animated_effect(sep->shader_effect, timer);
19703 
19704 	if ( sep->disables_rendering && (timer_get_milliseconds() > shipp->shader_effect_start_time + shipp->shader_effect_duration) ) {
19705 		shipp->flags.set(Ship_Flags::Cloaked);
19706 		shipp->shader_effect_active = false;
19707 	} else {
19708 		shipp->flags.remove(Ship_Flags::Cloaked);
19709 		if (timer_get_milliseconds() > shipp->shader_effect_start_time + shipp->shader_effect_duration) {
19710 			shipp->shader_effect_active = false;
19711 		}
19712 	}
19713 }
19714 
ship_render(object * obj,model_draw_list * scene)19715 void ship_render(object* obj, model_draw_list* scene)
19716 {
19717 	int num = obj->instance;
19718 	ship *shipp = &Ships[num];
19719 	ship_info *sip = &Ship_info[Ships[num].ship_info_index];
19720 	ship *warp_shipp = NULL;
19721 	bool is_first_stage_arrival = false;
19722 	bool show_thrusters = (!shipp->flags[Ship_Flags::No_thrusters]) && !Rendering_to_shadow_map;
19723 	dock_function_info dfi;
19724 
19725 	MONITOR_INC( NumShipsRend, 1 );
19726 
19727 	// look for a warping ship, whether for me or for anybody I'm docked with
19728 	dock_evaluate_all_docked_objects(obj, &dfi, ship_find_warping_ship_helper);
19729 
19730 	// if any docked objects are set to stage 1 arrival then set bool
19731 	if ( dfi.maintained_variables.bool_value ) {
19732 		warp_shipp = &Ships[dfi.maintained_variables.objp_value->instance];
19733 
19734 		is_first_stage_arrival = warp_shipp->flags[Ship_Flags::Arriving_stage_1];
19735 
19736 		// This is a hack to make ships using the hyperspace warpin type to
19737 		// render even in stage 1, which is used for collision detection
19738 		// purposes -zookeeper
19739 		if ( Warp_params[warp_shipp->warpin_params_index].warp_type == WT_HYPERSPACE ) {
19740 			warp_shipp = NULL;
19741 			is_first_stage_arrival = false;
19742 		}
19743 	}
19744 
19745 	if ( is_first_stage_arrival ) {
19746 		//WMC - Draw animated warp effect (ie BSG thingy)
19747 		//WMC - based on Bobb's secondary thruster stuff
19748 		//which was in turn based on the beam code.
19749 		//I'm gonna need some serious acid to neutralize this base.
19750 		if(shipp->is_arriving(ship::warpstage::BOTH, true)) {
19751 			shipp->warpin_effect->warpShipRender();
19752 		} else if(shipp->flags[Ship_Flags::Depart_warp]) {
19753 			shipp->warpout_effect->warpShipRender();
19754 		}
19755 
19756 		return;
19757 	}
19758 
19759 	if ( obj == Viewer_obj && !Rendering_to_shadow_map ) {
19760 		if (!(Viewer_mode & VM_TOPDOWN))
19761 		{
19762 			return;
19763 		}
19764 	}
19765 
19766 	auto pmi = model_get_instance(shipp->model_instance_num);
19767 	auto pm = model_get(pmi->model_num);
19768 
19769 	model_clear_instance(sip->model_num);
19770 	model_instance_clear_arcs(pm, pmi);
19771 
19772 	// Only render electrical arcs if within 500m of the eye (for a 10m piece)
19773 	if ( vm_vec_dist_quick( &obj->pos, &Eye_position ) < obj->radius*50.0f && !Rendering_to_shadow_map ) {
19774 		for ( int i = 0; i < MAX_SHIP_ARCS; i++ )	{
19775 			if ( timestamp_valid(shipp->arc_timestamp[i]) ) {
19776 				model_instance_add_arc(pm, pmi, -1, &shipp->arc_pts[i][0], &shipp->arc_pts[i][1], shipp->arc_type[i]);
19777 			}
19778 		}
19779 	}
19780 
19781 	uint render_flags = MR_NORMAL;
19782 
19783 	if ( shipp->large_ship_blowup_index >= 0 )	{
19784 		shipfx_large_blowup_queue_render(scene, shipp);
19785 
19786 		//WMC - Draw animated warp effect (ie BSG thingy)
19787 		//WMC - based on Bobb's secondary thruster stuff
19788 		//which was in turn based on the beam code.
19789 		//I'm gonna need some serious acid to neutralize this base.
19790 		if(shipp->is_arriving(ship::warpstage::BOTH, true)) {
19791 			shipp->warpin_effect->warpShipRender();
19792 		} else if(shipp->flags[Ship_Flags::Depart_warp]) {
19793 			shipp->warpout_effect->warpShipRender();
19794 		}
19795 
19796 		return;
19797 	}
19798 
19799 	ship_render_batch_thrusters(obj);
19800 
19801 	model_render_params render_info;
19802 
19803 	if ( !(shipp->flags[Ship_Flags::Disabled]) && !ship_subsys_disrupted(shipp, SUBSYSTEM_ENGINE) && show_thrusters) {
19804 		mst_info mst;
19805 
19806 		ship_set_thruster_info(&mst, obj, shipp, sip);
19807 
19808 		render_info.set_thruster_info(mst);
19809 
19810 		render_flags |= MR_SHOW_THRUSTERS;
19811 	}
19812 
19813 	// Warp_shipp points to the ship that is going through a
19814 	// warp... either this ship or the ship it is docked with.
19815 	if ( warp_shipp != NULL ) {
19816 		if ( warp_shipp->is_arriving(ship::warpstage::BOTH, true) ) {
19817 			warp_shipp->warpin_effect->warpShipClip(&render_info);
19818 		} else if ( warp_shipp->flags[Ship_Flags::Depart_warp] ) {
19819 			warp_shipp->warpout_effect->warpShipClip(&render_info);
19820 		}
19821 	}
19822 
19823 	// maybe set squad logo bitmap
19824 	render_info.set_insignia_bitmap(ship_render_get_insignia(obj, shipp));
19825 
19826 	// Valathil - maybe do a scripting hook here to do some scriptable effects?
19827 	ship_render_set_animated_effect(&render_info, shipp, &render_flags);
19828 
19829 	if ( sip->uses_team_colors && !shipp->flags[Ship_Flags::Render_without_miscmap] ) {
19830 		team_color model_team_color;
19831 
19832 		bool team_color_set = model_get_team_color(&model_team_color, shipp->team_name, shipp->secondary_team_name, shipp->team_change_timestamp, shipp->team_change_time);
19833 
19834 		if ( team_color_set ) {
19835 			render_info.set_team_color(model_team_color);
19836 		}
19837 	}
19838 
19839 	if ( sip->flags[Ship::Info_Flags::No_lighting] ) {
19840 		render_flags |= MR_NO_LIGHTING;
19841 	}
19842 
19843 	if ( Rendering_to_shadow_map ) {
19844 		render_flags = MR_NO_TEXTURING | MR_NO_LIGHTING;
19845 	}
19846 
19847 	if (shipp->flags[Ship_Flags::Glowmaps_disabled]) {
19848 		render_flags |= MR_NO_GLOWMAPS;
19849 	}
19850 
19851 	if (shipp->flags[Ship_Flags::Draw_as_wireframe]) {
19852 		render_flags |= MR_SHOW_OUTLINE_HTL | MR_NO_POLYS | MR_NO_TEXTURING;
19853 		render_info.set_color(Wireframe_color);
19854 	}
19855 
19856 	if (shipp->flags[Ship_Flags::Render_full_detail]) {
19857 		render_flags |= MR_FULL_DETAIL;
19858 	}
19859 
19860 	if (shipp->flags[Ship_Flags::Render_without_light]) {
19861 		render_flags |= MR_NO_LIGHTING;
19862 	}
19863 
19864 	uint debug_flags = render_info.get_debug_flags();
19865 
19866 	if (shipp->flags[Ship_Flags::Render_without_diffuse]) {
19867 		debug_flags |= MR_DEBUG_NO_DIFFUSE;
19868 	}
19869 
19870 	if (shipp->flags[Ship_Flags::Render_without_glowmap]) {
19871 		debug_flags |= MR_DEBUG_NO_GLOW;
19872 	}
19873 
19874 	if (shipp->flags[Ship_Flags::Render_without_normalmap]) {
19875 		debug_flags |= MR_DEBUG_NO_NORMAL;
19876 	}
19877 
19878 	if (shipp->flags[Ship_Flags::Render_without_ambientmap]) {
19879 		debug_flags |= MR_DEBUG_NO_AMBIENT;
19880 	}
19881 
19882 	if (shipp->flags[Ship_Flags::Render_without_specmap]) {
19883 		debug_flags |= MR_DEBUG_NO_SPEC;
19884 	}
19885 
19886 	if (shipp->flags[Ship_Flags::Render_without_reflectmap]) {
19887 		debug_flags |= MR_DEBUG_NO_REFLECT;
19888 	}
19889 
19890 	render_info.set_flags(render_flags);
19891 	render_info.set_debug_flags(debug_flags);
19892 
19893 	//draw weapon models
19894 	ship_render_weapon_models(&render_info, scene, obj, render_flags);
19895 
19896 	render_info.set_object_number(OBJ_INDEX(obj));
19897 	render_info.set_replacement_textures(shipp->ship_replacement_textures);
19898 
19899 	// small ships
19900 	if ( !( shipp->flags[Ship_Flags::Cloaked] ) ) {
19901 		if ( ( The_mission.flags[Mission::Mission_Flags::Fullneb] ) && ( sip->is_small_ship() ) ) {
19902 			// force detail levels
19903 			float fog_val = neb2_get_fog_visibility(&obj->pos, 1.0f);
19904 			if ( fog_val <= 0.15f ) {
19905 				render_info.set_detail_level_lock(2);
19906 			}
19907 		}
19908 
19909 		model_render_queue(&render_info, scene, sip->model_num, &obj->orient, &obj->pos);
19910 	}
19911 
19912 	if (shipp->shield_hits && !Rendering_to_shadow_map) {
19913 		create_shield_explosion_all(obj);
19914 		shipp->shield_hits = 0;
19915 	}
19916 
19917 	//WMC - Draw animated warp effect (ie BSG thingy)
19918 	//WMC - based on Bobb's secondary thruster stuff
19919 	//which was in turn based on the beam code.
19920 	//I'm gonna need some serious acid to neutralize this base.
19921 	if(shipp->is_arriving(ship::warpstage::BOTH, true)) {
19922 		shipp->warpin_effect->warpShipRender();
19923 	} else if(shipp->flags[Ship_Flags::Depart_warp]) {
19924 		shipp->warpout_effect->warpShipRender();
19925 	}
19926 }
19927 
19928 flagset<Ship::Ship_Flags> Ignore_List;
set_default_ignore_list()19929 void set_default_ignore_list() {
19930 	Ignore_List.reset();
19931 	Ignore_List.set(Ship::Ship_Flags::Exploded);
19932 	Ignore_List.set(Ship::Ship_Flags::Depart_warp);
19933 	Ignore_List.set(Ship::Ship_Flags::Dying);
19934 	Ignore_List.set(Ship::Ship_Flags::Arriving_stage_1);
19935 	Ignore_List.set(Ship::Ship_Flags::Arriving_stage_1_dock_follower);
19936 	Ignore_List.set(Ship::Ship_Flags::Hidden_from_sensors);
19937 }
19938 
toggle_ignore_list_flag(Ship::Ship_Flags flag)19939 void toggle_ignore_list_flag(Ship::Ship_Flags flag) {
19940 	if (Ignore_List[flag])
19941 		Ignore_List.remove(flag);
19942 	else
19943 		Ignore_List.set(flag);
19944 }
19945 
ship_get_subsys_for_submodel(ship * shipp,int submodel)19946 ship_subsys* ship_get_subsys_for_submodel(ship* shipp, int submodel)
19947 {
19948 	ship_subsys* subsys;
19949 
19950 	if (submodel == -1) {
19951 		return nullptr;
19952 	}
19953 
19954 	for (subsys = GET_FIRST(&shipp->subsys_list); subsys != END_OF_LIST(&shipp->subsys_list);
19955 	     subsys = GET_NEXT(subsys)) {
19956 		if (subsys->system_info->subobj_num == submodel) {
19957 			return subsys;
19958 		}
19959 	}
19960 
19961 	return nullptr;
19962 }
19963 
19964 /**
19965  * Is the requested type of ship arriving in the requested stage?
19966  *
19967  * Dock leaders (plus single ships) & dock followers need to be handled
19968  * separately such that leaders+singles handle the warp process, but
19969  * followers need to behave as though they are warping with respect to
19970  * HUD targeting, drawing thrusters, radar, AI targeting, etc
19971  *
19972  * @param stage Check one of stage1, stage2 or both
19973  * @param dock_leader_or_single check dock-leaders+singles, or dock-followers?
19974  *
19975  * @return is the ship arriving, bool
19976  */
is_arriving(ship::warpstage stage,bool dock_leader_or_single)19977 bool ship::is_arriving(ship::warpstage stage, bool dock_leader_or_single)
19978 {
19979 	if (stage == ship::warpstage::BOTH) {
19980 		if (!dock_leader_or_single) {
19981 			return flags[Ship::Ship_Flags::Arriving_stage_1, Ship::Ship_Flags::Arriving_stage_1_dock_follower, Ship::Ship_Flags::Arriving_stage_2, Ship::Ship_Flags::Arriving_stage_2_dock_follower];
19982 		}
19983 		else {
19984 			return flags[Ship::Ship_Flags::Arriving_stage_1, Ship::Ship_Flags::Arriving_stage_2];
19985 		}
19986 	}
19987 	else if (stage == ship::warpstage::STAGE1) {
19988 		if (!dock_leader_or_single) {
19989 			return flags[Ship::Ship_Flags::Arriving_stage_1, Ship::Ship_Flags::Arriving_stage_1_dock_follower];
19990 		}
19991 		else {
19992 			return flags[Ship::Ship_Flags::Arriving_stage_1];
19993 		}
19994 	}
19995 	if (stage == ship::warpstage::STAGE2) {
19996 		if (!dock_leader_or_single) {
19997 			return flags[Ship::Ship_Flags::Arriving_stage_2, Ship::Ship_Flags::Arriving_stage_2_dock_follower];
19998 		}
19999 		else {
20000 			return flags[Ship::Ship_Flags::Arriving_stage_2];
20001 		}
20002 	}
20003 
20004 	// should never reach here
20005 	Assertion(false, "ship::is_arriving didn't handle all possible states; get a coder!");
20006 	return false;
20007 }
20008 
ship_clear_lock(lock_info * slot)20009 void ship_clear_lock(lock_info *slot) {
20010 	vec3d zero_vec = ZERO_VECTOR;
20011 
20012 	slot->accumulated_x_pixels = 0;
20013 	slot->accumulated_y_pixels = 0;
20014 
20015 	slot->catch_up_distance = 0.0f;
20016 
20017 	slot->catching_up = 0;
20018 
20019 	slot->current_target_sx = -1;
20020 	slot->current_target_sy = -1;
20021 
20022 	slot->dist_to_lock = 0.0f;
20023 
20024 	slot->indicator_start_x = -1;
20025 	slot->indicator_start_y = -1;
20026 
20027 	slot->indicator_visible = false;
20028 
20029 	slot->indicator_x = -1;
20030 	slot->indicator_y = -1;
20031 
20032 	slot->last_dist_to_target = 0.0f;
20033 
20034 	slot->lock_anim_time_elapsed = 0.0f;
20035 	slot->lock_gauge_time_elapsed = 0.0f;
20036 
20037 	slot->maintain_lock_count = 0;
20038 
20039 	slot->need_new_start_pos = false;
20040 
20041 	slot->target_in_lock_cone = false;
20042 
20043 	slot->world_pos = zero_vec;
20044 
20045 	slot->locked = false;
20046 
20047 	slot->obj = nullptr;
20048 	slot->subsys = nullptr;
20049 
20050 
20051 	slot->time_to_lock = -1.0f;
20052 }
20053 
ship_queue_missile_locks(ship * shipp)20054 void ship_queue_missile_locks(ship *shipp)
20055 {
20056 	shipp->missile_locks_firing.clear();
20057 
20058 	// queue up valid missile locks
20059 	std::copy_if(shipp->missile_locks.begin(), shipp->missile_locks.end(), std::back_inserter(shipp->missile_locks_firing), [](lock_info lock) { return lock.locked; });
20060 }
20061 
ship_lock_present(ship * shipp)20062 bool ship_lock_present(ship *shipp)
20063 {
20064 	return std::any_of(shipp->missile_locks.begin(), shipp->missile_locks.end(), [](lock_info lock) { return lock.locked; });
20065 }
20066 
ship_start_secondary_fire(object * objp)20067 bool ship_start_secondary_fire(object* objp)
20068 {
20069 	Assert( objp != nullptr);
20070 
20071 	if ( objp->type != OBJ_SHIP ) {
20072 		return false;
20073 	}
20074 
20075 	int n = objp->instance;
20076 
20077 	Assert( n >= 0 && n < MAX_SHIPS );
20078 	Assert( Ships[n].objnum == OBJ_INDEX(objp) );
20079 
20080 	ship *shipp = &Ships[n];
20081 	ship_info *sip = &Ship_info[shipp->ship_info_index];
20082 	ship_weapon* swp = &shipp->weapons;
20083 
20084 	int bank = swp->current_secondary_bank;
20085 
20086 	if ( bank < 0 || bank >= sip->num_secondary_banks ) {
20087 		return false;
20088 	}
20089 
20090 	// It's possible for banks to be empty without issue
20091 	// but indices outside the weapon_info range are a problem
20092 	Assert( swp->secondary_bank_weapons[bank] < weapon_info_size() );
20093 	if ( (swp->secondary_bank_weapons[bank] < 0) || (swp->secondary_bank_weapons[bank] >= weapon_info_size()) ) {
20094 		return false;
20095 	}
20096 
20097 	weapon_info *wip = &Weapon_info[swp->secondary_bank_weapons[bank]];
20098 
20099 	if ( wip->trigger_lock ) {
20100 		swp->flags.set(Ship::Weapon_Flags::Trigger_Lock);
20101 
20102 		return true;
20103 	}
20104 
20105 	return false;
20106 }
20107 
ship_stop_secondary_fire(object * objp)20108 bool ship_stop_secondary_fire(object* objp)
20109 {
20110 	Assert( objp != nullptr);
20111 
20112 	if ( objp->type != OBJ_SHIP ) {
20113 		return false;
20114 	}
20115 
20116 	int n = objp->instance;
20117 
20118 	if ( n < 0 || n >= MAX_SHIPS ) {
20119 		return false;
20120 	}
20121 	Assert( Ships[n].objnum == OBJ_INDEX(objp) );
20122 
20123 	ship *shipp = &Ships[n];
20124 	ship_info *sip = &Ship_info[shipp->ship_info_index];
20125 	ship_weapon *swp = &shipp->weapons;
20126 
20127 	int bank = swp->current_secondary_bank;
20128 
20129 	if ( bank < 0 || bank >= sip->num_secondary_banks ) {
20130 		return false;
20131 	}
20132 
20133 	// It's possible for banks to be empty without issue
20134 	// but indices outside the weapon_info range are a problem
20135 	Assert(swp->secondary_bank_weapons[bank] < weapon_info_size());
20136 	if ((swp->secondary_bank_weapons[bank] < 0) || (swp->secondary_bank_weapons[bank] >= weapon_info_size())) {
20137 		return false;
20138 	}
20139 
20140 	weapon_info *wip = &Weapon_info[swp->secondary_bank_weapons[bank]];
20141 
20142 	if ( wip->trigger_lock && swp->flags[Ship::Weapon_Flags::Trigger_Lock]) {
20143 		swp->flags.remove(Ship::Weapon_Flags::Trigger_Lock);
20144 
20145 		return true;
20146 	}
20147 
20148 	return false;
20149 }
20150