2 * Copyright (C) Volition, Inc. 1999. All rights reserved.
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
10 * $Logfile: /Freespace2/code/Weapon/Swarm.cpp $
15 * C module for managing swarm missiles
18 * Revision 1.4 2002/06/17 06:33:11 relnev
19 * ryan's struct patch for gcc 2.95
21 * Revision 1.3 2002/06/09 04:41:29 relnev
22 * added copyright header
24 * Revision 1.2 2002/05/07 03:16:53 theoddone33
25 * The Great Newline Fix
27 * Revision 1.1.1.1 2002/05/03 03:28:11 root
31 * 8 5/20/99 7:00p Dave
32 * Added alternate type names for ships. Changed swarm missile table
35 * 7 2/25/99 4:19p Dave
36 * Added multiplayer_beta defines. Added cd_check define. Fixed a few
37 * release build warnings. Added more data to the squad war request and
40 * 6 1/30/99 1:46p Andsager
41 * Clean up code. Remove int3, replace warning.
43 * 5 1/29/99 2:47p Andsager
44 * Put asset back in for turret_swarm_missiles
46 * 4 1/29/99 2:25p Andsager
47 * Added turret_swarm_missiles
49 * 3 11/05/98 5:55p Dave
50 * Big pass at reducing #includes
52 * 2 10/07/98 10:54a Dave
55 * 1 10/07/98 10:51a Dave
57 * 14 5/03/98 5:52p Chad
58 * AL: Fix bug with WIF_SWARM flag getting cleared from weapon info
60 * 13 3/31/98 5:19p John
61 * Removed demo/save/restore. Made NDEBUG defined compile. Removed a
62 * bunch of debug stuff out of player file. Made model code be able to
63 * unload models and malloc out only however many models are needed.
66 * 12 2/26/98 10:08p Hoffoss
67 * Rewrote state saving and restoring to fix bugs and simplify the code.
69 * 11 2/13/98 3:36p Jim
70 * AL: Fix problem with swarm missiles that occurred at low framerates
72 * 10 1/14/98 3:02p Sandeep
73 * Fix bug where parent ship dies in the same frame where swarm missiles
76 * 9 11/06/97 12:27a Mike
77 * Better avoid behavior.
78 * Modify ai_turn_towards_vector() to take a flag parameter.
80 * 8 11/03/97 9:42p Lawrance
81 * make swarm paths more random, fix bug with wild swarmers after homing
84 * 7 8/24/97 10:26p Lawrance
85 * implemented homing swarm missiles
87 * 6 8/14/97 12:27a Mike
88 * Changed ai_turn_towards_vector() to take bank_override, had to change
89 * numerous calls to it.
91 * 5 8/13/97 4:45p Allender
92 * fixed ship_fire_primary and fire_secondary to not take parameter for
93 * determining whether to count ammo or not. Fixed countermeasure firing
96 * 4 8/13/97 12:06p Lawrance
97 * make swarm bursts more random
99 * 3 8/11/97 9:50a Lawrance
100 * make swarmers more erratic
102 * 2 8/10/97 6:16p Lawrance
103 * split off swarm missile code into a separate file
112 #include "freespace.h" // for Missiontime
113 #include "linklist.h"
115 #define SWARM_DIST_OFFSET 2.0 // distance swarm missile should vary from original path
116 #define SWARM_CONE_LENGTH 10000.0f // used to pick a target point far in the distance
117 #define SWARM_CHANGE_DIR_TIME 400 // time to force change in direction of swarm missile
118 #define SWARM_ANGLE_CHANGE (4*PI/180) // in rad
119 #define SWARM_MISSILE_DELAY 150 // time delay between each swarm missile that is fired
120 #define SWARM_TIME_VARIANCE 100 // max time variance when deciding when to change swarm missile course
122 #define SWARM_DIST_STOP_SWARMING 300
124 #define TURRET_SWARM_VALIDITY_CHECKTIME 5000 // number of ms between checks on turret_swam_info checks
126 #define SWARM_USED (1<<0)
127 #define SWARM_POSITIVE_PATH (1<<1)
129 typedef struct swarm_info {
131 int change_timestamp;
132 vector original_target;
134 vector circle_rvec, circle_uvec;
137 int path_num; // which path swarm missile is currently following
138 int homing_objnum; // object number that swarm missile is homing on, -1 if not homing
139 int change_time; // when swarm missile should next update direction, based on missile speed
141 float last_dist; // last distance to target
144 typedef struct turret_swarm_info {
152 ship_subsys* target_subsys;
157 #define MAX_SWARM_MISSILES 50
158 swarm_info Swarm_missiles[MAX_SWARM_MISSILES];
160 #define MAX_TURRET_SWARM_INFO 50
161 turret_swarm_info Turret_swarm_info[MAX_TURRET_SWARM_INFO];
163 int Turret_swarm_validity_next_check_time;
165 // ------------------------------------------------------------------
166 // swarm_level_init()
168 // Called at the start of each new mission
170 void swarm_level_init()
174 turret_swarm_info *tswarmp;
176 for ( i = 0; i < MAX_SWARM_MISSILES; i++ ) {
177 swarmp = &Swarm_missiles[i];
179 swarmp->change_timestamp = 1;
180 swarmp->path_num = -1;
183 for (i=0; i<MAX_TURRET_SWARM_INFO; i++) {
184 tswarmp = &Turret_swarm_info[i];
186 tswarmp->num_to_launch = 0;
187 tswarmp->parent_objnum = -1;
188 tswarmp->parent_sig = -1;
189 tswarmp->target_objnum = -1;
190 tswarmp->target_sig = -1;
191 tswarmp->turret = NULL;
192 tswarmp->target_subsys = NULL;
193 tswarmp->time_to_fire = 0;
196 Turret_swarm_validity_next_check_time = timestamp(TURRET_SWARM_VALIDITY_CHECKTIME);
199 // ------------------------------------------------------------------
200 // swarm_maybe_fire_missile()
202 // Check if there are any swarm missiles to fire, and if enough time
203 // has elapsed since last one fired, go ahead and fire it.
205 // This is called once per ship frame in ship_move()
207 void swarm_maybe_fire_missile(int shipnum)
211 int weapon_info_index;
213 SDL_assert(shipnum >= 0 && shipnum < MAX_SHIPS );
214 sp = &Ships[shipnum];
216 if ( sp->num_swarm_missiles_to_fire <= 0 )
220 if ( swp->current_secondary_bank == -1 ) {
221 sp->num_swarm_missiles_to_fire = 0;
225 weapon_info_index = swp->secondary_bank_weapons[swp->current_secondary_bank];
226 SDL_assert( weapon_info_index >= 0 && weapon_info_index < MAX_WEAPON_TYPES );
228 // if current secondary bank is not a swarm missile, return
229 if ( !(Weapon_info[weapon_info_index].wi_flags & WIF_SWARM) ) {
230 sp->num_swarm_missiles_to_fire = 0;
234 if ( timestamp_elapsed(sp->next_swarm_fire) ) {
235 sp->next_swarm_fire = timestamp(SWARM_MISSILE_DELAY);
236 ship_fire_secondary( &Objects[sp->objnum], 1 );
237 sp->num_swarm_missiles_to_fire--;
241 // ------------------------------------------------------------------
244 // Get a free swarm missile entry, and initialize the struct members
249 swarm_info *swarmp = NULL;
251 for ( i = 0; i < MAX_SWARM_MISSILES; i++ ) {
252 swarmp = &Swarm_missiles[i];
253 if ( !(swarmp->flags & SWARM_USED) )
257 if ( i >= MAX_SWARM_MISSILES ) {
258 nprintf(("Warning","No more swarm missiles are available\n"));
263 swarmp->change_timestamp = 1;
264 swarmp->path_num = -1;
265 swarmp->homing_objnum = -1;
267 swarmp->flags |= SWARM_USED;
271 // ------------------------------------------------------------------
275 void swarm_delete(int i)
279 SDL_assert(i >= 0 && i < MAX_SWARM_MISSILES);
280 swarmp = &Swarm_missiles[i];
282 if ( !(swarmp->flags & SWARM_USED) ) {
283 Int3(); // tried to delete a swarm missile that didn't exist, get Alan
289 // ------------------------------------------------------------------
290 // swarm_update_direction()
292 // Check if we want to update the direction of a swarm missile.
294 void swarm_update_direction(object *objp, float frametime)
300 vector obj_to_target;
301 float vel, target_dist, radius, missile_speed, missile_dist;
304 SDL_assert(objp->instance >= 0 && objp->instance < MAX_WEAPONS);
306 wp = &Weapons[objp->instance];
308 if (wp->swarm_index == -1) {
312 wip = &Weapon_info[wp->weapon_info_index];
313 hobjp = wp->homing_object;
314 pi = &Objects[wp->objnum].phys_info;
315 swarmp = &Swarm_missiles[wp->swarm_index];
317 // check if homing is lost.. if it is then get a new path to move swarm missile along
318 if ( swarmp->homing_objnum != -1 && hobjp == &obj_used_list ) {
319 swarmp->change_timestamp = 1;
320 swarmp->path_num = -1;
321 swarmp->homing_objnum = -1;
324 if ( hobjp != &obj_used_list ) {
325 swarmp->homing_objnum = OBJ_INDEX(hobjp);
328 if ( timestamp_elapsed(swarmp->change_timestamp) ) {
330 if ( swarmp->path_num == -1 ) {
331 if ( Objects[objp->parent].type != OBJ_SHIP ) {
332 //AL: parent ship died... so just pick some random paths
333 swarmp->path_num = myrand()%4;
336 parent_shipp = &Ships[Objects[objp->parent].instance];
337 swarmp->path_num = (parent_shipp->next_swarm_path++)%4;
339 if ( parent_shipp->next_swarm_path%4 == 0 ) {
340 swarmp->flags ^= SWARM_POSITIVE_PATH;
344 vm_vec_scale_add(&swarmp->original_target, &objp->pos, &objp->orient.v.fvec, SWARM_CONE_LENGTH);
345 swarmp->circle_rvec = objp->orient.v.rvec;
346 swarmp->circle_uvec = objp->orient.v.uvec;
348 swarmp->change_count = 1;
349 swarmp->change_time = fl2i(SWARM_CHANGE_DIR_TIME + SWARM_TIME_VARIANCE*(frand() - 0.5f) * 2);
351 vm_vec_zero(&swarmp->last_offset);
353 missile_speed = pi->speed;
354 missile_dist = missile_speed * swarmp->change_time/1000.0f;
355 if ( missile_dist < SWARM_DIST_OFFSET ) {
356 missile_dist=i2fl(SWARM_DIST_OFFSET);
358 swarmp->angle_offset = (float)(asin(SWARM_DIST_OFFSET / missile_dist));
359 SDL_assert(!_isnan(swarmp->angle_offset) );
362 swarmp->change_timestamp = timestamp(swarmp->change_time);
364 // check if swarm missile is homing, if so need to calculate a new target pos to turn towards
365 if ( hobjp != &obj_used_list && f2fl(Missiontime - wp->creation_time) > 0.5f ) {
366 swarmp->original_target = wp->homing_pos;
368 // Calculate a rvec and uvec that will determine the displacement from the
369 // intended target. Use crossprod to generate a right vector, from the missile
370 // up vector and the vector connecting missile to the homing object.
371 swarmp->circle_uvec = objp->orient.v.uvec;
372 swarmp->circle_rvec = objp->orient.v.rvec;
374 missile_speed = pi->speed;
375 missile_dist = missile_speed * swarmp->change_time/1000.0f;
376 swarmp->angle_offset = (float)(asin(SWARM_DIST_OFFSET / missile_dist));
377 SDL_assert(!_isnan(swarmp->angle_offset) );
380 vm_vec_sub(&obj_to_target, &swarmp->original_target, &objp->pos);
381 target_dist = vm_vec_mag_quick(&obj_to_target);
382 swarmp->last_dist = target_dist;
384 // If homing swarm missile is close to target, let missile home in on original target
385 if ( target_dist < SWARM_DIST_STOP_SWARMING ) {
386 swarmp->new_target = swarmp->original_target;
387 goto swarm_new_target_calced;
390 radius = (float)tan(swarmp->angle_offset) * target_dist;
391 vector rvec_component, uvec_component;
393 swarmp->change_count++;
394 if ( swarmp->change_count > 2 ) {
395 swarmp->flags ^= SWARM_POSITIVE_PATH;
396 swarmp->change_count = 0;
399 // pick a new path number to follow once at center
400 if ( swarmp->change_count == 1 ) {
401 swarmp->path_num = swarmp->path_num + myrand()%3;
402 if ( swarmp->path_num > 3 ) {
403 swarmp->path_num = 0;
407 vm_vec_zero(&rvec_component);
408 vm_vec_zero(&uvec_component);
410 switch ( swarmp->path_num ) {
411 case 0: // straight up and down
412 if ( swarmp->flags & SWARM_POSITIVE_PATH )
413 vm_vec_copy_scale( &uvec_component, &swarmp->circle_uvec, radius);
415 vm_vec_copy_scale( &uvec_component, &swarmp->circle_uvec, -radius);
418 case 1: // left/right
419 if ( swarmp->flags & SWARM_POSITIVE_PATH )
420 vm_vec_copy_scale( &rvec_component, &swarmp->circle_rvec, radius);
422 vm_vec_copy_scale( &rvec_component, &swarmp->circle_rvec, -radius);
425 case 2: // top/right - bottom/left
426 if ( swarmp->flags & SWARM_POSITIVE_PATH ) {
427 vm_vec_copy_scale( &rvec_component, &swarmp->circle_rvec, radius);
428 vm_vec_copy_scale( &uvec_component, &swarmp->circle_uvec, radius);
431 vm_vec_copy_scale( &rvec_component, &swarmp->circle_rvec, -radius);
432 vm_vec_copy_scale( &uvec_component, &swarmp->circle_uvec, -radius);
436 case 3: // top-left - bottom/right
437 if ( swarmp->flags & SWARM_POSITIVE_PATH ) {
438 vm_vec_copy_scale( &rvec_component, &swarmp->circle_rvec, -radius);
439 vm_vec_copy_scale( &uvec_component, &swarmp->circle_uvec, radius);
442 vm_vec_copy_scale( &rvec_component, &swarmp->circle_rvec, radius);
443 vm_vec_copy_scale( &uvec_component, &swarmp->circle_uvec, -radius);
451 swarmp->new_target = swarmp->original_target;
452 vm_vec_zero(&swarmp->last_offset);
453 vm_vec_add(&swarmp->last_offset, &uvec_component, &rvec_component);
454 vm_vec_add2(&swarmp->new_target, &swarmp->last_offset);
457 if ( hobjp != &obj_used_list && f2fl(Missiontime - wp->creation_time) > 0.5f ) {
459 swarmp->new_target = swarmp->original_target;
460 if ( swarmp->last_dist < SWARM_DIST_STOP_SWARMING ) {
461 swarmp->new_target = wp->homing_pos;
462 goto swarm_new_target_calced;
465 vm_vec_add2(&swarmp->new_target, &swarmp->last_offset);
469 swarm_new_target_calced:
471 ai_turn_towards_vector(&swarmp->new_target, objp, frametime, wip->turn_time, NULL, NULL, 0.0f, 0);
472 vel = vm_vec_mag(&objp->phys_info.desired_vel);
473 vm_vec_copy_scale(&objp->phys_info.desired_vel, &objp->orient.v.fvec, vel);
476 // ------------------------------------------------------------------
477 // turret_swarm_create()
479 // Get a free swarm missile entry, and initialize the struct members
481 int turret_swarm_create()
484 turret_swarm_info *tswarmp = NULL;
486 for (i=0; i<MAX_TURRET_SWARM_INFO; i++) {
487 tswarmp = &Turret_swarm_info[i];
488 if ( !(tswarmp->flags & SWARM_USED) )
492 if ( i >= MAX_TURRET_SWARM_INFO ) {
493 nprintf(("Warning","No more turret swarm info slots are available\n"));
499 tswarmp->num_to_launch = 0;
500 tswarmp->parent_objnum = -1;
501 tswarmp->parent_sig = -1;
502 tswarmp->target_objnum = -1;
503 tswarmp->target_sig = -1;
504 tswarmp->turret = NULL;
505 tswarmp->target_subsys = NULL;
506 tswarmp->time_to_fire = 0;
508 tswarmp->flags |= SWARM_USED;
512 // ------------------------------------------------------------------
513 // turret_swarm_delete()
515 void turret_swarm_delete(int i)
517 turret_swarm_info *tswarmp;
519 SDL_assert(i >= 0 && i < MAX_TURRET_SWARM_INFO);
520 tswarmp = &Turret_swarm_info[i];
522 if ( !(tswarmp->flags & SWARM_USED) ) {
523 Int3(); // tried to delete a swarm missile that didn't exist, get DaveA
529 // Set up turret swarm info struct
530 void turret_swarm_set_up_info(int parent_objnum, ship_subsys *turret, int turret_weapon_class)
532 turret_swarm_info *tsi;
533 object *parent_obj, *target_obj;
538 // weapon info pointer
539 SDL_assert((turret_weapon_class >= 0) && (turret_weapon_class < Num_weapon_types));
540 if((turret_weapon_class < 0) || (turret_weapon_class >= Num_weapon_types)){
543 wip = &Weapon_info[turret_weapon_class];
546 SDL_assert((parent_objnum >= 0) && (parent_objnum < MAX_OBJECTS));
547 if((parent_objnum < 0) || (parent_objnum >= MAX_OBJECTS)){
550 parent_obj = &Objects[parent_objnum];
551 SDL_assert(parent_obj->type == OBJ_SHIP);
552 shipp = &Ships[parent_obj->instance];
553 SDL_assert((turret->turret_enemy_objnum >= 0) && (turret->turret_enemy_objnum < MAX_OBJECTS));
554 if((turret->turret_enemy_objnum < 0) || (turret->turret_enemy_objnum >= MAX_OBJECTS)){
557 target_obj = &Objects[turret->turret_enemy_objnum];
559 // valid swarm weapon
560 SDL_assert((wip->wi_flags & WIF_SWARM) && (wip->swarm_count > 0));
561 if(!(wip->wi_flags & WIF_SWARM) || (wip->swarm_count <= 0)){
565 // get turret_swarm_info
566 tsi_index = turret_swarm_create();
567 if (tsi_index == -1) {
571 // set turret to point to tsi
572 tsi = &Turret_swarm_info[tsi_index];
573 if (turret->turret_swarm_info_index != -1) {
574 // overlapping firing intervals..., stop old and start new
575 mprintf(("Overlapping turret swarm firing intervals"));
576 turret_swarm_delete(turret->turret_swarm_info_index);
577 turret->turret_swarm_info_index = -1;
578 shipp->num_turret_swarm_info--;
580 turret->turret_swarm_info_index = tsi_index;
582 // increment ship tsi counter
583 shipp->num_turret_swarm_info++;
585 // make sure time is sufficient to launch all the missiles before next volley
587 SDL_assert(wip->swarm_count * SWARM_MISSILE_DELAY < wip->fire_wait * 1000.0f);
591 tsi->num_to_launch = wip->swarm_count;
592 tsi->parent_objnum = parent_objnum;
593 tsi->parent_sig = parent_obj->signature;
594 tsi->target_objnum = turret->turret_enemy_objnum;
595 tsi->target_sig = target_obj->signature;
596 tsi->turret = turret;
597 tsi->target_subsys = turret->targeted_subsys;
598 tsi->time_to_fire = 1; // first missile next frame
601 void turret_swarm_fire_from_turret(ship_subsys *turret, int parent_objnum, int target_objnum, ship_subsys *target_subsys);
602 // check if ship has turret ready to fire swarm type missiles
603 void turret_swarm_maybe_fire_missile(int shipnum)
605 ship *shipp = &Ships[shipnum];
607 turret_swarm_info *tsi;
608 object *parent_obj, *target_obj;
609 int target_objnum, num_turret_swarm_turrets_left;
611 // check if ship has any turrets ready to fire
612 if (shipp->num_turret_swarm_info <= 0) {
613 SDL_assert(shipp->num_turret_swarm_info == 0);
617 // ship obj which has fired turret swarm missiles
618 parent_obj = &Objects[shipp->objnum];
619 num_turret_swarm_turrets_left = shipp->num_turret_swarm_info;
621 // search ship subsystems for turrets with valid turret_swarm_info_index
622 for (subsys = GET_FIRST(&shipp->subsys_list); subsys != END_OF_LIST(&shipp->subsys_list); subsys = GET_NEXT(subsys)) {
623 if (subsys->turret_swarm_info_index != -1) {
625 num_turret_swarm_turrets_left--;
626 SDL_assert(num_turret_swarm_turrets_left >= 0);
628 // get turret_swarm_info
629 SDL_assert( (subsys->turret_swarm_info_index >= 0) && (subsys->turret_swarm_info_index < MAX_TURRET_SWARM_INFO) );
630 tsi = &Turret_swarm_info[subsys->turret_swarm_info_index];
632 // check if parent ship is valid (via signature)
633 if ( (tsi->parent_sig == parent_obj->signature) ) {
635 // make sure we have the right turret.
636 SDL_assert(tsi->turret == subsys);
638 // check if time to fire
639 if (timestamp_elapsed(tsi->time_to_fire)) {
640 SDL_assert(tsi->num_to_launch > 0);
642 // check target still alive
644 if (tsi->target_objnum > -1) {
645 target_obj= &Objects[tsi->target_objnum];
647 if (target_obj->signature == tsi->target_sig) {
648 target_objnum = tsi->target_objnum;
650 // poor target, it died
651 tsi->target_objnum = -1;
655 // make sure turret is still alive and fire swarmer
656 if (subsys->current_hits > 0) {
657 turret_swarm_fire_from_turret(tsi->turret, tsi->parent_objnum, target_objnum, tsi->target_subsys);
661 tsi->time_to_fire = timestamp(SWARM_MISSILE_DELAY);
664 tsi->num_to_launch--;
666 if (tsi->num_to_launch == 0) {
667 shipp->num_turret_swarm_info--;
668 turret_swarm_delete(subsys->turret_swarm_info_index);
669 subsys->turret_swarm_info_index = -1;
673 Warning(LOCATION, "Found turret swarm info on ship: %s with turret: %s, but signature does not match.", shipp->ship_name, subsys->system_info->subobj_name);
674 shipp->num_turret_swarm_info--;
675 turret_swarm_delete(subsys->turret_swarm_info_index);
676 subsys->turret_swarm_info_index = -1;
681 SDL_assert(num_turret_swarm_turrets_left == 0);
684 // check Turret_swarm_info for info that are invalid - ie, ships died while firing.
685 void turret_swarm_check_validity()
688 turret_swarm_info *tswarmp;
691 if (timestamp_elapsed(Turret_swarm_validity_next_check_time)) {
694 Turret_swarm_validity_next_check_time = timestamp(TURRET_SWARM_VALIDITY_CHECKTIME);
696 // go through all Turret_swarm_info, check obj and obj->signature
697 for (i=0; i<MAX_TURRET_SWARM_INFO; i++) {
698 tswarmp = &Turret_swarm_info[i];
700 if (tswarmp->flags & SWARM_USED) {
701 ship_obj = &Objects[tswarmp->parent_objnum];
702 if (ship_obj->type == OBJ_SHIP) {
703 if (ship_obj->signature == tswarmp->parent_sig) {
708 turret_swarm_delete(i);