Files
fs2open.github.com/code/weapon/swarm.cpp
T
DahBlount 688baa2bb6 Turret Ammo Implementation
This creates the foundation of turret ammo through the "turret use ammo" subsystem flag. Turret ammo works in the same manner as ship ammo, where the ammo is equal to bank capacity divided by the cargo size of the weapon. This was tested using the Blue Planet: War in Heaven mod.
Test case: UEFg Narayana turrets 19 through 21, all of which contain Apocalypse torpedos with a $Cargo Size: of 2.5, were changed to use ammo with a $SBank Capacity of 20, resulting in a total ammo capacity of 8 torpedos. The Narayana was ordered to attack a GTCv Diomedes; it fired 8 torpedos each from turrets 19 through 21 before stopping. The mission was left to proceed for an additional 2 minutes, in which time the Narayana did not fire any more torpedos. Note: The Apocalypse takes 20 seconds to fire each 4 torpedo burst.
Conclusion: Success
2015-07-28 12:32:08 -05:00

663 lines
19 KiB
C++

/*
* Copyright (C) Volition, Inc. 1999. All rights reserved.
*
* All source code herein is the property of Volition, Inc. You may not sell
* or otherwise commercially exploit the source or things you created based on the
* source.
*
*/
#include "weapon/swarm.h"
#include "weapon/weapon.h"
#include "ship/ship.h"
#include "io/timer.h"
#include "globalincs/linklist.h"
#include "object/object.h"
#define SWARM_DIST_OFFSET 2.0 // distance swarm missile should vary from original path
#define SWARM_CONE_LENGTH 10000.0f // used to pick a target point far in the distance
#define SWARM_CHANGE_DIR_TIME 400 // time to force change in direction of swarm missile
#define SWARM_ANGLE_CHANGE (4*PI/180) // in rad
// *No longer need this -Et1
//#define SWARM_MISSILE_DELAY 150 // time delay between each swarm missile that is fired
#define SWARM_TIME_VARIANCE 100 // max time variance when deciding when to change swarm missile course
#define SWARM_DIST_STOP_SWARMING 300
#define TURRET_SWARM_VALIDITY_CHECKTIME 5000 // number of ms between checks on turret_swam_info checks
#define SWARM_USED (1<<0)
#define SWARM_POSITIVE_PATH (1<<1)
swarm_info Swarm_missiles[MAX_SWARM_MISSILES];
turret_swarm_info Turret_swarm_info[MAX_TURRET_SWARM_INFO];
int Turret_swarm_validity_next_check_time;
// ------------------------------------------------------------------
// swarm_level_init()
//
// Called at the start of each new mission
//
void swarm_level_init()
{
int i;
swarm_info *swarmp;
turret_swarm_info *tswarmp;
for ( i = 0; i < MAX_SWARM_MISSILES; i++ ) {
swarmp = &Swarm_missiles[i];
swarmp->flags = 0;
swarmp->change_timestamp = 1;
swarmp->path_num = -1;
}
for (i=0; i<MAX_TURRET_SWARM_INFO; i++) {
tswarmp = &Turret_swarm_info[i];
tswarmp->flags = 0;
tswarmp->weapon_class = -1;
tswarmp->num_to_launch = 0;
tswarmp->parent_objnum = -1;
tswarmp->parent_sig = -1;
tswarmp->target_objnum = -1;
tswarmp->target_sig = -1;
tswarmp->turret = NULL;
tswarmp->target_subsys = NULL;
tswarmp->time_to_fire = 0;
}
Turret_swarm_validity_next_check_time = timestamp(TURRET_SWARM_VALIDITY_CHECKTIME);
}
// ------------------------------------------------------------------
// swarm_maybe_fire_missile()
//
// Check if there are any swarm missiles to fire, and if enough time
// has elapsed since last one fired, go ahead and fire it.
//
// This is called once per ship frame in ship_move()
//
void swarm_maybe_fire_missile(int shipnum)
{
ship *sp;
ship_weapon *swp;
int weapon_info_index;
Assert(shipnum >= 0 && shipnum < MAX_SHIPS );
sp = &Ships[shipnum];
if ( sp->num_swarm_missiles_to_fire <= 0 ) {
sp->swarm_missile_bank = -1;
return;
}
swp = &sp->weapons;
if ( sp->swarm_missile_bank == -1 ) {
sp->num_swarm_missiles_to_fire = 0;
return;
}
weapon_info_index = swp->secondary_bank_weapons[sp->swarm_missile_bank];
Assert( weapon_info_index >= 0 && weapon_info_index < MAX_WEAPON_TYPES );
// if swarm secondary bank is not a swarm missile, return
if ( !(Weapon_info[weapon_info_index].wi_flags & WIF_SWARM) ) {
sp->num_swarm_missiles_to_fire = 0;
sp->swarm_missile_bank = -1;
return;
}
if ( timestamp_elapsed(sp->next_swarm_fire) )
{
// *Get timestamp from weapon info's -Et1
sp->next_swarm_fire = timestamp(Weapon_info[weapon_info_index].SwarmWait );
ship_fire_secondary( &Objects[sp->objnum], 1 );
sp->num_swarm_missiles_to_fire--;
}
}
// ------------------------------------------------------------------
// swarm_create()
//
// Get a free swarm missile entry, and initialize the struct members
//
int swarm_create()
{
int i;
swarm_info *swarmp = NULL;
for ( i = 0; i < MAX_SWARM_MISSILES; i++ ) {
swarmp = &Swarm_missiles[i];
if ( !(swarmp->flags & SWARM_USED) )
break;
}
if ( i >= MAX_SWARM_MISSILES ) {
nprintf(("Warning","No more swarm missiles are available\n"));
return -1;
}
swarmp->flags = 0;
swarmp->change_timestamp = 1;
swarmp->path_num = -1;
swarmp->homing_objnum = -1;
swarmp->flags |= SWARM_USED;
return i;
}
// ------------------------------------------------------------------
// swarm_delete()
//
//
void swarm_delete(int i)
{
swarm_info *swarmp;
Assert(i >= 0 && i < MAX_SWARM_MISSILES);
swarmp = &Swarm_missiles[i];
if ( !(swarmp->flags & SWARM_USED) ) {
Int3(); // tried to delete a swarm missile that didn't exist, get Alan
}
swarmp->flags = 0;
}
// ------------------------------------------------------------------
// swarm_update_direction()
//
// Check if we want to update the direction of a swarm missile.
//
void swarm_update_direction(object *objp, float frametime)
{
weapon_info *wip;
weapon *wp;
object *hobjp;
swarm_info *swarmp;
vec3d obj_to_target;
float vel, target_dist, radius, missile_speed, missile_dist;
physics_info *pi;
Assert(objp->instance >= 0 && objp->instance < MAX_WEAPONS);
wp = &Weapons[objp->instance];
if (wp->swarm_index == -1) {
return;
}
wip = &Weapon_info[wp->weapon_info_index];
hobjp = wp->homing_object;
pi = &Objects[wp->objnum].phys_info;
swarmp = &Swarm_missiles[wp->swarm_index];
// check if homing is lost.. if it is then get a new path to move swarm missile along
if ( swarmp->homing_objnum != -1 && hobjp == &obj_used_list ) {
swarmp->change_timestamp = 1;
swarmp->path_num = -1;
swarmp->homing_objnum = -1;
}
if ( hobjp != &obj_used_list ) {
swarmp->homing_objnum = OBJ_INDEX(hobjp);
}
if ( timestamp_elapsed(swarmp->change_timestamp) ) {
if ( swarmp->path_num == -1 ) {
if ( Objects[objp->parent].type != OBJ_SHIP ) {
//AL: parent ship died... so just pick some random paths
swarmp->path_num = myrand()%4;
} else {
ship *parent_shipp;
parent_shipp = &Ships[Objects[objp->parent].instance];
swarmp->path_num = (parent_shipp->next_swarm_path++)%4;
if ( parent_shipp->next_swarm_path%4 == 0 ) {
swarmp->flags ^= SWARM_POSITIVE_PATH;
}
}
vm_vec_scale_add(&swarmp->original_target, &objp->pos, &objp->orient.vec.fvec, SWARM_CONE_LENGTH);
swarmp->circle_rvec = objp->orient.vec.rvec;
swarmp->circle_uvec = objp->orient.vec.uvec;
swarmp->change_count = 1;
swarmp->change_time = fl2i(SWARM_CHANGE_DIR_TIME + SWARM_TIME_VARIANCE*(frand() - 0.5f) * 2);
vm_vec_zero(&swarmp->last_offset);
missile_speed = pi->speed;
missile_dist = missile_speed * swarmp->change_time/1000.0f;
if ( missile_dist < SWARM_DIST_OFFSET ) {
missile_dist=i2fl(SWARM_DIST_OFFSET);
}
swarmp->angle_offset = (float)(asin(SWARM_DIST_OFFSET / missile_dist));
Assert(!_isnan(swarmp->angle_offset) );
}
swarmp->change_timestamp = timestamp(swarmp->change_time);
// check if swarm missile is homing, if so need to calculate a new target pos to turn towards
if ( hobjp != &obj_used_list && f2fl(Missiontime - wp->creation_time) > 0.5f && ( f2fl(Missiontime - wp->creation_time) > wip->free_flight_time ) ) {
swarmp->original_target = wp->homing_pos;
// Calculate a rvec and uvec that will determine the displacement from the
// intended target. Use crossprod to generate a right vector, from the missile
// up vector and the vector connecting missile to the homing object.
swarmp->circle_uvec = objp->orient.vec.uvec;
swarmp->circle_rvec = objp->orient.vec.rvec;
missile_speed = pi->speed;
missile_dist = missile_speed * swarmp->change_time/1000.0f;
if ( missile_dist < SWARM_DIST_OFFSET ) {
missile_dist = i2fl(SWARM_DIST_OFFSET);
}
swarmp->angle_offset = (float)(asin(SWARM_DIST_OFFSET / missile_dist));
Assert(!_isnan(swarmp->angle_offset) );
}
vm_vec_sub(&obj_to_target, &swarmp->original_target, &objp->pos);
target_dist = vm_vec_mag_quick(&obj_to_target);
swarmp->last_dist = target_dist;
// If homing swarm missile is close to target, let missile home in on original target
if ( target_dist < SWARM_DIST_STOP_SWARMING ) {
swarmp->new_target = swarmp->original_target;
goto swarm_new_target_calced;
}
radius = (float)tan(swarmp->angle_offset) * target_dist;
vec3d rvec_component, uvec_component;
swarmp->change_count++;
if ( swarmp->change_count > 2 ) {
swarmp->flags ^= SWARM_POSITIVE_PATH;
swarmp->change_count = 0;
}
// pick a new path number to follow once at center
if ( swarmp->change_count == 1 ) {
swarmp->path_num = swarmp->path_num + myrand()%3;
if ( swarmp->path_num > 3 ) {
swarmp->path_num = 0;
}
}
vm_vec_zero(&rvec_component);
vm_vec_zero(&uvec_component);
switch ( swarmp->path_num ) {
case 0: // straight up and down
if ( swarmp->flags & SWARM_POSITIVE_PATH )
vm_vec_copy_scale( &uvec_component, &swarmp->circle_uvec, radius);
else
vm_vec_copy_scale( &uvec_component, &swarmp->circle_uvec, -radius);
break;
case 1: // left/right
if ( swarmp->flags & SWARM_POSITIVE_PATH )
vm_vec_copy_scale( &rvec_component, &swarmp->circle_rvec, radius);
else
vm_vec_copy_scale( &rvec_component, &swarmp->circle_rvec, -radius);
break;
case 2: // top/right - bottom/left
if ( swarmp->flags & SWARM_POSITIVE_PATH ) {
vm_vec_copy_scale( &rvec_component, &swarmp->circle_rvec, radius);
vm_vec_copy_scale( &uvec_component, &swarmp->circle_uvec, radius);
}
else {
vm_vec_copy_scale( &rvec_component, &swarmp->circle_rvec, -radius);
vm_vec_copy_scale( &uvec_component, &swarmp->circle_uvec, -radius);
}
break;
case 3: // top-left - bottom/right
if ( swarmp->flags & SWARM_POSITIVE_PATH ) {
vm_vec_copy_scale( &rvec_component, &swarmp->circle_rvec, -radius);
vm_vec_copy_scale( &uvec_component, &swarmp->circle_uvec, radius);
}
else {
vm_vec_copy_scale( &rvec_component, &swarmp->circle_rvec, radius);
vm_vec_copy_scale( &uvec_component, &swarmp->circle_uvec, -radius);
}
break;
default:
Int3();
break;
}
swarmp->new_target = swarmp->original_target;
vm_vec_zero(&swarmp->last_offset);
vm_vec_add(&swarmp->last_offset, &uvec_component, &rvec_component);
vm_vec_add2(&swarmp->new_target, &swarmp->last_offset);
}
else {
if ( hobjp != &obj_used_list && f2fl(Missiontime - wp->creation_time) > 0.5f ) {
swarmp->new_target = swarmp->original_target;
if ( swarmp->last_dist < SWARM_DIST_STOP_SWARMING ) {
swarmp->new_target = wp->homing_pos;
goto swarm_new_target_calced;
}
vm_vec_add2(&swarmp->new_target, &swarmp->last_offset);
}
}
swarm_new_target_calced:
ai_turn_towards_vector(&swarmp->new_target, objp, frametime, wip->turn_time, NULL, NULL, 0.0f, 0);
vel = vm_vec_mag(&objp->phys_info.desired_vel);
vm_vec_copy_scale(&objp->phys_info.desired_vel, &objp->orient.vec.fvec, vel);
}
// ------------------------------------------------------------------
// turret_swarm_create()
//
// Get a free swarm missile entry, and initialize the struct members
//
int turret_swarm_create()
{
int i;
turret_swarm_info *tswarmp = NULL;
for (i=0; i<MAX_TURRET_SWARM_INFO; i++) {
tswarmp = &Turret_swarm_info[i];
if ( !(tswarmp->flags & SWARM_USED) )
break;
}
if ( i >= MAX_TURRET_SWARM_INFO ) {
nprintf(("Warning","No more turret swarm info slots are available\n"));
return -1;
}
tswarmp->flags = 0;
tswarmp->weapon_class = -1;
tswarmp->num_to_launch = 0;
tswarmp->parent_objnum = -1;
tswarmp->parent_sig = -1;
tswarmp->target_objnum = -1;
tswarmp->target_sig = -1;
tswarmp->turret = NULL;
tswarmp->target_subsys = NULL;
tswarmp->time_to_fire = 0;
tswarmp->flags |= SWARM_USED;
return i;
}
// ------------------------------------------------------------------
// turret_swarm_delete()
//
void turret_swarm_delete(int i)
{
turret_swarm_info *tswarmp;
Assert(i >= 0 && i < MAX_TURRET_SWARM_INFO);
tswarmp = &Turret_swarm_info[i];
if ( !(tswarmp->flags & SWARM_USED) ) {
Int3(); // tried to delete a swarm missile that didn't exist, get DaveA
}
tswarmp->flags = 0;
}
// Set up turret swarm info struct
void turret_swarm_set_up_info(int parent_objnum, ship_subsys *turret, weapon_info *wip, int weapon_num)
{
turret_swarm_info *tsi;
object *parent_obj, *target_obj;
ship *shipp;
int tsi_index;
// weapon info pointer
//Removed check in the interests of speed -WMC
/*
Assert((turret_weapon_class >= 0) && (turret_weapon_class < Num_weapon_types));
if((turret_weapon_class < 0) || (turret_weapon_class >= Num_weapon_types)){
return;
}
*/
// get ship pointer
Assert((parent_objnum >= 0) && (parent_objnum < MAX_OBJECTS));
if((parent_objnum < 0) || (parent_objnum >= MAX_OBJECTS)){
return;
}
parent_obj = &Objects[parent_objnum];
Assert(parent_obj->type == OBJ_SHIP);
shipp = &Ships[parent_obj->instance];
Assert((turret->turret_enemy_objnum >= 0) && (turret->turret_enemy_objnum < MAX_OBJECTS));
if((turret->turret_enemy_objnum < 0) || (turret->turret_enemy_objnum >= MAX_OBJECTS)){
return;
}
target_obj = &Objects[turret->turret_enemy_objnum];
// valid swarm weapon
Assert(((wip->wi_flags & WIF_SWARM) && (wip->swarm_count > 0)) || ((wip->wi_flags & WIF_CORKSCREW) && (wip->cs_num_fired > 0)));
if(!((wip->wi_flags & WIF_SWARM) || (wip->wi_flags & WIF_CORKSCREW)) || ((wip->wi_flags & WIF_SWARM) && (wip->swarm_count <= 0)) || ((wip->wi_flags & WIF_CORKSCREW) && (wip->cs_num_fired <= 0)))
return;
// get turret_swarm_info
tsi_index = turret_swarm_create();
if (tsi_index == -1) {
return;
}
// set turret to point to tsi
tsi = &Turret_swarm_info[tsi_index];
if (turret->turret_swarm_num == MAX_TFP)
{
mprintf(("Overlapping turret swarm firing intervals\n"));
turret_swarm_delete(turret->turret_swarm_info_index[0]);
int old_s;
for (old_s = 0; old_s < (MAX_TFP - 1); old_s++)
{
turret->turret_swarm_info_index[old_s] = turret->turret_swarm_info_index[old_s + 1];
}
turret->turret_swarm_info_index[MAX_TFP - 1] = -1;
turret->turret_swarm_num--;
shipp->num_turret_swarm_info--;
}
turret->turret_swarm_info_index[turret->turret_swarm_num] = tsi_index;
turret->turret_swarm_num++;
// increment ship tsi counter
shipp->num_turret_swarm_info++;
// *Unnecessary check, now done on startup -Et1
/*
// make sure time is sufficient to launch all the missiles before next volley
#ifndef NDEBUG
Assert(wip->swarm_count * SWARM_MISSILE_DELAY < wip->fire_wait * 1000.0f);
#endif
*/
ship_weapon *swp = &turret->weapons;
int bank_fired = swp->current_secondary_bank;
// initialize tsi
tsi->weapon_class = WEAPON_INFO_INDEX(wip);
if (wip->wi_flags & WIF_SWARM) {
tsi->num_to_launch = wip->swarm_count;
if (turret->system_info->flags2 & MSS_FLAG2_TURRET_USE_AMMO) {
swp->secondary_bank_ammo[bank_fired] -= wip->swarm_count;
}
} else {
tsi->num_to_launch = wip->cs_num_fired;
if (turret->system_info->flags2 & MSS_FLAG2_TURRET_USE_AMMO) {
swp->secondary_bank_ammo[bank_fired] -= wip->cs_num_fired;
}
}
tsi->parent_objnum = parent_objnum;
tsi->parent_sig = parent_obj->signature;
tsi->target_objnum = turret->turret_enemy_objnum;
tsi->target_sig = target_obj->signature;
tsi->turret = turret;
tsi->target_subsys = turret->targeted_subsys;
tsi->time_to_fire = 1; // first missile next frame
tsi->weapon_num = weapon_num;
}
void turret_swarm_fire_from_turret(turret_swarm_info *tsi);
// check if ship has turret ready to fire swarm type missiles
void turret_swarm_maybe_fire_missile(int shipnum)
{
ship *shipp = &Ships[shipnum];
ship_subsys *subsys;
turret_swarm_info *tsi;
object *parent_obj, *target_obj;
int num_turret_swarm_turrets_left;
int k, j;
weapon_info *wip;
// check if ship has any turrets ready to fire
if (shipp->num_turret_swarm_info <= 0) {
Assert(shipp->num_turret_swarm_info == 0);
return;
}
// ship obj which has fired turret swarm missiles
parent_obj = &Objects[shipp->objnum];
num_turret_swarm_turrets_left = shipp->num_turret_swarm_info;
// search ship subsystems for turrets with valid turret_swarm_info_index
for (subsys = GET_FIRST(&shipp->subsys_list); subsys != END_OF_LIST(&shipp->subsys_list); subsys = GET_NEXT(subsys)) {
if (subsys->turret_swarm_num > 0) {
int swarms_per_turret = subsys->turret_swarm_num;
for (k = 0; k < swarms_per_turret; k++)
{
int turret_tsi = subsys->turret_swarm_info_index[k];
num_turret_swarm_turrets_left--;
Assert(num_turret_swarm_turrets_left >= 0);
// get turret_swarm_info
Assert( (turret_tsi >= 0) && (turret_tsi < MAX_TURRET_SWARM_INFO) );
tsi = &Turret_swarm_info[turret_tsi];
wip = &Weapon_info[tsi->weapon_class];
// check if parent ship is valid (via signature)
if ( tsi->parent_sig == parent_obj->signature ) {
// make sure we have the right turret.
Assert(tsi->turret == subsys);
// check if time to fire
if (timestamp_elapsed(tsi->time_to_fire)) {
Assert(tsi->num_to_launch > 0);
// check target still alive
if (tsi->target_objnum > -1) {
target_obj= &Objects[tsi->target_objnum];
if (target_obj->signature != tsi->target_sig) {
// poor target, it died
tsi->target_objnum = -1;
}
}
// make sure turret is still alive and fire swarmer
if (subsys->current_hits > 0) {
turret_swarm_fire_from_turret(tsi);
}
// *Get timestamp from weapon info's -Et1
if (wip->wi_flags & WIF_SWARM) {
tsi->time_to_fire = timestamp( wip->SwarmWait );
} else {
tsi->time_to_fire = timestamp( wip->cs_delay );
}
// do book keeping
tsi->num_to_launch--;
if (tsi->num_to_launch == 0) {
// we are done firing, so see about resetting any animation timestamps for reversal (closing)...
// (I figure that a good estimate is to trigger a close after three additional swarms had fired - taylor)
if (subsys->turret_animation_position == MA_POS_READY)
subsys->turret_animation_done_time = timestamp( Weapon_info[tsi->weapon_class].SwarmWait * 3);
shipp->num_turret_swarm_info--;
subsys->turret_swarm_num--;
turret_swarm_delete(subsys->turret_swarm_info_index[k]);
subsys->turret_swarm_info_index[k] = -1;
}
}
} else {
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);
shipp->num_turret_swarm_info--;
subsys->turret_swarm_num--;
turret_swarm_delete(subsys->turret_swarm_info_index[k]);
subsys->turret_swarm_info_index[k] = -1;
}
}
//swarm reset stuff
for (k = 0; k < (MAX_TFP - 1); k++)
{
for (j = (k + 1); j < MAX_TFP; j++)
{
if ((subsys->turret_swarm_info_index[k] == -1) && (subsys->turret_swarm_info_index[j] != -1))
{
subsys->turret_swarm_info_index[k] = subsys->turret_swarm_info_index[j];
subsys->turret_swarm_info_index[j] = -1;
}
}
}
}
}
Assert(num_turret_swarm_turrets_left == 0);
}
// check Turret_swarm_info for info that are invalid - ie, ships died while firing.
void turret_swarm_check_validity()
{
int i;
turret_swarm_info *tswarmp;
object *ship_objp;
if (timestamp_elapsed(Turret_swarm_validity_next_check_time)) {
// reset timestamp
Turret_swarm_validity_next_check_time = timestamp(TURRET_SWARM_VALIDITY_CHECKTIME);
// go through all Turret_swarm_info, check obj and obj->signature
for (i=0; i<MAX_TURRET_SWARM_INFO; i++) {
tswarmp = &Turret_swarm_info[i];
if (tswarmp->flags & SWARM_USED) {
ship_objp = &Objects[tswarmp->parent_objnum];
if (ship_objp->type == OBJ_SHIP) {
if (ship_objp->signature == tswarmp->parent_sig) {
continue;
}
}
turret_swarm_delete(i);
}
}
}
}