1 /*
2 * Copyright (C) Volition, Inc. 1999. All rights reserved.
3 *
4 * All source code herein is the property of Volition, Inc. You may not sell
5 * or otherwise commercially exploit the source or things you created based on the
6 * source.
7 *
8 */
9
10
11
12 #include <algorithm>
13
14 #include "ai/aibig.h"
15 #include "asteroid/asteroid.h"
16 #include "cmdline/cmdline.h"
17 #include "cmeasure/cmeasure.h"
18 #include "debugconsole/console.h"
19 #include "fireball/fireballs.h"
20 #include "freespace.h"
21 #include "gamesnd/gamesnd.h"
22 #include "globalincs/linklist.h"
23 #include "hud/hud.h"
24 #include "hud/hudartillery.h"
25 #include "iff_defs/iff_defs.h"
26 #include "io/joy_ff.h"
27 #include "io/timer.h"
28 #include "math/staticrand.h"
29 #include "missionui/missionweaponchoice.h"
30 #include "mod_table/mod_table.h"
31 #include "nebula/neb.h"
32 #include "network/multi.h"
33 #include "network/multimsgs.h"
34 #include "network/multiutil.h"
35 #include "object/objcollide.h"
36 #include "object/objectdock.h"
37 #include "object/objectsnd.h"
38 #include "scripting/scripting.h"
39 #include "particle/particle.h"
40 #include "playerman/player.h"
41 #include "radar/radar.h"
42 #include "render/3d.h"
43 #include "render/batching.h"
44 #include "ship/ship.h"
45 #include "ship/shiphit.h"
46 #include "weapon/beam.h" // for BEAM_TYPE_? definitions
47 #include "weapon/corkscrew.h"
48 #include "weapon/emp.h"
49 #include "weapon/flak.h"
50 #include "weapon/muzzleflash.h"
51 #include "weapon/swarm.h"
52 #include "particle/effects/SingleParticleEffect.h"
53 #include "particle/effects/BeamPiercingEffect.h"
54 #include "particle/effects/ParticleEmitterEffect.h"
55 #include "tracing/Monitor.h"
56 #include "tracing/tracing.h"
57 #include "weapon.h"
58
59
60 // Since SSMs are parsed after weapons, if we want to allow SSM strikes to be specified by name, we need to store those names until after SSMs are parsed.
61 typedef struct delayed_ssm_data {
62 SCP_string filename;
63 int linenum;
64 SCP_string ssm_entry;
65 } delayed_ssm_data;
66 SCP_map<SCP_string, delayed_ssm_data> Delayed_SSM_data;
67 SCP_vector<SCP_string> Delayed_SSM_names;
68
69 typedef struct delayed_ssm_index_data {
70 SCP_string filename;
71 int linenum;
72 } delayed_ssm_index_data;
73 SCP_map<SCP_string, delayed_ssm_index_data> Delayed_SSM_indices_data;
74 SCP_vector<SCP_string> Delayed_SSM_indices;
75
76
77 #ifndef NDEBUG
78 int Weapon_flyby_sound_enabled = 1;
79 DCF_BOOL( weapon_flyby, Weapon_flyby_sound_enabled )
80 #endif
81
82 static int Weapon_flyby_sound_timer;
83
84 weapon Weapons[MAX_WEAPONS];
85 SCP_vector<weapon_info> Weapon_info;
86
87 #define MISSILE_OBJ_USED (1<<0) // flag used in missile_obj struct
88 #define MAX_MISSILE_OBJS MAX_WEAPONS // max number of missiles tracked in missile list
89 missile_obj Missile_objs[MAX_MISSILE_OBJS]; // array used to store missile object indexes
90 missile_obj Missile_obj_list; // head of linked list of missile_obj structs
91
92 //WEAPON SUBTYPE STUFF
93 const char *Weapon_subtype_names[] = {
94 "Laser",
95 "Missile",
96 "Beam"
97 };
98 int Num_weapon_subtypes = sizeof(Weapon_subtype_names)/sizeof(Weapon_subtype_names[0]);
99
100 flag_def_list_new<Weapon::Burst_Flags> Burst_fire_flags[] = {
101 { "fast firing", Weapon::Burst_Flags::Fast_firing, true, false },
102 { "random length", Weapon::Burst_Flags::Random_length, true, false },
103 { "resets", Weapon::Burst_Flags::Resets, true, false },
104 { "num firepoints for burst shots", Weapon::Burst_Flags::Num_firepoints_burst_shots, true, false }
105 };
106
107 const size_t Num_burst_fire_flags = sizeof(Burst_fire_flags)/sizeof(flag_def_list_new<Weapon::Burst_Flags>);
108
109 flag_def_list_new<Weapon::Beam_Info_Flags> Beam_info_flags[] = {
110 { "burst shares random target", Weapon::Beam_Info_Flags::Burst_share_random, true, false },
111 { "track own texture tiling", Weapon::Beam_Info_Flags::Track_own_texture_tiling, true, false }
112 };
113
114 const size_t Num_beam_info_flags = sizeof(Beam_info_flags) / sizeof(flag_def_list_new<Weapon::Beam_Info_Flags>);
115
116 weapon_explosions Weapon_explosions;
117
118 SCP_vector<lod_checker> LOD_checker;
119
120 flag_def_list_new<Weapon::Info_Flags> Weapon_Info_Flags[] = {
121 { "spawn", Weapon::Info_Flags::Spawn, true, true }, //special case
122 { "remote detonate", Weapon::Info_Flags::Remote, true, false },
123 { "puncture", Weapon::Info_Flags::Puncture, true, false },
124 { "big ship", Weapon::Info_Flags::Big_only, true, false },
125 { "huge", Weapon::Info_Flags::Huge, true, false },
126 { "bomber+", Weapon::Info_Flags::Bomber_plus, true, false },
127 { "child", Weapon::Info_Flags::Child, true, false },
128 { "bomb", Weapon::Info_Flags::Bomb, true, false },
129 { "no dumbfire", Weapon::Info_Flags::No_dumbfire, true, false },
130 { "no doublefire", Weapon::Info_Flags::No_doublefire, true, false },
131 { "in tech database", Weapon::Info_Flags::In_tech_database, true, false },
132 { "player allowed", Weapon::Info_Flags::Player_allowed, true, false },
133 { "particle spew", Weapon::Info_Flags::Particle_spew, true, false },
134 { "emp", Weapon::Info_Flags::Emp, true, false },
135 { "esuck", Weapon::Info_Flags::Energy_suck, true, false },
136 { "flak", Weapon::Info_Flags::Flak, true, false },
137 { "corkscrew", Weapon::Info_Flags::Corkscrew, true, false },
138 { "shudder", Weapon::Info_Flags::Shudder, true, false },
139 { "electronics", Weapon::Info_Flags::Electronics, true, false },
140 { "lockarm", Weapon::Info_Flags::Lockarm, true, false },
141 { "beam", Weapon::Info_Flags::Beam, true, true }, //special case
142 { "stream", Weapon::Info_Flags::Stream, true, false },
143 { "supercap", Weapon::Info_Flags::Supercap, true, false },
144 { "countermeasure", Weapon::Info_Flags::Cmeasure, true, false },
145 { "ballistic", Weapon::Info_Flags::Ballistic, true, false },
146 { "pierce shields", Weapon::Info_Flags::Pierce_shields, true, false },
147 { "local ssm", Weapon::Info_Flags::Local_ssm, true, false },
148 { "tagged only", Weapon::Info_Flags::Tagged_only, true, false },
149 { "beam no whack", Weapon::Info_Flags::NUM_VALUES, false, true }, //special case
150 { "cycle", Weapon::Info_Flags::Cycle, true, false },
151 { "small only", Weapon::Info_Flags::Small_only, true, false },
152 { "same turret cooldown", Weapon::Info_Flags::Same_turret_cooldown, true, false },
153 { "apply no light", Weapon::Info_Flags::Mr_no_lighting, true, false },
154 { "training", Weapon::Info_Flags::Training, true, false },
155 { "smart spawn", Weapon::Info_Flags::Smart_spawn, true, false },
156 { "inherit parent target", Weapon::Info_Flags::Inherit_parent_target, true, false },
157 { "no emp kill", Weapon::Info_Flags::No_emp_kill, true, false },
158 { "untargeted heat seeker", Weapon::Info_Flags::Untargeted_heat_seeker, true, false },
159 { "no radius doubling", Weapon::Info_Flags::No_radius_doubling, true, false },
160 { "no subsystem homing", Weapon::Info_Flags::Non_subsys_homing, true, false },
161 { "no lifeleft penalty", Weapon::Info_Flags::No_life_lost_if_missed, true, false },
162 { "can be targeted", Weapon::Info_Flags::Can_be_targeted, true, false },
163 { "show on radar", Weapon::Info_Flags::Shown_on_radar, true, false },
164 { "show friendly on radar", Weapon::Info_Flags::Show_friendly, true, false },
165 { "capital+", Weapon::Info_Flags::Capital_plus, true, false },
166 { "chain external model fps", Weapon::Info_Flags::External_weapon_fp, true, false },
167 { "external model launcher", Weapon::Info_Flags::External_weapon_lnch, true, false },
168 { "takes blast damage", Weapon::Info_Flags::Takes_blast_damage, true, false },
169 { "takes shockwave damage", Weapon::Info_Flags::Takes_shockwave_damage, true, false },
170 { "hide from radar", Weapon::Info_Flags::Dont_show_on_radar, true, false },
171 { "render flak", Weapon::Info_Flags::Render_flak, true, false },
172 { "ciws", Weapon::Info_Flags::Ciws, true, false },
173 { "anti-subsystem beam", Weapon::Info_Flags::Antisubsysbeam, true, false },
174 { "no primary linking", Weapon::Info_Flags::Nolink, true, false },
175 { "same emp time for capships", Weapon::Info_Flags::Use_emp_time_for_capship_turrets, true, false },
176 { "no primary linked penalty", Weapon::Info_Flags::No_linked_penalty, true, false },
177 { "no homing speed ramp", Weapon::Info_Flags::No_homing_speed_ramp, true, false },
178 { "pulls aspect seekers", Weapon::Info_Flags::Cmeasure_aspect_home_on, true, false },
179 { "turret interceptable", Weapon::Info_Flags::Turret_Interceptable, true, false },
180 { "fighter interceptable", Weapon::Info_Flags::Fighter_Interceptable, true, false },
181 { "aoe electronics", Weapon::Info_Flags::Aoe_Electronics, true, false },
182 { "apply recoil", Weapon::Info_Flags::Apply_Recoil, true, false },
183 { "don't spawn if shot", Weapon::Info_Flags::Dont_spawn_if_shot, true, false },
184 { "die on lost lock", Weapon::Info_Flags::Die_on_lost_lock, true, true }, //special case
185 { "no impact spew", Weapon::Info_Flags::No_impact_spew, true, false },
186 { "require exact los", Weapon::Info_Flags::Require_exact_los, true, false },
187 { "can damage shooter", Weapon::Info_Flags::Can_damage_shooter, true, false },
188 { "heals", Weapon::Info_Flags::Heals, true, false },
189 { "no collide", Weapon::Info_Flags::No_collide, true, false },
190 { "multilock target dead subsys", Weapon::Info_Flags::Multilock_target_dead_subsys, true, false },
191 };
192
193 const size_t num_weapon_info_flags = sizeof(Weapon_Info_Flags) / sizeof(flag_def_list_new<Weapon::Info_Flags>);
194
195 int Num_weapons = 0;
196 bool Weapons_inited = false;
197
198 int laser_model_inner = -1;
199 int laser_model_outer = -1;
200
201 int missile_model = -1;
202
203 int First_secondary_index = -1;
204 int Default_cmeasure_index = -1;
205
206 static int *used_weapons = NULL;
207
208 int Num_spawn_types = 0;
209 char **Spawn_names = NULL;
210
211 int Num_player_weapon_precedence; // Number of weapon types in Player_weapon_precedence
212 int Player_weapon_precedence[MAX_WEAPON_TYPES]; // Array of weapon types, precedence list for player weapon selection
213
214 // Used to avoid playing too many impact sounds in too short a time interval.
215 // This will elimate the odd "stereo" effect that occurs when two weapons impact at
216 // nearly the same time, like from a double laser (also saves sound channels!)
217 #define IMPACT_SOUND_DELTA 50 // in milliseconds
218 int Weapon_impact_timer; // timer, initialized at start of each mission
219
220 // energy suck defines
221 #define ESUCK_DEFAULT_WEAPON_REDUCE (10.0f)
222 #define ESUCK_DEFAULT_AFTERBURNER_REDUCE (10.0f)
223
224 // scale factor for big ships getting hit by flak
225 #define FLAK_DAMAGE_SCALE 0.05f
226
227 //default time of a homing missile to not home
228 #define HOMING_DEFAULT_FREE_FLIGHT_TIME 0.5f
229
230 // default percentage of max speed to coast at during freeflight
231 const float HOMING_DEFAULT_FREE_FLIGHT_FACTOR = 0.25f;
232
233 //default time of a homing primary to not home
234 #define HOMING_DEFAULT_PRIMARY_FREE_FLIGHT_TIME 0.0f
235
236 // time delay between each swarm missile that is fired
237 #define SWARM_MISSILE_DELAY 150
238
239 // homing missiles have an extended lifetime so they don't appear to run out of gas before they can hit a moving target at extreme
240 // range. Check the comment in weapon_set_tracking_info() for more details
241 #define LOCKED_HOMING_EXTENDED_LIFE_FACTOR 1.2f
242
243 // default number of missiles or bullets rearmed per load sound during rearm
244 #define REARM_NUM_MISSILES_PER_BATCH 4
245 #define REARM_NUM_BALLISTIC_PRIMARIES_PER_BATCH 100
246
247 // most frequently a continuous spawn weapon is allowed to spawn
248 static const float MINIMUM_SPAWN_INTERVAL = 0.1f;
249
250 extern int compute_num_homing_objects(object *target_objp);
251
252 void weapon_spew_stats(WeaponSpewType type);
253
254
weapon_explosions()255 weapon_explosions::weapon_explosions()
256 {
257 ExplosionInfo.clear();
258 }
259
GetIndex(char * filename)260 int weapon_explosions::GetIndex(char *filename)
261 {
262 if ( filename == NULL ) {
263 Int3();
264 return -1;
265 }
266
267 for (size_t i = 0; i < ExplosionInfo.size(); i++) {
268 if ( !stricmp(ExplosionInfo[i].lod[0].filename, filename)) {
269 return (int)i;
270 }
271 }
272
273 return -1;
274 }
275
Load(char * filename,int expected_lods)276 int weapon_explosions::Load(char *filename, int expected_lods)
277 {
278 char name_tmp[MAX_FILENAME_LEN] = "";
279 int bitmap_id = -1;
280 int nframes, nfps;
281 weapon_expl_info new_wei;
282
283 Assert( expected_lods <= MAX_WEAPON_EXPL_LOD );
284
285 //Check if it exists
286 int idx = GetIndex(filename);
287
288 if (idx != -1)
289 return idx;
290
291 new_wei.lod_count = 1;
292
293 strcpy_s(new_wei.lod[0].filename, filename);
294 new_wei.lod[0].bitmap_id = bm_load_animation(filename, &new_wei.lod[0].num_frames, &new_wei.lod[0].fps, nullptr, nullptr, true);
295
296 if (new_wei.lod[0].bitmap_id < 0) {
297 Warning(LOCATION, "Weapon explosion '%s' does not have an LOD0 anim!", filename);
298
299 // if we don't have the first then it's only safe to assume that the rest are missing or not usable
300 return -1;
301 }
302
303 // 2 chars for the lod, 4 for the extension that gets added automatically
304 if ( (MAX_FILENAME_LEN - strlen(filename)) > 6 ) {
305 for (idx = 1; idx < expected_lods; idx++) {
306 sprintf(name_tmp, "%s_%d", filename, idx);
307
308 bitmap_id = bm_load_animation(name_tmp, &nframes, &nfps, nullptr, nullptr, true);
309
310 if (bitmap_id > 0) {
311 strcpy_s(new_wei.lod[idx].filename, name_tmp);
312 new_wei.lod[idx].bitmap_id = bitmap_id;
313 new_wei.lod[idx].num_frames = nframes;
314 new_wei.lod[idx].fps = nfps;
315
316 new_wei.lod_count++;
317 } else {
318 break;
319 }
320 }
321
322 if (new_wei.lod_count != expected_lods)
323 Warning(LOCATION, "For '%s', %i of %i LODs are missing!", filename, expected_lods - new_wei.lod_count, expected_lods);
324 }
325 else {
326 Warning(LOCATION, "Filename '%s' is too long to have any LODs.", filename);
327 }
328
329 ExplosionInfo.push_back( new_wei );
330
331 return (int)(ExplosionInfo.size() - 1);
332 }
333
PageIn(int idx)334 void weapon_explosions::PageIn(int idx)
335 {
336 int i;
337
338 if ( (idx < 0) || (idx >= (int)ExplosionInfo.size()) )
339 return;
340
341 weapon_expl_info *wei = &ExplosionInfo[idx];
342
343 for ( i = 0; i < wei->lod_count; i++ ) {
344 if ( wei->lod[i].bitmap_id >= 0 ) {
345 bm_page_in_xparent_texture( wei->lod[i].bitmap_id, wei->lod[i].num_frames );
346 }
347 }
348 }
349
GetAnim(int weapon_expl_index,vec3d * pos,float size)350 int weapon_explosions::GetAnim(int weapon_expl_index, vec3d *pos, float size)
351 {
352 if ( (weapon_expl_index < 0) || (weapon_expl_index >= (int)ExplosionInfo.size()) )
353 return -1;
354
355 //Get our weapon expl for the day
356 weapon_expl_info *wei = &ExplosionInfo[weapon_expl_index];
357
358 if (wei->lod_count == 1)
359 return wei->lod[0].bitmap_id;
360
361 // now we have to do some work
362 vertex v;
363 int x, y, w, h, bm_size;
364 int must_stop = 0;
365 int best_lod = 1;
366 int behind = 0;
367
368 // start the frame
369 extern int G3_count;
370
371 if(!G3_count){
372 g3_start_frame(1);
373 must_stop = 1;
374 }
375 g3_set_view_matrix(&Eye_position, &Eye_matrix, Eye_fov);
376
377 // get extents of the rotated bitmap
378 g3_rotate_vertex(&v, pos);
379
380 // if vertex is behind, find size if in front, then drop down 1 LOD
381 if (v.codes & CC_BEHIND) {
382 float dist = vm_vec_dist_quick(&Eye_position, pos);
383 vec3d temp;
384
385 behind = 1;
386 vm_vec_scale_add(&temp, &Eye_position, &Eye_matrix.vec.fvec, dist);
387 g3_rotate_vertex(&v, &temp);
388
389 // if still behind, bail and go with default
390 if (v.codes & CC_BEHIND) {
391 behind = 0;
392 }
393 }
394
395 if (!g3_get_bitmap_dims(wei->lod[0].bitmap_id, &v, size, &x, &y, &w, &h, &bm_size)) {
396 if (Detail.hardware_textures == 4) {
397 // straight LOD
398 if(w <= bm_size/8){
399 best_lod = 3;
400 } else if(w <= bm_size/2){
401 best_lod = 2;
402 } else if(w <= 1.3f*bm_size){
403 best_lod = 1;
404 } else {
405 best_lod = 0;
406 }
407 } else {
408 // less aggressive LOD for lower detail settings
409 if(w <= bm_size/8){
410 best_lod = 3;
411 } else if(w <= bm_size/3){
412 best_lod = 2;
413 } else if(w <= (1.15f*bm_size)){
414 best_lod = 1;
415 } else {
416 best_lod = 0;
417 }
418 }
419 }
420
421 // if it's behind, bump up LOD by 1
422 if (behind)
423 best_lod++;
424
425 // end the frame
426 if (must_stop)
427 g3_end_frame();
428
429 best_lod = MIN(best_lod, wei->lod_count - 1);
430 Assert( (best_lod >= 0) && (best_lod < MAX_WEAPON_EXPL_LOD) );
431
432 return wei->lod[best_lod].bitmap_id;
433 }
434
435
parse_weapon_expl_tbl(const char * filename)436 void parse_weapon_expl_tbl(const char *filename)
437 {
438 uint i;
439 lod_checker lod_check;
440
441 try
442 {
443 read_file_text(filename, CF_TYPE_TABLES);
444 reset_parse();
445
446 required_string("#Start");
447 while (required_string_either("#End", "$Name:"))
448 {
449 memset(&lod_check, 0, sizeof(lod_checker));
450
451 // base filename
452 required_string("$Name:");
453 stuff_string(lod_check.filename, F_NAME, MAX_FILENAME_LEN);
454
455 //Do we have an LOD num
456 if (optional_string("$LOD:"))
457 {
458 stuff_int(&lod_check.num_lods);
459 }
460
461 // only bother with this if we have 1 or more lods and less than max lods,
462 // otherwise the stardard level loading will take care of the different effects
463 if ((lod_check.num_lods > 0) && (lod_check.num_lods < MAX_WEAPON_EXPL_LOD)) {
464 // name check, update lod count if it already exists
465 for (i = 0; i < LOD_checker.size(); i++) {
466 if (!stricmp(LOD_checker[i].filename, lod_check.filename)) {
467 LOD_checker[i].num_lods = lod_check.num_lods;
468 }
469 }
470
471 // old entry not found, add new entry
472 if (i == LOD_checker.size()) {
473 LOD_checker.push_back(lod_check);
474 }
475 }
476 }
477 required_string("#End");
478 }
479 catch (const parse::ParseException& e)
480 {
481 mprintf(("TABLES: Unable to parse '%s'! Error message = %s.\n", filename, e.what()));
482 return;
483 }
484 }
485
486 /**
487 * Clear out the Missile_obj_list
488 */
missile_obj_list_init()489 void missile_obj_list_init()
490 {
491 int i;
492
493 list_init(&Missile_obj_list);
494 for ( i = 0; i < MAX_MISSILE_OBJS; i++ ) {
495 Missile_objs[i].flags = 0;
496 }
497 }
498
499 /**
500 * Add a node from the Missile_obj_list.
501 * @note Only called from weapon_create()
502 */
missile_obj_list_add(int objnum)503 int missile_obj_list_add(int objnum)
504 {
505 int i;
506
507 for ( i = 0; i < MAX_MISSILE_OBJS; i++ ) {
508 if ( !(Missile_objs[i].flags & MISSILE_OBJ_USED) )
509 break;
510 }
511 if ( i == MAX_MISSILE_OBJS ) {
512 Error(LOCATION, "Fatal Error: Ran out of missile object nodes\n");
513 return -1;
514 }
515
516 Missile_objs[i].flags = 0;
517 Missile_objs[i].objnum = objnum;
518 list_append(&Missile_obj_list, &Missile_objs[i]);
519 Missile_objs[i].flags |= MISSILE_OBJ_USED;
520
521 return i;
522 }
523
524 /**
525 * Remove a node from the Missile_obj_list.
526 * @note Only called from weapon_delete()
527 */
missle_obj_list_remove(int index)528 void missle_obj_list_remove(int index)
529 {
530 Assert(index >= 0 && index < MAX_MISSILE_OBJS);
531 list_remove(&Missile_obj_list, &Missile_objs[index]);
532 Missile_objs[index].flags = 0;
533 }
534
535 /**
536 * Called externally to generate an address from an index into
537 * the Missile_objs[] array
538 */
missile_obj_return_address(int index)539 missile_obj *missile_obj_return_address(int index)
540 {
541 Assert(index >= 0 && index < MAX_MISSILE_OBJS);
542 return &Missile_objs[index];
543 }
544
545 /**
546 * Return the index of Weapon_info[].name that is *name.
547 */
weapon_info_lookup(const char * name)548 int weapon_info_lookup(const char *name)
549 {
550 Assertion(name != nullptr, "NULL name passed to weapon_info_lookup");
551
552 for (auto it = Weapon_info.cbegin(); it != Weapon_info.cend(); ++it)
553 if (!stricmp(name, it->name))
554 return (int)std::distance(Weapon_info.cbegin(), it);
555
556 return -1;
557 }
558
559 /**
560 * Return the index of Weapon_info used by this pointer. Equivalent to the old WEAPON_INFO_INDEX macro:
561 * #define WEAPON_INFO_INDEX(wip) (int)(wip-Weapon_info)
562 */
weapon_info_get_index(weapon_info * wip)563 int weapon_info_get_index(weapon_info *wip)
564 {
565 Assertion(wip != nullptr, "NULL wip passed to weapon_info_get_index");
566 return static_cast<int>(std::distance(Weapon_info.data(), wip));
567 }
568
569 #define DEFAULT_WEAPON_SPAWN_COUNT 10
570
571 // Parse the weapon flags.
parse_wi_flags(weapon_info * weaponp,flagset<Weapon::Info_Flags> preset_wi_flags)572 void parse_wi_flags(weapon_info *weaponp, flagset<Weapon::Info_Flags> preset_wi_flags)
573 {
574 const char *spawn_str = NOX("Spawn");
575 const size_t spawn_str_len = strlen(spawn_str);
576
577 //Make sure we HAVE flags :p
578 if (!optional_string("$Flags:"))
579 return;
580
581 // To make sure +override doesn't overwrite previously parsed values we parse the flags into a separate flagset
582 SCP_vector<SCP_string> unparsed_or_special;
583 flagset<Weapon::Info_Flags> parsed_flags;
584 parse_string_flag_list(parsed_flags, Weapon_Info_Flags, num_weapon_info_flags, &unparsed_or_special);
585
586 if (optional_string("+override")) {
587 // resetting the flag values if set to override the existing flags
588 weaponp->wi_flags = preset_wi_flags;
589 }
590 // Now add the parsed flags to the weapon flags
591 weaponp->wi_flags |= parsed_flags;
592
593 bool set_nopierce = false;
594
595 for (auto flag = unparsed_or_special.begin(); flag != unparsed_or_special.end(); ++flag) {
596 SCP_string flag_text = *flag;
597 //deal with spawn flag
598 if (!strnicmp(spawn_str, flag_text.c_str(), 5))
599 {
600 if (weaponp->num_spawn_weapons_defined < MAX_SPAWN_TYPES_PER_WEAPON)
601 {
602 //We need more spawning slots
603 //allocate in slots of 10
604 if ((Num_spawn_types % 10) == 0) {
605 Spawn_names = (char **)vm_realloc(Spawn_names, (Num_spawn_types + 10) * sizeof(*Spawn_names));
606 }
607
608 size_t skip_length, name_length;
609 std::unique_ptr<char[]> temp_string(new char[flag_text.size() + 1]);
610
611 strcpy(temp_string.get(), flag_text.c_str());
612
613 weaponp->wi_flags.set(Weapon::Info_Flags::Spawn);
614 weaponp->spawn_info[weaponp->num_spawn_weapons_defined].spawn_type = (short)Num_spawn_types;
615 skip_length = spawn_str_len + strspn(&temp_string[spawn_str_len], NOX(" \t"));
616 char *num_start = strchr(&temp_string[skip_length], ',');
617 if (num_start == NULL) {
618 weaponp->spawn_info[weaponp->num_spawn_weapons_defined].spawn_count = DEFAULT_WEAPON_SPAWN_COUNT;
619 name_length = 999;
620 }
621 else {
622 weaponp->spawn_info[weaponp->num_spawn_weapons_defined].spawn_count = (short)atoi(num_start + 1);
623 name_length = num_start - temp_string.get() - skip_length;
624 }
625
626 weaponp->maximum_children_spawned += weaponp->spawn_info[weaponp->num_spawn_weapons_defined].spawn_count;
627
628 Spawn_names[Num_spawn_types] = vm_strndup(&flag_text[skip_length], name_length);
629 Num_spawn_types++;
630 weaponp->num_spawn_weapons_defined++;
631 }
632 else {
633 Warning(LOCATION, "Illegal to have more than %d spawn types for one weapon.\nIgnoring weapon %s", MAX_SPAWN_TYPES_PER_WEAPON, weaponp->name);
634 }
635 }
636 else if (!stricmp(NOX("beam"), flag_text.c_str())) {
637 weaponp->wi_flags.set(Weapon::Info_Flags::Pierce_shields);
638 }
639 else if (!stricmp(NOX("no pierce shields"), flag_text.c_str())) {
640 set_nopierce = true;
641 }
642 else if (!stricmp(NOX("beam no whack"), flag_text.c_str())) {
643 Warning(LOCATION, "The \"beam no whack\" flag has been deprecated. Set the beam's mass to 0 instead. This has been done for you.\n");
644 weaponp->mass = 0.0f;
645 }
646 else if (!stricmp(NOX("interceptable"), flag_text.c_str())) {
647 weaponp->wi_flags.set(Weapon::Info_Flags::Turret_Interceptable);
648 weaponp->wi_flags.set(Weapon::Info_Flags::Fighter_Interceptable);
649 }
650 else if (!stricmp(NOX("die on lost lock"), flag_text.c_str())) {
651 if (!(weaponp->is_locked_homing())) {
652 Warning(LOCATION, "\"die on lost lock\" may only be used for Homing Type ASPECT/JAVELIN!");
653 weaponp->wi_flags.remove(Weapon::Info_Flags::Die_on_lost_lock);
654 }
655 }
656 else {
657 Warning(LOCATION, "Unrecognized flag in flag list for weapon %s: \"%s\"", weaponp->name, (*flag).c_str());
658 }
659 }
660
661 //Do cleanup and sanity checks
662
663 if (set_nopierce)
664 weaponp->wi_flags.remove(Weapon::Info_Flags::Pierce_shields);
665
666 if (weaponp->wi_flags[Weapon::Info_Flags::In_tech_database])
667 weaponp->wi_flags.set(Weapon::Info_Flags::Default_in_tech_database);
668
669 if (weaponp->wi_flags[Weapon::Info_Flags::Flak]) {
670 if (weaponp->wi_flags[Weapon::Info_Flags::Swarm] || weaponp->wi_flags[Weapon::Info_Flags::Corkscrew]) {
671 weaponp->wi_flags.remove(Weapon::Info_Flags::Swarm);
672 weaponp->wi_flags.remove(Weapon::Info_Flags::Corkscrew);
673 Warning(LOCATION, "Swarm, Corkscrew, and Flak are mutually exclusive! Removing Swarm and Corkscrew attributes from weapon %s.\n", weaponp->name);
674 }
675 }
676
677 if (weaponp->wi_flags[Weapon::Info_Flags::Swarm] && weaponp->wi_flags[Weapon::Info_Flags::Corkscrew]) {
678 weaponp->wi_flags.remove(Weapon::Info_Flags::Corkscrew);
679 Warning(LOCATION, "Swarm and Corkscrew are mutually exclusive! Defaulting to Swarm on weapon %s.\n", weaponp->name);
680 }
681
682 if (weaponp->wi_flags[Weapon::Info_Flags::Local_ssm]) {
683 if (!weaponp->is_homing() || weaponp->subtype != WP_MISSILE) {
684 Warning(LOCATION, "local ssm must be guided missile: %s", weaponp->name);
685 }
686 }
687
688 if (weaponp->wi_flags[Weapon::Info_Flags::Small_only] && weaponp->wi_flags[Weapon::Info_Flags::Huge])
689 {
690 Warning(LOCATION, "\"small only\" and \"huge\" flags are mutually exclusive.\nThey are used together in %s\nThe AI can not use this weapon on any targets", weaponp->name);
691 }
692
693 if (!weaponp->wi_flags[Weapon::Info_Flags::Spawn] && weaponp->wi_flags[Weapon::Info_Flags::Smart_spawn])
694 {
695 Warning(LOCATION, "\"smart spawn\" flag used without \"spawn\" flag in %s\n", weaponp->name);
696 }
697
698 if (!weaponp->wi_flags[Weapon::Info_Flags::Homing_heat] && weaponp->wi_flags[Weapon::Info_Flags::Untargeted_heat_seeker])
699 {
700 Warning(LOCATION, "Weapon '%s' has the \"untargeted heat seeker\" flag, but Homing Type is not set to \"HEAT\".", weaponp->name);
701 }
702
703 if (!weaponp->wi_flags[Weapon::Info_Flags::Cmeasure] && weaponp->wi_flags[Weapon::Info_Flags::Cmeasure_aspect_home_on])
704 {
705 weaponp->wi_flags.remove(Weapon::Info_Flags::Cmeasure_aspect_home_on);
706 Warning(LOCATION, "Weapon %s has the \"pulls aspect seekers\" flag, but is not a countermeasure.\n", weaponp->name);
707 }
708 }
709
parse_shockwave_info(shockwave_create_info * sci,const char * pre_char)710 void parse_shockwave_info(shockwave_create_info *sci, const char *pre_char)
711 {
712 char buf[NAME_LENGTH];
713
714 sprintf(buf, "%sShockwave damage:", pre_char);
715 if(optional_string(buf)) {
716 stuff_float(&sci->damage);
717 sci->damage_overidden = true;
718 }
719
720 sprintf(buf, "%sShockwave damage type:", pre_char);
721 if(optional_string(buf)) {
722 stuff_string(buf, F_NAME, NAME_LENGTH);
723 sci->damage_type_idx_sav = damage_type_add(buf);
724 sci->damage_type_idx = sci->damage_type_idx_sav;
725 }
726
727 sprintf(buf, "%sBlast Force:", pre_char);
728 if(optional_string(buf)) {
729 stuff_float(&sci->blast);
730 }
731
732 sprintf(buf, "%sInner Radius:", pre_char);
733 if(optional_string(buf)) {
734 stuff_float(&sci->inner_rad);
735 }
736
737 sprintf(buf, "%sOuter Radius:", pre_char);
738 if(optional_string(buf)) {
739 stuff_float(&sci->outer_rad);
740 }
741
742 if (sci->outer_rad < sci->inner_rad) {
743 Warning(LOCATION, "Shockwave outer radius must be greater than or equal to the inner radius!");
744 sci->outer_rad = sci->inner_rad;
745 }
746
747 sprintf(buf, "%sShockwave Speed:", pre_char);
748 if(optional_string(buf)) {
749 stuff_float(&sci->speed);
750 }
751
752 sprintf(buf, "%sShockwave Rotation:", pre_char);
753 if(optional_string(buf)) {
754 float angs[3];
755 stuff_float_list(angs, 3);
756 for(int i = 0; i < 3; i++)
757 {
758 angs[i] = fl_radians(angs[i]);
759 while(angs[i] < 0)
760 {
761 angs[i] += PI2;
762 }
763 while(angs[i] > PI2)
764 {
765 angs[i] -= PI2;
766 }
767 }
768 sci->rot_angles.p = angs[0];
769 sci->rot_angles.b = angs[1];
770 sci->rot_angles.h = angs[2];
771
772 sci->rot_defined = true;
773 }
774
775 sprintf(buf, "%sShockwave Model:", pre_char);
776 if(optional_string(buf)) {
777 stuff_string(sci->pof_name, F_NAME, MAX_FILENAME_LEN);
778 }
779
780 sprintf(buf, "%sShockwave Name:", pre_char);
781 if(optional_string(buf)) {
782 stuff_string(sci->name, F_NAME, MAX_FILENAME_LEN);
783 }
784 }
785
786 static SCP_vector<SCP_string> Removed_weapons;
787
788 /**
789 * Parse the information for a specific ship type.
790 * Return weapon index if successful, otherwise return -1
791 */
parse_weapon(int subtype,bool replace,const char * filename)792 int parse_weapon(int subtype, bool replace, const char *filename)
793 {
794 char buf[NAME_LENGTH];
795 char fname[NAME_LENGTH];
796 weapon_info *wip = nullptr;
797 int iff, idx;
798 int primary_rearm_rate_specified=0;
799 bool first_time = false;
800 bool create_if_not_found = true;
801 bool remove_weapon = false;
802 // be careful to keep this up to date because modular tables can clear flags
803 flagset<Weapon::Info_Flags> preset_wi_flags;
804
805 required_string("$Name:");
806 stuff_string(fname, F_NAME, NAME_LENGTH);
807 diag_printf("Weapon name -- %s\n", fname);
808
809 if(optional_string("+nocreate")) {
810 if(!replace) {
811 Warning(LOCATION, "+nocreate flag used for weapon in non-modular table");
812 }
813 create_if_not_found = false;
814 }
815
816 // check first if this is on the remove blacklist
817 auto it = std::find(Removed_weapons.begin(), Removed_weapons.end(), fname);
818 if (it != Removed_weapons.end()) {
819 remove_weapon = true;
820 }
821
822 // we might have a remove tag
823 if (optional_string("+remove")) {
824 if (!replace) {
825 Warning(LOCATION, "+remove flag used for weapon in non-modular table");
826 }
827 if (!remove_weapon) {
828 Removed_weapons.push_back(fname);
829 remove_weapon = true;
830 }
831 }
832
833 //Remove @ symbol
834 //these used to denote weapons that would
835 //only be parsed in demo builds
836 if ( fname[0] == '@' ) {
837 backspace(fname);
838 }
839
840 //Check if weapon exists already
841 int w_id = weapon_info_lookup(fname);
842
843 // maybe remove it
844 if (remove_weapon)
845 {
846 if (w_id >= 0) {
847 mprintf(("Removing previously parsed weapon '%s'\n", fname));
848 Weapon_info.erase(Weapon_info.begin() + w_id);
849 }
850
851 if (!skip_to_start_of_string_either("$Name:", "#End")) {
852 error_display(1, "Missing [#End] or [$Name] after weapon class %s", fname);
853 }
854 return -1;
855 }
856
857 // an entry for this weapon exists
858 if (w_id >= 0)
859 {
860 wip = &Weapon_info[w_id];
861 if (!replace)
862 {
863 Warning(LOCATION, "Weapon name %s already exists in weapons.tbl. All weapon names must be unique; the second entry has been skipped", fname);
864 if (!skip_to_start_of_string_either("$Name:", "#End")) {
865 error_display(1, "Missing [#End] or [$Name] after duplicate weapon %s", fname);
866 }
867 return -1;
868 }
869 }
870 // an entry does not exist
871 else
872 {
873 // Don't create weapon if it has +nocreate and is in a modular table.
874 if (!create_if_not_found && replace)
875 {
876 if (!skip_to_start_of_string_either("$Name:", "#End")) {
877 error_display(1, "Missing [#End] or [$Name] after weapon %s", fname);
878 }
879
880 return -1;
881 }
882
883 // Check if there are too many weapon classes
884 if (Weapon_info.size() >= MAX_WEAPON_TYPES) {
885 Error(LOCATION, "Too many weapon classes before '%s'; maximum is %d.\n", fname, MAX_WEAPON_TYPES);
886 }
887
888 w_id = weapon_info_size();
889 Weapon_info.push_back(weapon_info());
890 wip = &Weapon_info.back();
891 first_time = true;
892
893 strcpy_s(wip->name, fname);
894
895 // if this name has a hash, create a default display name
896 if (get_pointer_to_first_hash_symbol(wip->name)) {
897 strcpy_s(wip->display_name, wip->name);
898 end_string_at_first_hash_symbol(wip->display_name, true);
899 consolidate_double_characters(wip->display_name, '#');
900 wip->wi_flags.set(Weapon::Info_Flags::Has_display_name);
901 preset_wi_flags.set(Weapon::Info_Flags::Has_display_name);
902 }
903
904 // do German translation
905 if (Lcl_gr && !Disable_built_in_translations) {
906 if (!wip->display_name[0]) {
907 strcpy_s(wip->display_name, wip->name);
908 }
909 lcl_translate_wep_name_gr(wip->display_name);
910 }
911 }
912
913 if (optional_string("$Alt name:") || optional_string("$Display Name:"))
914 {
915 stuff_string(wip->display_name, F_NAME, NAME_LENGTH);
916 wip->wi_flags.set(Weapon::Info_Flags::Has_display_name);
917 preset_wi_flags.set(Weapon::Info_Flags::Has_display_name);
918 }
919
920 //Set subtype
921 if(optional_string("$Subtype:"))
922 {
923 stuff_string(fname, F_NAME, NAME_LENGTH);
924
925 if(!stricmp("Primary", fname)) {
926 wip->subtype = WP_LASER;
927 } else if(!stricmp("Secondary", fname)) {
928 wip->subtype = WP_MISSILE;
929 } else {
930 Warning(LOCATION, "Unknown subtype on weapon '%s'", wip->name);
931 }
932 }
933 else if(wip->subtype != WP_UNUSED && !first_time)
934 {
935 if(wip->subtype != subtype) {
936 Warning(LOCATION, "Type of weapon %s entry does not agree with original entry type.", wip->name);
937 }
938 }
939 else
940 {
941 wip->subtype = subtype;
942 }
943
944 if (optional_string("+Title:")) {
945 stuff_string(wip->title, F_NAME, WEAPON_TITLE_LEN);
946 }
947
948 if (optional_string("+Description:")) {
949 if (wip->desc != NULL) {
950 vm_free(wip->desc);
951 wip->desc = NULL;
952 }
953
954 stuff_malloc_string(&wip->desc, F_MULTITEXT);
955
956 // Check if the text exceeds the limits
957 auto current_line = wip->desc;
958 size_t num_lines = 0;
959 while (current_line != nullptr) {
960 auto line_end = strchr(current_line, '\n');
961 auto line_length = line_end - current_line;
962 if (line_end == nullptr) {
963 line_length = strlen(current_line);
964 }
965
966 if (line_length >= WEAPON_DESC_MAX_LENGTH) {
967 error_display(0, "Weapon description line " SIZE_T_ARG " is too long. Maximum is %d.", num_lines + 1, WEAPON_DESC_MAX_LENGTH);
968 }
969
970 ++num_lines;
971 current_line = line_end == nullptr ? nullptr : line_end + 1; // Skip the \n character if it was a complete line
972 }
973 if (num_lines >= WEAPON_DESC_MAX_LINES) {
974 error_display(0, "Weapon description has too many lines. Maximum is %d.", WEAPON_DESC_MAX_LINES);
975 }
976 }
977
978 if (optional_string("+Tech Title:")) {
979 stuff_string(wip->tech_title, F_NAME, NAME_LENGTH);
980 }
981
982 if (optional_string("+Tech Anim:")) {
983 stuff_string(wip->tech_anim_filename, F_NAME, MAX_FILENAME_LEN);
984 }
985
986 if (optional_string("+Tech Description:")) {
987 if (wip->tech_desc != NULL) {
988 vm_free(wip->tech_desc);
989 wip->tech_desc = NULL;
990 }
991
992 stuff_malloc_string(&wip->tech_desc, F_MULTITEXT);
993 }
994
995 if (optional_string("$Tech Model:")) {
996 stuff_string(wip->tech_model, F_NAME, MAX_FILENAME_LEN);
997
998 if (optional_string("+Closeup_pos:")) {
999 stuff_vec3d(&wip->closeup_pos);
1000 }
1001
1002 if (optional_string("+Closeup_zoom:")) {
1003 stuff_float(&wip->closeup_zoom);
1004 }
1005 }
1006
1007 // Weapon fadein effect, used when no ani is specified or weapon_select_3d is active
1008 wip->selection_effect = Default_weapon_select_effect; // By default, use the FS2 effect
1009 if(optional_string("$Selection Effect:")) {
1010 char effect[NAME_LENGTH];
1011 stuff_string(effect, F_NAME, NAME_LENGTH);
1012 if (!stricmp(effect, "FS2"))
1013 wip->selection_effect = 2;
1014 else if (!stricmp(effect, "FS1"))
1015 wip->selection_effect = 1;
1016 else if (!stricmp(effect, "off"))
1017 wip->selection_effect = 0;
1018 }
1019
1020 //Check for the HUD image string
1021 if(optional_string("$HUD Image:")) {
1022 stuff_string(wip->hud_filename, F_NAME, MAX_FILENAME_LEN);
1023 }
1024
1025 // Read the model file. It can be a POF file or none.
1026 // If there is no model file (Model file: = "none") then we use our special
1027 // laser renderer which requires inner, middle and outer information.
1028 if ( optional_string("$Model file:") ) {
1029 stuff_string(wip->pofbitmap_name, F_NAME, MAX_FILENAME_LEN);
1030
1031 if (VALID_FNAME(wip->pofbitmap_name))
1032 wip->render_type = WRT_POF;
1033 else
1034 wip->render_type = WRT_NONE;
1035
1036 diag_printf("Model pof file -- %s\n", wip->pofbitmap_name );
1037 }
1038
1039 // a special LOD level to use when rendering the weapon in the hud targetbox
1040 if ( optional_string( "$POF target LOD:" ) )
1041 stuff_int(&wip->hud_target_lod);
1042
1043 if(optional_string("$Detail distance:")) {
1044 wip->num_detail_levels = (int)stuff_int_list(wip->detail_distance, MAX_MODEL_DETAIL_LEVELS, RAW_INTEGER_TYPE);
1045 }
1046
1047 if ( optional_string("$External Model File:") )
1048 stuff_string(wip->external_model_name, F_NAME, MAX_FILENAME_LEN);
1049
1050 if ( optional_string("$Submodel Rotation Speed:") )
1051 stuff_float(&wip->weapon_submodel_rotate_vel);
1052
1053 if ( optional_string("$Submodel Rotation Acceleration:") )
1054 stuff_float(&wip->weapon_submodel_rotate_accell);
1055
1056 // No POF or AVI file specified, render as special laser type.(?)
1057 ubyte r,g,b;
1058
1059 // laser bitmap itself
1060 if ( optional_string("@Laser Bitmap:") ) {
1061 stuff_string(fname, F_NAME, NAME_LENGTH);
1062
1063 if (wip->render_type == WRT_POF) {
1064 mprintf(("WARNING: Weapon '%s' has both LASER and POF render types! Will only use POF type!\n", wip->name));
1065 generic_anim_init(&wip->laser_bitmap, nullptr);
1066 } else {
1067 generic_anim_init(&wip->laser_bitmap, fname);
1068 wip->render_type = WRT_LASER;
1069 }
1070 }
1071
1072 // optional laser glow
1073 if ( optional_string("@Laser Glow:") ) {
1074 stuff_string(fname, F_NAME, NAME_LENGTH);
1075
1076 if (wip->render_type != WRT_LASER) {
1077 mprintf(("WARNING: Laser glow specified on non-LASER type weapon (%s)!\n", wip->name));
1078 Int3();
1079 } else {
1080 generic_anim_init(&wip->laser_glow_bitmap, fname);
1081 }
1082 }
1083
1084 if(optional_string("@Laser Color:"))
1085 {
1086 // This might be confusing at first glance. If we're a modular table (!first_time),
1087 // AND we're providing a new color for the laser (being in this block at all),
1088 // AND the RGB values for laser_color_1 and laser_color_2 match...
1089 // THEN we conclude that laser_color_2 wasn't explicitly defined before, and the modder would probably prefer
1090 // it if the laser didn't suddenly start changing colors from the new to the old over its lifespan. -MageKing17
1091 bool reset = (!first_time && (
1092 (wip->laser_color_1.red == wip->laser_color_2.red) &&
1093 (wip->laser_color_1.green == wip->laser_color_2.green) &&
1094 (wip->laser_color_1.blue == wip->laser_color_2.blue)));
1095 stuff_ubyte(&r);
1096 stuff_ubyte(&g);
1097 stuff_ubyte(&b);
1098 gr_init_color( &wip->laser_color_1, r, g, b );
1099 if (reset) {
1100 gr_init_color( &wip->laser_color_2, wip->laser_color_1.red, wip->laser_color_1.green, wip->laser_color_1.blue );
1101 }
1102 }
1103
1104 // optional string for cycling laser colors
1105 if(optional_string("@Laser Color2:")){
1106 stuff_ubyte(&r);
1107 stuff_ubyte(&g);
1108 stuff_ubyte(&b);
1109 gr_init_color( &wip->laser_color_2, r, g, b );
1110 } else if (first_time) {
1111 gr_init_color( &wip->laser_color_2, wip->laser_color_1.red, wip->laser_color_1.green, wip->laser_color_1.blue );
1112 }
1113
1114 if(optional_string("@Laser Length:")) {
1115 stuff_float(&wip->laser_length);
1116 }
1117
1118 if(optional_string("@Laser Head Radius:")) {
1119 stuff_float(&wip->laser_head_radius);
1120 }
1121
1122 if(optional_string("@Laser Tail Radius:")) {
1123 stuff_float(&wip->laser_tail_radius );
1124 }
1125
1126 if (optional_string("$Collision Radius Override:")) {
1127 stuff_float(&wip->collision_radius_override);
1128 }
1129
1130 if(optional_string("$Mass:")) {
1131 stuff_float( &(wip->mass) );
1132
1133 // Goober5000 - hack in order to make the beam whack behavior of these three beams match all other beams
1134 // this relies on Bobboau's beam whack hack in beam_apply_whack()
1135 if ((!strcmp(wip->name, "SAAA") && (wip->mass == 4.0f))
1136 || (!strcmp(wip->name, "MjolnirBeam") && (wip->mass == 1000.0f))
1137 || (!strcmp(wip->name, "MjolnirBeam#home") && (wip->mass == 1000.0f)))
1138 {
1139 wip->mass = 100.0f;
1140 }
1141
1142 diag_printf ("Weapon mass -- %7.3f\n", wip->mass);
1143 }
1144
1145 if(optional_string("$Velocity:")) {
1146 stuff_float( &(wip->max_speed) );
1147 diag_printf ("Weapon mass -- %7.3f\n", wip->max_speed);
1148 }
1149
1150 if(optional_string("$Fire Wait:")) {
1151 stuff_float( &(wip->fire_wait) );
1152 diag_printf ("Weapon fire wait -- %7.3f\n", wip->fire_wait);
1153 // Min and max delay stuff for weapon fire wait randomization
1154 if (optional_string("+Max Delay:")) {
1155 stuff_float(&(wip->max_delay));
1156 diag_printf("Weapon fire max delay -- %7.3f\n", wip->max_delay);
1157 }
1158 if (optional_string("+Min Delay:")) {
1159 stuff_float(&(wip->min_delay));
1160 diag_printf("Weapon fire min delay -- %7.3f\n", wip->min_delay);
1161 }
1162 }
1163
1164 if(optional_string("$Damage:")) {
1165 stuff_float(&wip->damage);
1166 //WMC - now that shockwave damage can be set for them individually,
1167 //do this automagically
1168 if(!wip->shockwave.damage_overidden) {
1169 wip->shockwave.damage = wip->damage;
1170 }
1171 }
1172
1173 // Attenuation of non-beam primary weapon damage
1174 if(optional_string("$Damage Time:")) {
1175 stuff_float(&wip->damage_time);
1176 if(optional_string("+Attenuation Damage:")){
1177 stuff_float(&wip->atten_damage);
1178 } else if (optional_string_either("+Min Damage:", "+Max Damage:")) {
1179 Warning(LOCATION, "+Min Damage: and +Max Damage: in %s are deprecated, please change to +Attenuation Damage:.", wip->name);
1180 stuff_float(&wip->atten_damage);
1181 }
1182 }
1183
1184 // angle-based damage multiplier
1185 if (optional_string("$Angle of Incidence Damage Multiplier:")) {
1186 if (optional_string("+Min:")) {
1187 stuff_float(&wip->damage_incidence_min);
1188 }
1189 if (optional_string("+Max:")) {
1190 stuff_float(&wip->damage_incidence_max);
1191 }
1192 }
1193
1194 if(optional_string("$Damage Type:")) {
1195 //This is checked for validity on every armor type
1196 //If it's invalid (or -1), then armor has no effect
1197 stuff_string(buf, F_NAME, NAME_LENGTH);
1198 wip->damage_type_idx_sav = damage_type_add(buf);
1199 wip->damage_type_idx = wip->damage_type_idx_sav;
1200 }
1201
1202 if(optional_string("$Arm time:")) {
1203 float flit;
1204 stuff_float(&flit);
1205 wip->arm_time = fl2f(flit);
1206 }
1207
1208 if(optional_string("$Arm distance:")) {
1209 stuff_float(&wip->arm_dist);
1210 }
1211
1212 if(optional_string("$Arm radius:")) {
1213 stuff_float(&wip->arm_radius);
1214 }
1215
1216 if(optional_string("$Detonation Range:")) {
1217 stuff_float(&wip->det_range);
1218 }
1219
1220 if(optional_string("$Detonation Radius:")) {
1221 stuff_float(&wip->det_radius);
1222 }
1223
1224 if(optional_string("$Flak Detonation Accuracy:")) {
1225 stuff_float(&wip->flak_detonation_accuracy);
1226 }
1227
1228 if(optional_string("$Flak Targeting Accuracy:")) {
1229 stuff_float(&wip->flak_targeting_accuracy);
1230 }
1231
1232 if(optional_string("$Untargeted Flak Range Penalty:")) {
1233 stuff_float(&wip->untargeted_flak_range_penalty);
1234 }
1235
1236 parse_shockwave_info(&wip->shockwave, "$");
1237
1238 //Retain compatibility
1239 if(first_time)
1240 {
1241 wip->dinky_shockwave = wip->shockwave;
1242 wip->dinky_shockwave.damage *= Dinky_shockwave_default_multiplier;
1243 }
1244
1245 if(optional_string("$Dinky shockwave:"))
1246 {
1247 parse_shockwave_info(&wip->dinky_shockwave, "+");
1248 }
1249
1250 if(optional_string("$Armor Factor:")) {
1251 stuff_float(&wip->armor_factor);
1252 }
1253
1254 if(optional_string("$Shield Factor:")) {
1255 stuff_float(&wip->shield_factor);
1256 }
1257
1258 if(optional_string("$Subsystem Factor:")) {
1259 stuff_float(&wip->subsystem_factor);
1260 }
1261
1262 if(optional_string("$Lifetime Min:")) {
1263 stuff_float(&wip->life_min);
1264
1265 if(wip->life_min < 0.0f) {
1266 wip->life_min = 0.0f;
1267 Warning(LOCATION, "Lifetime min for weapon '%s' cannot be less than 0. Setting to 0.\n", wip->name);
1268 }
1269 }
1270
1271 if(optional_string("$Lifetime Max:")) {
1272 stuff_float(&wip->life_max);
1273
1274 if(wip->life_max < 0.0f) {
1275 wip->life_max = 0.0f;
1276 Warning(LOCATION, "Lifetime max for weapon '%s' cannot be less than 0. Setting to 0.\n", wip->name);
1277 } else if (wip->life_max < wip->life_min) {
1278 wip->life_max = wip->life_min + 0.1f;
1279 Warning(LOCATION, "Lifetime max for weapon '%s' cannot be less than its Lifetime Min (%f) value. Setting to %f.\n", wip->name, wip->life_min, wip->life_max);
1280 } else {
1281 wip->lifetime = (wip->life_min+wip->life_max)*0.5f;
1282 }
1283 }
1284
1285 if(wip->life_min >= 0.0f && wip->life_max < 0.0f) {
1286 wip->lifetime = wip->life_min;
1287 wip->life_min = -1.0f;
1288 Warning(LOCATION, "Lifetime min, but not lifetime max, specified for weapon %s. Assuming static lifetime of %.2f seconds.\n", wip->name, wip->lifetime);
1289 }
1290
1291 if(optional_string("$Lifetime:")) {
1292 if(wip->life_min >= 0.0f || wip->life_max >= 0.0f) {
1293 Warning(LOCATION, "Lifetime min or max specified, but $Lifetime was also specified; min or max will be used.");
1294 }
1295 stuff_float(&wip->lifetime);
1296 if (wip->damage_time > wip->lifetime) {
1297 Warning(LOCATION, "Lifetime is lower than Damage Time, setting Damage Time to be one half the value of Lifetime.");
1298 wip->damage_time = 0.5f * wip->lifetime;
1299 }
1300 }
1301
1302 if(optional_string("$Energy Consumed:")) {
1303 stuff_float(&wip->energy_consumed);
1304 }
1305
1306 // Goober5000: cargo size is checked for div-0 errors... see below (must parse flags first)
1307 if(optional_string("$Cargo Size:"))
1308 {
1309 stuff_float(&wip->cargo_size);
1310 }
1311
1312 bool is_homing=false;
1313 if(optional_string("$Homing:")) {
1314 stuff_boolean(&is_homing);
1315 }
1316
1317 if (is_homing || (wip->is_homing()))
1318 {
1319 char temp_type[NAME_LENGTH];
1320
1321 // the following five items only need to be recorded if the weapon is a homing weapon
1322 if(optional_string("+Type:"))
1323 {
1324 stuff_string(temp_type, F_NAME, NAME_LENGTH);
1325 if (!stricmp(temp_type, NOX("HEAT")))
1326 {
1327 wip->wi_flags.remove(Weapon::Info_Flags::Homing_aspect);
1328 wip->wi_flags.remove(Weapon::Info_Flags::Homing_javelin);
1329
1330 wip->wi_flags.set(Weapon::Info_Flags::Homing_heat);
1331 preset_wi_flags.set(Weapon::Info_Flags::Homing_heat);
1332 }
1333 else if (!stricmp(temp_type, NOX("ASPECT")))
1334 {
1335 wip->wi_flags.remove(Weapon::Info_Flags::Homing_heat);
1336 wip->wi_flags.remove(Weapon::Info_Flags::Homing_javelin);
1337
1338 wip->wi_flags.set(Weapon::Info_Flags::Homing_aspect);
1339 preset_wi_flags.set(Weapon::Info_Flags::Homing_aspect);
1340 }
1341 else if (!stricmp(temp_type, NOX("JAVELIN")))
1342 {
1343 wip->wi_flags.remove(Weapon::Info_Flags::Homing_heat);
1344 wip->wi_flags.remove(Weapon::Info_Flags::Homing_aspect);
1345
1346 wip->wi_flags.set(Weapon::Info_Flags::Homing_javelin);
1347 preset_wi_flags.set(Weapon::Info_Flags::Homing_javelin);
1348 }
1349
1350 wip->wi_flags.set(Weapon::Info_Flags::Turns);
1351 preset_wi_flags.set(Weapon::Info_Flags::Turns);
1352 //If you want to add another weapon, remember you need to reset
1353 //ALL homing flags.
1354 }
1355
1356 if (wip->wi_flags[Weapon::Info_Flags::Homing_heat])
1357 {
1358 float view_cone_angle;
1359
1360 if(optional_string("+Turn Time:")) {
1361 stuff_float(&wip->turn_time);
1362 }
1363
1364 if (optional_string("+Turning Acceleration Time:")) {
1365 stuff_float(&wip->turn_accel_time);
1366 if (!Framerate_independent_turning) {
1367 static bool No_flag_turn_accel_warned = false;
1368 if (!No_flag_turn_accel_warned) {
1369 No_flag_turn_accel_warned = true;
1370 Warning(LOCATION, "One or more weapons are using Turning Acceleration Time; this requires \"AI use framerate independent turning\" in game_settings.tbl ");
1371 }
1372 wip->turn_accel_time = 0.f;
1373 }
1374 }
1375
1376 if(optional_string("+View Cone:")) {
1377 stuff_float(&view_cone_angle);
1378 wip->fov = cosf(fl_radians(view_cone_angle * 0.5f));
1379 }
1380
1381 if (optional_string("+Seeker Strength:"))
1382 {
1383 //heat default seeker strength is 3
1384 stuff_float(&wip->seeker_strength);
1385 if (wip->seeker_strength > 0)
1386 {
1387 wip->wi_flags.set(Weapon::Info_Flags::Custom_seeker_str);
1388 preset_wi_flags.set(Weapon::Info_Flags::Custom_seeker_str);
1389 }
1390 else
1391 {
1392 Warning(LOCATION,"Seeker Strength for missile \'%s\' must be greater than zero\nResetting value to default.", wip->name);
1393 wip->seeker_strength = 3.0f;
1394 }
1395 }
1396 else
1397 {
1398 if(!(wip->wi_flags[Weapon::Info_Flags::Custom_seeker_str]))
1399 wip->seeker_strength = 3.0f;
1400 }
1401
1402 if (optional_string("+Target Lead Scaler:"))
1403 {
1404 stuff_float(&wip->target_lead_scaler);
1405 if (wip->target_lead_scaler == 0.0f)
1406 wip->wi_flags.remove(Weapon::Info_Flags::Variable_lead_homing);
1407 else {
1408 wip->wi_flags.set(Weapon::Info_Flags::Variable_lead_homing);
1409 preset_wi_flags.set(Weapon::Info_Flags::Variable_lead_homing);
1410 }
1411 }
1412 }
1413 else if ((wip->wi_flags[Weapon::Info_Flags::Homing_aspect]) || (wip->wi_flags[Weapon::Info_Flags::Homing_javelin]))
1414 {
1415 if(optional_string("+Turn Time:")) {
1416 stuff_float(&wip->turn_time);
1417 }
1418
1419 if (optional_string("+Turning Acceleration Time:")) {
1420 stuff_float(&wip->turn_accel_time);
1421 if (!Framerate_independent_turning) {
1422 static bool No_flag_turn_accel_warned = false;
1423 if (!No_flag_turn_accel_warned) {
1424 No_flag_turn_accel_warned = true;
1425 Warning(LOCATION, "One or more weapons are using Turning Acceleration Time; this requires \"AI use framerate independent turning\" in game_settings.tbl ");
1426 }
1427 wip->turn_accel_time = 0.f;
1428 }
1429 }
1430
1431 if(optional_string("+View Cone:")) {
1432 float view_cone_angle;
1433 stuff_float(&view_cone_angle);
1434 wip->fov = (float)cos(fl_radians(view_cone_angle * 0.5f));
1435 }
1436
1437 if(optional_string("+Min Lock Time:")) { // minimum time (in seconds) to achieve lock
1438 stuff_float(&wip->min_lock_time);
1439 }
1440
1441 if(optional_string("+Lock Pixels/Sec:")) { // pixels/sec moved while locking
1442 stuff_int(&wip->lock_pixels_per_sec);
1443 }
1444
1445 if(optional_string("+Catch-up Pixels/Sec:")) { // pixels/sec moved while catching-up for a lock
1446 stuff_int(&wip->catchup_pixels_per_sec);
1447 }
1448
1449 if(optional_string("+Catch-up Penalty:")) {
1450 // number of extra pixels to move while locking as a penalty for catching up for a lock
1451 stuff_int(&wip->catchup_pixel_penalty);
1452 }
1453
1454 if (optional_string("+Seeker Strength:"))
1455 {
1456 //aspect default seeker strength is 2
1457 stuff_float(&wip->seeker_strength);
1458 if (wip->seeker_strength > 0)
1459 {
1460 wip->wi_flags.set(Weapon::Info_Flags::Custom_seeker_str);
1461 preset_wi_flags.set(Weapon::Info_Flags::Custom_seeker_str);
1462 }
1463 else
1464 {
1465 Warning(LOCATION,"Seeker Strength for missile \'%s\' must be greater than zero\nReseting value to default.", wip->name);
1466 wip->seeker_strength = 2.0f;
1467 }
1468 }
1469 else
1470 {
1471 if(!(wip->wi_flags[Weapon::Info_Flags::Custom_seeker_str]))
1472 wip->seeker_strength = 2.0f;
1473 }
1474
1475 if (optional_string("+Target Lead Scaler:"))
1476 {
1477 stuff_float(&wip->target_lead_scaler);
1478 if (wip->target_lead_scaler == 1.0f)
1479 wip->wi_flags.remove(Weapon::Info_Flags::Variable_lead_homing);
1480 else {
1481 wip->wi_flags.set(Weapon::Info_Flags::Variable_lead_homing);
1482 preset_wi_flags.set(Weapon::Info_Flags::Variable_lead_homing);
1483 }
1484 }
1485
1486 if (optional_string("+Target Lock Restriction:")) {
1487 if (optional_string("current target, any subsystem")) {
1488 wip->target_restrict = LR_CURRENT_TARGET_SUBSYS;
1489
1490 }
1491 else if (optional_string("any target")) {
1492 wip->target_restrict = LR_ANY_TARGETS;
1493
1494 }
1495 else if (optional_string("current target only")) {
1496 wip->target_restrict = LR_CURRENT_TARGET;
1497
1498 }
1499 else {
1500 wip->target_restrict = LR_CURRENT_TARGET;
1501
1502 }
1503
1504 }
1505
1506 if (optional_string("+Independent Seekers:")) {
1507 stuff_boolean(&wip->multi_lock);
1508 }
1509
1510 if (optional_string("+Trigger Hold:")) {
1511 stuff_boolean(&wip->trigger_lock);
1512 }
1513
1514 if (optional_string("+Reset On Launch:")) {
1515 stuff_boolean(&wip->launch_reset_locks);
1516 }
1517
1518 if (optional_string("+Max Seekers Per Target:")) {
1519 stuff_int(&wip->max_seekers_per_target);
1520 }
1521
1522 if (optional_string("+Max Active Seekers:")) {
1523 stuff_int(&wip->max_seeking);
1524 }
1525
1526 if (wip->is_locked_homing()) {
1527 // locked homing missiles have a much longer lifespan than the AI think they do
1528 wip->max_lifetime = wip->lifetime * LOCKED_HOMING_EXTENDED_LIFE_FACTOR;
1529 }
1530 }
1531 else
1532 {
1533 Error(LOCATION, "Illegal homing type = %s.\nMust be HEAT, ASPECT or JAVELIN.\n", temp_type);
1534 }
1535
1536 // handle homing restrictions
1537 if (optional_string("+Ship Types:")) {
1538 SCP_vector<SCP_string> temp_names;
1539 stuff_string_list(temp_names);
1540
1541 for (auto& name : temp_names)
1542 wip->ship_restrict_strings.emplace_back(LockRestrictionType::TYPE, name);
1543 }
1544
1545 if (optional_string("+Ship Classes:")) {
1546 SCP_vector<SCP_string> temp_names;
1547 stuff_string_list(temp_names);
1548
1549 for (auto& name : temp_names)
1550 wip->ship_restrict_strings.emplace_back(LockRestrictionType::CLASS, name);
1551 }
1552
1553 if (optional_string("+Species:")) {
1554 SCP_vector<SCP_string> temp_names;
1555 stuff_string_list(temp_names);
1556
1557 for (auto& name : temp_names)
1558 wip->ship_restrict_strings.emplace_back(LockRestrictionType::SPECIES, name);
1559 }
1560
1561 if (optional_string("+IFFs:")) {
1562 SCP_vector<SCP_string> temp_names;
1563 stuff_string_list(temp_names);
1564
1565 for (auto& name : temp_names)
1566 wip->ship_restrict_strings.emplace_back(LockRestrictionType::IFF, name);
1567 }
1568
1569 if (subtype == WP_LASER && !wip->wi_flags[Weapon::Info_Flags::Homing_heat]) {
1570 Warning(LOCATION, "Homing primary %s must be a heat seeker. Setting to heat", wip->name);
1571 wip->wi_flags.remove(Weapon::Info_Flags::Homing_aspect);
1572 wip->wi_flags.remove(Weapon::Info_Flags::Homing_javelin);
1573 wip->wi_flags.set(Weapon::Info_Flags::Homing_heat);
1574 }
1575 }
1576
1577 if (optional_string("$Homing Auto-Target Method:"))
1578 {
1579 char temp[NAME_LENGTH];
1580 stuff_string(temp, F_NAME, NAME_LENGTH);
1581 if (!stricmp(temp, NOX("CLOSEST")))
1582 wip->auto_target_method = HomingAcquisitionType::CLOSEST;
1583 else if (!stricmp(temp, NOX("RANDOM")))
1584 wip->auto_target_method = HomingAcquisitionType::RANDOM;
1585 else
1586 Warning(LOCATION, "Homing weapon %s has an unrecognized Homing Auto-Target Method %s", wip->name, temp);
1587 }
1588
1589 // swarm missiles
1590 int s_count;
1591
1592 if(optional_string("$Swarm:"))
1593 {
1594 wip->swarm_count = SWARM_DEFAULT_NUM_MISSILES_FIRED;
1595 stuff_int(&s_count);
1596 wip->swarm_count = (short)s_count;
1597
1598 // flag as being a swarm weapon
1599 wip->wi_flags.set(Weapon::Info_Flags::Swarm);
1600 preset_wi_flags.set(Weapon::Info_Flags::Swarm);
1601 }
1602
1603 // *Swarm wait token -Et1
1604 if((wip->wi_flags[Weapon::Info_Flags::Swarm]) && optional_string( "+SwarmWait:" ))
1605 {
1606 float SwarmWait;
1607 stuff_float( &SwarmWait );
1608 if( SwarmWait > 0.0f && SwarmWait * wip->swarm_count < wip->fire_wait )
1609 {
1610 wip->SwarmWait = int( SwarmWait * 1000 );
1611 }
1612 }
1613
1614 if(optional_string("$Acceleration Time:")) {
1615 stuff_float(&wip->acceleration_time);
1616 }
1617
1618 if(optional_string("$Velocity Inherit:")) {
1619 stuff_float(&wip->vel_inherit_amount);
1620 wip->vel_inherit_amount /= 100.0f; // % -> 0..1
1621 }
1622
1623 if(optional_string("$Free Flight Time:")) {
1624 stuff_float(&(wip->free_flight_time));
1625 } else if(first_time && is_homing) {
1626 if (subtype == WP_LASER) {
1627 wip->free_flight_time = HOMING_DEFAULT_PRIMARY_FREE_FLIGHT_TIME;
1628 }
1629 else {
1630 wip->free_flight_time = HOMING_DEFAULT_FREE_FLIGHT_TIME;
1631 }
1632 }
1633
1634 if (optional_string("$Free Flight Speed:")) {
1635 error_display(0, "$Free Flight Speed: used for weapon '%s' is depreciated and ignored, use $Free Flight Speed Factor: instead. ", wip->name);
1636 float temp;
1637 stuff_float(&temp);
1638 }
1639
1640 if(optional_string("$Free Flight Speed Factor:")) {
1641 stuff_float(&(wip->free_flight_speed_factor));
1642 if (wip->free_flight_speed_factor < 0.01f) {
1643 error_display(0, "Free Flight Speed Factor value is too low for weapon '%s'. Resetting to default (0.25 times maximum)", wip->name);
1644 wip->free_flight_speed_factor = HOMING_DEFAULT_FREE_FLIGHT_FACTOR;
1645 }
1646 }
1647 else if (first_time && is_homing) {
1648 wip->free_flight_speed_factor = HOMING_DEFAULT_FREE_FLIGHT_FACTOR;
1649 }
1650 //Optional one-shot sound to play at the beginning of firing
1651 parse_game_sound("$PreLaunchSnd:", &wip->pre_launch_snd);
1652
1653 //Optional delay for Pre-Launch sound
1654 if(optional_string("+PreLaunchSnd Min Interval:"))
1655 {
1656 stuff_int(&wip->pre_launch_snd_min_interval);
1657 }
1658
1659 //Launch sound
1660 parse_game_sound("$LaunchSnd:", &wip->launch_snd);
1661
1662 //Impact sound
1663 parse_game_sound("$ImpactSnd:", &wip->impact_snd);
1664
1665 //Disarmed impact sound
1666 parse_game_sound("$Disarmed ImpactSnd:", &wip->disarmed_impact_snd);
1667
1668 parse_game_sound("$FlyBySnd:", &wip->flyby_snd);
1669
1670 parse_game_sound("$AmbientSnd:", &wip->ambient_snd);
1671
1672 parse_game_sound("$TrackingSnd:", &wip->hud_tracking_snd);
1673
1674 parse_game_sound("$LockedSnd:", &wip->hud_locked_snd);
1675
1676 parse_game_sound("$InFlightSnd:", &wip->hud_in_flight_snd);
1677
1678 if (optional_string("+Inflight sound type:"))
1679 {
1680 SCP_string type;
1681
1682 stuff_string(type, F_NAME);
1683
1684 if (!stricmp(type.c_str(), "TARGETED"))
1685 {
1686 wip->in_flight_play_type = TARGETED;
1687 }
1688 else if (!stricmp(type.c_str(), "UNTARGETED"))
1689 {
1690 wip->in_flight_play_type = UNTARGETED;
1691 }
1692 else if (!stricmp(type.c_str(), "ALWAYS"))
1693 {
1694 wip->in_flight_play_type = ALWAYS;
1695 }
1696 else
1697 {
1698 Warning(LOCATION, "Unknown in-flight sound type \"%s\"!", type.c_str());
1699 wip->in_flight_play_type = ALWAYS;
1700 }
1701 }
1702
1703 if(optional_string("$Model:"))
1704 {
1705 wip->render_type = WRT_POF;
1706 stuff_string(wip->pofbitmap_name, F_NAME, MAX_FILENAME_LEN);
1707 }
1708
1709 // handle rearm rate - modified by Goober5000
1710 primary_rearm_rate_specified = 0;
1711 float rearm_rate;
1712 // Anticipate rearm rate for ballistic primaries
1713 if (optional_string("$Rearm Rate:"))
1714 {
1715 if (subtype != WP_MISSILE) {
1716 primary_rearm_rate_specified = 1;
1717 }
1718
1719 stuff_float( &rearm_rate );
1720 if (rearm_rate > 0.0f)
1721 {
1722 wip->rearm_rate = 1.0f/rearm_rate;
1723 }
1724 else
1725 {
1726 Warning(LOCATION, "Rearm wait of less than 0 on weapon %s; setting to 1", wip->name);
1727 }
1728 }
1729
1730 // set the number of missles or ballistic ammo reloaded per batch
1731 int reloaded_each_batch;
1732
1733 if (optional_string("$Rearm Ammo Increment:")) {
1734 stuff_int(&reloaded_each_batch);
1735
1736 if (reloaded_each_batch > 0) {
1737 wip->reloaded_per_batch = reloaded_each_batch;
1738 } else if (wip->reloaded_per_batch != -1) {
1739 Warning(LOCATION, "Rearm increment amount of 0 or less in weapon %s, keeping at previously defined value.",
1740 wip->name);
1741 } else {
1742 Warning(LOCATION, "Rearm increment amount of 0 or less in weapon %s, setting to the default, 4 for missiles or 100 for ballistic primaries.", wip->name);
1743 }
1744 }
1745 // This setting requires a default if not specified, otherwise, rearming will break. - Cyborg17
1746 if (wip->reloaded_per_batch == -1) {
1747 if (subtype == WP_MISSILE) {
1748 wip->reloaded_per_batch = REARM_NUM_MISSILES_PER_BATCH;
1749 } else {
1750 wip->reloaded_per_batch = REARM_NUM_BALLISTIC_PRIMARIES_PER_BATCH;
1751 }
1752 }
1753
1754 if (optional_string("+Weapon Range:")) {
1755 stuff_float(&wip->weapon_range);
1756 }
1757
1758 if( optional_string( "+Weapon Min Range:" ) )
1759 {
1760 float min_range;
1761 stuff_float( &min_range);
1762
1763 if(min_range > 0.0f && min_range < MIN( wip->max_speed * wip->lifetime, wip->weapon_range ) )
1764 {
1765 wip->weapon_min_range = min_range;
1766 }
1767 else
1768 {
1769 Warning(LOCATION, "Invalid minimum range on weapon %s; setting to 0", wip->name);
1770 }
1771 }
1772
1773 if (optional_string("+Weapon Optimum Range:")) {
1774 stuff_float(&wip->optimum_range);
1775 if (wip->optimum_range < wip->weapon_min_range || wip->optimum_range > MIN(wip->max_speed * wip->lifetime, wip->weapon_range)) {
1776 Warning(LOCATION, "Optimum range on weapon %s must be within its min range and max range", wip->name);
1777 wip->optimum_range = 0.0f;
1778 }
1779 }
1780
1781 if (optional_string("$Pierce Objects:")) {
1782 stuff_boolean(&wip->pierce_objects);
1783
1784 if (optional_string("+Spawn Children On Pierce:")) {
1785 stuff_boolean(&wip->spawn_children_on_pierce);
1786 }
1787 }
1788
1789 parse_wi_flags(wip, preset_wi_flags);
1790
1791 // preset_wi_flags does not need to be maintained after the above function is called
1792
1793 // be friendly; make sure ballistic flags are synchronized - Goober5000
1794 // primary
1795 if (subtype == WP_LASER)
1796 {
1797 // ballistic
1798 if (wip->wi_flags[Weapon::Info_Flags::Ballistic])
1799 {
1800 // rearm rate not specified
1801 if (!primary_rearm_rate_specified && first_time)
1802 {
1803 Warning(LOCATION, "$Rearm Rate for ballistic primary %s not specified. Defaulting to 100...\n", wip->name);
1804 wip->rearm_rate = 100.0f;
1805 }
1806 }
1807 // not ballistic
1808 else
1809 {
1810 // rearm rate specified
1811 if (primary_rearm_rate_specified)
1812 {
1813 Warning(LOCATION, "$Rearm Rate specified for non-ballistic primary %s\n", wip->name);
1814 }
1815 }
1816
1817 }
1818 // secondary
1819 else
1820 {
1821 // ballistic
1822 if (wip->wi_flags[Weapon::Info_Flags::Ballistic])
1823 {
1824 Warning(LOCATION, "Secondary weapon %s can't be ballistic. Removing this flag...\n", wip->name);
1825 wip->wi_flags.remove(Weapon::Info_Flags::Ballistic);
1826 }
1827 }
1828
1829 // also make sure EMP is friendly - Goober5000
1830 if (wip->wi_flags[Weapon::Info_Flags::Emp])
1831 {
1832 if (!wip->shockwave.outer_rad)
1833 {
1834 Warning(LOCATION, "Outer blast radius of weapon %s is zero - EMP will not work.\nAdd $Outer Radius to weapon table entry.\n", wip->name);
1835 }
1836 }
1837
1838 // also make sure secondaries and ballistic primaries do not have 0 cargo size
1839 if (subtype == WP_MISSILE || wip->wi_flags[Weapon::Info_Flags::Ballistic])
1840 {
1841 if (wip->cargo_size == 0.0f)
1842 {
1843 Warning(LOCATION, "Cargo size of weapon %s cannot be 0. Setting to 1.\n", wip->name);
1844 wip->cargo_size = 1.0f;
1845 }
1846 }
1847
1848 if ( optional_string("$Trail:") ) {
1849 trail_info *ti = &wip->tr_info;
1850 wip->wi_flags.set(Weapon::Info_Flags::Trail); // missile leaves a trail
1851
1852 if ( optional_string("+Start Width:") )
1853 stuff_float(&ti->w_start);
1854
1855 if ( optional_string("+End Width:") )
1856 stuff_float(&ti->w_end);
1857
1858 if ( optional_string("+Start Alpha:") )
1859 stuff_float(&ti->a_start);
1860
1861 if ( optional_string("+End Alpha:") )
1862 stuff_float(&ti->a_end);
1863
1864 if (optional_string("+Alpha Decay Exponent:")) {
1865 stuff_float(&ti->a_decay_exponent);
1866 if (ti->a_decay_exponent < 0.0f) {
1867 Warning(LOCATION, "Trail Alpha Decay Exponent of weapon %s cannot be negative. Reseting to 1.\n", wip->name);
1868 ti->a_decay_exponent = 1.0f;
1869 }
1870 }
1871
1872 if ( optional_string("+Max Life:") ) {
1873 stuff_float(&ti->max_life);
1874 ti->stamp = fl2i(1000.0f*ti->max_life)/(NUM_TRAIL_SECTIONS+1);
1875 }
1876
1877 if (optional_string("+Spread:"))
1878 stuff_float(&ti->spread);
1879
1880 if ( required_string("+Bitmap:") ) {
1881 stuff_string(fname, F_NAME, NAME_LENGTH);
1882 generic_bitmap_init(&ti->texture, fname);
1883 }
1884
1885 if (optional_string("+Bitmap Stretch:")) {
1886 stuff_float(&ti->texture_stretch);
1887 if (ti->texture_stretch == 0.0f) {
1888 Warning(LOCATION, "Trail bitmap stretch of weapon %s cannot be 0. Setting to 1.\n", wip->name);
1889 ti->texture_stretch = 1.0f;
1890 }
1891 }
1892
1893 if ( optional_string("+Faded Out Sections:") ) {
1894 stuff_int(&ti->n_fade_out_sections);
1895 }
1896 }
1897
1898 // read in filename for icon that is used in weapons selection
1899 if ( optional_string("$Icon:") ) {
1900 stuff_string(wip->icon_filename, F_NAME, MAX_FILENAME_LEN);
1901 }
1902
1903 // read in filename for animation that is used in weapons selection
1904 if ( optional_string("$Anim:") ) {
1905 stuff_string(wip->anim_filename, F_NAME, MAX_FILENAME_LEN);
1906 }
1907
1908 if (optional_string("$Impact Effect:")) {
1909 wip->impact_weapon_expl_effect = particle::util::parseEffect(wip->name);
1910 } else {
1911 // This is for compatibility with old tables
1912 // Do not add features here!
1913
1914 float size = 1.0f;
1915 int bitmapIndex = -1;
1916
1917 // $Impact Effect was not found, parse the old values
1918 if (optional_string("$Impact Explosion:")) {
1919 stuff_string(fname, F_NAME, NAME_LENGTH);
1920
1921 if (VALID_FNAME(fname))
1922 {
1923 bitmapIndex = bm_load_animation(fname);
1924
1925 if (bitmapIndex < 0)
1926 {
1927 Warning(LOCATION, "Couldn't load effect '%s' for weapon '%s'.", fname, wip->name);
1928 }
1929 }
1930 }
1931
1932 if (optional_string("$Impact Explosion Radius:"))
1933 stuff_float(&size);
1934
1935 if (bitmapIndex >= 0 && size > 0.0f)
1936 {
1937 using namespace particle;
1938
1939 // Only beams do this randomization
1940 if (subtype == WP_BEAM)
1941 {
1942 // The original formula is (1.2f + 0.007f * (float)(rand() % 100)) which generates values within [1.2, 1.9)
1943 auto singleEffect = effects::SingleParticleEffect::createInstance(bitmapIndex, size * 1.2f, size * 1.9f);
1944 wip->impact_weapon_expl_effect = ParticleManager::get()->addEffect(singleEffect);
1945 }
1946 else
1947 {
1948 auto singleEffect = effects::SingleParticleEffect::createInstance(bitmapIndex, size, size);
1949 wip->impact_weapon_expl_effect = ParticleManager::get()->addEffect(singleEffect);
1950 }
1951 }
1952 }
1953
1954 if ( optional_string("$Shield Impact Explosion Radius:") ) {
1955 stuff_float(&wip->shield_impact_explosion_radius);
1956 } else if (first_time) {
1957 using namespace particle;
1958
1959 // Default value
1960 wip->shield_impact_explosion_radius = 1.0f;
1961 if (wip->impact_weapon_expl_effect.isValid()) {
1962 auto singleEffect = dynamic_cast<effects::SingleParticleEffect*>(ParticleManager::get()->getEffect(wip->impact_weapon_expl_effect));
1963
1964 if (singleEffect)
1965 {
1966 // Initialize with value of the previously created single particle effect
1967 wip->shield_impact_explosion_radius = singleEffect->getProperties().m_radius.next();
1968 }
1969 }
1970 }
1971
1972 if (optional_string("$Dinky Impact Effect:")) {
1973 wip->dinky_impact_weapon_expl_effect = particle::util::parseEffect(wip->name);
1974 } else {
1975 // This is for compatibility with old tables
1976 // Do not add features here!
1977
1978 if (optional_string("$Dinky Impact Explosion:")) {
1979 stuff_string(fname, F_NAME, NAME_LENGTH);
1980
1981 int bitmapID = -1;
1982 float size = 1.0f;
1983
1984 if (VALID_FNAME(fname))
1985 {
1986 bitmapID = bm_load_animation(fname);
1987
1988 if (bitmapID < 0)
1989 {
1990 Warning(LOCATION, "Couldn't load effect '%s' for weapon '%s'.", fname, wip->name);
1991 }
1992 }
1993
1994 if (optional_string("$Dinky Impact Explosion Radius:"))
1995 {
1996 stuff_float(&size);
1997 }
1998
1999 if (bitmapID >= 0 && size > 0.0f)
2000 {
2001 using namespace particle;
2002
2003 // Only beams do this randomization
2004 if (subtype == WP_BEAM)
2005 {
2006 // The original formula is (1.2f + 0.007f * (float)(rand() % 100)) which generates values within [1.2, 1.9)
2007 auto singleEffect = effects::SingleParticleEffect::createInstance(bitmapID, size * 1.2f, size * 1.9f);
2008 wip->dinky_impact_weapon_expl_effect = ParticleManager::get()->addEffect(singleEffect);
2009 }
2010 else
2011 {
2012 auto singleEffect = effects::SingleParticleEffect::createInstance(bitmapID, size, size);
2013 wip->dinky_impact_weapon_expl_effect = ParticleManager::get()->addEffect(singleEffect);
2014 }
2015 }
2016 }
2017 else if (first_time) {
2018 wip->dinky_impact_weapon_expl_effect = wip->impact_weapon_expl_effect;
2019 }
2020
2021 // Slight variation from original, as the original size is not known anymore the whole effect is copied
2022 // If the radius was stull specified it needs to be parsed to not mess up something else
2023 if (optional_string("$Dinky Impact Explosion Radius:"))
2024 {
2025 float temp;
2026 stuff_float(&temp);
2027 }
2028 }
2029
2030 if (optional_string("$Piercing Impact Effect:")) {
2031 wip->piercing_impact_effect = particle::util::parseEffect(wip->name);
2032 if (first_time)
2033 {
2034 // The secondary effect is only needed if the old effect got parsed
2035 wip->piercing_impact_secondary_effect = particle::ParticleEffectHandle::invalid();
2036 }
2037 }
2038 else
2039 {
2040 // This is for compatibility with old tables
2041 // Do not add features here!
2042
2043 using namespace particle;
2044 using namespace effects;
2045
2046 int effectIndex = -1;
2047 float radius = 0.0f;
2048 int count = 0;
2049 float life = 0.0f;
2050 float velocity = 0.0f;
2051 float back_velocity = 0.0f;
2052 float variance = 0.0f;
2053
2054 particle_emitter emitter;
2055 memset(&emitter, 0, sizeof(emitter));
2056
2057 if (optional_string("$Piercing Impact Explosion:"))
2058 {
2059 stuff_string(fname, F_NAME, NAME_LENGTH);
2060
2061 effectIndex = bm_load_animation(fname);
2062
2063 if (effectIndex < 0)
2064 {
2065 Warning(LOCATION, "Failed to load effect '%s' for weapon %s!", fname, wip->name);
2066 }
2067 }
2068
2069 if (optional_string("$Piercing Impact Radius:"))
2070 stuff_float(&radius);
2071
2072 if (optional_string("$Piercing Impact Velocity:"))
2073 stuff_float(&velocity);
2074
2075 if (optional_string("$Piercing Impact Splash Velocity:"))
2076 stuff_float(&back_velocity);
2077
2078 if (optional_string("$Piercing Impact Variance:"))
2079 stuff_float(&variance);
2080
2081 if (optional_string("$Piercing Impact Life:"))
2082 stuff_float(&life);
2083
2084 if (optional_string("$Piercing Impact Particles:"))
2085 stuff_int(&count);
2086
2087 if (effectIndex >= 0 && radius != 0.0f)
2088 {
2089 emitter.max_vel = 2.0f * velocity;
2090 emitter.min_vel = 0.5f * velocity;
2091 emitter.max_life = 2.0f * life;
2092 emitter.min_life = 0.25f * life;
2093 emitter.num_high = 2 * count;
2094 emitter.num_low = count / 2;
2095 emitter.normal_variance = variance;
2096 emitter.max_rad = 2.0f * radius;
2097 emitter.min_rad = 0.5f * radius;
2098 emitter.vel = vmd_zero_vector;
2099
2100 auto emitterEffect = new ParticleEmitterEffect();
2101 emitterEffect->setValues(emitter, effectIndex, 10.0f);
2102 wip->piercing_impact_effect = ParticleManager::get()->addEffect(emitterEffect);
2103
2104 if (back_velocity != 0.0f)
2105 {
2106 emitter.max_vel = 2.0f * back_velocity;
2107 emitter.min_vel = 0.5f * back_velocity;
2108 emitter.num_high /= 2;
2109 emitter.num_low /= 2;
2110
2111 auto secondaryEffect = new ParticleEmitterEffect();
2112 secondaryEffect->setValues(emitter, effectIndex, 10.0f);
2113 wip->piercing_impact_secondary_effect = ParticleManager::get()->addEffect(secondaryEffect);
2114 }
2115 }
2116 }
2117
2118 if (optional_string("$Inflight Effect:")) {
2119 auto effetIdx = particle::util::parseEffect(wip->name);
2120 wip->state_effects.insert(std::make_pair(WeaponState::NORMAL, effetIdx));
2121 }
2122
2123 if (wip->subtype == WP_MISSILE)
2124 {
2125 if (optional_string("$Freeflight Effect:")) {
2126 auto effetIdx = particle::util::parseEffect(wip->name);
2127 wip->state_effects.insert(std::make_pair(WeaponState::FREEFLIGHT, effetIdx));
2128 }
2129 if (optional_string("$Ignition Effect:")) {
2130 auto effetIdx = particle::util::parseEffect(wip->name);
2131 wip->state_effects.insert(std::make_pair(WeaponState::IGNITION, effetIdx));
2132 }
2133 if (optional_string("$Homed Flight Effect:")) {
2134 auto effetIdx = particle::util::parseEffect(wip->name);
2135 wip->state_effects.insert(std::make_pair(WeaponState::HOMED_FLIGHT, effetIdx));
2136 }
2137 if (optional_string("$Unhomed Flight Effect:")) {
2138 auto effetIdx = particle::util::parseEffect(wip->name);
2139 wip->state_effects.insert(std::make_pair(WeaponState::UNHOMED_FLIGHT, effetIdx));
2140 }
2141 }
2142
2143 // muzzle flash
2144 if ( optional_string("$Muzzleflash:") ) {
2145 stuff_string(fname, F_NAME, NAME_LENGTH);
2146
2147 // look it up
2148 wip->muzzle_flash = mflash_lookup(fname);
2149 }
2150
2151 // EMP optional stuff (if WIF_EMP is not set, none of this matters, anyway)
2152 if( optional_string("$EMP Intensity:") ){
2153 stuff_float(&wip->emp_intensity);
2154 }
2155
2156 if( optional_string("$EMP Time:") ){
2157 stuff_float(&wip->emp_time);
2158 }
2159
2160 // This is an optional modifier for a weapon that uses the "apply recoil" flag. recoil_force in ship.cpp line 10445 is multiplied by this if defined.
2161 if (optional_string("$Recoil Modifier:")){
2162 if (!(wip->wi_flags[Weapon::Info_Flags::Apply_Recoil])){
2163 Warning(LOCATION, "$Recoil Modifier specified for weapon %s but this weapon does not have the \"apply recoil\" weapon flag set. Automatically setting the flag", wip->name);
2164 wip->wi_flags.set(Weapon::Info_Flags::Apply_Recoil);
2165 }
2166 stuff_float(&wip->recoil_modifier);
2167 }
2168
2169 // Energy suck optional stuff (if WIF_ENERGY_SUCK is not set, none of this matters anyway)
2170 if( optional_string("$Leech Weapon:") ){
2171 stuff_float(&wip->weapon_reduce);
2172 }
2173
2174 if( optional_string("$Leech Afterburner:") ){
2175 stuff_float(&wip->afterburner_reduce);
2176 }
2177
2178 if (optional_string("$Corkscrew:"))
2179 {
2180 if(optional_string("+Num Fired:")) {
2181 stuff_int(&wip->cs_num_fired);
2182 }
2183
2184 if(optional_string("+Radius:")) {
2185 stuff_float(&wip->cs_radius);
2186 }
2187
2188 if(optional_string("+Fire Delay:")) {
2189 stuff_int(&wip->cs_delay);
2190 }
2191
2192 if(optional_string("+Counter rotate:")) {
2193 stuff_boolean(&wip->cs_crotate);
2194 }
2195
2196 if(optional_string("+Twist:")) {
2197 stuff_float(&wip->cs_twist);
2198 }
2199 }
2200
2201 //electronics tag optional stuff
2202 //Note that I made all these optional in the interest of modular tables.
2203 //TODO: Possibly add a warning on first_time define?
2204 if (optional_string("$Electronics:"))
2205 {
2206 if(optional_string("+New Style:")) {
2207 wip->elec_use_new_style=1;
2208 }
2209 else if(optional_string("+Old Style:")) {
2210 wip->elec_use_new_style=0;
2211 }
2212
2213 if(optional_string("+Area Of Effect")) {
2214 wip->wi_flags.set(Weapon::Info_Flags::Aoe_Electronics);
2215 }
2216
2217 //New only -WMC
2218 if(optional_string("+Intensity:")) {
2219 float temp;
2220 stuff_float(&temp);
2221 Warning(LOCATION, "+Intensity is deprecated");
2222 }
2223
2224 if(optional_string("+Lifetime:")) {
2225 stuff_int(&wip->elec_time);
2226 }
2227
2228 //New only -WMC
2229 if(optional_string("+Engine Multiplier:")) {
2230 stuff_float(&wip->elec_eng_mult);
2231 if(!wip->elec_use_new_style)Warning(LOCATION, "+Engine multiplier may only be used with new style electronics");
2232 }
2233
2234 //New only -WMC
2235 if(optional_string("+Weapon Multiplier:")) {
2236 stuff_float(&wip->elec_weap_mult);
2237 if(!wip->elec_use_new_style)Warning(LOCATION, "+Weapon multiplier may only be used with new style electronics");
2238 }
2239
2240 //New only -WMC
2241 if(optional_string("+Beam Turret Multiplier:")) {
2242 stuff_float(&wip->elec_beam_mult);
2243 if(!wip->elec_use_new_style)Warning(LOCATION, "+Beam turret multiplier may only be used with new style electronics");
2244 }
2245
2246 //New only -WMC
2247 if(optional_string("+Sensors Multiplier:")) {
2248 stuff_float(&wip->elec_sensors_mult);
2249 if(!wip->elec_use_new_style)Warning(LOCATION, "+Sensors multiplier may only be used with new style electronics");
2250 }
2251
2252 if(optional_string("+Randomness Time:")) {
2253 stuff_int(&wip->elec_randomness);
2254 }
2255 }
2256
2257 //read in the spawn angle info
2258 //if the weapon isn't a spawn weapon, then this is not going to be used.
2259 int spawn_weap = 0;
2260 float dum_float;
2261
2262 while (optional_string("$Spawn Angle:"))
2263 {
2264 stuff_float(&dum_float);
2265
2266 if (spawn_weap < MAX_SPAWN_TYPES_PER_WEAPON)
2267 {
2268 wip->spawn_info[spawn_weap].spawn_angle = dum_float;
2269 spawn_weap++;
2270 }
2271 }
2272
2273 spawn_weap = 0;
2274
2275 while (optional_string("$Spawn Minimum Angle:"))
2276 {
2277 stuff_float(&dum_float);
2278
2279 if (spawn_weap < MAX_SPAWN_TYPES_PER_WEAPON)
2280 {
2281 wip->spawn_info[spawn_weap].spawn_min_angle = dum_float;
2282 spawn_weap++;
2283 }
2284 }
2285
2286 spawn_weap = 0;
2287
2288 while (optional_string("$Spawn Interval:"))
2289 {
2290 float temp_interval;
2291 stuff_float(&temp_interval);
2292
2293 if (spawn_weap < MAX_SPAWN_TYPES_PER_WEAPON)
2294 {
2295 // Get delay out of the way before handling interval so we can do adjusted lifetime
2296 if (optional_string("+Delay:")) {
2297 stuff_float(&wip->spawn_info[spawn_weap].spawn_interval_delay);
2298 }
2299
2300 float adjusted_lifetime = wip->lifetime - wip->spawn_info[spawn_weap].spawn_interval_delay;
2301
2302 if (temp_interval >= MINIMUM_SPAWN_INTERVAL) {
2303 wip->spawn_info[spawn_weap].spawn_interval = temp_interval;
2304 wip->maximum_children_spawned +=
2305 (int)(wip->spawn_info[spawn_weap].spawn_count *
2306 (adjusted_lifetime / wip->spawn_info[spawn_weap].spawn_interval));
2307 }
2308 else if (temp_interval > 0.f) {
2309 // only warn if they tried to put it in between 0 and minimum, to make it clear that it won't be used despite being positive
2310 Warning(LOCATION, "Weapon \'%s\' spawn interval must be greater than %1.1f seconds.\n", wip->name, MINIMUM_SPAWN_INTERVAL);
2311 }
2312 spawn_weap++;
2313 }
2314 }
2315
2316 spawn_weap = 0;
2317
2318 while (optional_string("$Spawn Chance:"))
2319 {
2320 stuff_float(&dum_float);
2321
2322 if (spawn_weap < MAX_SPAWN_TYPES_PER_WEAPON)
2323 {
2324 if (dum_float < 0.f || dum_float > 1.f) {
2325 Warning(LOCATION, "Weapon \'%s\' spawn chance is invalid. Only 0 - 1 is valid.\n", wip->name);
2326 }
2327 else
2328 wip->spawn_info[spawn_weap].spawn_chance = dum_float;
2329 spawn_weap++;
2330 }
2331 }
2332
2333 spawn_weap = 0;
2334
2335 while (optional_string("$Spawn Effect:"))
2336 {
2337 if (spawn_weap < MAX_SPAWN_TYPES_PER_WEAPON)
2338 {
2339 wip->spawn_info[spawn_weap].spawn_effect = particle::util::parseEffect(wip->name);
2340 spawn_weap++;
2341 }
2342 }
2343
2344 if (wip->wi_flags[Weapon::Info_Flags::Local_ssm] && optional_string("$Local SSM:"))
2345 {
2346 if(optional_string("+Warpout Delay:")) {
2347 stuff_int(&wip->lssm_warpout_delay);
2348 }
2349
2350 if(optional_string("+Warpin Delay:")) {
2351 stuff_int(&wip->lssm_warpin_delay);
2352 }
2353
2354 if(optional_string("+Stage 5 Velocity:")) {
2355 stuff_float(&wip->lssm_stage5_vel);
2356 }
2357
2358 if(optional_string("+Warpin Radius:")) {
2359 stuff_float(&wip->lssm_warpin_radius);
2360 }
2361
2362 if (optional_string("+Lock Range:")) {
2363 stuff_float(&wip->lssm_lock_range);
2364 }
2365
2366 if (optional_string("+Warp Effect:")) {
2367 int temp;
2368 if (stuff_int_optional(&temp) != 2) {
2369 // We have a string to parse instead.
2370 char unique_id[NAME_LENGTH];
2371 memset(unique_id, 0, NAME_LENGTH);
2372 stuff_string(unique_id, F_NAME, NAME_LENGTH);
2373 int fireball_type = fireball_info_lookup(unique_id);
2374 if (fireball_type < 0) {
2375 error_display(0, "Unknown fireball [%s] to use as warp effect for LSSM weapon [%s].", unique_id, wip->name);
2376 wip->lssm_warpeffect = FIREBALL_WARP;
2377 }
2378 else {
2379 wip->lssm_warpeffect = fireball_type;
2380 }
2381 }
2382 else {
2383 if ((temp < 0) || (temp >= Num_fireball_types)) {
2384 error_display(0, "Fireball index [%d] out of range (should be 0-%d) for LSSM weapon [%s].", temp, Num_fireball_types - 1, wip->name);
2385 wip->lssm_warpeffect = FIREBALL_WARP;
2386 }
2387 else {
2388 wip->lssm_warpeffect = temp;
2389 }
2390 }
2391 }
2392 }
2393
2394 if (optional_string("$Countermeasure:"))
2395 {
2396 if (!(wip->wi_flags[Weapon::Info_Flags::Cmeasure]))
2397 {
2398 Warning(LOCATION,"Weapon \'%s\' has countermeasure information defined, but the \"countermeasure\" flag wasn\'t found in the \'$Flags:\' field.\n", wip->name);
2399 }
2400
2401 if (optional_string("+Heat Effectiveness:"))
2402 stuff_float(&wip->cm_heat_effectiveness);
2403
2404 if (optional_string("+Aspect Effectiveness:"))
2405 stuff_float(&wip->cm_aspect_effectiveness);
2406
2407 if (optional_string("+Effective Radius:"))
2408 stuff_float(&wip->cm_effective_rad);
2409
2410 if (optional_string("+Missile Detonation Radius:"))
2411 stuff_float(&wip->cm_detonation_rad);
2412
2413 if (optional_string("+Single Missile Kill:"))
2414 stuff_boolean(&wip->cm_kill_single);
2415
2416 if (optional_string("+Pulse Interval:")) {
2417 stuff_int(&wip->cmeasure_timer_interval);
2418 }
2419
2420 if (optional_string("+Use Fire Wait:")) {
2421 wip->cmeasure_firewait = (int) (wip->fire_wait*1000.0f);
2422 }
2423
2424 if (optional_string("+Failure Launch Delay Multiplier for AI:")) {
2425 int delay_multiplier;
2426 stuff_int(&delay_multiplier);
2427 if (delay_multiplier > 0) {
2428 wip->cmeasure_failure_delay_multiplier_ai = delay_multiplier;
2429 } else {
2430 Warning(LOCATION,"\"+Failure Launch Delay Multiplier for AI:\" should be >= 0 (read %i). Value will not be used. ", delay_multiplier);
2431 }
2432 }
2433
2434 if (optional_string("+Successful Launch Delay Multiplier for AI:")) {
2435 int delay_multiplier;
2436 stuff_int(&delay_multiplier);
2437 if (delay_multiplier > 0) {
2438 wip->cmeasure_sucess_delay_multiplier_ai = delay_multiplier;
2439 } else {
2440 Warning(LOCATION, "\"+Successful Launch Delay Multiplier for AI:\" should be >= 0 (read %i). Value will not be used. ", delay_multiplier);
2441 }
2442 }
2443
2444 }
2445
2446 // beam weapon optional stuff
2447 if ( optional_string("$BeamInfo:") ) {
2448 // beam type
2449 if(optional_string("+Type:")) {
2450 stuff_int(&wip->b_info.beam_type);
2451 }
2452
2453 // how long it lasts
2454 if(optional_string("+Life:")) {
2455 stuff_float(&wip->b_info.beam_life);
2456 }
2457
2458 // warmup time
2459 if(optional_string("+Warmup:")) {
2460 stuff_int(&wip->b_info.beam_warmup);
2461 }
2462
2463 // warmdowm time
2464 if(optional_string("+Warmdown:")) {
2465 stuff_int(&wip->b_info.beam_warmdown);
2466 }
2467
2468 // muzzle glow radius
2469 if(optional_string("+Radius:")) {
2470 stuff_float(&wip->b_info.beam_muzzle_radius);
2471 }
2472
2473 // particle spew count
2474 if(optional_string("+PCount:")) {
2475 stuff_int(&wip->b_info.beam_particle_count);
2476 }
2477
2478 // particle radius
2479 if(optional_string("+PRadius:")) {
2480 stuff_float(&wip->b_info.beam_particle_radius);
2481 }
2482
2483 // angle off turret normal
2484 if(optional_string("+PAngle:")) {
2485 stuff_float(&wip->b_info.beam_particle_angle);
2486 }
2487
2488 // particle bitmap/ani
2489 if ( optional_string("+PAni:") ) {
2490 stuff_string(fname, F_NAME, NAME_LENGTH);
2491 generic_anim_init(&wip->b_info.beam_particle_ani, fname);
2492 }
2493
2494 // magic miss #
2495 if(optional_string("+Miss Factor:")) {
2496 for(idx=0; idx<NUM_SKILL_LEVELS; idx++) {
2497 float temp;
2498 if(!stuff_float_optional(&temp)) {
2499 break;
2500 }
2501 // an unspecified Miss Factor should apply to all IFFs
2502 for(iff=0; iff<Num_iffs; iff++) {
2503 wip->b_info.beam_iff_miss_factor[iff][idx] = temp;
2504 }
2505 }
2506 }
2507 // now check miss factors for each IFF
2508 for(iff=0; iff<Num_iffs; iff++) {
2509 SCP_string miss_factor_string;
2510 sprintf(miss_factor_string, "+%s Miss Factor:", Iff_info[iff].iff_name);
2511 if(optional_string(miss_factor_string.c_str())) {
2512 // this Miss Factor applies only to the specified IFF
2513 for(idx=0; idx<NUM_SKILL_LEVELS; idx++) {
2514 if(!stuff_float_optional(&wip->b_info.beam_iff_miss_factor[iff][idx])) {
2515 break;
2516 }
2517 }
2518 }
2519 }
2520
2521 // beam fire sound
2522 parse_game_sound("+BeamSound:", &wip->b_info.beam_loop_sound);
2523
2524 // warmup sound
2525 parse_game_sound("+WarmupSound:", &wip->b_info.beam_warmup_sound);
2526
2527 // warmdown sound
2528 parse_game_sound("+WarmdownSound:", &wip->b_info.beam_warmdown_sound);
2529
2530 // glow bitmap
2531 if (optional_string("+Muzzleglow:") ) {
2532 stuff_string(fname, F_NAME, NAME_LENGTH);
2533 generic_anim_init(&wip->b_info.beam_glow, fname);
2534 }
2535
2536 if (optional_string("+Directional Glow:")) {
2537 stuff_float(&wip->b_info.glow_length);
2538 wip->b_info.directional_glow = true;
2539 }
2540
2541 // # of shots (only used for type D beams)
2542 if(optional_string("+Shots:")) {
2543 stuff_int(&wip->b_info.beam_shots);
2544 }
2545
2546 // make sure that we have at least one shot so that TYPE_D beams will work
2547 if ( (wip->b_info.beam_type == BEAM_TYPE_D) && (wip->b_info.beam_shots < 1) ) {
2548 Warning( LOCATION, "Type D beam weapon, '%s', has less than one \"+Shots\" specified! It must be set to at least 1!!", wip->name);
2549 wip->b_info.beam_shots = 1;
2550 }
2551
2552 // shrinkage
2553 if(optional_string("+ShrinkFactor:")) {
2554 stuff_float(&wip->b_info.beam_shrink_factor);
2555 if (wip->b_info.beam_shrink_factor > 1.f || wip->b_info.beam_shrink_factor < 0.f) {
2556 Warning(LOCATION, "Beam '%s' ShrinkFactor must be between 0 and 1", wip->name);
2557 CLAMP(wip->b_info.beam_shrink_factor, 0.f, 1.f);
2558 }
2559 }
2560
2561 if(optional_string("+ShrinkPct:")) {
2562 stuff_float(&wip->b_info.beam_shrink_pct);
2563 if (wip->b_info.beam_shrink_pct < 0.f) {
2564 Warning(LOCATION, "Beam '%s' ShrinkPct must be positive.", wip->name);
2565 wip->b_info.beam_shrink_pct = 0.f;
2566 }
2567 }
2568
2569 // grow.. age?
2570 if (optional_string("+GrowFactor:")) {
2571 stuff_float(&wip->b_info.beam_grow_factor);
2572 if (wip->b_info.beam_grow_factor > 1.f || wip->b_info.beam_grow_factor < 0.f) {
2573 Warning(LOCATION, "Beam '%s' GrowFactor must be between 0 and 1", wip->name);
2574 CLAMP(wip->b_info.beam_grow_factor, 0.f, 1.f);
2575 }
2576 }
2577
2578 if (optional_string("+GrowPct:")) {
2579 stuff_float(&wip->b_info.beam_grow_pct);
2580 if (wip->b_info.beam_grow_pct < 0.f) {
2581 Warning(LOCATION, "Beam '%s' GrowPct must be positive.", wip->name);
2582 wip->b_info.beam_grow_pct = 0.f;
2583 }
2584 }
2585
2586 if (optional_string("+Initial Width Factor:")) {
2587 stuff_float(&wip->b_info.beam_initial_width);
2588 if (wip->b_info.beam_initial_width > 1.f || wip->b_info.beam_initial_width < 0.f) {
2589 Warning(LOCATION, "Beam '%s' Initial Width Factor must be between 0 and 1", wip->name);
2590 CLAMP(wip->b_info.beam_initial_width, 0.f, 1.f);
2591 }
2592 }
2593
2594 if (optional_string("+Range:")) {
2595 stuff_float(&wip->b_info.range);
2596 }
2597
2598 if ( optional_string("+Attenuation:") )
2599 stuff_float(&wip->b_info.damage_threshold);
2600
2601 if ( optional_string("+BeamWidth:") )
2602 stuff_float(&wip->b_info.beam_width);
2603
2604 if (optional_string("+Beam Flash Particle Effect:")) {
2605 wip->flash_impact_weapon_expl_effect = particle::util::parseEffect(wip->name);
2606 } else {
2607 // This is for compatibility with old tables
2608 // Do not add features here!
2609
2610 int bitmapIndex = -1;
2611 float size = 0.0f;
2612 bool defaultEffect = false;
2613
2614 if (optional_string("+Beam Flash Effect:")) {
2615 stuff_string(fname, F_NAME, NAME_LENGTH);
2616
2617 if (VALID_FNAME(fname))
2618 {
2619 bitmapIndex = bm_load_animation(fname);
2620
2621 if (bitmapIndex < 0)
2622 {
2623 Warning(LOCATION, "Failed to load effect '%s' for weapon '%s'!", fname, wip->name);
2624 }
2625 }
2626 }
2627
2628 if (bitmapIndex < 0)
2629 {
2630 // Default to the smoke particle effect
2631 bitmapIndex = bm_load_animation("particlesmoke01");
2632 defaultEffect = true;
2633 }
2634
2635 if (optional_string("+Beam Flash Radius:"))
2636 stuff_float(&size);
2637
2638 if (bitmapIndex >= 0 && size > 0.0f)
2639 {
2640 using namespace particle;
2641
2642 if (defaultEffect)
2643 {
2644 // 'size * 1.5f * 0.005f' is another weird thing, the original code scales the lifetime of the flash particles based on size
2645 // so the new effects have to simulate that, but that onyl applies to the default effect, not a custom effect
2646 // seriously, who though that would be a good idea?
2647 auto singleEffect = effects::SingleParticleEffect::createInstance(bitmapIndex, size * 1.2f, size * 1.9f, size * 1.5f * 0.005f);
2648 wip->flash_impact_weapon_expl_effect = ParticleManager::get()->
2649 addEffect(singleEffect);
2650 }
2651 else
2652 {
2653 auto singleEffect = effects::SingleParticleEffect::createInstance(bitmapIndex, size * 1.2f, size * 1.9f);
2654 wip->flash_impact_weapon_expl_effect = ParticleManager::get()->
2655 addEffect(singleEffect);
2656 }
2657 }
2658 }
2659
2660 if (optional_string("+Beam Piercing Particle Effect:")) {
2661 wip->piercing_impact_effect = particle::util::parseEffect(wip->name);
2662 } else {
2663 // This is for compatibility with old tables
2664 // Do not add features here!
2665
2666 int bitmapIndex = -1;
2667 float radius = 0.0f;
2668 float velocity = 0.0f;
2669 float back_velocity = 0.0f;
2670 float variance = 0.0f;
2671
2672 if (optional_string("+Beam Piercing Effect:"))
2673 {
2674 stuff_string(fname, F_NAME, NAME_LENGTH);
2675
2676 if (VALID_FNAME(fname))
2677 {
2678 bitmapIndex = bm_load_animation(fname);
2679
2680 if (bitmapIndex < 0)
2681 {
2682 Warning(LOCATION, "Failed to load effect '%s' for weapon %s!", fname, wip->name);
2683 }
2684 }
2685 }
2686
2687 if (optional_string("+Beam Piercing Radius:"))
2688 stuff_float(&radius);
2689
2690 if (optional_string("+Beam Piercing Effect Velocity:"))
2691 stuff_float(&velocity);
2692
2693 if (optional_string("+Beam Piercing Splash Effect Velocity:"))
2694 stuff_float(&back_velocity);
2695
2696 if (optional_string("+Beam Piercing Effect Variance:"))
2697 stuff_float(&variance);
2698
2699 if (bitmapIndex >= 0 && radius > 0.0f)
2700 {
2701 using namespace particle;
2702 using namespace effects;
2703
2704 auto piercingEffect = new BeamPiercingEffect();
2705 piercingEffect->setValues(bitmapIndex, radius, velocity, back_velocity, variance);
2706
2707 wip->piercing_impact_effect = ParticleManager::get()->
2708 addEffect(piercingEffect);
2709 }
2710 }
2711
2712 if (optional_string("$Type 5 Beam Options:")) {
2713
2714 char temp_type[NAME_LENGTH];
2715 type5_beam_info* t5info = &wip->b_info.t5info;
2716
2717 if (optional_string("+Start Position:")) {
2718 stuff_string(temp_type, F_NAME, NAME_LENGTH);
2719 if (!stricmp(temp_type, NOX("RANDOM ON SHIP"))) {
2720 t5info->start_pos = Type5BeamPos::RANDOM_INSIDE;
2721 }
2722 else if (!stricmp(temp_type, NOX("RANDOM OFF SHIP"))) {
2723 t5info->start_pos = Type5BeamPos::RANDOM_OUTSIDE;
2724 }
2725 else if (!stricmp(temp_type, NOX("CENTER"))) {
2726 t5info->start_pos = Type5BeamPos::CENTER;
2727 }
2728 else if (!stricmp(temp_type, NOX("SAME RANDOM"))) {
2729 Warning(LOCATION, "'SAME RANDOM' is not applicable for start position on beam %s!", wip->name);
2730 }
2731 else {
2732 Warning(LOCATION, "Invalid start position on beam %s!\n Options are: 'RANDOM ON SHIP', 'RANDOM OFF SHIP', 'CENTER''", wip->name);
2733 }
2734 }
2735
2736 if (optional_string("+Start Position Offset:")) {
2737 stuff_vec3d(&t5info->start_pos_offset);
2738 }
2739
2740 if (optional_string("+Start Position Randomness:")) {
2741 stuff_vec3d(&t5info->start_pos_rand);
2742 }
2743
2744 if (optional_string("+End Position:")) {
2745 stuff_string(temp_type, F_NAME, NAME_LENGTH);
2746 if (!stricmp(temp_type, NOX("RANDOM ON SHIP"))) {
2747 t5info->end_pos = Type5BeamPos::RANDOM_INSIDE;
2748 t5info->no_translate = false;
2749 }
2750 else if (!stricmp(temp_type, NOX("RANDOM OFF SHIP"))) {
2751 t5info->end_pos = Type5BeamPos::RANDOM_OUTSIDE;
2752 t5info->no_translate = false;
2753 }
2754 else if (!stricmp(temp_type, NOX("CENTER"))) {
2755 t5info->end_pos = Type5BeamPos::CENTER;
2756 t5info->no_translate = false;
2757 }
2758 else if (!stricmp(temp_type, NOX("SAME RANDOM"))) {
2759 t5info->end_pos = Type5BeamPos::SAME_RANDOM;
2760 // offset could still be different so this counts as translating
2761 t5info->no_translate = false;
2762 }
2763 else {
2764 Warning(LOCATION, "Invalid end position on beam %s!\n Options are: 'RANDOM ON SHIP', 'RANDOM OFF SHIP', 'CENTER', 'SAME RANDOM'", wip->name);
2765 }
2766 }
2767
2768 if (optional_string("+End Position Offset:")) {
2769 stuff_vec3d(&t5info->end_pos_offset);
2770 }
2771
2772 if (optional_string("+End Position Randomness:")) {
2773 stuff_vec3d(&t5info->end_pos_rand);
2774 }
2775
2776 if (optional_string("+Orient Offsets to Target:")) {
2777 stuff_boolean(&t5info->target_orient_positions);
2778 }
2779
2780 if (optional_string("+Scale Offsets to Target:")) {
2781 stuff_boolean(&t5info->target_scale_positions);
2782 }
2783
2784 if (optional_string("+Continuous Rotation:")) {
2785 stuff_float(&t5info->continuous_rot);
2786 t5info->continuous_rot *= (PI / 180.f);
2787 }
2788
2789 if (optional_string("+Continuous Rotation Axis:")) {
2790 stuff_string(temp_type, F_NAME, NAME_LENGTH);
2791 if (!stricmp(temp_type, NOX("CENTER"))) {
2792 t5info->continuous_rot_axis = Type5BeamRotAxis::CENTER;
2793 }
2794 else if (!stricmp(temp_type, NOX("END POSITION BEFORE OFFSET"))) {
2795 t5info->continuous_rot_axis = Type5BeamRotAxis::ENDPOS_NO_OFFSET;
2796 }
2797 else if (!stricmp(temp_type, NOX("START POSITION BEFORE OFFSET"))) {
2798 t5info->continuous_rot_axis = Type5BeamRotAxis::STARTPOS_NO_OFFSET;
2799 }
2800 else if (!stricmp(temp_type, NOX("END POSITION AFTER OFFSET"))) {
2801 t5info->continuous_rot_axis = Type5BeamRotAxis::ENDPOS_OFFSET;
2802 }
2803 else if (!stricmp(temp_type, NOX("START POSITION AFTER OFFSET"))) {
2804 t5info->continuous_rot_axis = Type5BeamRotAxis::STARTPOS_OFFSET;
2805 }
2806 else {
2807 Warning(LOCATION, "Invalid continuous rotation axis on beam %s!\n Options are: 'CENTER', 'END POSITION BEFORE OFFSET', 'START POSITION BEFORE OFFSET', 'END POSITION AFTER OFFSET', 'START POSITION AFTER OFFSET'", wip->name);
2808 }
2809 }
2810
2811 if (optional_string("+Burst Rotation Pattern:")) {
2812 stuff_float_list(t5info->burst_rot_pattern);
2813 for (float &rot : t5info->burst_rot_pattern) {
2814 rot = fl_radians(rot);
2815 }
2816 }
2817
2818 if (optional_string("+Burst Rotation Axis:")) {
2819 stuff_string(temp_type, F_NAME, NAME_LENGTH);
2820 if (!stricmp(temp_type, NOX("CENTER"))) {
2821 t5info->burst_rot_axis = Type5BeamRotAxis::CENTER;
2822 }
2823 else if (!stricmp(temp_type, NOX("END POSITION BEFORE OFFSET"))) {
2824 t5info->burst_rot_axis = Type5BeamRotAxis::ENDPOS_NO_OFFSET;
2825 }
2826 else if (!stricmp(temp_type, NOX("START POSITION BEFORE OFFSET"))) {
2827 t5info->burst_rot_axis = Type5BeamRotAxis::STARTPOS_NO_OFFSET;
2828 }
2829 else if (!stricmp(temp_type, NOX("END POSITION AFTER OFFSET"))) {
2830 t5info->burst_rot_axis = Type5BeamRotAxis::ENDPOS_OFFSET;
2831 }
2832 else if (!stricmp(temp_type, NOX("START POSITION AFTER OFFSET"))) {
2833 t5info->burst_rot_axis = Type5BeamRotAxis::STARTPOS_OFFSET;
2834 }
2835 else {
2836 Warning(LOCATION, "Invalid burst rotation axis on beam %s!\n Options are: 'CENTER', 'END POSITION BEFORE OFFSET', 'START POSITION BEFORE OFFSET', 'END POSITION AFTER OFFSET', 'START POSITION AFTER OFFSET'", wip->name);
2837 }
2838 }
2839
2840 if (optional_string("+Per Burst Rotation:")) {
2841 stuff_float(&t5info->per_burst_rot);
2842 t5info->per_burst_rot = fl_radians(t5info->per_burst_rot);
2843 if (t5info->per_burst_rot < -PI2 || t5info->per_burst_rot > PI2) {
2844 Warning(LOCATION, "Per Burst Rotation on beam '%s' must not exceed 360 degrees.", wip->name);
2845 t5info->per_burst_rot = 0.0f;
2846 }
2847 }
2848
2849 if (optional_string("+Per Burst Rotation Axis:")) {
2850 stuff_string(temp_type, F_NAME, NAME_LENGTH);
2851 if (!stricmp(temp_type, NOX("CENTER"))) {
2852 t5info->per_burst_rot_axis = Type5BeamRotAxis::CENTER;
2853 }
2854 else if (!stricmp(temp_type, NOX("END POSITION BEFORE OFFSET"))) {
2855 t5info->per_burst_rot_axis = Type5BeamRotAxis::ENDPOS_NO_OFFSET;
2856 }
2857 else if (!stricmp(temp_type, NOX("START POSITION BEFORE OFFSET"))) {
2858 t5info->per_burst_rot_axis = Type5BeamRotAxis::STARTPOS_NO_OFFSET;
2859 }
2860 else if (!stricmp(temp_type, NOX("END POSITION AFTER OFFSET"))) {
2861 t5info->per_burst_rot_axis = Type5BeamRotAxis::ENDPOS_OFFSET;
2862 }
2863 else if (!stricmp(temp_type, NOX("START POSITION AFTER OFFSET"))) {
2864 t5info->per_burst_rot_axis = Type5BeamRotAxis::STARTPOS_OFFSET;
2865 }
2866 else {
2867 Warning(LOCATION, "Invalid per burst rotation axis on beam %s!\n Options are: 'CENTER', 'END POSITION BEFORE OFFSET', 'START POSITION BEFORE OFFSET', 'END POSITION AFTER OFFSET', 'START POSITION AFTER OFFSET'", wip->name);
2868 }
2869 }
2870 }
2871
2872 if (optional_string("+Beam Flags:")) {
2873 parse_string_flag_list(wip->b_info.flags, Beam_info_flags, Num_beam_info_flags, nullptr);
2874 }
2875
2876 // beam sections
2877 while ( optional_string("$Section:") ) {
2878 beam_weapon_section_info *bsip = NULL, tbsw;
2879 bool nocreate = false, remove = false;
2880 int bsw_index_override = -1;
2881
2882 if ( optional_string("+Index:") ) {
2883 stuff_int(&bsw_index_override);
2884 if (first_time) {
2885 Warning(LOCATION, "+Index should not be used on weapon '%s' in its initial definition. +Index is only for modification of an existing weapon.", wip->name);
2886 bsw_index_override = -1;
2887 } else {
2888
2889 if (optional_string("+remove")) {
2890 nocreate = true;
2891 remove = true;
2892 }
2893
2894 if ((bsw_index_override < 0) || (!remove && (bsw_index_override >= wip->b_info.beam_num_sections)))
2895 Warning(LOCATION, "Invalid +Index value of %d specified for beam section on weapon '%s'; valid values at this point are %d to %d.", bsw_index_override, wip->name, 0, wip->b_info.beam_num_sections - 1);
2896 }
2897 }
2898
2899 if ( optional_string("+nocreate") )
2900 nocreate = true;
2901
2902 // Where are we saving data?
2903 if (bsw_index_override >= 0) {
2904 if (bsw_index_override < wip->b_info.beam_num_sections) {
2905 bsip = &wip->b_info.sections[bsw_index_override];
2906 } else {
2907 if ( !nocreate ) {
2908 if ( (bsw_index_override == wip->b_info.beam_num_sections) && (bsw_index_override < MAX_BEAM_SECTIONS) ) {
2909 bsip = &wip->b_info.sections[wip->b_info.beam_num_sections++];
2910 } else {
2911 if ( !remove )
2912 Warning(LOCATION, "Invalid index for manually-indexed beam section %d (max %d) on weapon %s.", bsw_index_override, MAX_BEAM_SECTIONS, wip->name);
2913
2914 bsip = &tbsw;
2915 memset( bsip, 0, sizeof(beam_weapon_section_info) );
2916 generic_anim_init(&bsip->texture, NULL);
2917 }
2918 } else {
2919 if ( !remove )
2920 Warning(LOCATION, "Invalid index for manually-indexed beam section %d, and +nocreate specified, on weapon %s", bsw_index_override, wip->name);
2921
2922 bsip = &tbsw;
2923 memset( bsip, 0, sizeof(beam_weapon_section_info) );
2924 generic_anim_init(&bsip->texture, NULL);
2925 }
2926
2927 }
2928 } else {
2929 if (wip->b_info.beam_num_sections < MAX_BEAM_SECTIONS) {
2930 bsip = &wip->b_info.sections[wip->b_info.beam_num_sections++];
2931 generic_anim_init(&bsip->texture, NULL);
2932 } else {
2933 Warning(LOCATION, "Too many beam sections for weapon %s - max is %d", wip->name, MAX_BEAM_SECTIONS);
2934 bsip = &tbsw;
2935 memset( bsip, 0, sizeof(beam_weapon_section_info) );
2936 generic_anim_init(&bsip->texture, NULL);
2937 }
2938 }
2939
2940 // section width
2941 if ( optional_string("+Width:") )
2942 stuff_float(&bsip->width);
2943
2944 // texture
2945 if ( optional_string("+Texture:") ) {
2946 stuff_string(fname, F_NAME, NAME_LENGTH);
2947
2948 // invisible textures are okay - see weapon_clean_entries()
2949 generic_anim_init(&bsip->texture, fname);
2950 }
2951
2952 // The E -- Dummied out due to not being used anywhere
2953 if ( optional_string("+RGBA Inner:") ) {
2954 ubyte dummy;
2955 stuff_ubyte(&dummy);
2956 stuff_ubyte(&dummy);
2957 stuff_ubyte(&dummy);
2958 stuff_ubyte(&dummy);
2959 }
2960
2961 // The E -- Dummied out due to not being used anywhere
2962 if ( optional_string("+RGBA Outer:") ) {
2963 ubyte dummy;
2964 stuff_ubyte(&dummy);
2965 stuff_ubyte(&dummy);
2966 stuff_ubyte(&dummy);
2967 stuff_ubyte(&dummy);
2968 }
2969
2970 // flicker
2971 if ( optional_string("+Flicker:") ) {
2972 stuff_float(&bsip->flicker);
2973 //Sanity
2974 if (bsip->flicker < 0.0f || bsip->flicker > 1.0f) {
2975 mprintf(("WARNING: Invalid value found for +Flicker on section %d of beam %s. Valid range is 0.0 to 1.0, values will be adjusted.\n", wip->b_info.beam_num_sections, wip->name));
2976 CLAMP(bsip->flicker, 0.0f, 1.0f);
2977 }
2978 }
2979
2980 // zadd
2981 if ( optional_string("+Zadd:") )
2982 stuff_float(&bsip->z_add);
2983
2984 // beam texture tileing factor -Bobboau
2985 if ( optional_string("+Tile Factor:") ) {
2986 stuff_float(&bsip->tile_factor);
2987 stuff_int(&bsip->tile_type);
2988 }
2989
2990 // beam texture moveing stuff -Bobboau
2991 if ( optional_string("+Translation:") )
2992 stuff_float(&bsip->translation);
2993
2994 // if we are actually removing this index then reset it and we'll
2995 // clean up the entries later
2996 if (remove) {
2997 memset( bsip, 0, sizeof(beam_weapon_section_info) );
2998 generic_anim_init(&bsip->texture, NULL);
2999 }
3000 }
3001 }
3002
3003 while ( optional_string("$Pspew:") ) {
3004 int spew_index = -1;
3005 // check for pspew flag
3006 if (!( wip->wi_flags[Weapon::Info_Flags::Particle_spew] )) {
3007 Warning(LOCATION, "$Pspew specified for weapon %s but this weapon does not have the \"Particle Spew\" weapon flag set. Automatically setting the flag", wip->name);
3008 wip->wi_flags.set(Weapon::Info_Flags::Particle_spew);
3009 }
3010 // index for xmt edit, replace and remove support
3011 if (optional_string("+Index:")) {
3012 stuff_int(&spew_index);
3013 if (spew_index < 0 || spew_index >= MAX_PARTICLE_SPEWERS) {
3014 Warning(LOCATION, "+Index in particle spewer out of range. It must be between 0 and %i. Tag will be ignored.", MAX_PARTICLE_SPEWERS);
3015 spew_index = -1;
3016 }
3017 }
3018 // check for remove flag
3019 if (optional_string("+Remove")) {
3020 if (spew_index < 0) {
3021 Warning(LOCATION, "+Index not specified or is out of range, can not remove spewer.");
3022 } else { // restore defaults
3023 wip->particle_spewers[spew_index].particle_spew_type = PSPEW_NONE;
3024 wip->particle_spewers[spew_index].particle_spew_count = 1;
3025 wip->particle_spewers[spew_index].particle_spew_time = 25;
3026 wip->particle_spewers[spew_index].particle_spew_vel = 0.4f;
3027 wip->particle_spewers[spew_index].particle_spew_radius = 2.0f;
3028 wip->particle_spewers[spew_index].particle_spew_lifetime = 0.15f;
3029 wip->particle_spewers[spew_index].particle_spew_scale = 0.8f;
3030 wip->particle_spewers[spew_index].particle_spew_z_scale = 1.0f;
3031 wip->particle_spewers[spew_index].particle_spew_rotation_rate = 10.0f;
3032 wip->particle_spewers[spew_index].particle_spew_offset = vmd_zero_vector;
3033 wip->particle_spewers[spew_index].particle_spew_velocity = vmd_zero_vector;
3034 generic_anim_init(&wip->particle_spewers[spew_index].particle_spew_anim, NULL);
3035 }
3036 } else { // were not removing the spewer
3037 if (spew_index < 0) { // index us ether not used or is invalid, so figure out where to put things
3038 //find a free slot in the pspew info array
3039 for (size_t s = 0; s < MAX_PARTICLE_SPEWERS; s++) {
3040 if (wip->particle_spewers[s].particle_spew_type == PSPEW_NONE) {
3041 spew_index = (int)s;
3042 break;
3043 }
3044 }
3045 }
3046 // no empty spot found, the modder tried to define too many spewers, or screwed up the xmts, or my code sucks
3047 if ( spew_index < 0 ) {
3048 Warning(LOCATION, "Too many particle spewers, max number of spewers is %i.", MAX_PARTICLE_SPEWERS);
3049 } else { // we have a valid index, now parse the spewer already
3050 if (optional_string("+Type:")) { // added type field for pspew types, 0 is the default for reverse compatability -nuke
3051 char temp_pspew_type[NAME_LENGTH];
3052 stuff_string(temp_pspew_type, F_NAME, NAME_LENGTH);
3053
3054 if (!stricmp(temp_pspew_type, NOX("DEFAULT"))) {
3055 wip->particle_spewers[spew_index].particle_spew_type = PSPEW_DEFAULT;
3056 } else if (!stricmp(temp_pspew_type, NOX("HELIX"))) {
3057 wip->particle_spewers[spew_index].particle_spew_type = PSPEW_HELIX;
3058 } else if (!stricmp(temp_pspew_type, NOX("SPARKLER"))) { // new types can be added here
3059 wip->particle_spewers[spew_index].particle_spew_type = PSPEW_SPARKLER;
3060 } else if (!stricmp(temp_pspew_type, NOX("RING"))) {
3061 wip->particle_spewers[spew_index].particle_spew_type = PSPEW_RING;
3062 } else if (!stricmp(temp_pspew_type, NOX("PLUME"))) {
3063 wip->particle_spewers[spew_index].particle_spew_type = PSPEW_PLUME;
3064 } else {
3065 wip->particle_spewers[spew_index].particle_spew_type = PSPEW_DEFAULT;
3066 }
3067 // for compatibility with existing tables that don't have a type tag
3068 } else if (wip->particle_spewers[spew_index].particle_spew_type == PSPEW_NONE) { // make sure the omission of type wasn't to edit an existing entry
3069 wip->particle_spewers[spew_index].particle_spew_type = PSPEW_DEFAULT;
3070 }
3071
3072 if (optional_string("+Count:")) {
3073 stuff_int(&wip->particle_spewers[spew_index].particle_spew_count);
3074 }
3075
3076 if (optional_string("+Time:")) {
3077 stuff_int(&wip->particle_spewers[spew_index].particle_spew_time);
3078 }
3079
3080 if (optional_string("+Vel:")) {
3081 stuff_float(&wip->particle_spewers[spew_index].particle_spew_vel);
3082 }
3083
3084 if (optional_string("+Radius:")) {
3085 stuff_float(&wip->particle_spewers[spew_index].particle_spew_radius);
3086 }
3087
3088 if (optional_string("+Life:")) {
3089 stuff_float(&wip->particle_spewers[spew_index].particle_spew_lifetime);
3090 }
3091
3092 if (optional_string("+Scale:")) {
3093 stuff_float(&wip->particle_spewers[spew_index].particle_spew_scale);
3094 }
3095
3096 if (optional_string("+Z Scale:")) {
3097 stuff_float(&wip->particle_spewers[spew_index].particle_spew_z_scale);
3098 }
3099
3100 if (optional_string("+Rotation Rate:")) {
3101 stuff_float(&wip->particle_spewers[spew_index].particle_spew_rotation_rate);
3102 }
3103
3104 if (optional_string("+Offset:")) {
3105 stuff_vec3d(&wip->particle_spewers[spew_index].particle_spew_offset);
3106 }
3107
3108 if (optional_string("+Initial Velocity:")) {
3109 stuff_vec3d(&wip->particle_spewers[spew_index].particle_spew_velocity);
3110 }
3111
3112 if (optional_string("+Bitmap:")) {
3113 stuff_string(fname, F_NAME, MAX_FILENAME_LEN);
3114 generic_anim_init(&wip->particle_spewers[spew_index].particle_spew_anim, fname);
3115 }
3116 }
3117 }
3118 }
3119 // check to see if the pspew flag was enabled but no pspew tags were given, for compatability with retail tables
3120 if (wip->wi_flags[Weapon::Info_Flags::Particle_spew]) {
3121 bool nospew = true;
3122 for (size_t s = 0; s < MAX_PARTICLE_SPEWERS; s++)
3123 if (wip->particle_spewers[s].particle_spew_type != PSPEW_NONE) {
3124 nospew = false;
3125 }
3126 if (nospew) { // set first spewer to default
3127 wip->particle_spewers[0].particle_spew_type = PSPEW_DEFAULT;
3128 }
3129 }
3130
3131 // tag weapon optional stuff
3132 if( optional_string("$Tag:")){
3133 stuff_int(&wip->tag_level);
3134 stuff_float(&wip->tag_time);
3135 wip->wi_flags.set(Weapon::Info_Flags::Tag);
3136 }
3137
3138 if( optional_string("$SSM:")){
3139 if (stuff_int_optional(&wip->SSM_index) != 2) {
3140 // We can't make an SSM lookup yet, because weapons are parsed first, but we can save the data to process later. -MageKing17
3141 stuff_string(fname, F_NAME, NAME_LENGTH);
3142 delayed_ssm_data temp_data;
3143 temp_data.filename = filename;
3144 temp_data.linenum = get_line_num();
3145 temp_data.ssm_entry = fname;
3146 if (Delayed_SSM_data.find(wip->name) == Delayed_SSM_data.end())
3147 Delayed_SSM_names.push_back(wip->name);
3148 Delayed_SSM_data[wip->name] = temp_data;
3149 } else {
3150 // We'll still want to validate the index later. -MageKing17
3151 delayed_ssm_index_data temp_data;
3152 temp_data.filename = filename;
3153 temp_data.linenum = get_line_num();
3154 if (Delayed_SSM_indices_data.find(wip->name) == Delayed_SSM_indices_data.end())
3155 Delayed_SSM_indices.push_back(wip->name);
3156 Delayed_SSM_indices_data[wip->name] = temp_data;
3157 }
3158 }// SSM index -Bobboau
3159
3160 if(optional_string("$FOF:")){
3161 stuff_float(&wip->field_of_fire);
3162
3163 if(optional_string("+FOF Spread Rate:")){
3164 stuff_float(&wip->fof_spread_rate);
3165 if(required_string("+FOF Reset Rate:")){
3166 stuff_float(&wip->fof_reset_rate);
3167 }
3168
3169 if(required_string("+Max FOF:")){
3170 float max_fof;
3171 stuff_float(&max_fof);
3172 wip->max_fof_spread = max_fof - wip->field_of_fire;
3173
3174 if (wip->max_fof_spread <= 0.0f) {
3175 Warning(LOCATION, "WARNING: +Max FOF must be at least as big as $FOF for '%s'! Defaulting to match $FOF, no spread will occur!", wip->name);
3176 wip->max_fof_spread = 0.0f;
3177 }
3178 }
3179 }
3180 }
3181
3182
3183 if( optional_string("$Shots:")){
3184 stuff_int(&wip->shots);
3185 }
3186
3187 //Left in for compatibility
3188 if ( optional_string("$decal:") ) {
3189 mprintf(("WARNING: The decal system has been deactivated in FSO builds. Entries for weapon %s will be discarded.\n", wip->name));
3190 required_string("+texture:");
3191 stuff_string(fname, F_NAME, NAME_LENGTH);
3192
3193 if ( optional_string("+backface texture:") ) {
3194 stuff_string(fname, F_NAME, NAME_LENGTH);
3195 }
3196
3197 float bogus;
3198
3199 required_string("+radius:");
3200 stuff_float(&bogus);
3201
3202 if ( optional_string("+burn time:") ) {
3203 stuff_float(&bogus);
3204 }
3205 }
3206
3207 // New decal system parsing
3208 if (optional_string("$Impact Decal:")) {
3209 decals::parseDecalReference(wip->impact_decal, create_if_not_found);
3210 }
3211
3212 if (optional_string("$Transparent:")) {
3213 wip->wi_flags.set(Weapon::Info_Flags::Transparent);
3214
3215 required_string("+Alpha:");
3216 stuff_float(&wip->alpha_max);
3217
3218 if (wip->alpha_max > 1.0f)
3219 wip->alpha_max = 1.0f;
3220
3221 if (wip->alpha_max <= 0.0f) {
3222 Warning(LOCATION, "WARNING: Alpha is set to 0 or a negative value for '%s'! Defaulting to 1.0!", wip->name);
3223 }
3224
3225 if (optional_string("+Alpha Min:")) {
3226 stuff_float(&wip->alpha_min);
3227 CLAMP(wip->alpha_min, 0.0f, 1.0f);
3228 }
3229
3230 if (optional_string("+Alpha Cycle:")) {
3231 stuff_float(&wip->alpha_cycle);
3232
3233 if (wip->alpha_max == wip->alpha_min)
3234 Warning(LOCATION, "WARNING: Alpha is set to cycle for '%s', but max and min values are the same!", wip->name);
3235 }
3236 }
3237
3238 if (optional_string("$Weapon Hitpoints:")) {
3239 stuff_int(&wip->weapon_hitpoints);
3240 } else if (first_time && (wip->wi_flags[Weapon::Info_Flags::Turret_Interceptable, Weapon::Info_Flags::Fighter_Interceptable])) {
3241 wip->weapon_hitpoints = 25;
3242 }
3243
3244 // making sure bombs get their hitpoints assigned
3245 if ((wip->wi_flags[Weapon::Info_Flags::Bomb]) && (wip->weapon_hitpoints == 0)) {
3246 wip->weapon_hitpoints = 50;
3247 }
3248
3249 if (wip->weapon_hitpoints <= 0.0f && (wip->wi_flags[Weapon::Info_Flags::No_radius_doubling])) {
3250 Warning(LOCATION, "Weapon \'%s\' is not interceptable but has \"no radius doubling\" set. Ignoring the flag", wip->name);
3251 wip->wi_flags.set(Weapon::Info_Flags::No_radius_doubling, false);
3252 }
3253
3254 if(optional_string("$Armor Type:")) {
3255 stuff_string(buf, F_NAME, NAME_LENGTH);
3256 wip->armor_type_idx = armor_type_get_idx(buf);
3257
3258 if(wip->armor_type_idx == -1)
3259 Warning(LOCATION,"Invalid armor name %s specified for weapon %s", buf, wip->name);
3260 }
3261
3262 if (optional_string("$Burst Shots:")) {
3263 stuff_int(&wip->burst_shots);
3264 if (wip->burst_shots > 0)
3265 wip->burst_shots--;
3266 }
3267
3268 if (optional_string("$Burst Delay:")) {
3269 int temp;
3270 stuff_int(&temp);
3271 if (temp > 0) {
3272 wip->burst_delay = ((float) temp) / 1000.0f;
3273 }
3274 }
3275
3276 if (optional_string("$Burst Flags:")) {
3277 parse_string_flag_list(wip->burst_flags, Burst_fire_flags, Num_burst_fire_flags, NULL);
3278 }
3279
3280 if (optional_string("$Thruster Flame Effect:")) {
3281 stuff_string(fname, F_NAME, NAME_LENGTH);
3282
3283 if (VALID_FNAME(fname))
3284 generic_anim_init( &wip->thruster_flame, fname );
3285 }
3286
3287 if (optional_string("$Thruster Glow Effect:")) {
3288 stuff_string(fname, F_NAME, NAME_LENGTH);
3289
3290 if (VALID_FNAME(fname))
3291 generic_anim_init( &wip->thruster_glow, fname );
3292 }
3293
3294 if (optional_string("$Thruster Glow Radius Factor:")) {
3295 stuff_float(&wip->thruster_glow_factor);
3296 }
3297
3298 //pretty stupid if a target must be tagged to shoot tag missiles at it
3299 if ((wip->wi_flags[Weapon::Info_Flags::Tag]) && (wip->wi_flags[Weapon::Info_Flags::Tagged_only]))
3300 {
3301 Warning(LOCATION, "%s is a tag missile, but the target must be tagged to shoot it", wip->name);
3302 }
3303
3304 // if burst delay is longer than firewait skip the whole burst fire option
3305 if (wip->burst_delay >= wip->fire_wait)
3306 wip->burst_shots = 0;
3307
3308 // Set up weapon failure
3309 if (optional_string("$Failure Rate:")) {
3310 stuff_float(&wip->failure_rate);
3311 if (optional_string("+Failure Substitute:")) {
3312 stuff_string(wip->failure_sub_name, F_NAME);
3313 }
3314 }
3315
3316 // This only works with lasters and missiles for now
3317 if (wip->subtype == WP_MISSILE || wip->subtype == WP_LASER) {
3318 // We always have an object we are attached to
3319 flagset<actions::ProgramContextFlags> contexts{actions::ProgramContextFlags::HasObject};
3320
3321 if (wip->render_type == WRT_POF) {
3322 // We will render with a model so we can have subobejcts
3323 contexts.set(actions::ProgramContextFlags::HasSubobject, true);
3324 }
3325
3326 wip->on_create_program = actions::ProgramSet::parseProgramSet("$On Create:", contexts);
3327 }
3328
3329 /* Generate a substitution pattern for this weapon.
3330 This pattern is very naive such that it calculates the lowest common denominator as being all of
3331 the periods multiplied together.
3332 */
3333 while ( optional_string("$substitute:") ) {
3334 char subname[NAME_LENGTH];
3335 int period = 0;
3336 int index = 0;
3337 int offset = 0;
3338 stuff_string(subname, F_NAME, NAME_LENGTH);
3339 if ( optional_string("+period:") ) {
3340 stuff_int(&period);
3341 if ( period <= 0 ) {
3342 Warning(LOCATION, "Substitution '%s' for weapon '%s' requires a period greater than 0. Setting period to 1.", subname, wip->name);
3343 period = 1;
3344 }
3345 if ( optional_string("+offset:") ) {
3346 stuff_int(&offset);
3347 if ( offset <= 0 ) {
3348 Warning(LOCATION, "Period offset for substitution '%s' of weapon '%s' has to be greater than 0. Setting offset to 1.", subname, wip->name);
3349 offset = 1;
3350 }
3351 }
3352 } else if ( optional_string("+index:") ) {
3353 stuff_int(&index);
3354 if ( index < 0 ) {
3355 Warning(LOCATION, "Substitution '%s' for weapon '%s' requires an index greater than 0. Setting index to 0.", subname, wip->name);
3356 index = 0;
3357 }
3358 }
3359
3360 // we are going to use weapon substition so, make sure that the pattern array has at least one element
3361 if ( wip->num_substitution_patterns == 0 ) {
3362 // pattern is empty, initialize pattern with the weapon being currently parsed.
3363 strcpy_s(wip->weapon_substitution_pattern_names[0], wip->name);
3364 wip->num_substitution_patterns++;
3365 }
3366
3367 // if tbler specifies a period then determine if we can fit the resulting pattern
3368 // neatly into the pattern array.
3369 if ( period > 0 ) {
3370 if ( (wip->num_substitution_patterns % period) > 0 ) {
3371 // not neat, need to expand the pattern so that our frequency pattern fits completly.
3372 size_t current_size = wip->num_substitution_patterns;
3373 size_t desired_size = current_size*period;
3374 if (desired_size > MAX_SUBSTITUTION_PATTERNS) {
3375 Warning(LOCATION, "The period is too large for the number of substitution patterns! desired size=" SIZE_T_ARG ", max size=%d", desired_size, MAX_SUBSTITUTION_PATTERNS);
3376 }
3377 else {
3378 wip->num_substitution_patterns = desired_size;
3379
3380 // now duplicate the current pattern into the new area so the current pattern holds
3381 for ( size_t i = current_size; i < desired_size; i++ ) {
3382 strcpy_s(wip->weapon_substitution_pattern_names[i], wip->weapon_substitution_pattern_names[i%current_size]);
3383 }
3384 }
3385 }
3386
3387 /* Apply the substituted weapon at the requested period, barrel
3388 shifted by offset if needed.*/
3389 for ( size_t pos = (period + offset - 1) % period;
3390 pos < wip->num_substitution_patterns; pos += period )
3391 {
3392 strcpy_s(wip->weapon_substitution_pattern_names[pos], subname);
3393 }
3394 } else {
3395 // assume that tbler wanted to specify a index for the new weapon.
3396
3397 // make sure that there is enough room
3398 if (index >= MAX_SUBSTITUTION_PATTERNS) {
3399 Warning(LOCATION, "Substitution pattern index exceeds the maximum size! Index=%d, max size=%d", index, MAX_SUBSTITUTION_PATTERNS);
3400 } else {
3401 if ( (size_t)index >= wip->num_substitution_patterns ) {
3402 // need to make the pattern bigger by filling the extra with the current weapon.
3403 for ( size_t i = wip->num_substitution_patterns; i < (size_t)index; i++ ) {
3404 strcpy_s(wip->weapon_substitution_pattern_names[i], subname);
3405 }
3406 wip->num_substitution_patterns = index+1;
3407 }
3408
3409 strcpy_s(wip->weapon_substitution_pattern_names[index], subname);
3410 }
3411 }
3412 }
3413
3414 //Optional score for destroying this weapon.
3415 if (optional_string("$Score:")) {
3416 stuff_int(&wip->score);
3417 }
3418
3419 return w_id;
3420 }
3421
3422 /**
3423 * For all weapons that spawn weapons, given an index at weaponp->spawn_type,
3424 * convert the strings in Spawn_names to indices in the Weapon_types array.
3425 */
translate_spawn_types()3426 void translate_spawn_types()
3427 {
3428 int i, j, k;
3429
3430 for (i = 0; i < weapon_info_size(); i++)
3431 {
3432 for (j = 0; j < Weapon_info[i].num_spawn_weapons_defined; j++)
3433 {
3434 if ( (Weapon_info[i].spawn_info[j].spawn_type > -1) && (Weapon_info[i].spawn_info[j].spawn_type < Num_spawn_types) )
3435 {
3436 int spawn_type = Weapon_info[i].spawn_info[j].spawn_type;
3437
3438 Assert( spawn_type < Num_spawn_types );
3439
3440 for (k = 0; k < weapon_info_size(); k++)
3441 {
3442 if ( !stricmp(Spawn_names[spawn_type], Weapon_info[k].name) )
3443 {
3444 Weapon_info[i].spawn_info[j].spawn_type = (short)k;
3445
3446 if (i == k)
3447 Warning(LOCATION, "Weapon %s spawns itself. Infinite recursion?\n", Weapon_info[i].name);
3448
3449 break;
3450 }
3451 }
3452 }
3453 }
3454 }
3455 }
3456
3457 static char Default_cmeasure_name[NAME_LENGTH] = "";
3458
parse_weaponstbl(const char * filename)3459 void parse_weaponstbl(const char *filename)
3460 {
3461 try
3462 {
3463 read_file_text(filename, CF_TYPE_TABLES);
3464 reset_parse();
3465
3466 if (optional_string("#Primary Weapons"))
3467 {
3468 while (required_string_either("#End", "$Name:")) {
3469 // AL 28-3-98: If parse_weapon() fails, try next .tbl weapon
3470 if (parse_weapon(WP_LASER, Parsing_modular_table, filename) < 0) {
3471 continue;
3472 }
3473 }
3474 required_string("#End");
3475 }
3476
3477 if (optional_string("#Secondary Weapons"))
3478 {
3479 while (required_string_either("#End", "$Name:")) {
3480 // AL 28-3-98: If parse_weapon() fails, try next .tbl weapon
3481 if (parse_weapon(WP_MISSILE, Parsing_modular_table, filename) < 0) {
3482 continue;
3483 }
3484 }
3485 required_string("#End");
3486 }
3487
3488 if (optional_string("#Beam Weapons"))
3489 {
3490 while (required_string_either("#End", "$Name:")) {
3491 // AL 28-3-98: If parse_weapon() fails, try next .tbl weapon
3492 if (parse_weapon(WP_BEAM, Parsing_modular_table, filename) < 0) {
3493 continue;
3494 }
3495 }
3496 required_string("#End");
3497 }
3498
3499 if (optional_string("#Countermeasures"))
3500 {
3501 while (required_string_either("#End", "$Name:"))
3502 {
3503 int idx = parse_weapon(WP_MISSILE, Parsing_modular_table, filename);
3504
3505 if (idx < 0) {
3506 continue;
3507 }
3508
3509 //Make sure cmeasure flag is set
3510 Weapon_info[idx].wi_flags.set(Weapon::Info_Flags::Cmeasure);
3511
3512 //Set cmeasure index
3513 if (!strlen(Default_cmeasure_name)) {
3514 //We can't be sure that index will be the same after sorting, so save the name
3515 strcpy_s(Default_cmeasure_name, Weapon_info[idx].name);
3516 }
3517 }
3518
3519 required_string("#End");
3520 }
3521
3522 // Read in a list of weapon_info indicies that are an ordering of the player weapon precedence.
3523 // This list is used to select an alternate weapon when a particular weapon is not available
3524 // during weapon selection.
3525 if ((!Parsing_modular_table && required_string("$Player Weapon Precedence:")) || optional_string("$Player Weapon Precedence:"))
3526 {
3527 Num_player_weapon_precedence = (int)stuff_int_list(Player_weapon_precedence, MAX_WEAPON_TYPES, WEAPON_LIST_TYPE);
3528 }
3529 }
3530 catch (const parse::ParseException& e)
3531 {
3532 mprintf(("TABLES: Unable to parse '%s'! Error message = %s.\n", filename, e.what()));
3533 return;
3534 }
3535 }
3536
3537 //uses a simple bucket sort to sort weapons, order of importance is:
3538 //Lasers
3539 //Beams
3540 //Child primary weapons
3541 //Fighter missiles and bombs
3542 //Capital missiles and bombs
3543 //Child secondary weapons
weapon_sort_by_type()3544 void weapon_sort_by_type()
3545 {
3546 weapon_info *lasers = NULL, *big_lasers = NULL, *beams = NULL, *missiles = NULL, *big_missiles = NULL, *child_primaries = NULL, *child_secondaries = NULL;
3547 int num_lasers = 0, num_big_lasers = 0, num_beams = 0, num_missiles = 0, num_big_missiles = 0, num_child_primaries = 0, num_child_secondaries = 0;
3548 int i, weapon_index;
3549
3550 // get the initial count of each weapon type
3551 for (i = 0; i < weapon_info_size(); i++) {
3552 switch (Weapon_info[i].subtype)
3553 {
3554 case WP_UNUSED:
3555 continue;
3556
3557 case WP_LASER:
3558 if (Weapon_info[i].wi_flags[Weapon::Info_Flags::Child])
3559 num_child_primaries++;
3560 else if (Weapon_info[i].wi_flags[Weapon::Info_Flags::Big_only])
3561 num_big_lasers++;
3562 else
3563 num_lasers++;
3564 break;
3565
3566 case WP_BEAM:
3567 num_beams++;
3568 break;
3569
3570 case WP_MISSILE:
3571 if (Weapon_info[i].wi_flags[Weapon::Info_Flags::Child])
3572 num_child_secondaries++;
3573 else if (Weapon_info[i].wi_flags[Weapon::Info_Flags::Big_only])
3574 num_big_missiles++;
3575 else
3576 num_missiles++;
3577 break;
3578
3579 default:
3580 continue;
3581 }
3582
3583 }
3584
3585 // allocate the buckets
3586 if (num_lasers) {
3587 lasers = new weapon_info[num_lasers];
3588 Verify( lasers != NULL );
3589 num_lasers = 0;
3590 }
3591
3592 if (num_big_lasers) {
3593 big_lasers = new weapon_info[num_big_lasers];
3594 Verify( big_lasers != NULL );
3595 num_big_lasers = 0;
3596 }
3597
3598 if (num_beams) {
3599 beams = new weapon_info[num_beams];
3600 Verify( beams != NULL );
3601 num_beams = 0;
3602 }
3603
3604 if (num_missiles) {
3605 missiles = new weapon_info[num_missiles];
3606 Verify( missiles != NULL );
3607 num_missiles = 0;
3608 }
3609
3610 if (num_big_missiles) {
3611 big_missiles = new weapon_info[num_big_missiles];
3612 Verify( big_missiles != NULL );
3613 num_big_missiles = 0;
3614 }
3615
3616 if (num_child_primaries) {
3617 child_primaries = new weapon_info[num_child_primaries];
3618 Verify( child_primaries != NULL );
3619 num_child_primaries = 0;
3620 }
3621
3622 if (num_child_secondaries) {
3623 child_secondaries = new weapon_info[num_child_secondaries];
3624 Verify( child_secondaries != NULL );
3625 num_child_secondaries = 0;
3626 }
3627
3628 // fill the buckets
3629 for (i = 0; i < weapon_info_size(); i++) {
3630 switch (Weapon_info[i].subtype)
3631 {
3632 case WP_UNUSED:
3633 continue;
3634
3635 case WP_LASER:
3636 if (Weapon_info[i].wi_flags[Weapon::Info_Flags::Child])
3637 child_primaries[num_child_primaries++] = Weapon_info[i];
3638 else if (Weapon_info[i].wi_flags[Weapon::Info_Flags::Big_only])
3639 big_lasers[num_big_lasers++] = Weapon_info[i];
3640 else
3641 lasers[num_lasers++] = Weapon_info[i];
3642 break;
3643
3644 case WP_BEAM:
3645 beams[num_beams++] = Weapon_info[i];
3646 break;
3647
3648 case WP_MISSILE:
3649 if (Weapon_info[i].wi_flags[Weapon::Info_Flags::Child])
3650 child_secondaries[num_child_secondaries++] = Weapon_info[i];
3651 else if (Weapon_info[i].wi_flags[Weapon::Info_Flags::Big_only])
3652 big_missiles[num_big_missiles++] = Weapon_info[i];
3653 else
3654 missiles[num_missiles++]=Weapon_info[i];
3655 break;
3656
3657 default:
3658 continue;
3659 }
3660 }
3661
3662 weapon_index = 0;
3663
3664 // reorder the weapon_info structure according to our rules defined above
3665 for (i = 0; i < num_lasers; i++, weapon_index++)
3666 Weapon_info[weapon_index] = lasers[i];
3667
3668 for (i = 0; i < num_big_lasers; i++, weapon_index++)
3669 Weapon_info[weapon_index] = big_lasers[i];
3670
3671 for (i = 0; i < num_beams; i++, weapon_index++)
3672 Weapon_info[weapon_index] = beams[i];
3673
3674 for (i = 0; i < num_child_primaries; i++, weapon_index++)
3675 Weapon_info[weapon_index] = child_primaries[i];
3676
3677 // designate start of secondary weapons so that we'll have the correct offset later on
3678 First_secondary_index = weapon_index;
3679
3680 for (i = 0; i < num_missiles; i++, weapon_index++)
3681 Weapon_info[weapon_index] = missiles[i];
3682
3683 for (i = 0; i < num_big_missiles; i++, weapon_index++)
3684 Weapon_info[weapon_index] = big_missiles[i];
3685
3686 for (i = 0; i < num_child_secondaries; i++, weapon_index++)
3687 Weapon_info[weapon_index] = child_secondaries[i];
3688
3689
3690 if (lasers) delete [] lasers;
3691 if (big_lasers) delete [] big_lasers;
3692 if (beams) delete [] beams;
3693 if (missiles) delete [] missiles;
3694 if (big_missiles) delete [] big_missiles;
3695 if (child_primaries) delete [] child_primaries;
3696 if (child_secondaries) delete [] child_secondaries;
3697 }
3698
3699 /**
3700 * Do any post-parse cleaning on weapon entries
3701 */
weapon_clean_entries()3702 void weapon_clean_entries()
3703 {
3704 for (auto &wi : Weapon_info) {
3705 if (wi.wi_flags[Weapon::Info_Flags::Beam]) {
3706 // clean up any beam sections which may have been deleted
3707 int removed = 0;
3708
3709 for (int s_idx = 0; s_idx < wi.b_info.beam_num_sections; s_idx++) {
3710 beam_weapon_section_info *bsip = &wi.b_info.sections[s_idx];
3711
3712 // If this is an invisible beam section, we want to keep it. Originally invisible sections were initialized as they were parsed,
3713 // but then they were inadvertently cleaned up in this function. So let's properly set the filename here while not removing the section.
3714 if ( !stricmp(bsip->texture.filename, "invisible") ) {
3715 memset(bsip->texture.filename, 0, MAX_FILENAME_LEN);
3716 }
3717 // Now remove empty beam sections as before
3718 else if ( !strlen(bsip->texture.filename) ) {
3719 int new_idx = s_idx + 1;
3720
3721 while (new_idx < MAX_BEAM_SECTIONS) {
3722 memcpy( &wi.b_info.sections[new_idx-1], &wi.b_info.sections[new_idx], sizeof(beam_weapon_section_info) );
3723 new_idx++;
3724 }
3725
3726 removed++;
3727 }
3728 }
3729
3730 if (removed) {
3731 mprintf(("NOTE: weapon-cleanup is removing %i stale beam sections, out of %i original, from '%s'.\n", removed, wi.b_info.beam_num_sections, wi.name));
3732 wi.b_info.beam_num_sections -= removed;
3733 }
3734
3735 if (wi.b_info.beam_num_sections == 0) {
3736 Warning(LOCATION, "The beam '%s' has 0 usable sections!", wi.name);
3737 }
3738 }
3739 }
3740 }
3741
weapon_release_bitmaps()3742 void weapon_release_bitmaps()
3743 {
3744 // not for FRED...
3745 if (Fred_running)
3746 return;
3747
3748 // if we are just going to load them all again any, keep everything
3749 if (Cmdline_load_all_weapons)
3750 return;
3751
3752 for (int i = 0; i < weapon_info_size(); ++i) {
3753 weapon_info *wip = &Weapon_info[i];
3754
3755 // go ahead and clear out models, the model paging code will actually take care of
3756 // releasing this stuff if needed, but we have to keep track of current modelnums ourselves
3757 if (wip->render_type == WRT_POF)
3758 wip->model_num = -1;
3759
3760 // we are only interested in what we don't need for this mission
3761 if ( used_weapons[i] )
3762 continue;
3763
3764 if (wip->render_type == WRT_LASER) {
3765 if (wip->laser_bitmap.first_frame >= 0) {
3766 bm_release(wip->laser_bitmap.first_frame);
3767 wip->laser_bitmap.first_frame = -1;
3768 }
3769
3770 // now for the glow
3771 if (wip->laser_glow_bitmap.first_frame >= 0) {
3772 bm_release(wip->laser_glow_bitmap.first_frame);
3773 wip->laser_glow_bitmap.first_frame = -1;
3774 }
3775 }
3776
3777 if (wip->wi_flags[Weapon::Info_Flags::Beam]) {
3778 // particle animation
3779 if (wip->b_info.beam_particle_ani.first_frame >= 0) {
3780 bm_release(wip->b_info.beam_particle_ani.first_frame);
3781 wip->b_info.beam_particle_ani.first_frame = -1;
3782 }
3783
3784 // muzzle glow
3785 if (wip->b_info.beam_glow.first_frame >= 0) {
3786 bm_release(wip->b_info.beam_glow.first_frame);
3787 wip->b_info.beam_glow.first_frame = -1;
3788 }
3789
3790 // section textures
3791 for (int j = 0; j < wip->b_info.beam_num_sections; ++j) {
3792 beam_weapon_section_info *bsi = &wip->b_info.sections[j];
3793
3794 if (bsi->texture.first_frame >= 0) {
3795 bm_release(bsi->texture.first_frame);
3796 bsi->texture.first_frame = -1;
3797 }
3798 }
3799 }
3800
3801 if (wip->wi_flags[Weapon::Info_Flags::Trail]) {
3802 if (wip->tr_info.texture.bitmap_id >= 0) {
3803 bm_release(wip->tr_info.texture.bitmap_id);
3804 wip->tr_info.texture.bitmap_id = -1;
3805 }
3806 }
3807
3808 if (wip->wi_flags[Weapon::Info_Flags::Particle_spew]) { // tweaked for multiple particle spews -nuke
3809 for (size_t s = 0; s < MAX_PARTICLE_SPEWERS; s++) { // just bitmaps that got loaded
3810 if (wip->particle_spewers[s].particle_spew_type != PSPEW_NONE){
3811 if (wip->particle_spewers[s].particle_spew_anim.first_frame >= 0) {
3812 bm_release(wip->particle_spewers[s].particle_spew_anim.first_frame);
3813 wip->particle_spewers[s].particle_spew_anim.first_frame = -1;
3814 }
3815 }
3816 }
3817 }
3818
3819 if (wip->thruster_flame.first_frame >= 0) {
3820 bm_release(wip->thruster_flame.first_frame);
3821 wip->thruster_flame.first_frame = -1;
3822 }
3823
3824 if (wip->thruster_glow.first_frame >= 0) {
3825 bm_release(wip->thruster_glow.first_frame);
3826 wip->thruster_glow.first_frame = -1;
3827 }
3828 }
3829 }
3830
weapon_is_used(int weapon_index)3831 bool weapon_is_used(int weapon_index)
3832 {
3833 Assert( (weapon_index >= 0) || (weapon_index < weapon_info_size()) );
3834 return (used_weapons[weapon_index] > 0);
3835 }
3836
weapon_load_bitmaps(int weapon_index)3837 void weapon_load_bitmaps(int weapon_index)
3838 {
3839 weapon_info *wip;
3840
3841 // not for FRED...
3842 if (Fred_running)
3843 return;
3844
3845 if ( (weapon_index < 0) || (weapon_index >= weapon_info_size()) ) {
3846 Int3();
3847 return;
3848 }
3849
3850 wip = &Weapon_info[weapon_index];
3851
3852 if ( (wip->render_type == WRT_LASER) && (wip->laser_bitmap.first_frame < 0) ) {
3853 wip->laser_bitmap.first_frame = bm_load(wip->laser_bitmap.filename);
3854
3855 if (wip->laser_bitmap.first_frame >= 0) {
3856 wip->laser_bitmap.num_frames = 1;
3857 wip->laser_bitmap.total_time = 1;
3858 }
3859 // fall back to an animated type
3860 else if ( generic_anim_load(&wip->laser_bitmap) ) {
3861 mprintf(("Could not find a usable bitmap for '%s'!\n", wip->name));
3862 Warning(LOCATION, "Could not find a usable bitmap (%s) for weapon '%s'!\n", wip->laser_bitmap.filename, wip->name);
3863 }
3864
3865 // now see if we also have a glow
3866 if ( strlen(wip->laser_glow_bitmap.filename) ) {
3867 wip->laser_glow_bitmap.first_frame = bm_load(wip->laser_glow_bitmap.filename);
3868
3869 if (wip->laser_glow_bitmap.first_frame >= 0) {
3870 wip->laser_glow_bitmap.num_frames = 1;
3871 wip->laser_glow_bitmap.total_time = 1;
3872 }
3873 // fall back to an animated type
3874 else if ( generic_anim_load(&wip->laser_glow_bitmap) ) {
3875 mprintf(("Could not find a usable glow bitmap for '%s'!\n", wip->name));
3876 Warning(LOCATION, "Could not find a usable glow bitmap (%s) for weapon '%s'!\n", wip->laser_glow_bitmap.filename, wip->name);
3877 }
3878 }
3879 }
3880
3881 if (wip->wi_flags[Weapon::Info_Flags::Beam]) {
3882 // particle animation
3883 if ( (wip->b_info.beam_particle_ani.first_frame < 0) && strlen(wip->b_info.beam_particle_ani.filename) )
3884 generic_anim_load(&wip->b_info.beam_particle_ani);
3885
3886 // muzzle glow
3887 if ( (wip->b_info.beam_glow.first_frame < 0) && strlen(wip->b_info.beam_glow.filename) ) {
3888 if ( generic_anim_load(&wip->b_info.beam_glow) ) {
3889 // animated version failed to load, try static instead
3890 wip->b_info.beam_glow.first_frame = bm_load(wip->b_info.beam_glow.filename);
3891
3892 if (wip->b_info.beam_glow.first_frame >= 0) {
3893 wip->b_info.beam_glow.num_frames = 1;
3894 wip->b_info.beam_glow.total_time = 1;
3895 } else {
3896 mprintf(("Could not find a usable muzzle glow bitmap for '%s'!\n", wip->name));
3897 Warning(LOCATION, "Could not find a usable muzzle glow bitmap (%s) for weapon '%s'!\n", wip->b_info.beam_glow.filename, wip->name);
3898 }
3899 }
3900 }
3901
3902 // section textures
3903 for (int i = 0; i < wip->b_info.beam_num_sections; i++) {
3904 beam_weapon_section_info *bsi = &wip->b_info.sections[i];
3905
3906 if ( (bsi->texture.first_frame < 0) && strlen(bsi->texture.filename) ) {
3907 if ( generic_anim_load(&bsi->texture) ) {
3908 // animated version failed to load, try static instead
3909 bsi->texture.first_frame = bm_load(bsi->texture.filename);
3910
3911 if (bsi->texture.first_frame >= 0) {
3912 bsi->texture.num_frames = 1;
3913 bsi->texture.total_time = 1;
3914 } else {
3915 mprintf(("Could not find a usable beam section (%i) bitmap for '%s'!\n", i, wip->name));
3916 Warning(LOCATION, "Could not find a usable beam section (%i) bitmap (%s) for weapon '%s'!\n", i, bsi->texture.filename, wip->name);
3917 }
3918 }
3919 }
3920 }
3921 }
3922
3923 if ( (wip->wi_flags[Weapon::Info_Flags::Trail]) && (wip->tr_info.texture.bitmap_id < 0) )
3924 generic_bitmap_load(&wip->tr_info.texture);
3925
3926 //WMC - Don't try to load an anim if no anim is specified, Mmkay?
3927 if (wip->wi_flags[Weapon::Info_Flags::Particle_spew]) {
3928 for (size_t s = 0; s < MAX_PARTICLE_SPEWERS; s++) { // looperfied for multiple pspewers -nuke
3929 if (wip->particle_spewers[s].particle_spew_type != PSPEW_NONE){
3930
3931 if ((wip->particle_spewers[s].particle_spew_anim.first_frame < 0)
3932 && (wip->particle_spewers[s].particle_spew_anim.filename[0] != '\0') ) {
3933
3934 wip->particle_spewers[s].particle_spew_anim.first_frame = bm_load(wip->particle_spewers[s].particle_spew_anim.filename);
3935
3936 if (wip->particle_spewers[s].particle_spew_anim.first_frame >= 0) {
3937 wip->particle_spewers[s].particle_spew_anim.num_frames = 1;
3938 wip->particle_spewers[s].particle_spew_anim.total_time = 1;
3939 }
3940 // fall back to an animated type
3941 else if ( generic_anim_load(&wip->particle_spewers[s].particle_spew_anim) ) {
3942 mprintf(("Could not find a usable particle spew bitmap for '%s'!\n", wip->name));
3943 Warning(LOCATION, "Could not find a usable particle spew bitmap (%s) for weapon '%s'!\n", wip->particle_spewers[s].particle_spew_anim.filename, wip->name);
3944 }
3945 }
3946 }
3947 }
3948 }
3949
3950 // load alternate thruster textures
3951 if (strlen(wip->thruster_flame.filename)) {
3952 generic_anim_load(&wip->thruster_flame);
3953 }
3954
3955 if (strlen(wip->thruster_glow.filename)) {
3956 wip->thruster_glow.first_frame = bm_load(wip->thruster_glow.filename);
3957 if (wip->thruster_glow.first_frame >= 0) {
3958 wip->thruster_glow.num_frames = 1;
3959 wip->thruster_glow.total_time = 1;
3960 } else {
3961 generic_anim_load(&wip->thruster_glow);
3962 }
3963 }
3964
3965 decals::loadBitmaps(wip->impact_decal);
3966
3967 // if this weapon isn't already marked as used, then mark it as such now
3968 // (this should really only happen if the player is cheating)
3969 if ( !used_weapons[weapon_index] )
3970 used_weapons[weapon_index]++;
3971 }
3972
3973 /**
3974 * Checks all of the weapon infos for substitution patterns and caches the weapon_index of any that it finds.
3975 */
weapon_generate_indexes_for_substitution()3976 void weapon_generate_indexes_for_substitution() {
3977 for (int i = 0; i < weapon_info_size(); i++) {
3978 weapon_info *wip = &(Weapon_info[i]);
3979
3980 if ( wip->num_substitution_patterns > 0 ) {
3981 for ( size_t j = 0; j < wip->num_substitution_patterns; j++ ) {
3982 int weapon_index = -1;
3983 if ( stricmp("none", wip->weapon_substitution_pattern_names[j]) != 0 ) {
3984 weapon_index = weapon_info_lookup(wip->weapon_substitution_pattern_names[j]);
3985
3986 if ( weapon_index == -1 ) { // invalid sub weapon
3987 Warning(LOCATION, "Weapon '%s' requests substitution with '%s' which does not seem to exist",
3988 wip->name, wip->weapon_substitution_pattern_names[j]);
3989 continue;
3990 }
3991
3992 if (Weapon_info[weapon_index].subtype != wip->subtype) {
3993 // Check to make sure secondaries can't be launched by primaries and vice versa
3994 Warning(LOCATION, "Weapon '%s' requests substitution with '%s' which is of a different subtype.",
3995 wip->name, wip->weapon_substitution_pattern_names[j]);
3996 wip->num_substitution_patterns = 0;
3997 std::fill(std::begin(wip->weapon_substitution_pattern),
3998 std::end(wip->weapon_substitution_pattern),
3999 -1);
4000 break;
4001 }
4002
4003 if (Weapon_info[weapon_index].wi_flags[Weapon::Info_Flags::Beam] != wip->wi_flags[Weapon::Info_Flags::Beam]) {
4004 // Check to make sure beams and non-beams aren't being mixed
4005 Warning(LOCATION, "Beams and non-beams cannot be mixed in substitution for weapon '%s'.", wip->name);
4006 wip->num_substitution_patterns = 0;
4007 std::fill(std::begin(wip->weapon_substitution_pattern),
4008 std::end(wip->weapon_substitution_pattern),
4009 -1);
4010 break;
4011 }
4012 }
4013
4014 wip->weapon_substitution_pattern[j] = weapon_index;
4015 }
4016
4017 memset(wip->weapon_substitution_pattern_names, 0, sizeof(char) * MAX_SUBSTITUTION_PATTERNS * NAME_LENGTH);
4018 }
4019
4020 if (wip->failure_rate > 0.0f) {
4021 if (VALID_FNAME(wip->failure_sub_name)) {
4022 wip->failure_sub = weapon_info_lookup(wip->failure_sub_name.c_str());
4023
4024 if (wip->failure_sub == -1) { // invalid sub weapon
4025 Warning(LOCATION, "Weapon '%s' requests substitution with '%s' which does not seem to exist",
4026 wip->name, wip->failure_sub_name.c_str());
4027 wip->failure_rate = 0.0f;
4028 }
4029
4030 if (Weapon_info[wip->failure_sub].subtype != wip->subtype) {
4031 // Check to make sure secondaries can't be launched by primaries and vice versa
4032 Warning(LOCATION, "Weapon '%s' requests substitution with '%s' which is of a different subtype.",
4033 wip->name, wip->failure_sub_name.c_str());
4034 wip->failure_sub = -1;
4035 wip->failure_rate = 0.0f;
4036 }
4037 }
4038
4039 wip->failure_sub_name.clear();
4040 }
4041 }
4042 }
4043
weapon_do_post_parse()4044 void weapon_do_post_parse()
4045 {
4046 weapon_info *wip;
4047 int first_cmeasure_index = -1;
4048
4049 weapon_sort_by_type(); // NOTE: This has to be first thing!
4050 weapon_clean_entries();
4051 weapon_generate_indexes_for_substitution();
4052
4053 Default_cmeasure_index = -1;
4054
4055 // run through weapons list and deal with individual issues
4056 for (int i = 0; i < weapon_info_size(); ++i) {
4057 wip = &Weapon_info[i];
4058
4059 // set default counter-measure index from the saved name
4060 if ( (Default_cmeasure_index < 0) && strlen(Default_cmeasure_name) ) {
4061 if ( !stricmp(wip->name, Default_cmeasure_name) ) {
4062 Default_cmeasure_index = i;
4063 }
4064 }
4065
4066 // catch a fall back cmeasure index, just in case
4067 if ( (first_cmeasure_index < 0) && (wip->wi_flags[Weapon::Info_Flags::Cmeasure]) )
4068 first_cmeasure_index = i;
4069 }
4070
4071 // catch cmeasure fallback
4072 if (Default_cmeasure_index < 0)
4073 Default_cmeasure_index = first_cmeasure_index;
4074
4075 // now we want to resolve the countermeasures by species
4076 for (SCP_vector<species_info>::iterator ii = Species_info.begin(); ii != Species_info.end(); ++ii)
4077 {
4078 if (*ii->cmeasure_name)
4079 {
4080 int index = weapon_info_lookup(ii->cmeasure_name);
4081 if (index < 0)
4082 Warning(LOCATION, "Could not find weapon type '%s' to use as countermeasure on species '%s'", ii->cmeasure_name, ii->species_name);
4083 else if (Weapon_info[index].wi_flags[Weapon::Info_Flags::Beam])
4084 Warning(LOCATION, "Attempt made to set a beam weapon as a countermeasure on species '%s'", ii->species_name);
4085 else
4086 ii->cmeasure_index = index;
4087 }
4088 }
4089
4090 // translate all spawn type weapons to referrnce the appropriate spawned weapon entry
4091 translate_spawn_types();
4092 }
4093
weapon_expl_info_init()4094 void weapon_expl_info_init()
4095 {
4096 int i;
4097
4098 parse_weapon_expl_tbl("weapon_expl.tbl");
4099
4100 // check for, and load, modular tables
4101 parse_modular_table(NOX("*-wxp.tbm"), parse_weapon_expl_tbl);
4102
4103 // we've got our list so pass it off for final checking and loading
4104 for (i = 0; i < (int)LOD_checker.size(); i++) {
4105 Weapon_explosions.Load( LOD_checker[i].filename, LOD_checker[i].num_lods );
4106 }
4107
4108 // done
4109 LOD_checker.clear();
4110 }
4111
4112 /**
4113 * This will get called once at game startup
4114 */
weapon_init()4115 void weapon_init()
4116 {
4117 if ( !Weapons_inited ) {
4118 //Init weapon explosion info
4119 weapon_expl_info_init();
4120
4121 Num_spawn_types = 0;
4122
4123 // parse weapons.tbl
4124 Removed_weapons.clear();
4125 Weapon_info.clear();
4126 parse_weaponstbl("weapons.tbl");
4127
4128 parse_modular_table(NOX("*-wep.tbm"), parse_weaponstbl);
4129
4130 // do post-parse cleanup
4131 weapon_do_post_parse();
4132
4133 Weapons_inited = true;
4134 }
4135
4136 weapon_level_init();
4137
4138 if (Cmdline_spew_weapon_stats != WeaponSpewType::NONE)
4139 weapon_spew_stats(Cmdline_spew_weapon_stats);
4140 }
4141
4142
4143 /**
4144 * Call from game_shutdown() only!!
4145 */
weapon_close()4146 void weapon_close()
4147 {
4148 int i;
4149
4150 for (i = 0; i < weapon_info_size(); i++) {
4151 if (Weapon_info[i].desc) {
4152 vm_free(Weapon_info[i].desc);
4153 Weapon_info[i].desc = NULL;
4154 }
4155
4156 if (Weapon_info[i].tech_desc) {
4157 vm_free(Weapon_info[i].tech_desc);
4158 Weapon_info[i].tech_desc = NULL;
4159 }
4160 }
4161
4162 if (used_weapons != NULL) {
4163 delete[] used_weapons;
4164 used_weapons = NULL;
4165 }
4166
4167 if (Spawn_names != NULL) {
4168 for (i=0; i<Num_spawn_types; i++) {
4169 if (Spawn_names[i] != NULL) {
4170 vm_free(Spawn_names[i]);
4171 Spawn_names[i] = NULL;
4172 }
4173 }
4174
4175 vm_free(Spawn_names);
4176 Spawn_names = NULL;
4177 }
4178 }
4179
4180 /**
4181 * This will get called at the start of each level.
4182 */
weapon_level_init()4183 void weapon_level_init()
4184 {
4185 int i;
4186 extern bool Ships_inited;
4187
4188 // Reset everything between levels
4189 Num_weapons = 0;
4190 for (i=0; i<MAX_WEAPONS; i++) {
4191 Weapons[i].objnum = -1;
4192 Weapons[i].weapon_info_index = -1;
4193 }
4194
4195 for (i = 0; i < weapon_info_size(); i++) {
4196 Weapon_info[i].damage_type_idx = Weapon_info[i].damage_type_idx_sav;
4197 Weapon_info[i].shockwave.damage_type_idx = Weapon_info[i].shockwave.damage_type_idx_sav;
4198
4199 if ( Ships_inited ) {
4200 // populate ship type lock restrictions
4201 for (auto& pair : Weapon_info[i].ship_restrict_strings) {
4202 const char* name = pair.second.c_str();
4203 int idx;
4204 switch (pair.first)
4205 {
4206 case LockRestrictionType::TYPE: idx = ship_type_name_lookup(name); break;
4207 case LockRestrictionType::CLASS: idx = ship_info_lookup(name); break;
4208 case LockRestrictionType::SPECIES: idx = species_info_lookup(name); break;
4209 case LockRestrictionType::IFF: idx = iff_lookup(name); break;
4210 default: Assertion(false, "Unknown multi lock restriction type %d", (int)pair.first);
4211 idx = -1;
4212 }
4213 if ( idx >= 0 ) {
4214 Weapon_info[i].ship_restrict.emplace_back(pair.first, idx);
4215 }
4216 else {
4217 Warning(LOCATION, "Couldn't find multi lock restriction type '%s' for weapon '%s'", name, Weapon_info[i].name);
4218 }
4219 }
4220 Weapon_info[i].ship_restrict_strings.clear();
4221 }
4222 }
4223
4224 trail_level_init(); // reset all missile trails
4225
4226 swarm_level_init();
4227 missile_obj_list_init();
4228
4229 cscrew_level_init();
4230
4231 // emp effect
4232 emp_level_init();
4233
4234 if (used_weapons == nullptr)
4235 used_weapons = new int[Weapon_info.size()];
4236
4237 // clear out used_weapons between missions
4238 memset(used_weapons, 0, Weapon_info.size() * sizeof(int));
4239
4240 Weapon_flyby_sound_timer = timestamp(0);
4241 Weapon_impact_timer = 1; // inited each level, used to reduce impact sounds
4242 }
4243
4244 MONITOR( NumWeaponsRend )
4245
4246 const float weapon_glow_scale_f = 2.3f;
4247 const float weapon_glow_scale_r = 2.3f;
4248 const float weapon_glow_scale_l = 1.5f;
4249 const int weapon_glow_alpha = 217; // (0.85 * 255);
4250
weapon_delete(object * obj)4251 void weapon_delete(object *obj)
4252 {
4253 weapon *wp;
4254 int num;
4255
4256 if (Script_system.IsActiveAction(CHA_ONWEAPONDELETE)) {
4257 Script_system.SetHookObjects(2, "Weapon", obj, "Self", obj);
4258 Script_system.RunCondition(CHA_ONWEAPONDELETE);
4259 Script_system.RemHookVars({"Weapon", "Self"});
4260 }
4261
4262 num = obj->instance;
4263
4264 Assert( Weapons[num].objnum == OBJ_INDEX(obj));
4265 wp = &Weapons[num];
4266
4267 Assert(wp->weapon_info_index >= 0);
4268 wp->weapon_info_index = -1;
4269 if (wp->swarm_info_ptr != nullptr)
4270 wp->swarm_info_ptr.reset();
4271
4272 if(wp->cscrew_index >= 0) {
4273 cscrew_delete(wp->cscrew_index);
4274 wp->cscrew_index = -1;
4275 }
4276
4277 if (wp->missile_list_index >= 0) {
4278 missle_obj_list_remove(wp->missile_list_index);
4279 wp->missile_list_index = -1;
4280 }
4281
4282 if (wp->trail_ptr != NULL) {
4283 trail_object_died(wp->trail_ptr);
4284 wp->trail_ptr = NULL;
4285 }
4286
4287 if (wp->hud_in_flight_snd_sig.isValid() && snd_is_playing(wp->hud_in_flight_snd_sig))
4288 snd_stop(wp->hud_in_flight_snd_sig);
4289
4290 if (wp->model_instance_num >= 0)
4291 model_delete_instance(wp->model_instance_num);
4292
4293 if (wp->cmeasure_ignore_list != nullptr) {
4294 delete wp->cmeasure_ignore_list;
4295 wp->cmeasure_ignore_list = nullptr;
4296 }
4297
4298 if (wp->collisionInfo != nullptr) {
4299 delete wp->collisionInfo;
4300 wp->collisionInfo = nullptr;
4301 }
4302
4303 wp->objnum = -1;
4304 Num_weapons--;
4305 Assert(Num_weapons >= 0);
4306 }
4307
4308 /**
4309 * Check if missile is newly locked onto the Player, maybe play a launch warning
4310 */
weapon_maybe_play_warning(weapon * wp)4311 void weapon_maybe_play_warning(weapon *wp)
4312 {
4313 if ( wp->homing_object == Player_obj ) {
4314 if ( !(wp->weapon_flags[Weapon::Weapon_Flags::Lock_warning_played]) ) {
4315 wp->weapon_flags.set(Weapon::Weapon_Flags::Lock_warning_played);
4316 // Use heatlock-warning sound for Heat and Javelin for now
4317 // Possibly add an additional third sound later
4318 if ( (Weapon_info[wp->weapon_info_index].wi_flags[Weapon::Info_Flags::Homing_heat]) ||
4319 (Weapon_info[wp->weapon_info_index].wi_flags[Weapon::Info_Flags::Homing_javelin]) ) {
4320 snd_play(gamesnd_get_game_sound(ship_get_sound(Player_obj, GameSounds::HEATLOCK_WARN)));
4321 } else {
4322 Assert(Weapon_info[wp->weapon_info_index].wi_flags[Weapon::Info_Flags::Homing_aspect]);
4323 snd_play(gamesnd_get_game_sound(ship_get_sound(Player_obj, GameSounds::ASPECTLOCK_WARN)));
4324 }
4325 }
4326 }
4327 }
4328
4329
4330 /**
4331 * Detonate all missiles near this countermeasure.
4332 */
detonate_nearby_missiles(object * killer_objp,object * missile_objp)4333 void detonate_nearby_missiles(object* killer_objp, object* missile_objp)
4334 {
4335 if(killer_objp->type != OBJ_WEAPON || missile_objp->type != OBJ_WEAPON) {
4336 Int3();
4337 return;
4338 }
4339
4340 weapon_info* killer_infop = &Weapon_info[Weapons[killer_objp->instance].weapon_info_index];
4341
4342 if (killer_infop->cm_kill_single) {
4343 weapon* wp = &Weapons[missile_objp->instance];
4344 if (wp->lifeleft > 0.2f) {
4345 nprintf(("Countermeasures", "Countermeasure (%s-%i) detonated missile (%s-%i) Frame: %i\n",
4346 killer_infop->name, killer_objp->signature,
4347 Weapon_info[Weapons[missile_objp->instance].weapon_info_index].name, missile_objp->signature, Framecount));
4348 wp->lifeleft = 0.2f;
4349 }
4350 return;
4351 }
4352
4353 missile_obj* mop = GET_FIRST(&Missile_obj_list);
4354
4355 while(mop != END_OF_LIST(&Missile_obj_list)) {
4356 object* objp = &Objects[mop->objnum];
4357 weapon* wp = &Weapons[objp->instance];
4358
4359 if (iff_x_attacks_y(Weapons[killer_objp->instance].team, wp->team)) {
4360 if ( Missiontime - wp->creation_time > F1_0/2) {
4361 if (vm_vec_dist_quick(&killer_objp->pos, &objp->pos) < killer_infop->cm_detonation_rad) {
4362 if (wp->lifeleft > 0.2f) {
4363 nprintf(("Countermeasures", "Countermeasure (%s-%i) detonated missile (%s-%i) Frame: %i\n",
4364 killer_infop->name, killer_objp->signature,
4365 Weapon_info[Weapons[objp->instance].weapon_info_index].name, objp->signature, Framecount));
4366 wp->lifeleft = 0.2f;
4367 }
4368 }
4369 }
4370 }
4371
4372 mop = mop->next;
4373 }
4374 }
4375
4376 /**
4377 * Find an object for weapon #num (object *weapon_objp) to home on due to heat.
4378 */
find_homing_object(object * weapon_objp,int num)4379 void find_homing_object(object *weapon_objp, int num)
4380 {
4381 ship_subsys *target_engines = NULL;
4382
4383 weapon* wp = &Weapons[num];
4384
4385 weapon_info* wip = &Weapon_info[Weapons[num].weapon_info_index];
4386
4387 float best_dist = 99999.9f;
4388
4389 // save the old homing object so that multiplayer servers can give the right information
4390 // to clients if the object changes
4391 object* old_homing_objp = wp->homing_object;
4392
4393 wp->homing_object = &obj_used_list;
4394
4395 // only for random acquisition, accrue targets to later pick from randomly
4396 SCP_vector<object*> prospective_targets;
4397
4398 // Scan all objects, find a weapon to home on.
4399 for ( object* objp = GET_FIRST(&obj_used_list); objp !=END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) ) {
4400 if ((objp->type == OBJ_SHIP) || ((objp->type == OBJ_WEAPON) && (Weapon_info[Weapons[objp->instance].weapon_info_index].wi_flags[Weapon::Info_Flags::Cmeasure])))
4401 {
4402 //WMC - Spawn weapons shouldn't go for protected ships
4403 // ditto for untargeted heat seekers - niffiwan
4404 if ( (objp->flags[Object::Object_Flags::Protected]) &&
4405 ((wp->weapon_flags[Weapon::Weapon_Flags::Spawned]) || (wip->wi_flags[Weapon::Info_Flags::Untargeted_heat_seeker])) )
4406 continue;
4407
4408 // Spawned weapons should never home in on their parent - even in multiplayer dogfights where they would pass the iff test below
4409 if ((wp->weapon_flags[Weapon::Weapon_Flags::Spawned]) && (objp == &Objects[weapon_objp->parent]))
4410 continue;
4411
4412 int homing_object_team = obj_team(objp);
4413 bool can_attack = weapon_has_iff_restrictions(wip) || iff_x_attacks_y(wp->team, homing_object_team);
4414 if (weapon_target_satisfies_lock_restrictions(wip, objp) && can_attack)
4415 {
4416 if ( objp->type == OBJ_SHIP )
4417 {
4418 ship* sp = &Ships[objp->instance];
4419 ship_info* sip = &Ship_info[sp->ship_info_index];
4420
4421 //if the homing weapon is a huge weapon and the ship that is being
4422 //looked at is not huge, then don't home
4423 if ((wip->wi_flags[Weapon::Info_Flags::Huge]) &&
4424 !(sip->is_huge_ship()))
4425 {
4426 continue;
4427 }
4428
4429 // AL 2-17-98: If ship is immune to sensors, can't home on it (Sandeep says so)!
4430 if ( sp->flags[Ship::Ship_Flags::Hidden_from_sensors] ) {
4431 continue;
4432 }
4433
4434 // Goober5000: if missiles can't home on sensor-ghosted ships,
4435 // they definitely shouldn't home on stealth ships
4436 if ( sp->flags[Ship::Ship_Flags::Stealth] && (The_mission.ai_profile->flags[AI::Profile_Flags::Fix_heat_seeker_stealth_bug]) ) {
4437 continue;
4438 }
4439
4440 if (wip->wi_flags[Weapon::Info_Flags::Homing_javelin])
4441 {
4442 target_engines = ship_get_closest_subsys_in_sight(sp, SUBSYSTEM_ENGINE, &weapon_objp->pos);
4443
4444 if (!target_engines)
4445 continue;
4446 }
4447
4448 // MK, 9/4/99.
4449 // If this is a player object, make sure there aren't already too many homers.
4450 // Only in single player. In multiplayer, we don't want to restrict it in dogfight on team vs. team.
4451 // For co-op, it's probably also OK.
4452 if (!( Game_mode & GM_MULTIPLAYER ) && objp == Player_obj) {
4453 int num_homers = compute_num_homing_objects(objp);
4454 if (The_mission.ai_profile->max_allowed_player_homers[Game_skill_level] < num_homers)
4455 continue;
4456 }
4457 }
4458 else if (objp->type == OBJ_WEAPON)
4459 {
4460 //don't attempt to home on weapons if the weapon is a huge weapon or is a javelin homing weapon.
4461 if (wip->wi_flags[Weapon::Info_Flags::Huge, Weapon::Info_Flags::Homing_javelin])
4462 continue;
4463
4464 //don't look for local ssms that are gone for the time being
4465 if (Weapons[objp->instance].lssm_stage == 3)
4466 continue;
4467 }
4468
4469 vec3d vec_to_object;
4470 float dist = vm_vec_normalized_dir(&vec_to_object, &objp->pos, &weapon_objp->pos);
4471
4472 if (objp->type == OBJ_WEAPON && (Weapon_info[Weapons[objp->instance].weapon_info_index].wi_flags[Weapon::Info_Flags::Cmeasure])) {
4473 dist *= 0.5f;
4474 }
4475
4476 float dot = vm_vec_dot(&vec_to_object, &weapon_objp->orient.vec.fvec);
4477
4478 if (dot > wip->fov) {
4479 if (wip->auto_target_method == HomingAcquisitionType::CLOSEST && dist < best_dist) {
4480 best_dist = dist;
4481 wp->homing_object = objp;
4482 wp->target_sig = objp->signature;
4483 wp->homing_subsys = target_engines;
4484
4485 cmeasure_maybe_alert_success(objp);
4486 } else { // HomingAcquisitionType::RANDOM
4487 prospective_targets.push_back(objp);
4488 }
4489 }
4490 }
4491 }
4492 }
4493
4494 if (wip->auto_target_method == HomingAcquisitionType::RANDOM && prospective_targets.size() > 0) {
4495 // pick a random target from the valid ones
4496 object* target = prospective_targets[Random::next((int)prospective_targets.size())];
4497
4498 wp->homing_object = target;
4499 wp->target_sig = target->signature;
4500 wp->homing_subsys = nullptr;
4501
4502 if (wip->wi_flags[Weapon::Info_Flags::Homing_javelin] && target->type == OBJ_SHIP) {
4503 wp->homing_subsys = ship_get_closest_subsys_in_sight(&Ships[target->instance], SUBSYSTEM_ENGINE, &weapon_objp->pos);
4504 }
4505 }
4506
4507 if (wp->homing_object == Player_obj)
4508 weapon_maybe_play_warning(wp);
4509
4510 // if the old homing object is different that the new one, send a packet to clients
4511 if ( MULTIPLAYER_MASTER && (old_homing_objp != wp->homing_object) ) {
4512 send_homing_weapon_info( num );
4513 }
4514 }
4515
4516 /**
4517 * For all homing weapons, see if they should be decoyed by a countermeasure.
4518 */
find_homing_object_cmeasures(const SCP_vector<object * > & cmeasure_list)4519 void find_homing_object_cmeasures(const SCP_vector<object*> &cmeasure_list)
4520 {
4521 for (object *weapon_objp = GET_FIRST(&obj_used_list); weapon_objp != END_OF_LIST(&obj_used_list); weapon_objp = GET_NEXT(weapon_objp) ) {
4522 if (weapon_objp->type == OBJ_WEAPON) {
4523 weapon *wp = &Weapons[weapon_objp->instance];
4524 weapon_info *wip = &Weapon_info[wp->weapon_info_index];
4525
4526 if (wip->is_homing()) {
4527 float best_dot = wip->fov;
4528 for (auto cit = cmeasure_list.cbegin(); cit != cmeasure_list.cend(); ++cit) {
4529 //don't have a weapon try to home in on itself
4530 if (*cit == weapon_objp)
4531 continue;
4532
4533 weapon *cm_wp = &Weapons[(*cit)->instance];
4534 weapon_info *cm_wip = &Weapon_info[cm_wp->weapon_info_index];
4535
4536 //don't have a weapon try to home in on missiles fired by the same team, unless its the traitor team.
4537 if ((wp->team == cm_wp->team) && (wp->team != Iff_traitor))
4538 continue;
4539
4540 vec3d vec_to_object;
4541 float dist = vm_vec_normalized_dir(&vec_to_object, &(*cit)->pos, &weapon_objp->pos);
4542
4543 if (dist < cm_wip->cm_effective_rad)
4544 {
4545 float chance;
4546
4547 if (wp->cmeasure_ignore_list == nullptr) {
4548 wp->cmeasure_ignore_list = new SCP_vector<int>;
4549 }
4550 else {
4551 bool found = false;
4552 for (auto ii = wp->cmeasure_ignore_list->cbegin(); ii != wp->cmeasure_ignore_list->cend(); ++ii) {
4553 if ((*cit)->signature == *ii) {
4554 nprintf(("CounterMeasures", "Weapon (%s-%04i) already seen CounterMeasure (%s-%04i) Frame: %i\n",
4555 wip->name, weapon_objp->instance, cm_wip->name, (*cit)->signature, Framecount));
4556 found = true;
4557 break;
4558 }
4559 }
4560 if (found) {
4561 continue;
4562 }
4563 }
4564
4565 if (wip->wi_flags[Weapon::Info_Flags::Homing_aspect]) {
4566 // aspect seeker this likely to chase a countermeasure
4567 chance = cm_wip->cm_aspect_effectiveness/wip->seeker_strength;
4568 } else {
4569 // heat seeker and javelin HS this likely to chase a countermeasure
4570 chance = cm_wip->cm_heat_effectiveness/wip->seeker_strength;
4571 }
4572
4573 // remember this cmeasure so it can be ignored in future
4574 wp->cmeasure_ignore_list->push_back((*cit)->signature);
4575
4576 if (frand() >= chance) {
4577 // failed to decoy
4578 nprintf(("CounterMeasures", "Weapon (%s-%04i) ignoring CounterMeasure (%s-%04i) Frame: %i\n",
4579 wip->name, weapon_objp->instance, cm_wip->name, (*cit)->signature, Framecount));
4580 }
4581 else {
4582 // successful decoy, maybe chase the new cm
4583 float dot = vm_vec_dot(&vec_to_object, &weapon_objp->orient.vec.fvec);
4584
4585 if (dot > best_dot)
4586 {
4587 best_dot = dot;
4588 wp->homing_object = (*cit);
4589 cmeasure_maybe_alert_success((*cit));
4590 nprintf(("CounterMeasures", "Weapon (%s-%04i) chasing CounterMeasure (%s-%04i) Frame: %i\n",
4591 wip->name, weapon_objp->instance, cm_wip->name, (*cit)->signature, Framecount));
4592 }
4593 }
4594 }
4595 }
4596 }
4597 }
4598 }
4599 }
4600
4601 /**
4602 * Find object with signature "sig" and make weapon home on it.
4603 */
find_homing_object_by_sig(object * weapon_objp,int sig)4604 void find_homing_object_by_sig(object *weapon_objp, int sig)
4605 {
4606 ship_obj *sop;
4607 weapon *wp;
4608 object *old_homing_objp;
4609
4610 wp = &Weapons[weapon_objp->instance];
4611
4612 // save the old object so that multiplayer masters know whether to send a homing update packet
4613 old_homing_objp = wp->homing_object;
4614
4615 sop = GET_FIRST(&Ship_obj_list);
4616 while(sop != END_OF_LIST(&Ship_obj_list)) {
4617 object *objp;
4618
4619 objp = &Objects[sop->objnum];
4620 if (objp->signature == sig) {
4621 wp->homing_object = objp;
4622 wp->target_sig = objp->signature;
4623 break;
4624 }
4625
4626 sop = sop->next;
4627 }
4628
4629 // if the old homing object is different that the new one, send a packet to clients
4630 if ( MULTIPLAYER_MASTER && (old_homing_objp != wp->homing_object) ) {
4631 send_homing_weapon_info( weapon_objp->instance );
4632 }
4633 }
4634
aspect_should_lose_target(weapon * wp)4635 bool aspect_should_lose_target(weapon* wp)
4636 {
4637 Assert(wp != NULL);
4638
4639 if (wp->homing_object->signature != wp->target_sig) {
4640 if (wp->homing_object->type == OBJ_WEAPON)
4641 {
4642 weapon_info* target_info = &Weapon_info[Weapons[wp->homing_object->instance].weapon_info_index];
4643
4644 if (target_info->wi_flags[Weapon::Info_Flags::Cmeasure])
4645 {
4646 // Check if we can home on this countermeasure
4647 bool home_on_cmeasure = The_mission.ai_profile->flags[AI::Profile_Flags::Aspect_lock_countermeasure]
4648 || target_info->wi_flags[Weapon::Info_Flags::Cmeasure_aspect_home_on];
4649
4650 if (!home_on_cmeasure)
4651 {
4652 return true;
4653 }
4654 }
4655 }
4656 }
4657
4658 return false;
4659 }
4660
4661 /**
4662 * Make weapon num home. It's also object *obj.
4663 */
weapon_home(object * obj,int num,float frame_time)4664 void weapon_home(object *obj, int num, float frame_time)
4665 {
4666 weapon *wp;
4667 weapon_info *wip;
4668 object *hobjp;
4669
4670 Assert(obj->type == OBJ_WEAPON);
4671 Assert(obj->instance == num);
4672 wp = &Weapons[num];
4673 wip = &Weapon_info[wp->weapon_info_index];
4674 hobjp = Weapons[num].homing_object;
4675
4676 //local ssms home only in stages 1 and 5
4677 if ( (wp->lssm_stage==2) || (wp->lssm_stage==3) || (wp->lssm_stage==4))
4678 return;
4679
4680 float max_speed;
4681
4682 if ((wip->wi_flags[Weapon::Info_Flags::Local_ssm]) && (wp->lssm_stage==5))
4683 max_speed=wip->lssm_stage5_vel;
4684 else
4685 max_speed=wip->max_speed;
4686
4687 // If not [free-flight-time] gone by, don't home yet.
4688 // Goober5000 - this has been fixed back to more closely follow the original logic. Remember, the retail code
4689 // had 0.5 second of free flight time, the first half of which was spent ramping up to full speed.
4690 if ((hobjp == &obj_used_list) || ( f2fl(Missiontime - wp->creation_time) < (wip->free_flight_time / 2) )) {
4691 if (f2fl(Missiontime - wp->creation_time) > wip->free_flight_time) {
4692 // If this is a heat seeking homing missile and [free-flight-time] has elapsed since firing
4693 // and we don't have a target (else we wouldn't be inside the IF), find a new target.
4694 if (wip->wi_flags[Weapon::Info_Flags::Homing_heat]) {
4695 find_homing_object(obj, num);
4696 }
4697 // modders may want aspect homing missiles to die if they lose their target
4698 else if (wip->is_locked_homing() && wip->wi_flags[Weapon::Info_Flags::Die_on_lost_lock]) {
4699 if (wp->lifeleft > 0.5f) {
4700 wp->lifeleft = frand_range(0.1f, 0.5f); // randomise a bit to avoid multiple missiles detonating in one frame
4701 }
4702 return;
4703 }
4704 }
4705
4706 if (wip->acceleration_time > 0.0f) {
4707 if (Missiontime - wp->creation_time < fl2f(wip->acceleration_time)) {
4708 float t;
4709
4710 t = f2fl(Missiontime - wp->creation_time) / wip->acceleration_time;
4711 obj->phys_info.speed = wp->launch_speed + MAX(0.0f, wp->weapon_max_vel - wp->launch_speed) * t;
4712 }
4713 }
4714 // since free_flight_time can now be 0, guard against that
4715 else if (wip->free_flight_time > 0.0f) {
4716 if (obj->phys_info.speed > max_speed) {
4717 obj->phys_info.speed -= frame_time * 4;
4718 } else if ((obj->phys_info.speed < max_speed * wip->free_flight_speed_factor) && (wip->wi_flags[Weapon::Info_Flags::Homing_heat])) {
4719 obj->phys_info.speed = max_speed * wip->free_flight_speed_factor;
4720 }
4721 }
4722 // no free_flight_time, so immediately set desired speed
4723 else {
4724 obj->phys_info.speed = max_speed;
4725 }
4726
4727 // set velocity using whatever speed we have
4728 vm_vec_copy_scale( &obj->phys_info.desired_vel, &obj->orient.vec.fvec, obj->phys_info.speed);
4729
4730 return;
4731 }
4732
4733 if (wip->acceleration_time > 0.0f) {
4734 if (Missiontime - wp->creation_time < fl2f(wip->acceleration_time)) {
4735 float t;
4736
4737 t = f2fl(Missiontime - wp->creation_time) / wip->acceleration_time;
4738 obj->phys_info.speed = wp->launch_speed + (wp->weapon_max_vel - wp->launch_speed) * t;
4739 vm_vec_copy_scale( &obj->phys_info.desired_vel, &obj->orient.vec.fvec, obj->phys_info.speed);
4740 }
4741 }
4742
4743 // AL 4-8-98: If original target for aspect lock missile is lost, stop homing
4744 // WCS - or javelin
4745 if (wip->is_locked_homing()) {
4746 if ( wp->target_sig > 0 ) {
4747 if (aspect_should_lose_target(wp))
4748 {
4749 wp->homing_object = &obj_used_list;
4750 return;
4751 }
4752 }
4753 }
4754
4755 // AL 4-13-98: Stop homing on a subsystem if parent ship has changed
4756 if (wip->wi_flags[Weapon::Info_Flags::Homing_heat]) {
4757 if ( wp->target_sig > 0 ) {
4758 if ( wp->homing_object->signature != wp->target_sig ) {
4759 wp->homing_subsys = NULL;
4760 }
4761 }
4762 }
4763
4764 // If target subsys is dead make missile pick random spot on target as attack point.
4765 if (wp->homing_subsys != NULL) {
4766 if (wp->homing_subsys->flags[Ship::Subsystem_Flags::Missiles_ignore_if_dead]) {
4767 if ((wp->homing_subsys->max_hits > 0) && (wp->homing_subsys->current_hits <= 0)) {
4768 wp->homing_object = &obj_used_list;
4769 return;
4770 }
4771 }
4772 }
4773
4774 // Make sure Javelin HS missiles always home on engine subsystems if ships
4775 if ((wip->wi_flags[Weapon::Info_Flags::Homing_javelin]) &&
4776 (hobjp->type == OBJ_SHIP) &&
4777 (wp->target_sig > 0) &&
4778 (wp->homing_subsys != NULL) &&
4779 (wp->homing_subsys->system_info->type != SUBSYSTEM_ENGINE)) {
4780 int sindex = ship_get_by_signature(wp->target_sig);
4781 if (sindex >= 0) {
4782 ship *enemy = &Ships[sindex];
4783 wp->homing_subsys = ship_get_closest_subsys_in_sight(enemy, SUBSYSTEM_ENGINE, &Objects[wp->objnum].pos);
4784 }
4785 }
4786
4787 // If Javelin HS missile doesn't home in on a subsystem but homing in on a
4788 // ship, lose lock alltogether
4789 // Javelins can only home in one Engines or bombs.
4790 if ((wip->wi_flags[Weapon::Info_Flags::Homing_javelin]) &&
4791 (hobjp->type == OBJ_SHIP) &&
4792 (wp->target_sig > 0) &&
4793 (wp->homing_subsys == NULL)) {
4794 wp->homing_object = &obj_used_list;
4795 return;
4796 }
4797
4798 // TODO maybe add flag to allow WF_LOCKED_HOMING to lose target when target is dead
4799
4800 switch (hobjp->type) {
4801 case OBJ_NONE:
4802 if (wip->is_locked_homing()) {
4803 find_homing_object_by_sig(obj, wp->target_sig);
4804 }
4805 else {
4806 find_homing_object(obj, num);
4807 }
4808 return;
4809 break;
4810 case OBJ_SHIP:
4811 if (hobjp->signature != wp->target_sig) {
4812 if (wip->is_locked_homing()) {
4813 find_homing_object_by_sig(obj, wp->target_sig);
4814 }
4815 else {
4816 find_homing_object(obj, num);
4817 }
4818 return;
4819 }
4820 break;
4821 case OBJ_WEAPON:
4822 {
4823 weapon_info* hobj_infop = &Weapon_info[Weapons[hobjp->instance].weapon_info_index];
4824
4825 bool home_on_cmeasure = The_mission.ai_profile->flags[AI::Profile_Flags::Aspect_lock_countermeasure]
4826 || hobj_infop->wi_flags[Weapon::Info_Flags::Cmeasure_aspect_home_on];
4827
4828 // don't home on countermeasures or non-bombs, that's handled elsewhere
4829 if (((hobj_infop->wi_flags[Weapon::Info_Flags::Cmeasure]) && !home_on_cmeasure))
4830 {
4831 break;
4832 }
4833 else if (!(hobj_infop->wi_flags[Weapon::Info_Flags::Bomb]))
4834 {
4835 break;
4836 }
4837
4838 if (wip->is_locked_homing()) {
4839 find_homing_object_by_sig(obj, wp->target_sig);
4840 }
4841 else {
4842 find_homing_object(obj, num);
4843 }
4844 break;
4845 }
4846 default:
4847 return;
4848 }
4849
4850 // See if this weapon is the nearest homing object to the object it is homing on.
4851 // If so, update some fields in the target object's ai_info.
4852 if (hobjp != &obj_used_list) {
4853
4854 if (hobjp->type == OBJ_SHIP) {
4855 ai_info *aip;
4856
4857 aip = &Ai_info[Ships[hobjp->instance].ai_index];
4858
4859 vec3d target_vector;
4860 float dist = vm_vec_normalized_dir(&target_vector, &hobjp->pos, &obj->pos);
4861
4862 // add this missile to nearest_locked_object if its the closest
4863 // with the flag, only do it if its also mostly pointed at its target
4864 if (((aip->nearest_locked_object == -1) || (dist < aip->nearest_locked_distance)) &&
4865 (!(The_mission.ai_profile->flags[AI::Profile_Flags::Improved_missile_avoidance]) || vm_vec_dot(&target_vector, &obj->orient.vec.fvec) > 0.5f)) {
4866 aip->nearest_locked_object = OBJ_INDEX(obj);
4867 aip->nearest_locked_distance = dist;
4868 }
4869 }
4870 }
4871
4872 // If the object it is homing on is still valid, home some more!
4873 if (hobjp != &obj_used_list) {
4874 float old_dot, vel;
4875 vec3d vec_to_goal;
4876 vec3d target_pos; // position of what the homing missile is seeking
4877
4878 vm_vec_zero(&target_pos);
4879
4880 if (wp->weapon_flags[Weapon::Weapon_Flags::Overridden_homing]) {
4881 target_pos = wp->homing_pos;
4882 } else {
4883 // the homing missile may be seeking a subsystem on a ship. If so, we need to calculate the
4884 // world coordinates of that subsystem so the homing missile can seek it out.
4885 // For now, March 7, 1997, MK, heat seeking homing missiles will be able to home on
4886 // any subsystem. Probably makes sense for them to only home on certain kinds of subsystems.
4887 if ((wp->homing_subsys != nullptr) && !(wip->wi_flags[Weapon::Info_Flags::Non_subsys_homing]) &&
4888 hobjp->type == OBJ_SHIP) {
4889 get_subsystem_world_pos(hobjp, Weapons[num].homing_subsys, &target_pos);
4890 wp->homing_pos = target_pos; // store the homing position in weapon data
4891 Assert(!vm_is_vec_nan(&wp->homing_pos));
4892 } else {
4893 float fov;
4894 float dist;
4895
4896 dist = vm_vec_dist_quick(&obj->pos, &hobjp->pos);
4897 if (hobjp->type == OBJ_WEAPON) {
4898 weapon_info* hobj_infop = &Weapon_info[Weapons[hobjp->instance].weapon_info_index];
4899 if (hobj_infop->wi_flags[Weapon::Info_Flags::Cmeasure] && dist < hobj_infop->cm_detonation_rad) {
4900 // Make this missile detonate soon. Not right away, not sure why. Seems better.
4901 if (iff_x_attacks_y(Weapons[hobjp->instance].team, wp->team)) {
4902 detonate_nearby_missiles(hobjp, obj);
4903 return;
4904 }
4905 }
4906 }
4907
4908 fov = -1.0f;
4909
4910 int pick_homing_point = 0;
4911 if (IS_VEC_NULL(&wp->homing_pos)) {
4912 pick_homing_point = 1;
4913 }
4914
4915 // Update homing position if it hasn't been set, you're within 500 meters, or every half second,
4916 //approximately. For large objects, don't lead them.
4917 if (hobjp->radius < 40.0f) {
4918 target_pos = hobjp->pos;
4919 wp->homing_pos = target_pos;
4920 // only recalculate a homing point if you are not a client. You will get the new point from the server.
4921 } else if (pick_homing_point || (!MULTIPLAYER_CLIENT && ((dist < 500.0f) || (rand_chance(flFrametime, 2.0f))))) {
4922
4923 if (hobjp->type == OBJ_SHIP) {
4924 if (!pick_homing_point) {
4925 // ensure that current attack point is only updated in world coords (ie not pick a different
4926 // vertex)
4927 wp->pick_big_attack_point_timestamp = 0;
4928 }
4929
4930 if (pick_homing_point && !(wip->wi_flags[Weapon::Info_Flags::Non_subsys_homing])) {
4931 // If *any* player is parent of homing missile, then use position where lock indicator is
4932 if (Objects[obj->parent].flags[Object::Object_Flags::Player_ship]) {
4933 player* pp;
4934
4935 // determine the player
4936 pp = Player;
4937
4938 if (Game_mode & GM_MULTIPLAYER) {
4939 int pnum;
4940
4941 pnum = multi_find_player_by_object(&Objects[obj->parent]);
4942 if (pnum != -1) {
4943 pp = Net_players[pnum].m_player;
4944 }
4945 }
4946
4947 // If player has apect lock, we don't want to find a homing point on the closest
4948 // octant... setting the timestamp to 0 ensures this.
4949 if (wip->is_locked_homing()) {
4950 wp->pick_big_attack_point_timestamp = 0;
4951 } else {
4952 wp->pick_big_attack_point_timestamp = 1;
4953 }
4954
4955 if (pp && pp->locking_subsys) {
4956 wp->big_attack_point = pp->locking_subsys->system_info->pnt;
4957 } else {
4958 vm_vec_zero(&wp->big_attack_point);
4959 }
4960 }
4961 }
4962
4963 ai_big_pick_attack_point(hobjp, obj, &target_pos, fov);
4964
4965 } else {
4966 target_pos = hobjp->pos;
4967 }
4968
4969 wp->homing_pos = target_pos;
4970 Assert(!vm_is_vec_nan(&wp->homing_pos));
4971 } else
4972 target_pos = wp->homing_pos;
4973 }
4974 }
4975
4976 // Couldn't find a lock.
4977 if (IS_VEC_NULL(&target_pos))
4978 return;
4979
4980 // Cause aspect seeking weapon to home at target's predicted position.
4981 // But don't use predicted position if dot product small or negative.
4982 // If do this, with a ship headed towards missile, could choose a point behind missile.
4983 float dist_to_target, time_to_target;
4984
4985 dist_to_target = vm_vec_normalized_dir(&vec_to_goal, &target_pos, &obj->pos);
4986 time_to_target = dist_to_target/max_speed;
4987
4988 vec3d tvec;
4989 tvec = obj->phys_info.vel;
4990 vm_vec_normalize(&tvec);
4991
4992 old_dot = vm_vec_dot(&tvec, &vec_to_goal);
4993
4994 // If a weapon has missed its target, detonate it.
4995 // This solves the problem of a weapon circling the center of a subsystem that has been blown away.
4996 // Problem: It does not do impact damage, just proximity damage.
4997 if ((dist_to_target < flFrametime * obj->phys_info.speed * 4.0f + 10.0f) &&
4998 (old_dot < wip->fov) &&
4999 (wp->lifeleft > 0.01f) &&
5000 (wp->homing_object != &obj_used_list) &&
5001 (wp->homing_object->type == OBJ_SHIP) &&
5002 (wip->subtype != WP_LASER))
5003 {
5004 wp->lifeleft = 0.01f;
5005 }
5006
5007 // Only lead target if more than one second away. Otherwise can miss target. I think this
5008 // is what's causing Harbingers to miss the super destroyer. -- MK, 4/15/98
5009 if ((old_dot > 0.1f) && (time_to_target > 0.1f)) {
5010 if (wip->wi_flags[Weapon::Info_Flags::Variable_lead_homing]) {
5011 vm_vec_scale_add2(&target_pos, &hobjp->phys_info.vel, (0.33f * wip->target_lead_scaler * MIN(time_to_target, 6.0f)));
5012 } else if (wip->is_locked_homing()) {
5013 vm_vec_scale_add2(&target_pos, &hobjp->phys_info.vel, MIN(time_to_target, 2.0f));
5014 }
5015 }
5016
5017 // If a HEAT seeking (rather than ASPECT seeking) homing missile, verify that target is in viewcone.
5018 if (wip->wi_flags[Weapon::Info_Flags::Homing_heat]) {
5019 if ((old_dot < wip->fov) && (dist_to_target > wip->shockwave.inner_rad*1.1f)) { // Delay finding new target one frame to allow detonation.
5020 find_homing_object(obj, num);
5021 return; // Maybe found a new homing object. Return, process more next frame.
5022 } else // Subtract out life based on how far from target this missile points.
5023 if ((wip->fov < 0.95f) && !(wip->wi_flags[Weapon::Info_Flags::No_life_lost_if_missed])) {
5024 wp->lifeleft -= flFrametime * (0.95f - old_dot);
5025 }
5026 } else if (wip->is_locked_homing()) { // subtract life as if max turn is 90 degrees.
5027 if ((wip->fov < 0.95f) && !(wip->wi_flags[Weapon::Info_Flags::No_life_lost_if_missed]))
5028 wp->lifeleft -= flFrametime * (0.95f - old_dot);
5029 } else {
5030 Warning(LOCATION, "Tried to make weapon '%s' home, but found it wasn't aspect-seeking or heat-seeking or a Javelin!", wip->name);
5031 }
5032
5033
5034 // Control speed based on dot product to goal. If close to straight ahead, move
5035 // at max speed, else move slower based on how far from ahead.
5036 // Asteroth - but not for homing primaries
5037 if (old_dot < 0.90f && wip->subtype != WP_LASER) {
5038 obj->phys_info.speed = MAX(0.2f, old_dot* (float) fabs(old_dot));
5039 if (obj->phys_info.speed < max_speed*0.75f)
5040 obj->phys_info.speed = max_speed*0.75f;
5041 } else
5042 obj->phys_info.speed = max_speed;
5043
5044
5045 if (wip->acceleration_time > 0.0f) {
5046 // Ramp up speed linearly for the given duration
5047 if (Missiontime - wp->creation_time < fl2f(wip->acceleration_time)) {
5048 float t;
5049
5050 t = f2fl(Missiontime - wp->creation_time) / wip->acceleration_time;
5051 obj->phys_info.speed = wp->launch_speed + MAX(0.0f, wp->weapon_max_vel - wp->launch_speed) * t;
5052 }
5053 } else if (!(wip->wi_flags[Weapon::Info_Flags::No_homing_speed_ramp]) && Missiontime - wp->creation_time < i2f(1)) {
5054 // Default behavior:
5055 // For first second of weapon's life, it doesn't fly at top speed. It ramps up.
5056 float t;
5057
5058 t = f2fl(Missiontime - wp->creation_time);
5059 obj->phys_info.speed *= t*t;
5060 }
5061
5062 Assert( obj->phys_info.speed > 0.0f );
5063
5064 vm_vec_copy_scale( &obj->phys_info.desired_vel, &obj->orient.vec.fvec, obj->phys_info.speed);
5065
5066 // turn the missile towards the target only if non-swarm. Homing swarm missiles choose
5067 // a different vector to turn towards, this is done in swarm_update_direction().
5068 if ( wp->swarm_info_ptr == nullptr ) {
5069 ai_turn_towards_vector(&target_pos, obj, nullptr, nullptr, 0.0f, 0, nullptr);
5070 vel = vm_vec_mag(&obj->phys_info.desired_vel);
5071
5072 vm_vec_copy_scale(&obj->phys_info.desired_vel, &obj->orient.vec.fvec, vel);
5073
5074 }
5075 }
5076 }
5077
5078
5079 // moved out of weapon_process_post() so it can be called from either -post() or -pre() depending on Framerate_independent_turning
weapon_update_missiles(object * obj,float frame_time)5080 void weapon_update_missiles(object* obj, float frame_time) {
5081
5082 Assertion(obj->type == OBJ_WEAPON, "weapon_update_missiles called on a non-weapon object");
5083
5084 weapon* wp = &Weapons[obj->instance];
5085 weapon_info* wip = &Weapon_info[wp->weapon_info_index];
5086
5087 // a single player or multiplayer server function -- it affects actual weapon movement.
5088 if (wip->is_homing() && !(wp->weapon_flags[Weapon::Weapon_Flags::No_homing])) {
5089 vec3d pos_hold = wp->homing_pos;
5090 int target_hold = wp->target_sig;
5091
5092 weapon_home(obj, obj->instance, frame_time);
5093
5094 // tell the server to send an update of the missile.
5095 if (MULTIPLAYER_MASTER && !IS_VEC_NULL(&wp->homing_pos)) {
5096 wp->weapon_flags.set(Weapon::Weapon_Flags::Multi_homing_update_needed);
5097 // if the first update for a weapon has already been sent, then we do not need to send any others unless the homing_pos
5098 // drastically changes.
5099 if (wp->weapon_flags[Weapon::Weapon_Flags::Multi_Update_Sent]) {
5100 vm_vec_sub2(&pos_hold, &wp->homing_pos);
5101 if ((vm_vec_mag(&pos_hold) < 1.0f) && (wp->target_sig == target_hold)) {
5102 wp->weapon_flags.remove(Weapon::Weapon_Flags::Multi_homing_update_needed);
5103 }
5104 }
5105 }
5106
5107 // If this is a swarm type missile,
5108 if (wp->swarm_info_ptr != nullptr) {
5109 swarm_update_direction(obj, wp->swarm_info_ptr.get());
5110 }
5111 }
5112 else if (wip->acceleration_time > 0.0f) {
5113 if (Missiontime - wp->creation_time < fl2f(wip->acceleration_time)) {
5114 float t;
5115
5116 t = f2fl(Missiontime - wp->creation_time) / wip->acceleration_time;
5117 obj->phys_info.speed = wp->launch_speed + MAX(0.0f, wp->weapon_max_vel - wp->launch_speed) * t;
5118 }
5119 else {
5120 obj->phys_info.speed = wip->max_speed;
5121 obj->phys_info.flags |= PF_CONST_VEL; // Max speed reached, can use simpler physics calculations now
5122 }
5123
5124 vm_vec_copy_scale(&obj->phys_info.desired_vel, &obj->orient.vec.fvec, obj->phys_info.speed);
5125 }
5126 }
5127
5128 // as Mike K did with ships -- break weapon into process_pre and process_post for code to execute
5129 // before and after physics movement
5130
weapon_process_pre(object * obj,float frame_time)5131 void weapon_process_pre( object *obj, float frame_time)
5132 {
5133 if(obj->type != OBJ_WEAPON)
5134 return;
5135
5136 weapon *wp = &Weapons[obj->instance];
5137 weapon_info *wip = &Weapon_info[wp->weapon_info_index];
5138
5139 // if the object is a corkscrew style weapon, process it now
5140 if((obj->type == OBJ_WEAPON) && (Weapons[obj->instance].cscrew_index >= 0)){
5141 cscrew_process_pre(obj);
5142 }
5143
5144 //WMC - Originally flak_maybe_detonate, moved here.
5145 if(wp->det_range > 0.0f)
5146 {
5147 vec3d temp;
5148 vm_vec_sub(&temp, &obj->pos, &wp->start_pos);
5149 if(vm_vec_mag(&temp) >= wp->det_range){
5150 weapon_detonate(obj);
5151 }
5152 }
5153
5154 //WMC - Maybe detonate weapon anyway!
5155 if(wip->det_radius > 0.0f)
5156 {
5157 if((wp->homing_object != &obj_used_list) && (wp->homing_object->type != 0))
5158 {
5159 if(!IS_VEC_NULL(&wp->homing_pos) && vm_vec_dist(&wp->homing_pos, &obj->pos) <= wip->det_radius)
5160 {
5161 weapon_detonate(obj);
5162 }
5163 } else if(wp->target_num > -1)
5164 {
5165 if(vm_vec_dist(&obj->pos, &Objects[wp->target_num].pos) <= wip->det_radius)
5166 {
5167 weapon_detonate(obj);
5168 }
5169 }
5170 }
5171
5172 // If this flag is false missile turning is evaluated in weapon_process_post()
5173 if (Framerate_independent_turning) {
5174 weapon_update_missiles(obj, frame_time);
5175 }
5176 }
5177
5178 int Homing_hits = 0, Homing_misses = 0;
5179
5180
MONITOR(NumWeapons)5181 MONITOR( NumWeapons )
5182
5183 /**
5184 * Maybe play a "whizz sound" if close enough to view position
5185 */
5186 void weapon_maybe_play_flyby_sound(object *weapon_objp, weapon *wp)
5187 {
5188 // don't play flyby sounds too close together
5189 if ( !timestamp_elapsed(Weapon_flyby_sound_timer) ) {
5190 return;
5191 }
5192
5193 if ( !(wp->weapon_flags[Weapon::Weapon_Flags::Played_flyby_sound]) ) {
5194 float dist, dot, radius;
5195
5196 if ( (Weapon_info[wp->weapon_info_index].wi_flags[Weapon::Info_Flags::Corkscrew]) ) {
5197 dist = vm_vec_dist_quick(&weapon_objp->last_pos, &Eye_position);
5198 } else {
5199 dist = vm_vec_dist_quick(&weapon_objp->pos, &Eye_position);
5200 }
5201
5202 if ( Viewer_obj ) {
5203 radius = Viewer_obj->radius;
5204 } else {
5205 radius = 0.0f;
5206 }
5207
5208 if ( (dist > radius) && (dist < 55) ) {
5209 vec3d vec_to_weapon;
5210
5211 vm_vec_sub(&vec_to_weapon, &weapon_objp->pos, &Eye_position);
5212 vm_vec_normalize(&vec_to_weapon);
5213
5214 // ensure laser is in front of eye
5215 dot = vm_vec_dot(&vec_to_weapon, &Eye_matrix.vec.fvec);
5216 if ( dot < 0.1 ) {
5217 return;
5218 }
5219
5220 // ensure that laser is moving in similar direction to fvec
5221 dot = vm_vec_dot(&vec_to_weapon, &weapon_objp->orient.vec.fvec);
5222
5223 if ( (dot < -0.80) && (dot > -0.98) ) {
5224 if(Weapon_info[wp->weapon_info_index].flyby_snd.isValid()) {
5225 snd_play_3d( gamesnd_get_game_sound(Weapon_info[wp->weapon_info_index].flyby_snd), &weapon_objp->pos, &Eye_position );
5226 } else {
5227 if ( Weapon_info[wp->weapon_info_index].subtype == WP_LASER ) {
5228 snd_play_3d( gamesnd_get_game_sound(GameSounds::WEAPON_FLYBY), &weapon_objp->pos, &Eye_position );
5229 }
5230 }
5231 Weapon_flyby_sound_timer = timestamp(200);
5232 wp->weapon_flags.set(Weapon::Weapon_Flags::Played_flyby_sound);
5233 }
5234 }
5235 }
5236 }
5237
weapon_set_state(weapon_info * wip,weapon * wp,WeaponState state)5238 static void weapon_set_state(weapon_info* wip, weapon* wp, WeaponState state)
5239 {
5240 if (wp->weapon_state == state)
5241 {
5242 // No change
5243 return;
5244 }
5245
5246 wp->weapon_state = state;
5247
5248 auto map_entry = wip->state_effects.find(wp->weapon_state);
5249
5250 if (map_entry != wip->state_effects.end())
5251 {
5252 auto source = particle::ParticleManager::get()->createSource(map_entry->second);
5253
5254 source.moveToObject(&Objects[wp->objnum], &vmd_zero_vector);
5255 source.setWeaponState(wp->weapon_state);
5256
5257 source.finish();
5258 }
5259 }
5260
weapon_update_state(weapon * wp)5261 static void weapon_update_state(weapon* wp)
5262 {
5263 weapon_info* wip = &Weapon_info[wp->weapon_info_index];
5264
5265 if (wip->subtype == WP_LASER)
5266 {
5267 weapon_set_state(wip, wp, WeaponState::NORMAL);
5268 return;
5269 }
5270
5271 auto infree_flight = false;
5272 if (wip->free_flight_time)
5273 {
5274 fix lifetime = Missiontime - wp->creation_time;
5275 if (lifetime < fl2f(wip->free_flight_time))
5276 {
5277 weapon_set_state(wip, wp, WeaponState::FREEFLIGHT);
5278 infree_flight = true;
5279 }
5280 else if (lifetime >= fl2f(wip->free_flight_time) && // V indicates a valid homing_objp V
5281 (lifetime - Frametime) <= fl2f(wip->free_flight_time) && wp->homing_object != &obj_used_list)
5282 {
5283 weapon_set_state(wip, wp, WeaponState::IGNITION);
5284 infree_flight = true;
5285 }
5286 }
5287
5288 if (!infree_flight)
5289 { // V same here; means no homing_objp V
5290 if (wp->homing_object == &obj_used_list)
5291 {
5292 weapon_set_state(wip, wp, WeaponState::UNHOMED_FLIGHT);
5293 }
5294 else
5295 {
5296 weapon_set_state(wip, wp, WeaponState::HOMED_FLIGHT);
5297 }
5298 }
5299 }
5300
5301 // process a weapon after physics movement. MWA reorders some of the code on 8/13 for multiplayer. When
5302 // adding something to this function, decide whether or not a client in a multiplayer game needs to do
5303 // what is normally done in a single player game. Things like plotting an object on a radar, effect
5304 // for exhaust are things that are done on all machines. Things which calculate weapon targets, new
5305 // velocities, etc, are server only functions and should go after the if ( !MULTIPLAYER_MASTER ) statement
5306 // See Allender if you cannot decide what to do.
weapon_process_post(object * obj,float frame_time)5307 void weapon_process_post(object * obj, float frame_time)
5308 {
5309 int num;
5310 weapon_info *wip;
5311 weapon *wp;
5312
5313 MONITOR_INC( NumWeapons, 1 );
5314
5315 Assert(obj->type == OBJ_WEAPON);
5316
5317 num = obj->instance;
5318
5319 #ifndef NDEBUG
5320 int objnum;
5321 objnum = OBJ_INDEX(obj);
5322 Assert( Weapons[num].objnum == objnum );
5323 #endif
5324
5325 wp = &Weapons[num];
5326
5327 wp->lifeleft -= frame_time;
5328
5329 wip = &Weapon_info[wp->weapon_info_index];
5330
5331 // do continuous spawns
5332 if (wip->wi_flags[Weapon::Info_Flags::Spawn]) {
5333 for (int i = 0; i < wip->num_spawn_weapons_defined; i++) {
5334 if (wip->spawn_info[i].spawn_interval >= MINIMUM_SPAWN_INTERVAL) {
5335 if (timestamp_elapsed(wp->next_spawn_time[i])) {
5336 spawn_child_weapons(obj, i);
5337
5338 // do the spawn effect
5339 if (wip->spawn_info[i].spawn_effect.isValid()) {
5340 auto particleSource = particle::ParticleManager::get()->createSource(wip->spawn_info[i].spawn_effect);
5341 particleSource.moveTo(&obj->pos);
5342 particleSource.setOrientationFromVec(&obj->phys_info.vel);
5343 particleSource.finish();
5344 }
5345
5346 // update next_spawn_time
5347 wp->next_spawn_time[i] = timestamp() + (int)(wip->spawn_info[i].spawn_interval * 1000.f);
5348 }
5349 }
5350 }
5351 }
5352
5353 if (wip->wi_flags[Weapon::Info_Flags::Local_ssm])
5354 {
5355 if ((wp->lssm_stage != 5) && (wp->lssm_stage != 0))
5356 {
5357 wp->lifeleft += frame_time;
5358 }
5359 }
5360
5361
5362 // check life left. Multiplayer client code will go through here as well. We must be careful in weapon_hit
5363 // when killing a missile that spawn child weapons!!!!
5364 if ( wp->lifeleft < 0.0f ) {
5365 if ( wip->subtype & WP_MISSILE ) {
5366 if(Game_mode & GM_MULTIPLAYER){
5367 if ( !MULTIPLAYER_CLIENT || (MULTIPLAYER_CLIENT && (wip->wi_flags[Weapon::Info_Flags::Child]))) { // don't call this function multiplayer client -- host will send this packet to us
5368 weapon_detonate(obj);
5369 }
5370
5371 if (MULTIPLAYER_MASTER) {
5372 send_missile_kill_packet(obj);
5373 }
5374
5375 } else {
5376 weapon_detonate(obj);
5377 }
5378 if (wip->is_homing()) {
5379 Homing_misses++;
5380 }
5381 } else {
5382 obj->flags.set(Object::Object_Flags::Should_be_dead);
5383 }
5384 return;
5385 }
5386 else if ((MULTIPLAYER_MASTER) && (wip->is_locked_homing() && wp->weapon_flags[Weapon::Weapon_Flags::Multi_homing_update_needed])) {
5387 send_homing_weapon_info(obj->instance);
5388 wp->weapon_flags.remove(Weapon::Weapon_Flags::Multi_homing_update_needed);
5389 }
5390
5391 // plot homing missiles on the radar
5392 if (((wip->wi_flags[Weapon::Info_Flags::Bomb]) || (wip->wi_flags[Weapon::Info_Flags::Shown_on_radar])) && !(wip->wi_flags[Weapon::Info_Flags::Dont_show_on_radar])) {
5393 if ( hud_gauge_active(HUD_RADAR) ) {
5394 radar_plot_object( obj );
5395 }
5396 }
5397
5398 // trail missiles
5399 if ((wip->wi_flags[Weapon::Info_Flags::Trail]) && !(wip->wi_flags[Weapon::Info_Flags::Corkscrew])) {
5400 if ( (wp->trail_ptr != NULL ) && (wp->lssm_stage!=3)) {
5401 vec3d pos;
5402
5403 if (wip->render_type == WRT_LASER) {
5404 // place tail origin in center of the bolt
5405 vm_vec_scale_add(&pos, &obj->pos, &obj->orient.vec.fvec, (wip->laser_length / 2));
5406 } else {
5407 pos = obj->pos;
5408 }
5409
5410 if (trail_stamp_elapsed(wp->trail_ptr)) {
5411
5412 trail_add_segment( wp->trail_ptr, &pos, &obj->orient);
5413
5414 trail_set_stamp(wp->trail_ptr);
5415 } else {
5416 trail_set_segment( wp->trail_ptr, &pos );
5417 }
5418
5419 }
5420 }
5421
5422 if ( wip->wi_flags[Weapon::Info_Flags::Thruster] ) {
5423 ship_do_weapon_thruster_frame( wp, obj, flFrametime );
5424 }
5425
5426 // maybe play a "whizz sound" if close enough to view position
5427 #ifndef NDEBUG
5428 if ( Weapon_flyby_sound_enabled ) {
5429 weapon_maybe_play_flyby_sound(obj, wp);
5430 }
5431 #else
5432 weapon_maybe_play_flyby_sound(obj, wp);
5433 #endif
5434
5435 if(wip->wi_flags[Weapon::Info_Flags::Particle_spew] && wp->lssm_stage != 3 ){
5436 weapon_maybe_spew_particle(obj);
5437 }
5438
5439 // If this flag is true this is evaluated in weapon_process_pre()
5440 if (!Framerate_independent_turning) {
5441 weapon_update_missiles(obj, frame_time);
5442 }
5443
5444 //handle corkscrew missiles
5445 if (wip->is_homing() && !(wp->weapon_flags[Weapon::Weapon_Flags::No_homing]) && wp->cscrew_index >= 0) {
5446 cscrew_process_post(obj);
5447 }
5448
5449 //local ssm stuff
5450 if (wip->wi_flags[Weapon::Info_Flags::Local_ssm])
5451 {
5452 //go into subspace if the missile is locked and its time to warpout
5453 if ((wp->lssm_stage==1) && (timestamp_elapsed(wp->lssm_warpout_time)))
5454 {
5455 //if we don't have a lock at this point, just stay in normal space
5456 if (wp->homing_object == &obj_used_list)
5457 {
5458 wp->lssm_stage=0;
5459 return;
5460 }
5461
5462 //point where to warpout
5463 vec3d warpout;
5464
5465 //create a warp effect
5466 vm_vec_copy_scale(&warpout,&obj->phys_info.vel,3.0f);
5467
5468 //get the size of the warp, directly using the model if possible
5469 float warp_size = obj->radius;
5470 if (wip->model_num >= 0)
5471 warp_size = model_get_radius(wip->model_num);
5472
5473 //set the time the warphole stays open, minimum of 7 seconds
5474 wp->lssm_warp_time = ((warp_size * 2) / (obj->phys_info.speed)) +1.5f;
5475 wp->lssm_warp_time = MAX(wp->lssm_warp_time,7.0f);
5476
5477 //calculate the percerentage of the warpholes life at which the missile is fully in subspace.
5478 wp->lssm_warp_pct = 1.0f - (3.0f/wp->lssm_warp_time);
5479
5480 //create the warphole
5481 vm_vec_add2(&warpout,&obj->pos);
5482 wp->lssm_warp_idx = fireball_create(&warpout, wip->lssm_warpeffect, FIREBALL_WARP_EFFECT, -1, warp_size * 1.5f, true, &vmd_zero_vector, wp->lssm_warp_time, 0, &obj->orient);
5483
5484 if (wp->lssm_warp_idx < 0) {
5485 mprintf(("LSSM: Failed to create warp effect! Please report if this happens frequently.\n"));
5486 // Abort warping
5487 wp->lssm_stage = 0;
5488 } else {
5489 wp->lssm_stage = 2;
5490 }
5491 }
5492
5493 //its just entered subspace subspace. don't collide or render
5494 if ((wp->lssm_stage==2) && (fireball_lifeleft_percent(&Objects[wp->lssm_warp_idx]) <= wp->lssm_warp_pct))
5495 {
5496 auto flags = obj->flags;
5497 flags.remove(Object::Object_Flags::Renders);
5498 flags.remove(Object::Object_Flags::Collides);
5499
5500 obj_set_flags(obj, flags);
5501
5502 // stop its trail here
5503 if (wp->trail_ptr != nullptr) {
5504 trail_object_died(wp->trail_ptr);
5505 wp->trail_ptr = nullptr;
5506 }
5507
5508 //get the position of the target, and estimate its position when it warps out
5509 //so we have an idea of where it will be.
5510 auto target_objp = wp->homing_object;
5511 if (target_objp == nullptr && wp->target_num != -1) {
5512 target_objp = &Objects[wp->target_num];
5513 }
5514
5515 if (target_objp != nullptr)
5516 {
5517 vm_vec_scale_add(&wp->lssm_target_pos, &target_objp->pos, &target_objp->phys_info.vel, (float)wip->lssm_warpin_delay / 1000.0f);
5518 }
5519 else
5520 {
5521 // Our target is invalid, just jump to our position
5522 wp->lssm_target_pos = obj->pos;
5523 }
5524
5525 wp->lssm_stage=3;
5526
5527 }
5528
5529 //time to warp in.
5530 if ((wp->lssm_stage==3) && (timestamp_elapsed(wp->lssm_warpin_time)))
5531 {
5532
5533 vec3d warpin;
5534 object* target_objp=wp->homing_object;
5535 vec3d fvec;
5536 matrix orient;
5537
5538 //spawn the ssm at a random point in a circle around the target
5539 vm_vec_random_in_circle(&warpin, &wp->lssm_target_pos, &target_objp->orient, wip->lssm_warpin_radius + target_objp->radius, true);
5540
5541 //orient the missile properly
5542 vm_vec_sub(&fvec,&wp->lssm_target_pos, &warpin);
5543 vm_vector_2_matrix(&orient,&fvec,NULL,NULL);
5544
5545 //get the size of the warp, directly using the model if possible
5546 float warp_size = obj->radius;
5547 if (wip->model_num >= 0)
5548 warp_size = model_get_radius(wip->model_num);
5549
5550 //create a warpin effect
5551 wp->lssm_warp_idx = fireball_create(&warpin, wip->lssm_warpeffect, FIREBALL_WARP_EFFECT, -1, warp_size * 1.5f, false, &vmd_zero_vector, wp->lssm_warp_time, 0, &orient);
5552
5553 if (wp->lssm_warp_idx < 0) {
5554 mprintf(("LSSM: Failed to create warp effect! Please report if this happens frequently.\n"));
5555 }
5556
5557 obj->orient=orient;
5558 obj->pos=warpin;
5559 obj->phys_info.speed=0;
5560 obj->phys_info.desired_vel = vmd_zero_vector;
5561 obj->phys_info.vel = obj->phys_info.desired_vel;
5562
5563 wp->lssm_stage = 4;
5564 }
5565
5566 //done warping in. render and collide it. let the fun begin
5567 // If the previous fireball creation failed just put it into normal space now
5568 if ((wp->lssm_stage==4) && (wp->lssm_warp_idx < 0 || fireball_lifeleft_percent(&Objects[wp->lssm_warp_idx]) <=0.5f))
5569 {
5570 vm_vec_copy_scale(&obj->phys_info.desired_vel, &obj->orient.vec.fvec, wip->lssm_stage5_vel );
5571 obj->phys_info.vel = obj->phys_info.desired_vel;
5572 obj->phys_info.speed = vm_vec_mag(&obj->phys_info.desired_vel);
5573
5574 wp->lssm_stage=5;
5575
5576 auto flags = obj->flags;
5577 flags.set(Object::Object_Flags::Renders);
5578 flags.set(Object::Object_Flags::Collides);
5579
5580 obj_set_flags(obj,flags);
5581
5582 // start the trail back up if applicable
5583 if (wip->wi_flags[Weapon::Info_Flags::Trail]) {
5584 wp->trail_ptr = trail_create(&wip->tr_info);
5585
5586 if (wp->trail_ptr != nullptr) {
5587 // Add two segments. One to stay at launch pos, one to move.
5588 trail_add_segment(wp->trail_ptr, &obj->pos, &obj->orient);
5589 trail_add_segment(wp->trail_ptr, &obj->pos, &obj->orient);
5590 }
5591 }
5592 }
5593 }
5594
5595 if (wip->hud_in_flight_snd.isValid() && obj->parent_sig == Player_obj->signature)
5596 {
5597 bool play_sound = false;
5598 switch (wip->in_flight_play_type)
5599 {
5600 case TARGETED:
5601 play_sound = wp->homing_object != &obj_used_list;
5602 break;
5603 case UNTARGETED:
5604 play_sound = wp->homing_object == &obj_used_list;
5605 break;
5606 case ALWAYS:
5607 play_sound = true;
5608 break;
5609 default:
5610 Error(LOCATION, "Unknown in-flight sound status %d!", (int) wip->in_flight_play_type);
5611 break;
5612 }
5613
5614 if (play_sound)
5615 {
5616 if (!wp->hud_in_flight_snd_sig.isValid() || !snd_is_playing(wp->hud_in_flight_snd_sig)) {
5617 wp->hud_in_flight_snd_sig = snd_play_looping(gamesnd_get_game_sound(wip->hud_in_flight_snd));
5618 }
5619 }
5620 }
5621
5622 weapon_update_state(wp);
5623 }
5624
5625 /**
5626 * Update weapon tracking information.
5627 */
weapon_set_tracking_info(int weapon_objnum,int parent_objnum,int target_objnum,int target_is_locked,ship_subsys * target_subsys)5628 void weapon_set_tracking_info(int weapon_objnum, int parent_objnum, int target_objnum, int target_is_locked, ship_subsys *target_subsys)
5629 {
5630 object *parent_objp;
5631 weapon *wp;
5632 weapon_info *wip;
5633 int targeting_same = 0;
5634
5635 if ( weapon_objnum < 0 ) {
5636 return;
5637 }
5638
5639 Assert(Objects[weapon_objnum].type == OBJ_WEAPON);
5640
5641 wp = &Weapons[Objects[weapon_objnum].instance];
5642 wip = &Weapon_info[wp->weapon_info_index];
5643
5644 if (wp->weapon_flags[Weapon::Weapon_Flags::No_homing]) {
5645 return;
5646 }
5647
5648 if (parent_objnum >= 0) {
5649 parent_objp = &Objects[parent_objnum];
5650 Assert(parent_objp->type == OBJ_SHIP);
5651 } else {
5652 parent_objp = NULL;
5653 }
5654
5655 if (parent_objp != NULL && (Ships[parent_objp->instance].flags[Ship::Ship_Flags::No_secondary_lockon])) {
5656 wp->weapon_flags.set(Weapon::Weapon_Flags::No_homing);
5657 wp->homing_object = &obj_used_list;
5658 wp->homing_subsys = NULL;
5659 wp->target_num = -1;
5660 wp->target_sig = -1;
5661
5662 return;
5663 }
5664
5665 if ( parent_objp == NULL || Ships[parent_objp->instance].ai_index >= 0 ) {
5666 int target_team = -1;
5667 if ( target_objnum >= 0 ) {
5668 int obj_type = Objects[target_objnum].type;
5669
5670 if ( (obj_type == OBJ_SHIP) || (obj_type == OBJ_WEAPON) ) {
5671 target_team = obj_team(&Objects[target_objnum]);
5672
5673 }
5674 }
5675
5676 // determining if we're targeting the same team
5677 if (parent_objp != NULL && Ships[parent_objp->instance].team == target_team){
5678 targeting_same = 1;
5679
5680 // Goober5000 - if we're going bonkers, pretend we're not targeting our own team
5681 ai_info *parent_aip = &Ai_info[Ships[parent_objp->instance].ai_index];
5682 if (parent_aip->active_goal != AI_GOAL_NONE && parent_aip->active_goal != AI_ACTIVE_GOAL_DYNAMIC) {
5683 if (parent_aip->goals[parent_aip->active_goal].flags[AI::Goal_Flags::Target_own_team]) {
5684 targeting_same = 0;
5685 }
5686 }
5687 } else {
5688 targeting_same = 0;
5689 }
5690
5691 bool can_lock = (weapon_has_iff_restrictions(wip) && target_objnum >= 0) ? weapon_target_satisfies_lock_restrictions(wip, &Objects[target_objnum]) :
5692 (!targeting_same || (MULTI_DOGFIGHT && (target_team == Iff_traitor)));
5693
5694 // Cyborg17 - exclude all invalid object numbers here since in multi, the lock slots can get out of sync.
5695 if ((target_objnum > -1) && (target_objnum < (MAX_OBJECTS)) && can_lock) {
5696 wp->target_num = target_objnum;
5697 wp->target_sig = Objects[target_objnum].signature;
5698 if ( (wip->wi_flags[Weapon::Info_Flags::Homing_aspect]) && target_is_locked) {
5699 wp->homing_object = &Objects[target_objnum];
5700 wp->homing_subsys = target_subsys;
5701 weapon_maybe_play_warning(wp);
5702 } else if ( (wip->wi_flags[Weapon::Info_Flags::Homing_javelin]) && target_is_locked) {
5703 if ((Objects[target_objnum].type == OBJ_SHIP) &&
5704 ( (wp->homing_subsys == NULL) ||
5705 (wp->homing_subsys->system_info->type != SUBSYSTEM_ENGINE) )) {
5706 ship *target_ship = &Ships[Objects[target_objnum].instance];
5707 wp->homing_subsys = ship_get_closest_subsys_in_sight(target_ship, SUBSYSTEM_ENGINE, &Objects[weapon_objnum].pos);
5708 if (wp->homing_subsys == NULL) {
5709 wp->homing_object = &obj_used_list;
5710 } else {
5711 Assert(wp->homing_subsys->parent_objnum == target_objnum);
5712 wp->homing_object = &Objects[target_objnum];
5713 weapon_maybe_play_warning(wp);
5714 }
5715 } else {
5716 wp->homing_object = &Objects[target_objnum];
5717 wp->homing_subsys = target_subsys;
5718 weapon_maybe_play_warning(wp);
5719 }
5720 } else if ( wip->wi_flags[Weapon::Info_Flags::Homing_heat] ) {
5721 // Make a heat seeking missile try to home. If the target is outside the view cone, it will
5722 // immediately drop it and try to find one in its view cone.
5723 if ((target_objnum != -1) && !(wip->wi_flags[Weapon::Info_Flags::Untargeted_heat_seeker])) {
5724 wp->homing_object = &Objects[target_objnum];
5725 wp->homing_subsys = target_subsys;
5726 weapon_maybe_play_warning(wp);
5727 } else {
5728 wp->homing_object = &obj_used_list;
5729 wp->homing_subsys = NULL;
5730 }
5731 }
5732 } else {
5733 wp->target_num = -1;
5734 wp->target_sig = -1;
5735 }
5736
5737 // If missile is locked on target, increase its lifetime by 20% since missiles can be fired at limit of range
5738 // as defined by velocity*lifeleft, but missiles often slow down a bit, plus can be fired at a moving away target.
5739 // Confusing to many players when their missiles run out of gas before getting to target.
5740 // DB - removed 7:14 pm 9/6/99. was totally messing up lifetimes for all weapons.
5741 // MK, 7:11 am, 9/7/99. Put it back in, but with a proper check here to make sure it's an aspect seeker and
5742 // put a sanity check in the color changing laser code that was broken by this code.
5743 if (target_is_locked && (wp->target_num != -1) && (wip->is_locked_homing()) ) {
5744 wp->lifeleft *= LOCKED_HOMING_EXTENDED_LIFE_FACTOR;
5745 }
5746
5747 ai_update_danger_weapon(target_objnum, weapon_objnum);
5748 }
5749 }
5750
get_pointer_to_weapon_fire_pattern_index(int weapon_type,int ship_idx,ship_subsys * src_turret)5751 size_t* get_pointer_to_weapon_fire_pattern_index(int weapon_type, int ship_idx, ship_subsys * src_turret)
5752 {
5753 Assertion(ship_idx >= 0 && ship_idx < MAX_SHIPS, "Invalid ship index in get_pointer_to_weapon_fire_pattern_index()");
5754 ship* shipp = &Ships[ship_idx];
5755 ship_weapon* ship_weapon_p = &(shipp->weapons);
5756 if(src_turret)
5757 {
5758 ship_weapon_p = &src_turret->weapons;
5759 }
5760 Assert( ship_weapon_p != NULL );
5761
5762 // search for the corresponding bank pattern index for the weapon_type that is being fired.
5763 // Note: Because a weapon_type may not be unique to a weapon bank per ship this search may attribute
5764 // the weapon to the wrong bank. Hopefully this isn't a problem.
5765 for ( int pi = 0; pi < MAX_SHIP_PRIMARY_BANKS; pi++ ) {
5766 if ( ship_weapon_p->primary_bank_weapons[pi] == weapon_type ) {
5767 return &(ship_weapon_p->primary_bank_pattern_index[pi]);
5768 }
5769 }
5770 for ( int si = 0; si < MAX_SHIP_SECONDARY_BANKS; si++ ) {
5771 if ( ship_weapon_p->secondary_bank_weapons[si] == weapon_type ) {
5772 return &(ship_weapon_p->secondary_bank_pattern_index[si]);
5773 }
5774 }
5775 return NULL;
5776 }
5777
5778 /**
5779 * Create a weapon object
5780 *
5781 * @return Index of weapon in the Objects[] array, -1 if the weapon object was not created
5782 */
5783 int Weapons_created = 0;
weapon_create(vec3d * pos,matrix * porient,int weapon_type,int parent_objnum,int group_id,int is_locked,int is_spawned,float fof_cooldown,ship_subsys * src_turret)5784 int weapon_create( vec3d * pos, matrix * porient, int weapon_type, int parent_objnum, int group_id, int is_locked, int is_spawned, float fof_cooldown, ship_subsys * src_turret)
5785 {
5786 int n, objnum;
5787 int num_deleted;
5788 object *objp, *parent_objp=NULL;
5789 weapon *wp;
5790 weapon_info *wip;
5791
5792 Assert(weapon_type >= 0 && weapon_type < weapon_info_size());
5793
5794 wip = &Weapon_info[weapon_type];
5795
5796 // beam weapons should never come through here!
5797 if(wip->wi_flags[Weapon::Info_Flags::Beam])
5798 {
5799 Warning(LOCATION, "An attempt to fire a beam ('%s') through weapon_create() was made.\n", wip->name);
5800 return -1;
5801 }
5802
5803 parent_objp = NULL;
5804 if(parent_objnum >= 0){
5805 parent_objp = &Objects[parent_objnum];
5806 }
5807
5808 if ( (wip->num_substitution_patterns > 0) && (parent_objp != NULL)) {
5809 // using substitution
5810
5811 // get to the instance of the gun
5812 Assertion( parent_objp->type == OBJ_SHIP, "Expected type OBJ_SHIP, got %d", parent_objp->type );
5813 Assertion( (parent_objp->instance < MAX_SHIPS) && (parent_objp->instance >= 0),
5814 "Ship index is %d, which is out of range [%d,%d)", parent_objp->instance, 0, MAX_SHIPS);
5815 ship* parent_shipp = &(Ships[parent_objp->instance]);
5816 Assert( parent_shipp != NULL );
5817
5818 size_t *position = get_pointer_to_weapon_fire_pattern_index(weapon_type, parent_objp->instance, src_turret);
5819 Assertion( position != NULL, "'%s' is trying to fire a weapon that is not selected", Ships[parent_objp->instance].ship_name );
5820
5821 size_t curr_pos = *position;
5822 if (((Weapon_info[weapon_type].subtype == WP_LASER && parent_shipp->flags[Ship::Ship_Flags::Primary_linked]) ||
5823 (Weapon_info[weapon_type].subtype == WP_MISSILE && parent_shipp->flags[Ship::Ship_Flags::Secondary_dual_fire])) &&
5824 (curr_pos > 0)) {
5825 curr_pos--;
5826 }
5827 ++(*position);
5828 *position = (*position) % wip->num_substitution_patterns;
5829
5830 if ( wip->weapon_substitution_pattern[curr_pos] == -1 ) {
5831 // weapon doesn't want any sub
5832 return -1;
5833 } else if ( wip->weapon_substitution_pattern[curr_pos] != weapon_type ) {
5834 // weapon wants to sub with weapon other than me
5835 return weapon_create(pos, porient, wip->weapon_substitution_pattern[curr_pos], parent_objnum, group_id, is_locked, is_spawned, fof_cooldown);
5836 }
5837 }
5838
5839 // Let's setup a fast failure check with a uniform distribution.
5840 if (wip->failure_rate > 0.0f) {
5841 util::UniformFloatRange rng(0.0f, 1.0f);
5842 float test = rng.next();
5843 if (test < wip->failure_rate) {
5844 if (wip->failure_sub != -1) {
5845 return weapon_create(pos, porient, wip->failure_sub, parent_objnum, group_id, is_locked, is_spawned, fof_cooldown);
5846 } else {
5847 return -1;
5848 }
5849 }
5850 }
5851
5852 num_deleted = 0;
5853 if (Num_weapons >= MAX_WEAPONS-5) {
5854
5855 num_deleted = collide_remove_weapons();
5856
5857 mprintf(("Deleted %d weapons because of lack of slots.\n", num_deleted));
5858 if (num_deleted == 0){
5859 return -1;
5860 }
5861 }
5862
5863 for (n=0; n<MAX_WEAPONS; n++ ){
5864 if (Weapons[n].weapon_info_index < 0){
5865 break;
5866 }
5867 }
5868
5869 if (n == MAX_WEAPONS) {
5870 // if we supposedly deleted weapons above, what happened here!!!!
5871 if (num_deleted){
5872 Int3(); // get allender -- something funny is going on!!!
5873 }
5874
5875 return -1;
5876 }
5877
5878 // make sure we are loaded and useable
5879 if ( (wip->render_type == WRT_POF) && (wip->model_num < 0) ) {
5880 wip->model_num = model_load(wip->pofbitmap_name, 0, NULL);
5881
5882 if (wip->model_num < 0) {
5883 Int3();
5884 return -1;
5885 }
5886 }
5887
5888 // make sure that our textures are loaded as well
5889 if ( !used_weapons[weapon_type] )
5890 weapon_load_bitmaps(weapon_type);
5891
5892 //I am hopeing that this way does not alter the input orient matrix
5893 //Feild of Fire code -Bobboau
5894 matrix morient;
5895 matrix *orient;
5896
5897 if(porient != NULL) {
5898 morient = *porient;
5899 } else {
5900 morient = vmd_identity_matrix;
5901 }
5902
5903 orient = &morient;
5904
5905 float combined_fof = wip->field_of_fire;
5906 // If there is a fof_cooldown value, increase the spread linearly
5907 if (fof_cooldown != 0.0f) {
5908 combined_fof = wip->field_of_fire + (fof_cooldown * wip->max_fof_spread);
5909 }
5910
5911 if(combined_fof > 0.0f){
5912 vec3d f;
5913 vm_vec_random_cone(&f, &orient->vec.fvec, combined_fof);
5914 vm_vec_normalize(&f);
5915 vm_vector_2_matrix( orient, &f, NULL, NULL);
5916 }
5917
5918 Weapons_created++;
5919 flagset<Object::Object_Flags> default_flags;
5920 default_flags.set(Object::Object_Flags::Renders);
5921 default_flags.set(Object::Object_Flags::Physics);
5922
5923 if (!wip->wi_flags[Weapon::Info_Flags::No_collide])
5924 default_flags.set(Object::Object_Flags::Collides);
5925
5926 if (wip->wi_flags[Weapon::Info_Flags::Can_damage_shooter])
5927 default_flags.set(Object::Object_Flags::Collides_with_parent);
5928
5929 // mark this object creation as essential, if it is created by a player.
5930 // You don't want players mysteriously wondering why they aren't firing.
5931 objnum = obj_create( OBJ_WEAPON, parent_objnum, n, orient, pos, 2.0f, default_flags, (parent_objp != nullptr && parent_objp->flags[Object::Object_Flags::Player_ship]));
5932
5933 if (objnum < 0) {
5934 mprintf(("A weapon failed to be created because FSO is running out of object slots!\n"));
5935 return -1;
5936 }
5937
5938 objp = &Objects[objnum];
5939
5940 // Create laser n!
5941 wp = &Weapons[n];
5942
5943 // check if laser or dumbfire missile
5944 // set physics flag to allow optimization
5945 if (((wip->subtype == WP_LASER) || (wip->subtype == WP_MISSILE)) && !(wip->is_homing()) && wip->acceleration_time == 0.0f) {
5946 // set physics flag
5947 objp->phys_info.flags |= PF_CONST_VEL;
5948 }
5949
5950 wp->start_pos = *pos;
5951 wp->objnum = objnum;
5952 wp->model_instance_num = -1;
5953 wp->homing_object = &obj_used_list; // Assume not homing on anything.
5954 wp->homing_subsys = NULL;
5955 wp->creation_time = Missiontime;
5956 wp->group_id = group_id;
5957
5958 // we don't necessarily need a parent
5959 if(parent_objp != NULL){
5960 Assert(parent_objp->type == OBJ_SHIP); // Get Mike, a non-ship has fired a weapon!
5961 Assert((parent_objp->instance >= 0) && (parent_objp->instance < MAX_SHIPS));
5962 wp->team = Ships[parent_objp->instance].team;
5963 wp->species = Ship_info[Ships[parent_objp->instance].ship_info_index].species;
5964 } else {
5965 // ugh - we need to prevent bad array accesses
5966 wp->team = Iff_traitor;
5967 wp->species = 0;
5968 }
5969 wp->turret_subsys = NULL;
5970 vm_vec_zero(&wp->homing_pos);
5971 wp->weapon_flags.reset();
5972 wp->target_sig = -1;
5973 wp->cmeasure_ignore_list = nullptr;
5974 wp->det_range = wip->det_range;
5975
5976 // Init the thruster info
5977 wp->thruster_bitmap = -1;
5978 wp->thruster_frame = 0.0f;
5979 wp->thruster_glow_bitmap = -1;
5980 wp->thruster_glow_noise = 1.0f;
5981 wp->thruster_glow_frame = 0.0f;
5982
5983 // init the laser info
5984 wp->laser_bitmap_frame = 0.0f;
5985 wp->laser_glow_bitmap_frame = 0.0f;
5986
5987 // init the weapon state
5988 wp->weapon_state = WeaponState::INVALID;
5989
5990 if ( wip->wi_flags[Weapon::Info_Flags::Swarm] ) {
5991 wp->swarm_info_ptr.reset(new swarm_info);
5992 swarm_create(objp, wp->swarm_info_ptr.get());
5993 }
5994
5995 // if this is a particle spewing weapon, setup some stuff
5996 if (wip->wi_flags[Weapon::Info_Flags::Particle_spew]) {
5997 for (size_t s = 0; s < MAX_PARTICLE_SPEWERS; s++) { // allow for multiple time values
5998 if (wip->particle_spewers[s].particle_spew_type != PSPEW_NONE) {
5999 wp->particle_spew_time[s] = -1;
6000 wp->particle_spew_rand = frand_range(0, PI2); // per weapon randomness
6001 }
6002 }
6003 }
6004
6005 // assign the network signature. The starting sig is sent to all clients, so this call should
6006 // result in the same net signature numbers getting assigned to every player in the game
6007 if ( Game_mode & GM_MULTIPLAYER ) {
6008 if(wip->subtype == WP_MISSILE){
6009 Objects[objnum].net_signature = multi_assign_network_signature( MULTI_SIG_NON_PERMANENT );
6010
6011 // for weapons that respawn, add the number of respawnable weapons to the net signature pool
6012 // to reserve N signatures for the spawned weapons
6013 if ( wip->wi_flags[Weapon::Info_Flags::Spawn] ){
6014 multi_set_network_signature( (ushort)(Objects[objnum].net_signature + wip->maximum_children_spawned), MULTI_SIG_NON_PERMANENT );
6015 }
6016 } else {
6017 Objects[objnum].net_signature = multi_assign_network_signature( MULTI_SIG_NON_PERMANENT );
6018 }
6019 // for multiplayer clients, when creating lasers, add some more life to the lasers. This helps
6020 // to overcome some problems associated with lasers dying on client machine before they get message
6021 // from server saying it hit something.
6022 }
6023
6024 //Check if we want to gen a random number
6025 //This is used for lifetime min/max
6026 float rand_val;
6027 if ( Game_mode & GM_NORMAL ){
6028 rand_val = frand();
6029 } else {
6030 rand_val = static_randf(Objects[objnum].net_signature);
6031 }
6032
6033 wp->weapon_info_index = weapon_type;
6034 if(wip->life_min < 0.0f && wip->life_max < 0.0f) {
6035 wp->lifeleft = wip->lifetime;
6036 } else {
6037 wp->lifeleft = ((rand_val) * (wip->life_max - wip->life_min)) + wip->life_min;
6038 if((wip->wi_flags[Weapon::Info_Flags::Cmeasure]) && (parent_objp != NULL) && (parent_objp->flags[Object::Object_Flags::Player_ship])) {
6039 wp->lifeleft *= The_mission.ai_profile->cmeasure_life_scale[Game_skill_level];
6040 }
6041 }
6042
6043 if(wip->wi_flags[Weapon::Info_Flags::Cmeasure]) {
6044 // For the next two frames, any non-timer-based countermeasures will pulse each frame.
6045 Cmeasures_homing_check = 2;
6046 if (wip->cmeasure_timer_interval > 0) {
6047 // Timer-based countermeasures spawn pulsing as well.
6048 wp->cmeasure_timer = timestamp(); // Could also use timestamp(0), but it doesn't really matter either way.
6049 }
6050 }
6051
6052 // Make remote detonate missiles look like they're getting detonated by firer simply by giving them variable lifetimes.
6053 if (parent_objp != NULL && !(parent_objp->flags[Object::Object_Flags::Player_ship]) && (wip->wi_flags[Weapon::Info_Flags::Remote])) {
6054 wp->lifeleft = wp->lifeleft/2.0f + rand_val * wp->lifeleft/2.0f;
6055 }
6056
6057 objp->phys_info.mass = wip->mass;
6058 objp->phys_info.side_slip_time_const = 0.0f;
6059 objp->phys_info.rotdamp = wip->turn_accel_time ? wip->turn_accel_time / 2.f : 0.0f;
6060 vm_vec_zero(&objp->phys_info.max_vel);
6061 objp->phys_info.max_vel.xyz.z = wip->max_speed;
6062 vm_vec_zero(&objp->phys_info.max_rotvel);
6063 objp->shield_quadrant[0] = wip->damage;
6064 if (wip->weapon_hitpoints > 0){
6065 objp->hull_strength = (float) wip->weapon_hitpoints;
6066 } else {
6067 objp->hull_strength = 0.0f;
6068 }
6069
6070 if (wip->collision_radius_override > 0.0f)
6071 objp->radius = wip->collision_radius_override;
6072 else if ( wip->render_type == WRT_POF ) {
6073 // this should have been checked above, but let's be extra sure
6074 Assert(wip->model_num >= 0);
6075
6076 objp->radius = model_get_radius(wip->model_num);
6077
6078 // Always create an instance in case we need them
6079 if (model_get(wip->model_num)->flags & PM_FLAG_HAS_INTRINSIC_ROTATE || !wip->on_create_program.isEmpty()) {
6080 wp->model_instance_num = model_create_instance(false, wip->model_num);
6081 }
6082 } else if ( wip->render_type == WRT_LASER ) {
6083 objp->radius = wip->laser_head_radius;
6084 }
6085
6086 // Set desired velocity and initial velocity.
6087 // For lasers, velocity is always the same.
6088 // For missiles, it is a small amount plus the firing ship's velocity.
6089 // For missiles, the velocity trends towards some goal.
6090 // Note: If you change how speed works here, such as adding in speed of parent ship, you'll need to change the AI code
6091 // that predicts collision points. See Mike Kulas or Dave Andsager. (Or see ai_get_weapon_speed().)
6092 bool already_inherited_parent_speed = false;
6093 if (wip->acceleration_time > 0.0f) {
6094 vm_vec_copy_scale(&objp->phys_info.desired_vel, &objp->orient.vec.fvec, 0.01f ); // Tiny initial velocity to avoid possible null vec issues
6095 objp->phys_info.vel = objp->phys_info.desired_vel;
6096 objp->phys_info.speed = 0.0f;
6097 wp->launch_speed = 0.0f;
6098 } else if (!(wip->is_homing())) {
6099 vm_vec_copy_scale(&objp->phys_info.desired_vel, &objp->orient.vec.fvec, objp->phys_info.max_vel.xyz.z );
6100 objp->phys_info.vel = objp->phys_info.desired_vel;
6101 objp->phys_info.speed = vm_vec_mag(&objp->phys_info.desired_vel);
6102 } else {
6103 // For weapons that home, set velocity to sum of the component of parent's velocity in the fire direction and freeflight speed factor(default 1/4)
6104 // Note that it is important to extract the component of parent's velocity in the fire direction to factor out sliding, else
6105 // the missile will not be moving forward.
6106 if(parent_objp != NULL){
6107 if (wip->free_flight_time > 0.0) {
6108 vm_vec_copy_scale(&objp->phys_info.desired_vel, &objp->orient.vec.fvec, vm_vec_dot(&parent_objp->phys_info.vel, &objp->orient.vec.fvec) + objp->phys_info.max_vel.xyz.z * wip->free_flight_speed_factor);
6109 already_inherited_parent_speed = true;
6110 }
6111 else
6112 vm_vec_copy_scale(&objp->phys_info.desired_vel, &objp->orient.vec.fvec, objp->phys_info.max_vel.xyz.z*wip->free_flight_speed_factor );
6113 } else {
6114 if (!is_locked && wip->free_flight_time > 0.0)
6115 {
6116 vm_vec_copy_scale(&objp->phys_info.desired_vel, &objp->orient.vec.fvec, objp->phys_info.max_vel.xyz.z* wip->free_flight_speed_factor);
6117 }
6118 else
6119 {
6120 vm_vec_copy_scale(&objp->phys_info.desired_vel, &objp->orient.vec.fvec, objp->phys_info.max_vel.xyz.z );
6121 }
6122 }
6123 objp->phys_info.vel = objp->phys_info.desired_vel;
6124 objp->phys_info.speed = vm_vec_mag(&objp->phys_info.vel);
6125 }
6126
6127 wp->weapon_max_vel = objp->phys_info.max_vel.xyz.z;
6128
6129 // Turey - maybe make the initial speed of the weapon take into account the velocity of the parent.
6130 // Improves aiming during gliding.
6131 if ((parent_objp != nullptr) && (The_mission.ai_profile->flags[AI::Profile_Flags::Use_additive_weapon_velocity]) && !(already_inherited_parent_speed)) {
6132 float pspeed = vm_vec_mag( &parent_objp->phys_info.vel );
6133 vm_vec_scale_add2( &objp->phys_info.vel, &parent_objp->phys_info.vel, wip->vel_inherit_amount );
6134 wp->weapon_max_vel += pspeed * wip->vel_inherit_amount;
6135 objp->phys_info.speed = vm_vec_mag(&objp->phys_info.vel);
6136
6137 if (wip->acceleration_time > 0.0f)
6138 wp->launch_speed += pspeed;
6139 }
6140
6141 // create the corkscrew
6142 if ( wip->wi_flags[Weapon::Info_Flags::Corkscrew] ) {
6143 wp->cscrew_index = (short)cscrew_create(objp);
6144 } else {
6145 wp->cscrew_index = -1;
6146 }
6147
6148 if (wip->wi_flags[Weapon::Info_Flags::Local_ssm])
6149 {
6150
6151 Assert(parent_objp); //local ssms must have a parent
6152
6153 wp->lssm_warpout_time=timestamp(wip->lssm_warpout_delay);
6154 wp->lssm_warpin_time=timestamp(wip->lssm_warpout_delay + wip->lssm_warpin_delay);
6155 wp->lssm_stage=1;
6156 }
6157 else{
6158 wp->lssm_stage=-1;
6159 }
6160
6161
6162 // if this is a flak weapon shell, make it so
6163 // NOTE : this function will change some fundamental things about the weapon object
6164 if ( (wip->wi_flags[Weapon::Info_Flags::Flak]) && !(wip->wi_flags[Weapon::Info_Flags::Render_flak]) ) {
6165 obj_set_flags(&Objects[wp->objnum], Objects[wp->objnum].flags - Object::Object_Flags::Renders);
6166 }
6167
6168 wp->missile_list_index = -1;
6169 // If this is a missile, then add it to the Missile_obj_list
6170 if ( wip->subtype == WP_MISSILE ) {
6171 wp->missile_list_index = missile_obj_list_add(objnum);
6172 }
6173
6174 if (wip->wi_flags[Weapon::Info_Flags::Trail] /*&& !(wip->wi_flags[Weapon::Info_Flags::Corkscrew]) */) {
6175 wp->trail_ptr = trail_create(&wip->tr_info);
6176
6177 if ( wp->trail_ptr != NULL ) {
6178 // Add two segments. One to stay at launch pos, one to move.
6179 trail_add_segment( wp->trail_ptr, &objp->pos, &objp->orient );
6180 trail_add_segment( wp->trail_ptr, &objp->pos, &objp->orient );
6181 }
6182 }
6183 else
6184 {
6185 //If a weapon has no trails, make sure we don't try to do anything with them.
6186 wp->trail_ptr = NULL;
6187 }
6188
6189 // Ensure weapon flyby sound doesn't get played for player lasers
6190 if ( parent_objp != NULL && parent_objp == Player_obj ) {
6191 wp->weapon_flags.set(Weapon::Weapon_Flags::Played_flyby_sound);
6192 }
6193
6194 wp->pick_big_attack_point_timestamp = timestamp(1);
6195
6196 if (wip->wi_flags[Weapon::Info_Flags::Spawn]) {
6197 for (int i = 0; i < wip->num_spawn_weapons_defined; i++) {
6198 if (wip->spawn_info[i].spawn_interval >= MINIMUM_SPAWN_INTERVAL) {
6199 int delay;
6200 if (wip->spawn_info[i].spawn_interval_delay < 0.f)
6201 delay = (int)(wip->spawn_info[i].spawn_interval * 1000);
6202 else
6203 delay = (int)(wip->spawn_info[i].spawn_interval_delay * 1000);
6204
6205 if (delay < 10)
6206 delay = 10; // cap at 10 ms
6207 wp->next_spawn_time[i] = timestamp(delay);
6208 }
6209 else
6210 wp->next_spawn_time[i] = INT_MAX; // basically never expire
6211 }
6212 }
6213
6214 // Set detail levels for POF-type weapons.
6215 if (Weapon_info[wp->weapon_info_index].model_num != -1) {
6216 polymodel * pm;
6217 int i;
6218 pm = model_get(Weapon_info[wp->weapon_info_index].model_num);
6219
6220 for (i=0; i<pm->n_detail_levels; i++){
6221 // for weapons, detail levels are all preset to -1
6222 if (wip->detail_distance[i] >= 0)
6223 pm->detail_depth[i] = i2fl(wip->detail_distance[i]);
6224 else
6225 pm->detail_depth[i] = (objp->radius*20.0f + 20.0f) * i;
6226 }
6227
6228 #ifndef NDEBUG
6229 // since debug builds always have cheats enabled, we don't necessarily get the chance
6230 // to enable thrusters for previously non-loaded weapons (ie, weapons_page_in_cheats())
6231 // when using cheat-keys, so we need to make sure and enable thrusters here if needed
6232 if (pm->n_thrusters > 0) {
6233 wip->wi_flags.set(Weapon::Info_Flags::Thruster);
6234 }
6235 #endif
6236 }
6237
6238 // if the weapon was fired locked
6239 if(is_locked){
6240 wp->weapon_flags.set(Weapon::Weapon_Flags::Locked_when_fired);
6241 }
6242
6243 //if the weapon was spawned from a spawning type weapon
6244 if(is_spawned){
6245 wp->weapon_flags.set(Weapon::Weapon_Flags::Spawned);
6246 }
6247
6248 wp->alpha_current = -1.0f;
6249 wp->alpha_backward = 0;
6250
6251 wp->collisionInfo = nullptr;
6252 wp->hud_in_flight_snd_sig = sound_handle::invalid();
6253
6254 Num_weapons++;
6255
6256 if (Weapons_inherit_parent_collision_group) {
6257 Objects[objnum].collision_group_id = Objects[parent_objnum].collision_group_id;
6258 }
6259
6260 weapon_update_state(wp);
6261
6262 if (wip->render_type == WRT_LASER) {
6263 // No model so we also have no submodel
6264 wip->on_create_program.start(&Objects[objnum], &vmd_zero_vector, &vmd_identity_matrix, -1);
6265 } else if (wip->render_type == WRT_POF) {
6266 // We have a model so we can specify a subobject
6267 wip->on_create_program.start(&Objects[objnum],
6268 &vmd_zero_vector,
6269 &vmd_identity_matrix,
6270 model_get(wip->model_num)->detail[0]);
6271 }
6272
6273 if (wip->ambient_snd.isValid()) {
6274 obj_snd_assign(objnum, wip->ambient_snd, &vmd_zero_vector , OS_MAIN);
6275 }
6276
6277 if (Script_system.IsActiveAction(CHA_ONWEAPONCREATED)) {
6278 Script_system.SetHookObject("Weapon", &Objects[objnum]);
6279 Script_system.RunCondition(CHA_ONWEAPONCREATED);
6280 Script_system.RemHookVar("Weapon");
6281 }
6282
6283 return objnum;
6284 }
6285
6286 /**
6287 * Spawn child weapons from object *objp.
6288 * Spawns all non-continuous spawn weapons when the overrides are -1 (on detonation)
6289 * When they are provided it is for continuous spawn weapons
6290 */
spawn_child_weapons(object * objp,int spawn_index_override)6291 void spawn_child_weapons(object *objp, int spawn_index_override)
6292 {
6293 int child_id;
6294 int parent_num;
6295 ushort starting_sig;
6296 weapon *wp = NULL;
6297 beam *bp = NULL;
6298 weapon_info *wip, *child_wip;
6299 vec3d *opos, *fvec;
6300
6301 Assertion(objp->type == OBJ_WEAPON || objp->type == OBJ_BEAM, "spawn_child_weapons() doesn't make sense for non-weapon non-beam objects; get a coder!\n");
6302 Assertion(objp->instance >= 0, "spawn_child_weapons() called with an object with an instance of %d; get a coder!\n", objp->instance);
6303 Assertion(!(objp->type == OBJ_WEAPON) || (objp->instance < MAX_WEAPONS), "spawn_child_weapons() called with a weapon with an instance of %d while MAX_WEAPONS is %d; get a coder!\n", objp->instance, MAX_WEAPONS);
6304 Assertion(!(objp->type == OBJ_BEAM) || (objp->instance < MAX_BEAMS), "spawn_child_weapons() called with a beam with an instance of %d while MAX_BEAMS is %d; get a coder!\n", objp->instance, MAX_BEAMS);
6305
6306 if (objp->type == OBJ_WEAPON) {
6307 wp = &Weapons[objp->instance];
6308 Assertion((wp->weapon_info_index >= 0) && (wp->weapon_info_index < weapon_info_size()), "Invalid weapon_info_index of %d; get a coder!\n", wp->weapon_info_index);
6309 wip = &Weapon_info[wp->weapon_info_index];
6310 } else if (objp->type == OBJ_BEAM) {
6311 bp = &Beams[objp->instance];
6312 Assertion((bp->weapon_info_index >= 0) && (bp->weapon_info_index < weapon_info_size()), "Invalid weapon_info_index of %d; get a coder!\n", bp->weapon_info_index);
6313 wip = &Weapon_info[bp->weapon_info_index];
6314 } else { // Let's make sure we don't do screwball things in a release build if this gets called with a non-weapon non-beam.
6315 return;
6316 }
6317
6318 parent_num = objp->parent;
6319
6320 if (parent_num >= 0) {
6321 if ((Objects[parent_num].type != objp->parent_type) || (Objects[parent_num].signature != objp->parent_sig)) {
6322 mprintf(("Warning: Parent of spawn weapon does not exist. Not spawning.\n"));
6323 return;
6324 }
6325 }
6326
6327 ship* parent_shipp;
6328 parent_shipp = &Ships[Objects[objp->parent].instance];
6329 starting_sig = 0;
6330
6331 if ( Game_mode & GM_MULTIPLAYER ) {
6332 // get the next network signature and save it. Set the next usable network signature to be
6333 // the passed in objects signature + 1. We "reserved" N of these slots when we created objp
6334 // for it's spawned children.
6335 starting_sig = multi_get_next_network_signature( MULTI_SIG_NON_PERMANENT );
6336 multi_set_network_signature( objp->net_signature, MULTI_SIG_NON_PERMANENT );
6337 }
6338
6339 opos = &objp->pos;
6340 fvec = &objp->orient.vec.fvec;
6341
6342 // as opposed to continuous spawn
6343 bool detonate_spawn = spawn_index_override == -1;
6344
6345 int start_index = detonate_spawn ? 0 : spawn_index_override;
6346
6347 for (int i = start_index; i < wip->num_spawn_weapons_defined; i++)
6348 {
6349 // don't spawn continuous spawning weapons on detonation
6350 if (detonate_spawn && wip->spawn_info[i].spawn_interval >= MINIMUM_SPAWN_INTERVAL)
6351 continue;
6352
6353 // maybe roll for spawning
6354 int spawn_count;
6355 if (wip->spawn_info[i].spawn_chance < 1.f) {
6356 spawn_count = 0;
6357 for (int j = 0; j < wip->spawn_info[i].spawn_count; j++) {
6358 if (frand() < wip->spawn_info[i].spawn_chance)
6359 spawn_count++;
6360 }
6361 }
6362 else {
6363 spawn_count = wip->spawn_info[i].spawn_count;
6364 }
6365
6366 for (int j = 0; j < spawn_count; j++)
6367 {
6368 int weapon_objnum;
6369 vec3d tvec, pos;
6370 matrix orient;
6371
6372 child_id = wip->spawn_info[i].spawn_type;
6373 child_wip = &Weapon_info[child_id];
6374
6375 // for multiplayer, use the static randvec functions based on the network signatures to provide
6376 // the randomness so that it is the same on all machines.
6377 if ( Game_mode & GM_MULTIPLAYER ) {
6378 if (wip->spawn_info[i].spawn_min_angle <= 0)
6379 static_rand_cone(objp->net_signature + j, &tvec, fvec, wip->spawn_info[i].spawn_angle);
6380 else
6381 static_rand_cone(objp->net_signature + j, &tvec, fvec, wip->spawn_info[i].spawn_min_angle, wip->spawn_info[i].spawn_angle);
6382 } else {
6383 if(wip->spawn_info[i].spawn_min_angle <= 0)
6384 vm_vec_random_cone(&tvec, fvec, wip->spawn_info[i].spawn_angle);
6385 else
6386 vm_vec_random_cone(&tvec, fvec, wip->spawn_info[i].spawn_min_angle, wip->spawn_info[i].spawn_angle);
6387 }
6388 vm_vec_scale_add(&pos, opos, &tvec, objp->radius);
6389
6390 // Let's allow beam-spawn! -MageKing17
6391 if (child_wip->wi_flags[Weapon::Info_Flags::Beam]) {
6392 beam_fire_info fire_info;
6393 memset(&fire_info, 0, sizeof(beam_fire_info));
6394
6395 fire_info.accuracy = 0.000001f; // this will guarantee a hit
6396 fire_info.shooter = &Objects[parent_num];
6397 fire_info.turret = NULL;
6398 if (child_wip->wi_flags[Weapon::Info_Flags::Inherit_parent_target] && wp->homing_object != &obj_used_list)
6399 fire_info.target = wp->homing_object;
6400 else
6401 fire_info.target = nullptr;
6402 fire_info.target_subsys = NULL;
6403 fire_info.target_pos1 = fire_info.target_pos2 = pos;
6404 fire_info.bfi_flags |= BFIF_FLOATING_BEAM | BFIF_TARGETING_COORDS;
6405 fire_info.starting_pos = *opos;
6406 fire_info.beam_info_index = child_id;
6407 fire_info.team = static_cast<char>(obj_team(&Objects[parent_num]));
6408 fire_info.fire_method = BFM_SPAWNED;
6409 fire_info.burst_index = j;
6410 // we would normally accumulate this per burst rotation, which we can't do
6411 // but we still need to do this once for the beam because it could be a negative, randomizing rotation
6412 fire_info.per_burst_rotation = child_wip->b_info.t5info.per_burst_rot;
6413
6414 // fire the beam
6415 beam_fire(&fire_info);
6416 } else {
6417 vm_vector_2_matrix(&orient, &tvec, nullptr, nullptr);
6418 weapon_objnum = weapon_create(&pos, &orient, child_id, parent_num, -1, wp->weapon_flags[Weapon::Weapon_Flags::Locked_when_fired], 1);
6419
6420 //if the child inherits parent target, do it only if the parent weapon was locked to begin with
6421 if ((child_wip->wi_flags[Weapon::Info_Flags::Inherit_parent_target]) && (wp->homing_object != &obj_used_list))
6422 {
6423 //Deal with swarm weapons
6424 if (wp->swarm_info_ptr != nullptr) {
6425 weapon_set_tracking_info(weapon_objnum, parent_num, wp->swarm_info_ptr->homing_objnum, 1, wp->homing_subsys);
6426 } else {
6427 weapon_set_tracking_info(weapon_objnum, parent_num, wp->target_num, 1, wp->homing_subsys);
6428 }
6429 }
6430
6431 // Assign a little randomness to lifeleft so they don't all disappear at the same time.
6432 if (weapon_objnum != -1) {
6433 float rand_val;
6434
6435 if ( Game_mode & GM_NORMAL ){
6436 rand_val = frand();
6437 } else {
6438 rand_val = static_randf(objp->net_signature + j);
6439 }
6440
6441 Weapons[Objects[weapon_objnum].instance].lifeleft *= rand_val*0.4f + 0.8f;
6442 if (child_wip->wi_flags[Weapon::Info_Flags::Remote]) {
6443 parent_shipp->weapons.detonate_weapon_time = timestamp((int)(DEFAULT_REMOTE_DETONATE_TRIGGER_WAIT * 1000));
6444 parent_shipp->weapons.remote_detonaters_active++;
6445 }
6446 }
6447 }
6448 }
6449
6450 // only spawn one type if continuous spawn
6451 if (!detonate_spawn)
6452 break;
6453 }
6454
6455 // in multiplayer, reset the next network signature to the one that was saved.
6456 if ( Game_mode & GM_MULTIPLAYER ){
6457 multi_set_network_signature( starting_sig, MULTI_SIG_NON_PERMANENT );
6458 }
6459 }
6460
6461 /**
6462 * Figures out whether to play disarmed or armed hit sound, checks that the
6463 * chosen one exists, and plays it
6464 */
weapon_play_impact_sound(weapon_info * wip,vec3d * hitpos,bool is_armed)6465 void weapon_play_impact_sound(weapon_info *wip, vec3d *hitpos, bool is_armed)
6466 {
6467 if(is_armed)
6468 {
6469 if(wip->impact_snd.isValid()) {
6470 snd_play_3d( gamesnd_get_game_sound(wip->impact_snd), hitpos, &Eye_position );
6471 }
6472 }
6473 else
6474 {
6475 if(wip->disarmed_impact_snd.isValid()) {
6476 snd_play_3d(gamesnd_get_game_sound(wip->disarmed_impact_snd), hitpos, &Eye_position);
6477 }
6478 }
6479 }
6480
6481 /**
6482 * Play a sound effect when a weapon hits a ship
6483 *
6484 * To elimate the "stereo" effect of two lasers hitting at nearly
6485 * the same time, and to reduce the number of sound channels used,
6486 * only play one impact sound if IMPACT_SOUND_DELTA has elapsed
6487 *
6488 * @note Uses Weapon_impact_timer global for timer variable
6489 */
weapon_hit_do_sound(object * hit_obj,weapon_info * wip,vec3d * hitpos,bool is_armed,int quadrant)6490 void weapon_hit_do_sound(object *hit_obj, weapon_info *wip, vec3d *hitpos, bool is_armed, int quadrant)
6491 {
6492 float shield_str;
6493
6494 // If non-missiles (namely lasers) expire without hitting a ship, don't play impact sound
6495 if ( wip->subtype != WP_MISSILE ) {
6496 if ( !hit_obj ) {
6497 // flak weapons make sounds
6498 if(wip->wi_flags[Weapon::Info_Flags::Flak])
6499 {
6500 weapon_play_impact_sound(wip, hitpos, is_armed);
6501 }
6502 return;
6503 }
6504
6505 switch(hit_obj->type) {
6506 case OBJ_SHIP:
6507 // do nothing
6508 break;
6509
6510 case OBJ_ASTEROID:
6511 if ( timestamp_elapsed(Weapon_impact_timer) ) {
6512 weapon_play_impact_sound(wip, hitpos, is_armed);
6513 Weapon_impact_timer = timestamp(IMPACT_SOUND_DELTA);
6514 }
6515 return;
6516 break;
6517
6518 default:
6519 return;
6520 }
6521 }
6522
6523 if ( hit_obj == NULL ) {
6524 weapon_play_impact_sound(wip, hitpos, is_armed);
6525 return;
6526 }
6527
6528 if ( timestamp_elapsed(Weapon_impact_timer) ) {
6529
6530 if ( hit_obj->type == OBJ_SHIP && quadrant >= 0 ) {
6531 shield_str = ship_quadrant_shield_strength(hit_obj, quadrant);
6532 } else {
6533 shield_str = 0.0f;
6534 }
6535
6536 // play a shield hit if shields are above 10% max in this quadrant
6537 if ( shield_str > 0.1f ) {
6538 // Play a shield impact sound effect
6539 if ( hit_obj == Player_obj ) {
6540 snd_play_3d( gamesnd_get_game_sound(GameSounds::SHIELD_HIT_YOU), hitpos, &Eye_position );
6541 // AL 12-15-97: Add missile impact sound even when shield is hit
6542 if ( wip->subtype == WP_MISSILE ) {
6543 snd_play_3d( gamesnd_get_game_sound(GameSounds::PLAYER_HIT_MISSILE), hitpos, &Eye_position);
6544 }
6545 } else {
6546 snd_play_3d( gamesnd_get_game_sound(GameSounds::SHIELD_HIT), hitpos, &Eye_position );
6547 }
6548 } else {
6549 // Play a hull impact sound effect
6550 switch ( wip->subtype ) {
6551 case WP_LASER:
6552 if ( hit_obj == Player_obj )
6553 snd_play_3d( gamesnd_get_game_sound(GameSounds::PLAYER_HIT_LASER), hitpos, &Eye_position );
6554 else {
6555 weapon_play_impact_sound(wip, hitpos, is_armed);
6556 }
6557 break;
6558 case WP_MISSILE:
6559 if ( hit_obj == Player_obj )
6560 snd_play_3d( gamesnd_get_game_sound(GameSounds::PLAYER_HIT_MISSILE), hitpos, &Eye_position);
6561 else {
6562 weapon_play_impact_sound(wip, hitpos, is_armed);
6563 }
6564 break;
6565 default:
6566 nprintf(("Warning","WARNING ==> Cannot determine sound to play for weapon impact\n"));
6567 break;
6568 } // end switch
6569 }
6570
6571 Weapon_impact_timer = timestamp(IMPACT_SOUND_DELTA);
6572 }
6573 }
6574
6575 extern bool turret_weapon_has_flags(ship_weapon *swp, Weapon::Info_Flags flags);
6576
6577 /**
6578 * Distrupt any subsystems that fall into damage sphere of this Electronics missile
6579 *
6580 * @param ship_objp Pointer to ship that holds subsystem
6581 * @param blast_pos World pos of weapon blast
6582 * @param wi_index Weapon info index of weapon causing blast
6583 */
weapon_do_electronics_effect(object * ship_objp,vec3d * blast_pos,int wi_index)6584 void weapon_do_electronics_effect(object *ship_objp, vec3d *blast_pos, int wi_index)
6585 {
6586 weapon_info *wip;
6587 ship *shipp;
6588 ship_subsys *ss;
6589 model_subsystem *psub;
6590 vec3d subsys_world_pos;
6591 float dist;
6592
6593 shipp = &Ships[ship_objp->instance];
6594 wip = &Weapon_info[wi_index];
6595
6596 for ( ss = GET_FIRST(&shipp->subsys_list); ss != END_OF_LIST(&shipp->subsys_list); ss = GET_NEXT(ss) )
6597 {
6598 psub = ss->system_info;
6599
6600 // convert subsys point to world coords
6601 vm_vec_unrotate(&subsys_world_pos, &psub->pnt, &ship_objp->orient);
6602 vm_vec_add2(&subsys_world_pos, &ship_objp->pos);
6603
6604 // see if subsys point is within damage sphere
6605 dist = vm_vec_dist_quick(blast_pos, &subsys_world_pos);
6606 if ( dist < wip->shockwave.outer_rad )
6607 {
6608 float disrupt_time = (float)wip->elec_time;
6609
6610 //use new style electronics disruption
6611 if (wip->elec_use_new_style)
6612 {
6613 //if its an engine subsytem, take the multiplier into account
6614 if (psub->type==SUBSYSTEM_ENGINE)
6615 {
6616 disrupt_time*=wip->elec_eng_mult;
6617 }
6618
6619 //if its a turret or weapon subsytem, take the multiplier into account
6620 if ((psub->type==SUBSYSTEM_TURRET) || (psub->type==SUBSYSTEM_WEAPONS))
6621 {
6622 //disrupt beams
6623 //WMC - do this even if there are other types of weapons on the turret.
6624 //I figure, the big fancy electronics on beams will be used for the other
6625 //weapons as well. No reason having two targeting computers on a turret.
6626 //Plus, it's easy and fast to code. :)
6627 if ((psub->type==SUBSYSTEM_TURRET) && turret_weapon_has_flags(&ss->weapons, Weapon::Info_Flags::Beam))
6628 {
6629 disrupt_time*=wip->elec_beam_mult;
6630 }
6631 //disrupt other weapons
6632 else
6633 {
6634 disrupt_time*=wip->elec_weap_mult;
6635 }
6636 }
6637
6638 //disrupt sensor and awacs systems.
6639 if ((psub->type==SUBSYSTEM_SENSORS) || (psub->flags[Model::Subsystem_Flags::Awacs]))
6640 {
6641 disrupt_time*=wip->elec_sensors_mult;
6642 }
6643 }
6644
6645 //add a little randomness to the disruption time
6646 disrupt_time += frand_range(-1.0f, 1.0f) * wip->elec_randomness;
6647
6648 //disrupt this subsystem for the calculated time
6649 //if it turns out to be less than 0 seconds, don't bother
6650 if (disrupt_time > 0)
6651 {
6652 ship_subsys_set_disrupted(ss, fl2i(disrupt_time));
6653 }
6654 }
6655 }
6656 }
6657
6658 /**
6659 * Calculate the damage for an object based on the location of an area-effect
6660 * explosion.
6661 *
6662 * @param objp Object pointer ship receiving blast effect
6663 * @param pos World pos of blast center
6664 * @param inner_rad Smallest radius at which full damage is done
6665 * @param outer_rad Radius at which no damage is done
6666 * @param max_blast Maximum blast possible from explosion
6667 * @param max_damage Maximum damage possible from explosion
6668 * @param blast OUTPUT PARAMETER: receives blast value from explosion
6669 * @param damage OUTPUT PARAMETER: receives damage value from explosion
6670 * @param limit A limit on the area, needed for shockwave damage
6671 *
6672 * @return No damage occurred, -1
6673 * @return Damage occured, 0
6674 */
weapon_area_calc_damage(object * objp,vec3d * pos,float inner_rad,float outer_rad,float max_blast,float max_damage,float * blast,float * damage,float limit)6675 int weapon_area_calc_damage(object *objp, vec3d *pos, float inner_rad, float outer_rad, float max_blast, float max_damage, float *blast, float *damage, float limit)
6676 {
6677 float dist;
6678 vec3d box_pt;
6679
6680 // if object receiving the blast is a ship, use the bbox for distances
6681 // otherwise use the objects radius
6682 // could possibly exclude SIF_SMALL_SHIP (& other small objects) from using the bbox
6683 if (objp->type == OBJ_SHIP) {
6684 int inside = get_nearest_bbox_point(objp, pos, &box_pt);
6685 if (inside) {
6686 dist = 0.0001f;
6687 } else {
6688 dist = vm_vec_dist_quick(pos, &box_pt);
6689 }
6690 } else {
6691 dist = vm_vec_dist_quick(&objp->pos, pos) - objp->radius;
6692 }
6693
6694 if ( (dist > outer_rad) || (dist > limit) ) {
6695 return -1; // spheres don't intersect at all
6696 }
6697
6698 if ( dist < inner_rad ) {
6699 // damage is maximum within inner radius
6700 *damage = max_damage;
6701 *blast = max_blast;
6702 } else {
6703 float dist_to_outer_rad_squared = (outer_rad-dist)*(outer_rad-dist);
6704 float total_dist_squared = (inner_rad-outer_rad)*(inner_rad-outer_rad);
6705
6706 // this means the inner and outer radii are basically equal... and since we aren't within the inner radius,
6707 // we fudge the law of excluded middle to place ourselves outside the outer radius
6708 if (total_dist_squared < 0.0001f) {
6709 return -1; // avoid divide-by-zero; we won't take damage anyway
6710 }
6711
6712 // AL 2-24-98: drop off damage relative to square of distance
6713 Assert(dist_to_outer_rad_squared <= total_dist_squared);
6714 *damage = max_damage * dist_to_outer_rad_squared/total_dist_squared;
6715 *blast = (dist - outer_rad) * max_blast /(inner_rad - outer_rad);
6716 }
6717
6718 return 0;
6719 }
6720
6721 /**
6722 * Apply the blast effects of an explosion to a ship
6723 *
6724 * @param force_apply_pos World pos of where force is applied to object
6725 * @param ship_objp Object pointer of ship receiving the blast
6726 * @param blast_pos World pos of blast center
6727 * @param blast Force of blast
6728 * @param make_shockwave Boolean, whether to create a shockwave or not
6729 */
weapon_area_apply_blast(vec3d *,object * ship_objp,vec3d * blast_pos,float blast,int make_shockwave)6730 void weapon_area_apply_blast(vec3d * /*force_apply_pos*/, object *ship_objp, vec3d *blast_pos, float blast, int make_shockwave)
6731 {
6732 vec3d force, vec_blast_to_ship, vec_ship_to_impact;
6733 polymodel *pm;
6734
6735 // don't waste time here if there is no blast force
6736 if ( blast == 0.0f )
6737 return;
6738
6739 // apply blast force based on distance from center of explosion
6740 vm_vec_sub(&vec_blast_to_ship, &ship_objp->pos, blast_pos);
6741 vm_vec_normalize_safe(&vec_blast_to_ship);
6742 vm_vec_copy_scale(&force, &vec_blast_to_ship, blast );
6743
6744 vm_vec_sub(&vec_ship_to_impact, blast_pos, &ship_objp->pos);
6745
6746 pm = model_get(Ship_info[Ships[ship_objp->instance].ship_info_index].model_num);
6747 Assert ( pm != NULL );
6748
6749 if (make_shockwave) {
6750 if (object_is_docked(ship_objp)) {
6751 // TODO: this sales down the effect properly but physics_apply_shock will apply
6752 // forces based on this ship's bbox, rather than the whole assembly's bbox like it should
6753 blast *= ship_objp->phys_info.mass / dock_calc_total_docked_mass(ship_objp);
6754 }
6755 physics_apply_shock (&force, blast, &ship_objp->phys_info, &ship_objp->orient, &pm->mins, &pm->maxs, pm->rad);
6756 if (ship_objp == Player_obj) {
6757 joy_ff_play_vector_effect(&vec_blast_to_ship, blast * 2.0f);
6758 }
6759 } else {
6760 ship_apply_whack( &force, blast_pos, ship_objp);
6761 }
6762 }
6763
6764 /**
6765 * Do the area effect for a weapon
6766 *
6767 * @param wobjp Object pointer to weapon causing explosion
6768 * @param sci Shockwave info
6769 * @param pos World pos of explosion center
6770 * @param other_obj Object pointer to ship that weapon impacted on (can be NULL)
6771 */
weapon_do_area_effect(object * wobjp,shockwave_create_info * sci,vec3d * pos,object * other_obj)6772 void weapon_do_area_effect(object *wobjp, shockwave_create_info *sci, vec3d *pos, object *other_obj)
6773 {
6774 weapon_info *wip;
6775 object *objp;
6776 float damage, blast;
6777
6778 wip = &Weapon_info[Weapons[wobjp->instance].weapon_info_index];
6779
6780 // only blast ships and asteroids
6781 // And (some) weapons
6782 for ( objp = GET_FIRST(&obj_used_list); objp !=END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) ) {
6783 if ( (objp->type != OBJ_SHIP) && (objp->type != OBJ_ASTEROID) && (objp->type != OBJ_WEAPON) ) {
6784 continue;
6785 }
6786
6787 if (objp->type == OBJ_WEAPON) {
6788 // only apply to missiles with hitpoints
6789 weapon_info* wip2 = &Weapon_info[Weapons[objp->instance].weapon_info_index];
6790 if (wip2->weapon_hitpoints <= 0)
6791 continue;
6792 if (!((wip2->wi_flags[Weapon::Info_Flags::Takes_blast_damage]) || (wip->wi_flags[Weapon::Info_Flags::Ciws])))
6793 continue;
6794 }
6795
6796 if ( objp->type == OBJ_SHIP ) {
6797 // don't blast navbuoys
6798 if ( ship_get_SIF(objp->instance)[Ship::Info_Flags::Navbuoy] ) {
6799 continue;
6800 }
6801 }
6802
6803 if ( weapon_area_calc_damage(objp, pos, sci->inner_rad, sci->outer_rad, sci->blast, sci->damage, &blast, &damage, sci->outer_rad) == -1 ){
6804 continue;
6805 }
6806
6807 // scale damage
6808 damage *= weapon_get_damage_scale(wip, wobjp, other_obj);
6809
6810 weapon_info* target_wip;
6811
6812 switch ( objp->type ) {
6813 case OBJ_SHIP:
6814 // If we're doing an AoE Electronics blast, do the electronics stuff (unless it also has the regular "electronics"
6815 // flag and this is the ship the missile directly impacted; then leave it for the regular code below) -MageKing17
6816 if ( (wip->wi_flags[Weapon::Info_Flags::Aoe_Electronics]) && !((objp->flags[Object::Object_Flags::Invulnerable]) || ((objp == other_obj) && (wip->wi_flags[Weapon::Info_Flags::Electronics]))) ) {
6817 weapon_do_electronics_effect(objp, pos, Weapons[wobjp->instance].weapon_info_index);
6818 }
6819 ship_apply_global_damage(objp, wobjp, pos, damage, wip->shockwave.damage_type_idx);
6820 weapon_area_apply_blast(NULL, objp, pos, blast, 0);
6821 break;
6822 case OBJ_ASTEROID:
6823 asteroid_hit(objp, NULL, NULL, damage);
6824 break;
6825 case OBJ_WEAPON:
6826 target_wip = &Weapon_info[Weapons[objp->instance].weapon_info_index];
6827 if (target_wip->armor_type_idx >= 0)
6828 damage = Armor_types[target_wip->armor_type_idx].GetDamage(damage, wip->shockwave.damage_type_idx, 1.0f);
6829
6830 objp->hull_strength -= damage;
6831 if (objp->hull_strength < 0.0f) {
6832 Weapons[objp->instance].lifeleft = 0.01f;
6833 Weapons[objp->instance].weapon_flags.set(Weapon::Weapon_Flags::Destroyed_by_weapon);
6834 }
6835 break;
6836 default:
6837 Int3();
6838 break;
6839 }
6840
6841 } // end for
6842
6843
6844 }
6845
6846 // ----------------------------------------------------------------------
6847 // weapon_armed(weapon)
6848 //
6849 // Call to figure out if a weapon is armed or not
6850 //
6851 //Weapon is armed when...
6852 //1: Weapon is shot down by weapon
6853 //OR
6854 //1: weapon is destroyed before arm time
6855 //2: weapon is destroyed before arm distance from ship
6856 //3: weapon is outside arm radius from target ship
weapon_armed(weapon * wp,bool hit_target)6857 bool weapon_armed(weapon *wp, bool hit_target)
6858 {
6859 Assert(wp != NULL);
6860
6861 weapon_info *wip = &Weapon_info[wp->weapon_info_index];
6862
6863 if((wp->weapon_flags[Weapon::Weapon_Flags::Destroyed_by_weapon])
6864 && !wip->arm_time
6865 && wip->arm_dist == 0.0f
6866 && wip->arm_radius == 0.0f)
6867 {
6868 return false;
6869 }
6870 else
6871 {
6872 object *wobj = &Objects[wp->objnum];
6873 object *pobj;
6874
6875 if(wobj->parent > -1) {
6876 pobj = &Objects[wobj->parent];
6877 } else {
6878 pobj = NULL;
6879 }
6880
6881 if( ((wip->arm_time) && ((Missiontime - wp->creation_time) < wip->arm_time))
6882 || ((wip->arm_dist) && (pobj != NULL && pobj->type != OBJ_NONE && (vm_vec_dist(&wobj->pos, &pobj->pos) < wip->arm_dist))))
6883 {
6884 return false;
6885 }
6886 if(wip->arm_radius && (!hit_target)) {
6887 if(wp->homing_object == &obj_used_list)
6888 return false;
6889 if(IS_VEC_NULL(&wp->homing_pos) || vm_vec_dist(&wobj->pos, &wp->homing_pos) > wip->arm_radius)
6890 return false;
6891 }
6892 }
6893
6894 return true;
6895 }
6896
6897
6898 /**
6899 * Called when a weapon hits something (or, in the case of
6900 * missiles explodes for any particular reason)
6901 */
weapon_hit(object * weapon_obj,object * other_obj,vec3d * hitpos,int quadrant,vec3d * hitnormal)6902 void weapon_hit( object * weapon_obj, object * other_obj, vec3d * hitpos, int quadrant, vec3d* hitnormal )
6903 {
6904 Assert(weapon_obj != NULL);
6905 if(weapon_obj == NULL){
6906 return;
6907 }
6908 Assert((weapon_obj->type == OBJ_WEAPON) && (weapon_obj->instance >= 0) && (weapon_obj->instance < MAX_WEAPONS));
6909 if((weapon_obj->type != OBJ_WEAPON) || (weapon_obj->instance < 0) || (weapon_obj->instance >= MAX_WEAPONS)){
6910 return;
6911 }
6912
6913 int num = weapon_obj->instance;
6914 int weapon_type = Weapons[num].weapon_info_index;
6915 weapon_info *wip;
6916 weapon *wp;
6917 bool hit_target = false;
6918
6919 object *other_objp;
6920 ship_obj *so;
6921 ship *shipp;
6922 int objnum;
6923
6924 Assert((weapon_type >= 0) && (weapon_type < weapon_info_size()));
6925 if ((weapon_type < 0) || (weapon_type >= weapon_info_size())) {
6926 return;
6927 }
6928 wp = &Weapons[weapon_obj->instance];
6929 wip = &Weapon_info[weapon_type];
6930 objnum = wp->objnum;
6931
6932 // check if the weapon actually hit the intended target
6933 if (wp->homing_object != &obj_used_list)
6934 if (wp->homing_object == other_obj)
6935 hit_target = true;
6936
6937 //This is an expensive check
6938 bool armed_weapon = weapon_armed(&Weapons[num], hit_target);
6939
6940 // if this is the player ship, and is a laser hit, skip it. wait for player "pain" to take care of it
6941 if ((other_obj != Player_obj) || (wip->subtype != WP_LASER) || !MULTIPLAYER_CLIENT) {
6942 weapon_hit_do_sound(other_obj, wip, hitpos, armed_weapon, quadrant);
6943 }
6944
6945 if (wip->impact_weapon_expl_effect.isValid() && armed_weapon) {
6946 auto particleSource = particle::ParticleManager::get()->createSource(wip->impact_weapon_expl_effect);
6947 particleSource.moveTo(hitpos);
6948 particleSource.setOrientationFromVec(&weapon_obj->phys_info.vel);
6949
6950 if (hitnormal)
6951 {
6952 particleSource.setOrientationNormal(hitnormal);
6953 }
6954
6955 particleSource.finish();
6956 } else if (wip->dinky_impact_weapon_expl_effect.isValid() && !armed_weapon) {
6957 auto particleSource = particle::ParticleManager::get()->createSource(wip->dinky_impact_weapon_expl_effect);
6958 particleSource.moveTo(hitpos);
6959 particleSource.setOrientationFromVec(&weapon_obj->phys_info.vel);
6960
6961 if (hitnormal)
6962 {
6963 particleSource.setOrientationNormal(hitnormal);
6964 }
6965
6966 particleSource.finish();
6967 }
6968
6969 if ((other_obj != nullptr) && (quadrant == -1) && (wip->piercing_impact_effect.isValid() && armed_weapon)) {
6970 if ((other_obj->type == OBJ_SHIP) || (other_obj->type == OBJ_DEBRIS)) {
6971
6972 int ok_to_draw = 1;
6973
6974 if (other_obj->type == OBJ_SHIP) {
6975 float draw_limit, hull_pct;
6976 int dmg_type_idx, piercing_type;
6977
6978 shipp = &Ships[other_obj->instance];
6979
6980 hull_pct = other_obj->hull_strength / shipp->ship_max_hull_strength;
6981 dmg_type_idx = wip->damage_type_idx;
6982 draw_limit = Ship_info[shipp->ship_info_index].piercing_damage_draw_limit;
6983
6984 if (shipp->armor_type_idx != -1) {
6985 piercing_type = Armor_types[shipp->armor_type_idx].GetPiercingType(dmg_type_idx);
6986 if (piercing_type == SADTF_PIERCING_DEFAULT) {
6987 draw_limit = Armor_types[shipp->armor_type_idx].GetPiercingLimit(dmg_type_idx);
6988 } else if ((piercing_type == SADTF_PIERCING_NONE) || (piercing_type == SADTF_PIERCING_RETAIL)) {
6989 ok_to_draw = 0;
6990 }
6991 }
6992
6993 if (hull_pct > draw_limit)
6994 ok_to_draw = 0;
6995 }
6996
6997 if (ok_to_draw) {
6998 using namespace particle;
6999
7000 auto primarySource = ParticleManager::get()->createSource(wip->piercing_impact_effect);
7001 primarySource.moveTo(&weapon_obj->pos);
7002 primarySource.setOrientationMatrix(&weapon_obj->last_orient);
7003
7004 if (hitnormal)
7005 {
7006 primarySource.setOrientationNormal(hitnormal);
7007 }
7008
7009 primarySource.finish();
7010
7011 if (wip->piercing_impact_secondary_effect.isValid()) {
7012 auto secondarySource = ParticleManager::get()->createSource(wip->piercing_impact_secondary_effect);
7013 secondarySource.moveTo(&weapon_obj->pos);
7014 secondarySource.setOrientationMatrix(&weapon_obj->last_orient);
7015
7016 if (hitnormal)
7017 {
7018 secondarySource.setOrientationNormal(hitnormal);
7019 }
7020
7021 secondarySource.finish();
7022 }
7023 }
7024 }
7025 }
7026
7027 //Set shockwaves flag
7028 int sw_flag = SW_WEAPON;
7029
7030 if ( ((other_obj) && (other_obj->type == OBJ_WEAPON)) || (Weapons[num].weapon_flags[Weapon::Weapon_Flags::Destroyed_by_weapon])) {
7031 sw_flag |= SW_WEAPON_KILL;
7032 }
7033
7034 //Which shockwave?
7035 shockwave_create_info *sci = &wip->shockwave;
7036 if(!armed_weapon) {
7037 sci = &wip->dinky_shockwave;
7038 }
7039
7040 // check if this is an area effect weapon (i.e. has a blast radius)
7041 if (sci->inner_rad != 0.0f || sci->outer_rad != 0.0f)
7042 {
7043 if(sci->speed > 0.0f) {
7044 shockwave_create(OBJ_INDEX(weapon_obj), hitpos, sci, sw_flag, -1);
7045 }
7046 else {
7047 weapon_do_area_effect(weapon_obj, sci, hitpos, other_obj);
7048 }
7049 }
7050
7051 // check if this is an EMP weapon
7052 if(wip->wi_flags[Weapon::Info_Flags::Emp]){
7053 emp_apply(&weapon_obj->pos, wip->shockwave.inner_rad, wip->shockwave.outer_rad, wip->emp_intensity, wip->emp_time, (wip->wi_flags[Weapon::Info_Flags::Use_emp_time_for_capship_turrets]) != 0);
7054 }
7055
7056 // if this weapon has the "Electronics" flag set, then disrupt subsystems in sphere
7057 if ((other_obj != NULL) && (wip->wi_flags[Weapon::Info_Flags::Electronics])) {
7058 if (other_obj->type == OBJ_SHIP) {
7059 weapon_do_electronics_effect(other_obj, &weapon_obj->pos, Weapons[weapon_obj->instance].weapon_info_index);
7060 }
7061 }
7062
7063 if (!wip->pierce_objects || wip->spawn_children_on_pierce || !other_obj) {
7064 // spawn weapons - note the change from FS 1 multiplayer.
7065 if (wip->wi_flags[Weapon::Info_Flags::Spawn]) {
7066 if (!((wip->wi_flags[Weapon::Info_Flags::Dont_spawn_if_shot]) && (Weapons[num].weapon_flags[Weapon::Weapon_Flags::Destroyed_by_weapon]))) { // prevent spawning of children if shot down and the dont spawn if shot flag is set (DahBlount)
7067 spawn_child_weapons(weapon_obj);
7068 }
7069 }
7070 }
7071
7072 //No other_obj means this weapon detonates
7073 if (wip->pierce_objects && other_obj)
7074 return;
7075
7076 // For all objects that had this weapon as a target, wipe it out, forcing find of a new enemy
7077 for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) {
7078 other_objp = &Objects[so->objnum];
7079 Assert(other_objp->instance != -1);
7080
7081 shipp = &Ships[other_objp->instance];
7082 Assert(shipp->ai_index != -1);
7083
7084 ai_info *aip = &Ai_info[shipp->ai_index];
7085
7086 if (aip->target_objnum == objnum) {
7087 set_target_objnum(aip, -1);
7088 // If this ship had a dynamic goal of chasing this weapon, clear the dynamic goal.
7089 if (aip->resume_goal_time != -1)
7090 aip->active_goal = AI_GOAL_NONE;
7091 }
7092
7093 if (aip->goal_objnum == objnum) {
7094 aip->goal_objnum = -1;
7095 aip->goal_signature = -1;
7096 }
7097
7098 if (aip->guard_objnum == objnum) {
7099 aip->guard_objnum = -1;
7100 aip->guard_signature = -1;
7101 }
7102
7103 if (aip->hitter_objnum == objnum) {
7104 aip->hitter_objnum = -1;
7105 }
7106 }
7107
7108 weapon_obj->flags.set(Object::Object_Flags::Should_be_dead);
7109
7110 // decrement parent's number of active remote detonators if applicable
7111 if (wip->wi_flags[Weapon::Info_Flags::Remote] && weapon_obj->parent >= 0 && (weapon_obj->parent < MAX_OBJECTS)) {
7112 object* parent = &Objects[weapon_obj->parent];
7113 if ( parent->type == OBJ_SHIP && parent->signature == weapon_obj->parent_sig)
7114 Ships[Objects[weapon_obj->parent].instance].weapons.remote_detonaters_active--;
7115 }
7116 }
7117
weapon_detonate(object * objp)7118 void weapon_detonate(object *objp)
7119 {
7120 Assert(objp != NULL);
7121 if(objp == NULL){
7122 return;
7123 }
7124 Assert((objp->type == OBJ_WEAPON) && (objp->instance >= 0));
7125 if((objp->type != OBJ_WEAPON) || (objp->instance < 0)){
7126 return;
7127 }
7128
7129 // send a detonate packet in multiplayer
7130 if(MULTIPLAYER_MASTER){
7131 send_weapon_detonate_packet(objp);
7132 }
7133
7134 // call weapon hit
7135 // Wanderer - use last frame pos for the corkscrew missiles
7136 if ( (Weapon_info[Weapons[objp->instance].weapon_info_index].wi_flags[Weapon::Info_Flags::Corkscrew]) ) {
7137 weapon_hit(objp, NULL, &objp->last_pos);
7138 } else {
7139 weapon_hit(objp, NULL, &objp->pos);
7140 }
7141 }
7142
7143
7144 // Group_id: If you should quad lasers, they should all have the same group id.
7145 // This will be used to optimize lighting, since each group only needs to cast one light.
7146 // Call this to get a new group id, then pass it to each weapon_create call for all the
7147 // weapons in the group. Number will be between 0 and WEAPON_MAX_GROUP_IDS and will
7148 // get reused.
weapon_create_group_id()7149 int weapon_create_group_id()
7150 {
7151 static int current_id = 0;
7152
7153 int n = current_id;
7154
7155 current_id++;
7156 if ( current_id >= WEAPON_MAX_GROUP_IDS ) {
7157 current_id = 0;
7158 }
7159 return n;
7160 }
7161
7162 /**
7163 * Call before weapons_page_in to mark a weapon as used
7164 */
weapon_mark_as_used(int weapon_type)7165 void weapon_mark_as_used(int weapon_type)
7166 {
7167 if (weapon_type < 0)
7168 return;
7169
7170 if ( used_weapons == NULL )
7171 return;
7172
7173 Assert( weapon_type < weapon_info_size() );
7174
7175 if (weapon_type < weapon_info_size()) {
7176 used_weapons[weapon_type]++;
7177 if (Weapon_info[weapon_type].num_substitution_patterns > 0) {
7178 for (size_t i = 0; i < Weapon_info[weapon_type].num_substitution_patterns; i++) {
7179 used_weapons[Weapon_info[weapon_type].weapon_substitution_pattern[i]]++;
7180 }
7181 }
7182 }
7183 }
7184
weapons_page_in()7185 void weapons_page_in()
7186 {
7187 TRACE_SCOPE(tracing::WeaponPageIn);
7188
7189 int i, j, idx;
7190
7191 Assert( used_weapons != NULL );
7192
7193 // for weapons in weaponry pool
7194 for (i = 0; i < Num_teams; i++) {
7195 for (j = 0; j < Team_data[i].num_weapon_choices; j++) {
7196 used_weapons[Team_data[i].weaponry_pool[j]] += Team_data[i].weaponry_count[j];
7197 }
7198 }
7199
7200 // this grabs all spawn weapon types (Cluster Baby, etc.) which can't be
7201 // assigned directly to a ship
7202 for (i = 0; i < weapon_info_size(); i++) {
7203 // we only want entries that already exist
7204 if ( !used_weapons[i] )
7205 continue;
7206
7207 // if it's got a spawn type then grab it
7208 for (j = 0; j < Weapon_info[i].num_spawn_weapons_defined; j++)
7209 {
7210 used_weapons[(int)Weapon_info[i].spawn_info[j].spawn_type]++;
7211 }
7212 }
7213
7214 // release anything loaded that we don't have marked as used for this mission
7215 if ( !Cmdline_load_all_weapons )
7216 weapon_release_bitmaps();
7217
7218 // Page in bitmaps for all used weapons
7219 for (i = 0; i < weapon_info_size(); i++) {
7220 if ( !Cmdline_load_all_weapons ) {
7221 if ( !used_weapons[i] ) {
7222 nprintf(("Weapons", "Not loading weapon id %d (%s)\n", i, Weapon_info[i].name));
7223 continue;
7224 }
7225 }
7226
7227 weapon_load_bitmaps(i);
7228
7229 weapon_info *wip = &Weapon_info[i];
7230
7231 wip->wi_flags.remove(Weapon::Info_Flags::Thruster); // Assume no thrusters
7232
7233 switch (wip->render_type)
7234 {
7235 case WRT_POF:
7236 {
7237 wip->model_num = model_load( wip->pofbitmap_name, 0, NULL );
7238
7239 polymodel *pm = model_get( wip->model_num );
7240
7241 // If it has a model, and the model pof has thrusters, then set
7242 // the flags
7243 if (pm->n_thrusters > 0) {
7244 wip->wi_flags.set(Weapon::Info_Flags::Thruster);
7245 }
7246
7247 for (j = 0; j < pm->n_textures; j++)
7248 pm->maps[j].PageIn();
7249
7250 break;
7251 }
7252
7253 case WRT_LASER:
7254 {
7255 bm_page_in_texture( wip->laser_bitmap.first_frame );
7256 bm_page_in_texture( wip->laser_glow_bitmap.first_frame );
7257
7258 break;
7259 }
7260
7261 default:
7262 Assertion(wip->render_type != WRT_POF && wip->render_type != WRT_LASER, "Weapon %s does not have a valid rendering type. Type passed: %d\n", wip->name, wip->render_type); // Invalid weapon rendering type.
7263 }
7264
7265 wip->external_model_num = -1;
7266
7267 if ( strlen(wip->external_model_name) )
7268 wip->external_model_num = model_load( wip->external_model_name, 0, NULL );
7269
7270 if (wip->external_model_num == -1)
7271 wip->external_model_num = wip->model_num;
7272
7273
7274 //Load shockwaves
7275 shockwave_create_info_load(&wip->shockwave);
7276 shockwave_create_info_load(&wip->dinky_shockwave);
7277
7278 // trail bitmaps
7279 if ( (wip->wi_flags[Weapon::Info_Flags::Trail]) && (wip->tr_info.texture.bitmap_id > -1) )
7280 bm_page_in_texture( wip->tr_info.texture.bitmap_id );
7281
7282 // if this is a beam weapon, page in its stuff
7283 if (wip->wi_flags[Weapon::Info_Flags::Beam]) {
7284 // all beam sections
7285 for (idx = 0; idx < wip->b_info.beam_num_sections; idx++)
7286 bm_page_in_texture(wip->b_info.sections[idx].texture.first_frame);
7287
7288 // muzzle glow
7289 bm_page_in_texture(wip->b_info.beam_glow.first_frame);
7290
7291 // particle ani
7292 bm_page_in_texture(wip->b_info.beam_particle_ani.first_frame);
7293 }
7294
7295 if (wip->wi_flags[Weapon::Info_Flags::Particle_spew]) {
7296 for (size_t s = 0; s < MAX_PARTICLE_SPEWERS; s++) { // looped, multi particle spew -nuke
7297 if (wip->particle_spewers[s].particle_spew_type != PSPEW_NONE) {
7298 bm_page_in_texture(wip->particle_spewers[s].particle_spew_anim.first_frame);
7299 }
7300 }
7301 }
7302
7303 // muzzle flashes
7304 if (wip->muzzle_flash >= 0)
7305 mflash_mark_as_used(wip->muzzle_flash);
7306
7307 bm_page_in_texture(wip->thruster_flame.first_frame);
7308 bm_page_in_texture(wip->thruster_glow.first_frame);
7309
7310 decals::pageInDecal(wip->impact_decal);
7311 }
7312 }
7313
7314 /**
7315 * Page_in function for cheaters, grabs all weapons that weren't already in a mission
7316 * and loads the models for them.
7317 *
7318 * Non-model graphics elements will get loaded when they are rendered for the first time.
7319 * Maybe not the best way to do this but faster and a lot less error prone.
7320 */
weapons_page_in_cheats()7321 void weapons_page_in_cheats()
7322 {
7323 int i;
7324
7325 // don't bother if they are all loaded already
7326 if ( Cmdline_load_all_weapons )
7327 return;
7328
7329 Assert( used_weapons != NULL );
7330
7331 // force a page in of all muzzle flashes
7332 mflash_page_in(true);
7333
7334 // page in models for all weapon types that aren't already loaded
7335 for (i = 0; i < weapon_info_size(); i++) {
7336 // skip over anything that's already loaded
7337 if (used_weapons[i])
7338 continue;
7339
7340 weapon_load_bitmaps(i);
7341
7342 weapon_info *wip = &Weapon_info[i];
7343
7344 wip->wi_flags.remove(Weapon::Info_Flags::Thruster); // Assume no thrusters
7345
7346 if ( wip->render_type == WRT_POF ) {
7347 wip->model_num = model_load( wip->pofbitmap_name, 0, NULL );
7348
7349 polymodel *pm = model_get( wip->model_num );
7350
7351 // If it has a model, and the model pof has thrusters, then set
7352 // the flags
7353 if ( pm->n_thrusters > 0 ) {
7354 wip->wi_flags.set(Weapon::Info_Flags::Thruster);
7355 }
7356 }
7357
7358 wip->external_model_num = -1;
7359
7360 if ( strlen(wip->external_model_name) )
7361 wip->external_model_num = model_load( wip->external_model_name, 0, NULL );
7362
7363 if (wip->external_model_num == -1)
7364 wip->external_model_num = wip->model_num;
7365
7366
7367 //Load shockwaves
7368 shockwave_create_info_load(&wip->shockwave);
7369 shockwave_create_info_load(&wip->dinky_shockwave);
7370
7371 used_weapons[i]++;
7372 }
7373 }
7374
7375 /* Helper function for l_Weaponclass.isWeaponLoaded()
7376 * Pages in a single weapon and its substitutes and chilren given the weapon_info index for it
7377 */
weapon_page_in(int weapon_type)7378 bool weapon_page_in(int weapon_type)
7379 {
7380 Assert(used_weapons != NULL);
7381
7382 if (weapon_type < 0 || weapon_type >= weapon_info_size()) {
7383 return false;
7384 }
7385
7386 SCP_vector<int> page_in_weapons;
7387 page_in_weapons.push_back(weapon_type);
7388
7389 // Make sure substitution weapons are paged in as well
7390 if (Weapon_info[weapon_type].num_substitution_patterns > 0) {
7391 for (size_t i = 0; i < Weapon_info[weapon_type].num_substitution_patterns; i++) {
7392 page_in_weapons.push_back(Weapon_info[weapon_type].weapon_substitution_pattern[i]);
7393 }
7394 }
7395
7396 // this grabs all spawn weapon types (Cluster Baby, etc.) which can't be
7397 // assigned directly to a ship
7398 // if it's got a spawn type then grab it
7399 size_t size = page_in_weapons.size();
7400 for (size_t x = 0; x < size; x++) {
7401 if (Weapon_info[page_in_weapons.at(x)].num_spawn_weapons_defined > 0) {
7402 for (int j = 0; j < Weapon_info[page_in_weapons.at(x)].num_spawn_weapons_defined; j++) {
7403 page_in_weapons.push_back((int)Weapon_info[page_in_weapons.at(x)].spawn_info[j].spawn_type);
7404 }
7405 }
7406 }
7407
7408 for (size_t k = 0; k < page_in_weapons.size(); k++) {
7409 if (used_weapons[page_in_weapons.at(k)]) {
7410 continue; // If weapon is already paged_in, we don't need to page it in again
7411 }
7412
7413 // Page in bitmaps for the weapon
7414 weapon_load_bitmaps(page_in_weapons.at(k));
7415
7416 weapon_info *wip = &Weapon_info[page_in_weapons.at(k)];
7417
7418 wip->wi_flags.remove(Weapon::Info_Flags::Thruster); // Assume no thrusters
7419
7420 switch (wip->render_type)
7421 {
7422 case WRT_POF:
7423 {
7424 wip->model_num = model_load(wip->pofbitmap_name, 0, NULL);
7425
7426 polymodel *pm = model_get(wip->model_num);
7427
7428 // If it has a model, and the model pof has thrusters, then set
7429 // the flags
7430 if (pm->n_thrusters > 0) {
7431 wip->wi_flags.set(Weapon::Info_Flags::Thruster);
7432 }
7433
7434 for (int j = 0; j < pm->n_textures; j++)
7435 pm->maps[j].PageIn();
7436
7437 break;
7438 }
7439
7440 case WRT_LASER:
7441 {
7442 bm_page_in_texture(wip->laser_bitmap.first_frame);
7443 bm_page_in_texture(wip->laser_glow_bitmap.first_frame);
7444
7445 break;
7446 }
7447
7448 default:
7449 Assertion(wip->render_type != WRT_POF && wip->render_type != WRT_LASER, "Weapon %s does not have a valid rendering type. Type passed: %d\n", wip->name, wip->render_type); // Invalid weapon rendering type.
7450 }
7451
7452 wip->external_model_num = -1;
7453
7454 if (strlen(wip->external_model_name))
7455 wip->external_model_num = model_load(wip->external_model_name, 0, NULL);
7456
7457 if (wip->external_model_num == -1)
7458 wip->external_model_num = wip->model_num;
7459
7460
7461 //Load shockwaves
7462 shockwave_create_info_load(&wip->shockwave);
7463 shockwave_create_info_load(&wip->dinky_shockwave);
7464
7465 // trail bitmaps
7466 if ((wip->wi_flags[Weapon::Info_Flags::Trail]) && (wip->tr_info.texture.bitmap_id > -1))
7467 bm_page_in_texture(wip->tr_info.texture.bitmap_id);
7468
7469 // if this is a beam weapon, page in its stuff
7470 if (wip->wi_flags[Weapon::Info_Flags::Beam]) {
7471 // all beam sections
7472 for (int idx = 0; idx < wip->b_info.beam_num_sections; idx++)
7473 bm_page_in_texture(wip->b_info.sections[idx].texture.first_frame);
7474
7475 // muzzle glow
7476 bm_page_in_texture(wip->b_info.beam_glow.first_frame);
7477
7478 // particle ani
7479 bm_page_in_texture(wip->b_info.beam_particle_ani.first_frame);
7480 }
7481
7482 if (wip->wi_flags[Weapon::Info_Flags::Particle_spew]) {
7483 for (size_t s = 0; s < MAX_PARTICLE_SPEWERS; s++) { // looped, multi particle spew -nuke
7484 if (wip->particle_spewers[s].particle_spew_type != PSPEW_NONE) {
7485 bm_page_in_texture(wip->particle_spewers[s].particle_spew_anim.first_frame);
7486 }
7487 }
7488 }
7489
7490 // muzzle flashes
7491 if (wip->muzzle_flash >= 0)
7492 mflash_mark_as_used(wip->muzzle_flash);
7493
7494 bm_page_in_texture(wip->thruster_flame.first_frame);
7495 bm_page_in_texture(wip->thruster_glow.first_frame);
7496
7497 // Page in decal bitmaps
7498 decals::pageInDecal(wip->impact_decal);
7499
7500 used_weapons[page_in_weapons.at(k)]++; // Ensures weapon can be counted as used
7501 }
7502
7503 return true;
7504 }
7505
weapon_used(int weapon_type)7506 bool weapon_used(int weapon_type) {
7507 return used_weapons[weapon_type] > 0;
7508 }
7509
7510 /**
7511 * Get the "color" of the laser at the given moment (since glowing lasers can cycle colors)
7512 */
weapon_get_laser_color(color * c,object * objp)7513 void weapon_get_laser_color(color *c, object *objp)
7514 {
7515 weapon *wep;
7516 weapon_info *winfo;
7517 float pct;
7518
7519 // sanity
7520 if (c == NULL)
7521 return;
7522
7523 // sanity
7524 Assert(objp != NULL);
7525 Assert(objp->type == OBJ_WEAPON);
7526 Assert(objp->instance >= 0);
7527 Assert(Weapons[objp->instance].weapon_info_index >= 0);
7528
7529 if ( (objp == NULL) || (objp->type != OBJ_WEAPON) || (objp->instance < 0) || (Weapons[objp->instance].weapon_info_index < 0) )
7530 return;
7531
7532 wep = &Weapons[objp->instance];
7533 winfo = &Weapon_info[wep->weapon_info_index];
7534
7535 // if we're a one-color laser
7536 if ( (winfo->laser_color_2.red == winfo->laser_color_1.red) && (winfo->laser_color_2.green == winfo->laser_color_1.green) && (winfo->laser_color_2.blue == winfo->laser_color_1.blue) ) {
7537 *c = winfo->laser_color_1;
7538 return;
7539 }
7540
7541 int r = winfo->laser_color_1.red;
7542 int g = winfo->laser_color_1.green;
7543 int b = winfo->laser_color_1.blue;
7544
7545 // lifetime pct
7546 pct = 1.0f - (wep->lifeleft / winfo->lifetime);
7547 CLAMP(pct, 0.0f, 0.5f);
7548
7549 if (pct > 0.0f) {
7550 pct *= 2.0f;
7551
7552 r += fl2i((winfo->laser_color_2.red - winfo->laser_color_1.red) * pct);
7553 g += fl2i((winfo->laser_color_2.green - winfo->laser_color_1.green) * pct);
7554 b += fl2i((winfo->laser_color_2.blue - winfo->laser_color_1.blue) * pct);
7555 }
7556
7557 // otherwise interpolate between the colors
7558 gr_init_color( c, r, g, b );
7559 }
7560
7561 // default weapon particle spew data
7562
7563 int Weapon_particle_spew_count = 1;
7564 int Weapon_particle_spew_time = 25;
7565 float Weapon_particle_spew_vel = 0.4f;
7566 float Weapon_particle_spew_radius = 2.0f;
7567 float Weapon_particle_spew_lifetime = 0.15f;
7568 float Weapon_particle_spew_scale = 0.8f;
7569
7570 /**
7571 * For weapons flagged as particle spewers, spew particles. wheee
7572 */
weapon_maybe_spew_particle(object * obj)7573 void weapon_maybe_spew_particle(object *obj)
7574 {
7575 weapon *wp;
7576 weapon_info *wip;
7577 int idx;
7578
7579 // check some stuff
7580 Assert(obj->type == OBJ_WEAPON);
7581 Assert(obj->instance >= 0);
7582 Assert(Weapons[obj->instance].weapon_info_index >= 0);
7583 Assert(Weapon_info[Weapons[obj->instance].weapon_info_index].wi_flags[Weapon::Info_Flags::Particle_spew]);
7584
7585 wp = &Weapons[obj->instance];
7586 wip = &Weapon_info[wp->weapon_info_index];
7587 vec3d spawn_pos, spawn_vel, output_pos, output_vel, input_pos, input_vel;
7588
7589 for (int psi = 0; psi < MAX_PARTICLE_SPEWERS; psi++) { // iterate through spewers -nuke
7590 if (wip->particle_spewers[psi].particle_spew_type != PSPEW_NONE) {
7591 // if the weapon's particle timestamp has elapsed
7592 if ((wp->particle_spew_time[psi] == -1) || timestamp_elapsed(wp->particle_spew_time[psi])) {
7593 // reset the timestamp
7594 wp->particle_spew_time[psi] = timestamp(wip->particle_spewers[0].particle_spew_time);
7595
7596 // turn normals and origins to world space if we need to
7597 if (!vm_vec_same(&wip->particle_spewers[psi].particle_spew_offset, &vmd_zero_vector)) { // don't xform unused vectors
7598 vm_vec_unrotate(&spawn_pos, &wip->particle_spewers[psi].particle_spew_offset, &obj->orient);
7599 } else {
7600 spawn_pos = vmd_zero_vector;
7601 }
7602
7603 if (!vm_vec_same(&wip->particle_spewers[psi].particle_spew_velocity, &vmd_zero_vector)) {
7604 vm_vec_unrotate(&spawn_vel, &wip->particle_spewers[psi].particle_spew_velocity, &obj->orient);
7605 } else {
7606 spawn_vel = vmd_zero_vector;
7607 }
7608
7609 // spew some particles
7610 if (wip->particle_spewers[psi].particle_spew_type == PSPEW_DEFAULT) // default pspew type
7611 { // do the default pspew
7612 vec3d direct, direct_temp, particle_pos;
7613 vec3d null_vec = ZERO_VECTOR;
7614 vec3d vel;
7615 float ang;
7616
7617 for (idx = 0; idx < wip->particle_spewers[psi].particle_spew_count; idx++) {
7618 // get the backward vector of the weapon
7619 direct = obj->orient.vec.fvec;
7620 vm_vec_negate(&direct);
7621
7622 // randomly perturb x, y and z
7623
7624 // uvec
7625 ang = frand_range(-PI_2,PI_2); // fl_radian(frand_range(-90.0f, 90.0f)); -optimized by nuke
7626 vm_rot_point_around_line(&direct_temp, &direct, ang, &null_vec, &obj->orient.vec.fvec);
7627 direct = direct_temp;
7628 vm_vec_scale(&direct, wip->particle_spewers[psi].particle_spew_scale);
7629
7630 // rvec
7631 ang = frand_range(-PI_2,PI_2); // fl_radian(frand_range(-90.0f, 90.0f)); -optimized by nuke
7632 vm_rot_point_around_line(&direct_temp, &direct, ang, &null_vec, &obj->orient.vec.rvec);
7633 direct = direct_temp;
7634 vm_vec_scale(&direct, wip->particle_spewers[psi].particle_spew_scale);
7635
7636 // fvec
7637 ang = frand_range(-PI_2,PI_2); // fl_radian(frand_range(-90.0f, 90.0f)); -optimized by nuke
7638 vm_rot_point_around_line(&direct_temp, &direct, ang, &null_vec, &obj->orient.vec.uvec);
7639 direct = direct_temp;
7640 vm_vec_scale(&direct, wip->particle_spewers[psi].particle_spew_scale);
7641
7642 // get a velocity vector of some percentage of the weapon's velocity
7643 vel = obj->phys_info.vel;
7644 vm_vec_scale(&vel, wip->particle_spewers[psi].particle_spew_vel);
7645
7646 // maybe add in offset and initial velocity
7647 if (!vm_vec_same(&spawn_vel, &vmd_zero_vector)) { // add in particle velocity if its available
7648 vm_vec_add2(&vel, &spawn_vel);
7649 }
7650 if (!vm_vec_same(&spawn_pos, &vmd_zero_vector)) { // add offset if available
7651 vm_vec_add2(&direct, &spawn_pos);
7652 }
7653
7654 if (wip->wi_flags[Weapon::Info_Flags::Corkscrew]) {
7655 vm_vec_add(&particle_pos, &obj->last_pos, &direct);
7656 } else {
7657 vm_vec_add(&particle_pos, &obj->pos, &direct);
7658 }
7659
7660 // emit the particle
7661 if (wip->particle_spewers[psi].particle_spew_anim.first_frame < 0) {
7662 particle::create(&particle_pos,
7663 &vel,
7664 wip->particle_spewers[psi].particle_spew_lifetime,
7665 wip->particle_spewers[psi].particle_spew_radius,
7666 particle::PARTICLE_SMOKE);
7667 } else {
7668 particle::create(&particle_pos,
7669 &vel,
7670 wip->particle_spewers[psi].particle_spew_lifetime,
7671 wip->particle_spewers[psi].particle_spew_radius,
7672 particle::PARTICLE_BITMAP,
7673 wip->particle_spewers[psi].particle_spew_anim.first_frame);
7674 }
7675 }
7676 } else if (wip->particle_spewers[psi].particle_spew_type == PSPEW_HELIX) { // helix
7677 float segment_length = wip->max_speed * flFrametime; // determine how long the segment is
7678 float segment_angular_length = PI2 * wip->particle_spewers[psi].particle_spew_rotation_rate * flFrametime; // determine how much the segment rotates
7679 float rotation_value = (wp->lifeleft * PI2 * wip->particle_spewers[psi].particle_spew_rotation_rate) + wp->particle_spew_rand; // calculate a rotational start point based on remaining life
7680 float inc = 1.0f / wip->particle_spewers[psi].particle_spew_count; // determine our incriment
7681 float particle_rot;
7682 vec3d input_pos_l = ZERO_VECTOR;
7683
7684 for (float is = 0; is < 1; is += inc ) { // use iterator as a scaler
7685 particle_rot = rotation_value + (segment_angular_length * is); // find what point of the rotation were at
7686 input_vel.xyz.x = sinf(particle_rot) * wip->particle_spewers[psi].particle_spew_scale; // determine x/y velocity based on scale and rotation
7687 input_vel.xyz.y = cosf(particle_rot) * wip->particle_spewers[psi].particle_spew_scale;
7688 input_vel.xyz.z = wip->max_speed * wip->particle_spewers[psi].particle_spew_vel; // velocity inheritance
7689 vm_vec_unrotate(&output_vel, &input_vel, &obj->orient); // orient velocity to weapon
7690 input_pos_l.xyz.x = input_vel.xyz.x * flFrametime * (1.0f - is); // interpolate particle motion
7691 input_pos_l.xyz.y = input_vel.xyz.y * flFrametime * (1.0f - is);
7692 input_pos_l.xyz.z = segment_length * is; // position particle correctly on the z axis
7693 vm_vec_unrotate(&input_pos, &input_pos_l, &obj->orient); // orient to weapon
7694 vm_vec_sub(&output_pos, &obj->pos, &input_pos); // translate to world space
7695
7696 //maybe add in offset and initial velocity
7697 if (!vm_vec_same(&spawn_vel, &vmd_zero_vector)) { // add particle velocity if needed
7698 vm_vec_add2(&output_vel, &spawn_vel);
7699 }
7700 if (!vm_vec_same(&spawn_pos, &vmd_zero_vector)) { // add offset if needed
7701 vm_vec_add2(&output_pos, &spawn_pos);
7702 }
7703
7704 //emit particles
7705 if (wip->particle_spewers[psi].particle_spew_anim.first_frame < 0) {
7706 particle::create(&output_pos,
7707 &output_vel,
7708 wip->particle_spewers[psi].particle_spew_lifetime,
7709 wip->particle_spewers[psi].particle_spew_radius,
7710 particle::PARTICLE_SMOKE);
7711 } else {
7712 particle::create(&output_pos,
7713 &output_vel,
7714 wip->particle_spewers[psi].particle_spew_lifetime,
7715 wip->particle_spewers[psi].particle_spew_radius,
7716 particle::PARTICLE_BITMAP,
7717 wip->particle_spewers[psi].particle_spew_anim.first_frame);
7718 }
7719 }
7720 } else if (wip->particle_spewers[psi].particle_spew_type == PSPEW_SPARKLER) { // sparkler
7721 vec3d temp_vel;
7722 output_vel = obj->phys_info.vel;
7723 vm_vec_scale(&output_vel, wip->particle_spewers[psi].particle_spew_vel);
7724
7725 for (idx = 0; idx < wip->particle_spewers[psi].particle_spew_count; idx++) {
7726 // create a random unit vector and scale it
7727 vm_vec_rand_vec_quick(&input_vel);
7728 vm_vec_scale(&input_vel, wip->particle_spewers[psi].particle_spew_scale);
7729
7730 if (wip->particle_spewers[psi].particle_spew_z_scale != 1.0f) { // don't do the extra math for spherical effect
7731 temp_vel = input_vel;
7732 temp_vel.xyz.z *= wip->particle_spewers[psi].particle_spew_z_scale; // for an ovoid particle effect to better combine with laser effects
7733 vm_vec_unrotate(&input_vel, &temp_vel, &obj->orient); // so it has to be rotated
7734 }
7735
7736 vm_vec_add2(&output_vel, &input_vel); // add to weapon velocity
7737 output_pos = obj->pos;
7738
7739 // maybe add in offset and initial velocity
7740 if (!vm_vec_same(&spawn_vel, &vmd_zero_vector)) { // add particle velocity if needed
7741 vm_vec_add2(&output_vel, &spawn_vel);
7742 }
7743 if (!vm_vec_same(&spawn_pos, &vmd_zero_vector)) { // add offset if needed
7744 vm_vec_add2(&output_pos, &spawn_pos);
7745 }
7746
7747 // emit particles
7748 if (wip->particle_spewers[psi].particle_spew_anim.first_frame < 0) {
7749 particle::create(&output_pos,
7750 &output_vel,
7751 wip->particle_spewers[psi].particle_spew_lifetime,
7752 wip->particle_spewers[psi].particle_spew_radius,
7753 particle::PARTICLE_SMOKE);
7754 } else {
7755 particle::create(&output_pos,
7756 &output_vel,
7757 wip->particle_spewers[psi].particle_spew_lifetime,
7758 wip->particle_spewers[psi].particle_spew_radius,
7759 particle::PARTICLE_BITMAP,
7760 wip->particle_spewers[psi].particle_spew_anim.first_frame);
7761 }
7762 }
7763 } else if (wip->particle_spewers[psi].particle_spew_type == PSPEW_RING) {
7764 float inc = PI2 / wip->particle_spewers[psi].particle_spew_count;
7765
7766 for (float ir = 0; ir < PI2; ir += inc) { // use iterator for rotation
7767 input_vel.xyz.x = sinf(ir) * wip->particle_spewers[psi].particle_spew_scale; // generate velocity from rotation data
7768 input_vel.xyz.y = cosf(ir) * wip->particle_spewers[psi].particle_spew_scale;
7769 input_vel.xyz.z = obj->phys_info.fspeed * wip->particle_spewers[psi].particle_spew_vel;
7770 vm_vec_unrotate(&output_vel, &input_vel, &obj->orient); // rotate it to model
7771
7772 output_pos = obj->pos;
7773
7774 // maybe add in offset amd iitial velocity
7775 if (!vm_vec_same(&spawn_vel, &vmd_zero_vector)) { // add particle velocity if needed
7776 vm_vec_add2(&output_vel, &spawn_vel);
7777 }
7778 if (!vm_vec_same(&spawn_pos, &vmd_zero_vector)) { // add offset if needed
7779 vm_vec_add2(&output_pos, &spawn_pos);
7780 }
7781
7782 // emit particles
7783 if (wip->particle_spewers[psi].particle_spew_anim.first_frame < 0) {
7784 particle::create(&output_pos,
7785 &output_vel,
7786 wip->particle_spewers[psi].particle_spew_lifetime,
7787 wip->particle_spewers[psi].particle_spew_radius,
7788 particle::PARTICLE_SMOKE);
7789 } else {
7790 particle::create(&output_pos,
7791 &output_vel,
7792 wip->particle_spewers[psi].particle_spew_lifetime,
7793 wip->particle_spewers[psi].particle_spew_radius,
7794 particle::PARTICLE_BITMAP,
7795 wip->particle_spewers[psi].particle_spew_anim.first_frame);
7796 }
7797 }
7798 } else if (wip->particle_spewers[psi].particle_spew_type == PSPEW_PLUME) {
7799 float ang_rand, len_rand, sin_ang, cos_ang;
7800 vec3d input_pos_l = ZERO_VECTOR;
7801
7802 for (int i = 0; i < wip->particle_spewers[psi].particle_spew_count; i++) {
7803 // use polar coordinates to ensure a disk shaped spew plane
7804 ang_rand = frand_range(-PI,PI);
7805 len_rand = frand() * wip->particle_spewers[psi].particle_spew_scale;
7806 sin_ang = sinf(ang_rand);
7807 cos_ang = cosf(ang_rand);
7808 // compute velocity
7809 input_vel.xyz.x = wip->particle_spewers[psi].particle_spew_z_scale * -sin_ang;
7810 input_vel.xyz.y = wip->particle_spewers[psi].particle_spew_z_scale * -cos_ang;
7811 input_vel.xyz.z = obj->phys_info.fspeed * wip->particle_spewers[psi].particle_spew_vel;
7812 vm_vec_unrotate(&output_vel, &input_vel, &obj->orient); // rotate it to model
7813 // place particle on a disk prependicular to the weapon normal and rotate to model space
7814 input_pos_l.xyz.x = sin_ang * len_rand;
7815 input_pos_l.xyz.y = cos_ang * len_rand;
7816 vm_vec_unrotate(&input_pos, &input_pos_l, &obj->orient); // rotate to world
7817 vm_vec_sub(&output_pos, &obj->pos, &input_pos); // translate to world
7818
7819 // maybe add in offset amd iitial velocity
7820 if (!vm_vec_same(&spawn_vel, &vmd_zero_vector)) { // add particle velocity if needed
7821 vm_vec_add2(&output_vel, &spawn_vel);
7822 }
7823 if (!vm_vec_same(&spawn_pos, &vmd_zero_vector)) { // add offset if needed
7824 vm_vec_add2(&output_pos, &spawn_pos);
7825 }
7826
7827 //emit particles
7828 if (wip->particle_spewers[psi].particle_spew_anim.first_frame < 0) {
7829 particle::create(&output_pos,
7830 &output_vel,
7831 wip->particle_spewers[psi].particle_spew_lifetime,
7832 wip->particle_spewers[psi].particle_spew_radius,
7833 particle::PARTICLE_SMOKE);
7834 } else {
7835 particle::create(&output_pos,
7836 &output_vel,
7837 wip->particle_spewers[psi].particle_spew_lifetime,
7838 wip->particle_spewers[psi].particle_spew_radius,
7839 particle::PARTICLE_BITMAP,
7840 wip->particle_spewers[psi].particle_spew_anim.first_frame);
7841 }
7842 }
7843 }
7844 }
7845 }
7846 }
7847 }
7848
7849 /**
7850 * Debug console functionality
7851 */
7852 void dcf_pspew();
7853 DCF(pspew_count, "Number of particles spewed at a time")
7854 {
7855 if (dc_optional_string_either("help", "--help")) {
7856 dcf_pspew();
7857 return;
7858 }
7859
7860 if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) {
7861 dc_printf("Partical count is %i\n", Weapon_particle_spew_count);
7862 return;
7863 }
7864
7865 dc_stuff_int(&Weapon_particle_spew_count);
7866
7867 dc_printf("Partical count set to %i\n", Weapon_particle_spew_count);
7868 }
7869
7870 DCF(pspew_time, "Time between particle spews")
7871 {
7872 if (dc_optional_string_either("help", "--help")) {
7873 dcf_pspew();
7874 return;
7875 }
7876
7877 if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) {
7878 dc_printf("Particle spawn period is %i\n", Weapon_particle_spew_time);
7879 return;
7880 }
7881
7882 dc_stuff_int(&Weapon_particle_spew_time);
7883
7884 dc_printf("Particle spawn period set to %i\n", Weapon_particle_spew_time);
7885 }
7886
7887 DCF(pspew_vel, "Relative velocity of particles (0.0 - 1.0)")
7888 {
7889 if (dc_optional_string_either("help", "--help")) {
7890 dcf_pspew();
7891 return;
7892 }
7893
7894 if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) {
7895 dc_printf("Particle relative velocity is %f\n", Weapon_particle_spew_vel);
7896 return;
7897 }
7898
7899 dc_stuff_float(&Weapon_particle_spew_vel);
7900
7901 dc_printf("Particle relative velocity set to %f\n", Weapon_particle_spew_vel);
7902 }
7903
7904 DCF(pspew_size, "Size of spewed particles")
7905 {
7906 if (dc_optional_string_either("help", "--help")) {
7907 dcf_pspew();
7908 return;
7909 }
7910
7911 if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) {
7912 dc_printf("Particle size is %f\n", Weapon_particle_spew_radius);
7913 return;
7914 }
7915
7916 dc_stuff_float(&Weapon_particle_spew_radius);
7917
7918 dc_printf("Particle size set to %f\n", Weapon_particle_spew_radius);
7919 }
7920
7921 DCF(pspew_life, "Lifetime of spewed particles")
7922 {
7923 if (dc_optional_string_either("help", "--help")) {
7924 dcf_pspew();
7925 return;
7926 }
7927
7928 if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) {
7929 dc_printf("Particle lifetime is %f\n", Weapon_particle_spew_lifetime);
7930 return;
7931 }
7932
7933 dc_stuff_float(&Weapon_particle_spew_lifetime);
7934
7935 dc_printf("Particle lifetime set to %f\n", Weapon_particle_spew_lifetime);
7936 }
7937
7938 DCF(pspew_scale, "How far away particles are from the weapon path")
7939 {
7940 if (dc_optional_string_either("help", "--help")) {
7941 dcf_pspew();
7942 return;
7943 }
7944
7945 if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) {
7946 dc_printf("Particle scale is %f\n", Weapon_particle_spew_scale);
7947 }
7948
7949 dc_stuff_float(&Weapon_particle_spew_scale);
7950
7951 dc_printf("Particle scale set to %f\n", Weapon_particle_spew_scale);
7952 }
7953
7954 // Help and Status provider
7955 DCF(pspew, "Particle spew help and status provider")
7956 {
7957 if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) {
7958 dc_printf("Particle spew settings\n\n");
7959
7960 dc_printf(" Count (pspew_count) : %d\n", Weapon_particle_spew_count);
7961 dc_printf(" Time (pspew_time) : %d\n", Weapon_particle_spew_time);
7962 dc_printf(" Velocity (pspew_vel) : %f\n", Weapon_particle_spew_vel);
7963 dc_printf(" Size (pspew_size) : %f\n", Weapon_particle_spew_radius);
7964 dc_printf(" Lifetime (pspew_life) : %f\n", Weapon_particle_spew_lifetime);
7965 dc_printf(" Scale (psnew_scale) : %f\n", Weapon_particle_spew_scale);
7966 return;
7967 }
7968
7969 dc_printf("Available particlar spew commands:\n");
7970 dc_printf("pspew_count : %s\n", dcmd_pspew_count.help);
7971 dc_printf("pspew_time : %s\n", dcmd_pspew_time.help);
7972 dc_printf("pspew_vel : %s\n", dcmd_pspew_vel.help);
7973 dc_printf("pspew_size : %s\n", dcmd_pspew_size.help);
7974 dc_printf("pspew_life : %s\n", dcmd_pspew_life.help);
7975 dc_printf("pspew_scale : %s\n\n", dcmd_pspew_scale.help);
7976
7977 dc_printf("To view status of all pspew settings, type in 'pspew --status'.\n");
7978 dc_printf("Passing '--status' as an argument to any of the individual spew commands will show the status of that variable only.\n\n");
7979
7980 dc_printf("These commands adjust the various properties of the particle spew system, which is used by weapons when they are fired, are in-flight, and die (either by impact or by end of life time.\n");
7981 dc_printf("Generally, a large particle count with small size and scale will result in a nice dense particle spew.\n");
7982 dc_printf("Be advised, this effect is applied to _ALL_ weapons, and as such may drastically reduce framerates on lower powered platforms.\n");
7983 }
7984
7985 /**
7986 * Return a scale factor for damage which should be applied for 2 collisions
7987 */
weapon_get_damage_scale(weapon_info * wip,object * wep,object * target)7988 float weapon_get_damage_scale(weapon_info *wip, object *wep, object *target)
7989 {
7990 weapon *wp;
7991 int from_player = 0;
7992 float total_scale = 1.0f;
7993 float hull_pct;
7994 int is_big_damage_ship = 0;
7995
7996 // Goober5000 - additional sanity (target can be NULL)
7997 Assert(wip);
7998 Assert(wep);
7999
8000 // sanity
8001 if((wip == NULL) || (wep == NULL) || (target == NULL)){
8002 return 1.0f;
8003 }
8004
8005 // don't scale any damage if its not a weapon
8006 if((wep->type != OBJ_WEAPON) || (wep->instance < 0) || (wep->instance >= MAX_WEAPONS)){
8007 return 1.0f;
8008 }
8009 wp = &Weapons[wep->instance];
8010
8011 // was the weapon fired by the player
8012 from_player = 0;
8013 if((wep->parent >= 0) && (wep->parent < MAX_OBJECTS) && (Objects[wep->parent].flags[Object::Object_Flags::Player_ship])){
8014 from_player = 1;
8015 }
8016
8017 // if this is a lockarm weapon, and it was fired unlocked
8018 if((wip->wi_flags[Weapon::Info_Flags::Lockarm]) && !(wp->weapon_flags[Weapon::Weapon_Flags::Locked_when_fired])){
8019 total_scale *= 0.1f;
8020 }
8021
8022 // if the hit object was a ship and we're doing damage scaling
8023 if ( (target->type == OBJ_SHIP) &&
8024 !(The_mission.ai_profile->flags[AI::Profile_Flags::Disable_weapon_damage_scaling]) &&
8025 !(Ship_info[Ships[target->instance].ship_info_index].flags[Ship::Info_Flags::Disable_weapon_damage_scaling])
8026 ) {
8027 ship_info *sip;
8028
8029 // get some info on the ship
8030 Assert((target->instance >= 0) && (target->instance < MAX_SHIPS));
8031 if((target->instance < 0) || (target->instance >= MAX_SHIPS)){
8032 return total_scale;
8033 }
8034 sip = &Ship_info[Ships[target->instance].ship_info_index];
8035
8036 // get hull pct of the ship currently
8037 hull_pct = get_hull_pct(target);
8038
8039 // if it has hit a supercap ship and is not a supercap class weapon
8040 if((sip->flags[Ship::Info_Flags::Supercap]) && !(wip->wi_flags[Weapon::Info_Flags::Supercap])){
8041 // if the supercap is around 3/4 damage, apply nothing
8042 if(hull_pct <= 0.75f){
8043 return 0.0f;
8044 } else {
8045 total_scale *= SUPERCAP_DAMAGE_SCALE;
8046 }
8047 }
8048
8049 // determine if this is a big damage ship
8050 is_big_damage_ship = (sip->flags[Ship::Info_Flags::Big_damage]);
8051
8052 // if this is a large ship, and is being hit by flak
8053 if(is_big_damage_ship && (wip->wi_flags[Weapon::Info_Flags::Flak])){
8054 total_scale *= FLAK_DAMAGE_SCALE;
8055 }
8056
8057 // if the weapon is a small weapon being fired at a big ship
8058 if( is_big_damage_ship && !(wip->hurts_big_ships()) ){
8059
8060 // if the player is firing it
8061 if ( from_player && !(The_mission.ai_profile->flags[AI::Profile_Flags::Player_weapon_scale_fix])) {
8062 // if it's a laser weapon
8063 if(wip->subtype == WP_LASER){
8064 total_scale *= 0.01f;
8065 } else {
8066 total_scale *= 0.05f;
8067 }
8068 }
8069
8070 // scale based on hull
8071 if(hull_pct > 0.1f){
8072 total_scale *= hull_pct;
8073 } else {
8074 return 0.0f;
8075 }
8076 }
8077 }
8078
8079 return total_scale;
8080 }
8081
pause_in_flight_sounds()8082 void pause_in_flight_sounds()
8083 {
8084 for (int i = 0; i < MAX_WEAPONS; i++)
8085 {
8086 if (Weapons[i].objnum != -1)
8087 {
8088 weapon* wp = &Weapons[i];
8089
8090 if (wp->hud_in_flight_snd_sig.isValid() && snd_is_playing(wp->hud_in_flight_snd_sig)) {
8091 // Stop sound, it will be restarted in the first frame after the game is unpaused
8092 snd_stop(wp->hud_in_flight_snd_sig);
8093 }
8094 }
8095 }
8096 }
8097
weapon_pause_sounds()8098 void weapon_pause_sounds()
8099 {
8100 // Pause all beam sounds
8101 beam_pause_sounds();
8102
8103 // Pause in-flight sounds
8104 pause_in_flight_sounds();
8105 }
8106
weapon_unpause_sounds()8107 void weapon_unpause_sounds()
8108 {
8109 // Pause all beam sounds
8110 beam_unpause_sounds();
8111 }
8112
shield_impact_explosion(vec3d * hitpos,object * objp,float radius,int idx)8113 void shield_impact_explosion(vec3d *hitpos, object *objp, float radius, int idx) {
8114 int expl_ani_handle = Weapon_explosions.GetAnim(idx, hitpos, radius);
8115 particle::create(hitpos,
8116 &vmd_zero_vector,
8117 0.0f,
8118 radius,
8119 particle::PARTICLE_BITMAP_PERSISTENT,
8120 expl_ani_handle,
8121 objp);
8122 }
8123
weapon_render(object * obj,model_draw_list * scene)8124 void weapon_render(object* obj, model_draw_list *scene)
8125 {
8126 int num;
8127 weapon_info *wip;
8128 weapon *wp;
8129 color c;
8130
8131 MONITOR_INC(NumWeaponsRend, 1);
8132
8133 Assert(obj->type == OBJ_WEAPON);
8134
8135 num = obj->instance;
8136 wp = &Weapons[num];
8137 wip = &Weapon_info[Weapons[num].weapon_info_index];
8138
8139 if (wip->wi_flags[Weapon::Info_Flags::Transparent]) {
8140 if (wp->alpha_current == -1.0f) {
8141 wp->alpha_current = wip->alpha_max;
8142 } else if (wip->alpha_cycle > 0.0f) {
8143 if (wp->alpha_backward) {
8144 wp->alpha_current += wip->alpha_cycle;
8145
8146 if (wp->alpha_current > wip->alpha_max) {
8147 wp->alpha_current = wip->alpha_max;
8148 wp->alpha_backward = 0;
8149 }
8150 } else {
8151 wp->alpha_current -= wip->alpha_cycle;
8152
8153 if (wp->alpha_current < wip->alpha_min) {
8154 wp->alpha_current = wip->alpha_min;
8155 wp->alpha_backward = 1;
8156 }
8157 }
8158 }
8159 }
8160
8161 switch (wip->render_type)
8162 {
8163 case WRT_LASER:
8164 {
8165 if(wip->laser_length < 0.0001f)
8166 return;
8167
8168 int alpha = 255;
8169 int framenum = 0;
8170
8171 if (wip->laser_bitmap.first_frame >= 0) {
8172 gr_set_color_fast(&wip->laser_color_1);
8173
8174 if (wip->laser_bitmap.num_frames > 1) {
8175 wp->laser_bitmap_frame += flFrametime;
8176
8177 framenum = bm_get_anim_frame(wip->laser_bitmap.first_frame, wp->laser_bitmap_frame, wip->laser_bitmap.total_time, true);
8178 }
8179
8180 if (wip->wi_flags[Weapon::Info_Flags::Transparent])
8181 alpha = fl2i(wp->alpha_current * 255.0f);
8182
8183 if (The_mission.flags[Mission::Mission_Flags::Fullneb] && Neb_affects_weapons)
8184 alpha = (int)(alpha * neb2_get_fog_visibility(&obj->pos, NEB_FOG_VISIBILITY_MULT_WEAPON));
8185
8186 vec3d headp;
8187
8188 vm_vec_scale_add(&headp, &obj->pos, &obj->orient.vec.fvec, wip->laser_length);
8189
8190 batching_add_laser(wip->laser_bitmap.first_frame + framenum, &headp, wip->laser_head_radius, &obj->pos, wip->laser_tail_radius, alpha, alpha, alpha);
8191 }
8192
8193 // maybe draw laser glow bitmap
8194 if (wip->laser_glow_bitmap.first_frame >= 0) {
8195 // get the laser color
8196 weapon_get_laser_color(&c, obj);
8197
8198 // *Tail point "getting bigger" as well as headpoint isn't being taken into consideration, so
8199 // it caused uneven glow between the head and tail, which really shows in big lasers. So...fixed! -Et1
8200 vec3d headp2, tailp;
8201
8202 vm_vec_scale_add(&headp2, &obj->pos, &obj->orient.vec.fvec, wip->laser_length * weapon_glow_scale_l);
8203 vm_vec_scale_add(&tailp, &obj->pos, &obj->orient.vec.fvec, wip->laser_length * (1 - weapon_glow_scale_l) );
8204
8205 framenum = 0;
8206
8207 if (wip->laser_glow_bitmap.num_frames > 1) {
8208 wp->laser_glow_bitmap_frame += flFrametime;
8209
8210 // Sanity checks
8211 if (wp->laser_glow_bitmap_frame < 0.0f)
8212 wp->laser_glow_bitmap_frame = 0.0f;
8213 if (wp->laser_glow_bitmap_frame > 100.0f)
8214 wp->laser_glow_bitmap_frame = 0.0f;
8215
8216 while (wp->laser_glow_bitmap_frame > wip->laser_glow_bitmap.total_time)
8217 wp->laser_glow_bitmap_frame -= wip->laser_glow_bitmap.total_time;
8218
8219 framenum = fl2i( (wp->laser_glow_bitmap_frame * wip->laser_glow_bitmap.num_frames) / wip->laser_glow_bitmap.total_time );
8220
8221 CLAMP(framenum, 0, wip->laser_glow_bitmap.num_frames-1);
8222 }
8223
8224 if (wip->wi_flags[Weapon::Info_Flags::Transparent]) {
8225 alpha = fl2i(wp->alpha_current * 255.0f);
8226 alpha -= 38; // take 1.5f into account for the normal glow alpha
8227
8228 if (alpha < 0)
8229 alpha = 0;
8230 } else {
8231 alpha = weapon_glow_alpha;
8232 }
8233
8234 if (The_mission.flags[Mission::Mission_Flags::Fullneb] && Neb_affects_weapons)
8235 alpha = (int)(alpha * neb2_get_fog_visibility(&obj->pos, NEB_FOG_VISIBILITY_MULT_WEAPON));
8236
8237 batching_add_laser(wip->laser_glow_bitmap.first_frame + framenum, &headp2, wip->laser_head_radius * weapon_glow_scale_f, &tailp, wip->laser_tail_radius * weapon_glow_scale_r, (c.red*alpha)/255, (c.green*alpha)/255, (c.blue*alpha)/255);
8238 }
8239
8240 break;
8241 }
8242
8243 case WRT_POF:
8244 {
8245 model_render_params render_info;
8246
8247 uint render_flags = MR_NORMAL|MR_IS_MISSILE|MR_NO_BATCH;
8248
8249 if (wip->wi_flags[Weapon::Info_Flags::Mr_no_lighting])
8250 render_flags |= MR_NO_LIGHTING;
8251
8252 if (wip->wi_flags[Weapon::Info_Flags::Transparent]) {
8253 render_info.set_alpha(wp->alpha_current);
8254 render_flags |= MR_ALL_XPARENT;
8255 }
8256
8257 model_clear_instance(wip->model_num);
8258
8259 if ( (wip->wi_flags[Weapon::Info_Flags::Thruster]) && ((wp->thruster_bitmap > -1) || (wp->thruster_glow_bitmap > -1)) ) {
8260 float ft;
8261 mst_info mst;
8262
8263 // Add noise to thruster geometry.
8264 ft = 1.0f; // Always use 1.0f for missiles
8265 ft *= (1.0f + frand()/5.0f - 1.0f/10.0f);
8266 if (ft > 1.0f)
8267 ft = 1.0f;
8268
8269 mst.length.xyz.x = ft;
8270 mst.length.xyz.y = ft;
8271 mst.length.xyz.z = ft;
8272
8273 mst.primary_bitmap = wp->thruster_bitmap;
8274 mst.primary_glow_bitmap = wp->thruster_glow_bitmap;
8275 mst.glow_rad_factor = wip->thruster_glow_factor;
8276 mst.glow_noise = wp->thruster_glow_noise;
8277
8278 render_info.set_thruster_info(mst);
8279
8280 render_flags |= MR_SHOW_THRUSTERS;
8281 }
8282
8283
8284 //don't render local ssm's when they are still in subspace
8285 if (wp->lssm_stage==3)
8286 break;
8287
8288 // start a clip plane
8289 if ( wp->lssm_stage == 2 ) {
8290 object *wobj=&Objects[wp->lssm_warp_idx]; //warphole object
8291
8292 render_info.set_clip_plane(wobj->pos, wobj->orient.vec.fvec);
8293 }
8294
8295 render_info.set_flags(render_flags);
8296
8297 model_render_queue(&render_info, scene, wip->model_num, &obj->orient, &obj->pos);
8298
8299 break;
8300 }
8301
8302 default:
8303 Warning(LOCATION, "Unknown weapon rendering type = %i for weapon %s\n", wip->render_type, wip->name);
8304 }
8305 }
8306
8307 // Called by hudartillery.cpp after SSMs have been parsed to make sure that $SSM: entries defined in weapons are valid.
validate_SSM_entries()8308 void validate_SSM_entries()
8309 {
8310 int wi;
8311 SCP_vector<SCP_string>::const_iterator it;
8312 weapon_info *wip;
8313
8314 for (it = Delayed_SSM_names.begin(); it != Delayed_SSM_names.end(); ++it) {
8315 delayed_ssm_data *dat = &Delayed_SSM_data[*it];
8316 wi = weapon_info_lookup(it->c_str());
8317 Assertion(wi >= 0, "Trying to validate non-existant weapon '%s'; get a coder!\n", it->c_str());
8318 wip = &Weapon_info[wi];
8319 nprintf(("parse", "Starting validation of '%s' [wip->name is '%s'], currently has an SSM_index of %d.\n", it->c_str(), wip->name, wip->SSM_index));
8320 wip->SSM_index = ssm_info_lookup(dat->ssm_entry.c_str());
8321 if (wip->SSM_index < 0) {
8322 if (Ssm_info.empty()) {
8323 Warning(LOCATION, "SSM entry '%s' in specification for %s (%s:line %d), despite no SSM strikes being defined.\n", dat->ssm_entry.c_str(), it->c_str(), dat->filename.c_str(), dat->linenum);
8324 } else {
8325 Warning(LOCATION, "Unknown SSM entry '%s' in specification for %s (%s:line %d).\n", dat->ssm_entry.c_str(), it->c_str(), dat->filename.c_str(), dat->linenum);
8326 }
8327 }
8328 nprintf(("parse", "Validation complete, SSM_index is %d.\n", wip->SSM_index));
8329 }
8330
8331 // This information is no longer relevant, so might as well clear it out.
8332 Delayed_SSM_data.clear();
8333 Delayed_SSM_names.clear();
8334
8335 for (it = Delayed_SSM_indices.begin(); it != Delayed_SSM_indices.end(); ++it) {
8336 delayed_ssm_index_data *dat = &Delayed_SSM_indices_data[*it];
8337 wi = weapon_info_lookup(it->c_str());
8338 Assertion(wi >= 0, "Trying to validate non-existant weapon '%s'; get a coder!\n", it->c_str());
8339 wip = &Weapon_info[wi];
8340 nprintf(("parse", "Starting validation of '%s' [wip->name is '%s'], currently has an SSM_index of %d.\n", it->c_str(), wip->name, wip->SSM_index));
8341 if (wip->SSM_index < -1 || wip->SSM_index >= static_cast<int>(Ssm_info.size())) {
8342 if (Ssm_info.empty()) {
8343 Warning(LOCATION, "SSM index '%d' in specification for %s (%s:line %d), despite no SSM strikes being defined.\n", wip->SSM_index, it->c_str(), dat->filename.c_str(), dat->linenum);
8344 } else {
8345 Warning(LOCATION, "Invalid SSM index '%d' (should be 0-" SIZE_T_ARG ") in specification for %s (%s:line %d).\n", wip->SSM_index, Ssm_info.size() - 1, it->c_str(), dat->filename.c_str(), dat->linenum);
8346 }
8347 wip->SSM_index = -1;
8348 }
8349 nprintf(("parse", "Validation complete, SSM-index is %d.\n", wip->SSM_index));
8350 }
8351 }
8352
weapon_get_random_player_usable_weapon()8353 int weapon_get_random_player_usable_weapon()
8354 {
8355 SCP_vector<int> weapon_list;
8356
8357 for (int i = 0; i < weapon_info_size(); ++i)
8358 {
8359 // skip if we aren't supposed to use it
8360 if (!Weapon_info[i].wi_flags[Weapon::Info_Flags::Player_allowed])
8361 continue;
8362
8363 weapon_list.push_back(i);
8364 }
8365
8366 if (weapon_list.empty())
8367 return -1;
8368
8369 auto rand_wep = Random::next((int)weapon_list.size());
8370
8371 return weapon_list[rand_wep];
8372 }
8373
weapon_info()8374 weapon_info::weapon_info()
8375 {
8376 this->reset();
8377 }
8378
reset()8379 void weapon_info::reset()
8380 {
8381 // INITIALIZE NEW FIELDS HERE
8382 // The order should match the order in the struct!
8383 int i, j;
8384
8385 memset(this->name, 0, sizeof(this->name));
8386 memset(this->display_name, 0, sizeof(this->display_name));
8387 memset(this->title, 0, sizeof(this->title));
8388 this->desc = nullptr;
8389
8390 memset(this->pofbitmap_name, 0, sizeof(this->pofbitmap_name));
8391 this->model_num = -1;
8392 memset(this->external_model_name, 0, sizeof(this->external_model_name));
8393 this->external_model_num = -1;
8394
8395 this->tech_desc = nullptr;
8396 memset(this->tech_anim_filename, 0, sizeof(this->tech_anim_filename));
8397 memset(this->tech_title, 0, sizeof(this->tech_title));
8398 memset(this->tech_model, 0, sizeof(this->tech_model));
8399
8400 this->hud_target_lod = -1;
8401 this->num_detail_levels = -1;
8402 for (i = 0; i < MAX_MODEL_DETAIL_LEVELS; i++)
8403 {
8404 this->detail_distance[i] = -1;
8405 }
8406 this->subtype = WP_UNUSED;
8407 this->render_type = WRT_NONE;
8408
8409 vm_vec_zero(&this->closeup_pos);
8410 this->closeup_zoom = 1.0f;
8411
8412 memset(this->hud_filename, 0, sizeof(this->hud_filename));
8413 this->hud_image_index = -1;
8414
8415 generic_anim_init(&this->laser_bitmap);
8416 generic_anim_init(&this->laser_glow_bitmap);
8417
8418 this->laser_length = 10.0f;
8419 gr_init_color(&this->laser_color_1, 255, 255, 255);
8420 gr_init_color(&this->laser_color_2, 255, 255, 255);
8421 this->laser_head_radius = 1.0f;
8422 this->laser_tail_radius = 1.0f;
8423
8424 this->collision_radius_override = -1.0f;
8425 this->max_speed = 10.0f;
8426 this->acceleration_time = 0.0f;
8427 this->vel_inherit_amount = 1.0f;
8428 this->free_flight_time = 0.0f;
8429 this->free_flight_speed_factor = 0.25f;
8430 this->mass = 1.0f;
8431 this->fire_wait = 1.0f;
8432 this->max_delay = 0.0f;
8433 this->min_delay = 0.0f;
8434
8435 this->damage = 0.0f;
8436 this->damage_time = -1.0f;
8437 this->atten_damage = -1.0f;
8438 this->damage_incidence_max = 1.0f;
8439 this->damage_incidence_min = 1.0f;
8440
8441 shockwave_create_info_init(&this->shockwave);
8442 shockwave_create_info_init(&this->dinky_shockwave);
8443
8444 this->arm_time = 0;
8445 this->arm_dist = 0.0f;
8446 this->arm_radius = 0.0f;
8447 this->det_range = 0.0f;
8448 this->det_radius = 0.0f;
8449
8450 this->flak_detonation_accuracy = 65.0f;
8451 this->flak_targeting_accuracy = 60.0f; // Standard value as defined in flak.cpp
8452 this->untargeted_flak_range_penalty = 20.0f;
8453
8454 this->armor_factor = 1.0f;
8455 this->shield_factor = 1.0f;
8456 this->subsystem_factor = 1.0f;
8457
8458 this->life_min = -1.0f;
8459 this->life_max = -1.0f;
8460 this->max_lifetime = 1.0f;
8461 this->lifetime = 1.0f;
8462
8463 this->energy_consumed = 0.0f;
8464
8465 this->wi_flags.reset();
8466
8467 this->turn_time = 1.0f;
8468 this->turn_accel_time = 0.f;
8469 this->cargo_size = 1.0f;
8470 this->rearm_rate = 1.0f;
8471 this->reloaded_per_batch = -1;
8472 this->weapon_range = WEAPON_DEFAULT_TABLED_MAX_RANGE;
8473 // *Minimum weapon range, default is 0 -Et1
8474 this->weapon_min_range = 0.0f;
8475 this->optimum_range = 0.0f;
8476
8477 this->pierce_objects = false;
8478 this->spawn_children_on_pierce = false;
8479
8480 this->num_spawn_weapons_defined = 0;
8481 this->maximum_children_spawned = 0;
8482 for (i = 0; i < MAX_SPAWN_TYPES_PER_WEAPON; i++)
8483 {
8484 this->spawn_info[i].spawn_type = -1;
8485 this->spawn_info[i].spawn_angle = 180;
8486 this->spawn_info[i].spawn_min_angle = 0;
8487 this->spawn_info[i].spawn_count = DEFAULT_WEAPON_SPAWN_COUNT;
8488 this->spawn_info[i].spawn_interval = -1.f;
8489 this->spawn_info[i].spawn_interval_delay = -1.f;
8490 this->spawn_info[i].spawn_chance = 1.f;
8491 }
8492
8493 this->swarm_count = -1;
8494 // *Default is 150 -Et1
8495 this->SwarmWait = SWARM_MISSILE_DELAY;
8496
8497 this->target_restrict = LR_CURRENT_TARGET;
8498 this->multi_lock = false;
8499 this->trigger_lock = false;
8500 this->launch_reset_locks = false;
8501
8502 this->max_seeking = 1;
8503 this->max_seekers_per_target = 1;
8504 this->ship_restrict.clear();
8505 this->ship_restrict_strings.clear();
8506
8507 this->acquire_method = WLOCK_PIXEL;
8508 this->auto_target_method = HomingAcquisitionType::CLOSEST;
8509
8510 this->min_lock_time = 0.0f;
8511 this->lock_pixels_per_sec = 50;
8512 this->catchup_pixels_per_sec = 50;
8513 this->catchup_pixel_penalty = 50;
8514 this->fov = 0; //should be cos(pi), not pi
8515 this->seeker_strength = 1.0f;
8516 this->lock_fov = 0.85f;
8517
8518 this->pre_launch_snd = gamesnd_id();
8519 this->pre_launch_snd_min_interval = 0;
8520
8521 this->launch_snd = gamesnd_id();
8522 this->impact_snd = gamesnd_id();
8523 this->disarmed_impact_snd = gamesnd_id();
8524 this->flyby_snd = gamesnd_id();
8525
8526 this->hud_tracking_snd = gamesnd_id();
8527 this->hud_locked_snd = gamesnd_id();
8528 this->hud_in_flight_snd = gamesnd_id();
8529 this->in_flight_play_type = ALWAYS;
8530
8531 // Trails
8532 this->tr_info.pt = vmd_zero_vector;
8533 this->tr_info.w_start = 1.0f;
8534 this->tr_info.w_end = 1.0f;
8535 this->tr_info.a_start = 1.0f;
8536 this->tr_info.a_end = 1.0f;
8537 this->tr_info.max_life = 1.0f;
8538 this->tr_info.spread = 0.0f;
8539 this->tr_info.a_decay_exponent = 1.0f;
8540 this->tr_info.stamp = 0;
8541 generic_bitmap_init(&this->tr_info.texture, NULL);
8542 this->tr_info.n_fade_out_sections = 0;
8543 this->tr_info.texture_stretch = 1.0f;
8544
8545 memset(this->icon_filename, 0, sizeof(this->icon_filename));
8546 memset(this->anim_filename, 0, sizeof(this->anim_filename));
8547 this->selection_effect = Default_weapon_select_effect;
8548
8549 this->shield_impact_explosion_radius = 1.0f;
8550
8551 this->impact_weapon_expl_effect = particle::ParticleEffectHandle::invalid();
8552 this->dinky_impact_weapon_expl_effect = particle::ParticleEffectHandle::invalid();
8553 this->flash_impact_weapon_expl_effect = particle::ParticleEffectHandle::invalid();
8554
8555 this->piercing_impact_effect = particle::ParticleEffectHandle::invalid();
8556 this->piercing_impact_secondary_effect = particle::ParticleEffectHandle::invalid();
8557
8558 this->state_effects.clear();
8559
8560 this->emp_intensity = EMP_DEFAULT_INTENSITY;
8561 this->emp_time = EMP_DEFAULT_TIME; // Goober5000: <-- Look! I fixed a Volition bug! Gimme $5, Dave!
8562
8563 this->recoil_modifier = 1.0f;
8564
8565 this->weapon_reduce = ESUCK_DEFAULT_WEAPON_REDUCE;
8566 this->afterburner_reduce = ESUCK_DEFAULT_AFTERBURNER_REDUCE;
8567
8568 this->tag_time = -1.0f;
8569 this->tag_level = -1;
8570
8571 this->muzzle_flash = -1;
8572
8573 this->field_of_fire = 0.0f;
8574 this->fof_spread_rate = 0.0f;
8575 this->fof_reset_rate = 0.0f;
8576 this->max_fof_spread = 0.0f;
8577 this->shots = 1;
8578
8579 //customizeable corkscrew stuff
8580 this->cs_num_fired = 4;
8581 this->cs_radius = 1.25f;
8582 this->cs_delay = 30;
8583 this->cs_crotate = 1;
8584 this->cs_twist = 5.0f;
8585
8586 this->elec_time = 8000;
8587 this->elec_eng_mult = 1.0f;
8588 this->elec_weap_mult = 1.0f;
8589 this->elec_beam_mult = 1.0f;
8590 this->elec_sensors_mult = 1.0f;
8591 this->elec_randomness = 2000;
8592 this->elec_use_new_style = 0;
8593
8594 this->SSM_index = -1; // tag C SSM index, wich entry in the SSM table this weapon calls -Bobboau
8595
8596 this->lssm_warpout_delay = 0; //delay between launch and warpout (ms)
8597 this->lssm_warpin_delay = 0; //delay between warpout and warpin (ms)
8598 this->lssm_stage5_vel = 0; //velocity during final stage
8599 this->lssm_warpin_radius = 0;
8600 this->lssm_lock_range = 1000000.0f; //local ssm lock range (optional)
8601 this->lssm_warpeffect = FIREBALL_WARP; //Which fireballtype is used for the warp effect
8602
8603 this->b_info.beam_type = -1;
8604 this->b_info.beam_life = -1.0f;
8605 this->b_info.beam_warmup = -1;
8606 this->b_info.beam_warmdown = -1;
8607 this->b_info.beam_muzzle_radius = 0.0f;
8608 this->b_info.beam_particle_count = -1;
8609 this->b_info.beam_particle_radius = 0.0f;
8610 this->b_info.beam_particle_angle = 0.0f;
8611 this->b_info.beam_loop_sound = gamesnd_id();
8612 this->b_info.beam_warmup_sound = gamesnd_id();
8613 this->b_info.beam_warmdown_sound = gamesnd_id();
8614 this->b_info.beam_num_sections = 0;
8615 this->b_info.glow_length = 0;
8616 this->b_info.directional_glow = false;
8617 this->b_info.beam_shots = 1;
8618 this->b_info.beam_shrink_factor = 0.0f;
8619 this->b_info.beam_shrink_pct = 0.0f;
8620 this->b_info.beam_grow_factor = 0.0f;
8621 this->b_info.beam_grow_pct = 0.0f;
8622 this->b_info.beam_initial_width = 1.0f;
8623 this->b_info.range = BEAM_FAR_LENGTH;
8624 this->b_info.damage_threshold = 1.0f;
8625 this->b_info.beam_width = -1.0f;
8626 this->b_info.flags.reset();
8627
8628 // type 5 beam stuff
8629 this->b_info.t5info.no_translate = true;
8630 this->b_info.t5info.start_pos = Type5BeamPos::CENTER;
8631 this->b_info.t5info.end_pos = Type5BeamPos::CENTER;
8632 vm_vec_zero(&this->b_info.t5info.start_pos_offset);
8633 vm_vec_zero(&this->b_info.t5info.end_pos_offset);
8634 vm_vec_zero(&this->b_info.t5info.start_pos_rand);
8635 vm_vec_zero(&this->b_info.t5info.end_pos_rand);
8636 this->b_info.t5info.target_orient_positions = false;
8637 this->b_info.t5info.target_scale_positions = false;
8638 this->b_info.t5info.continuous_rot = 0.f;
8639 this->b_info.t5info.continuous_rot_axis = Type5BeamRotAxis::UNSPECIFIED;
8640 this->b_info.t5info.per_burst_rot = 0.f;
8641 this->b_info.t5info.per_burst_rot_axis = Type5BeamRotAxis::UNSPECIFIED;
8642 this->b_info.t5info.burst_rot_pattern.clear();
8643 this->b_info.t5info.burst_rot_axis = Type5BeamRotAxis::UNSPECIFIED;
8644
8645 generic_anim_init(&this->b_info.beam_glow, NULL);
8646 generic_anim_init(&this->b_info.beam_particle_ani, NULL);
8647
8648 for (i = 0; i < MAX_IFFS; i++)
8649 for (j = 0; j < NUM_SKILL_LEVELS; j++)
8650 this->b_info.beam_iff_miss_factor[i][j] = 0.00001f;
8651
8652 //WMC - Okay, so this is needed now
8653 beam_weapon_section_info *bsip;
8654 for (i = 0; i < MAX_BEAM_SECTIONS; i++) {
8655 bsip = &this->b_info.sections[i];
8656
8657 generic_anim_init(&bsip->texture, NULL);
8658
8659 bsip->width = 1.0f;
8660 bsip->flicker = 0.1f;
8661 bsip->z_add = i2fl(MAX_BEAM_SECTIONS - i - 1);
8662 bsip->tile_type = 0;
8663 bsip->tile_factor = 1.0f;
8664 bsip->translation = 0.0f;
8665 }
8666
8667 for (size_t s = 0; s < MAX_PARTICLE_SPEWERS; s++) { // default values for everything -nuke
8668 this->particle_spewers[s].particle_spew_type = PSPEW_NONE; // added by nuke
8669 this->particle_spewers[s].particle_spew_count = 1;
8670 this->particle_spewers[s].particle_spew_time = 25;
8671 this->particle_spewers[s].particle_spew_vel = 0.4f;
8672 this->particle_spewers[s].particle_spew_radius = 2.0f;
8673 this->particle_spewers[s].particle_spew_lifetime = 0.15f;
8674 this->particle_spewers[s].particle_spew_scale = 0.8f;
8675 this->particle_spewers[s].particle_spew_z_scale = 1.0f; // added by nuke
8676 this->particle_spewers[s].particle_spew_rotation_rate = 10.0f;
8677 this->particle_spewers[s].particle_spew_offset = vmd_zero_vector;
8678 this->particle_spewers[s].particle_spew_velocity = vmd_zero_vector;
8679 generic_anim_init(&this->particle_spewers[s].particle_spew_anim, NULL);
8680 }
8681
8682 this->cm_aspect_effectiveness = 1.0f;
8683 this->cm_heat_effectiveness = 1.0f;
8684 this->cm_effective_rad = MAX_CMEASURE_TRACK_DIST;
8685 this->cm_detonation_rad = CMEASURE_DETONATE_DISTANCE;
8686 this->cm_kill_single = false;
8687 this->cmeasure_timer_interval = 0;
8688 this->cmeasure_firewait = CMEASURE_WAIT;
8689 this->cmeasure_use_firewait = false;
8690 this->cmeasure_failure_delay_multiplier_ai = -1;
8691 this->cmeasure_sucess_delay_multiplier_ai = 2;
8692
8693 this->weapon_submodel_rotate_accell = 10.0f;
8694 this->weapon_submodel_rotate_vel = 0.0f;
8695
8696 this->damage_type_idx = -1;
8697 this->damage_type_idx_sav = -1;
8698
8699 this->armor_type_idx = -1;
8700
8701 this->alpha_max = 1.0f;
8702 this->alpha_min = 0.0f;
8703 this->alpha_cycle = 0.0f;
8704
8705 this->weapon_hitpoints = 0;
8706
8707 this->burst_shots = 0;
8708 this->burst_delay = 1.0f; // 1 second, just incase its not defined
8709 this->burst_flags.reset();
8710
8711 generic_anim_init(&this->thruster_flame);
8712 generic_anim_init(&this->thruster_glow);
8713 this->thruster_glow_factor = 1.0f;
8714
8715 this->target_lead_scaler = 0.0f;
8716 this->num_targeting_priorities = 0;
8717 for (i = 0; i < 32; ++i)
8718 this->targeting_priorities[i] = -1;
8719
8720 this->failure_rate = 0.0f;
8721 this->failure_sub_name.clear();
8722 this->failure_sub = -1;
8723
8724 this->num_substitution_patterns = 0;
8725 for (i = 0; i < MAX_SUBSTITUTION_PATTERNS; ++i)
8726 {
8727 this->weapon_substitution_pattern[i] = -1;
8728 this->weapon_substitution_pattern_names[i][0] = 0;
8729 }
8730
8731 this->score = 0;
8732
8733 // Reset using default constructor
8734 this->impact_decal = decals::creation_info();
8735
8736 this->on_create_program = actions::ProgramSet();
8737 }
8738
get_display_name() const8739 const char* weapon_info::get_display_name() const
8740 {
8741 if (has_display_name())
8742 return display_name;
8743 else
8744 return name;
8745 }
8746
has_display_name() const8747 bool weapon_info::has_display_name() const
8748 {
8749 return wi_flags[Weapon::Info_Flags::Has_display_name];
8750 }
8751
weapon_spew_stats(WeaponSpewType type)8752 void weapon_spew_stats(WeaponSpewType type)
8753 {
8754 #ifndef NDEBUG
8755 if (type == WeaponSpewType::NONE)
8756 return; // then why did we even call the function?
8757 bool all_weapons = (type == WeaponSpewType::ALL);
8758
8759 // csv weapon stats for comparisons
8760 mprintf(("Name,Type,Velocity,Range,Damage Hull,DPS Hull,Damage Shield,DPS Shield,Damage Subsystem,DPS Subsystem,Power Use,Fire Wait,ROF,Reload,1/Reload,Area Effect,Shockwave%s\n", all_weapons ? ",Player Allowed" : ""));
8761 for (auto &wi : Weapon_info)
8762 {
8763 if (wi.subtype != WP_LASER && wi.subtype != WP_BEAM)
8764 continue;
8765
8766 if (all_weapons || wi.wi_flags[Weapon::Info_Flags::Player_allowed])
8767 {
8768 mprintf(("%s,%s,", wi.name, "Primary"));
8769 //Beam range is set in the b_info and velocity isn't very relevant to them.
8770 if (wi.wi_flags[Weapon::Info_Flags::Beam])
8771 mprintf((",%.2f,",wi.b_info.range));
8772 else
8773 mprintf(("%.2f,%.2f,", wi.max_speed, wi.max_speed * wi.lifetime));
8774
8775 float damage;
8776 if (wi.wi_flags[Weapon::Info_Flags::Beam])
8777 damage = wi.damage * wi.b_info.beam_life * (1000.0f / i2fl(BEAM_DAMAGE_TIME));
8778 else
8779 damage = wi.damage;
8780
8781 float fire_rate;
8782 //To get overall fire rate, divide the number of shots in a firing cycle by the length of that cycle
8783 //In random length bursts, average between the longest and shortest firing cycle to get average rof
8784 if (wi.burst_shots > 1 && (wi.burst_flags[Weapon::Burst_Flags::Random_length]))
8785 fire_rate = (wi.burst_shots / (wi.fire_wait + wi.burst_delay * (wi.burst_shots - 1)) + (1 / wi.fire_wait)) / 2;
8786 else if (wi.burst_shots > 1)
8787 fire_rate = wi.burst_shots / (wi.fire_wait + wi.burst_delay * (wi.burst_shots - 1));
8788 else
8789 fire_rate = 1 / wi.fire_wait;
8790
8791 // doubled damage is handled strangely...
8792 float hull_multiplier = 1.0f;
8793 float shield_multiplier = 1.0f;
8794 float subsys_multiplier = 1.0f;
8795
8796 // area effect?
8797 if (wi.shockwave.inner_rad > 0.0f || wi.shockwave.outer_rad > 0.0f)
8798 {
8799 hull_multiplier = 2.0f;
8800 shield_multiplier = 2.0f;
8801
8802 // shockwave?
8803 if (wi.shockwave.speed > 0.0f)
8804 {
8805 subsys_multiplier = 2.0f;
8806 }
8807 }
8808
8809 // puncture?
8810 if (wi.wi_flags[Weapon::Info_Flags::Puncture])
8811 hull_multiplier /= 4;
8812
8813 // beams ignore factors unless specified
8814 float armor_factor = wi.armor_factor;
8815 float shield_factor = wi.shield_factor;
8816 float subsys_factor = wi.subsystem_factor;
8817 if (wi.wi_flags[Weapon::Info_Flags::Beam] && !Beams_use_damage_factors)
8818 armor_factor = shield_factor = subsys_factor = 1.0f;
8819
8820 mprintf(("%.2f,%.2f,", hull_multiplier * damage * armor_factor, hull_multiplier * damage * armor_factor * fire_rate));
8821 mprintf(("%.2f,%.2f,", shield_multiplier * damage * shield_factor, shield_multiplier * damage * shield_factor * fire_rate));
8822 mprintf(("%.2f,%.2f,", subsys_multiplier * damage * subsys_factor, subsys_multiplier * damage * subsys_factor * fire_rate));
8823
8824 mprintf(("%.2f,", wi.energy_consumed / wi.fire_wait));
8825 mprintf(("%.2f,%.2f,", wi.fire_wait, fire_rate));
8826 mprintf((",,")); // no reload for primaries
8827
8828 if (wi.shockwave.inner_rad > 0.0f || wi.shockwave.outer_rad > 0.0f)
8829 mprintf(("Yes,"));
8830 else
8831 mprintf((","));
8832
8833 if (wi.shockwave.speed > 0.0f)
8834 mprintf(("Yes"));
8835
8836 if (all_weapons)
8837 {
8838 mprintf((","));
8839 mprintf((wi.wi_flags[Weapon::Info_Flags::Player_allowed] ? "Yes" : ""));
8840 }
8841
8842 mprintf(("\n"));
8843 }
8844 }
8845 for (auto &wi : Weapon_info)
8846 {
8847 if (wi.subtype != WP_MISSILE)
8848 continue;
8849
8850 if (all_weapons || wi.wi_flags[Weapon::Info_Flags::Player_allowed] || wi.wi_flags[Weapon::Info_Flags::Child])
8851 {
8852 mprintf(("%s,%s,", wi.name, "Secondary"));
8853 mprintf(("%.2f,%.2f,", wi.max_speed, wi.max_speed * wi.lifetime));
8854
8855 // doubled damage is handled strangely...
8856 float hull_multiplier = 1.0f;
8857 float shield_multiplier = 1.0f;
8858 float subsys_multiplier = 1.0f;
8859
8860 // area effect?
8861 if (wi.shockwave.inner_rad > 0.0f || wi.shockwave.outer_rad > 0.0f)
8862 {
8863 hull_multiplier = 2.0f;
8864 shield_multiplier = 2.0f;
8865
8866 // shockwave?
8867 if (wi.shockwave.speed > 0.0f)
8868 {
8869 subsys_multiplier = 2.0f;
8870 }
8871 }
8872
8873 // puncture?
8874 if (wi.wi_flags[Weapon::Info_Flags::Puncture])
8875 hull_multiplier /= 4;
8876
8877 mprintf(("%.2f,%.2f,", hull_multiplier * wi.damage * wi.armor_factor, hull_multiplier * wi.damage * wi.armor_factor / wi.fire_wait));
8878 mprintf(("%.2f,%.2f,", shield_multiplier * wi.damage * wi.shield_factor, shield_multiplier * wi.damage * wi.shield_factor / wi.fire_wait));
8879 mprintf(("%.2f,%.2f,", subsys_multiplier * wi.damage * wi.subsystem_factor, subsys_multiplier * wi.damage * wi.subsystem_factor / wi.fire_wait));
8880
8881 mprintf((",")); // no power use for secondaries
8882 mprintf(("%.2f,%.2f,", wi.fire_wait, 1.0f / wi.fire_wait));
8883 mprintf(("%.2f,%.2f,", wi.reloaded_per_batch / wi.rearm_rate, wi.rearm_rate / wi.reloaded_per_batch)); // rearm_rate is actually the reciprocal of what is in weapons.tbl
8884
8885 if (wi.shockwave.inner_rad > 0.0f || wi.shockwave.outer_rad > 0.0f)
8886 mprintf(("Yes,"));
8887 else
8888 mprintf((","));
8889
8890 if (wi.shockwave.speed > 0.0f)
8891 mprintf(("Yes"));
8892
8893 if (all_weapons)
8894 {
8895 mprintf((","));
8896 mprintf((wi.wi_flags[Weapon::Info_Flags::Player_allowed] ? "Yes" : ""));
8897 }
8898
8899 mprintf(("\n"));
8900 }
8901 }
8902
8903 // mvp-style stats
8904 mprintf(("\n"));
8905 for (auto &wi : Weapon_info)
8906 {
8907 if (wi.subtype != WP_LASER && wi.subtype != WP_BEAM)
8908 continue;
8909
8910 if (all_weapons || wi.wi_flags[Weapon::Info_Flags::Player_allowed])
8911 {
8912 mprintf(("%s\n", wi.name));
8913 //Beam range is set in the b_info and velocity isn't very relevant to them.
8914 if (wi.wi_flags[Weapon::Info_Flags::Beam])
8915 mprintf(("\tVelocity: N/A Range: %.0f\n", wi.b_info.range));
8916 else
8917 mprintf(("\tVelocity: %-11.0fRange: %.0f\n", wi.max_speed, wi.max_speed* wi.lifetime));
8918
8919 float damage;
8920 if (wi.wi_flags[Weapon::Info_Flags::Beam])
8921 damage = wi.damage * wi.b_info.beam_life * (1000.0f / i2fl(BEAM_DAMAGE_TIME));
8922 else
8923 damage = wi.damage;
8924
8925 float fire_rate;
8926 //We need to count the length of a firing cycle and then divide by the number of shots in that cycle
8927 //In random length bursts, average between the longest and shortest firing cycle to get average rof
8928 if (wi.burst_shots > 1 && (wi.burst_flags[Weapon::Burst_Flags::Random_length]))
8929 fire_rate = (wi.burst_shots / (wi.fire_wait + wi.burst_delay * (wi.burst_shots - 1)) + (1 / wi.fire_wait)) / 2;
8930 else if (wi.burst_shots > 1)
8931 fire_rate = wi.burst_shots / (wi.fire_wait + wi.burst_delay * (wi.burst_shots - 1));
8932 else
8933 fire_rate = 1 / wi.fire_wait;
8934
8935 // doubled damage is handled strangely...
8936 float hull_multiplier = 1.0f;
8937 float shield_multiplier = 1.0f;
8938 float subsys_multiplier = 1.0f;
8939
8940 // area effect?
8941 if (wi.shockwave.inner_rad > 0.0f || wi.shockwave.outer_rad > 0.0f)
8942 {
8943 hull_multiplier = 2.0f;
8944 shield_multiplier = 2.0f;
8945
8946 // shockwave?
8947 if (wi.shockwave.speed > 0.0f)
8948 {
8949 subsys_multiplier = 2.0f;
8950 }
8951 }
8952
8953 // puncture?
8954 if (wi.wi_flags[Weapon::Info_Flags::Puncture])
8955 hull_multiplier /= 4;
8956
8957 // beams ignore factors unless specified
8958 float armor_factor = wi.armor_factor;
8959 float shield_factor = wi.shield_factor;
8960 float subsys_factor = wi.subsystem_factor;
8961 if (wi.wi_flags[Weapon::Info_Flags::Beam] && !Beams_use_damage_factors)
8962 armor_factor = shield_factor = subsys_factor = 1.0f;
8963
8964 mprintf(("\tDPS: "));
8965 mprintf(("%.0f Hull, ", hull_multiplier * damage * armor_factor * fire_rate));
8966 mprintf(("%.0f Shield, ", shield_multiplier * damage * shield_factor * fire_rate));
8967 mprintf(("%.0f Subsystem\n", subsys_multiplier * damage * subsys_factor * fire_rate));
8968
8969 char watts[NAME_LENGTH];
8970 sprintf(watts, "%.1f", wi.energy_consumed * fire_rate);
8971 char *p = strstr(watts, ".0");
8972 if (p)
8973 *p = 0;
8974 strcat(watts, "W");
8975
8976 char rof[NAME_LENGTH];
8977 sprintf(rof, "%.1f", fire_rate);
8978 p = strstr(rof, ".0");
8979 if (p)
8980 *p = 0;
8981 strcat(rof, "/s");
8982
8983 mprintf(("\tPower Use: %-10sROF: %s\n\n", watts, rof));
8984 }
8985 }
8986 for (auto &wi : Weapon_info)
8987 {
8988 if (wi.subtype != WP_MISSILE)
8989 continue;
8990
8991 if (all_weapons || wi.wi_flags[Weapon::Info_Flags::Player_allowed] || wi.wi_flags[Weapon::Info_Flags::Child])
8992 {
8993 mprintf(("%s\n", wi.name));
8994 mprintf(("\tVelocity: %-11.0fRange: %.0f\n", wi.max_speed, wi.max_speed * wi.lifetime));
8995
8996 // doubled damage is handled strangely...
8997 float hull_multiplier = 1.0f;
8998 float shield_multiplier = 1.0f;
8999 float subsys_multiplier = 1.0f;
9000
9001 // area effect?
9002 if (wi.shockwave.inner_rad > 0.0f || wi.shockwave.outer_rad > 0.0f)
9003 {
9004 hull_multiplier = 2.0f;
9005 shield_multiplier = 2.0f;
9006
9007 // shockwave?
9008 if (wi.shockwave.speed > 0.0f)
9009 {
9010 subsys_multiplier = 2.0f;
9011 }
9012 }
9013
9014 // puncture?
9015 if (wi.wi_flags[Weapon::Info_Flags::Puncture])
9016 hull_multiplier /= 4;
9017
9018 mprintf(("\tDamage: "));
9019 mprintf(("%.0f Hull, ", hull_multiplier * wi.damage * wi.armor_factor));
9020 mprintf(("%.0f Shield, ", shield_multiplier * wi.damage * wi.shield_factor));
9021 mprintf(("%.0f Subsystem\n", subsys_multiplier * wi.damage * wi.subsystem_factor));
9022
9023 char wait[NAME_LENGTH];
9024 sprintf(wait, "%.1f", wi.fire_wait);
9025 char *p = strstr(wait, ".0");
9026 if (p)
9027 *p = 0;
9028 strcat(wait, "s");
9029
9030 bool flip = wi.rearm_rate <= 1.0f;
9031 char flip_str[NAME_LENGTH];
9032 sprintf(flip_str, "%d/", wi.reloaded_per_batch);
9033
9034 char reload[NAME_LENGTH];
9035 sprintf(reload, "%.1f", flip ? wi.reloaded_per_batch / wi.rearm_rate : wi.rearm_rate);
9036 p = strstr(reload, ".0");
9037 if (p)
9038 *p = 0;
9039
9040 mprintf(("\tFire Wait: %-10sReload: %s%s%ss\n\n", wait, !flip ? flip_str : "", reload, flip ? "/" : ""));
9041 }
9042 }
9043 #endif
9044 }
9045
9046 // Given a weapon, figure out how many independent locks we can have with it.
weapon_get_max_missile_seekers(weapon_info * wip)9047 int weapon_get_max_missile_seekers(weapon_info *wip)
9048 {
9049 int max_target_locks;
9050
9051 if ( wip->multi_lock ) {
9052 if ( wip->wi_flags[Weapon::Info_Flags::Swarm] ) {
9053 max_target_locks = wip->swarm_count;
9054 } else if ( wip->wi_flags[Weapon::Info_Flags::Corkscrew] ) {
9055 max_target_locks = wip->cs_num_fired;
9056 } else {
9057 max_target_locks = 1;
9058 }
9059 } else {
9060 max_target_locks = 1;
9061 }
9062
9063 return max_target_locks;
9064 }
9065
9066 // returns whether a homing weapon can home on a particular ship
weapon_target_satisfies_lock_restrictions(weapon_info * wip,object * target)9067 bool weapon_target_satisfies_lock_restrictions(weapon_info* wip, object* target)
9068 {
9069 if (target->type != OBJ_SHIP)
9070 return true;
9071
9072 auto& restrictions = wip->ship_restrict;
9073 // if you didn't specify any restrictions, you can always lock
9074 if (restrictions.empty()) return true;
9075
9076 int type_num = Ship_info[Ships[target->instance].ship_info_index].class_type;
9077 int class_num = Ships[target->instance].ship_info_index;
9078 int species_num = Ship_info[Ships[target->instance].ship_info_index].species;
9079 int iff_num = Ships[target->instance].team;
9080
9081 // otherwise, you're good as long as it matches one of the allowances in the restriction list
9082 return std::any_of(restrictions.begin(), restrictions.end(),
9083 [=](std::pair<LockRestrictionType, int>& restriction) {
9084 switch (restriction.first) {
9085 case LockRestrictionType::TYPE: return restriction.second == type_num;
9086 case LockRestrictionType::CLASS: return restriction.second == class_num;
9087 case LockRestrictionType::SPECIES: return restriction.second == species_num;
9088 case LockRestrictionType::IFF: return restriction.second == iff_num;
9089 default: return true;
9090 }
9091 });
9092 }
9093
9094 // what it says on the tin
9095 // if true, the the IFF restrictions (later to be checked by the above) will replace the normal logic (can lock on enemies, not on friendlies)
weapon_has_iff_restrictions(weapon_info * wip)9096 bool weapon_has_iff_restrictions(weapon_info* wip)
9097 {
9098 auto& restrictions = wip->ship_restrict;
9099
9100 return std::any_of(restrictions.begin(), restrictions.end(),
9101 [=](std::pair<LockRestrictionType, int>& restriction) {
9102 return restriction.first == LockRestrictionType::IFF;
9103 });
9104 }
9105
weapon_secondary_world_pos_in_range(object * shooter,weapon_info * wip,vec3d * target_world_pos)9106 bool weapon_secondary_world_pos_in_range(object* shooter, weapon_info* wip, vec3d* target_world_pos)
9107 {
9108 vec3d vec_to_target;
9109 vm_vec_sub(&vec_to_target, target_world_pos, &shooter->pos);
9110 float dist_to_target = vm_vec_mag(&vec_to_target);
9111
9112 float weapon_range;
9113 //local ssms are always in range :)
9114 if (wip->wi_flags[Weapon::Info_Flags::Local_ssm])
9115 weapon_range = wip->lssm_lock_range;
9116 else
9117 // if the weapon can actually hit the target
9118 weapon_range = MIN((wip->max_speed * wip->lifetime), wip->weapon_range);
9119
9120
9121 extern int Nebula_sec_range;
9122 // reduce firing range in nebula
9123 if ((The_mission.flags[Mission::Mission_Flags::Fullneb]) && Nebula_sec_range) {
9124 weapon_range *= 0.8f;
9125 }
9126
9127 return dist_to_target <= weapon_range;
9128 }
9129
weapon_multilock_can_lock_on_subsys(object * shooter,object * target,ship_subsys * target_subsys,weapon_info * wip,float * out_dot)9130 bool weapon_multilock_can_lock_on_subsys(object* shooter, object* target, ship_subsys* target_subsys, weapon_info* wip, float* out_dot) {
9131 Assertion(shooter->type == OBJ_SHIP, "weapon_multilock_can_lock_on_subsys called with a non-ship shooter");
9132 if (shooter->type != OBJ_SHIP)
9133 return false;
9134
9135 if (target_subsys->flags[Ship::Subsystem_Flags::Untargetable])
9136 return false;
9137
9138 //by not checking for max_hits > 0 here, subsys' with disabled hitpoints are also excluded.
9139 if (!wip->wi_flags[Weapon::Info_Flags::Multilock_target_dead_subsys] && target_subsys->current_hits <= 0.0f)
9140 return false;
9141
9142 vec3d ss_pos;
9143 get_subsystem_world_pos(target, target_subsys, &ss_pos);
9144
9145 if (!weapon_secondary_world_pos_in_range(shooter, wip, &ss_pos))
9146 return false;
9147
9148 vec3d vec_to_target;
9149 vm_vec_normalized_dir(&vec_to_target, &ss_pos, &shooter->pos);
9150 float dot = vm_vec_dot(&shooter->orient.vec.fvec, &vec_to_target);
9151
9152 if (out_dot != nullptr)
9153 *out_dot = dot;
9154
9155 if (dot < wip->lock_fov)
9156 return false;
9157
9158 vec3d gsubpos;
9159 vm_vec_unrotate(&gsubpos, &target_subsys->system_info->pnt, &target->orient);
9160 vm_vec_add2(&gsubpos, &target->pos);
9161
9162 polymodel* pm = model_get(Ship_info[Ships[shooter->instance].ship_info_index].model_num);
9163 vec3d eye_pos = pm->view_positions[0].pnt + shooter->pos;
9164
9165 return ship_subsystem_in_sight(target, target_subsys, &eye_pos, &gsubpos) == 1;
9166 }
9167
weapon_multilock_can_lock_on_target(object * shooter,object * target_objp,weapon_info * wip,float * out_dot)9168 bool weapon_multilock_can_lock_on_target(object* shooter, object* target_objp, weapon_info* wip, float* out_dot) {
9169 Assertion(shooter->type == OBJ_SHIP, "weapon_multilock_can_lock_on_target called with a non-ship shooter");
9170 if (target_objp->type != OBJ_SHIP)
9171 return false;
9172
9173 if (hud_target_invalid_awacs(target_objp))
9174 return false;
9175
9176 ship* target_ship = &Ships[target_objp->instance];
9177
9178 if (target_objp->flags[Object::Object_Flags::Should_be_dead])
9179 return false;
9180
9181 if (target_ship->flags[Ship::Ship_Flags::Dying])
9182 return false;
9183
9184 if (should_be_ignored(target_ship))
9185 return false;
9186
9187 // if this is part of the same team and doesn't have any iff restrictions, reject lock
9188 if (!weapon_has_iff_restrictions(wip) && Ships[shooter->instance].team == obj_team(target_objp))
9189 return false;
9190
9191 vec3d vec_to_target;
9192 vm_vec_normalized_dir(&vec_to_target, &target_objp->pos, &shooter->pos);
9193 float dot = vm_vec_dot(&shooter->orient.vec.fvec, &vec_to_target);
9194
9195 if (out_dot != nullptr)
9196 *out_dot = dot;
9197
9198 return weapon_target_satisfies_lock_restrictions(wip, target_objp);
9199 }
9200