mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-09 04:49:39 -10:00
7253 lines
221 KiB
C++
7253 lines
221 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/weapon.h"
|
|
#include "render/3d.h"
|
|
#include "object/object.h"
|
|
#include "ship/ship.h"
|
|
#include "fireball/fireballs.h"
|
|
#include "playerman/player.h"
|
|
#include "freespace2/freespace.h"
|
|
#include "radar/radar.h"
|
|
#include "globalincs/linklist.h"
|
|
#include "io/timer.h"
|
|
#include "gamesnd/gamesnd.h"
|
|
#include "cmeasure/cmeasure.h"
|
|
#include "math/staticrand.h"
|
|
#include "weapon/swarm.h"
|
|
#include "ship/shiphit.h"
|
|
#include "hud/hud.h"
|
|
#include "object/objcollide.h"
|
|
#include "ai/aibig.h"
|
|
#include "particle/particle.h"
|
|
#include "asteroid/asteroid.h"
|
|
#include "io/joy_ff.h"
|
|
#include "weapon/corkscrew.h"
|
|
#include "weapon/emp.h"
|
|
#include "localization/localize.h"
|
|
#include "weapon/flak.h"
|
|
#include "weapon/muzzleflash.h"
|
|
#include "cmdline/cmdline.h"
|
|
#include "graphics/grbatch.h"
|
|
#include "parse/parselo.h"
|
|
#include "radar/radarsetup.h"
|
|
#include "weapon/beam.h" // for BEAM_TYPE_? definitions
|
|
#include "iff_defs/iff_defs.h"
|
|
#include "network/multi.h"
|
|
#include "network/multimsgs.h"
|
|
#include "network/multiutil.h"
|
|
#include "parse/scripting.h"
|
|
#include "stats/scoring.h"
|
|
#include "mod_table/mod_table.h"
|
|
#include "model/modelrender.h"
|
|
#include "debugconsole/console.h"
|
|
|
|
#ifndef NDEBUG
|
|
int Weapon_flyby_sound_enabled = 1;
|
|
DCF_BOOL( weapon_flyby, Weapon_flyby_sound_enabled )
|
|
#endif
|
|
|
|
static int Weapon_flyby_sound_timer;
|
|
|
|
weapon Weapons[MAX_WEAPONS];
|
|
weapon_info Weapon_info[MAX_WEAPON_TYPES];
|
|
|
|
#define MISSILE_OBJ_USED (1<<0) // flag used in missile_obj struct
|
|
#define MAX_MISSILE_OBJS MAX_WEAPONS // max number of missiles tracked in missile list
|
|
missile_obj Missile_objs[MAX_MISSILE_OBJS]; // array used to store missile object indexes
|
|
missile_obj Missile_obj_list; // head of linked list of missile_obj structs
|
|
|
|
//WEAPON SUBTYPE STUFF
|
|
char *Weapon_subtype_names[] = {
|
|
"Laser",
|
|
"Missile",
|
|
"Beam"
|
|
};
|
|
int Num_weapon_subtypes = sizeof(Weapon_subtype_names)/sizeof(char *);
|
|
|
|
flag_def_list Burst_fire_flags[] = {
|
|
{ "fast firing", WBF_FAST_FIRING, 0 },
|
|
{ "random length", WBF_RANDOM_LENGTH, 0 }
|
|
};
|
|
|
|
int Num_burst_fire_flags = sizeof(Burst_fire_flags)/sizeof(flag_def_list);
|
|
|
|
weapon_explosions Weapon_explosions;
|
|
|
|
SCP_vector<lod_checker> LOD_checker;
|
|
|
|
int Num_weapon_types = 0;
|
|
|
|
int Num_weapons = 0;
|
|
int Weapons_inited = 0;
|
|
int Weapon_expl_initted = 0;
|
|
|
|
int laser_model_inner = -1;
|
|
int laser_model_outer = -1;
|
|
|
|
int missile_model = -1;
|
|
|
|
int First_secondary_index = -1;
|
|
int Default_cmeasure_index = -1;
|
|
|
|
static int *used_weapons = NULL;
|
|
|
|
int Num_spawn_types = 0;
|
|
char **Spawn_names = NULL;
|
|
|
|
int Num_player_weapon_precedence; // Number of weapon types in Player_weapon_precedence
|
|
int Player_weapon_precedence[MAX_WEAPON_TYPES]; // Array of weapon types, precedence list for player weapon selection
|
|
|
|
// Used to avoid playing too many impact sounds in too short a time interval.
|
|
// This will elimate the odd "stereo" effect that occurs when two weapons impact at
|
|
// nearly the same time, like from a double laser (also saves sound channels!)
|
|
#define IMPACT_SOUND_DELTA 50 // in milliseconds
|
|
int Weapon_impact_timer; // timer, initialized at start of each mission
|
|
|
|
// energy suck defines
|
|
#define ESUCK_DEFAULT_WEAPON_REDUCE (10.0f)
|
|
#define ESUCK_DEFAULT_AFTERBURNER_REDUCE (10.0f)
|
|
|
|
// scale factor for supercaps taking damage from weapons which are not "supercap" weapons
|
|
#define SUPERCAP_DAMAGE_SCALE 0.25f
|
|
|
|
// scale factor for big ships getting hit by flak
|
|
#define FLAK_DAMAGE_SCALE 0.05f
|
|
|
|
//default time of a homing weapon to not home
|
|
#define HOMING_DEFAULT_FREE_FLIGHT_TIME 0.5f
|
|
|
|
// time delay between each swarm missile that is fired
|
|
#define SWARM_MISSILE_DELAY 150
|
|
|
|
// 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
|
|
// range. Check the comment in weapon_set_tracking_info() for more details
|
|
#define LOCKED_HOMING_EXTENDED_LIFE_FACTOR 1.2f
|
|
|
|
extern int compute_num_homing_objects(object *target_objp);
|
|
|
|
extern void fs2netd_add_table_validation(const char *tblname);
|
|
|
|
|
|
weapon_explosions::weapon_explosions()
|
|
{
|
|
ExplosionInfo.clear();
|
|
}
|
|
|
|
int weapon_explosions::GetIndex(char *filename)
|
|
{
|
|
if ( filename == NULL ) {
|
|
Int3();
|
|
return -1;
|
|
}
|
|
|
|
for (size_t i = 0; i < ExplosionInfo.size(); i++) {
|
|
if ( !stricmp(ExplosionInfo[i].lod[0].filename, filename)) {
|
|
return (int)i;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
int weapon_explosions::Load(char *filename, int expected_lods)
|
|
{
|
|
char name_tmp[MAX_FILENAME_LEN] = "";
|
|
int bitmap_id = -1;
|
|
int nframes, nfps;
|
|
weapon_expl_info new_wei;
|
|
|
|
Assert( expected_lods <= MAX_WEAPON_EXPL_LOD );
|
|
|
|
//Check if it exists
|
|
int idx = GetIndex(filename);
|
|
|
|
if (idx != -1)
|
|
return idx;
|
|
|
|
new_wei.lod_count = 1;
|
|
|
|
strcpy_s(new_wei.lod[0].filename, filename);
|
|
new_wei.lod[0].bitmap_id = bm_load_animation(filename, &new_wei.lod[0].num_frames, &new_wei.lod[0].fps, NULL, 1);
|
|
|
|
if (new_wei.lod[0].bitmap_id < 0) {
|
|
Warning(LOCATION, "Weapon explosion '%s' does not have an LOD0 anim!", filename);
|
|
|
|
// if we don't have the first then it's only safe to assume that the rest are missing or not usable
|
|
return -1;
|
|
}
|
|
|
|
// 2 chars for the lod, 4 for the extension that gets added automatically
|
|
if ( (MAX_FILENAME_LEN - strlen(filename)) > 6 ) {
|
|
for (idx = 1; idx < expected_lods; idx++) {
|
|
sprintf(name_tmp, "%s_%d", filename, idx);
|
|
|
|
bitmap_id = bm_load_animation(name_tmp, &nframes, &nfps, NULL, 1);
|
|
|
|
if (bitmap_id > 0) {
|
|
strcpy_s(new_wei.lod[idx].filename, name_tmp);
|
|
new_wei.lod[idx].bitmap_id = bitmap_id;
|
|
new_wei.lod[idx].num_frames = nframes;
|
|
new_wei.lod[idx].fps = nfps;
|
|
|
|
new_wei.lod_count++;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (new_wei.lod_count != expected_lods)
|
|
Warning(LOCATION, "For '%s', %i of %i LODs are missing!", filename, expected_lods - new_wei.lod_count, expected_lods);
|
|
}
|
|
else {
|
|
Warning(LOCATION, "Filename '%s' is too long to have any LODs.", filename);
|
|
}
|
|
|
|
ExplosionInfo.push_back( new_wei );
|
|
|
|
return (int)(ExplosionInfo.size() - 1);
|
|
}
|
|
|
|
void weapon_explosions::PageIn(int idx)
|
|
{
|
|
int i;
|
|
|
|
if ( (idx < 0) || (idx >= (int)ExplosionInfo.size()) )
|
|
return;
|
|
|
|
weapon_expl_info *wei = &ExplosionInfo[idx];
|
|
|
|
for ( i = 0; i < wei->lod_count; i++ ) {
|
|
if ( wei->lod[i].bitmap_id >= 0 ) {
|
|
bm_page_in_xparent_texture( wei->lod[i].bitmap_id, wei->lod[i].num_frames );
|
|
}
|
|
}
|
|
}
|
|
|
|
int weapon_explosions::GetAnim(int weapon_expl_index, vec3d *pos, float size)
|
|
{
|
|
if ( (weapon_expl_index < 0) || (weapon_expl_index >= (int)ExplosionInfo.size()) )
|
|
return -1;
|
|
|
|
//Get our weapon expl for the day
|
|
weapon_expl_info *wei = &ExplosionInfo[weapon_expl_index];
|
|
|
|
if (wei->lod_count == 1)
|
|
return wei->lod[0].bitmap_id;
|
|
|
|
// now we have to do some work
|
|
vertex v;
|
|
int x, y, w, h, bm_size;
|
|
int must_stop = 0;
|
|
int best_lod = 1;
|
|
int behind = 0;
|
|
|
|
// start the frame
|
|
extern int G3_count;
|
|
|
|
if(!G3_count){
|
|
g3_start_frame(1);
|
|
must_stop = 1;
|
|
}
|
|
g3_set_view_matrix(&Eye_position, &Eye_matrix, Eye_fov);
|
|
|
|
// get extents of the rotated bitmap
|
|
g3_rotate_vertex(&v, pos);
|
|
|
|
// if vertex is behind, find size if in front, then drop down 1 LOD
|
|
if (v.codes & CC_BEHIND) {
|
|
float dist = vm_vec_dist_quick(&Eye_position, pos);
|
|
vec3d temp;
|
|
|
|
behind = 1;
|
|
vm_vec_scale_add(&temp, &Eye_position, &Eye_matrix.vec.fvec, dist);
|
|
g3_rotate_vertex(&v, &temp);
|
|
|
|
// if still behind, bail and go with default
|
|
if (v.codes & CC_BEHIND) {
|
|
behind = 0;
|
|
}
|
|
}
|
|
|
|
if (!g3_get_bitmap_dims(wei->lod[0].bitmap_id, &v, size, &x, &y, &w, &h, &bm_size)) {
|
|
if (Detail.hardware_textures == 4) {
|
|
// straight LOD
|
|
if(w <= bm_size/8){
|
|
best_lod = 3;
|
|
} else if(w <= bm_size/2){
|
|
best_lod = 2;
|
|
} else if(w <= 1.3f*bm_size){
|
|
best_lod = 1;
|
|
} else {
|
|
best_lod = 0;
|
|
}
|
|
} else {
|
|
// less aggressive LOD for lower detail settings
|
|
if(w <= bm_size/8){
|
|
best_lod = 3;
|
|
} else if(w <= bm_size/3){
|
|
best_lod = 2;
|
|
} else if(w <= (1.15f*bm_size)){
|
|
best_lod = 1;
|
|
} else {
|
|
best_lod = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// if it's behind, bump up LOD by 1
|
|
if (behind)
|
|
best_lod++;
|
|
|
|
// end the frame
|
|
if (must_stop)
|
|
g3_end_frame();
|
|
|
|
best_lod = MIN(best_lod, wei->lod_count - 1);
|
|
Assert( (best_lod >= 0) && (best_lod < MAX_WEAPON_EXPL_LOD) );
|
|
|
|
return wei->lod[best_lod].bitmap_id;
|
|
}
|
|
|
|
|
|
void parse_weapon_expl_tbl(const char *filename)
|
|
{
|
|
uint i;
|
|
lod_checker lod_check;
|
|
|
|
try
|
|
{
|
|
read_file_text(filename, CF_TYPE_TABLES);
|
|
reset_parse();
|
|
|
|
required_string("#Start");
|
|
while (required_string_either("#End", "$Name:"))
|
|
{
|
|
memset(&lod_check, 0, sizeof(lod_checker));
|
|
|
|
// base filename
|
|
required_string("$Name:");
|
|
stuff_string(lod_check.filename, F_NAME, MAX_FILENAME_LEN);
|
|
|
|
//Do we have an LOD num
|
|
if (optional_string("$LOD:"))
|
|
{
|
|
stuff_int(&lod_check.num_lods);
|
|
}
|
|
|
|
// only bother with this if we have 1 or more lods and less than max lods,
|
|
// otherwise the stardard level loading will take care of the different effects
|
|
if ((lod_check.num_lods > 0) || (lod_check.num_lods < MAX_WEAPON_EXPL_LOD)) {
|
|
// name check, update lod count if it already exists
|
|
for (i = 0; i < LOD_checker.size(); i++) {
|
|
if (!stricmp(LOD_checker[i].filename, lod_check.filename)) {
|
|
LOD_checker[i].num_lods = lod_check.num_lods;
|
|
}
|
|
}
|
|
|
|
// old entry not found, add new entry
|
|
if (i == LOD_checker.size()) {
|
|
LOD_checker.push_back(lod_check);
|
|
}
|
|
}
|
|
}
|
|
required_string("#End");
|
|
}
|
|
catch (const parse::ParseException& e)
|
|
{
|
|
mprintf(("TABLES: Unable to parse '%s'! Error message = %s.\n", filename, e.what()));
|
|
return;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Clear out the Missile_obj_list
|
|
*/
|
|
void missile_obj_list_init()
|
|
{
|
|
int i;
|
|
|
|
list_init(&Missile_obj_list);
|
|
for ( i = 0; i < MAX_MISSILE_OBJS; i++ ) {
|
|
Missile_objs[i].flags = 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add a node from the Missile_obj_list.
|
|
* @note Only called from weapon_create()
|
|
*/
|
|
int missile_obj_list_add(int objnum)
|
|
{
|
|
int i;
|
|
|
|
for ( i = 0; i < MAX_MISSILE_OBJS; i++ ) {
|
|
if ( !(Missile_objs[i].flags & MISSILE_OBJ_USED) )
|
|
break;
|
|
}
|
|
if ( i == MAX_MISSILE_OBJS ) {
|
|
Error(LOCATION, "Fatal Error: Ran out of missile object nodes\n");
|
|
return -1;
|
|
}
|
|
|
|
Missile_objs[i].flags = 0;
|
|
Missile_objs[i].objnum = objnum;
|
|
list_append(&Missile_obj_list, &Missile_objs[i]);
|
|
Missile_objs[i].flags |= MISSILE_OBJ_USED;
|
|
|
|
return i;
|
|
}
|
|
|
|
/**
|
|
* Remove a node from the Missile_obj_list.
|
|
* @note Only called from weapon_delete()
|
|
*/
|
|
void missle_obj_list_remove(int index)
|
|
{
|
|
Assert(index >= 0 && index < MAX_MISSILE_OBJS);
|
|
list_remove(&Missile_obj_list, &Missile_objs[index]);
|
|
Missile_objs[index].flags = 0;
|
|
}
|
|
|
|
/**
|
|
* Called by the save/restore code to rebuild Missile_obj_list
|
|
*/
|
|
void missile_obj_list_rebuild()
|
|
{
|
|
object *objp;
|
|
|
|
missile_obj_list_init();
|
|
|
|
for ( objp = GET_FIRST(&obj_used_list); objp !=END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) ) {
|
|
if ( objp->type == OBJ_WEAPON && Weapon_info[Weapons[objp->instance].weapon_info_index].subtype == WP_MISSILE ) {
|
|
Weapons[objp->instance].missile_list_index = missile_obj_list_add(OBJ_INDEX(objp));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called externally to generate an address from an index into
|
|
* the Missile_objs[] array
|
|
*/
|
|
missile_obj *missile_obj_return_address(int index)
|
|
{
|
|
Assert(index >= 0 && index < MAX_MISSILE_OBJS);
|
|
return &Missile_objs[index];
|
|
}
|
|
|
|
/**
|
|
* Return the index of Weapon_info[].name that is *name.
|
|
*/
|
|
int weapon_info_lookup(const char *name)
|
|
{
|
|
// bogus
|
|
if (name == NULL)
|
|
return -1;
|
|
|
|
for (int i=0; i<Num_weapon_types; i++)
|
|
if (!stricmp(name, Weapon_info[i].name))
|
|
return i;
|
|
|
|
return -1;
|
|
}
|
|
|
|
#define DEFAULT_WEAPON_SPAWN_COUNT 10
|
|
|
|
// Parse the weapon flags.
|
|
void parse_wi_flags(weapon_info *weaponp, int wi_flags, int wi_flags2, int wi_flags3)
|
|
{
|
|
const char *spawn_str = NOX("Spawn");
|
|
const size_t spawn_str_len = strlen(spawn_str);
|
|
|
|
//Make sure we HAVE flags :p
|
|
if(!optional_string("$Flags:"))
|
|
return;
|
|
|
|
char weapon_strings[MAX_WEAPON_FLAGS][NAME_LENGTH];
|
|
int num_strings;
|
|
|
|
num_strings = stuff_string_list(weapon_strings, MAX_WEAPON_FLAGS);
|
|
|
|
if (optional_string("+override")) {
|
|
// reseting the flag values if set to override the existing flags
|
|
weaponp->wi_flags = wi_flags;
|
|
weaponp->wi_flags2 = wi_flags2;
|
|
weaponp->wi_flags3 = wi_flags3;
|
|
}
|
|
|
|
bool set_pierce = false;
|
|
bool set_nopierce = false;
|
|
|
|
for (int i=0; i<num_strings; i++) {
|
|
if (!strnicmp(spawn_str, weapon_strings[i], 5))
|
|
{
|
|
if (weaponp->num_spawn_weapons_defined < MAX_SPAWN_TYPES_PER_WEAPON)
|
|
{
|
|
//We need more spawning slots
|
|
//allocate in slots of 10
|
|
if((Num_spawn_types % 10) == 0) {
|
|
Spawn_names = (char **)vm_realloc(Spawn_names, (Num_spawn_types + 10) * sizeof(*Spawn_names));
|
|
}
|
|
|
|
int skip_length, name_length;
|
|
char *temp_string;
|
|
|
|
temp_string = weapon_strings[i];
|
|
|
|
weaponp->wi_flags |= WIF_SPAWN;
|
|
weaponp->spawn_info[weaponp->num_spawn_weapons_defined].spawn_type = (short)Num_spawn_types;
|
|
skip_length = spawn_str_len + strspn(&temp_string[spawn_str_len], NOX(" \t"));
|
|
char *num_start = strchr(&temp_string[skip_length], ',');
|
|
if (num_start == NULL) {
|
|
weaponp->spawn_info[weaponp->num_spawn_weapons_defined].spawn_count = DEFAULT_WEAPON_SPAWN_COUNT;
|
|
name_length = 999;
|
|
} else {
|
|
weaponp->spawn_info[weaponp->num_spawn_weapons_defined].spawn_count = (short)atoi(num_start+1);
|
|
name_length = num_start - temp_string - skip_length;
|
|
}
|
|
|
|
weaponp->total_children_spawned += weaponp->spawn_info[weaponp->num_spawn_weapons_defined].spawn_count;
|
|
|
|
Spawn_names[Num_spawn_types] = vm_strndup( &weapon_strings[i][skip_length], name_length );
|
|
Num_spawn_types++;
|
|
weaponp->num_spawn_weapons_defined++;
|
|
} else {
|
|
Warning(LOCATION, "Illegal to have more than %d spawn types for one weapon.\nIgnoring weapon %s", MAX_SPAWN_TYPES_PER_WEAPON, weaponp->name);
|
|
}
|
|
} else if (!stricmp(NOX("Remote Detonate"), weapon_strings[i]))
|
|
weaponp->wi_flags |= WIF_REMOTE;
|
|
else if (!stricmp(NOX("Puncture"), weapon_strings[i]))
|
|
weaponp->wi_flags |= WIF_PUNCTURE;
|
|
else if (!stricmp(NOX("Big Ship"), weapon_strings[i]))
|
|
weaponp->wi_flags |= WIF_BIG_ONLY;
|
|
else if (!stricmp(NOX("Huge"), weapon_strings[i]))
|
|
weaponp->wi_flags |= WIF_HUGE;
|
|
else if (!stricmp(NOX("Bomber+"), weapon_strings[i]))
|
|
weaponp->wi_flags |= WIF_BOMBER_PLUS;
|
|
else if (!stricmp(NOX("child"), weapon_strings[i]))
|
|
weaponp->wi_flags |= WIF_CHILD;
|
|
else if (!stricmp(NOX("Bomb"), weapon_strings[i]))
|
|
weaponp->wi_flags |= WIF_BOMB;
|
|
else if (!stricmp(NOX("No Dumbfire"), weapon_strings[i]))
|
|
weaponp->wi_flags |= WIF_NO_DUMBFIRE;
|
|
else if (!stricmp(NOX("In tech database"), weapon_strings[i]))
|
|
weaponp->wi_flags |= WIF_IN_TECH_DATABASE;
|
|
else if (!stricmp(NOX("Player allowed"), weapon_strings[i]))
|
|
weaponp->wi_flags |= WIF_PLAYER_ALLOWED;
|
|
else if (!stricmp(NOX("Particle Spew"), weapon_strings[i]))
|
|
weaponp->wi_flags |= WIF_PARTICLE_SPEW;
|
|
else if (!stricmp(NOX("EMP"), weapon_strings[i]))
|
|
weaponp->wi_flags |= WIF_EMP;
|
|
else if (!stricmp(NOX("Esuck"), weapon_strings[i]))
|
|
weaponp->wi_flags |= WIF_ENERGY_SUCK;
|
|
else if (!stricmp(NOX("Flak"), weapon_strings[i]))
|
|
weaponp->wi_flags |= WIF_FLAK;
|
|
else if (!stricmp(NOX("Corkscrew"), weapon_strings[i]))
|
|
weaponp->wi_flags |= WIF_CORKSCREW;
|
|
else if (!stricmp(NOX("Shudder"), weapon_strings[i]))
|
|
weaponp->wi_flags |= WIF_SHUDDER;
|
|
else if (!stricmp(NOX("Electronics"), weapon_strings[i]))
|
|
weaponp->wi_flags |= WIF_ELECTRONICS;
|
|
else if (!stricmp(NOX("lockarm"), weapon_strings[i]))
|
|
weaponp->wi_flags |= WIF_LOCKARM;
|
|
else if (!stricmp(NOX("beam"), weapon_strings[i]))
|
|
{
|
|
weaponp->wi_flags |= WIF_BEAM;
|
|
|
|
// IMPORTANT: beams pierce shields by default :rolleyes: :p - Goober5000
|
|
weaponp->wi_flags2 |= WIF2_PIERCE_SHIELDS;
|
|
}
|
|
else if (!stricmp(NOX("stream"), weapon_strings[i]))
|
|
weaponp->wi_flags |= WIF_STREAM;
|
|
else if (!stricmp(NOX("supercap"), weapon_strings[i]))
|
|
weaponp->wi_flags |= WIF_SUPERCAP;
|
|
else if (!stricmp(NOX("countermeasure"), weapon_strings[i]))
|
|
weaponp->wi_flags |= WIF_CMEASURE;
|
|
else if (!stricmp(NOX("ballistic"), weapon_strings[i]))
|
|
weaponp->wi_flags2 |= WIF2_BALLISTIC;
|
|
else if (!stricmp(NOX("pierce shields"), weapon_strings[i]))
|
|
set_pierce = true;
|
|
else if (!stricmp(NOX("no pierce shields"), weapon_strings[i])) // only for beams
|
|
set_nopierce = true;
|
|
else if (!stricmp(NOX("local ssm"), weapon_strings[i]))
|
|
weaponp->wi_flags2 |= WIF2_LOCAL_SSM;
|
|
else if (!stricmp(NOX("tagged only"), weapon_strings[i]))
|
|
weaponp->wi_flags2 |= WIF2_TAGGED_ONLY;
|
|
else if (!stricmp(NOX("beam no whack"), weapon_strings[i]))
|
|
{
|
|
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");
|
|
weaponp->mass = 0.0f;
|
|
}
|
|
else if (!stricmp(NOX("cycle"), weapon_strings[i]))
|
|
weaponp->wi_flags2 |= WIF2_CYCLE;
|
|
else if (!stricmp(NOX("small only"), weapon_strings[i]))
|
|
weaponp->wi_flags2 |= WIF2_SMALL_ONLY;
|
|
else if (!stricmp(NOX("same turret cooldown"), weapon_strings[i]))
|
|
weaponp->wi_flags2 |= WIF2_SAME_TURRET_COOLDOWN;
|
|
else if (!stricmp(NOX("apply no light"), weapon_strings[i]))
|
|
weaponp->wi_flags2 |= WIF2_MR_NO_LIGHTING;
|
|
else if (!stricmp(NOX("training"), weapon_strings[i]))
|
|
weaponp->wi_flags2 |= WIF2_TRAINING;
|
|
else if (!stricmp(NOX("smart spawn"), weapon_strings[i]))
|
|
weaponp->wi_flags2 |= WIF2_SMART_SPAWN;
|
|
else if (!stricmp(NOX("inherit parent target"), weapon_strings[i]))
|
|
weaponp->wi_flags2 |= WIF2_INHERIT_PARENT_TARGET;
|
|
else if (!stricmp(NOX("no emp kill"), weapon_strings[i]))
|
|
weaponp->wi_flags2 |= WIF2_NO_EMP_KILL;
|
|
else if (!stricmp(NOX("untargeted heat seeker"), weapon_strings[i]))
|
|
weaponp->wi_flags2 |= WIF2_UNTARGETED_HEAT_SEEKER;
|
|
else if (!stricmp(NOX("no radius doubling"), weapon_strings[i])) {
|
|
if (weaponp->wi_flags & WIF_BOMB) {
|
|
weaponp->wi_flags2 |= WIF2_HARD_TARGET_BOMB;
|
|
}
|
|
else {
|
|
Warning(LOCATION, "Weapon %s is not a bomb but has \"no radius doubling\" set. Ignoring this flag", weaponp->name);
|
|
}
|
|
}
|
|
else if (!stricmp(NOX("no subsystem homing"), weapon_strings[i]))
|
|
weaponp->wi_flags2 |= WIF2_NON_SUBSYS_HOMING;
|
|
else if (!stricmp(NOX("no lifeleft penalty"), weapon_strings[i]))
|
|
weaponp->wi_flags2 |= WIF2_NO_LIFE_LOST_IF_MISSED;
|
|
else if (!stricmp(NOX("can be targeted"), weapon_strings[i]))
|
|
weaponp->wi_flags2 |= WIF2_CAN_BE_TARGETED;
|
|
else if (!stricmp(NOX("show on radar"), weapon_strings[i]))
|
|
weaponp->wi_flags2 |= WIF2_SHOWN_ON_RADAR;
|
|
else if (!stricmp(NOX("show friendly on radar"), weapon_strings[i]))
|
|
weaponp->wi_flags2 |= WIF2_SHOW_FRIENDLY;
|
|
else if (!stricmp(NOX("capital+"), weapon_strings[i]))
|
|
weaponp->wi_flags2 |= WIF2_CAPITAL_PLUS;
|
|
else if (!stricmp(NOX("chain external model fps"), weapon_strings[i]))
|
|
weaponp->wi_flags2 |= WIF2_EXTERNAL_WEAPON_FP;
|
|
else if (!stricmp(NOX("external model launcher"), weapon_strings[i]))
|
|
weaponp->wi_flags2 |= WIF2_EXTERNAL_WEAPON_LNCH;
|
|
else if (!stricmp(NOX("takes blast damage"), weapon_strings[i]))
|
|
weaponp->wi_flags2 |= WIF2_TAKES_BLAST_DAMAGE;
|
|
else if (!stricmp(NOX("takes shockwave damage"), weapon_strings[i]))
|
|
weaponp->wi_flags2 |= WIF2_TAKES_SHOCKWAVE_DAMAGE;
|
|
else if (!stricmp(NOX("hide from radar"), weapon_strings[i]))
|
|
weaponp->wi_flags2 |= WIF2_DONT_SHOW_ON_RADAR;
|
|
else if (!stricmp(NOX("render flak"), weapon_strings[i]))
|
|
weaponp->wi_flags2 |= WIF2_RENDER_FLAK;
|
|
else if (!stricmp(NOX("ciws"), weapon_strings[i]))
|
|
weaponp->wi_flags2 |= WIF2_CIWS;
|
|
else if (!stricmp(NOX("anti-subsystem beam"), weapon_strings[i]))
|
|
weaponp->wi_flags2 |= WIF2_ANTISUBSYSBEAM;
|
|
else if (!stricmp(NOX("no primary linking"), weapon_strings[i]))
|
|
weaponp->wi_flags3 |= WIF3_NOLINK;
|
|
else if (!stricmp(NOX("same emp time for capships"), weapon_strings[i]))
|
|
weaponp->wi_flags3 |= WIF3_USE_EMP_TIME_FOR_CAPSHIP_TURRETS;
|
|
else if (!stricmp(NOX("no primary linked penalty"), weapon_strings[i]))
|
|
weaponp->wi_flags3 |= WIF3_NO_LINKED_PENALTY;
|
|
else if (!stricmp(NOX("no homing speed ramp"), weapon_strings[i]))
|
|
weaponp->wi_flags3 |= WIF3_NO_HOMING_SPEED_RAMP;
|
|
else if (!stricmp(NOX("pulls aspect seekers"), weapon_strings[i]))
|
|
{
|
|
if (!(weaponp->wi_flags & WIF_CMEASURE))
|
|
{
|
|
Warning(LOCATION, "\"pulls aspect seekers\" may only be used for countermeasures!");
|
|
}
|
|
else
|
|
{
|
|
weaponp->wi_flags3 |= WIF3_CMEASURE_ASPECT_HOME_ON;
|
|
}
|
|
}
|
|
else if (!stricmp(NOX("interceptable"), weapon_strings[i]))
|
|
weaponp->wi_flags3 |= WIF3_TURRET_INTERCEPTABLE | WIF3_FIGHTER_INTERCEPTABLE;
|
|
else if (!stricmp(NOX("turret interceptable"), weapon_strings[i]))
|
|
weaponp->wi_flags3 |= WIF3_TURRET_INTERCEPTABLE;
|
|
else if (!stricmp(NOX("fighter interceptable"), weapon_strings[i]))
|
|
weaponp->wi_flags3 |= WIF3_FIGHTER_INTERCEPTABLE;
|
|
else if (!stricmp(NOX("apply recoil"), weapon_strings[i]))
|
|
weaponp->wi_flags3 |= WIF3_APPLY_RECOIL;
|
|
else
|
|
Warning(LOCATION, "Bogus string in weapon flags: %s\n", weapon_strings[i]);
|
|
}
|
|
|
|
// Goober5000 - fix up pierce/nopierce flags, per Mantis #2442
|
|
if (set_pierce)
|
|
weaponp->wi_flags2 |= WIF2_PIERCE_SHIELDS;
|
|
if (set_nopierce)
|
|
weaponp->wi_flags2 &= ~WIF2_PIERCE_SHIELDS;
|
|
|
|
// set default tech room status - Goober5000
|
|
if (weaponp->wi_flags & WIF_IN_TECH_DATABASE)
|
|
weaponp->wi_flags2 |= WIF2_DEFAULT_IN_TECH_DATABASE;
|
|
|
|
// SWARM, CORKSCREW and FLAK should be mutually exclusive
|
|
if (weaponp->wi_flags & WIF_FLAK)
|
|
{
|
|
if ((weaponp->wi_flags & WIF_SWARM) || (weaponp->wi_flags & WIF_CORKSCREW))
|
|
{
|
|
Warning(LOCATION, "Swarm, Corkscrew, and Flak are mutually exclusive! Removing Swarm and Corkscrew attributes.\n");
|
|
weaponp->wi_flags &= ~WIF_SWARM;
|
|
weaponp->wi_flags &= ~WIF_CORKSCREW;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ((weaponp->wi_flags & WIF_SWARM) && (weaponp->wi_flags & WIF_CORKSCREW))
|
|
{
|
|
Warning(LOCATION, "Swarm and Corkscrew are mutually exclusive! Defaulting to Swarm.\n");
|
|
weaponp->wi_flags &= ~WIF_CORKSCREW;
|
|
}
|
|
}
|
|
|
|
if (weaponp->wi_flags2 & WIF2_LOCAL_SSM)
|
|
{
|
|
if (!(weaponp->wi_flags & WIF_HOMING) || (weaponp->subtype !=WP_MISSILE))
|
|
{
|
|
Warning(LOCATION, "local ssm must be guided missile: %s", weaponp->name);
|
|
}
|
|
}
|
|
|
|
if ((weaponp->wi_flags2 & WIF2_SMALL_ONLY) && (weaponp->wi_flags & WIF_HUGE))
|
|
{
|
|
Warning(LOCATION,"\"small only\" and \"huge\" flags are mutually exclusive.\nThey are used together in %s\nAI will most likely not use this weapon",weaponp->name);
|
|
}
|
|
|
|
if (!(weaponp->wi_flags & WIF_SPAWN) && (weaponp->wi_flags2 & WIF2_SMART_SPAWN))
|
|
{
|
|
Warning(LOCATION,"\"smart spawn\" flag used without \"spawn\" flag in %s\n",weaponp->name);
|
|
}
|
|
|
|
if ((weaponp->wi_flags2 & WIF2_INHERIT_PARENT_TARGET) && (!(weaponp->wi_flags & WIF_CHILD)))
|
|
{
|
|
Warning(LOCATION,"Weapon %s has the \"inherit parent target\" flag, but not the \"child\" flag. No changes in behavior will occur.", weaponp->name);
|
|
}
|
|
|
|
if (!(weaponp->wi_flags & WIF_HOMING_HEAT) && (weaponp->wi_flags2 & WIF2_UNTARGETED_HEAT_SEEKER))
|
|
{
|
|
Warning(LOCATION,"Weapon '%s' has the \"untargeted heat seeker\" flag, but Homing Type is not set to \"HEAT\".", weaponp->name);
|
|
}
|
|
}
|
|
|
|
void parse_shockwave_info(shockwave_create_info *sci, char *pre_char)
|
|
{
|
|
char buf[NAME_LENGTH];
|
|
|
|
sprintf(buf, "%sShockwave damage:", pre_char);
|
|
if(optional_string(buf)) {
|
|
stuff_float(&sci->damage);
|
|
}
|
|
|
|
sprintf(buf, "%sShockwave damage type:", pre_char);
|
|
if(optional_string(buf)) {
|
|
stuff_string(buf, F_NAME, NAME_LENGTH);
|
|
sci->damage_type_idx_sav = damage_type_add(buf);
|
|
sci->damage_type_idx = sci->damage_type_idx_sav;
|
|
}
|
|
|
|
sprintf(buf, "%sBlast Force:", pre_char);
|
|
if(optional_string(buf)) {
|
|
stuff_float(&sci->blast);
|
|
}
|
|
|
|
sprintf(buf, "%sInner Radius:", pre_char);
|
|
if(optional_string(buf)) {
|
|
stuff_float(&sci->inner_rad);
|
|
}
|
|
|
|
sprintf(buf, "%sOuter Radius:", pre_char);
|
|
if(optional_string(buf)) {
|
|
stuff_float(&sci->outer_rad);
|
|
}
|
|
|
|
if (sci->outer_rad < sci->inner_rad) {
|
|
Warning(LOCATION, "Shockwave outer radius must be greater than or equal to the inner radius!");
|
|
sci->outer_rad = sci->inner_rad;
|
|
}
|
|
|
|
sprintf(buf, "%sShockwave Speed:", pre_char);
|
|
if(optional_string(buf)) {
|
|
stuff_float(&sci->speed);
|
|
}
|
|
|
|
sprintf(buf, "%sShockwave Rotation:", pre_char);
|
|
if(optional_string(buf)) {
|
|
float angs[3];
|
|
stuff_float_list(angs, 3);
|
|
for(int i = 0; i < 3; i++)
|
|
{
|
|
angs[i] = angs[i] * (PI2/180.0f);
|
|
while(angs[i] < 0)
|
|
{
|
|
angs[i] += PI2;
|
|
}
|
|
while(angs[i] > PI2)
|
|
{
|
|
angs[i] -= PI2;
|
|
}
|
|
}
|
|
sci->rot_angles.p = angs[0];
|
|
sci->rot_angles.b = angs[1];
|
|
sci->rot_angles.h = angs[2];
|
|
}
|
|
|
|
sprintf(buf, "%sShockwave Model:", pre_char);
|
|
if(optional_string(buf)) {
|
|
stuff_string(sci->pof_name, F_NAME, MAX_FILENAME_LEN);
|
|
}
|
|
|
|
sprintf(buf, "%sShockwave Name:", pre_char);
|
|
if(optional_string(buf)) {
|
|
stuff_string(sci->name, F_NAME, MAX_FILENAME_LEN);
|
|
}
|
|
}
|
|
|
|
void init_weapon_entry(int weap_info_index)
|
|
{
|
|
Assert(weap_info_index > -1 && weap_info_index < MAX_WEAPON_TYPES);
|
|
weapon_info *wip = &Weapon_info[weap_info_index];
|
|
int i, j;
|
|
|
|
wip->wi_flags = WIF_DEFAULT_VALUE;
|
|
wip->wi_flags2 = WIF2_DEFAULT_VALUE;
|
|
|
|
wip->subtype = WP_UNUSED;
|
|
wip->render_type = WRT_NONE;
|
|
|
|
memset(wip->name, 0, sizeof(wip->name));
|
|
memset(wip->title, 0, sizeof(wip->title));
|
|
wip->desc = NULL;
|
|
|
|
memset(wip->tech_title, 0, sizeof(wip->tech_title));
|
|
memset(wip->tech_anim_filename, 0, sizeof(wip->tech_anim_filename));
|
|
wip->tech_desc = NULL;
|
|
memset(wip->tech_model, 0, sizeof(wip->tech_model));
|
|
|
|
memset(wip->hud_filename, 0, sizeof(wip->hud_filename));
|
|
wip->hud_image_index = -1;
|
|
|
|
memset(wip->pofbitmap_name, 0, sizeof(wip->pofbitmap_name));
|
|
|
|
wip->model_num = -1;
|
|
wip->hud_target_lod = -1;
|
|
wip->num_detail_levels = -1;
|
|
for ( i = 0; i < MAX_MODEL_DETAIL_LEVELS; i++ )
|
|
{
|
|
wip->detail_distance[i] = -1;
|
|
}
|
|
|
|
vm_vec_zero(&wip->closeup_pos);
|
|
wip->closeup_zoom = 1.0f;
|
|
|
|
generic_anim_init(&wip->laser_bitmap);
|
|
generic_anim_init(&wip->laser_glow_bitmap);
|
|
|
|
gr_init_color(&wip->laser_color_1, 255, 255, 255);
|
|
gr_init_color(&wip->laser_color_2, 255, 255, 255);
|
|
|
|
wip->laser_length = 10.0f;
|
|
wip->laser_head_radius = 1.0f;
|
|
wip->laser_tail_radius = 1.0f;
|
|
|
|
memset(wip->external_model_name, 0, sizeof(wip->external_model_name));
|
|
wip->external_model_num = -1;
|
|
|
|
wip->weapon_submodel_rotate_accell = 10.0f;
|
|
wip->weapon_submodel_rotate_vel = 0.0f;
|
|
|
|
wip->mass = 1.0f;
|
|
wip->max_speed = 10.0f;
|
|
wip->acceleration_time = 0.0f;
|
|
wip->vel_inherit_amount = 1.0f;
|
|
wip->free_flight_time = 0.0f;
|
|
wip->fire_wait = 1.0f;
|
|
wip->damage = 0.0f;
|
|
|
|
wip->damage_type_idx = -1;
|
|
wip->damage_type_idx_sav = -1;
|
|
|
|
wip->armor_type_idx = -1;
|
|
|
|
wip->arm_time = 0;
|
|
wip->arm_dist = 0.0f;
|
|
wip->arm_radius = 0.0f;
|
|
wip->det_range = 0.0f;
|
|
wip->det_radius = 0.0f;
|
|
wip->flak_targeting_accuracy = 60.0f; // Standard value as defined in flak.cpp
|
|
wip->flak_detonation_accuracy = 65.0f;
|
|
wip->untargeted_flak_range_penalty = 20.0f;
|
|
|
|
wip->armor_factor = 1.0f;
|
|
wip->shield_factor = 1.0f;
|
|
wip->subsystem_factor = 1.0f;
|
|
|
|
wip->life_min = -1.0f;
|
|
wip->life_max = -1.0f;
|
|
wip->lifetime = 1.0f;
|
|
wip->energy_consumed = 0.0f;
|
|
|
|
wip->cargo_size = 1.0f;
|
|
|
|
wip->turn_time = 1.0f;
|
|
wip->fov = 0; //should be cos(pi), not pi
|
|
|
|
wip->min_lock_time = 0.0f;
|
|
wip->lock_pixels_per_sec = 50;
|
|
wip->catchup_pixels_per_sec = 50;
|
|
wip->catchup_pixel_penalty = 50;
|
|
wip->seeker_strength = 1.0f;
|
|
|
|
wip->swarm_count = -1;
|
|
// *Default is 150 -Et1
|
|
wip->SwarmWait = SWARM_MISSILE_DELAY;
|
|
|
|
wip->pre_launch_snd = -1;
|
|
wip->pre_launch_snd_min_interval = 0;
|
|
|
|
wip->launch_snd = -1;
|
|
wip->impact_snd = -1;
|
|
wip->disarmed_impact_snd = -1;
|
|
wip->flyby_snd = -1;
|
|
|
|
wip->rearm_rate = 1.0f;
|
|
|
|
wip->weapon_range = 999999999.9f;
|
|
// *Minimum weapon range, default is 0 -Et1
|
|
wip->WeaponMinRange = 0.0f;
|
|
|
|
wip->num_spawn_weapons_defined = 0;
|
|
|
|
for (i = 0; i < MAX_SPAWN_TYPES_PER_WEAPON; i++)
|
|
{
|
|
wip->spawn_info[i].spawn_type = -1;
|
|
wip->spawn_info[i].spawn_angle = 180;
|
|
wip->spawn_info[i].spawn_count = DEFAULT_WEAPON_SPAWN_COUNT;
|
|
}
|
|
|
|
// Trails
|
|
wip->tr_info.pt = vmd_zero_vector;
|
|
wip->tr_info.w_start = 1.0f;
|
|
wip->tr_info.w_end = 1.0f;
|
|
wip->tr_info.a_start = 1.0f;
|
|
wip->tr_info.a_end = 1.0f;
|
|
wip->tr_info.max_life = 1.0f;
|
|
wip->tr_info.stamp = 0;
|
|
generic_bitmap_init(&wip->tr_info.texture, NULL);
|
|
wip->tr_info.n_fade_out_sections = 0;
|
|
|
|
memset(wip->icon_filename, 0, sizeof(wip->icon_filename));
|
|
|
|
memset(wip->anim_filename, 0, sizeof(wip->anim_filename));
|
|
|
|
wip->impact_explosion_radius = 1.0f;
|
|
wip->impact_weapon_expl_index = -1;
|
|
|
|
wip->dinky_impact_explosion_radius = 1.0f;
|
|
wip->dinky_impact_weapon_expl_index = -1;
|
|
|
|
wip->flash_impact_weapon_expl_index = -1;
|
|
wip->flash_impact_explosion_radius = 0.0f;
|
|
|
|
wip->piercing_impact_explosion_radius = 0.0f;
|
|
wip->piercing_impact_particle_count = 0;
|
|
wip->piercing_impact_particle_life = 0.0f;
|
|
wip->piercing_impact_particle_velocity = 0.0f;
|
|
wip->piercing_impact_weapon_expl_index = -1;
|
|
wip->piercing_impact_particle_back_velocity = 0.0f;
|
|
wip->piercing_impact_particle_variance = 0.0f;
|
|
|
|
wip->muzzle_flash = -1;
|
|
|
|
wip->emp_intensity = EMP_DEFAULT_INTENSITY;
|
|
wip->emp_time = EMP_DEFAULT_TIME; // Goober5000: <-- Look! I fixed a Volition bug! Gimme $5, Dave!
|
|
wip->recoil_modifier = 1.0f;
|
|
wip->weapon_reduce = ESUCK_DEFAULT_WEAPON_REDUCE;
|
|
wip->afterburner_reduce = ESUCK_DEFAULT_AFTERBURNER_REDUCE;
|
|
|
|
//customizeable corkscrew stuff
|
|
wip->cs_num_fired=4;
|
|
wip->cs_radius=1.25f;
|
|
wip->cs_delay=30;
|
|
wip->cs_crotate=1;
|
|
wip->cs_twist=5.0f;
|
|
|
|
wip->elec_time=8000;
|
|
wip->elec_eng_mult=1.0f;
|
|
wip->elec_weap_mult=1.0f;
|
|
wip->elec_beam_mult=1.0f;
|
|
wip->elec_sensors_mult=1.0f;
|
|
wip->elec_randomness=2000;
|
|
wip->elec_use_new_style=0;
|
|
|
|
wip->lssm_warpout_delay=0; //delay between launch and warpout (ms)
|
|
wip->lssm_warpin_delay=0; //delay between warpout and warpin (ms)
|
|
wip->lssm_stage5_vel=0; //velocity during final stage
|
|
wip->lssm_warpin_radius=0;
|
|
wip->lssm_lock_range=1000000.0f; //local ssm lock range (optional)
|
|
|
|
wip->cm_aspect_effectiveness = 1.0f;
|
|
wip->cm_heat_effectiveness = 1.0f;
|
|
wip->cm_effective_rad = MAX_CMEASURE_TRACK_DIST;
|
|
|
|
wip->b_info.beam_type = -1;
|
|
wip->b_info.beam_life = -1.0f;
|
|
wip->b_info.beam_warmup = -1;
|
|
wip->b_info.beam_warmdown = -1;
|
|
wip->b_info.beam_muzzle_radius = 0.0f;
|
|
wip->b_info.beam_particle_count = -1;
|
|
wip->b_info.beam_particle_radius = 0.0f;
|
|
wip->b_info.beam_particle_angle = 0.0f;
|
|
wip->b_info.beam_loop_sound = -1;
|
|
wip->b_info.beam_warmup_sound = -1;
|
|
wip->b_info.beam_warmdown_sound = -1;
|
|
wip->b_info.beam_num_sections = 0;
|
|
wip->b_info.beam_shots = 1;
|
|
wip->b_info.beam_shrink_factor = 0.0f;
|
|
wip->b_info.beam_shrink_pct = 0.0f;
|
|
wip->b_info.range = BEAM_FAR_LENGTH;
|
|
wip->b_info.damage_threshold = 1.0f;
|
|
wip->b_info.beam_width = -1.0f;
|
|
|
|
generic_anim_init(&wip->b_info.beam_glow, NULL);
|
|
generic_anim_init(&wip->b_info.beam_particle_ani, NULL);
|
|
|
|
for (i = 0; i < MAX_IFFS; i++)
|
|
for (j = 0; j < NUM_SKILL_LEVELS; j++)
|
|
wip->b_info.beam_iff_miss_factor[i][j] = 0.00001f;
|
|
|
|
//WMC - Okay, so this is needed now
|
|
beam_weapon_section_info *bsip;
|
|
for (i = 0; i < MAX_BEAM_SECTIONS; i++) {
|
|
bsip = &wip->b_info.sections[i];
|
|
|
|
generic_anim_init(&bsip->texture, NULL);
|
|
|
|
bsip->width = 1.0f;
|
|
bsip->flicker = 0.1f;
|
|
bsip->z_add = i2fl(MAX_BEAM_SECTIONS - i - 1);
|
|
bsip->tile_type = 0;
|
|
bsip->tile_factor = 1.0f;
|
|
bsip->translation = 0.0f;
|
|
}
|
|
|
|
for (size_t s = 0; s < MAX_PARTICLE_SPEWERS; s++) { // default values for everything -nuke
|
|
wip->particle_spewers[s].particle_spew_type = PSPEW_NONE; // added by nuke
|
|
wip->particle_spewers[s].particle_spew_count = 1;
|
|
wip->particle_spewers[s].particle_spew_time = 25;
|
|
wip->particle_spewers[s].particle_spew_vel = 0.4f;
|
|
wip->particle_spewers[s].particle_spew_radius = 2.0f;
|
|
wip->particle_spewers[s].particle_spew_lifetime = 0.15f;
|
|
wip->particle_spewers[s].particle_spew_scale = 0.8f;
|
|
wip->particle_spewers[s].particle_spew_z_scale = 1.0f; // added by nuke
|
|
wip->particle_spewers[s].particle_spew_rotation_rate = 10.0f;
|
|
wip->particle_spewers[s].particle_spew_offset = vmd_zero_vector;
|
|
wip->particle_spewers[s].particle_spew_velocity = vmd_zero_vector;
|
|
generic_anim_init(&wip->particle_spewers[s].particle_spew_anim, NULL);
|
|
}
|
|
|
|
wip->tag_level = -1;
|
|
wip->tag_time = -1.0f;
|
|
|
|
wip->SSM_index =-1; // tag C SSM index, wich entry in the SSM table this weapon calls -Bobboau
|
|
|
|
wip->field_of_fire = 0.0f;
|
|
wip->fof_spread_rate = 0.0f;
|
|
wip->fof_reset_rate = 0.0f;
|
|
wip->max_fof_spread = 0.0f;
|
|
|
|
wip->shots = 1;
|
|
|
|
wip->alpha_max = 1.0f;
|
|
wip->alpha_min = 0.0f;
|
|
wip->alpha_cycle = 0.0f;
|
|
|
|
shockwave_create_info_init(&wip->shockwave);
|
|
shockwave_create_info_init(&wip->dinky_shockwave);
|
|
|
|
wip->weapon_hitpoints = 0;
|
|
|
|
wip->burst_delay = 1.0f; // 1 second, just incase its not defined
|
|
wip->burst_shots = 0;
|
|
wip->burst_flags = 0;
|
|
|
|
generic_anim_init( &wip->thruster_flame );
|
|
generic_anim_init( &wip->thruster_glow );
|
|
|
|
wip->thruster_glow_factor = 1.0f;
|
|
wip->target_lead_scaler = 0.0f;
|
|
|
|
wip->selection_effect = Default_weapon_select_effect;
|
|
|
|
wip->hud_locked_snd = -1;
|
|
wip->hud_tracking_snd = -1;
|
|
wip->hud_in_flight_snd = -1;
|
|
}
|
|
|
|
// function to parse the information for a specific weapon type.
|
|
// return 0 if successful, otherwise return -1
|
|
#define WEAPONS_MULTITEXT_LENGTH 2048
|
|
|
|
int parse_weapon(int subtype, bool replace)
|
|
{
|
|
char buf[WEAPONS_MULTITEXT_LENGTH];
|
|
weapon_info *wip = NULL;
|
|
char fname[NAME_LENGTH];
|
|
int iff, idx;
|
|
int primary_rearm_rate_specified=0;
|
|
bool first_time = false;
|
|
bool create_if_not_found = true;
|
|
int wi_flags = WIF_DEFAULT_VALUE;
|
|
int wi_flags2 = WIF2_DEFAULT_VALUE;
|
|
int wi_flags3 = WIF3_DEFAULT_VALUE;
|
|
|
|
required_string("$Name:");
|
|
stuff_string(fname, F_NAME, NAME_LENGTH);
|
|
diag_printf ("Weapon name -- %s\n", fname);
|
|
|
|
if(optional_string("+nocreate")) {
|
|
if(!replace) {
|
|
Warning(LOCATION, "+nocreate flag used for weapon in non-modular table");
|
|
}
|
|
create_if_not_found = false;
|
|
}
|
|
|
|
//Remove @ symbol
|
|
//these used to be used to denote weapons that would
|
|
//only be parsed in demo builds
|
|
if ( fname[0] == '@' ) {
|
|
backspace(fname);
|
|
}
|
|
|
|
int w_id = weapon_info_lookup(fname);
|
|
|
|
if(w_id != -1)
|
|
{
|
|
wip = &Weapon_info[w_id];
|
|
if(!replace)
|
|
{
|
|
Warning(LOCATION, "Weapon name %s already exists in weapons.tbl. All weapon names must be unique; the second entry has been skipped", wip->name);
|
|
if ( !skip_to_start_of_string_either("$Name:", "#End")) {
|
|
Int3();
|
|
}
|
|
return -1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//Don't create weapon if it has +nocreate and is in a modular table.
|
|
if(!create_if_not_found && replace)
|
|
{
|
|
if ( !skip_to_start_of_string_either("$Name:", "#End")) {
|
|
Int3();
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
if(Num_weapon_types >= MAX_WEAPON_TYPES) {
|
|
Warning(LOCATION, "Too many weapon classes before '%s'; maximum is %d, so only the first %d will be used", fname, MAX_WEAPON_TYPES, Num_weapon_types);
|
|
|
|
//Skip the rest of the ships in non-modular tables, since we can't add them.
|
|
if(!replace) {
|
|
if ( !skip_to_start_of_string_either("$Name:", "#End")) {
|
|
Int3();
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
wip = &Weapon_info[Num_weapon_types];
|
|
init_weapon_entry(Num_weapon_types);
|
|
first_time = true;
|
|
|
|
strcpy_s(wip->name, fname);
|
|
Num_weapon_types++;
|
|
}
|
|
|
|
if(optional_string("$Alt name:"))
|
|
stuff_string(wip->alt_name, F_NAME, NAME_LENGTH);
|
|
|
|
//Set subtype
|
|
if(optional_string("$Subtype:"))
|
|
{
|
|
stuff_string(fname, F_NAME, NAME_LENGTH);
|
|
|
|
if(!stricmp("Primary", fname)) {
|
|
wip->subtype = WP_LASER;
|
|
} else if(!stricmp("Secondary", fname)) {
|
|
wip->subtype = WP_MISSILE;
|
|
} else {
|
|
Warning(LOCATION, "Unknown subtype on weapon '%s'", wip->name);
|
|
}
|
|
}
|
|
else if(wip->subtype != WP_UNUSED && !first_time)
|
|
{
|
|
if(wip->subtype != subtype) {
|
|
Warning(LOCATION, "Type of weapon %s entry does not agree with original entry type.", wip->name);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
wip->subtype = subtype;
|
|
}
|
|
|
|
if (optional_string("+Title:")) {
|
|
stuff_string(wip->title, F_NAME, WEAPON_TITLE_LEN);
|
|
}
|
|
|
|
if (optional_string("+Description:")) {
|
|
if (wip->desc != NULL) {
|
|
vm_free(wip->desc);
|
|
wip->desc = NULL;
|
|
}
|
|
|
|
stuff_malloc_string(&wip->desc, F_MULTITEXT);
|
|
}
|
|
|
|
if (optional_string("+Tech Title:")) {
|
|
stuff_string(wip->tech_title, F_NAME, NAME_LENGTH);
|
|
}
|
|
|
|
if (optional_string("+Tech Anim:")) {
|
|
stuff_string(wip->tech_anim_filename, F_NAME, MAX_FILENAME_LEN);
|
|
}
|
|
|
|
if (optional_string("+Tech Description:")) {
|
|
if (wip->tech_desc != NULL) {
|
|
vm_free(wip->tech_desc);
|
|
wip->tech_desc = NULL;
|
|
}
|
|
|
|
stuff_malloc_string(&wip->tech_desc, F_MULTITEXT);
|
|
}
|
|
|
|
if (optional_string("$Tech Model:")) {
|
|
stuff_string(wip->tech_model, F_NAME, MAX_FILENAME_LEN);
|
|
|
|
if (optional_string("+Closeup_pos:")) {
|
|
stuff_vec3d(&wip->closeup_pos);
|
|
}
|
|
|
|
if (optional_string("+Closeup_zoom:")) {
|
|
stuff_float(&wip->closeup_zoom);
|
|
}
|
|
}
|
|
|
|
// Weapon fadein effect, used when no ani is specified or weapon_select_3d is active
|
|
wip->selection_effect = Default_weapon_select_effect; // By default, use the FS2 effect
|
|
if(optional_string("$Selection Effect:")) {
|
|
char effect[NAME_LENGTH];
|
|
stuff_string(effect, F_NAME, NAME_LENGTH);
|
|
if (!stricmp(effect, "FS2"))
|
|
wip->selection_effect = 2;
|
|
else if (!stricmp(effect, "FS1"))
|
|
wip->selection_effect = 1;
|
|
else if (!stricmp(effect, "off"))
|
|
wip->selection_effect = 0;
|
|
}
|
|
|
|
//Check for the HUD image string
|
|
if(optional_string("$HUD Image:")) {
|
|
stuff_string(wip->hud_filename, F_NAME, MAX_FILENAME_LEN);
|
|
}
|
|
|
|
// Read the model file. It can be a POF file or none.
|
|
// If there is no model file (Model file: = "none") then we use our special
|
|
// laser renderer which requires inner, middle and outer information.
|
|
if ( optional_string("$Model file:") ) {
|
|
stuff_string(wip->pofbitmap_name, F_NAME, MAX_FILENAME_LEN);
|
|
|
|
if ( VALID_FNAME(wip->pofbitmap_name) )
|
|
wip->render_type = WRT_POF;
|
|
|
|
diag_printf("Model pof file -- %s\n", wip->pofbitmap_name );
|
|
}
|
|
|
|
// a special LOD level to use when rendering the weapon in the hud targetbox
|
|
if ( optional_string( "$POF target LOD:" ) )
|
|
stuff_int(&wip->hud_target_lod);
|
|
|
|
if(optional_string("$Detail distance:")) {
|
|
wip->num_detail_levels = stuff_int_list(wip->detail_distance, MAX_MODEL_DETAIL_LEVELS, RAW_INTEGER_TYPE);
|
|
}
|
|
|
|
if ( optional_string("$External Model File:") )
|
|
stuff_string(wip->external_model_name, F_NAME, MAX_FILENAME_LEN);
|
|
|
|
if ( optional_string("$Submodel Rotation Speed:") )
|
|
stuff_float(&wip->weapon_submodel_rotate_vel);
|
|
|
|
if ( optional_string("$Submodel Rotation Acceleration:") )
|
|
stuff_float(&wip->weapon_submodel_rotate_accell);
|
|
|
|
// No POF or AVI file specified, render as special laser type.(?)
|
|
ubyte r,g,b;
|
|
|
|
// laser bitmap itself
|
|
if ( optional_string("@Laser Bitmap:") ) {
|
|
stuff_string(fname, F_NAME, NAME_LENGTH);
|
|
|
|
if (wip->render_type == WRT_POF) {
|
|
mprintf(("WARNING: Weapon '%s' has both LASER and POF render types! Will only use POF type!\n", wip->name));
|
|
generic_anim_init(&wip->laser_bitmap, NULL);
|
|
} else {
|
|
generic_anim_init(&wip->laser_bitmap, fname);
|
|
wip->render_type = WRT_LASER;
|
|
}
|
|
}
|
|
|
|
// optional laser glow
|
|
if ( optional_string("@Laser Glow:") ) {
|
|
stuff_string(fname, F_NAME, NAME_LENGTH);
|
|
|
|
if (wip->render_type != WRT_LASER) {
|
|
mprintf(("WARNING: Laser glow specified on non-LASER type weapon (%s)!\n", wip->name));
|
|
Int3();
|
|
} else {
|
|
generic_anim_init(&wip->laser_glow_bitmap, fname);
|
|
}
|
|
}
|
|
|
|
if(optional_string("@Laser Color:"))
|
|
{
|
|
// This might be confusing at first glance. If we're a modular table (!first_time),
|
|
// AND we're providing a new color for the laser (being in this block at all),
|
|
// AND the RGB values for laser_color_1 and laser_color_2 match...
|
|
// THEN we conclude that laser_color_2 wasn't explicitly defined before, and the modder would probably prefer
|
|
// it if the laser didn't suddenly start changing colors from the new to the old over its lifespan. -MageKing17
|
|
bool reset = (!first_time && (
|
|
(wip->laser_color_1.red == wip->laser_color_2.red) &&
|
|
(wip->laser_color_1.green == wip->laser_color_2.green) &&
|
|
(wip->laser_color_1.blue == wip->laser_color_2.blue)));
|
|
stuff_ubyte(&r);
|
|
stuff_ubyte(&g);
|
|
stuff_ubyte(&b);
|
|
gr_init_color( &wip->laser_color_1, r, g, b );
|
|
if (reset) {
|
|
gr_init_color( &wip->laser_color_2, wip->laser_color_1.red, wip->laser_color_1.green, wip->laser_color_1.blue );
|
|
}
|
|
}
|
|
|
|
// optional string for cycling laser colors
|
|
if(optional_string("@Laser Color2:")){
|
|
stuff_ubyte(&r);
|
|
stuff_ubyte(&g);
|
|
stuff_ubyte(&b);
|
|
gr_init_color( &wip->laser_color_2, r, g, b );
|
|
} else if (first_time) {
|
|
gr_init_color( &wip->laser_color_2, wip->laser_color_1.red, wip->laser_color_1.green, wip->laser_color_1.blue );
|
|
}
|
|
|
|
if(optional_string("@Laser Length:")) {
|
|
stuff_float(&wip->laser_length);
|
|
}
|
|
|
|
if(optional_string("@Laser Head Radius:")) {
|
|
stuff_float(&wip->laser_head_radius);
|
|
}
|
|
|
|
if(optional_string("@Laser Tail Radius:")) {
|
|
stuff_float(&wip->laser_tail_radius );
|
|
}
|
|
|
|
if(optional_string("$Mass:")) {
|
|
stuff_float( &(wip->mass) );
|
|
|
|
// Goober5000 - hack in order to make the beam whack behavior of these three beams match all other beams
|
|
// this relies on Bobboau's beam whack hack in beam_apply_whack()
|
|
if ((!strcmp(wip->name, "SAAA") && (wip->mass == 4.0f))
|
|
|| (!strcmp(wip->name, "MjolnirBeam") && (wip->mass == 1000.0f))
|
|
|| (!strcmp(wip->name, "MjolnirBeam#home") && (wip->mass == 1000.0f)))
|
|
{
|
|
wip->mass = 100.0f;
|
|
}
|
|
|
|
diag_printf ("Weapon mass -- %7.3f\n", wip->mass);
|
|
}
|
|
|
|
if(optional_string("$Velocity:")) {
|
|
stuff_float( &(wip->max_speed) );
|
|
diag_printf ("Weapon mass -- %7.3f\n", wip->max_speed);
|
|
}
|
|
|
|
if(optional_string("$Fire Wait:")) {
|
|
stuff_float( &(wip->fire_wait) );
|
|
diag_printf ("Weapon fire wait -- %7.3f\n", wip->fire_wait);
|
|
}
|
|
|
|
if(optional_string("$Damage:")) {
|
|
stuff_float(&wip->damage);
|
|
//WMC - now that shockwave damage can be set for them individually,
|
|
//do this automagically
|
|
if(first_time) {
|
|
wip->shockwave.damage = wip->damage;
|
|
}
|
|
}
|
|
|
|
if(optional_string("$Damage Type:")) {
|
|
//This is checked for validity on every armor type
|
|
//If it's invalid (or -1), then armor has no effect
|
|
stuff_string(buf, F_NAME, WEAPONS_MULTITEXT_LENGTH);
|
|
wip->damage_type_idx_sav = damage_type_add(buf);
|
|
wip->damage_type_idx = wip->damage_type_idx_sav;
|
|
}
|
|
|
|
if(optional_string("$Arm time:")) {
|
|
float flit;
|
|
stuff_float(&flit);
|
|
wip->arm_time = fl2f(flit);
|
|
}
|
|
|
|
if(optional_string("$Arm distance:")) {
|
|
stuff_float(&wip->arm_dist);
|
|
}
|
|
|
|
if(optional_string("$Arm radius:")) {
|
|
stuff_float(&wip->arm_radius);
|
|
}
|
|
|
|
if(optional_string("$Detonation Range:")) {
|
|
stuff_float(&wip->det_range);
|
|
}
|
|
|
|
if(optional_string("$Detonation Radius:")) {
|
|
stuff_float(&wip->det_radius);
|
|
}
|
|
|
|
if(optional_string("$Flak Detonation Accuracy:")) {
|
|
stuff_float(&wip->flak_detonation_accuracy);
|
|
}
|
|
|
|
if(optional_string("$Flak Targeting Accuracy:")) {
|
|
stuff_float(&wip->flak_targeting_accuracy);
|
|
}
|
|
|
|
if(optional_string("$Untargeted Flak Range Penalty:")) {
|
|
stuff_float(&wip->untargeted_flak_range_penalty);
|
|
}
|
|
|
|
parse_shockwave_info(&wip->shockwave, "$");
|
|
|
|
//Retain compatibility
|
|
if(first_time)
|
|
{
|
|
wip->dinky_shockwave = wip->shockwave;
|
|
wip->dinky_shockwave.damage /= 4.0f;
|
|
}
|
|
|
|
if(optional_string("$Dinky shockwave:"))
|
|
{
|
|
parse_shockwave_info(&wip->dinky_shockwave, "+");
|
|
}
|
|
|
|
if(optional_string("$Armor Factor:")) {
|
|
stuff_float(&wip->armor_factor);
|
|
}
|
|
|
|
if(optional_string("$Shield Factor:")) {
|
|
stuff_float(&wip->shield_factor);
|
|
}
|
|
|
|
if(optional_string("$Subsystem Factor:")) {
|
|
stuff_float(&wip->subsystem_factor);
|
|
}
|
|
|
|
if(optional_string("$Lifetime Min:")) {
|
|
stuff_float(&wip->life_min);
|
|
|
|
if(wip->life_min < 0.0f) {
|
|
wip->life_min = 0.0f;
|
|
Warning(LOCATION, "Lifetime min for weapon '%s' cannot be less than 0. Setting to 0.\n", wip->name);
|
|
}
|
|
}
|
|
|
|
if(optional_string("$Lifetime Max:")) {
|
|
stuff_float(&wip->life_max);
|
|
|
|
if(wip->life_max < 0.0f) {
|
|
wip->life_max = 0.0f;
|
|
Warning(LOCATION, "Lifetime max for weapon '%s' cannot be less than 0. Setting to 0.\n", wip->name);
|
|
} else if (wip->life_max < wip->life_min) {
|
|
wip->life_max = wip->life_min + 0.1f;
|
|
Warning(LOCATION, "Lifetime max for weapon '%s' cannot be less than its Lifetime Min (%d) value. Setting to %d.\n", wip->name, wip->life_min, wip->life_max);
|
|
} else {
|
|
wip->lifetime = (wip->life_min+wip->life_max)*0.5f;
|
|
}
|
|
}
|
|
|
|
if(wip->life_min >= 0.0f && wip->life_max < 0.0f) {
|
|
wip->lifetime = wip->life_min;
|
|
wip->life_min = -1.0f;
|
|
Warning(LOCATION, "Lifetime min, but not lifetime max, specified for weapon %s. Assuming static lifetime of %.2f seconds.\n", wip->lifetime);
|
|
}
|
|
|
|
if(optional_string("$Lifetime:")) {
|
|
if(wip->life_min >= 0.0f || wip->life_max >= 0.0f) {
|
|
Warning(LOCATION, "Lifetime min or max specified, but $Lifetime was also specified; min or max will be used.");
|
|
}
|
|
stuff_float(&wip->lifetime);
|
|
}
|
|
|
|
if(optional_string("$Energy Consumed:")) {
|
|
stuff_float(&wip->energy_consumed);
|
|
}
|
|
|
|
// Goober5000: cargo size is checked for div-0 errors... see below (must parse flags first)
|
|
if(optional_string("$Cargo Size:"))
|
|
{
|
|
stuff_float(&wip->cargo_size);
|
|
}
|
|
|
|
bool is_homing=false;
|
|
if(optional_string("$Homing:")) {
|
|
stuff_boolean(&is_homing);
|
|
}
|
|
|
|
if (is_homing || (wip->wi_flags & WIF_HOMING))
|
|
{
|
|
char temp_type[NAME_LENGTH];
|
|
|
|
// the following five items only need to be recorded if the weapon is a homing weapon
|
|
if(optional_string("+Type:"))
|
|
{
|
|
stuff_string(temp_type, F_NAME, NAME_LENGTH);
|
|
if (!stricmp(temp_type, NOX("HEAT")))
|
|
{
|
|
if(wip->wi_flags & WIF_HOMING_ASPECT) {
|
|
wip->wi_flags &= ~WIF_HOMING_ASPECT;
|
|
}
|
|
if(wip->wi_flags & WIF_HOMING_JAVELIN) {
|
|
wip->wi_flags &= ~WIF_HOMING_JAVELIN;
|
|
}
|
|
|
|
wip->wi_flags |= WIF_HOMING_HEAT | WIF_TURNS;
|
|
wi_flags |= WIF_HOMING_HEAT | WIF_TURNS;
|
|
}
|
|
else if (!stricmp(temp_type, NOX("ASPECT")))
|
|
{
|
|
if(wip->wi_flags & WIF_HOMING_HEAT) {
|
|
wip->wi_flags &= ~WIF_HOMING_HEAT;
|
|
}
|
|
if(wip->wi_flags & WIF_HOMING_JAVELIN) {
|
|
wip->wi_flags &= ~WIF_HOMING_JAVELIN;
|
|
}
|
|
|
|
wip->wi_flags |= WIF_HOMING_ASPECT | WIF_TURNS;
|
|
wi_flags |= WIF_HOMING_ASPECT | WIF_TURNS;
|
|
}
|
|
else if (!stricmp(temp_type, NOX("JAVELIN")))
|
|
{
|
|
if(wip->wi_flags & WIF_HOMING_HEAT) {
|
|
wip->wi_flags &= ~WIF_HOMING_HEAT;
|
|
}
|
|
if(wip->wi_flags & WIF_HOMING_ASPECT) {
|
|
wip->wi_flags &= ~WIF_HOMING_ASPECT;
|
|
}
|
|
|
|
wip->wi_flags |= (WIF_HOMING_JAVELIN | WIF_TURNS);
|
|
wi_flags |= (WIF_HOMING_JAVELIN | WIF_TURNS);
|
|
}
|
|
//If you want to add another weapon, remember you need to reset
|
|
//ALL homing flags.
|
|
}
|
|
|
|
if (wip->wi_flags & WIF_HOMING_HEAT)
|
|
{
|
|
float view_cone_angle;
|
|
|
|
if(optional_string("+Turn Time:")) {
|
|
stuff_float(&wip->turn_time);
|
|
}
|
|
|
|
if(optional_string("+View Cone:")) {
|
|
stuff_float(&view_cone_angle);
|
|
wip->fov = (float)cos((float)(ANG_TO_RAD(view_cone_angle/2.0f)));
|
|
}
|
|
|
|
if (optional_string("+Seeker Strength:"))
|
|
{
|
|
//heat default seeker strength is 3
|
|
stuff_float(&wip->seeker_strength);
|
|
wip->wi_flags2 |= WIF2_CUSTOM_SEEKER_STR;
|
|
if (wip->seeker_strength <= 0)
|
|
{
|
|
Warning(LOCATION,"Seeker Strength for missile \'%s\' must be greater than zero\nReseting value to default.", wip->name);
|
|
wip->seeker_strength = 3.0f;
|
|
wip->wi_flags2 &= ~WIF2_CUSTOM_SEEKER_STR;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(!(wip->wi_flags2 & WIF2_CUSTOM_SEEKER_STR))
|
|
wip->seeker_strength = 3.0f;
|
|
}
|
|
|
|
if (optional_string("+Target Lead Scaler:"))
|
|
{
|
|
stuff_float(&wip->target_lead_scaler);
|
|
if (wip->target_lead_scaler == 0.0f)
|
|
wip->wi_flags2 &= ~WIF2_VARIABLE_LEAD_HOMING;
|
|
else {
|
|
wip->wi_flags2 |= WIF2_VARIABLE_LEAD_HOMING;
|
|
wi_flags2 |= WIF2_VARIABLE_LEAD_HOMING;
|
|
}
|
|
}
|
|
}
|
|
else if ((wip->wi_flags & WIF_HOMING_ASPECT) || (wip->wi_flags & WIF_HOMING_JAVELIN))
|
|
{
|
|
if(optional_string("+Turn Time:")) {
|
|
stuff_float(&wip->turn_time);
|
|
}
|
|
|
|
if(optional_string("+View Cone:")) {
|
|
float view_cone_angle;
|
|
stuff_float(&view_cone_angle);
|
|
wip->fov = (float)cos((float)(ANG_TO_RAD(view_cone_angle/2.0f)));
|
|
}
|
|
|
|
if(optional_string("+Min Lock Time:")) { // minimum time (in seconds) to achieve lock
|
|
stuff_float(&wip->min_lock_time);
|
|
}
|
|
|
|
if(optional_string("+Lock Pixels/Sec:")) { // pixels/sec moved while locking
|
|
stuff_int(&wip->lock_pixels_per_sec);
|
|
}
|
|
|
|
if(optional_string("+Catch-up Pixels/Sec:")) { // pixels/sec moved while catching-up for a lock
|
|
stuff_int(&wip->catchup_pixels_per_sec);
|
|
}
|
|
|
|
if(optional_string("+Catch-up Penalty:")) {
|
|
// number of extra pixels to move while locking as a penalty for catching up for a lock
|
|
stuff_int(&wip->catchup_pixel_penalty);
|
|
}
|
|
|
|
if (optional_string("+Seeker Strength:"))
|
|
{
|
|
//aspect default seeker strength is 2
|
|
stuff_float(&wip->seeker_strength);
|
|
wip->wi_flags2 |= WIF2_CUSTOM_SEEKER_STR;
|
|
if (wip->seeker_strength <= 0)
|
|
{
|
|
Warning(LOCATION,"Seeker Strength for missile \'%s\' must be greater than zero\nReseting value to default.", wip->name);
|
|
wip->seeker_strength = 2.0f;
|
|
wip->wi_flags2 &= ~WIF2_CUSTOM_SEEKER_STR;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(!(wip->wi_flags2 & WIF2_CUSTOM_SEEKER_STR))
|
|
wip->seeker_strength = 2.0f;
|
|
}
|
|
if (optional_string("+Target Lead Scaler:"))
|
|
{
|
|
stuff_float(&wip->target_lead_scaler);
|
|
if (wip->target_lead_scaler == 1.0f)
|
|
wip->wi_flags2 &= ~WIF2_VARIABLE_LEAD_HOMING;
|
|
else {
|
|
wip->wi_flags2 |= WIF2_VARIABLE_LEAD_HOMING;
|
|
wi_flags2 |= WIF2_VARIABLE_LEAD_HOMING;
|
|
}
|
|
}
|
|
|
|
if (wip->wi_flags & WIF_LOCKED_HOMING) {
|
|
// locked homing missiles have a much longer lifespan than the AI think they do
|
|
wip->max_lifetime = wip->lifetime * LOCKED_HOMING_EXTENDED_LIFE_FACTOR;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Error(LOCATION, "Illegal homing type = %s.\nMust be HEAT, ASPECT or JAVELIN.\n", temp_type);
|
|
}
|
|
|
|
}
|
|
|
|
// swarm missiles
|
|
int s_count;
|
|
|
|
if(optional_string("$Swarm:"))
|
|
{
|
|
wip->swarm_count = SWARM_DEFAULT_NUM_MISSILES_FIRED;
|
|
stuff_int(&s_count);
|
|
wip->swarm_count = (short)s_count;
|
|
|
|
// flag as being a swarm weapon
|
|
wip->wi_flags |= WIF_SWARM;
|
|
wi_flags |= WIF_SWARM;
|
|
}
|
|
|
|
// *Swarm wait token -Et1
|
|
if((wip->wi_flags & WIF_SWARM) && optional_string( "+SwarmWait:" ))
|
|
{
|
|
float SwarmWait;
|
|
stuff_float( &SwarmWait );
|
|
if( SwarmWait > 0.0f && SwarmWait * wip->swarm_count < wip->fire_wait )
|
|
{
|
|
wip->SwarmWait = int( SwarmWait * 1000 );
|
|
}
|
|
}
|
|
|
|
if(optional_string("$Acceleration Time:")) {
|
|
stuff_float(&wip->acceleration_time);
|
|
}
|
|
|
|
if(optional_string("$Velocity Inherit:")) {
|
|
stuff_float(&wip->vel_inherit_amount);
|
|
wip->vel_inherit_amount /= 100.0f; // % -> 0..1
|
|
}
|
|
|
|
if(optional_string("$Free Flight Time:")) {
|
|
stuff_float(&(wip->free_flight_time));
|
|
} else if(first_time && is_homing) {
|
|
wip->free_flight_time = HOMING_DEFAULT_FREE_FLIGHT_TIME;
|
|
}
|
|
|
|
if(optional_string("$Free Flight Speed:")) {
|
|
float temp;
|
|
stuff_float(&temp);
|
|
nprintf(("Warning", "Ignoring free flight speed for weapon '%s'\n", wip->name));
|
|
}
|
|
//Optional one-shot sound to play at the beginning of firing
|
|
parse_sound("$PreLaunchSnd:", &wip->pre_launch_snd, wip->name);
|
|
|
|
//Optional delay for Pre-Launch sound
|
|
if(optional_string("+PreLaunchSnd Min Interval:"))
|
|
{
|
|
stuff_int(&wip->pre_launch_snd_min_interval);
|
|
}
|
|
|
|
//Launch sound
|
|
parse_sound("$LaunchSnd:", &wip->launch_snd, wip->name);
|
|
|
|
//Impact sound
|
|
parse_sound("$ImpactSnd:", &wip->impact_snd, wip->name);
|
|
|
|
//Disarmed impact sound
|
|
parse_sound("$Disarmed ImpactSnd:", &wip->impact_snd, wip->name);
|
|
|
|
parse_sound("$FlyBySnd:", &wip->flyby_snd, wip->name);
|
|
|
|
parse_sound("$TrackingSnd:", &wip->hud_tracking_snd, wip->name);
|
|
|
|
parse_sound("$LockedSnd:", &wip->hud_locked_snd, wip->name);
|
|
|
|
parse_sound("$InFlightSnd:", &wip->hud_in_flight_snd, wip->name);
|
|
|
|
if (optional_string("+Inflight sound type:"))
|
|
{
|
|
SCP_string type;
|
|
|
|
stuff_string(type, F_NAME);
|
|
|
|
if (!stricmp(type.c_str(), "TARGETED"))
|
|
{
|
|
wip->in_flight_play_type = TARGETED;
|
|
}
|
|
else if (!stricmp(type.c_str(), "UNTARGETED"))
|
|
{
|
|
wip->in_flight_play_type = UNTARGETED;
|
|
}
|
|
else if (!stricmp(type.c_str(), "ALWAYS"))
|
|
{
|
|
wip->in_flight_play_type = ALWAYS;
|
|
}
|
|
else
|
|
{
|
|
Warning(LOCATION, "Unknown in-flight sound type \"%s\"!", type.c_str());
|
|
wip->in_flight_play_type = ALWAYS;
|
|
}
|
|
}
|
|
|
|
if(optional_string("$Model:"))
|
|
{
|
|
wip->render_type = WRT_POF;
|
|
stuff_string(wip->pofbitmap_name, F_NAME, MAX_FILENAME_LEN);
|
|
}
|
|
|
|
// handle rearm rate - modified by Goober5000
|
|
primary_rearm_rate_specified = 0;
|
|
float rearm_rate;
|
|
// Anticipate rearm rate for ballistic primaries
|
|
if (optional_string("$Rearm Rate:"))
|
|
{
|
|
if (subtype != WP_MISSILE) {
|
|
primary_rearm_rate_specified = 1;
|
|
}
|
|
|
|
stuff_float( &rearm_rate );
|
|
if (rearm_rate > 0.0f)
|
|
{
|
|
wip->rearm_rate = 1.0f/rearm_rate;
|
|
}
|
|
else
|
|
{
|
|
Warning(LOCATION, "Rearm wait of less than 0 on weapon %s; setting to 1", wip->name);
|
|
}
|
|
}
|
|
|
|
|
|
if (optional_string("+Weapon Range:")) {
|
|
stuff_float(&wip->weapon_range);
|
|
}
|
|
|
|
if( optional_string( "+Weapon Min Range:" ) )
|
|
{
|
|
float MinRange;
|
|
stuff_float( &MinRange );
|
|
|
|
if( MinRange > 0.0f && MinRange < MIN( wip->max_speed * wip->lifetime, wip->weapon_range ) )
|
|
{
|
|
wip->WeaponMinRange = MinRange;
|
|
}
|
|
else
|
|
{
|
|
Warning(LOCATION, "Invalid minimum range on weapon %s; setting to 0", wip->name);
|
|
}
|
|
|
|
}
|
|
|
|
parse_wi_flags(wip, wi_flags, wi_flags2, wi_flags3);
|
|
|
|
// be friendly; make sure ballistic flags are synchronized - Goober5000
|
|
// primary
|
|
if (subtype == WP_LASER)
|
|
{
|
|
// ballistic
|
|
if (wip->wi_flags2 & WIF2_BALLISTIC)
|
|
{
|
|
// rearm rate not specified
|
|
if (!primary_rearm_rate_specified && first_time)
|
|
{
|
|
Warning(LOCATION, "$Rearm Rate for ballistic primary %s not specified. Defaulting to 100...\n", wip->name);
|
|
wip->rearm_rate = 100.0f;
|
|
}
|
|
}
|
|
// not ballistic
|
|
else
|
|
{
|
|
// rearm rate specified
|
|
if (primary_rearm_rate_specified)
|
|
{
|
|
Warning(LOCATION, "$Rearm Rate specified for non-ballistic primary %s\n", wip->name);
|
|
}
|
|
}
|
|
|
|
}
|
|
// secondary
|
|
else
|
|
{
|
|
// ballistic
|
|
if (wip->wi_flags2 & WIF2_BALLISTIC)
|
|
{
|
|
Warning(LOCATION, "Secondary weapon %s can't be ballistic. Removing this flag...\n", wip->name);
|
|
wip->wi_flags2 &= ~WIF2_BALLISTIC;
|
|
}
|
|
}
|
|
|
|
// also make sure EMP is friendly - Goober5000
|
|
if (wip->wi_flags & WIF_EMP)
|
|
{
|
|
if (!wip->shockwave.outer_rad)
|
|
{
|
|
Warning(LOCATION, "Outer blast radius of weapon %s is zero - EMP will not work.\nAdd $Outer Radius to weapon table entry.\n", wip->name);
|
|
}
|
|
}
|
|
|
|
// also make sure secondaries and ballistic primaries do not have 0 cargo size
|
|
if (subtype == WP_MISSILE || wip->wi_flags2 & WIF2_BALLISTIC)
|
|
{
|
|
if (wip->cargo_size == 0.0f)
|
|
{
|
|
Warning(LOCATION, "Cargo size of weapon %s cannot be 0. Setting to 1.\n", wip->name);
|
|
wip->cargo_size = 1.0f;
|
|
}
|
|
}
|
|
|
|
if ( optional_string("$Trail:") ) {
|
|
trail_info *ti = &wip->tr_info;
|
|
wip->wi_flags |= WIF_TRAIL; // missile leaves a trail
|
|
|
|
if ( optional_string("+Start Width:") )
|
|
stuff_float(&ti->w_start);
|
|
|
|
if ( optional_string("+End Width:") )
|
|
stuff_float(&ti->w_end);
|
|
|
|
if ( optional_string("+Start Alpha:") )
|
|
stuff_float(&ti->a_start);
|
|
|
|
if ( optional_string("+End Alpha:") )
|
|
stuff_float(&ti->a_end);
|
|
|
|
if ( optional_string("+Max Life:") ) {
|
|
stuff_float(&ti->max_life);
|
|
ti->stamp = fl2i(1000.0f*ti->max_life)/(NUM_TRAIL_SECTIONS+1);
|
|
}
|
|
|
|
if ( required_string("+Bitmap:") ) {
|
|
stuff_string(fname, F_NAME, NAME_LENGTH);
|
|
generic_bitmap_init(&ti->texture, fname);
|
|
}
|
|
|
|
if ( optional_string("+Faded Out Sections:") ) {
|
|
stuff_int(&ti->n_fade_out_sections);
|
|
}
|
|
}
|
|
|
|
// read in filename for icon that is used in weapons selection
|
|
if ( optional_string("$Icon:") ) {
|
|
stuff_string(wip->icon_filename, F_NAME, MAX_FILENAME_LEN);
|
|
}
|
|
|
|
// read in filename for animation that is used in weapons selection
|
|
if ( optional_string("$Anim:") ) {
|
|
stuff_string(wip->anim_filename, F_NAME, MAX_FILENAME_LEN);
|
|
}
|
|
|
|
if ( optional_string("$Impact Explosion:") ) {
|
|
stuff_string(fname, F_NAME, NAME_LENGTH);
|
|
|
|
if ( VALID_FNAME(fname) )
|
|
wip->impact_weapon_expl_index = Weapon_explosions.Load(fname);
|
|
}
|
|
|
|
if ( optional_string("$Impact Explosion Radius:") )
|
|
stuff_float(&wip->impact_explosion_radius);
|
|
|
|
if ( optional_string("$Dinky Impact Explosion:") ) {
|
|
stuff_string(fname, F_NAME, NAME_LENGTH);
|
|
|
|
if ( VALID_FNAME(fname) )
|
|
wip->dinky_impact_weapon_expl_index = Weapon_explosions.Load(fname);
|
|
} else if (first_time) {
|
|
wip->dinky_impact_weapon_expl_index = wip->impact_weapon_expl_index;
|
|
}
|
|
|
|
if ( optional_string("$Dinky Impact Explosion Radius:") )
|
|
stuff_float(&wip->dinky_impact_explosion_radius);
|
|
else if (first_time)
|
|
wip->dinky_impact_explosion_radius = wip->impact_explosion_radius;
|
|
|
|
if ( optional_string("$Piercing Impact Explosion:") ) {
|
|
stuff_string(fname, F_NAME, NAME_LENGTH);
|
|
|
|
if ( VALID_FNAME(fname) )
|
|
wip->piercing_impact_weapon_expl_index = Weapon_explosions.Load(fname);
|
|
}
|
|
|
|
if ( optional_string("$Piercing Impact Radius:") )
|
|
stuff_float(&wip->piercing_impact_explosion_radius);
|
|
|
|
if ( optional_string("$Piercing Impact Velocity:") )
|
|
stuff_float(&wip->piercing_impact_particle_velocity);
|
|
|
|
if ( optional_string("$Piercing Impact Splash Velocity:") )
|
|
stuff_float(&wip->piercing_impact_particle_back_velocity);
|
|
|
|
if ( optional_string("$Piercing Impact Variance:") )
|
|
stuff_float(&wip->piercing_impact_particle_variance);
|
|
|
|
if ( optional_string("$Piercing Impact Life:") )
|
|
stuff_float(&wip->piercing_impact_particle_life);
|
|
|
|
if ( optional_string("$Piercing Impact Particles:") )
|
|
stuff_int(&wip->piercing_impact_particle_count);
|
|
|
|
// muzzle flash
|
|
if ( optional_string("$Muzzleflash:") ) {
|
|
stuff_string(fname, F_NAME, NAME_LENGTH);
|
|
|
|
// look it up
|
|
wip->muzzle_flash = mflash_lookup(fname);
|
|
}
|
|
|
|
// EMP optional stuff (if WIF_EMP is not set, none of this matters, anyway)
|
|
if( optional_string("$EMP Intensity:") ){
|
|
stuff_float(&wip->emp_intensity);
|
|
}
|
|
|
|
if( optional_string("$EMP Time:") ){
|
|
stuff_float(&wip->emp_time);
|
|
}
|
|
|
|
// 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.
|
|
if (optional_string("$Recoil Modifier:")){
|
|
if (!(wip->wi_flags3 & WIF3_APPLY_RECOIL)){
|
|
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);
|
|
wip->wi_flags3 |= WIF3_APPLY_RECOIL;
|
|
}
|
|
stuff_float(&wip->recoil_modifier);
|
|
}
|
|
|
|
// Energy suck optional stuff (if WIF_ENERGY_SUCK is not set, none of this matters anyway)
|
|
if( optional_string("$Leech Weapon:") ){
|
|
stuff_float(&wip->weapon_reduce);
|
|
}
|
|
|
|
if( optional_string("$Leech Afterburner:") ){
|
|
stuff_float(&wip->afterburner_reduce);
|
|
}
|
|
|
|
if (optional_string("$Corkscrew:"))
|
|
{
|
|
if(optional_string("+Num Fired:")) {
|
|
stuff_int(&wip->cs_num_fired);
|
|
}
|
|
|
|
if(optional_string("+Radius:")) {
|
|
stuff_float(&wip->cs_radius);
|
|
}
|
|
|
|
if(optional_string("+Fire Delay:")) {
|
|
stuff_int(&wip->cs_delay);
|
|
}
|
|
|
|
if(optional_string("+Counter rotate:")) {
|
|
stuff_boolean(&wip->cs_crotate);
|
|
}
|
|
|
|
if(optional_string("+Twist:")) {
|
|
stuff_float(&wip->cs_twist);
|
|
}
|
|
}
|
|
|
|
//electronics tag optional stuff
|
|
//Note that I made all these optional in the interest of modular tables.
|
|
//TODO: Possibly add a warning on first_time define?
|
|
if (optional_string("$Electronics:"))
|
|
{
|
|
if(optional_string("+New Style:")) {
|
|
wip->elec_use_new_style=1;
|
|
}
|
|
else if(optional_string("+Old Style:")) {
|
|
wip->elec_use_new_style=0;
|
|
}
|
|
|
|
if(optional_string("+Area Of Effect")) {
|
|
wip->wi_flags3 |= WIF3_AOE_ELECTRONICS;
|
|
}
|
|
|
|
//New only -WMC
|
|
if(optional_string("+Intensity:")) {
|
|
float temp;
|
|
stuff_float(&temp);
|
|
Warning(LOCATION, "+Intensity is deprecated");
|
|
}
|
|
|
|
if(optional_string("+Lifetime:")) {
|
|
stuff_int(&wip->elec_time);
|
|
}
|
|
|
|
//New only -WMC
|
|
if(optional_string("+Engine Multiplier:")) {
|
|
stuff_float(&wip->elec_eng_mult);
|
|
if(!wip->elec_use_new_style)Warning(LOCATION, "+Engine multiplier may only be used with new style electronics");
|
|
}
|
|
|
|
//New only -WMC
|
|
if(optional_string("+Weapon Multiplier:")) {
|
|
stuff_float(&wip->elec_weap_mult);
|
|
if(!wip->elec_use_new_style)Warning(LOCATION, "+Weapon multiplier may only be used with new style electronics");
|
|
}
|
|
|
|
//New only -WMC
|
|
if(optional_string("+Beam Turret Multiplier:")) {
|
|
stuff_float(&wip->elec_beam_mult);
|
|
if(!wip->elec_use_new_style)Warning(LOCATION, "+Beam turret multiplier may only be used with new style electronics");
|
|
}
|
|
|
|
//New only -WMC
|
|
if(optional_string("+Sensors Multiplier:")) {
|
|
stuff_float(&wip->elec_sensors_mult);
|
|
if(!wip->elec_use_new_style)Warning(LOCATION, "+Sensors multiplier may only be used with new style electronics");
|
|
}
|
|
|
|
if(optional_string("+Randomness Time:")) {
|
|
stuff_int(&wip->elec_randomness);
|
|
}
|
|
}
|
|
|
|
//read in the spawn angle info
|
|
//if the weapon isn't a spawn weapon, then this is not going to be used.
|
|
int num_spawn_angs_defined = 0;
|
|
float dum_float;
|
|
|
|
while (optional_string("$Spawn Angle:"))
|
|
{
|
|
stuff_float(&dum_float);
|
|
|
|
if (num_spawn_angs_defined < MAX_SPAWN_TYPES_PER_WEAPON)
|
|
{
|
|
wip->spawn_info[num_spawn_angs_defined].spawn_angle = dum_float;
|
|
num_spawn_angs_defined++;
|
|
}
|
|
}
|
|
|
|
if (wip->wi_flags2 & WIF2_LOCAL_SSM && optional_string("$Local SSM:"))
|
|
{
|
|
if(optional_string("+Warpout Delay:")) {
|
|
stuff_int(&wip->lssm_warpout_delay);
|
|
}
|
|
|
|
if(optional_string("+Warpin Delay:")) {
|
|
stuff_int(&wip->lssm_warpin_delay);
|
|
}
|
|
|
|
if(optional_string("+Stage 5 Velocity:")) {
|
|
stuff_float(&wip->lssm_stage5_vel);
|
|
}
|
|
|
|
if(optional_string("+Warpin Radius:")) {
|
|
stuff_float(&wip->lssm_warpin_radius);
|
|
}
|
|
|
|
if (optional_string("+Lock Range:")) {
|
|
stuff_float(&wip->lssm_lock_range);
|
|
}
|
|
}
|
|
|
|
if (optional_string("$Countermeasure:"))
|
|
{
|
|
if (!(wip->wi_flags & WIF_CMEASURE))
|
|
{
|
|
Warning(LOCATION,"Weapon \'%s\' has countermeasure information defined, but the \"countermeasure\" flag wasn\'t found in the \'$Flags:\' field.\n", wip->name);
|
|
}
|
|
|
|
if (optional_string("+Heat Effectiveness:"))
|
|
stuff_float(&wip->cm_heat_effectiveness);
|
|
|
|
if (optional_string("+Aspect Effectiveness:"))
|
|
stuff_float(&wip->cm_aspect_effectiveness);
|
|
|
|
if (optional_string("+Effective Radius:"))
|
|
stuff_float(&wip->cm_effective_rad);
|
|
}
|
|
|
|
// beam weapon optional stuff
|
|
if ( optional_string("$BeamInfo:") ) {
|
|
// beam type
|
|
if(optional_string("+Type:")) {
|
|
stuff_int(&wip->b_info.beam_type);
|
|
}
|
|
|
|
// how long it lasts
|
|
if(optional_string("+Life:")) {
|
|
stuff_float(&wip->b_info.beam_life);
|
|
}
|
|
|
|
// warmup time
|
|
if(optional_string("+Warmup:")) {
|
|
stuff_int(&wip->b_info.beam_warmup);
|
|
}
|
|
|
|
// warmdowm time
|
|
if(optional_string("+Warmdown:")) {
|
|
stuff_int(&wip->b_info.beam_warmdown);
|
|
}
|
|
|
|
// muzzle glow radius
|
|
if(optional_string("+Radius:")) {
|
|
stuff_float(&wip->b_info.beam_muzzle_radius);
|
|
}
|
|
|
|
// particle spew count
|
|
if(optional_string("+PCount:")) {
|
|
stuff_int(&wip->b_info.beam_particle_count);
|
|
}
|
|
|
|
// particle radius
|
|
if(optional_string("+PRadius:")) {
|
|
stuff_float(&wip->b_info.beam_particle_radius);
|
|
}
|
|
|
|
// angle off turret normal
|
|
if(optional_string("+PAngle:")) {
|
|
stuff_float(&wip->b_info.beam_particle_angle);
|
|
}
|
|
|
|
// particle bitmap/ani
|
|
if ( optional_string("+PAni:") ) {
|
|
stuff_string(fname, F_NAME, NAME_LENGTH);
|
|
generic_anim_init(&wip->b_info.beam_particle_ani, fname);
|
|
}
|
|
|
|
// magic miss #
|
|
if(optional_string("+Miss Factor:")) {
|
|
for(idx=0; idx<NUM_SKILL_LEVELS; idx++) {
|
|
float temp;
|
|
if(!stuff_float_optional(&temp)) {
|
|
break;
|
|
}
|
|
// an unspecified Miss Factor should apply to all IFFs
|
|
for(iff=0; iff<Num_iffs; iff++) {
|
|
wip->b_info.beam_iff_miss_factor[iff][idx] = temp;
|
|
}
|
|
}
|
|
}
|
|
// now check miss factors for each IFF
|
|
for(iff=0; iff<Num_iffs; iff++) {
|
|
char miss_factor_string[NAME_LENGTH + 15];
|
|
sprintf(miss_factor_string, "+%s Miss Factor:", Iff_info[iff].iff_name);
|
|
if(optional_string(miss_factor_string)) {
|
|
// this Miss Factor applies only to the specified IFF
|
|
for(idx=0; idx<NUM_SKILL_LEVELS; idx++) {
|
|
if(!stuff_float_optional(&wip->b_info.beam_iff_miss_factor[iff][idx])) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// beam fire sound
|
|
parse_sound("+BeamSound:", &wip->b_info.beam_loop_sound, wip->name);
|
|
|
|
// warmup sound
|
|
parse_sound("+WarmupSound:", &wip->b_info.beam_warmup_sound, wip->name);
|
|
|
|
// warmdown sound
|
|
parse_sound("+WarmdownSound:", &wip->b_info.beam_warmdown_sound, wip->name);
|
|
|
|
// glow bitmap
|
|
if (optional_string("+Muzzleglow:") ) {
|
|
stuff_string(fname, F_NAME, NAME_LENGTH);
|
|
generic_anim_init(&wip->b_info.beam_glow, fname);
|
|
}
|
|
|
|
// # of shots (only used for type D beams)
|
|
if(optional_string("+Shots:")) {
|
|
stuff_int(&wip->b_info.beam_shots);
|
|
}
|
|
|
|
// make sure that we have at least one shot so that TYPE_D beams will work
|
|
if ( (wip->b_info.beam_type == BEAM_TYPE_D) && (wip->b_info.beam_shots < 1) ) {
|
|
Warning( LOCATION, "Type D beam weapon, '%s', has less than one \"+Shots\" specified! It must be set to at least 1!!", wip->name);
|
|
wip->b_info.beam_shots = 1;
|
|
}
|
|
|
|
// shrinkage
|
|
if(optional_string("+ShrinkFactor:")) {
|
|
stuff_float(&wip->b_info.beam_shrink_factor);
|
|
}
|
|
|
|
if(optional_string("+ShrinkPct:")) {
|
|
stuff_float(&wip->b_info.beam_shrink_pct);
|
|
}
|
|
|
|
if (optional_string("+Range:")) {
|
|
stuff_float(&wip->b_info.range);
|
|
}
|
|
|
|
if ( optional_string("+Attenuation:") )
|
|
stuff_float(&wip->b_info.damage_threshold);
|
|
|
|
if ( optional_string("+BeamWidth:") )
|
|
stuff_float(&wip->b_info.beam_width);
|
|
|
|
if ( optional_string("+Beam Flash Effect:") ) {
|
|
stuff_string(fname, F_NAME, NAME_LENGTH);
|
|
|
|
if ( VALID_FNAME(fname) )
|
|
wip->flash_impact_weapon_expl_index = Weapon_explosions.Load(fname);
|
|
}
|
|
|
|
if ( optional_string("+Beam Flash Radius:") )
|
|
stuff_float(&wip->flash_impact_explosion_radius);
|
|
|
|
if ( optional_string("+Beam Piercing Effect:") ) {
|
|
stuff_string(fname, F_NAME, NAME_LENGTH);
|
|
|
|
if ( VALID_FNAME(fname) )
|
|
wip->piercing_impact_weapon_expl_index = Weapon_explosions.Load(fname);
|
|
}
|
|
|
|
if ( optional_string("+Beam Piercing Radius:") )
|
|
stuff_float(&wip->piercing_impact_explosion_radius);
|
|
|
|
if ( optional_string("+Beam Piercing Effect Velocity:") )
|
|
stuff_float(&wip->piercing_impact_particle_velocity);
|
|
|
|
if ( optional_string("+Beam Piercing Splash Effect Velocity:") )
|
|
stuff_float(&wip->piercing_impact_particle_back_velocity);
|
|
|
|
if ( optional_string("+Beam Piercing Effect Variance:") )
|
|
stuff_float(&wip->piercing_impact_particle_variance);
|
|
|
|
// beam sections
|
|
while ( optional_string("$Section:") ) {
|
|
beam_weapon_section_info *bsip = NULL, tbsw;
|
|
bool nocreate = false, remove = false;
|
|
int bsw_index_override = -1;
|
|
|
|
if ( optional_string("+Index:") ) {
|
|
stuff_int(&bsw_index_override);
|
|
|
|
if ( optional_string("+remove") ) {
|
|
nocreate = true;
|
|
remove = true;
|
|
}
|
|
|
|
if ( (bsw_index_override < 0) || (!remove && (bsw_index_override >= wip->b_info.beam_num_sections)) )
|
|
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);
|
|
}
|
|
|
|
if ( optional_string("+nocreate") )
|
|
nocreate = true;
|
|
|
|
// Where are we saving data?
|
|
if (bsw_index_override >= 0) {
|
|
if (bsw_index_override < wip->b_info.beam_num_sections) {
|
|
bsip = &wip->b_info.sections[bsw_index_override];
|
|
} else {
|
|
if ( !nocreate ) {
|
|
if ( (bsw_index_override == wip->b_info.beam_num_sections) && (bsw_index_override < MAX_BEAM_SECTIONS) ) {
|
|
bsip = &wip->b_info.sections[wip->b_info.beam_num_sections++];
|
|
} else {
|
|
if ( !remove )
|
|
Warning(LOCATION, "Invalid index for manually-indexed beam section %d (max %d) on weapon %s.", bsw_index_override, MAX_BEAM_SECTIONS, wip->name);
|
|
|
|
bsip = &tbsw;
|
|
memset( bsip, 0, sizeof(beam_weapon_section_info) );
|
|
generic_anim_init(&bsip->texture, NULL);
|
|
}
|
|
} else {
|
|
if ( !remove )
|
|
Warning(LOCATION, "Invalid index for manually-indexed beam section %d, and +nocreate specified, on weapon %s", bsw_index_override, wip->name);
|
|
|
|
bsip = &tbsw;
|
|
memset( bsip, 0, sizeof(beam_weapon_section_info) );
|
|
generic_anim_init(&bsip->texture, NULL);
|
|
}
|
|
|
|
}
|
|
} else {
|
|
if (wip->b_info.beam_num_sections < MAX_BEAM_SECTIONS) {
|
|
bsip = &wip->b_info.sections[wip->b_info.beam_num_sections++];
|
|
generic_anim_init(&bsip->texture, NULL);
|
|
} else {
|
|
Warning(LOCATION, "Too many beam sections for weapon %s - max is %d", wip->name, MAX_BEAM_SECTIONS);
|
|
bsip = &tbsw;
|
|
memset( bsip, 0, sizeof(beam_weapon_section_info) );
|
|
generic_anim_init(&bsip->texture, NULL);
|
|
}
|
|
}
|
|
|
|
// section width
|
|
if ( optional_string("+Width:") )
|
|
stuff_float(&bsip->width);
|
|
|
|
// texture
|
|
if ( optional_string("+Texture:") ) {
|
|
stuff_string(fname, F_NAME, NAME_LENGTH);
|
|
|
|
// invisible textures are okay
|
|
if (!stricmp(fname, "invisible")) {
|
|
generic_anim_init(&bsip->texture);
|
|
} else {
|
|
generic_anim_init(&bsip->texture, fname);
|
|
}
|
|
}
|
|
|
|
// The E -- Dummied out due to not being used anywhere
|
|
if ( optional_string("+RGBA Inner:") ) {
|
|
ubyte dummy;
|
|
stuff_ubyte(&dummy);
|
|
stuff_ubyte(&dummy);
|
|
stuff_ubyte(&dummy);
|
|
stuff_ubyte(&dummy);
|
|
}
|
|
|
|
// The E -- Dummied out due to not being used anywhere
|
|
if ( optional_string("+RGBA Outer:") ) {
|
|
ubyte dummy;
|
|
stuff_ubyte(&dummy);
|
|
stuff_ubyte(&dummy);
|
|
stuff_ubyte(&dummy);
|
|
stuff_ubyte(&dummy);
|
|
}
|
|
|
|
// flicker
|
|
if ( optional_string("+Flicker:") ) {
|
|
stuff_float(&bsip->flicker);
|
|
//Sanity
|
|
if (bsip->flicker < 0.0f || bsip->flicker > 1.0f) {
|
|
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));
|
|
CLAMP(bsip->flicker, 0.0f, 1.0f);
|
|
}
|
|
}
|
|
|
|
// zadd
|
|
if ( optional_string("+Zadd:") )
|
|
stuff_float(&bsip->z_add);
|
|
|
|
// beam texture tileing factor -Bobboau
|
|
if ( optional_string("+Tile Factor:") ) {
|
|
stuff_float(&bsip->tile_factor);
|
|
stuff_int(&bsip->tile_type);
|
|
}
|
|
|
|
// beam texture moveing stuff -Bobboau
|
|
if ( optional_string("+Translation:") )
|
|
stuff_float(&bsip->translation);
|
|
|
|
// if we are actually removing this index then reset it and we'll
|
|
// clean up the entries later
|
|
if (remove) {
|
|
memset( bsip, 0, sizeof(beam_weapon_section_info) );
|
|
generic_anim_init(&bsip->texture, NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
while ( optional_string("$Pspew:") ) {
|
|
int spew_index = -1;
|
|
// check for pspew flag
|
|
if (!( wip->wi_flags & WIF_PARTICLE_SPEW )) {
|
|
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);
|
|
wip->wi_flags |= WIF_PARTICLE_SPEW;
|
|
}
|
|
// index for xmt edit, replace and remove support
|
|
if (optional_string("+Index:")) {
|
|
stuff_int(&spew_index);
|
|
if (spew_index < 0 || spew_index >= MAX_PARTICLE_SPEWERS) {
|
|
Warning(LOCATION, "+Index in particle spewer out of range. It must be between 0 and %i. Tag will be ignored.", MAX_PARTICLE_SPEWERS);
|
|
spew_index = -1;
|
|
}
|
|
}
|
|
// check for remove flag
|
|
if (optional_string("+Remove")) {
|
|
if (spew_index < 0) {
|
|
Warning(LOCATION, "+Index not specified or is out of range, can not remove spewer.");
|
|
} else { // restore defaults
|
|
wip->particle_spewers[spew_index].particle_spew_type = PSPEW_NONE;
|
|
wip->particle_spewers[spew_index].particle_spew_count = 1;
|
|
wip->particle_spewers[spew_index].particle_spew_time = 25;
|
|
wip->particle_spewers[spew_index].particle_spew_vel = 0.4f;
|
|
wip->particle_spewers[spew_index].particle_spew_radius = 2.0f;
|
|
wip->particle_spewers[spew_index].particle_spew_lifetime = 0.15f;
|
|
wip->particle_spewers[spew_index].particle_spew_scale = 0.8f;
|
|
wip->particle_spewers[spew_index].particle_spew_z_scale = 1.0f;
|
|
wip->particle_spewers[spew_index].particle_spew_rotation_rate = 10.0f;
|
|
wip->particle_spewers[spew_index].particle_spew_offset = vmd_zero_vector;
|
|
wip->particle_spewers[spew_index].particle_spew_velocity = vmd_zero_vector;
|
|
generic_anim_init(&wip->particle_spewers[spew_index].particle_spew_anim, NULL);
|
|
}
|
|
} else { // were not removing the spewer
|
|
if (spew_index < 0) { // index us ether not used or is invalid, so figure out where to put things
|
|
//find a free slot in the pspew info array
|
|
for (size_t s = 0; s < MAX_PARTICLE_SPEWERS; s++) {
|
|
if (wip->particle_spewers[s].particle_spew_type == PSPEW_NONE) {
|
|
spew_index = s;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// no empty spot found, the modder tried to define too many spewers, or screwed up the xmts, or my code sucks
|
|
if ( spew_index < 0 ) {
|
|
Warning(LOCATION, "Too many particle spewers, max number of spewers is %i.", MAX_PARTICLE_SPEWERS);
|
|
} else { // we have a valid index, now parse the spewer already
|
|
if (optional_string("+Type:")) { // added type field for pspew types, 0 is the default for reverse compatability -nuke
|
|
char temp_pspew_type[NAME_LENGTH];
|
|
stuff_string(temp_pspew_type, F_NAME, NAME_LENGTH);
|
|
|
|
if (!stricmp(temp_pspew_type, NOX("DEFAULT"))) {
|
|
wip->particle_spewers[spew_index].particle_spew_type = PSPEW_DEFAULT;
|
|
} else if (!stricmp(temp_pspew_type, NOX("HELIX"))) {
|
|
wip->particle_spewers[spew_index].particle_spew_type = PSPEW_HELIX;
|
|
} else if (!stricmp(temp_pspew_type, NOX("SPARKLER"))) { // new types can be added here
|
|
wip->particle_spewers[spew_index].particle_spew_type = PSPEW_SPARKLER;
|
|
} else if (!stricmp(temp_pspew_type, NOX("RING"))) {
|
|
wip->particle_spewers[spew_index].particle_spew_type = PSPEW_RING;
|
|
} else if (!stricmp(temp_pspew_type, NOX("PLUME"))) {
|
|
wip->particle_spewers[spew_index].particle_spew_type = PSPEW_PLUME;
|
|
} else {
|
|
wip->particle_spewers[spew_index].particle_spew_type = PSPEW_DEFAULT;
|
|
}
|
|
// for compatibility with existing tables that don't have a type tag
|
|
} 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
|
|
wip->particle_spewers[spew_index].particle_spew_type = PSPEW_DEFAULT;
|
|
}
|
|
|
|
if (optional_string("+Count:")) {
|
|
stuff_int(&wip->particle_spewers[spew_index].particle_spew_count);
|
|
}
|
|
|
|
if (optional_string("+Time:")) {
|
|
stuff_int(&wip->particle_spewers[spew_index].particle_spew_time);
|
|
}
|
|
|
|
if (optional_string("+Vel:")) {
|
|
stuff_float(&wip->particle_spewers[spew_index].particle_spew_vel);
|
|
}
|
|
|
|
if (optional_string("+Radius:")) {
|
|
stuff_float(&wip->particle_spewers[spew_index].particle_spew_radius);
|
|
}
|
|
|
|
if (optional_string("+Life:")) {
|
|
stuff_float(&wip->particle_spewers[spew_index].particle_spew_lifetime);
|
|
}
|
|
|
|
if (optional_string("+Scale:")) {
|
|
stuff_float(&wip->particle_spewers[spew_index].particle_spew_scale);
|
|
}
|
|
|
|
if (optional_string("+Z Scale:")) {
|
|
stuff_float(&wip->particle_spewers[spew_index].particle_spew_z_scale);
|
|
}
|
|
|
|
if (optional_string("+Rotation Rate:")) {
|
|
stuff_float(&wip->particle_spewers[spew_index].particle_spew_rotation_rate);
|
|
}
|
|
|
|
if (optional_string("+Offset:")) {
|
|
stuff_vec3d(&wip->particle_spewers[spew_index].particle_spew_offset);
|
|
}
|
|
|
|
if (optional_string("+Initial Velocity:")) {
|
|
stuff_vec3d(&wip->particle_spewers[spew_index].particle_spew_velocity);
|
|
}
|
|
|
|
if (optional_string("+Bitmap:")) {
|
|
stuff_string(fname, F_NAME, MAX_FILENAME_LEN);
|
|
generic_anim_init(&wip->particle_spewers[spew_index].particle_spew_anim, fname);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// check to see if the pspew flag was enabled but no pspew tags were given, for compatability with retail tables
|
|
if (wip->wi_flags & WIF_PARTICLE_SPEW) {
|
|
bool nospew = true;
|
|
for (size_t s = 0; s < MAX_PARTICLE_SPEWERS; s++)
|
|
if (wip->particle_spewers[s].particle_spew_type != PSPEW_NONE) {
|
|
nospew = false;
|
|
}
|
|
if (nospew) { // set first spewer to default
|
|
wip->particle_spewers[0].particle_spew_type = PSPEW_DEFAULT;
|
|
}
|
|
}
|
|
|
|
// tag weapon optional stuff
|
|
if( optional_string("$Tag:")){
|
|
stuff_int(&wip->tag_level);
|
|
stuff_float(&wip->tag_time);
|
|
wip->wi_flags |= WIF_TAG;
|
|
}
|
|
|
|
if( optional_string("$SSM:")){
|
|
stuff_int(&wip->SSM_index);
|
|
}// SSM index -Bobboau
|
|
|
|
if(optional_string("$FOF:")){
|
|
stuff_float(&wip->field_of_fire);
|
|
|
|
if(optional_string("+FOF Spread Rate:")){
|
|
stuff_float(&wip->fof_spread_rate);
|
|
if(required_string("+FOF Reset Rate:")){
|
|
stuff_float(&wip->fof_reset_rate);
|
|
}
|
|
|
|
if(required_string("+Max FOF:")){
|
|
float max_fof;
|
|
stuff_float(&max_fof);
|
|
wip->max_fof_spread = max_fof - wip->field_of_fire;
|
|
|
|
if (wip->max_fof_spread <= 0.0f) {
|
|
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);
|
|
wip->max_fof_spread = 0.0f;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if( optional_string("$Shots:")){
|
|
stuff_int(&wip->shots);
|
|
}
|
|
|
|
//Left in for compatibility
|
|
if ( optional_string("$decal:") ) {
|
|
mprintf(("WARNING: The decal system has been deactivated in FSO builds. Entries for weapon %s will be discarded.\n", wip->name));
|
|
required_string("+texture:");
|
|
stuff_string(fname, F_NAME, NAME_LENGTH);
|
|
|
|
if ( optional_string("+backface texture:") ) {
|
|
stuff_string(fname, F_NAME, NAME_LENGTH);
|
|
}
|
|
|
|
float bogus;
|
|
|
|
required_string("+radius:");
|
|
stuff_float(&bogus);
|
|
|
|
if ( optional_string("+burn time:") ) {
|
|
stuff_float(&bogus);
|
|
}
|
|
}
|
|
|
|
|
|
if (optional_string("$Transparent:")) {
|
|
wip->wi_flags2 |= WIF2_TRANSPARENT;
|
|
|
|
required_string("+Alpha:");
|
|
stuff_float(&wip->alpha_max);
|
|
|
|
if (wip->alpha_max > 1.0f)
|
|
wip->alpha_max = 1.0f;
|
|
|
|
if (wip->alpha_max <= 0.0f) {
|
|
Warning(LOCATION, "WARNING: Alpha is set to 0 or a negative value for '%s'! Defaulting to 1.0!", wip->name);
|
|
}
|
|
|
|
if (optional_string("+Alpha Min:")) {
|
|
stuff_float(&wip->alpha_min);
|
|
CLAMP(wip->alpha_min, 0.0f, 1.0f);
|
|
}
|
|
|
|
if (optional_string("+Alpha Cycle:")) {
|
|
stuff_float(&wip->alpha_cycle);
|
|
|
|
if (wip->alpha_max == wip->alpha_min)
|
|
Warning(LOCATION, "WARNING: Alpha is set to cycle for '%s', but max and min values are the same!", wip->name);
|
|
}
|
|
}
|
|
|
|
if (optional_string("$Weapon Hitpoints:")) {
|
|
stuff_int(&wip->weapon_hitpoints);
|
|
} else if (first_time && (wip->wi_flags3 & (WIF3_TURRET_INTERCEPTABLE | WIF3_FIGHTER_INTERCEPTABLE))) {
|
|
wip->weapon_hitpoints = 25;
|
|
}
|
|
|
|
// making sure bombs get their hitpoints assigned
|
|
if ((wip->wi_flags & WIF_BOMB) && (wip->weapon_hitpoints == 0)) {
|
|
wip->weapon_hitpoints = 50;
|
|
}
|
|
|
|
if(optional_string("$Armor Type:")) {
|
|
stuff_string(buf, F_NAME, WEAPONS_MULTITEXT_LENGTH);
|
|
wip->armor_type_idx = armor_type_get_idx(buf);
|
|
|
|
if(wip->armor_type_idx == -1)
|
|
Warning(LOCATION,"Invalid armor name %s specified for weapon %s", buf, wip->name);
|
|
}
|
|
|
|
if (optional_string("$Burst Shots:")) {
|
|
stuff_int(&wip->burst_shots);
|
|
if (wip->burst_shots > 0)
|
|
wip->burst_shots--;
|
|
}
|
|
|
|
if (optional_string("$Burst Delay:")) {
|
|
int temp;
|
|
stuff_int(&temp);
|
|
if (temp > 0) {
|
|
wip->burst_delay = ((float) temp) / 1000.0f;
|
|
}
|
|
}
|
|
|
|
if (optional_string("$Burst Flags:")) {
|
|
parse_string_flag_list((int*)&wip->burst_flags, Burst_fire_flags, Num_burst_fire_flags);
|
|
}
|
|
|
|
if (optional_string("$Thruster Flame Effect:")) {
|
|
stuff_string(fname, F_NAME, NAME_LENGTH);
|
|
|
|
if (VALID_FNAME(fname))
|
|
generic_anim_init( &wip->thruster_flame, fname );
|
|
}
|
|
|
|
if (optional_string("$Thruster Glow Effect:")) {
|
|
stuff_string(fname, F_NAME, NAME_LENGTH);
|
|
|
|
if (VALID_FNAME(fname))
|
|
generic_anim_init( &wip->thruster_glow, fname );
|
|
}
|
|
|
|
if (optional_string("$Thruster Glow Radius Factor:")) {
|
|
stuff_float(&wip->thruster_glow_factor);
|
|
}
|
|
|
|
//pretty stupid if a target must be tagged to shoot tag missiles at it
|
|
if ((wip->wi_flags & WIF_TAG) && (wip->wi_flags2 & WIF2_TAGGED_ONLY))
|
|
{
|
|
Warning(LOCATION, "%s is a tag missile, but the target must be tagged to shoot it", wip->name);
|
|
}
|
|
|
|
// if burst delay is longer than firewait skip the whole burst fire option
|
|
if (wip->burst_delay >= wip->fire_wait)
|
|
wip->burst_shots = 0;
|
|
|
|
/* Generate a substitution pattern for this weapon.
|
|
This pattern is very naive such that it calculates the lowest common denominator as being all of
|
|
the periods multiplied together.
|
|
*/
|
|
while ( optional_string("$substitute:") ) {
|
|
char subname[NAME_LENGTH];
|
|
int period = 0;
|
|
int index = 0;
|
|
int offset = 0;
|
|
stuff_string(subname, F_NAME, NAME_LENGTH);
|
|
if ( optional_string("+period:") ) {
|
|
stuff_int(&period);
|
|
if ( period <= 0 ) {
|
|
Warning(LOCATION, "Substitution '%s' for weapon '%s' requires a period greater than 0. Setting period to 1.", subname, wip->name);
|
|
period = 1;
|
|
}
|
|
if ( optional_string("+offset:") ) {
|
|
stuff_int(&offset);
|
|
if ( offset <= 0 ) {
|
|
Warning(LOCATION, "Period offset for substitution '%s' of weapon '%s' has to be greater than 0. Setting offset to 1.", subname, wip->name);
|
|
offset = 1;
|
|
}
|
|
}
|
|
} else if ( optional_string("+index:") ) {
|
|
stuff_int(&index);
|
|
if ( index < 0 ) {
|
|
Warning(LOCATION, "Substitution '%s' for weapon '%s' requires an index greater than 0. Setting index to 0.", subname, wip->name);
|
|
index = 0;
|
|
}
|
|
}
|
|
|
|
// we are going to use weapon substition so, make sure that the pattern array has at least one element
|
|
if ( wip->num_substitution_patterns == 0 ) {
|
|
// pattern is empty, initialize pattern with the weapon being currently parsed.
|
|
strcpy_s(wip->weapon_substitution_pattern_names[0], wip->name);
|
|
wip->num_substitution_patterns++;
|
|
}
|
|
|
|
// if tbler specifies a period then determine if we can fit the resulting pattern
|
|
// neatly into the pattern array.
|
|
if ( period > 0 ) {
|
|
if ( (wip->num_substitution_patterns % period) > 0 ) {
|
|
// not neat, need to expand the pattern so that our frequency pattern fits completly.
|
|
size_t current_size = wip->num_substitution_patterns;
|
|
size_t desired_size = current_size*period;
|
|
if (desired_size > MAX_SUBSTITUTION_PATTERNS) {
|
|
Warning(LOCATION, "The period is too large for the number of substitution patterns! desired size=%d, max size=%d", desired_size, MAX_SUBSTITUTION_PATTERNS);
|
|
}
|
|
else {
|
|
wip->num_substitution_patterns = desired_size;
|
|
|
|
// now duplicate the current pattern into the new area so the current pattern holds
|
|
for ( size_t i = current_size; i < desired_size; i++ ) {
|
|
strcpy_s(wip->weapon_substitution_pattern_names[i], wip->weapon_substitution_pattern_names[i%current_size]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Apply the substituted weapon at the requested period, barrel
|
|
shifted by offset if needed.*/
|
|
for ( size_t pos = (period + offset - 1) % period;
|
|
pos < wip->num_substitution_patterns; pos += period )
|
|
{
|
|
strcpy_s(wip->weapon_substitution_pattern_names[pos], subname);
|
|
}
|
|
} else {
|
|
// assume that tbler wanted to specify a index for the new weapon.
|
|
|
|
// make sure that there is enough room
|
|
if (index >= MAX_SUBSTITUTION_PATTERNS) {
|
|
Warning(LOCATION, "Substitution pattern index exceeds the maximum size! Index=%d, max size=%d", index, MAX_SUBSTITUTION_PATTERNS);
|
|
} else {
|
|
if ( (size_t)index >= wip->num_substitution_patterns ) {
|
|
// need to make the pattern bigger by filling the extra with the current weapon.
|
|
for ( size_t i = wip->num_substitution_patterns; i < (size_t)index; i++ ) {
|
|
strcpy_s(wip->weapon_substitution_pattern_names[i], subname);
|
|
}
|
|
wip->num_substitution_patterns = index+1;
|
|
}
|
|
|
|
strcpy_s(wip->weapon_substitution_pattern_names[index], subname);
|
|
}
|
|
}
|
|
}
|
|
|
|
//Optional score for destroying this weapon.
|
|
if (optional_string("$Score:")) {
|
|
stuff_int(&wip->score);
|
|
}
|
|
|
|
return WEAPON_INFO_INDEX(wip);
|
|
}
|
|
|
|
/**
|
|
* For all weapons that spawn weapons, given an index at weaponp->spawn_type,
|
|
* convert the strings in Spawn_names to indices in the Weapon_types array.
|
|
*/
|
|
void translate_spawn_types()
|
|
{
|
|
int i, j, k;
|
|
|
|
for (i = 0; i < Num_weapon_types; i++)
|
|
{
|
|
for (j = 0; j < Weapon_info[i].num_spawn_weapons_defined; j++)
|
|
{
|
|
if ( (Weapon_info[i].spawn_info[j].spawn_type > -1) && (Weapon_info[i].spawn_info[j].spawn_type < Num_spawn_types) )
|
|
{
|
|
int spawn_type = Weapon_info[i].spawn_info[j].spawn_type;
|
|
|
|
Assert( spawn_type < Num_spawn_types );
|
|
|
|
for (k = 0; k < Num_weapon_types; k++)
|
|
{
|
|
if ( !stricmp(Spawn_names[spawn_type], Weapon_info[k].name) )
|
|
{
|
|
Weapon_info[i].spawn_info[j].spawn_type = (short)k;
|
|
|
|
if (i == k)
|
|
Warning(LOCATION, "Weapon %s spawns itself. Infinite recursion?\n", Weapon_info[i].name);
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static char Default_cmeasure_name[NAME_LENGTH] = "";
|
|
|
|
void parse_weaponstbl(const char *filename)
|
|
{
|
|
try
|
|
{
|
|
read_file_text(filename, CF_TYPE_TABLES);
|
|
reset_parse();
|
|
|
|
if (optional_string("#Primary Weapons"))
|
|
{
|
|
while (required_string_either("#End", "$Name:")) {
|
|
// AL 28-3-98: If parse_weapon() fails, try next .tbl weapon
|
|
if (parse_weapon(WP_LASER, Parsing_modular_table) < 0) {
|
|
continue;
|
|
}
|
|
}
|
|
required_string("#End");
|
|
}
|
|
|
|
if (optional_string("#Secondary Weapons"))
|
|
{
|
|
while (required_string_either("#End", "$Name:")) {
|
|
// AL 28-3-98: If parse_weapon() fails, try next .tbl weapon
|
|
if (parse_weapon(WP_MISSILE, Parsing_modular_table) < 0) {
|
|
continue;
|
|
}
|
|
}
|
|
required_string("#End");
|
|
}
|
|
|
|
if (optional_string("#Beam Weapons"))
|
|
{
|
|
while (required_string_either("#End", "$Name:")) {
|
|
// AL 28-3-98: If parse_weapon() fails, try next .tbl weapon
|
|
if (parse_weapon(WP_BEAM, Parsing_modular_table) < 0) {
|
|
continue;
|
|
}
|
|
}
|
|
required_string("#End");
|
|
}
|
|
|
|
if (optional_string("#Countermeasures"))
|
|
{
|
|
while (required_string_either("#End", "$Name:"))
|
|
{
|
|
int idx = parse_weapon(WP_MISSILE, Parsing_modular_table);
|
|
|
|
if (idx < 0) {
|
|
continue;
|
|
}
|
|
|
|
//Make sure cmeasure flag is set
|
|
Weapon_info[idx].wi_flags |= WIF_CMEASURE;
|
|
|
|
//Set cmeasure index
|
|
if (!strlen(Default_cmeasure_name)) {
|
|
//We can't be sure that index will be the same after sorting, so save the name
|
|
strcpy_s(Default_cmeasure_name, Weapon_info[idx].name);
|
|
}
|
|
}
|
|
|
|
required_string("#End");
|
|
}
|
|
|
|
// Read in a list of weapon_info indicies that are an ordering of the player weapon precedence.
|
|
// This list is used to select an alternate weapon when a particular weapon is not available
|
|
// during weapon selection.
|
|
if ((!Parsing_modular_table && required_string("$Player Weapon Precedence:")) || optional_string("$Player Weapon Precedence:"))
|
|
{
|
|
Num_player_weapon_precedence = stuff_int_list(Player_weapon_precedence, MAX_WEAPON_TYPES, WEAPON_LIST_TYPE);
|
|
}
|
|
|
|
// add tbl/tbm to multiplayer validation list
|
|
fs2netd_add_table_validation(filename);
|
|
}
|
|
catch (const parse::ParseException& e)
|
|
{
|
|
mprintf(("TABLES: Unable to parse '%s'! Error message = %s.\n", filename, e.what()));
|
|
return;
|
|
}
|
|
}
|
|
|
|
//uses a simple bucket sort to sort weapons, order of importance is:
|
|
//Lasers
|
|
//Beams
|
|
//Child primary weapons
|
|
//Fighter missiles and bombs
|
|
//Capital missiles and bombs
|
|
//Child secondary weapons
|
|
void weapon_sort_by_type()
|
|
{
|
|
weapon_info *lasers = NULL, *big_lasers = NULL, *beams = NULL, *missiles = NULL, *big_missiles = NULL, *child_primaries = NULL, *child_secondaries = NULL;
|
|
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;
|
|
int i, weapon_index;
|
|
|
|
// get the initial count of each weapon type
|
|
for (i = 0; i < MAX_WEAPON_TYPES; i++) {
|
|
switch (Weapon_info[i].subtype)
|
|
{
|
|
case WP_UNUSED:
|
|
continue;
|
|
|
|
case WP_LASER:
|
|
if (Weapon_info[i].wi_flags & WIF_CHILD)
|
|
num_child_primaries++;
|
|
else if (Weapon_info[i].wi_flags & WIF_BIG_ONLY)
|
|
num_big_lasers++;
|
|
else
|
|
num_lasers++;
|
|
break;
|
|
|
|
case WP_BEAM:
|
|
num_beams++;
|
|
break;
|
|
|
|
case WP_MISSILE:
|
|
if (Weapon_info[i].wi_flags & WIF_CHILD)
|
|
num_child_secondaries++;
|
|
else if (Weapon_info[i].wi_flags & WIF_BIG_ONLY)
|
|
num_big_missiles++;
|
|
else
|
|
num_missiles++;
|
|
break;
|
|
|
|
default:
|
|
continue;
|
|
}
|
|
|
|
}
|
|
|
|
// allocate the buckets
|
|
if (num_lasers) {
|
|
lasers = new weapon_info[num_lasers];
|
|
Verify( lasers != NULL );
|
|
num_lasers = 0;
|
|
}
|
|
|
|
if (num_big_lasers) {
|
|
big_lasers = new weapon_info[num_big_lasers];
|
|
Verify( big_lasers != NULL );
|
|
num_big_lasers = 0;
|
|
}
|
|
|
|
if (num_beams) {
|
|
beams = new weapon_info[num_beams];
|
|
Verify( beams != NULL );
|
|
num_beams = 0;
|
|
}
|
|
|
|
if (num_missiles) {
|
|
missiles = new weapon_info[num_missiles];
|
|
Verify( missiles != NULL );
|
|
num_missiles = 0;
|
|
}
|
|
|
|
if (num_big_missiles) {
|
|
big_missiles = new weapon_info[num_big_missiles];
|
|
Verify( big_missiles != NULL );
|
|
num_big_missiles = 0;
|
|
}
|
|
|
|
if (num_child_primaries) {
|
|
child_primaries = new weapon_info[num_child_primaries];
|
|
Verify( child_primaries != NULL );
|
|
num_child_primaries = 0;
|
|
}
|
|
|
|
if (num_child_secondaries) {
|
|
child_secondaries = new weapon_info[num_child_secondaries];
|
|
Verify( child_secondaries != NULL );
|
|
num_child_secondaries = 0;
|
|
}
|
|
|
|
// fill the buckets
|
|
for (i = 0; i < MAX_WEAPON_TYPES; i++) {
|
|
switch (Weapon_info[i].subtype)
|
|
{
|
|
case WP_UNUSED:
|
|
continue;
|
|
|
|
case WP_LASER:
|
|
if (Weapon_info[i].wi_flags & WIF_CHILD)
|
|
child_primaries[num_child_primaries++] = Weapon_info[i];
|
|
else if (Weapon_info[i].wi_flags & WIF_BIG_ONLY)
|
|
big_lasers[num_big_lasers++] = Weapon_info[i];
|
|
else
|
|
lasers[num_lasers++] = Weapon_info[i];
|
|
break;
|
|
|
|
case WP_BEAM:
|
|
beams[num_beams++] = Weapon_info[i];
|
|
break;
|
|
|
|
case WP_MISSILE:
|
|
if (Weapon_info[i].wi_flags & WIF_CHILD)
|
|
child_secondaries[num_child_secondaries++] = Weapon_info[i];
|
|
else if (Weapon_info[i].wi_flags & WIF_BIG_ONLY)
|
|
big_missiles[num_big_missiles++] = Weapon_info[i];
|
|
else
|
|
missiles[num_missiles++]=Weapon_info[i];
|
|
break;
|
|
|
|
default:
|
|
continue;
|
|
}
|
|
}
|
|
|
|
weapon_index = 0;
|
|
|
|
// reorder the weapon_info structure according to our rules defined above
|
|
for (i = 0; i < num_lasers; i++, weapon_index++)
|
|
Weapon_info[weapon_index] = lasers[i];
|
|
|
|
for (i = 0; i < num_big_lasers; i++, weapon_index++)
|
|
Weapon_info[weapon_index] = big_lasers[i];
|
|
|
|
for (i = 0; i < num_beams; i++, weapon_index++)
|
|
Weapon_info[weapon_index] = beams[i];
|
|
|
|
for (i = 0; i < num_child_primaries; i++, weapon_index++)
|
|
Weapon_info[weapon_index] = child_primaries[i];
|
|
|
|
// designate start of secondary weapons so that we'll have the correct offset later on
|
|
First_secondary_index = weapon_index;
|
|
|
|
for (i = 0; i < num_missiles; i++, weapon_index++)
|
|
Weapon_info[weapon_index] = missiles[i];
|
|
|
|
for (i = 0; i < num_big_missiles; i++, weapon_index++)
|
|
Weapon_info[weapon_index] = big_missiles[i];
|
|
|
|
for (i = 0; i < num_child_secondaries; i++, weapon_index++)
|
|
Weapon_info[weapon_index] = child_secondaries[i];
|
|
|
|
|
|
if (lasers) delete [] lasers;
|
|
if (big_lasers) delete [] big_lasers;
|
|
if (beams) delete [] beams;
|
|
if (missiles) delete [] missiles;
|
|
if (big_missiles) delete [] big_missiles;
|
|
if (child_primaries) delete [] child_primaries;
|
|
if (child_secondaries) delete [] child_secondaries;
|
|
}
|
|
|
|
/**
|
|
* Do any post-parse cleaning on weapon entries
|
|
*/
|
|
void weapon_clean_entries()
|
|
{
|
|
weapon_info *wip;
|
|
int i;
|
|
|
|
for (i = 0; i < Num_weapon_types; i++) {
|
|
wip = &Weapon_info[i];
|
|
|
|
if (wip->wi_flags & WIF_BEAM) {
|
|
// clean up any beam sections which may have been deleted
|
|
int removed = 0;
|
|
|
|
for (int s_idx = 0; s_idx < wip->b_info.beam_num_sections; s_idx++) {
|
|
if ( !strlen(wip->b_info.sections[s_idx].texture.filename) ) {
|
|
int new_idx = s_idx + 1;
|
|
|
|
while (new_idx < MAX_BEAM_SECTIONS) {
|
|
memcpy( &wip->b_info.sections[new_idx-1], &wip->b_info.sections[new_idx], sizeof(beam_weapon_section_info) );
|
|
new_idx++;
|
|
}
|
|
|
|
removed++;
|
|
}
|
|
}
|
|
|
|
if (removed) {
|
|
mprintf(("NOTE: weapon-cleanup is removing %i stale beam sections, out of %i original, from '%s'.\n", removed, wip->b_info.beam_num_sections, wip->name));
|
|
wip->b_info.beam_num_sections -= removed;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void weapon_release_bitmaps()
|
|
{
|
|
int i, j;
|
|
weapon_info *wip;
|
|
|
|
// not for FRED...
|
|
if (Fred_running)
|
|
return;
|
|
|
|
// if we are just going to load them all again any, keep everything
|
|
if (Cmdline_load_all_weapons)
|
|
return;
|
|
|
|
for (i = 0; i < Num_weapon_types; i++) {
|
|
wip = &Weapon_info[i];
|
|
|
|
// go ahead and clear out models, the model paging code will actually take care of
|
|
// releasing this stuff if needed, but we have to keep track of current modelnums ourselves
|
|
if (wip->render_type == WRT_POF)
|
|
wip->model_num = -1;
|
|
|
|
// we are only interested in what we don't need for this mission
|
|
if ( used_weapons[i] )
|
|
continue;
|
|
|
|
if (wip->render_type == WRT_LASER) {
|
|
if (wip->laser_bitmap.first_frame >= 0) {
|
|
bm_release(wip->laser_bitmap.first_frame);
|
|
wip->laser_bitmap.first_frame = -1;
|
|
}
|
|
|
|
// now for the glow
|
|
if (wip->laser_glow_bitmap.first_frame >= 0) {
|
|
bm_release(wip->laser_glow_bitmap.first_frame);
|
|
wip->laser_glow_bitmap.first_frame = -1;
|
|
}
|
|
}
|
|
|
|
if (wip->wi_flags & WIF_BEAM) {
|
|
// particle animation
|
|
if (wip->b_info.beam_particle_ani.first_frame >= 0) {
|
|
bm_release(wip->b_info.beam_particle_ani.first_frame);
|
|
wip->b_info.beam_particle_ani.first_frame = -1;
|
|
}
|
|
|
|
// muzzle glow
|
|
if (wip->b_info.beam_glow.first_frame >= 0) {
|
|
bm_release(wip->b_info.beam_glow.first_frame);
|
|
wip->b_info.beam_glow.first_frame = -1;
|
|
}
|
|
|
|
// section textures
|
|
for (j = 0; j < wip->b_info.beam_num_sections; j++) {
|
|
beam_weapon_section_info *bsi = &wip->b_info.sections[j];
|
|
|
|
if (bsi->texture.first_frame >= 0) {
|
|
bm_release(bsi->texture.first_frame);
|
|
bsi->texture.first_frame = -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (wip->wi_flags & WIF_TRAIL) {
|
|
if (wip->tr_info.texture.bitmap_id >= 0) {
|
|
bm_release(wip->tr_info.texture.bitmap_id);
|
|
wip->tr_info.texture.bitmap_id = -1;
|
|
}
|
|
}
|
|
|
|
if (wip->wi_flags & WIF_PARTICLE_SPEW) { // tweaked for multiple particle spews -nuke
|
|
for (size_t s = 0; s < MAX_PARTICLE_SPEWERS; s++) { // just bitmaps that got loaded
|
|
if (wip->particle_spewers[s].particle_spew_type != PSPEW_NONE){
|
|
if (wip->particle_spewers[s].particle_spew_anim.first_frame >= 0) {
|
|
bm_release(wip->particle_spewers[s].particle_spew_anim.first_frame);
|
|
wip->particle_spewers[s].particle_spew_anim.first_frame = -1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (wip->thruster_flame.first_frame >= 0) {
|
|
bm_release(wip->thruster_flame.first_frame);
|
|
wip->thruster_flame.first_frame = -1;
|
|
}
|
|
|
|
if (wip->thruster_glow.first_frame >= 0) {
|
|
bm_release(wip->thruster_glow.first_frame);
|
|
wip->thruster_glow.first_frame = -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool weapon_is_used(int weapon_index)
|
|
{
|
|
Assert( (weapon_index >= 0) || (weapon_index < Num_weapon_types) );
|
|
return (used_weapons[weapon_index] > 0);
|
|
}
|
|
|
|
void weapon_load_bitmaps(int weapon_index)
|
|
{
|
|
weapon_info *wip;
|
|
|
|
// not for FRED...
|
|
if (Fred_running)
|
|
return;
|
|
|
|
if ( (weapon_index < 0) || (weapon_index >= Num_weapon_types) ) {
|
|
Int3();
|
|
return;
|
|
}
|
|
|
|
wip = &Weapon_info[weapon_index];
|
|
|
|
if ( (wip->render_type == WRT_LASER) && (wip->laser_bitmap.first_frame < 0) ) {
|
|
wip->laser_bitmap.first_frame = bm_load(wip->laser_bitmap.filename);
|
|
|
|
if (wip->laser_bitmap.first_frame >= 0) {
|
|
wip->laser_bitmap.num_frames = 1;
|
|
wip->laser_bitmap.total_time = 1;
|
|
}
|
|
// fall back to an animated type
|
|
else if ( generic_anim_load(&wip->laser_bitmap) ) {
|
|
mprintf(("Could not find a usable bitmap for '%s'!\n", wip->name));
|
|
Warning(LOCATION, "Could not find a usable bitmap (%s) for weapon '%s'!\n", wip->laser_bitmap.filename, wip->name);
|
|
}
|
|
|
|
// now see if we also have a glow
|
|
if ( strlen(wip->laser_glow_bitmap.filename) ) {
|
|
wip->laser_glow_bitmap.first_frame = bm_load(wip->laser_glow_bitmap.filename);
|
|
|
|
if (wip->laser_glow_bitmap.first_frame >= 0) {
|
|
wip->laser_glow_bitmap.num_frames = 1;
|
|
wip->laser_glow_bitmap.total_time = 1;
|
|
}
|
|
// fall back to an animated type
|
|
else if ( generic_anim_load(&wip->laser_glow_bitmap) ) {
|
|
mprintf(("Could not find a usable glow bitmap for '%s'!\n", wip->name));
|
|
Warning(LOCATION, "Could not find a usable glow bitmap (%s) for weapon '%s'!\n", wip->laser_glow_bitmap.filename, wip->name);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (wip->wi_flags & WIF_BEAM) {
|
|
// particle animation
|
|
if ( (wip->b_info.beam_particle_ani.first_frame < 0) && strlen(wip->b_info.beam_particle_ani.filename) )
|
|
generic_anim_load(&wip->b_info.beam_particle_ani);
|
|
|
|
// muzzle glow
|
|
if ( (wip->b_info.beam_glow.first_frame < 0) && strlen(wip->b_info.beam_glow.filename) ) {
|
|
if ( generic_anim_load(&wip->b_info.beam_glow) ) {
|
|
// animated version failed to load, try static instead
|
|
wip->b_info.beam_glow.first_frame = bm_load(wip->b_info.beam_glow.filename);
|
|
|
|
if (wip->b_info.beam_glow.first_frame >= 0) {
|
|
wip->b_info.beam_glow.num_frames = 1;
|
|
wip->b_info.beam_glow.total_time = 1;
|
|
} else {
|
|
mprintf(("Could not find a usable muzzle glow bitmap for '%s'!\n", wip->name));
|
|
Warning(LOCATION, "Could not find a usable muzzle glow bitmap (%s) for weapon '%s'!\n", wip->b_info.beam_glow.filename, wip->name);
|
|
}
|
|
}
|
|
}
|
|
|
|
// section textures
|
|
for (int i = 0; i < wip->b_info.beam_num_sections; i++) {
|
|
beam_weapon_section_info *bsi = &wip->b_info.sections[i];
|
|
|
|
if ( (bsi->texture.first_frame < 0) && strlen(bsi->texture.filename) ) {
|
|
if ( generic_anim_load(&bsi->texture) ) {
|
|
// animated version failed to load, try static instead
|
|
bsi->texture.first_frame = bm_load(bsi->texture.filename);
|
|
|
|
if (bsi->texture.first_frame >= 0) {
|
|
bsi->texture.num_frames = 1;
|
|
bsi->texture.total_time = 1;
|
|
} else {
|
|
mprintf(("Could not find a usable beam section (%i) bitmap for '%s'!\n", i, wip->name));
|
|
Warning(LOCATION, "Could not find a usable beam section (%i) bitmap (%s) for weapon '%s'!\n", i, bsi->texture.filename, wip->name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( (wip->wi_flags & WIF_TRAIL) && (wip->tr_info.texture.bitmap_id < 0) )
|
|
generic_bitmap_load(&wip->tr_info.texture);
|
|
|
|
//WMC - Don't try to load an anim if no anim is specified, Mmkay?
|
|
if (wip->wi_flags & WIF_PARTICLE_SPEW) {
|
|
for (size_t s = 0; s < MAX_PARTICLE_SPEWERS; s++) { // looperfied for multiple pspewers -nuke
|
|
if (wip->particle_spewers[s].particle_spew_type != PSPEW_NONE){
|
|
|
|
if ((wip->particle_spewers[s].particle_spew_anim.first_frame < 0)
|
|
&& (wip->particle_spewers[s].particle_spew_anim.filename[0] != '\0') ) {
|
|
|
|
wip->particle_spewers[s].particle_spew_anim.first_frame = bm_load(wip->particle_spewers[s].particle_spew_anim.filename);
|
|
|
|
if (wip->particle_spewers[s].particle_spew_anim.first_frame >= 0) {
|
|
wip->particle_spewers[s].particle_spew_anim.num_frames = 1;
|
|
wip->particle_spewers[s].particle_spew_anim.total_time = 1;
|
|
}
|
|
// fall back to an animated type
|
|
else if ( generic_anim_load(&wip->particle_spewers[s].particle_spew_anim) ) {
|
|
mprintf(("Could not find a usable particle spew bitmap for '%s'!\n", wip->name));
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// load alternate thruster textures
|
|
if (strlen(wip->thruster_flame.filename)) {
|
|
generic_anim_load(&wip->thruster_flame);
|
|
}
|
|
|
|
if (strlen(wip->thruster_glow.filename)) {
|
|
wip->thruster_glow.first_frame = bm_load(wip->thruster_glow.filename);
|
|
if (wip->thruster_glow.first_frame >= 0) {
|
|
wip->thruster_glow.num_frames = 1;
|
|
wip->thruster_glow.total_time = 1;
|
|
} else {
|
|
generic_anim_load(&wip->thruster_glow);
|
|
}
|
|
}
|
|
|
|
// if this weapon isn't already marked as used, then mark it as such now
|
|
// (this should really only happen if the player is cheating)
|
|
if ( !used_weapons[weapon_index] )
|
|
used_weapons[weapon_index]++;
|
|
}
|
|
|
|
/**
|
|
* Checks all of the weapon infos for substitution patterns and caches the weapon_index of any that it finds.
|
|
*/
|
|
void weapon_generate_indexes_for_substitution() {
|
|
for (int i = 0; i < MAX_WEAPON_TYPES; i++) {
|
|
weapon_info *wip = &(Weapon_info[i]);
|
|
|
|
if ( wip->num_substitution_patterns > 0 ) {
|
|
for ( size_t j = 0; j < wip->num_substitution_patterns; j++ ) {
|
|
int weapon_index = -1;
|
|
if ( stricmp("none", wip->weapon_substitution_pattern_names[j]) != 0 ) {
|
|
weapon_index = weapon_info_lookup(wip->weapon_substitution_pattern_names[j]);
|
|
if ( weapon_index == -1 ) { // invalid sub weapon
|
|
Warning(LOCATION, "Weapon '%s' requests substitution with '%s' which does not seem to exist",
|
|
wip->name, wip->weapon_substitution_pattern_names[j]);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
wip->weapon_substitution_pattern[j] = weapon_index;
|
|
}
|
|
|
|
memset(wip->weapon_substitution_pattern_names, 0, sizeof(char) * MAX_SUBSTITUTION_PATTERNS * NAME_LENGTH);
|
|
}
|
|
}
|
|
}
|
|
|
|
void weapon_do_post_parse()
|
|
{
|
|
weapon_info *wip;
|
|
int first_cmeasure_index = -1;
|
|
int i;
|
|
|
|
weapon_sort_by_type(); // NOTE: This has to be first thing!
|
|
weapon_clean_entries();
|
|
weapon_generate_indexes_for_substitution();
|
|
|
|
Default_cmeasure_index = -1;
|
|
|
|
// run through weapons list and deal with individual issues
|
|
for (i = 0; i < Num_weapon_types; i++) {
|
|
wip = &Weapon_info[i];
|
|
|
|
// set default counter-measure index from the saved name
|
|
if ( (Default_cmeasure_index < 0) && strlen(Default_cmeasure_name) ) {
|
|
if ( !stricmp(wip->name, Default_cmeasure_name) ) {
|
|
Default_cmeasure_index = i;
|
|
}
|
|
}
|
|
|
|
// catch a fall back cmeasure index, just in case
|
|
if ( (first_cmeasure_index < 0) && (wip->wi_flags & WIF_CMEASURE) )
|
|
first_cmeasure_index = i;
|
|
}
|
|
|
|
// catch cmeasure fallback
|
|
if (Default_cmeasure_index < 0)
|
|
Default_cmeasure_index = first_cmeasure_index;
|
|
|
|
// now we want to resolve the countermeasures by species
|
|
for (SCP_vector<species_info>::iterator ii = Species_info.begin(); ii != Species_info.end(); ++ii)
|
|
{
|
|
if (*ii->cmeasure_name)
|
|
{
|
|
int index = weapon_info_lookup(ii->cmeasure_name);
|
|
if (index < 0)
|
|
Warning(LOCATION, "Could not find weapon type '%s' to use as countermeasure on species '%s'", ii->cmeasure_name, ii->species_name);
|
|
else if (Weapon_info[index].wi_flags & WIF_BEAM)
|
|
Warning(LOCATION, "Attempt made to set a beam weapon as a countermeasure on species '%s'", ii->species_name);
|
|
else
|
|
ii->cmeasure_index = index;
|
|
}
|
|
}
|
|
|
|
// translate all spawn type weapons to referrnce the appropriate spawned weapon entry
|
|
translate_spawn_types();
|
|
}
|
|
|
|
void weapon_expl_info_init()
|
|
{
|
|
int i;
|
|
|
|
parse_weapon_expl_tbl("weapon_expl.tbl");
|
|
|
|
// check for, and load, modular tables
|
|
parse_modular_table(NOX("*-wxp.tbm"), parse_weapon_expl_tbl);
|
|
|
|
// we've got our list so pass it off for final checking and loading
|
|
for (i = 0; i < (int)LOD_checker.size(); i++) {
|
|
Weapon_explosions.Load( LOD_checker[i].filename, LOD_checker[i].num_lods );
|
|
}
|
|
|
|
// done
|
|
LOD_checker.clear();
|
|
}
|
|
|
|
void weapon_reset_info()
|
|
{
|
|
memset( Weapon_info, 0, sizeof(weapon_info) * MAX_WEAPON_TYPES );
|
|
|
|
for (int i = 0; i < MAX_WEAPON_TYPES; i++)
|
|
init_weapon_entry(i);
|
|
}
|
|
|
|
/**
|
|
* This will get called once at game startup
|
|
*/
|
|
void weapon_init()
|
|
{
|
|
if ( !Weapons_inited ) {
|
|
//Init weapon explosion info
|
|
weapon_expl_info_init();
|
|
|
|
// parse weapons.tbl
|
|
weapon_reset_info();
|
|
|
|
Num_weapon_types = 0;
|
|
Num_spawn_types = 0;
|
|
|
|
parse_weaponstbl("weapons.tbl");
|
|
|
|
parse_modular_table(NOX("*-wep.tbm"), parse_weaponstbl);
|
|
|
|
// do post-parse cleanup
|
|
weapon_do_post_parse();
|
|
|
|
Weapons_inited = 1;
|
|
}
|
|
|
|
weapon_level_init();
|
|
}
|
|
|
|
|
|
/**
|
|
* Call from game_shutdown() only!!
|
|
*/
|
|
void weapon_close()
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i<MAX_WEAPON_TYPES; i++) {
|
|
if (Weapon_info[i].desc) {
|
|
vm_free(Weapon_info[i].desc);
|
|
Weapon_info[i].desc = NULL;
|
|
}
|
|
|
|
if (Weapon_info[i].tech_desc) {
|
|
vm_free(Weapon_info[i].tech_desc);
|
|
Weapon_info[i].tech_desc = NULL;
|
|
}
|
|
}
|
|
|
|
if (used_weapons != NULL) {
|
|
delete[] used_weapons;
|
|
used_weapons = NULL;
|
|
}
|
|
|
|
if (Spawn_names != NULL) {
|
|
for (i=0; i<Num_spawn_types; i++) {
|
|
if (Spawn_names[i] != NULL) {
|
|
vm_free(Spawn_names[i]);
|
|
Spawn_names[i] = NULL;
|
|
}
|
|
}
|
|
|
|
vm_free(Spawn_names);
|
|
Spawn_names = NULL;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This will get called at the start of each level.
|
|
*/
|
|
void weapon_level_init()
|
|
{
|
|
int i;
|
|
|
|
// Reset everything between levels
|
|
Num_weapons = 0;
|
|
for (i=0; i<MAX_WEAPONS; i++) {
|
|
Weapons[i].objnum = -1;
|
|
Weapons[i].weapon_info_index = -1;
|
|
}
|
|
|
|
for (i=0; i<MAX_WEAPON_TYPES; i++) {
|
|
Weapon_info[i].damage_type_idx = Weapon_info[i].damage_type_idx_sav;
|
|
Weapon_info[i].shockwave.damage_type_idx = Weapon_info[i].shockwave.damage_type_idx_sav;
|
|
}
|
|
|
|
trail_level_init(); // reset all missile trails
|
|
|
|
swarm_level_init();
|
|
missile_obj_list_init();
|
|
|
|
cscrew_level_init();
|
|
|
|
// emp effect
|
|
emp_level_init();
|
|
|
|
if (used_weapons == NULL)
|
|
used_weapons = new int[Num_weapon_types];
|
|
|
|
// clear out used_weapons between missions
|
|
memset(used_weapons, 0, Num_weapon_types * sizeof(int));
|
|
|
|
Weapon_flyby_sound_timer = timestamp(0);
|
|
Weapon_impact_timer = 1; // inited each level, used to reduce impact sounds
|
|
}
|
|
|
|
MONITOR( NumWeaponsRend )
|
|
|
|
const float weapon_glow_scale_f = 2.3f;
|
|
const float weapon_glow_scale_r = 2.3f;
|
|
const float weapon_glow_scale_l = 1.5f;
|
|
const int weapon_glow_alpha = 217; // (0.85 * 255);
|
|
|
|
void weapon_render_DEPRECATED(object *obj)
|
|
{
|
|
int num;
|
|
weapon_info *wip;
|
|
weapon *wp;
|
|
color c;
|
|
|
|
MONITOR_INC(NumWeaponsRend, 1);
|
|
|
|
Assert(obj->type == OBJ_WEAPON);
|
|
|
|
num = obj->instance;
|
|
wp = &Weapons[num];
|
|
wip = &Weapon_info[Weapons[num].weapon_info_index];
|
|
|
|
if (wip->wi_flags2 & WIF2_TRANSPARENT) {
|
|
if (wp->alpha_current == -1.0f) {
|
|
wp->alpha_current = wip->alpha_max;
|
|
} else if (wip->alpha_cycle > 0.0f) {
|
|
if (wp->alpha_backward) {
|
|
wp->alpha_current += wip->alpha_cycle;
|
|
|
|
if (wp->alpha_current > wip->alpha_max) {
|
|
wp->alpha_current = wip->alpha_max;
|
|
wp->alpha_backward = 0;
|
|
}
|
|
} else {
|
|
wp->alpha_current -= wip->alpha_cycle;
|
|
|
|
if (wp->alpha_current < wip->alpha_min) {
|
|
wp->alpha_current = wip->alpha_min;
|
|
wp->alpha_backward = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
switch (wip->render_type)
|
|
{
|
|
case WRT_LASER:
|
|
{
|
|
if(wip->laser_length < 0.0001f)
|
|
return;
|
|
// turn off fogging for good measure
|
|
gr_fog_set(GR_FOGMODE_NONE, 0, 0, 0);
|
|
int alpha = 255;
|
|
int framenum = 0;
|
|
|
|
if (wip->laser_bitmap.first_frame >= 0) {
|
|
gr_set_color_fast(&wip->laser_color_1);
|
|
|
|
if (wip->laser_bitmap.num_frames > 1) {
|
|
wp->laser_bitmap_frame += flFrametime;
|
|
|
|
// Sanity checks
|
|
if (wp->laser_bitmap_frame < 0.0f)
|
|
wp->laser_bitmap_frame = 0.0f;
|
|
if (wp->laser_bitmap_frame > 100.0f)
|
|
wp->laser_bitmap_frame = 0.0f;
|
|
|
|
while (wp->laser_bitmap_frame > wip->laser_bitmap.total_time)
|
|
wp->laser_bitmap_frame -= wip->laser_bitmap.total_time;
|
|
|
|
framenum = fl2i( (wp->laser_bitmap_frame * wip->laser_bitmap.num_frames) / wip->laser_bitmap.total_time );
|
|
|
|
CLAMP(framenum, 0, wip->laser_bitmap.num_frames-1);
|
|
}
|
|
|
|
if (wip->wi_flags2 & WIF2_TRANSPARENT)
|
|
alpha = fl2i(wp->alpha_current * 255.0f);
|
|
|
|
vec3d headp;
|
|
|
|
vm_vec_scale_add(&headp, &obj->pos, &obj->orient.vec.fvec, wip->laser_length);
|
|
wp->weapon_flags &= ~WF_CONSIDER_FOR_FLYBY_SOUND;
|
|
|
|
if ( batch_add_laser(wip->laser_bitmap.first_frame + framenum, &headp, wip->laser_head_radius, &obj->pos, wip->laser_tail_radius, alpha, alpha, alpha) ) {
|
|
wp->weapon_flags |= WF_CONSIDER_FOR_FLYBY_SOUND;
|
|
}
|
|
}
|
|
|
|
// maybe draw laser glow bitmap
|
|
if (wip->laser_glow_bitmap.first_frame >= 0) {
|
|
// get the laser color
|
|
weapon_get_laser_color(&c, obj);
|
|
|
|
// *Tail point "getting bigger" as well as headpoint isn't being taken into consideration, so
|
|
// it caused uneven glow between the head and tail, which really shows in big lasers. So...fixed! -Et1
|
|
vec3d headp2, tailp;
|
|
|
|
vm_vec_scale_add(&headp2, &obj->pos, &obj->orient.vec.fvec, wip->laser_length * weapon_glow_scale_l);
|
|
vm_vec_scale_add(&tailp, &obj->pos, &obj->orient.vec.fvec, wip->laser_length * (1 - weapon_glow_scale_l) );
|
|
|
|
framenum = 0;
|
|
|
|
if (wip->laser_glow_bitmap.num_frames > 1) {
|
|
wp->laser_glow_bitmap_frame += flFrametime;
|
|
|
|
// Sanity checks
|
|
if (wp->laser_glow_bitmap_frame < 0.0f)
|
|
wp->laser_glow_bitmap_frame = 0.0f;
|
|
if (wp->laser_glow_bitmap_frame > 100.0f)
|
|
wp->laser_glow_bitmap_frame = 0.0f;
|
|
|
|
while (wp->laser_glow_bitmap_frame > wip->laser_glow_bitmap.total_time)
|
|
wp->laser_glow_bitmap_frame -= wip->laser_glow_bitmap.total_time;
|
|
|
|
framenum = fl2i( (wp->laser_glow_bitmap_frame * wip->laser_glow_bitmap.num_frames) / wip->laser_glow_bitmap.total_time );
|
|
|
|
CLAMP(framenum, 0, wip->laser_glow_bitmap.num_frames-1);
|
|
}
|
|
|
|
if (wip->wi_flags2 & WIF2_TRANSPARENT) {
|
|
alpha = fl2i(wp->alpha_current * 255.0f);
|
|
alpha -= 38; // take 1.5f into account for the normal glow alpha
|
|
|
|
if (alpha < 0)
|
|
alpha = 0;
|
|
} else {
|
|
alpha = weapon_glow_alpha;
|
|
}
|
|
|
|
batch_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);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case WRT_POF:
|
|
{
|
|
uint render_flags = MR_DEPRECATED_NORMAL|MR_DEPRECATED_IS_MISSILE|MR_DEPRECATED_NO_LIGHTING;
|
|
|
|
if (Cmdline_missile_lighting && !(wip->wi_flags2 & WIF2_MR_NO_LIGHTING))
|
|
render_flags &= ~MR_DEPRECATED_NO_LIGHTING;
|
|
|
|
if (wip->wi_flags2 & WIF2_TRANSPARENT) {
|
|
model_set_alpha(wp->alpha_current);
|
|
render_flags |= MR_DEPRECATED_ALL_XPARENT;
|
|
}
|
|
|
|
model_clear_instance(wip->model_num);
|
|
|
|
if ( (wip->wi_flags & WIF_THRUSTER) && ((wp->thruster_bitmap > -1) || (wp->thruster_glow_bitmap > -1)) ) {
|
|
float ft;
|
|
mst_info mst;
|
|
|
|
// Add noise to thruster geometry.
|
|
ft = 1.0f; // Always use 1.0f for missiles
|
|
ft *= (1.0f + frand()/5.0f - 1.0f/10.0f);
|
|
if (ft > 1.0f)
|
|
ft = 1.0f;
|
|
|
|
mst.length.xyz.x = ft;
|
|
mst.length.xyz.y = ft;
|
|
mst.length.xyz.z = ft;
|
|
|
|
mst.primary_bitmap = wp->thruster_bitmap;
|
|
mst.primary_glow_bitmap = wp->thruster_glow_bitmap;
|
|
mst.glow_rad_factor = wip->thruster_glow_factor;
|
|
mst.glow_noise = wp->thruster_glow_noise;
|
|
|
|
model_set_thrust(wip->model_num, &mst);
|
|
|
|
render_flags |= MR_DEPRECATED_SHOW_THRUSTERS;
|
|
}
|
|
|
|
|
|
//don't render local ssm's when they are still in subspace
|
|
if (wp->lssm_stage==3)
|
|
break;
|
|
|
|
int clip_plane=0;
|
|
|
|
//start a clip plane
|
|
if (wp->lssm_stage==2)
|
|
{
|
|
object *wobj=&Objects[wp->lssm_warp_idx]; //warphole object
|
|
clip_plane=1;
|
|
|
|
g3_start_user_clip_plane(&wobj->pos,&wobj->orient.vec.fvec);
|
|
}
|
|
|
|
|
|
model_render_DEPRECATED(wip->model_num, &obj->orient, &obj->pos, render_flags);
|
|
wp->weapon_flags |= WF_CONSIDER_FOR_FLYBY_SOUND;
|
|
if (clip_plane)
|
|
{
|
|
g3_stop_user_clip_plane();
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
Warning(LOCATION, "Unknown weapon rendering type = %i for weapon %s\n", wip->render_type, wip->name);
|
|
}
|
|
}
|
|
|
|
void weapon_delete(object *obj)
|
|
{
|
|
weapon *wp;
|
|
int num;
|
|
|
|
Script_system.SetHookObjects(2, "Weapon", obj, "Self", obj);
|
|
Script_system.RunCondition(CHA_ONWEAPONDELETE);
|
|
Script_system.RemHookVars(2, "Weapon", "Self");
|
|
|
|
num = obj->instance;
|
|
|
|
Assert( Weapons[num].objnum == OBJ_INDEX(obj));
|
|
wp = &Weapons[num];
|
|
|
|
Assert(wp->weapon_info_index >= 0);
|
|
wp->weapon_info_index = -1;
|
|
if (wp->swarm_index >= 0) {
|
|
swarm_delete(wp->swarm_index);
|
|
wp->swarm_index = -1;
|
|
}
|
|
|
|
if(wp->cscrew_index >= 0) {
|
|
cscrew_delete(wp->cscrew_index);
|
|
wp->cscrew_index = -1;
|
|
}
|
|
|
|
if (wp->missile_list_index >= 0) {
|
|
missle_obj_list_remove(wp->missile_list_index);
|
|
wp->missile_list_index = -1;
|
|
}
|
|
|
|
if (wp->trail_ptr != NULL) {
|
|
trail_object_died(wp->trail_ptr);
|
|
wp->trail_ptr = NULL;
|
|
}
|
|
|
|
if (wp->hud_in_flight_snd_sig >= 0 && snd_is_playing(wp->hud_in_flight_snd_sig))
|
|
{
|
|
snd_stop(wp->hud_in_flight_snd_sig);
|
|
}
|
|
|
|
wp->objnum = -1;
|
|
Num_weapons--;
|
|
Assert(Num_weapons >= 0);
|
|
}
|
|
|
|
/**
|
|
* Check if missile is newly locked onto the Player, maybe play a launch warning
|
|
*/
|
|
void weapon_maybe_play_warning(weapon *wp)
|
|
{
|
|
if ( wp->homing_object == Player_obj ) {
|
|
if ( !(wp->weapon_flags & WF_LOCK_WARNING_PLAYED) ) {
|
|
wp->weapon_flags |= WF_LOCK_WARNING_PLAYED;
|
|
// Use heatlock-warning sound for Heat and Javelin for now
|
|
// Possibly add an additional third sound later
|
|
if ( (Weapon_info[wp->weapon_info_index].wi_flags & WIF_HOMING_HEAT) ||
|
|
(Weapon_info[wp->weapon_info_index].wi_flags & WIF_HOMING_JAVELIN) ) {
|
|
snd_play(&Snds[ship_get_sound(Player_obj, SND_HEATLOCK_WARN)]);
|
|
} else {
|
|
Assert(Weapon_info[wp->weapon_info_index].wi_flags & WIF_HOMING_ASPECT);
|
|
snd_play(&Snds[ship_get_sound(Player_obj, SND_ASPECTLOCK_WARN)]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#define CMEASURE_DETONATE_DISTANCE 40.0f
|
|
|
|
/**
|
|
* Detonate all missiles near this countermeasure.
|
|
*/
|
|
void detonate_nearby_missiles(object *killer_objp)
|
|
{
|
|
if(killer_objp->type != OBJ_WEAPON) {
|
|
Int3();
|
|
return;
|
|
}
|
|
|
|
missile_obj *mop;
|
|
|
|
mop = GET_FIRST(&Missile_obj_list);
|
|
while(mop != END_OF_LIST(&Missile_obj_list)) {
|
|
object *objp;
|
|
weapon *wp;
|
|
|
|
objp = &Objects[mop->objnum];
|
|
wp = &Weapons[objp->instance];
|
|
|
|
if (iff_x_attacks_y(Weapons[killer_objp->instance].team, wp->team)) {
|
|
if ( Missiontime - wp->creation_time > F1_0/2) {
|
|
if (vm_vec_dist_quick(&killer_objp->pos, &objp->pos) < CMEASURE_DETONATE_DISTANCE) {
|
|
if (wp->lifeleft > 0.2f) {
|
|
wp->lifeleft = 0.2f;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
mop = mop->next;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Find an object for weapon #num (object *weapon_objp) to home on due to heat.
|
|
*/
|
|
void find_homing_object(object *weapon_objp, int num)
|
|
{
|
|
object *objp, *old_homing_objp;
|
|
weapon_info *wip;
|
|
weapon *wp;
|
|
ship *sp;
|
|
ship_info *sip;
|
|
float best_dist;
|
|
int homing_object_team;
|
|
float dist;
|
|
float dot;
|
|
vec3d vec_to_object;
|
|
ship_subsys *target_engines = NULL;
|
|
|
|
wp = &Weapons[num];
|
|
|
|
wip = &Weapon_info[Weapons[num].weapon_info_index];
|
|
|
|
best_dist = 99999.9f;
|
|
|
|
// save the old homing object so that multiplayer servers can give the right information
|
|
// to clients if the object changes
|
|
old_homing_objp = wp->homing_object;
|
|
|
|
wp->homing_object = &obj_used_list;
|
|
|
|
// Scan all objects, find a weapon to home on.
|
|
for ( objp = GET_FIRST(&obj_used_list); objp !=END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) ) {
|
|
if ((objp->type == OBJ_SHIP) || ((objp->type == OBJ_WEAPON) && (Weapon_info[Weapons[objp->instance].weapon_info_index].wi_flags & WIF_CMEASURE)))
|
|
{
|
|
//WMC - Spawn weapons shouldn't go for protected ships
|
|
// ditto for untargeted heat seekers - niffiwan
|
|
if ( (objp->flags & OF_PROTECTED) &&
|
|
((wp->weapon_flags & WF_SPAWNED) || (wip->wi_flags2 & WIF2_UNTARGETED_HEAT_SEEKER)) )
|
|
continue;
|
|
|
|
// Spawned weapons should never home in on their parent - even in multiplayer dogfights where they would pass the iff test below
|
|
if ((wp->weapon_flags & WF_SPAWNED) && (objp == &Objects[weapon_objp->parent]))
|
|
continue;
|
|
|
|
homing_object_team = obj_team(objp);
|
|
if (iff_x_attacks_y(wp->team, homing_object_team))
|
|
{
|
|
if ( objp->type == OBJ_SHIP )
|
|
{
|
|
sp = &Ships[objp->instance];
|
|
sip = &Ship_info[sp->ship_info_index];
|
|
|
|
//if the homing weapon is a huge weapon and the ship that is being
|
|
//looked at is not huge, then don't home
|
|
if ((wip->wi_flags & WIF_HUGE) &&
|
|
(sip->flags & (SIF_SMALL_SHIP | SIF_NOT_FLYABLE | SIF_HARMLESS)))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// AL 2-17-98: If ship is immune to sensors, can't home on it (Sandeep says so)!
|
|
if ( sp->flags & SF_HIDDEN_FROM_SENSORS ) {
|
|
continue;
|
|
}
|
|
|
|
// Goober5000: if missiles can't home on sensor-ghosted ships,
|
|
// they definitely shouldn't home on stealth ships
|
|
if ( sp->flags2 & SF2_STEALTH && (The_mission.ai_profile->flags & AIPF_FIX_HEAT_SEEKER_STEALTH_BUG) ) {
|
|
continue;
|
|
}
|
|
|
|
if (wip->wi_flags & WIF_HOMING_JAVELIN)
|
|
{
|
|
target_engines = ship_get_closest_subsys_in_sight(sp, SUBSYSTEM_ENGINE, &weapon_objp->pos);
|
|
|
|
if (!target_engines)
|
|
continue;
|
|
}
|
|
|
|
// MK, 9/4/99.
|
|
// If this is a player object, make sure there aren't already too many homers.
|
|
// Only in single player. In multiplayer, we don't want to restrict it in dogfight on team vs. team.
|
|
// For co-op, it's probably also OK.
|
|
if (!( Game_mode & GM_MULTIPLAYER )) {
|
|
int num_homers = compute_num_homing_objects(objp);
|
|
if (The_mission.ai_profile->max_allowed_player_homers[Game_skill_level] < num_homers)
|
|
continue;
|
|
}
|
|
}
|
|
else if (objp->type == OBJ_WEAPON)
|
|
{
|
|
//don't attempt to home on weapons if the weapon is a huge weapon or is a javelin homing weapon.
|
|
if (wip->wi_flags & (WIF_HUGE | WIF_HOMING_JAVELIN))
|
|
continue;
|
|
|
|
//don't look for local ssms that are gone for the time being
|
|
if (Weapons[objp->instance].lssm_stage == 3)
|
|
continue;
|
|
}
|
|
|
|
dist = vm_vec_normalized_dir(&vec_to_object, &objp->pos, &weapon_objp->pos);
|
|
|
|
if (objp->type == OBJ_WEAPON && (Weapon_info[Weapons[objp->instance].weapon_info_index].wi_flags & WIF_CMEASURE)) {
|
|
dist *= 0.5f;
|
|
}
|
|
|
|
dot = vm_vec_dot(&vec_to_object, &weapon_objp->orient.vec.fvec);
|
|
|
|
if (dot > wip->fov) {
|
|
if (dist < best_dist) {
|
|
best_dist = dist;
|
|
wp->homing_object = objp;
|
|
wp->target_sig = objp->signature;
|
|
wp->homing_subsys = target_engines;
|
|
|
|
cmeasure_maybe_alert_success(objp);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (wp->homing_object == Player_obj)
|
|
weapon_maybe_play_warning(wp);
|
|
|
|
// if the old homing object is different that the new one, send a packet to clients
|
|
if ( MULTIPLAYER_MASTER && (old_homing_objp != wp->homing_object) ) {
|
|
send_homing_weapon_info( num );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Scan all countermeasures. Maybe make weapon_objp home on it.
|
|
*/
|
|
void find_homing_object_cmeasures_1(object *weapon_objp)
|
|
{
|
|
object *objp;
|
|
weapon *wp, *cm_wp;
|
|
weapon_info *wip, *cm_wip;
|
|
float best_dot, dist, dot;
|
|
|
|
wp = &Weapons[weapon_objp->instance];
|
|
wip = &Weapon_info[wp->weapon_info_index];
|
|
|
|
best_dot = wip->fov; // Note, setting to this avoids comparison below.
|
|
|
|
for ( objp = GET_FIRST(&obj_used_list); objp !=END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) )
|
|
{
|
|
//first check if its a weapon, then setup the pointers
|
|
if (objp->type == OBJ_WEAPON)
|
|
{
|
|
cm_wp = &Weapons[objp->instance];
|
|
cm_wip = &Weapon_info[cm_wp->weapon_info_index];
|
|
|
|
if (cm_wip->wi_flags & WIF_CMEASURE)
|
|
{
|
|
//don't have a weapon try to home in on itself
|
|
if (objp==weapon_objp)
|
|
continue;
|
|
|
|
//don't have a weapon try to home in on missiles fired by the same team, unless its the traitor team.
|
|
if ((wp->team == cm_wp->team) && (wp->team != Iff_traitor))
|
|
continue;
|
|
|
|
vec3d vec_to_object;
|
|
dist = vm_vec_normalized_dir(&vec_to_object, &objp->pos, &weapon_objp->pos);
|
|
|
|
if (dist < cm_wip->cm_effective_rad)
|
|
{
|
|
float chance;
|
|
if (wip->wi_flags & WIF_HOMING_ASPECT) {
|
|
// aspect seeker this likely to chase a countermeasure
|
|
chance = cm_wip->cm_aspect_effectiveness/wip->seeker_strength;
|
|
} else {
|
|
// heat seeker and javelin HS this likely to chase a countermeasure
|
|
chance = cm_wip->cm_heat_effectiveness/wip->seeker_strength;
|
|
}
|
|
if ((objp->signature != wp->cmeasure_ignore_objnum) && (objp->signature != wp->cmeasure_chase_objnum))
|
|
{
|
|
if (frand() >= chance) {
|
|
wp->cmeasure_ignore_objnum = objp->signature; // Don't process this countermeasure again.
|
|
} else {
|
|
wp->cmeasure_chase_objnum = objp->signature; // Don't process this countermeasure again.
|
|
}
|
|
}
|
|
|
|
if (objp->signature != wp->cmeasure_ignore_objnum)
|
|
{
|
|
dot = vm_vec_dot(&vec_to_object, &weapon_objp->orient.vec.fvec);
|
|
|
|
if (dot > best_dot)
|
|
{
|
|
best_dot = dot;
|
|
wp->homing_object = objp;
|
|
cmeasure_maybe_alert_success(objp);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Someone launched countermeasures.
|
|
* For all heat-seeking homing objects, see if should favor tracking a countermeasure instead.
|
|
*/
|
|
void find_homing_object_cmeasures()
|
|
{
|
|
object *weapon_objp;
|
|
|
|
if (Cmeasures_homing_check == 0)
|
|
return;
|
|
|
|
if (Cmeasures_homing_check <= 0)
|
|
Cmeasures_homing_check = 1;
|
|
|
|
Cmeasures_homing_check--;
|
|
|
|
for (weapon_objp = GET_FIRST(&obj_used_list); weapon_objp != END_OF_LIST(&obj_used_list); weapon_objp = GET_NEXT(weapon_objp) ) {
|
|
if (weapon_objp->type == OBJ_WEAPON) {
|
|
weapon_info *wip = &Weapon_info[Weapons[weapon_objp->instance].weapon_info_index];
|
|
|
|
if (wip->wi_flags & WIF_HOMING)
|
|
find_homing_object_cmeasures_1(weapon_objp);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Find object with signature "sig" and make weapon home on it.
|
|
*/
|
|
void find_homing_object_by_sig(object *weapon_objp, int sig)
|
|
{
|
|
ship_obj *sop;
|
|
weapon *wp;
|
|
object *old_homing_objp;
|
|
|
|
wp = &Weapons[weapon_objp->instance];
|
|
|
|
// save the old object so that multiplayer masters know whether to send a homing update packet
|
|
old_homing_objp = wp->homing_object;
|
|
|
|
sop = GET_FIRST(&Ship_obj_list);
|
|
while(sop != END_OF_LIST(&Ship_obj_list)) {
|
|
object *objp;
|
|
|
|
objp = &Objects[sop->objnum];
|
|
if (objp->signature == sig) {
|
|
wp->homing_object = objp;
|
|
wp->target_sig = objp->signature;
|
|
break;
|
|
}
|
|
|
|
sop = sop->next;
|
|
}
|
|
|
|
// if the old homing object is different that the new one, send a packet to clients
|
|
if ( MULTIPLAYER_MASTER && (old_homing_objp != wp->homing_object) ) {
|
|
send_homing_weapon_info( weapon_objp->instance );
|
|
}
|
|
}
|
|
|
|
bool aspect_should_lose_target(weapon* wp)
|
|
{
|
|
Assert(wp != NULL);
|
|
|
|
if (wp->homing_object->signature != wp->target_sig) {
|
|
if (wp->homing_object->type == OBJ_WEAPON)
|
|
{
|
|
weapon_info* target_info = &Weapon_info[Weapons[wp->homing_object->instance].weapon_info_index];
|
|
|
|
if (target_info->wi_flags & WIF_CMEASURE)
|
|
{
|
|
// Check if we can home on this countermeasure
|
|
bool home_on_cmeasure = The_mission.ai_profile->flags2 & AIPF2_ASPECT_LOCK_COUNTERMEASURE
|
|
|| target_info->wi_flags3 & WIF3_CMEASURE_ASPECT_HOME_ON;
|
|
|
|
if (!home_on_cmeasure)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Make weapon num home. It's also object *obj.
|
|
*/
|
|
void weapon_home(object *obj, int num, float frame_time)
|
|
{
|
|
weapon *wp;
|
|
weapon_info *wip;
|
|
object *hobjp;
|
|
|
|
Assert(obj->type == OBJ_WEAPON);
|
|
Assert(obj->instance == num);
|
|
wp = &Weapons[num];
|
|
wip = &Weapon_info[wp->weapon_info_index];
|
|
hobjp = Weapons[num].homing_object;
|
|
|
|
//local ssms home only in stages 1 and 5
|
|
if ( (wp->lssm_stage==2) || (wp->lssm_stage==3) || (wp->lssm_stage==4))
|
|
return;
|
|
|
|
float max_speed;
|
|
|
|
if ((wip->wi_flags2 & WIF2_LOCAL_SSM) && (wp->lssm_stage==5))
|
|
max_speed=wip->lssm_stage5_vel;
|
|
else
|
|
max_speed=wip->max_speed;
|
|
|
|
// If not [free-flight-time] gone by, don't home yet.
|
|
// Goober5000 - this has been fixed back to more closely follow the original logic. Remember, the retail code
|
|
// had 0.5 second of free flight time, the first half of which was spent ramping up to full speed.
|
|
if ((hobjp == &obj_used_list) || ( f2fl(Missiontime - wp->creation_time) < (wip->free_flight_time / 2) )) {
|
|
// If this is a heat seeking homing missile and [free-flight-time] has elapsed since firing
|
|
// and we don't have a target (else we wouldn't be inside the IF), find a new target.
|
|
if ((wip->wi_flags & WIF_HOMING_HEAT) &&
|
|
(f2fl(Missiontime - wp->creation_time) > wip->free_flight_time))
|
|
{
|
|
find_homing_object(obj, num);
|
|
}
|
|
else if (MULTIPLAYER_MASTER && (wip->wi_flags & WIF_LOCKED_HOMING) && (wp->weapon_flags & WF_HOMING_UPDATE_NEEDED)) {
|
|
wp->weapon_flags &= ~WF_HOMING_UPDATE_NEEDED;
|
|
send_homing_weapon_info(num);
|
|
}
|
|
|
|
if (wip->acceleration_time > 0.0f) {
|
|
if (Missiontime - wp->creation_time < fl2f(wip->acceleration_time)) {
|
|
float t;
|
|
|
|
t = f2fl(Missiontime - wp->creation_time) / wip->acceleration_time;
|
|
obj->phys_info.speed = wp->launch_speed + MAX(0.0f, wp->weapon_max_vel - wp->launch_speed) * t;
|
|
}
|
|
}
|
|
// since free_flight_time can now be 0, guard against that
|
|
else if (wip->free_flight_time > 0.0f) {
|
|
if (obj->phys_info.speed > max_speed) {
|
|
obj->phys_info.speed -= frame_time * (2 / wip->free_flight_time);
|
|
} else if ((obj->phys_info.speed < max_speed / (2 / wip->free_flight_time)) && (wip->wi_flags & WIF_HOMING_HEAT)) {
|
|
obj->phys_info.speed = max_speed / (2 / wip->free_flight_time);
|
|
}
|
|
}
|
|
// no free_flight_time, so immediately set desired speed
|
|
else {
|
|
obj->phys_info.speed = max_speed;
|
|
}
|
|
|
|
// set velocity using whatever speed we have
|
|
vm_vec_copy_scale( &obj->phys_info.desired_vel, &obj->orient.vec.fvec, obj->phys_info.speed);
|
|
|
|
return;
|
|
}
|
|
|
|
if (wip->acceleration_time > 0.0f) {
|
|
if (Missiontime - wp->creation_time < fl2f(wip->acceleration_time)) {
|
|
float t;
|
|
|
|
t = f2fl(Missiontime - wp->creation_time) / wip->acceleration_time;
|
|
obj->phys_info.speed = wp->launch_speed + (wp->weapon_max_vel - wp->launch_speed) * t;
|
|
vm_vec_copy_scale( &obj->phys_info.desired_vel, &obj->orient.vec.fvec, obj->phys_info.speed);
|
|
}
|
|
}
|
|
|
|
// AL 4-8-98: If original target for aspect lock missile is lost, stop homing
|
|
// WCS - or javelin
|
|
if (wip->wi_flags & WIF_LOCKED_HOMING) {
|
|
if ( wp->target_sig > 0 ) {
|
|
if (aspect_should_lose_target(wp))
|
|
{
|
|
wp->homing_object = &obj_used_list;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// AL 4-13-98: Stop homing on a subsystem if parent ship has changed
|
|
if (wip->wi_flags & WIF_HOMING_HEAT) {
|
|
if ( wp->target_sig > 0 ) {
|
|
if ( wp->homing_object->signature != wp->target_sig ) {
|
|
wp->homing_subsys = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If target subsys is dead make missile pick random spot on target as attack point.
|
|
if (wp->homing_subsys != NULL) {
|
|
if (wp->homing_subsys->flags & SSF_MISSILES_IGNORE_IF_DEAD) {
|
|
if ((wp->homing_subsys->max_hits > 0) && (wp->homing_subsys->current_hits <= 0)) {
|
|
wp->homing_object = &obj_used_list;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Make sure Javelin HS missiles always home on engine subsystems if ships
|
|
if ((wip->wi_flags & WIF_HOMING_JAVELIN) &&
|
|
(hobjp->type == OBJ_SHIP) &&
|
|
(wp->target_sig > 0) &&
|
|
(wp->homing_subsys != NULL) &&
|
|
(wp->homing_subsys->system_info->type != SUBSYSTEM_ENGINE)) {
|
|
int sindex = ship_get_by_signature(wp->target_sig);
|
|
if (sindex >= 0) {
|
|
ship *enemy = &Ships[sindex];
|
|
wp->homing_subsys = ship_get_closest_subsys_in_sight(enemy, SUBSYSTEM_ENGINE, &Objects[wp->objnum].pos);
|
|
}
|
|
}
|
|
|
|
// If Javelin HS missile doesn't home in on a subsystem but homing in on a
|
|
// ship, lose lock alltogether
|
|
// Javelins can only home in one Engines or bombs.
|
|
if ((wip->wi_flags & WIF_HOMING_JAVELIN) &&
|
|
(hobjp->type == OBJ_SHIP) &&
|
|
(wp->target_sig > 0) &&
|
|
(wp->homing_subsys == NULL)) {
|
|
wp->homing_object = &obj_used_list;
|
|
return;
|
|
}
|
|
|
|
switch (hobjp->type) {
|
|
case OBJ_NONE:
|
|
if (wip->wi_flags & WIF_LOCKED_HOMING) {
|
|
find_homing_object_by_sig(obj, wp->target_sig);
|
|
}
|
|
else {
|
|
find_homing_object(obj, num);
|
|
}
|
|
return;
|
|
break;
|
|
case OBJ_SHIP:
|
|
if (hobjp->signature != wp->target_sig) {
|
|
if (wip->wi_flags & WIF_LOCKED_HOMING) {
|
|
find_homing_object_by_sig(obj, wp->target_sig);
|
|
}
|
|
else {
|
|
find_homing_object(obj, num);
|
|
}
|
|
return;
|
|
}
|
|
break;
|
|
case OBJ_WEAPON:
|
|
{
|
|
bool home_on_cmeasure = The_mission.ai_profile->flags2 & AIPF2_ASPECT_LOCK_COUNTERMEASURE
|
|
|| Weapon_info[Weapons[hobjp->instance].weapon_info_index].wi_flags3 & WIF3_CMEASURE_ASPECT_HOME_ON;
|
|
|
|
// don't home on countermeasures or non-bombs, that's handled elsewhere
|
|
if (((Weapon_info[Weapons[hobjp->instance].weapon_info_index].wi_flags & WIF_CMEASURE) && !home_on_cmeasure))
|
|
{
|
|
break;
|
|
}
|
|
else if (!(Weapon_info[Weapons[hobjp->instance].weapon_info_index].wi_flags & WIF_BOMB))
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (wip->wi_flags & WIF_LOCKED_HOMING) {
|
|
find_homing_object_by_sig(obj, wp->target_sig);
|
|
}
|
|
else {
|
|
find_homing_object(obj, num);
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
return;
|
|
}
|
|
|
|
// See if this weapon is the nearest homing object to the object it is homing on.
|
|
// If so, update some fields in the target object's ai_info.
|
|
if (hobjp != &obj_used_list) {
|
|
float dist;
|
|
|
|
dist = vm_vec_dist_quick(&obj->pos, &hobjp->pos);
|
|
|
|
if (hobjp->type == OBJ_SHIP) {
|
|
ai_info *aip;
|
|
|
|
aip = &Ai_info[Ships[hobjp->instance].ai_index];
|
|
|
|
if ((aip->nearest_locked_object == -1) || (dist < aip->nearest_locked_distance)) {
|
|
aip->nearest_locked_object = obj-Objects;
|
|
aip->nearest_locked_distance = dist;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If the object it is homing on is still valid, home some more!
|
|
if (hobjp != &obj_used_list) {
|
|
float old_dot, vel;
|
|
vec3d vec_to_goal;
|
|
vec3d target_pos; // position of what the homing missile is seeking
|
|
|
|
vm_vec_zero(&target_pos);
|
|
|
|
// the homing missile may be seeking a subsystem on a ship. If so, we need to calculate the
|
|
// world coordinates of that subsystem so the homing missile can seek it out.
|
|
// For now, March 7, 1997, MK, heat seeking homing missiles will be able to home on
|
|
// any subsystem. Probably makes sense for them to only home on certain kinds of subsystems.
|
|
if ( (wp->homing_subsys != NULL) && !(wip->wi_flags2 & WIF2_NON_SUBSYS_HOMING) && hobjp->type == OBJ_SHIP) {
|
|
get_subsystem_world_pos(hobjp, Weapons[num].homing_subsys, &target_pos);
|
|
wp->homing_pos = target_pos; // store the homing position in weapon data
|
|
Assert( !vm_is_vec_nan(&wp->homing_pos) );
|
|
} else {
|
|
float fov;
|
|
float dist;
|
|
|
|
dist = vm_vec_dist_quick(&obj->pos, &hobjp->pos);
|
|
if (hobjp->type == OBJ_WEAPON && (Weapon_info[Weapons[hobjp->instance].weapon_info_index].wi_flags & WIF_CMEASURE))
|
|
{
|
|
if (dist < CMEASURE_DETONATE_DISTANCE)
|
|
{
|
|
// Make this missile detonate soon. Not right away, not sure why. Seems better.
|
|
if (iff_x_attacks_y(Weapons[hobjp->instance].team, wp->team)) {
|
|
detonate_nearby_missiles(hobjp);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
fov = -1.0f;
|
|
|
|
int pick_homing_point = 0;
|
|
if ( IS_VEC_NULL(&wp->homing_pos) ) {
|
|
pick_homing_point = 1;
|
|
}
|
|
|
|
// Update homing position if it hasn't been set, you're within 500 meters, or every half second, approximately.
|
|
// For large objects, don't lead them.
|
|
if (hobjp->radius < 40.0f) {
|
|
target_pos = hobjp->pos;
|
|
wp->homing_pos = target_pos;
|
|
} else if ( pick_homing_point || (dist < 500.0f) || (rand_chance(flFrametime, 2.0f)) ) {
|
|
|
|
if (hobjp->type == OBJ_SHIP) {
|
|
if ( !pick_homing_point ) {
|
|
// ensure that current attack point is only updated in world coords (ie not pick a different vertex)
|
|
wp->pick_big_attack_point_timestamp = 0;
|
|
}
|
|
|
|
if ( pick_homing_point && !(wip->wi_flags2 & WIF2_NON_SUBSYS_HOMING) ) {
|
|
// If *any* player is parent of homing missile, then use position where lock indicator is
|
|
if ( Objects[obj->parent].flags & OF_PLAYER_SHIP ) {
|
|
player *pp;
|
|
|
|
// determine the player
|
|
pp = Player;
|
|
|
|
if ( Game_mode & GM_MULTIPLAYER ) {
|
|
int pnum;
|
|
|
|
pnum = multi_find_player_by_object( &Objects[obj->parent] );
|
|
if ( pnum != -1 ){
|
|
pp = Net_players[pnum].m_player;
|
|
}
|
|
}
|
|
|
|
// If player has apect lock, we don't want to find a homing point on the closest
|
|
// octant... setting the timestamp to 0 ensures this.
|
|
if (wip->wi_flags & WIF_LOCKED_HOMING) {
|
|
wp->pick_big_attack_point_timestamp = 0;
|
|
} else {
|
|
wp->pick_big_attack_point_timestamp = 1;
|
|
}
|
|
|
|
if ( pp && pp->locking_subsys ) {
|
|
wp->big_attack_point = pp->locking_subsys->system_info->pnt;
|
|
} else {
|
|
vm_vec_zero(&wp->big_attack_point);
|
|
}
|
|
}
|
|
}
|
|
|
|
ai_big_pick_attack_point(hobjp, obj, &target_pos, fov);
|
|
|
|
} else {
|
|
target_pos = hobjp->pos;
|
|
}
|
|
|
|
wp->homing_pos = target_pos;
|
|
Assert( !vm_is_vec_nan(&wp->homing_pos) );
|
|
} else
|
|
target_pos = wp->homing_pos;
|
|
}
|
|
|
|
// Couldn't find a lock.
|
|
if (IS_VEC_NULL(&target_pos))
|
|
return;
|
|
|
|
// Cause aspect seeking weapon to home at target's predicted position.
|
|
// But don't use predicted position if dot product small or negative.
|
|
// If do this, with a ship headed towards missile, could choose a point behind missile.
|
|
float dist_to_target, time_to_target;
|
|
|
|
dist_to_target = vm_vec_normalized_dir(&vec_to_goal, &target_pos, &obj->pos);
|
|
time_to_target = dist_to_target/max_speed;
|
|
|
|
vec3d tvec;
|
|
tvec = obj->phys_info.vel;
|
|
vm_vec_normalize(&tvec);
|
|
|
|
old_dot = vm_vec_dot(&tvec, &vec_to_goal);
|
|
|
|
// If a weapon has missed its target, detonate it.
|
|
// This solves the problem of a weapon circling the center of a subsystem that has been blown away.
|
|
// Problem: It does not do impact damage, just proximity damage.
|
|
if ((dist_to_target < flFrametime * obj->phys_info.speed * 4.0f + 10.0f) &&
|
|
(old_dot < wip->fov) &&
|
|
(wp->lifeleft > 0.01f) &&
|
|
(wp->homing_object) &&
|
|
(wp->homing_object->type == OBJ_SHIP))
|
|
{
|
|
wp->lifeleft = 0.01f;
|
|
}
|
|
|
|
// Only lead target if more than one second away. Otherwise can miss target. I think this
|
|
// is what's causing Harbingers to miss the super destroyer. -- MK, 4/15/98
|
|
if ((old_dot > 0.1f) && (time_to_target > 0.1f)) {
|
|
if (wip->wi_flags2 & WIF2_VARIABLE_LEAD_HOMING) {
|
|
vm_vec_scale_add2(&target_pos, &hobjp->phys_info.vel, (0.33f * wip->target_lead_scaler * MIN(time_to_target, 6.0f)));
|
|
} else if (wip->wi_flags & WIF_LOCKED_HOMING) {
|
|
vm_vec_scale_add2(&target_pos, &hobjp->phys_info.vel, MIN(time_to_target, 2.0f));
|
|
}
|
|
}
|
|
|
|
// If a HEAT seeking (rather than ASPECT seeking) homing missile, verify that target is in viewcone.
|
|
if (wip->wi_flags & WIF_HOMING_HEAT) {
|
|
if ((old_dot < wip->fov) && (dist_to_target > wip->shockwave.inner_rad*1.1f)) { // Delay finding new target one frame to allow detonation.
|
|
find_homing_object(obj, num);
|
|
return; // Maybe found a new homing object. Return, process more next frame.
|
|
} else // Subtract out life based on how far from target this missile points.
|
|
if ((wip->fov < 0.95f) && !(wip->wi_flags2 & WIF2_NO_LIFE_LOST_IF_MISSED)) {
|
|
wp->lifeleft -= flFrametime * (0.95f - old_dot);
|
|
}
|
|
} else if (wip->wi_flags & WIF_LOCKED_HOMING) { // subtract life as if max turn is 90 degrees.
|
|
if ((wip->fov < 0.95f) && !(wip->wi_flags2 & WIF2_NO_LIFE_LOST_IF_MISSED))
|
|
wp->lifeleft -= flFrametime * (0.95f - old_dot);
|
|
} else {
|
|
Warning(LOCATION, "Tried to make weapon '%s' home, but found it wasn't aspect-seeking or heat-seeking or a Javelin!", wip->name);
|
|
}
|
|
|
|
|
|
// Control speed based on dot product to goal. If close to straight ahead, move
|
|
// at max speed, else move slower based on how far from ahead.
|
|
if (old_dot < 0.90f) {
|
|
obj->phys_info.speed = MAX(0.2f, old_dot* (float) fabs(old_dot));
|
|
if (obj->phys_info.speed < max_speed*0.75f)
|
|
obj->phys_info.speed = max_speed*0.75f;
|
|
} else
|
|
obj->phys_info.speed = max_speed;
|
|
|
|
|
|
if (wip->acceleration_time > 0.0f) {
|
|
// Ramp up speed linearly for the given duration
|
|
if (Missiontime - wp->creation_time < fl2f(wip->acceleration_time)) {
|
|
float t;
|
|
|
|
t = f2fl(Missiontime - wp->creation_time) / wip->acceleration_time;
|
|
obj->phys_info.speed = wp->launch_speed + MAX(0.0f, wp->weapon_max_vel - wp->launch_speed) * t;
|
|
}
|
|
} else if (!(wip->wi_flags3 & WIF3_NO_HOMING_SPEED_RAMP) && Missiontime - wp->creation_time < i2f(1)) {
|
|
// Default behavior:
|
|
// For first second of weapon's life, it doesn't fly at top speed. It ramps up.
|
|
float t;
|
|
|
|
t = f2fl(Missiontime - wp->creation_time);
|
|
obj->phys_info.speed *= t*t;
|
|
}
|
|
|
|
Assert( obj->phys_info.speed > 0.0f );
|
|
|
|
vm_vec_copy_scale( &obj->phys_info.desired_vel, &obj->orient.vec.fvec, obj->phys_info.speed);
|
|
|
|
// turn the missile towards the target only if non-swarm. Homing swarm missiles choose
|
|
// a different vector to turn towards, this is done in swarm_update_direction().
|
|
if ( wp->swarm_index < 0 ) {
|
|
ai_turn_towards_vector(&target_pos, obj, frame_time, wip->turn_time, NULL, NULL, 0.0f, 0, NULL);
|
|
vel = vm_vec_mag(&obj->phys_info.desired_vel);
|
|
|
|
vm_vec_copy_scale(&obj->phys_info.desired_vel, &obj->orient.vec.fvec, vel);
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
// as Mike K did with ships -- break weapon into process_pre and process_post for code to execute
|
|
// before and after physics movement
|
|
|
|
void weapon_process_pre( object *obj, float frame_time)
|
|
{
|
|
if(obj->type != OBJ_WEAPON)
|
|
return;
|
|
|
|
weapon *wp = &Weapons[obj->instance];
|
|
weapon_info *wip = &Weapon_info[wp->weapon_info_index];
|
|
|
|
// if the object is a corkscrew style weapon, process it now
|
|
if((obj->type == OBJ_WEAPON) && (Weapons[obj->instance].cscrew_index >= 0)){
|
|
cscrew_process_pre(obj);
|
|
}
|
|
|
|
//WMC - Originally flak_maybe_detonate, moved here.
|
|
if(wp->det_range > 0.0f)
|
|
{
|
|
vec3d temp;
|
|
vm_vec_sub(&temp, &obj->pos, &wp->start_pos);
|
|
if(vm_vec_mag(&temp) >= wp->det_range){
|
|
weapon_detonate(obj);
|
|
}
|
|
}
|
|
|
|
//WMC - Maybe detonate weapon anyway!
|
|
if(wip->det_radius > 0.0f)
|
|
{
|
|
if((wp->homing_object != NULL) && (wp->homing_object->type != 0))
|
|
{
|
|
if(vm_vec_dist(&wp->homing_pos, &obj->pos) <= wip->det_radius)
|
|
{
|
|
weapon_detonate(obj);
|
|
}
|
|
} else if(wp->target_num > -1)
|
|
{
|
|
if(vm_vec_dist(&obj->pos, &Objects[wp->target_num].pos) <= wip->det_radius)
|
|
{
|
|
weapon_detonate(obj);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int Homing_hits = 0, Homing_misses = 0;
|
|
|
|
|
|
MONITOR( NumWeapons )
|
|
|
|
/**
|
|
* Maybe play a "whizz sound" if close enough to view position
|
|
*/
|
|
void weapon_maybe_play_flyby_sound(object *weapon_objp, weapon *wp)
|
|
{
|
|
// don't play flyby sounds too close together
|
|
if ( !timestamp_elapsed(Weapon_flyby_sound_timer) ) {
|
|
return;
|
|
}
|
|
|
|
if ( !(wp->weapon_flags & WF_PLAYED_FLYBY_SOUND) && (wp->weapon_flags & WF_CONSIDER_FOR_FLYBY_SOUND) ) {
|
|
float dist, dot, radius;
|
|
|
|
if ( (Weapon_info[wp->weapon_info_index].wi_flags & WIF_CORKSCREW) ) {
|
|
dist = vm_vec_dist_quick(&weapon_objp->last_pos, &Eye_position);
|
|
} else {
|
|
dist = vm_vec_dist_quick(&weapon_objp->pos, &Eye_position);
|
|
}
|
|
|
|
if ( Viewer_obj ) {
|
|
radius = Viewer_obj->radius;
|
|
} else {
|
|
radius = 0.0f;
|
|
}
|
|
|
|
if ( (dist > radius) && (dist < 55) ) {
|
|
vec3d vec_to_weapon;
|
|
|
|
vm_vec_sub(&vec_to_weapon, &weapon_objp->pos, &Eye_position);
|
|
vm_vec_normalize(&vec_to_weapon);
|
|
|
|
// ensure laser is in front of eye
|
|
dot = vm_vec_dot(&vec_to_weapon, &Eye_matrix.vec.fvec);
|
|
if ( dot < 0.1 ) {
|
|
return;
|
|
}
|
|
|
|
// ensure that laser is moving in similar direction to fvec
|
|
dot = vm_vec_dot(&vec_to_weapon, &weapon_objp->orient.vec.fvec);
|
|
|
|
if ( (dot < -0.80) && (dot > -0.98) ) {
|
|
if(Weapon_info[wp->weapon_info_index].flyby_snd != -1) {
|
|
snd_play_3d( &Snds[Weapon_info[wp->weapon_info_index].flyby_snd], &weapon_objp->pos, &Eye_position );
|
|
} else {
|
|
if ( Weapon_info[wp->weapon_info_index].subtype == WP_LASER ) {
|
|
snd_play_3d( &Snds[SND_WEAPON_FLYBY], &weapon_objp->pos, &Eye_position );
|
|
}
|
|
}
|
|
Weapon_flyby_sound_timer = timestamp(200);
|
|
wp->weapon_flags |= WF_PLAYED_FLYBY_SOUND;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// process a weapon after physics movement. MWA reorders some of the code on 8/13 for multiplayer. When
|
|
// adding something to this function, decide whether or not a client in a multiplayer game needs to do
|
|
// what is normally done in a single player game. Things like plotting an object on a radar, effect
|
|
// for exhaust are things that are done on all machines. Things which calculate weapon targets, new
|
|
// velocities, etc, are server only functions and should go after the if ( !MULTIPLAYER_MASTER ) statement
|
|
// See Allender if you cannot decide what to do.
|
|
void weapon_process_post(object * obj, float frame_time)
|
|
{
|
|
int num;
|
|
weapon_info *wip;
|
|
weapon *wp;
|
|
|
|
MONITOR_INC( NumWeapons, 1 );
|
|
|
|
Assert(obj->type == OBJ_WEAPON);
|
|
|
|
num = obj->instance;
|
|
|
|
#ifndef NDEBUG
|
|
int objnum;
|
|
objnum = OBJ_INDEX(obj);
|
|
Assert( Weapons[num].objnum == objnum );
|
|
#endif
|
|
|
|
wp = &Weapons[num];
|
|
|
|
wp->lifeleft -= frame_time;
|
|
|
|
wip = &Weapon_info[wp->weapon_info_index];
|
|
|
|
|
|
if (wip->wi_flags2 & WIF2_LOCAL_SSM)
|
|
{
|
|
if ((wp->lssm_stage != 5) && (wp->lssm_stage != 0))
|
|
{
|
|
wp->lifeleft += frame_time;
|
|
}
|
|
}
|
|
|
|
|
|
// check life left. Multiplayer client code will go through here as well. We must be careful in weapon_hit
|
|
// when killing a missile that spawn child weapons!!!!
|
|
if ( wp->lifeleft < 0.0f ) {
|
|
if ( wip->subtype & WP_MISSILE ) {
|
|
if(Game_mode & GM_MULTIPLAYER){
|
|
if ( !MULTIPLAYER_CLIENT || (MULTIPLAYER_CLIENT && (wp->lifeleft < -2.0f)) || (MULTIPLAYER_CLIENT && (wip->wi_flags & WIF_CHILD))) { // don't call this function multiplayer client -- host will send this packet to us
|
|
weapon_detonate(obj);
|
|
}
|
|
} else {
|
|
weapon_detonate(obj);
|
|
}
|
|
if (wip->wi_flags & WIF_HOMING) {
|
|
Homing_misses++;
|
|
}
|
|
} else {
|
|
obj->flags |= OF_SHOULD_BE_DEAD;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// plot homing missiles on the radar
|
|
if (((wip->wi_flags & WIF_BOMB) || (wip->wi_flags2 & WIF2_SHOWN_ON_RADAR)) && !(wip->wi_flags2 & WIF2_DONT_SHOW_ON_RADAR)) {
|
|
if ( hud_gauge_active(HUD_RADAR) ) {
|
|
radar_plot_object( obj );
|
|
}
|
|
}
|
|
|
|
// trail missiles
|
|
if ((wip->wi_flags & WIF_TRAIL) && !(wip->wi_flags & WIF_CORKSCREW)) {
|
|
if ( (wp->trail_ptr != NULL ) && (wp->lssm_stage!=3)) {
|
|
vec3d pos;
|
|
|
|
if (wip->render_type == WRT_LASER) {
|
|
// place tail origin in center of the bolt
|
|
vm_vec_scale_add(&pos, &obj->pos, &obj->orient.vec.fvec, (wip->laser_length / 2));
|
|
} else {
|
|
pos = obj->pos;
|
|
}
|
|
|
|
if (trail_stamp_elapsed(wp->trail_ptr)) {
|
|
|
|
trail_add_segment( wp->trail_ptr, &pos );
|
|
|
|
trail_set_stamp(wp->trail_ptr);
|
|
} else {
|
|
trail_set_segment( wp->trail_ptr, &pos );
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
if ( wip->wi_flags & WIF_THRUSTER ) {
|
|
ship_do_weapon_thruster_frame( wp, obj, flFrametime );
|
|
}
|
|
|
|
// maybe play a "whizz sound" if close enough to view position
|
|
#ifndef NDEBUG
|
|
if ( Weapon_flyby_sound_enabled ) {
|
|
weapon_maybe_play_flyby_sound(obj, wp);
|
|
}
|
|
#else
|
|
weapon_maybe_play_flyby_sound(obj, wp);
|
|
#endif
|
|
|
|
// If our target is still valid, then update some info.
|
|
if (wp->target_num != -1) {
|
|
if (Objects[wp->target_num].signature == wp->target_sig) {
|
|
float cur_dist;
|
|
vec3d v0;
|
|
|
|
vm_vec_avg(&v0, &obj->pos, &obj->last_pos);
|
|
|
|
cur_dist = vm_vec_dist_quick(&v0, &Objects[wp->target_num].pos);
|
|
|
|
if (cur_dist < wp->nearest_dist) {
|
|
wp->nearest_dist = cur_dist;
|
|
} else if (cur_dist > wp->nearest_dist + 1.0f) {
|
|
float dot;
|
|
vec3d tvec;
|
|
ai_info *parent_aip;
|
|
|
|
parent_aip = NULL;
|
|
if (obj->parent != Player_obj-Objects) {
|
|
parent_aip = &Ai_info[Ships[Objects[obj->parent].instance].ai_index];
|
|
}
|
|
|
|
vm_vec_normalized_dir(&tvec, &v0, &Objects[wp->target_num].pos);
|
|
dot = vm_vec_dot(&tvec, &Objects[wp->target_num].orient.vec.fvec);
|
|
wp->target_num = -1;
|
|
|
|
// Learn! If over-shooting or under-shooting, compensate.
|
|
// Really need to compensate for left/right errors. This does no good against someone circling
|
|
// in a plane perpendicular to the attacker's forward vector.
|
|
if (parent_aip != NULL) {
|
|
if (cur_dist > 100.0f)
|
|
parent_aip->lead_scale = 0.0f;
|
|
|
|
if (dot < -0.1f){
|
|
parent_aip->lead_scale += cur_dist/2000.0f;
|
|
} else if (dot > 0.1f) {
|
|
parent_aip->lead_scale -= cur_dist/2000.0f;
|
|
}
|
|
|
|
if (fl_abs(parent_aip->lead_scale) > 1.0f){
|
|
parent_aip->lead_scale *= 0.9f;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(wip->wi_flags & WIF_PARTICLE_SPEW){
|
|
weapon_maybe_spew_particle(obj);
|
|
}
|
|
|
|
// a single player or multiplayer server function -- it affects actual weapon movement.
|
|
if (wip->wi_flags & WIF_HOMING) {
|
|
weapon_home(obj, num, frame_time);
|
|
|
|
// If this is a swarm type missile,
|
|
if ( wp->swarm_index >= 0 ) {
|
|
swarm_update_direction(obj, frame_time);
|
|
}
|
|
|
|
if( wp->cscrew_index >= 0) {
|
|
cscrew_process_post(obj);
|
|
}
|
|
} else if (wip->acceleration_time > 0.0f) {
|
|
if (Missiontime - wp->creation_time < fl2f(wip->acceleration_time)) {
|
|
float t;
|
|
|
|
t = f2fl(Missiontime - wp->creation_time) / wip->acceleration_time;
|
|
obj->phys_info.speed = wp->launch_speed + MAX(0.0f, wp->weapon_max_vel - wp->launch_speed) * t;
|
|
} else {
|
|
obj->phys_info.speed = wip->max_speed;;
|
|
}
|
|
|
|
vm_vec_copy_scale( &obj->phys_info.desired_vel, &obj->orient.vec.fvec, obj->phys_info.speed);
|
|
}
|
|
|
|
//local ssm stuff
|
|
if (wip->wi_flags2 & WIF2_LOCAL_SSM)
|
|
{
|
|
//go into subspace if the missile is locked and its time to warpout
|
|
if ((wp->lssm_stage==1) && (timestamp_elapsed(wp->lssm_warpout_time)))
|
|
{
|
|
//if we don't have a lock at this point, just stay in normal space
|
|
if (wp->homing_object == &obj_used_list)
|
|
{
|
|
wp->lssm_stage=0;
|
|
return;
|
|
}
|
|
|
|
//point where to warpout
|
|
vec3d warpout;
|
|
|
|
//create a warp effect
|
|
vm_vec_copy_scale(&warpout,&obj->phys_info.vel,3.0f);
|
|
|
|
//set the time the warphole stays open, minimum of 7 seconds
|
|
wp->lssm_warp_time = ((obj->radius * 2) / (obj->phys_info.speed)) +1.5f;
|
|
wp->lssm_warp_time = MAX(wp->lssm_warp_time,7.0f);
|
|
|
|
//calculate the percerentage of the warpholes life at which the missile is fully in subspace.
|
|
wp->lssm_warp_pct = 1.0f - (3.0f/wp->lssm_warp_time);
|
|
|
|
//create the warphole
|
|
vm_vec_add2(&warpout,&obj->pos);
|
|
wp->lssm_warp_idx=fireball_create(&warpout, FIREBALL_WARP, FIREBALL_WARP_EFFECT, -1,obj->radius*1.5f,1,&vmd_zero_vector,wp->lssm_warp_time,0,&obj->orient);
|
|
|
|
if (wp->lssm_warp_idx < 0) {
|
|
mprintf(("LSSM: Failed to create warp effect! Please report if this happens frequently.\n"));
|
|
// Abort warping
|
|
wp->lssm_stage = 0;
|
|
} else {
|
|
wp->lssm_stage = 2;
|
|
}
|
|
}
|
|
|
|
//its just entered subspace subspace. don't collide or render
|
|
if ((wp->lssm_stage==2) && (fireball_lifeleft_percent(&Objects[wp->lssm_warp_idx]) <= wp->lssm_warp_pct))
|
|
{
|
|
uint flags=obj->flags & ~(OF_RENDERS | OF_COLLIDES);
|
|
|
|
obj_set_flags(obj, flags);
|
|
|
|
//get the position of the target, and estimate its position when it warps out
|
|
//so we have an idea of where it will be.
|
|
if (wp->target_num >= 0)
|
|
{
|
|
vm_vec_scale_add(&wp->lssm_target_pos, &Objects[wp->target_num].pos, &Objects[wp->target_num].phys_info.vel, (float)wip->lssm_warpin_delay / 1000.0f);
|
|
}
|
|
else
|
|
{
|
|
// Our target is invalid, just jump to our position
|
|
wp->lssm_target_pos = obj->pos;
|
|
}
|
|
|
|
wp->lssm_stage=3;
|
|
|
|
}
|
|
|
|
//time to warp in.
|
|
if ((wp->lssm_stage==3) && (timestamp_elapsed(wp->lssm_warpin_time)))
|
|
{
|
|
|
|
vec3d warpin;
|
|
object* target_objp=wp->homing_object;
|
|
vec3d fvec;
|
|
matrix orient;
|
|
|
|
//spawn the ssm at a random point in a circle around the target
|
|
vm_vec_random_in_circle(&warpin, &wp->lssm_target_pos, &target_objp->orient, wip->lssm_warpin_radius + target_objp->radius,1);
|
|
|
|
//orient the missile properly
|
|
vm_vec_sub(&fvec,&wp->lssm_target_pos, &warpin);
|
|
vm_vector_2_matrix(&orient,&fvec,NULL,NULL);
|
|
|
|
//create a warpin effect
|
|
wp->lssm_warp_idx=fireball_create(&warpin, FIREBALL_WARP, FIREBALL_WARP_EFFECT, -1,obj->radius*1.5f,0,&vmd_zero_vector,wp->lssm_warp_time,0,&orient);
|
|
|
|
obj->orient=orient;
|
|
obj->pos=warpin;
|
|
obj->phys_info.speed=0;
|
|
obj->phys_info.desired_vel = vmd_zero_vector;
|
|
obj->phys_info.vel = obj->phys_info.desired_vel;
|
|
|
|
wp->lssm_stage=4;
|
|
|
|
}
|
|
|
|
|
|
//done warping in. render and collide it. let the fun begin
|
|
if ((wp->lssm_stage==4) && (fireball_lifeleft_percent(&Objects[wp->lssm_warp_idx]) <=0.5f))
|
|
{
|
|
vm_vec_copy_scale(&obj->phys_info.desired_vel, &obj->orient.vec.fvec, wip->lssm_stage5_vel );
|
|
obj->phys_info.vel = obj->phys_info.desired_vel;
|
|
obj->phys_info.speed = vm_vec_mag(&obj->phys_info.desired_vel);
|
|
|
|
wp->lssm_stage=5;
|
|
|
|
uint flags=obj->flags | OF_RENDERS | OF_COLLIDES;
|
|
|
|
obj_set_flags(obj,flags);
|
|
}
|
|
}
|
|
|
|
if (wip->hud_in_flight_snd >= 0 && obj->parent_sig == Player_obj->signature)
|
|
{
|
|
bool play_sound = false;
|
|
switch (wip->in_flight_play_type)
|
|
{
|
|
case TARGETED:
|
|
play_sound = wp->homing_object != &obj_used_list;
|
|
break;
|
|
case UNTARGETED:
|
|
play_sound = wp->homing_object == &obj_used_list;
|
|
break;
|
|
case ALWAYS:
|
|
play_sound = true;
|
|
break;
|
|
default:
|
|
Error(LOCATION, "Unknown in-flight sound status %d!", (int) wip->in_flight_play_type);
|
|
break;
|
|
}
|
|
|
|
if (play_sound)
|
|
{
|
|
if (wp->hud_in_flight_snd_sig < 0 || !snd_is_playing(wp->hud_in_flight_snd_sig))
|
|
{
|
|
wp->hud_in_flight_snd_sig = snd_play_looping(&Snds[wip->hud_in_flight_snd]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update weapon tracking information.
|
|
*/
|
|
void weapon_set_tracking_info(int weapon_objnum, int parent_objnum, int target_objnum, int target_is_locked, ship_subsys *target_subsys)
|
|
{
|
|
object *parent_objp;
|
|
weapon *wp;
|
|
weapon_info *wip;
|
|
int targeting_same = 0;
|
|
|
|
if ( weapon_objnum < 0 ) {
|
|
return;
|
|
}
|
|
|
|
Assert(Objects[weapon_objnum].type == OBJ_WEAPON);
|
|
|
|
wp = &Weapons[Objects[weapon_objnum].instance];
|
|
wip = &Weapon_info[wp->weapon_info_index];
|
|
|
|
if (parent_objnum >= 0) {
|
|
parent_objp = &Objects[parent_objnum];
|
|
Assert(parent_objp->type == OBJ_SHIP);
|
|
} else {
|
|
parent_objp = NULL;
|
|
}
|
|
|
|
if ( parent_objp == NULL || Ships[parent_objp->instance].ai_index >= 0 ) {
|
|
int target_team = -1;
|
|
if ( target_objnum >= 0 ) {
|
|
int obj_type = Objects[target_objnum].type;
|
|
if ( (obj_type == OBJ_SHIP) || (obj_type == OBJ_WEAPON) ) {
|
|
target_team = obj_team(&Objects[target_objnum]);
|
|
}
|
|
}
|
|
|
|
// determining if we're targeting the same team
|
|
if (parent_objp != NULL && Ships[parent_objp->instance].team == target_team){
|
|
targeting_same = 1;
|
|
|
|
// Goober5000 - if we're going bonkers, pretend we're not targeting our own team
|
|
ai_info *parent_aip = &Ai_info[Ships[parent_objp->instance].ai_index];
|
|
if (parent_aip->active_goal != AI_GOAL_NONE && parent_aip->active_goal != AI_ACTIVE_GOAL_DYNAMIC) {
|
|
if (parent_aip->goals[parent_aip->active_goal].flags & AIGF_TARGET_OWN_TEAM) {
|
|
targeting_same = 0;
|
|
}
|
|
}
|
|
} else {
|
|
targeting_same = 0;
|
|
}
|
|
|
|
if ((target_objnum != -1) && (!targeting_same || (MULTI_DOGFIGHT && (target_team == Iff_traitor))) ) {
|
|
wp->target_num = target_objnum;
|
|
wp->target_sig = Objects[target_objnum].signature;
|
|
wp->nearest_dist = 99999.0f;
|
|
if ( (wip->wi_flags & WIF_HOMING_ASPECT) && target_is_locked) {
|
|
wp->homing_object = &Objects[target_objnum];
|
|
wp->homing_subsys = target_subsys;
|
|
weapon_maybe_play_warning(wp);
|
|
} else if ( (wip->wi_flags & WIF_HOMING_JAVELIN) && target_is_locked) {
|
|
if ((Objects[target_objnum].type == OBJ_SHIP) &&
|
|
( (wp->homing_subsys == NULL) ||
|
|
(wp->homing_subsys->system_info->type != SUBSYSTEM_ENGINE) )) {
|
|
ship *target_ship = &Ships[Objects[target_objnum].instance];
|
|
wp->homing_subsys = ship_get_closest_subsys_in_sight(target_ship, SUBSYSTEM_ENGINE, &Objects[weapon_objnum].pos);
|
|
if (wp->homing_subsys == NULL) {
|
|
wp->homing_object = &obj_used_list;
|
|
} else {
|
|
Assert(wp->homing_subsys->parent_objnum == target_objnum);
|
|
wp->homing_object = &Objects[target_objnum];
|
|
weapon_maybe_play_warning(wp);
|
|
}
|
|
} else {
|
|
wp->homing_object = &Objects[target_objnum];
|
|
wp->homing_subsys = target_subsys;
|
|
weapon_maybe_play_warning(wp);
|
|
}
|
|
} else if ( wip->wi_flags & WIF_HOMING_HEAT ) {
|
|
// Make a heat seeking missile try to home. If the target is outside the view cone, it will
|
|
// immediately drop it and try to find one in its view cone.
|
|
if ((target_objnum != -1) && !(wip->wi_flags2 & WIF2_UNTARGETED_HEAT_SEEKER)) {
|
|
wp->homing_object = &Objects[target_objnum];
|
|
wp->homing_subsys = target_subsys;
|
|
weapon_maybe_play_warning(wp);
|
|
} else {
|
|
wp->homing_object = &obj_used_list;
|
|
wp->homing_subsys = NULL;
|
|
}
|
|
}
|
|
} else {
|
|
wp->target_num = -1;
|
|
wp->target_sig = -1;
|
|
}
|
|
|
|
// If missile is locked on target, increase its lifetime by 20% since missiles can be fired at limit of range
|
|
// as defined by velocity*lifeleft, but missiles often slow down a bit, plus can be fired at a moving away target.
|
|
// Confusing to many players when their missiles run out of gas before getting to target.
|
|
// DB - removed 7:14 pm 9/6/99. was totally messing up lifetimes for all weapons.
|
|
// 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
|
|
// put a sanity check in the color changing laser code that was broken by this code.
|
|
if (target_is_locked && (wp->target_num != -1) && (wip->wi_flags & WIF_LOCKED_HOMING) ) {
|
|
wp->lifeleft *= LOCKED_HOMING_EXTENDED_LIFE_FACTOR;
|
|
if (MULTIPLAYER_MASTER) {
|
|
wp->weapon_flags |= WF_HOMING_UPDATE_NEEDED;
|
|
}
|
|
}
|
|
|
|
ai_update_danger_weapon(target_objnum, weapon_objnum);
|
|
}
|
|
}
|
|
|
|
size_t* get_pointer_to_weapon_fire_pattern_index(int weapon_type, ship* shipp, ship_subsys * src_turret)
|
|
{
|
|
Assert( shipp != NULL );
|
|
ship_weapon* ship_weapon_p = &(shipp->weapons);
|
|
if(src_turret)
|
|
{
|
|
ship_weapon_p = &src_turret->weapons;
|
|
}
|
|
Assert( ship_weapon_p != NULL );
|
|
|
|
// search for the corresponding bank pattern index for the weapon_type that is being fired.
|
|
// Note: Because a weapon_type may not be unique to a weapon bank per ship this search may attribute
|
|
// the weapon to the wrong bank. Hopefully this isn't a problem.
|
|
for ( int pi = 0; pi < MAX_SHIP_PRIMARY_BANKS; pi++ ) {
|
|
if ( ship_weapon_p->primary_bank_weapons[pi] == weapon_type ) {
|
|
return &(ship_weapon_p->primary_bank_pattern_index[pi]);
|
|
}
|
|
}
|
|
for ( int si = 0; si < MAX_SHIP_SECONDARY_BANKS; si++ ) {
|
|
if ( ship_weapon_p->secondary_bank_weapons[si] == weapon_type ) {
|
|
return &(ship_weapon_p->secondary_bank_pattern_index[si]);
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Create a weapon object
|
|
*
|
|
* @return Index of weapon in the Objects[] array, -1 if the weapon object was not created
|
|
*/
|
|
int Weapons_created = 0;
|
|
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)
|
|
{
|
|
int n, objnum;
|
|
int num_deleted;
|
|
object *objp, *parent_objp=NULL;
|
|
weapon *wp;
|
|
weapon_info *wip;
|
|
|
|
Assert(weapon_type >= 0 && weapon_type < Num_weapon_types);
|
|
|
|
wip = &Weapon_info[weapon_type];
|
|
|
|
// beam weapons should never come through here!
|
|
if(wip->wi_flags & WIF_BEAM)
|
|
{
|
|
Warning(LOCATION, "An attempt to fire a beam ('%s') through weapon_create() was made.\n", wip->name);
|
|
return -1;
|
|
}
|
|
|
|
parent_objp = NULL;
|
|
if(parent_objnum >= 0){
|
|
parent_objp = &Objects[parent_objnum];
|
|
}
|
|
|
|
if ( (wip->num_substitution_patterns > 0) && (parent_objp != NULL)) {
|
|
// using substitution
|
|
|
|
// get to the instance of the gun
|
|
Assertion( parent_objp->type == OBJ_SHIP, "Expected type OBJ_SHIP, got %d", parent_objp->type );
|
|
Assertion( (parent_objp->instance < MAX_SHIPS) && (parent_objp->instance >= 0),
|
|
"Ship index is %d, which is out of range [%d,%d)", parent_objp->instance, 0, MAX_SHIPS);
|
|
ship* parent_shipp = &(Ships[parent_objp->instance]);
|
|
Assert( parent_shipp != NULL );
|
|
|
|
size_t *position = get_pointer_to_weapon_fire_pattern_index(weapon_type, parent_shipp, src_turret);
|
|
Assertion( position != NULL, "'%s' is trying to fire a weapon that is not selected", Ships[parent_objp->instance].ship_name );
|
|
|
|
++(*position);
|
|
*position = (*position) % wip->num_substitution_patterns;
|
|
|
|
if ( wip->weapon_substitution_pattern[*position] == -1 ) {
|
|
// weapon doesn't want any sub
|
|
return -1;
|
|
} else if ( wip->weapon_substitution_pattern[*position] != weapon_type ) {
|
|
// weapon wants to sub with weapon other than me
|
|
return weapon_create(pos, porient, wip->weapon_substitution_pattern[*position], parent_objnum, group_id, is_locked, is_spawned, fof_cooldown);
|
|
}
|
|
}
|
|
|
|
num_deleted = 0;
|
|
if (Num_weapons >= MAX_WEAPONS-5) {
|
|
|
|
//No, do remove for AI ships -- MK, 3/12/98 // don't need to try and delete weapons for ai ships
|
|
//if ( !(Objects[parent_objnum].flags & OF_PLAYER_SHIP) )
|
|
// return -1;
|
|
|
|
num_deleted = collide_remove_weapons();
|
|
|
|
nprintf(("WARNING", "Deleted %d weapons because of lack of slots\n", num_deleted));
|
|
if (num_deleted == 0){
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
for (n=0; n<MAX_WEAPONS; n++ ){
|
|
if (Weapons[n].weapon_info_index < 0){
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (n == MAX_WEAPONS) {
|
|
// if we supposedly deleted weapons above, what happened here!!!!
|
|
if (num_deleted){
|
|
Int3(); // get allender -- something funny is going on!!!
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
// make sure we are loaded and useable
|
|
if ( (wip->render_type == WRT_POF) && (wip->model_num < 0) ) {
|
|
wip->model_num = model_load(wip->pofbitmap_name, 0, NULL);
|
|
|
|
if (wip->model_num < 0) {
|
|
Int3();
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
// make sure that our textures are loaded as well
|
|
if ( !used_weapons[weapon_type] )
|
|
weapon_load_bitmaps(weapon_type);
|
|
|
|
//I am hopeing that this way does not alter the input orient matrix
|
|
//Feild of Fire code -Bobboau
|
|
matrix morient;
|
|
matrix *orient;
|
|
|
|
if(porient != NULL) {
|
|
morient = *porient;
|
|
} else {
|
|
morient = vmd_identity_matrix;
|
|
}
|
|
|
|
orient = &morient;
|
|
|
|
float combined_fof = wip->field_of_fire;
|
|
// If there is a fof_cooldown value, increase the spread linearly
|
|
if (fof_cooldown != 0.0f) {
|
|
combined_fof = wip->field_of_fire + (fof_cooldown * wip->max_fof_spread);
|
|
}
|
|
|
|
if(combined_fof > 0.0f){
|
|
vec3d f;
|
|
vm_vec_random_cone(&f, &orient->vec.fvec, combined_fof);
|
|
vm_vec_normalize(&f);
|
|
vm_vector_2_matrix( orient, &f, NULL, NULL);
|
|
}
|
|
|
|
Weapons_created++;
|
|
objnum = obj_create( OBJ_WEAPON, parent_objnum, n, orient, pos, 2.0f, OF_RENDERS | OF_COLLIDES | OF_PHYSICS );
|
|
Assert(objnum >= 0);
|
|
objp = &Objects[objnum];
|
|
|
|
// Create laser n!
|
|
wp = &Weapons[n];
|
|
|
|
// check if laser or dumbfire missile
|
|
// set physics flag to allow optimization
|
|
if ((wip->subtype == WP_LASER) || ((wip->subtype == WP_MISSILE) && !(wip->wi_flags & WIF_HOMING))) {
|
|
// set physics flag
|
|
objp->phys_info.flags |= PF_CONST_VEL;
|
|
}
|
|
|
|
wp->start_pos = *pos;
|
|
wp->objnum = objnum;
|
|
wp->homing_object = &obj_used_list; // Assume not homing on anything.
|
|
wp->homing_subsys = NULL;
|
|
wp->creation_time = Missiontime;
|
|
wp->group_id = group_id;
|
|
|
|
// we don't necessarily need a parent
|
|
if(parent_objp != NULL){
|
|
Assert(parent_objp->type == OBJ_SHIP); // Get Mike, a non-ship has fired a weapon!
|
|
Assert((parent_objp->instance >= 0) && (parent_objp->instance < MAX_SHIPS));
|
|
wp->team = Ships[parent_objp->instance].team;
|
|
wp->species = Ship_info[Ships[parent_objp->instance].ship_info_index].species;
|
|
} else {
|
|
// ugh - we need to prevent bad array accesses
|
|
wp->team = Iff_traitor;
|
|
wp->species = 0;
|
|
}
|
|
wp->turret_subsys = NULL;
|
|
vm_vec_zero(&wp->homing_pos);
|
|
wp->weapon_flags = 0;
|
|
wp->target_sig = -1;
|
|
wp->cmeasure_ignore_objnum = -1;
|
|
wp->cmeasure_chase_objnum = -1;
|
|
wp->det_range = wip->det_range;
|
|
|
|
// Init the thruster info
|
|
wp->thruster_bitmap = -1;
|
|
wp->thruster_frame = 0.0f;
|
|
wp->thruster_glow_bitmap = -1;
|
|
wp->thruster_glow_noise = 1.0f;
|
|
wp->thruster_glow_frame = 0.0f;
|
|
|
|
// init the laser info
|
|
wp->laser_bitmap_frame = 0.0f;
|
|
wp->laser_glow_bitmap_frame = 0.0f;
|
|
|
|
if ( wip->wi_flags & WIF_SWARM ) {
|
|
wp->swarm_index = (short)swarm_create();
|
|
} else {
|
|
wp->swarm_index = -1;
|
|
}
|
|
|
|
// if this is a particle spewing weapon, setup some stuff
|
|
if (wip->wi_flags & WIF_PARTICLE_SPEW) {
|
|
for (size_t s = 0; s < MAX_PARTICLE_SPEWERS; s++) { // allow for multiple time values
|
|
if (wip->particle_spewers[s].particle_spew_type != PSPEW_NONE) {
|
|
wp->particle_spew_time[s] = -1;
|
|
wp->particle_spew_rand = frand_range(0, PI2); // per weapon randomness
|
|
}
|
|
}
|
|
}
|
|
|
|
// assign the network signature. The starting sig is sent to all clients, so this call should
|
|
// result in the same net signature numbers getting assigned to every player in the game
|
|
if ( Game_mode & GM_MULTIPLAYER ) {
|
|
if(wip->subtype == WP_MISSILE){
|
|
Objects[objnum].net_signature = multi_assign_network_signature( MULTI_SIG_NON_PERMANENT );
|
|
|
|
// for weapons that respawn, add the number of respawnable weapons to the net signature pool
|
|
// to reserve N signatures for the spawned weapons
|
|
if ( wip->wi_flags & WIF_SPAWN ){
|
|
multi_set_network_signature( (ushort)(Objects[objnum].net_signature + wip->total_children_spawned), MULTI_SIG_NON_PERMANENT );
|
|
}
|
|
} else {
|
|
Objects[objnum].net_signature = multi_assign_network_signature( MULTI_SIG_NON_PERMANENT );
|
|
}
|
|
// for multiplayer clients, when creating lasers, add some more life to the lasers. This helps
|
|
// to overcome some problems associated with lasers dying on client machine before they get message
|
|
// from server saying it hit something.
|
|
}
|
|
|
|
//Check if we want to gen a random number
|
|
//This is used for lifetime min/max
|
|
float rand_val;
|
|
if ( Game_mode & GM_NORMAL ){
|
|
rand_val = frand();
|
|
} else {
|
|
rand_val = static_randf(Objects[objnum].net_signature);
|
|
}
|
|
|
|
wp->weapon_info_index = weapon_type;
|
|
if(wip->life_min < 0.0f && wip->life_max < 0.0f) {
|
|
wp->lifeleft = wip->lifetime;
|
|
} else {
|
|
wp->lifeleft = (rand_val) * (wip->life_max - wip->life_min) / wip->life_min;
|
|
if((wip->wi_flags & WIF_CMEASURE) && (parent_objp != NULL) && (parent_objp->flags & OF_PLAYER_SHIP)) {
|
|
wp->lifeleft *= The_mission.ai_profile->cmeasure_life_scale[Game_skill_level];
|
|
}
|
|
wp->lifeleft = wip->life_min + wp->lifeleft * (wip->life_max - wip->life_min);
|
|
}
|
|
|
|
if(wip->wi_flags & WIF_CMEASURE) {
|
|
//2-frame homing check, to fend off sync errors
|
|
Cmeasures_homing_check = 2;
|
|
}
|
|
|
|
// Make remote detonate missiles look like they're getting detonated by firer simply by giving them variable lifetimes.
|
|
if (parent_objp != NULL && !(parent_objp->flags & OF_PLAYER_SHIP) && (wip->wi_flags & WIF_REMOTE)) {
|
|
wp->lifeleft = wp->lifeleft/2.0f + rand_val * wp->lifeleft/2.0f;
|
|
}
|
|
|
|
objp->phys_info.mass = wip->mass;
|
|
objp->phys_info.side_slip_time_const = 0.0f;
|
|
objp->phys_info.rotdamp = 0.0f;
|
|
vm_vec_zero(&objp->phys_info.max_vel);
|
|
objp->phys_info.max_vel.xyz.z = wip->max_speed;
|
|
vm_vec_zero(&objp->phys_info.max_rotvel);
|
|
objp->shield_quadrant[0] = wip->damage;
|
|
if (wip->weapon_hitpoints > 0){
|
|
objp->hull_strength = (float) wip->weapon_hitpoints;
|
|
} else {
|
|
objp->hull_strength = 0.0f;
|
|
}
|
|
|
|
if ( wip->render_type == WRT_POF ) {
|
|
objp->radius = model_get_radius(wip->model_num);
|
|
} else if ( wip->render_type == WRT_LASER ) {
|
|
objp->radius = wip->laser_head_radius;
|
|
}
|
|
|
|
// Set desired velocity and initial velocity.
|
|
// For lasers, velocity is always the same.
|
|
// For missiles, it is a small amount plus the firing ship's velocity.
|
|
// For missiles, the velocity trends towards some goal.
|
|
// Note: If you change how speed works here, such as adding in speed of parent ship, you'll need to change the AI code
|
|
// that predicts collision points. See Mike Kulas or Dave Andsager. (Or see ai_get_weapon_speed().)
|
|
if (wip->acceleration_time > 0.0f) {
|
|
vm_vec_copy_scale(&objp->phys_info.desired_vel, &objp->orient.vec.fvec, 0.01f ); // Tiny initial velocity to avoid possible null vec issues
|
|
objp->phys_info.vel = objp->phys_info.desired_vel;
|
|
objp->phys_info.speed = 0.0f;
|
|
wp->launch_speed = 0.0f;
|
|
} else if (!(wip->wi_flags & WIF_HOMING)) {
|
|
vm_vec_copy_scale(&objp->phys_info.desired_vel, &objp->orient.vec.fvec, objp->phys_info.max_vel.xyz.z );
|
|
objp->phys_info.vel = objp->phys_info.desired_vel;
|
|
objp->phys_info.speed = vm_vec_mag(&objp->phys_info.desired_vel);
|
|
} else {
|
|
// For weapons that home, set velocity to sum of forward component of parent's velocity and 1/4 weapon's max speed.
|
|
// Note that it is important to extract the forward component of the parent's velocity to factor out sliding, else
|
|
// the missile will not be moving forward.
|
|
if(parent_objp != NULL){
|
|
if (wip->free_flight_time > 0.0)
|
|
vm_vec_copy_scale(&objp->phys_info.desired_vel, &objp->orient.vec.fvec, vm_vec_dot(&parent_objp->phys_info.vel, &parent_objp->orient.vec.fvec) + objp->phys_info.max_vel.xyz.z/4 );
|
|
else
|
|
vm_vec_copy_scale(&objp->phys_info.desired_vel, &objp->orient.vec.fvec, objp->phys_info.max_vel.xyz.z );
|
|
} else {
|
|
if (!is_locked && wip->free_flight_time > 0.0)
|
|
{
|
|
vm_vec_copy_scale(&objp->phys_info.desired_vel, &objp->orient.vec.fvec, objp->phys_info.max_vel.xyz.z/4 );
|
|
}
|
|
else
|
|
{
|
|
vm_vec_copy_scale(&objp->phys_info.desired_vel, &objp->orient.vec.fvec, objp->phys_info.max_vel.xyz.z );
|
|
}
|
|
}
|
|
objp->phys_info.vel = objp->phys_info.desired_vel;
|
|
objp->phys_info.speed = vm_vec_mag(&objp->phys_info.vel);
|
|
}
|
|
|
|
wp->weapon_max_vel = objp->phys_info.max_vel.xyz.z;
|
|
|
|
// Turey - maybe make the initial speed of the weapon take into account the velocity of the parent.
|
|
// Improves aiming during gliding.
|
|
if ((parent_objp != NULL) && (The_mission.ai_profile->flags & AIPF_USE_ADDITIVE_WEAPON_VELOCITY)) {
|
|
float pspeed = vm_vec_mag( &parent_objp->phys_info.vel );
|
|
vm_vec_scale_add2( &objp->phys_info.vel, &parent_objp->phys_info.vel, wip->vel_inherit_amount );
|
|
wp->weapon_max_vel += pspeed * wip->vel_inherit_amount;
|
|
objp->phys_info.speed = vm_vec_mag(&objp->phys_info.vel);
|
|
|
|
if (wip->acceleration_time > 0.0f)
|
|
wp->launch_speed += pspeed;
|
|
}
|
|
|
|
// create the corkscrew
|
|
if ( wip->wi_flags & WIF_CORKSCREW ) {
|
|
wp->cscrew_index = (short)cscrew_create(objp);
|
|
} else {
|
|
wp->cscrew_index = -1;
|
|
}
|
|
|
|
if (wip->wi_flags2 & WIF2_LOCAL_SSM)
|
|
{
|
|
|
|
Assert(parent_objp); //local ssms must have a parent
|
|
|
|
wp->lssm_warpout_time=timestamp(wip->lssm_warpout_delay);
|
|
wp->lssm_warpin_time=timestamp(wip->lssm_warpout_delay + wip->lssm_warpin_delay);
|
|
wp->lssm_stage=1;
|
|
}
|
|
else{
|
|
wp->lssm_stage=-1;
|
|
}
|
|
|
|
|
|
// if this is a flak weapon shell, make it so
|
|
// NOTE : this function will change some fundamental things about the weapon object
|
|
if ( (wip->wi_flags & WIF_FLAK) && !(wip->wi_flags2 & WIF2_RENDER_FLAK) ) {
|
|
obj_set_flags(&Objects[wp->objnum], Objects[wp->objnum].flags & ~(OF_RENDERS));
|
|
}
|
|
|
|
wp->missile_list_index = -1;
|
|
// If this is a missile, then add it to the Missile_obj_list
|
|
if ( wip->subtype == WP_MISSILE ) {
|
|
wp->missile_list_index = missile_obj_list_add(objnum);
|
|
}
|
|
|
|
if (wip->wi_flags & WIF_TRAIL /*&& !(wip->wi_flags & WIF_CORKSCREW) */) {
|
|
wp->trail_ptr = trail_create(&wip->tr_info);
|
|
|
|
if ( wp->trail_ptr != NULL ) {
|
|
// Add two segments. One to stay at launch pos, one to move.
|
|
trail_add_segment( wp->trail_ptr, &objp->pos );
|
|
trail_add_segment( wp->trail_ptr, &objp->pos );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//If a weapon has no trails, make sure we don't try to do anything with them.
|
|
wp->trail_ptr = NULL;
|
|
}
|
|
|
|
// Ensure weapon flyby sound doesn't get played for player lasers
|
|
if ( parent_objp != NULL && parent_objp == Player_obj ) {
|
|
wp->weapon_flags |= WF_PLAYED_FLYBY_SOUND;
|
|
}
|
|
|
|
wp->pick_big_attack_point_timestamp = timestamp(1);
|
|
|
|
// Set detail levels for POF-type weapons.
|
|
if (Weapon_info[wp->weapon_info_index].model_num != -1) {
|
|
polymodel * pm;
|
|
int i;
|
|
pm = model_get(Weapon_info[wp->weapon_info_index].model_num);
|
|
|
|
for (i=0; i<pm->n_detail_levels; i++){
|
|
// for weapons, detail levels are all preset to -1
|
|
if (wip->detail_distance[i] >= 0)
|
|
pm->detail_depth[i] = i2fl(wip->detail_distance[i]);
|
|
else
|
|
pm->detail_depth[i] = (objp->radius*20.0f + 20.0f) * i;
|
|
}
|
|
|
|
#ifndef NDEBUG
|
|
// since debug builds always have cheats enabled, we don't necessarily get the chance
|
|
// to enable thrusters for previously non-loaded weapons (ie, weapons_page_in_cheats())
|
|
// when using cheat-keys, so we need to make sure and enable thrusters here if needed
|
|
if (pm->n_thrusters > 0) {
|
|
wip->wi_flags |= WIF_THRUSTER;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// if the weapon was fired locked
|
|
if(is_locked){
|
|
wp->weapon_flags |= WF_LOCKED_WHEN_FIRED;
|
|
}
|
|
|
|
//if the weapon was spawned from a spawning type weapon
|
|
if(is_spawned){
|
|
wp->weapon_flags |= WF_SPAWNED;
|
|
}
|
|
|
|
wp->alpha_current = -1.0f;
|
|
wp->alpha_backward = 0;
|
|
|
|
wp->collisionOccured = false;
|
|
wp->hud_in_flight_snd_sig = -1;
|
|
|
|
Num_weapons++;
|
|
|
|
// reset the damage record fields (for scoring purposes)
|
|
wp->total_damage_received = 0.0f;
|
|
for(int i=0;i<MAX_WEP_DAMAGE_SLOTS;i++)
|
|
{
|
|
wp->damage_ship[i] = 0.0f;
|
|
wp->damage_ship_id[i] = -1;
|
|
}
|
|
|
|
if (Weapons_inherit_parent_collision_group) {
|
|
Objects[objnum].collision_group_id = Objects[parent_objnum].collision_group_id;
|
|
}
|
|
|
|
return objnum;
|
|
}
|
|
|
|
/**
|
|
* Spawn child weapons from object *objp.
|
|
*/
|
|
void spawn_child_weapons(object *objp)
|
|
{
|
|
int i, j;
|
|
int child_id;
|
|
int parent_num;
|
|
ushort starting_sig;
|
|
weapon *wp;
|
|
weapon_info *wip, *child_wip;
|
|
|
|
Assert(objp->type == OBJ_WEAPON);
|
|
Assert((objp->instance >= 0) && (objp->instance < MAX_WEAPONS));
|
|
|
|
wp = &Weapons[objp->instance];
|
|
Assert((wp->weapon_info_index >= 0) && (wp->weapon_info_index < MAX_WEAPON_TYPES));
|
|
wip = &Weapon_info[wp->weapon_info_index];
|
|
|
|
parent_num = objp->parent;
|
|
|
|
if (parent_num >= 0) {
|
|
if ((Objects[parent_num].type != objp->parent_type) || (Objects[parent_num].signature != objp->parent_sig)) {
|
|
mprintf(("Warning: Parent of spawn weapon does not exist. Not spawning.\n"));
|
|
return;
|
|
}
|
|
}
|
|
|
|
starting_sig = 0;
|
|
|
|
if ( Game_mode & GM_MULTIPLAYER ) {
|
|
// get the next network signature and save it. Set the next usable network signature to be
|
|
// the passed in objects signature + 1. We "reserved" N of these slots when we created objp
|
|
// for it's spawned children.
|
|
starting_sig = multi_get_next_network_signature( MULTI_SIG_NON_PERMANENT );
|
|
multi_set_network_signature( objp->net_signature, MULTI_SIG_NON_PERMANENT );
|
|
}
|
|
|
|
for (i = 0; i < wip->num_spawn_weapons_defined; i++)
|
|
{
|
|
for (j = 0; j < wip->spawn_info[i].spawn_count; j++)
|
|
{
|
|
int weapon_objnum;
|
|
vec3d tvec, pos;
|
|
matrix orient;
|
|
|
|
child_id = wip->spawn_info[i].spawn_type;
|
|
child_wip = &Weapon_info[child_id];
|
|
|
|
// for multiplayer, use the static randvec functions based on the network signatures to provide
|
|
// the randomness so that it is the same on all machines.
|
|
if ( Game_mode & GM_MULTIPLAYER ) {
|
|
static_rand_cone(objp->net_signature + j, &tvec, &objp->orient.vec.fvec, wip->spawn_info[i].spawn_angle);
|
|
} else {
|
|
vm_vec_random_cone(&tvec, &objp->orient.vec.fvec, wip->spawn_info[i].spawn_angle);
|
|
}
|
|
vm_vec_scale_add(&pos, &objp->pos, &tvec, objp->radius);
|
|
|
|
vm_vector_2_matrix(&orient, &tvec, NULL, NULL);
|
|
weapon_objnum = weapon_create(&pos, &orient, child_id, parent_num, -1, wp->weapon_flags & WF_LOCKED_WHEN_FIRED, 1);
|
|
|
|
//if the child inherits parent target, do it only if the parent weapon was locked to begin with
|
|
if ((child_wip->wi_flags2 & WIF2_INHERIT_PARENT_TARGET) && (wp->homing_object != &obj_used_list))
|
|
{
|
|
//Deal with swarm weapons
|
|
if (wp->swarm_index >= 0) {
|
|
swarm_info *swarmp;
|
|
swarmp = &Swarm_missiles[wp->swarm_index];
|
|
|
|
weapon_set_tracking_info(weapon_objnum, parent_num, swarmp->homing_objnum, 1, wp->homing_subsys);
|
|
} else {
|
|
weapon_set_tracking_info(weapon_objnum, parent_num, wp->target_num, 1, wp->homing_subsys);
|
|
}
|
|
}
|
|
|
|
// Assign a little randomness to lifeleft so they don't all disappear at the same time.
|
|
if (weapon_objnum != -1) {
|
|
float rand_val;
|
|
|
|
if ( Game_mode & GM_NORMAL ){
|
|
rand_val = frand();
|
|
} else {
|
|
rand_val = static_randf(objp->net_signature + j);
|
|
}
|
|
|
|
Weapons[Objects[weapon_objnum].instance].lifeleft *= rand_val*0.4f + 0.8f;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// in multiplayer, reset the next network signature to the one that was saved.
|
|
if ( Game_mode & GM_MULTIPLAYER ){
|
|
multi_set_network_signature( starting_sig, MULTI_SIG_NON_PERMANENT );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Figures out whether to play disarmed or armed hit sound, checks that the
|
|
* chosen one exists, and plays it
|
|
*/
|
|
void weapon_play_impact_sound(weapon_info *wip, vec3d *hitpos, bool is_armed)
|
|
{
|
|
if(is_armed)
|
|
{
|
|
if(wip->impact_snd != -1) {
|
|
snd_play_3d( &Snds[wip->impact_snd], hitpos, &Eye_position );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(wip->disarmed_impact_snd != -1) {
|
|
snd_play_3d(&Snds[wip->disarmed_impact_snd], hitpos, &Eye_position);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Play a sound effect when a weapon hits a ship
|
|
*
|
|
* To elimate the "stereo" effect of two lasers hitting at nearly
|
|
* the same time, and to reduce the number of sound channels used,
|
|
* only play one impact sound if IMPACT_SOUND_DELTA has elapsed
|
|
*
|
|
* @note Uses Weapon_impact_timer global for timer variable
|
|
*/
|
|
void weapon_hit_do_sound(object *hit_obj, weapon_info *wip, vec3d *hitpos, bool is_armed)
|
|
{
|
|
int is_hull_hit;
|
|
float shield_str;
|
|
|
|
// If non-missiles (namely lasers) expire without hitting a ship, don't play impact sound
|
|
if ( wip->subtype != WP_MISSILE ) {
|
|
if ( !hit_obj ) {
|
|
// flak weapons make sounds
|
|
if(wip->wi_flags & WIF_FLAK)
|
|
{
|
|
weapon_play_impact_sound(wip, hitpos, is_armed);
|
|
}
|
|
return;
|
|
}
|
|
|
|
switch(hit_obj->type) {
|
|
case OBJ_SHIP:
|
|
// do nothing
|
|
break;
|
|
|
|
case OBJ_ASTEROID:
|
|
if ( timestamp_elapsed(Weapon_impact_timer) ) {
|
|
weapon_play_impact_sound(wip, hitpos, is_armed);
|
|
Weapon_impact_timer = timestamp(IMPACT_SOUND_DELTA);
|
|
}
|
|
return;
|
|
break;
|
|
|
|
default:
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ( hit_obj == NULL ) {
|
|
weapon_play_impact_sound(wip, hitpos, is_armed);
|
|
return;
|
|
}
|
|
|
|
if ( timestamp_elapsed(Weapon_impact_timer) ) {
|
|
|
|
is_hull_hit = 1;
|
|
if ( hit_obj->type == OBJ_SHIP ) {
|
|
shield_str = ship_quadrant_shield_strength(hit_obj, hitpos);
|
|
} else {
|
|
shield_str = 0.0f;
|
|
}
|
|
|
|
// play a shield hit if shields are above 10% max in this quadrant
|
|
if ( shield_str > 0.1f ) {
|
|
is_hull_hit = 0;
|
|
}
|
|
|
|
if ( !is_hull_hit ) {
|
|
// Play a shield impact sound effect
|
|
if ( hit_obj == Player_obj ) {
|
|
snd_play_3d( &Snds[SND_SHIELD_HIT_YOU], hitpos, &Eye_position );
|
|
// AL 12-15-97: Add missile impact sound even when shield is hit
|
|
if ( wip->subtype == WP_MISSILE ) {
|
|
snd_play_3d( &Snds[SND_PLAYER_HIT_MISSILE], hitpos, &Eye_position);
|
|
}
|
|
} else {
|
|
snd_play_3d( &Snds[SND_SHIELD_HIT], hitpos, &Eye_position );
|
|
}
|
|
} else {
|
|
// Play a hull impact sound effect
|
|
switch ( wip->subtype ) {
|
|
case WP_LASER:
|
|
if ( hit_obj == Player_obj )
|
|
snd_play_3d( &Snds[SND_PLAYER_HIT_LASER], hitpos, &Eye_position );
|
|
else {
|
|
weapon_play_impact_sound(wip, hitpos, is_armed);
|
|
}
|
|
break;
|
|
case WP_MISSILE:
|
|
if ( hit_obj == Player_obj )
|
|
snd_play_3d( &Snds[SND_PLAYER_HIT_MISSILE], hitpos, &Eye_position);
|
|
else {
|
|
weapon_play_impact_sound(wip, hitpos, is_armed);
|
|
}
|
|
break;
|
|
default:
|
|
nprintf(("Warning","WARNING ==> Cannot determine sound to play for weapon impact\n"));
|
|
break;
|
|
} // end switch
|
|
}
|
|
|
|
Weapon_impact_timer = timestamp(IMPACT_SOUND_DELTA);
|
|
}
|
|
}
|
|
|
|
extern bool turret_weapon_has_flags(ship_weapon *swp, int flags);
|
|
|
|
/**
|
|
* Distrupt any subsystems that fall into damage sphere of this Electronics missile
|
|
*
|
|
* @param ship_objp Pointer to ship that holds subsystem
|
|
* @param blast_pos World pos of weapon blast
|
|
* @param wi_index Weapon info index of weapon causing blast
|
|
*/
|
|
void weapon_do_electronics_effect(object *ship_objp, vec3d *blast_pos, int wi_index)
|
|
{
|
|
weapon_info *wip;
|
|
ship *shipp;
|
|
ship_subsys *ss;
|
|
model_subsystem *psub;
|
|
vec3d subsys_world_pos;
|
|
float dist;
|
|
|
|
shipp = &Ships[ship_objp->instance];
|
|
wip = &Weapon_info[wi_index];
|
|
|
|
for ( ss = GET_FIRST(&shipp->subsys_list); ss != END_OF_LIST(&shipp->subsys_list); ss = GET_NEXT(ss) )
|
|
{
|
|
psub = ss->system_info;
|
|
|
|
// convert subsys point to world coords
|
|
vm_vec_unrotate(&subsys_world_pos, &psub->pnt, &ship_objp->orient);
|
|
vm_vec_add2(&subsys_world_pos, &ship_objp->pos);
|
|
|
|
// see if subsys point is within damage sphere
|
|
dist = vm_vec_dist_quick(blast_pos, &subsys_world_pos);
|
|
if ( dist < wip->shockwave.outer_rad )
|
|
{
|
|
float disrupt_time = (float)wip->elec_time;
|
|
|
|
//use new style electronics disruption
|
|
if (wip->elec_use_new_style)
|
|
{
|
|
//if its an engine subsytem, take the multiplier into account
|
|
if (psub->type==SUBSYSTEM_ENGINE)
|
|
{
|
|
disrupt_time*=wip->elec_eng_mult;
|
|
}
|
|
|
|
//if its a turret or weapon subsytem, take the multiplier into account
|
|
if ((psub->type==SUBSYSTEM_TURRET) || (psub->type==SUBSYSTEM_WEAPONS))
|
|
{
|
|
//disrupt beams
|
|
//WMC - do this even if there are other types of weapons on the turret.
|
|
//I figure, the big fancy electronics on beams will be used for the other
|
|
//weapons as well. No reason having two targeting computers on a turret.
|
|
//Plus, it's easy and fast to code. :)
|
|
if ((psub->type==SUBSYSTEM_TURRET) && turret_weapon_has_flags(&ss->weapons, WIF_BEAM))
|
|
{
|
|
disrupt_time*=wip->elec_beam_mult;
|
|
}
|
|
//disrupt other weapons
|
|
else
|
|
{
|
|
disrupt_time*=wip->elec_weap_mult;
|
|
}
|
|
}
|
|
|
|
//disrupt sensor and awacs systems.
|
|
if ((psub->type==SUBSYSTEM_SENSORS) || (psub->flags & MSS_FLAG_AWACS))
|
|
{
|
|
disrupt_time*=wip->elec_sensors_mult;
|
|
}
|
|
}
|
|
|
|
//add a little randomness to the disruption time
|
|
disrupt_time += frand_range(-1.0f, 1.0f) * wip->elec_randomness;
|
|
|
|
//disrupt this subsystem for the calculated time
|
|
//if it turns out to be less than 0 seconds, don't bother
|
|
if (disrupt_time > 0)
|
|
{
|
|
ship_subsys_set_disrupted(ss, fl2i(disrupt_time));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Calculate the damage for an object based on the location of an area-effect
|
|
* explosion.
|
|
*
|
|
* @param objp Object pointer ship receiving blast effect
|
|
* @param pos World pos of blast center
|
|
* @param inner_rad Smallest radius at which full damage is done
|
|
* @param outer_rad Radius at which no damage is done
|
|
* @param max_blast Maximum blast possible from explosion
|
|
* @param max_damage Maximum damage possible from explosion
|
|
* @param blast OUTPUT PARAMETER: receives blast value from explosion
|
|
* @param damage OUTPUT PARAMETER: receives damage value from explosion
|
|
* @param limit A limit on the area, needed for shockwave damage
|
|
*
|
|
* @return No damage occurred, -1
|
|
* @return Damage occured, 0
|
|
*/
|
|
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)
|
|
{
|
|
float dist;
|
|
vec3d box_pt;
|
|
|
|
// if object receiving the blast is a ship, use the bbox for distances
|
|
// otherwise use the objects radius
|
|
// could possibly exclude SIF_SMALL_SHIP (& other small objects) from using the bbox
|
|
if (objp->type == OBJ_SHIP) {
|
|
int inside = get_nearest_bbox_point(objp, pos, &box_pt);
|
|
if (inside) {
|
|
dist = 0.0001f;
|
|
} else {
|
|
dist = vm_vec_dist_quick(pos, &box_pt);
|
|
}
|
|
} else {
|
|
dist = vm_vec_dist_quick(&objp->pos, pos) - objp->radius;
|
|
}
|
|
|
|
if ( (dist > outer_rad) || (dist > limit) ) {
|
|
return -1; // spheres don't intersect at all
|
|
}
|
|
|
|
if ( dist < inner_rad ) {
|
|
// damage is maximum within inner radius
|
|
*damage = max_damage;
|
|
*blast = max_blast;
|
|
} else {
|
|
float dist_to_outer_rad_squared = (outer_rad-dist)*(outer_rad-dist);
|
|
float total_dist_squared = (inner_rad-outer_rad)*(inner_rad-outer_rad);
|
|
|
|
// this means the inner and outer radii are basically equal... and since we aren't within the inner radius,
|
|
// we fudge the law of excluded middle to place ourselves outside the outer radius
|
|
if (total_dist_squared < 0.0001f) {
|
|
return -1; // avoid divide-by-zero; we won't take damage anyway
|
|
}
|
|
|
|
// AL 2-24-98: drop off damage relative to square of distance
|
|
Assert(dist_to_outer_rad_squared <= total_dist_squared);
|
|
*damage = max_damage * dist_to_outer_rad_squared/total_dist_squared;
|
|
*blast = (dist - outer_rad) * max_blast /(inner_rad - outer_rad);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Apply the blast effects of an explosion to a ship
|
|
*
|
|
* @param force_apply_pos World pos of where force is applied to object
|
|
* @param ship_objp Object pointer of ship receiving the blast
|
|
* @param blast_pos World pos of blast center
|
|
* @param blast Force of blast
|
|
* @param make_shockwave Boolean, whether to create a shockwave or not
|
|
*/
|
|
void weapon_area_apply_blast(vec3d *force_apply_pos, object *ship_objp, vec3d *blast_pos, float blast, int make_shockwave)
|
|
{
|
|
#define SHAKE_CONST 3000
|
|
vec3d force, vec_blast_to_ship, vec_ship_to_impact;
|
|
polymodel *pm;
|
|
|
|
// don't waste time here if there is no blast force
|
|
if ( blast == 0.0f )
|
|
return;
|
|
|
|
// apply blast force based on distance from center of explosion
|
|
vm_vec_sub(&vec_blast_to_ship, &ship_objp->pos, blast_pos);
|
|
vm_vec_normalize_safe(&vec_blast_to_ship);
|
|
vm_vec_copy_scale(&force, &vec_blast_to_ship, blast );
|
|
|
|
vm_vec_sub(&vec_ship_to_impact, blast_pos, &ship_objp->pos);
|
|
|
|
pm = model_get(Ship_info[Ships[ship_objp->instance].ship_info_index].model_num);
|
|
Assert ( pm != NULL );
|
|
|
|
if (make_shockwave) {
|
|
physics_apply_shock (&force, blast, &ship_objp->phys_info, &ship_objp->orient, &pm->mins, &pm->maxs, pm->rad);
|
|
if (ship_objp == Player_obj) {
|
|
joy_ff_play_vector_effect(&vec_blast_to_ship, blast * 2.0f);
|
|
}
|
|
} else {
|
|
ship_apply_whack( &force, &vec_ship_to_impact, ship_objp);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Do the area effect for a weapon
|
|
*
|
|
* @param wobjp Object pointer to weapon causing explosion
|
|
* @param sci Shockwave info
|
|
* @param pos World pos of explosion center
|
|
* @param other_obj Object pointer to ship that weapon impacted on (can be NULL)
|
|
*/
|
|
void weapon_do_area_effect(object *wobjp, shockwave_create_info *sci, vec3d *pos, object *other_obj)
|
|
{
|
|
weapon_info *wip;
|
|
object *objp;
|
|
float damage, blast;
|
|
|
|
wip = &Weapon_info[Weapons[wobjp->instance].weapon_info_index];
|
|
|
|
// only blast ships and asteroids
|
|
// And (some) weapons
|
|
for ( objp = GET_FIRST(&obj_used_list); objp !=END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) ) {
|
|
if ( (objp->type != OBJ_SHIP) && (objp->type != OBJ_ASTEROID) && (objp->type != OBJ_WEAPON) ) {
|
|
continue;
|
|
}
|
|
|
|
if ( objp->type == OBJ_WEAPON ) {
|
|
// only apply to missiles with hitpoints
|
|
weapon_info* wip2 = &Weapon_info[Weapons[objp->instance].weapon_info_index];
|
|
if (wip2->weapon_hitpoints <= 0 || !(wip2->wi_flags2 & WIF2_TAKES_BLAST_DAMAGE) || (wip->wi_flags2 & WIF2_CIWS))
|
|
continue;
|
|
}
|
|
|
|
if ( objp->type == OBJ_SHIP ) {
|
|
// don't blast navbuoys
|
|
if ( ship_get_SIF(objp->instance) & SIF_NAVBUOY ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if ( weapon_area_calc_damage(objp, pos, sci->inner_rad, sci->outer_rad, sci->blast, sci->damage, &blast, &damage, sci->outer_rad) == -1 ){
|
|
continue;
|
|
}
|
|
|
|
// scale damage
|
|
damage *= weapon_get_damage_scale(wip, wobjp, other_obj);
|
|
|
|
weapon_info* target_wip;
|
|
|
|
switch ( objp->type ) {
|
|
case OBJ_SHIP:
|
|
// If we're doing an AoE Electronics blast, do the electronics stuff (unless it also has the regular "electronics"
|
|
// flag and this is the ship the missile directly impacted; then leave it for the regular code below) -MageKing17
|
|
if ( (wip->wi_flags3 & WIF3_AOE_ELECTRONICS) && !((objp->flags & OF_INVULNERABLE) || ((objp == other_obj) && (wip->wi_flags & WIF_ELECTRONICS))) ) {
|
|
weapon_do_electronics_effect(objp, pos, Weapons[wobjp->instance].weapon_info_index);
|
|
}
|
|
ship_apply_global_damage(objp, wobjp, pos, damage);
|
|
weapon_area_apply_blast(NULL, objp, pos, blast, 0);
|
|
break;
|
|
case OBJ_ASTEROID:
|
|
asteroid_hit(objp, NULL, NULL, damage);
|
|
break;
|
|
case OBJ_WEAPON:
|
|
target_wip = &Weapon_info[Weapons[objp->instance].weapon_info_index];
|
|
if (target_wip->armor_type_idx >= 0)
|
|
damage = Armor_types[target_wip->armor_type_idx].GetDamage(damage, wip->damage_type_idx, 1.0f);
|
|
|
|
objp->hull_strength -= damage;
|
|
if (objp->hull_strength < 0.0f) {
|
|
Weapons[objp->instance].lifeleft = 0.01f;
|
|
Weapons[objp->instance].weapon_flags |= WF_DESTROYED_BY_WEAPON;
|
|
}
|
|
break;
|
|
default:
|
|
Int3();
|
|
break;
|
|
}
|
|
|
|
} // end for
|
|
|
|
// if this weapon has the "Electronics" flag set, then disrupt subsystems in sphere
|
|
if ( (other_obj != NULL) && (wip->wi_flags & WIF_ELECTRONICS) ) {
|
|
if ( other_obj->type == OBJ_SHIP ) {
|
|
weapon_do_electronics_effect(other_obj, pos, Weapons[wobjp->instance].weapon_info_index);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
// weapon_armed(weapon)
|
|
//
|
|
// Call to figure out if a weapon is armed or not
|
|
//
|
|
//Weapon is armed when...
|
|
//1: Weapon is shot down by weapon
|
|
//OR
|
|
//1: weapon is destroyed before arm time
|
|
//2: weapon is destroyed before arm distance from ship
|
|
//3: weapon is outside arm radius from target ship
|
|
bool weapon_armed(weapon *wp, bool hit_target)
|
|
{
|
|
Assert(wp != NULL);
|
|
|
|
weapon_info *wip = &Weapon_info[wp->weapon_info_index];
|
|
|
|
if((wp->weapon_flags & WF_DESTROYED_BY_WEAPON)
|
|
&& !wip->arm_time
|
|
&& wip->arm_dist == 0.0f
|
|
&& wip->arm_radius == 0.0f)
|
|
{
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
object *wobj = &Objects[wp->objnum];
|
|
object *pobj;
|
|
|
|
if(wobj->parent > -1) {
|
|
pobj = &Objects[wobj->parent];
|
|
} else {
|
|
pobj = NULL;
|
|
}
|
|
|
|
if( ((wip->arm_time) && ((Missiontime - wp->creation_time) < wip->arm_time))
|
|
|| ((wip->arm_dist) && (pobj != NULL && pobj->type != OBJ_NONE && (vm_vec_dist(&wobj->pos, &pobj->pos) < wip->arm_dist))))
|
|
{
|
|
return false;
|
|
}
|
|
if(wip->arm_radius && (!hit_target)) {
|
|
if(wp->homing_object == NULL)
|
|
return false;
|
|
if(vm_vec_dist(&wobj->pos, &wp->homing_pos) > wip->arm_radius)
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/**
|
|
* Called when a weapon hits something (or, in the case of
|
|
* missiles explodes for any particular reason)
|
|
*/
|
|
void weapon_hit( object * weapon_obj, object * other_obj, vec3d * hitpos, int quadrant )
|
|
{
|
|
Assert(weapon_obj != NULL);
|
|
if(weapon_obj == NULL){
|
|
return;
|
|
}
|
|
Assert((weapon_obj->type == OBJ_WEAPON) && (weapon_obj->instance >= 0) && (weapon_obj->instance < MAX_WEAPONS));
|
|
if((weapon_obj->type != OBJ_WEAPON) || (weapon_obj->instance < 0) || (weapon_obj->instance >= MAX_WEAPONS)){
|
|
return;
|
|
}
|
|
|
|
int num = weapon_obj->instance;
|
|
int weapon_type = Weapons[num].weapon_info_index;
|
|
int expl_ani_handle;
|
|
weapon_info *wip;
|
|
weapon *wp;
|
|
bool hit_target = false;
|
|
|
|
object *other_objp;
|
|
ship_obj *so;
|
|
ship *shipp;
|
|
int objnum;
|
|
|
|
Assert((weapon_type >= 0) && (weapon_type < MAX_WEAPON_TYPES));
|
|
if((weapon_type < 0) || (weapon_type >= MAX_WEAPON_TYPES)){
|
|
return;
|
|
}
|
|
wp = &Weapons[weapon_obj->instance];
|
|
wip = &Weapon_info[weapon_type];
|
|
objnum = wp->objnum;
|
|
|
|
// check if the weapon actually hit the intended target
|
|
if (wp->homing_object != NULL)
|
|
if (wp->homing_object == other_obj)
|
|
hit_target = true;
|
|
|
|
//This is an expensive check
|
|
bool armed_weapon = weapon_armed(&Weapons[num], hit_target);
|
|
|
|
// if this is the player ship, and is a laser hit, skip it. wait for player "pain" to take care of it
|
|
if ((other_obj != Player_obj) || (wip->subtype != WP_LASER) || !MULTIPLAYER_CLIENT) {
|
|
weapon_hit_do_sound(other_obj, wip, hitpos, armed_weapon);
|
|
}
|
|
|
|
if ( wip->impact_weapon_expl_index > -1 && armed_weapon)
|
|
{
|
|
expl_ani_handle = Weapon_explosions.GetAnim(wip->impact_weapon_expl_index, hitpos, wip->impact_explosion_radius);
|
|
particle_create( hitpos, &vmd_zero_vector, 0.0f, wip->impact_explosion_radius, PARTICLE_BITMAP_PERSISTENT, expl_ani_handle );
|
|
}
|
|
else if(wip->dinky_impact_weapon_expl_index > -1 && !armed_weapon)
|
|
{
|
|
expl_ani_handle = Weapon_explosions.GetAnim(wip->dinky_impact_weapon_expl_index, hitpos, wip->dinky_impact_explosion_radius);
|
|
particle_create( hitpos, &vmd_zero_vector, 0.0f, wip->dinky_impact_explosion_radius, PARTICLE_BITMAP_PERSISTENT, expl_ani_handle );
|
|
}
|
|
|
|
if((other_obj != NULL) && (quadrant == -1) && (wip->piercing_impact_weapon_expl_index > -1 && armed_weapon)) {
|
|
if ((other_obj->type == OBJ_SHIP) || (other_obj->type == OBJ_DEBRIS)) {
|
|
|
|
int ok_to_draw = 1;
|
|
|
|
if (other_obj->type == OBJ_SHIP) {
|
|
float draw_limit, hull_pct;
|
|
int dmg_type_idx, piercing_type;
|
|
|
|
shipp = &Ships[other_obj->instance];
|
|
|
|
hull_pct = other_obj->hull_strength / shipp->ship_max_hull_strength;
|
|
dmg_type_idx = wip->damage_type_idx;
|
|
draw_limit = Ship_info[shipp->ship_info_index].piercing_damage_draw_limit;
|
|
|
|
if (shipp->armor_type_idx != -1) {
|
|
piercing_type = Armor_types[shipp->armor_type_idx].GetPiercingType(dmg_type_idx);
|
|
if (piercing_type == SADTF_PIERCING_DEFAULT) {
|
|
draw_limit = Armor_types[shipp->armor_type_idx].GetPiercingLimit(dmg_type_idx);
|
|
} else if ((piercing_type == SADTF_PIERCING_NONE) || (piercing_type == SADTF_PIERCING_RETAIL)) {
|
|
ok_to_draw = 0;
|
|
}
|
|
}
|
|
|
|
if (hull_pct > draw_limit)
|
|
ok_to_draw = 0;
|
|
}
|
|
|
|
if (ok_to_draw) {
|
|
particle_emitter pe;
|
|
vec3d null_v;
|
|
|
|
vm_vec_zero(&null_v);
|
|
expl_ani_handle = Weapon_explosions.GetAnim(wip->piercing_impact_weapon_expl_index, hitpos, wip->piercing_impact_explosion_radius);
|
|
|
|
pe.max_vel = 2.0f * wip->piercing_impact_particle_velocity;
|
|
pe.min_vel = 0.5f * wip->piercing_impact_particle_velocity;
|
|
pe.max_life = 2.0f * wip->piercing_impact_particle_life;
|
|
pe.min_life = 0.25f * wip->piercing_impact_particle_life;
|
|
pe.num_high = 2 * wip->piercing_impact_particle_count;
|
|
pe.num_low = wip->piercing_impact_particle_count / 2;
|
|
pe.pos = weapon_obj->pos;
|
|
pe.normal = weapon_obj->last_orient.vec.fvec;
|
|
pe.normal_variance = wip->piercing_impact_particle_variance;
|
|
pe.max_rad = 2.0f * wip->piercing_impact_explosion_radius;
|
|
pe.min_rad = 0.5f * wip->piercing_impact_explosion_radius;
|
|
pe.vel = null_v;
|
|
|
|
particle_emit(&pe, PARTICLE_BITMAP, expl_ani_handle, 10.0f);
|
|
|
|
if (wip->piercing_impact_particle_back_velocity != 0.0f) {
|
|
|
|
pe.max_vel = 2.0f * wip->piercing_impact_particle_back_velocity;
|
|
pe.min_vel = 0.5f * wip->piercing_impact_particle_back_velocity;
|
|
pe.num_high /= 2;
|
|
pe.num_low /= 2;
|
|
|
|
particle_emit(&pe, PARTICLE_BITMAP, expl_ani_handle, 10.0f);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// For all objects that had this weapon as a target, wipe it out, forcing find of a new enemy
|
|
for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) {
|
|
other_objp = &Objects[so->objnum];
|
|
Assert(other_objp->instance != -1);
|
|
|
|
shipp = &Ships[other_objp->instance];
|
|
Assert(shipp->ai_index != -1);
|
|
|
|
ai_info *aip = &Ai_info[shipp->ai_index];
|
|
|
|
if (aip->target_objnum == objnum) {
|
|
set_target_objnum(aip, -1);
|
|
// If this ship had a dynamic goal of chasing this weapon, clear the dynamic goal.
|
|
if (aip->resume_goal_time != -1)
|
|
aip->active_goal = AI_GOAL_NONE;
|
|
}
|
|
|
|
if (aip->goal_objnum == objnum) {
|
|
aip->goal_objnum = -1;
|
|
aip->goal_signature = -1;
|
|
}
|
|
|
|
if (aip->guard_objnum == objnum) {
|
|
aip->guard_objnum = -1;
|
|
aip->guard_signature = -1;
|
|
}
|
|
|
|
if (aip->hitter_objnum == objnum) {
|
|
aip->hitter_objnum = -1;
|
|
}
|
|
}
|
|
|
|
// single player and multiplayer masters evaluate the scoring and kill stuff
|
|
if (!MULTIPLAYER_CLIENT) {
|
|
//If this is a bomb, set it up for scoring. -Halleck
|
|
if (wip->wi_flags & WIF_BOMB) {
|
|
scoring_eval_kill_on_weapon(weapon_obj, other_obj);
|
|
}
|
|
}
|
|
|
|
weapon_obj->flags |= OF_SHOULD_BE_DEAD;
|
|
|
|
//Set shockwaves flag
|
|
int sw_flag = SW_WEAPON;
|
|
|
|
if ( ((other_obj) && (other_obj->type == OBJ_WEAPON)) || (Weapons[num].weapon_flags & WF_DESTROYED_BY_WEAPON)) {
|
|
sw_flag |= SW_WEAPON_KILL;
|
|
}
|
|
|
|
//Which shockwave?
|
|
shockwave_create_info *sci = &wip->shockwave;
|
|
if(!armed_weapon) {
|
|
sci = &wip->dinky_shockwave;
|
|
}
|
|
|
|
// check if this is an area effect weapon (i.e. has a blast radius)
|
|
if (sci->inner_rad != 0.0f || sci->outer_rad != 0.0f)
|
|
{
|
|
if(sci->speed > 0.0f) {
|
|
shockwave_create(OBJ_INDEX(weapon_obj), hitpos, sci, sw_flag, -1);
|
|
}
|
|
else {
|
|
weapon_do_area_effect(weapon_obj, sci, hitpos, other_obj);
|
|
}
|
|
}
|
|
|
|
// check if this is an EMP weapon
|
|
if(wip->wi_flags & WIF_EMP){
|
|
emp_apply(&weapon_obj->pos, wip->shockwave.inner_rad, wip->shockwave.outer_rad, wip->emp_intensity, wip->emp_time, (wip->wi_flags3 & WIF3_USE_EMP_TIME_FOR_CAPSHIP_TURRETS) != 0);
|
|
}
|
|
|
|
// spawn weapons - note the change from FS 1 multiplayer.
|
|
if (wip->wi_flags & WIF_SPAWN){
|
|
spawn_child_weapons(weapon_obj);
|
|
}
|
|
}
|
|
|
|
void weapon_detonate(object *objp)
|
|
{
|
|
Assert(objp != NULL);
|
|
if(objp == NULL){
|
|
return;
|
|
}
|
|
Assert((objp->type == OBJ_WEAPON) && (objp->instance >= 0));
|
|
if((objp->type != OBJ_WEAPON) || (objp->instance < 0)){
|
|
return;
|
|
}
|
|
|
|
// send a detonate packet in multiplayer
|
|
if(MULTIPLAYER_MASTER){
|
|
send_weapon_detonate_packet(objp);
|
|
}
|
|
|
|
// call weapon hit
|
|
// Wanderer - use last frame pos for the corkscrew missiles
|
|
if ( (Weapon_info[Weapons[objp->instance].weapon_info_index].wi_flags & WIF_CORKSCREW) ) {
|
|
weapon_hit(objp, NULL, &objp->last_pos);
|
|
} else {
|
|
weapon_hit(objp, NULL, &objp->pos);
|
|
}
|
|
}
|
|
|
|
|
|
// Group_id: If you should quad lasers, they should all have the same group id.
|
|
// This will be used to optimize lighting, since each group only needs to cast one light.
|
|
// Call this to get a new group id, then pass it to each weapon_create call for all the
|
|
// weapons in the group. Number will be between 0 and WEAPON_MAX_GROUP_IDS and will
|
|
// get reused.
|
|
int weapon_create_group_id()
|
|
{
|
|
static int current_id = 0;
|
|
|
|
int n = current_id;
|
|
|
|
current_id++;
|
|
if ( current_id >= WEAPON_MAX_GROUP_IDS ) {
|
|
current_id = 0;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
/**
|
|
* Call before weapons_page_in to mark a weapon as used
|
|
*/
|
|
void weapon_mark_as_used(int weapon_type)
|
|
{
|
|
if (weapon_type < 0)
|
|
return;
|
|
|
|
if ( used_weapons == NULL )
|
|
return;
|
|
|
|
Assert( weapon_type < MAX_WEAPON_TYPES );
|
|
|
|
if (weapon_type < Num_weapon_types) {
|
|
used_weapons[weapon_type]++;
|
|
}
|
|
}
|
|
|
|
void weapons_page_in()
|
|
{
|
|
int i, j, idx;
|
|
|
|
Assert( used_weapons != NULL );
|
|
|
|
// for weapons in weaponry pool
|
|
for (i = 0; i < Num_teams; i++) {
|
|
for (j = 0; j < Team_data[i].num_weapon_choices; j++) {
|
|
used_weapons[Team_data[i].weaponry_pool[j]] += Team_data[i].weaponry_count[j];
|
|
}
|
|
}
|
|
|
|
// this grabs all spawn weapon types (Cluster Baby, etc.) which can't be
|
|
// assigned directly to a ship
|
|
for (i = 0; i < Num_weapon_types; i++) {
|
|
// we only want entries that already exist
|
|
if ( !used_weapons[i] )
|
|
continue;
|
|
|
|
// if it's got a spawn type then grab it
|
|
for (j = 0; j < Weapon_info[i].num_spawn_weapons_defined; j++)
|
|
{
|
|
used_weapons[(int)Weapon_info[i].spawn_info[j].spawn_type]++;
|
|
}
|
|
}
|
|
|
|
// release anything loaded that we don't have marked as used for this mission
|
|
if ( !Cmdline_load_all_weapons )
|
|
weapon_release_bitmaps();
|
|
|
|
// Page in bitmaps for all used weapons
|
|
for (i = 0; i < Num_weapon_types; i++) {
|
|
if ( !Cmdline_load_all_weapons ) {
|
|
if ( !used_weapons[i] ) {
|
|
nprintf(("Weapons", "Not loading weapon id %d (%s)\n", i, Weapon_info[i].name));
|
|
continue;
|
|
}
|
|
}
|
|
|
|
weapon_load_bitmaps(i);
|
|
|
|
weapon_info *wip = &Weapon_info[i];
|
|
|
|
wip->wi_flags &= (~WIF_THRUSTER); // Assume no thrusters
|
|
|
|
switch (wip->render_type)
|
|
{
|
|
case WRT_POF:
|
|
{
|
|
wip->model_num = model_load( wip->pofbitmap_name, 0, NULL );
|
|
|
|
polymodel *pm = model_get( wip->model_num );
|
|
|
|
// If it has a model, and the model pof has thrusters, then set
|
|
// the flags
|
|
if (pm->n_thrusters > 0) {
|
|
wip->wi_flags |= WIF_THRUSTER;
|
|
}
|
|
|
|
for (j = 0; j < pm->n_textures; j++)
|
|
pm->maps[j].PageIn();
|
|
|
|
break;
|
|
}
|
|
|
|
case WRT_LASER:
|
|
{
|
|
bm_page_in_texture( wip->laser_bitmap.first_frame );
|
|
bm_page_in_texture( wip->laser_glow_bitmap.first_frame );
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
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.
|
|
}
|
|
|
|
wip->external_model_num = -1;
|
|
|
|
if ( strlen(wip->external_model_name) )
|
|
wip->external_model_num = model_load( wip->external_model_name, 0, NULL );
|
|
|
|
if (wip->external_model_num == -1)
|
|
wip->external_model_num = wip->model_num;
|
|
|
|
|
|
//Load shockwaves
|
|
shockwave_create_info_load(&wip->shockwave);
|
|
shockwave_create_info_load(&wip->dinky_shockwave);
|
|
|
|
//Explosions
|
|
Weapon_explosions.PageIn(wip->impact_weapon_expl_index);
|
|
Weapon_explosions.PageIn(wip->dinky_impact_weapon_expl_index);
|
|
Weapon_explosions.PageIn(wip->flash_impact_weapon_expl_index);
|
|
Weapon_explosions.PageIn(wip->piercing_impact_weapon_expl_index);
|
|
|
|
// trail bitmaps
|
|
if ( (wip->wi_flags & WIF_TRAIL) && (wip->tr_info.texture.bitmap_id > -1) )
|
|
bm_page_in_texture( wip->tr_info.texture.bitmap_id );
|
|
|
|
// if this is a beam weapon, page in its stuff
|
|
if (wip->wi_flags & WIF_BEAM) {
|
|
// all beam sections
|
|
for (idx = 0; idx < wip->b_info.beam_num_sections; idx++)
|
|
bm_page_in_texture(wip->b_info.sections[idx].texture.first_frame);
|
|
|
|
// muzzle glow
|
|
bm_page_in_texture(wip->b_info.beam_glow.first_frame);
|
|
|
|
// particle ani
|
|
bm_page_in_texture(wip->b_info.beam_particle_ani.first_frame);
|
|
}
|
|
|
|
if (wip->wi_flags & WIF_PARTICLE_SPEW) {
|
|
for (size_t s = 0; s < MAX_PARTICLE_SPEWERS; s++) { // looped, multi particle spew -nuke
|
|
if (wip->particle_spewers[s].particle_spew_type != PSPEW_NONE) {
|
|
bm_page_in_texture(wip->particle_spewers[s].particle_spew_anim.first_frame);
|
|
}
|
|
}
|
|
}
|
|
|
|
// muzzle flashes
|
|
if (wip->muzzle_flash >= 0)
|
|
mflash_mark_as_used(wip->muzzle_flash);
|
|
|
|
bm_page_in_texture(wip->thruster_flame.first_frame);
|
|
bm_page_in_texture(wip->thruster_glow.first_frame);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Page_in function for cheaters, grabs all weapons that weren't already in a mission
|
|
* and loads the models for them.
|
|
*
|
|
* Non-model graphics elements will get loaded when they are rendered for the first time.
|
|
* Maybe not the best way to do this but faster and a lot less error prone.
|
|
*/
|
|
void weapons_page_in_cheats()
|
|
{
|
|
int i;
|
|
|
|
// don't bother if they are all loaded already
|
|
if ( Cmdline_load_all_weapons )
|
|
return;
|
|
|
|
Assert( used_weapons != NULL );
|
|
|
|
// force a page in of all muzzle flashes
|
|
mflash_page_in(true);
|
|
|
|
// page in models for all weapon types that aren't already loaded
|
|
for (i = 0; i < Num_weapon_types; i++) {
|
|
// skip over anything that's already loaded
|
|
if (used_weapons[i])
|
|
continue;
|
|
|
|
weapon_load_bitmaps(i);
|
|
|
|
weapon_info *wip = &Weapon_info[i];
|
|
|
|
wip->wi_flags &= (~WIF_THRUSTER); // Assume no thrusters
|
|
|
|
if ( wip->render_type == WRT_POF ) {
|
|
wip->model_num = model_load( wip->pofbitmap_name, 0, NULL );
|
|
|
|
polymodel *pm = model_get( wip->model_num );
|
|
|
|
// If it has a model, and the model pof has thrusters, then set
|
|
// the flags
|
|
if ( pm->n_thrusters > 0 ) {
|
|
wip->wi_flags |= WIF_THRUSTER;
|
|
}
|
|
}
|
|
|
|
wip->external_model_num = -1;
|
|
|
|
if ( strlen(wip->external_model_name) )
|
|
wip->external_model_num = model_load( wip->external_model_name, 0, NULL );
|
|
|
|
if (wip->external_model_num == -1)
|
|
wip->external_model_num = wip->model_num;
|
|
|
|
|
|
//Load shockwaves
|
|
shockwave_create_info_load(&wip->shockwave);
|
|
shockwave_create_info_load(&wip->dinky_shockwave);
|
|
|
|
used_weapons[i]++;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the "color" of the laser at the given moment (since glowing lasers can cycle colors)
|
|
*/
|
|
void weapon_get_laser_color(color *c, object *objp)
|
|
{
|
|
weapon *wep;
|
|
weapon_info *winfo;
|
|
float pct;
|
|
|
|
// sanity
|
|
if (c == NULL)
|
|
return;
|
|
|
|
// sanity
|
|
Assert(objp != NULL);
|
|
Assert(objp->type == OBJ_WEAPON);
|
|
Assert(objp->instance >= 0);
|
|
Assert(Weapons[objp->instance].weapon_info_index >= 0);
|
|
|
|
if ( (objp == NULL) || (objp->type != OBJ_WEAPON) || (objp->instance < 0) || (Weapons[objp->instance].weapon_info_index < 0) )
|
|
return;
|
|
|
|
wep = &Weapons[objp->instance];
|
|
winfo = &Weapon_info[wep->weapon_info_index];
|
|
|
|
// if we're a one-color laser
|
|
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) ) {
|
|
*c = winfo->laser_color_1;
|
|
return;
|
|
}
|
|
|
|
int r = winfo->laser_color_1.red;
|
|
int g = winfo->laser_color_1.green;
|
|
int b = winfo->laser_color_1.blue;
|
|
|
|
// lifetime pct
|
|
pct = 1.0f - (wep->lifeleft / winfo->lifetime);
|
|
CLAMP(pct, 0.0f, 0.5f);
|
|
|
|
if (pct > 0.0f) {
|
|
pct *= 2.0f;
|
|
|
|
r += fl2i((winfo->laser_color_2.red - winfo->laser_color_1.red) * pct);
|
|
g += fl2i((winfo->laser_color_2.green - winfo->laser_color_1.green) * pct);
|
|
b += fl2i((winfo->laser_color_2.blue - winfo->laser_color_1.blue) * pct);
|
|
}
|
|
|
|
// otherwise interpolate between the colors
|
|
gr_init_color( c, r, g, b );
|
|
}
|
|
|
|
// default weapon particle spew data
|
|
|
|
int Weapon_particle_spew_count = 1;
|
|
int Weapon_particle_spew_time = 25;
|
|
float Weapon_particle_spew_vel = 0.4f;
|
|
float Weapon_particle_spew_radius = 2.0f;
|
|
float Weapon_particle_spew_lifetime = 0.15f;
|
|
float Weapon_particle_spew_scale = 0.8f;
|
|
|
|
/**
|
|
* For weapons flagged as particle spewers, spew particles. wheee
|
|
*/
|
|
void weapon_maybe_spew_particle(object *obj)
|
|
{
|
|
weapon *wp;
|
|
weapon_info *wip;
|
|
int idx;
|
|
|
|
// check some stuff
|
|
Assert(obj->type == OBJ_WEAPON);
|
|
Assert(obj->instance >= 0);
|
|
Assert(Weapons[obj->instance].weapon_info_index >= 0);
|
|
Assert(Weapon_info[Weapons[obj->instance].weapon_info_index].wi_flags & WIF_PARTICLE_SPEW);
|
|
|
|
wp = &Weapons[obj->instance];
|
|
wip = &Weapon_info[wp->weapon_info_index];
|
|
vec3d spawn_pos, spawn_vel, output_pos, output_vel, input_pos, input_vel;
|
|
|
|
for (int psi = 0; psi < MAX_PARTICLE_SPEWERS; psi++) { // iterate through spewers -nuke
|
|
if (wip->particle_spewers[psi].particle_spew_type != PSPEW_NONE) {
|
|
// if the weapon's particle timestamp has elapsed
|
|
if ((wp->particle_spew_time[psi] == -1) || timestamp_elapsed(wp->particle_spew_time[psi])) {
|
|
// reset the timestamp
|
|
wp->particle_spew_time[psi] = timestamp(wip->particle_spewers[0].particle_spew_time);
|
|
|
|
// turn normals and origins to world space if we need to
|
|
if (!vm_vec_same(&wip->particle_spewers[psi].particle_spew_offset, &vmd_zero_vector)) { // don't xform unused vectors
|
|
vm_vec_unrotate(&spawn_pos, &wip->particle_spewers[psi].particle_spew_offset, &obj->orient);
|
|
} else {
|
|
spawn_pos = vmd_zero_vector;
|
|
}
|
|
|
|
if (!vm_vec_same(&wip->particle_spewers[psi].particle_spew_velocity, &vmd_zero_vector)) {
|
|
vm_vec_unrotate(&spawn_vel, &wip->particle_spewers[psi].particle_spew_velocity, &obj->orient);
|
|
} else {
|
|
spawn_vel = vmd_zero_vector;
|
|
}
|
|
|
|
// spew some particles
|
|
if (wip->particle_spewers[psi].particle_spew_type == PSPEW_DEFAULT) // default pspew type
|
|
{ // do the default pspew
|
|
vec3d direct, direct_temp, particle_pos;
|
|
vec3d null_vec = ZERO_VECTOR;
|
|
vec3d vel;
|
|
float ang;
|
|
|
|
for (idx = 0; idx < wip->particle_spewers[psi].particle_spew_count; idx++) {
|
|
// get the backward vector of the weapon
|
|
direct = obj->orient.vec.fvec;
|
|
vm_vec_negate(&direct);
|
|
|
|
// randomly perturb x, y and z
|
|
|
|
// uvec
|
|
ang = frand_range(-PI_2,PI_2); // fl_radian(frand_range(-90.0f, 90.0f)); -optimized by nuke
|
|
vm_rot_point_around_line(&direct_temp, &direct, ang, &null_vec, &obj->orient.vec.fvec);
|
|
direct = direct_temp;
|
|
vm_vec_scale(&direct, wip->particle_spewers[psi].particle_spew_scale);
|
|
|
|
// rvec
|
|
ang = frand_range(-PI_2,PI_2); // fl_radian(frand_range(-90.0f, 90.0f)); -optimized by nuke
|
|
vm_rot_point_around_line(&direct_temp, &direct, ang, &null_vec, &obj->orient.vec.rvec);
|
|
direct = direct_temp;
|
|
vm_vec_scale(&direct, wip->particle_spewers[psi].particle_spew_scale);
|
|
|
|
// fvec
|
|
ang = frand_range(-PI_2,PI_2); // fl_radian(frand_range(-90.0f, 90.0f)); -optimized by nuke
|
|
vm_rot_point_around_line(&direct_temp, &direct, ang, &null_vec, &obj->orient.vec.uvec);
|
|
direct = direct_temp;
|
|
vm_vec_scale(&direct, wip->particle_spewers[psi].particle_spew_scale);
|
|
|
|
// get a velocity vector of some percentage of the weapon's velocity
|
|
vel = obj->phys_info.vel;
|
|
vm_vec_scale(&vel, wip->particle_spewers[psi].particle_spew_vel);
|
|
|
|
// maybe add in offset and initial velocity
|
|
if (!vm_vec_same(&spawn_vel, &vmd_zero_vector)) { // add in particle velocity if its available
|
|
vm_vec_add2(&vel, &spawn_vel);
|
|
}
|
|
if (!vm_vec_same(&spawn_pos, &vmd_zero_vector)) { // add offset if available
|
|
vm_vec_add2(&direct, &spawn_pos);
|
|
}
|
|
|
|
if (wip->wi_flags & WIF_CORKSCREW) {
|
|
vm_vec_add(&particle_pos, &obj->last_pos, &direct);
|
|
} else {
|
|
vm_vec_add(&particle_pos, &obj->pos, &direct);
|
|
}
|
|
|
|
// emit the particle
|
|
if (wip->particle_spewers[psi].particle_spew_anim.first_frame < 0) {
|
|
particle_create(&particle_pos, &vel, wip->particle_spewers[psi].particle_spew_lifetime, wip->particle_spewers[psi].particle_spew_radius, PARTICLE_SMOKE);
|
|
} else {
|
|
particle_create(&particle_pos, &vel, wip->particle_spewers[psi].particle_spew_lifetime, wip->particle_spewers[psi].particle_spew_radius, PARTICLE_BITMAP, wip->particle_spewers[psi].particle_spew_anim.first_frame);
|
|
}
|
|
}
|
|
} else if (wip->particle_spewers[psi].particle_spew_type == PSPEW_HELIX) { // helix
|
|
float segment_length = wip->max_speed * flFrametime; // determine how long the segment is
|
|
float segment_angular_length = PI2 * wip->particle_spewers[psi].particle_spew_rotation_rate * flFrametime; // determine how much the segment rotates
|
|
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
|
|
float inc = 1.0f / wip->particle_spewers[psi].particle_spew_count; // determine our incriment
|
|
float particle_rot;
|
|
vec3d input_pos_l = ZERO_VECTOR;
|
|
|
|
for (float is = 0; is < 1; is += inc ) { // use iterator as a scaler
|
|
particle_rot = rotation_value + (segment_angular_length * is); // find what point of the rotation were at
|
|
input_vel.xyz.x = sinf(particle_rot) * wip->particle_spewers[psi].particle_spew_scale; // determine x/y velocity based on scale and rotation
|
|
input_vel.xyz.y = cosf(particle_rot) * wip->particle_spewers[psi].particle_spew_scale;
|
|
input_vel.xyz.z = wip->max_speed * wip->particle_spewers[psi].particle_spew_vel; // velocity inheritance
|
|
vm_vec_unrotate(&output_vel, &input_vel, &obj->orient); // orient velocity to weapon
|
|
input_pos_l.xyz.x = input_vel.xyz.x * flFrametime * (1.0f - is); // interpolate particle motion
|
|
input_pos_l.xyz.y = input_vel.xyz.y * flFrametime * (1.0f - is);
|
|
input_pos_l.xyz.z = segment_length * is; // position particle correctly on the z axis
|
|
vm_vec_unrotate(&input_pos, &input_pos_l, &obj->orient); // orient to weapon
|
|
vm_vec_sub(&output_pos, &obj->pos, &input_pos); // translate to world space
|
|
|
|
//maybe add in offset and initial velocity
|
|
if (!vm_vec_same(&spawn_vel, &vmd_zero_vector)) { // add particle velocity if needed
|
|
vm_vec_add2(&output_vel, &spawn_vel);
|
|
}
|
|
if (!vm_vec_same(&spawn_pos, &vmd_zero_vector)) { // add offset if needed
|
|
vm_vec_add2(&output_pos, &spawn_pos);
|
|
}
|
|
|
|
//emit particles
|
|
if (wip->particle_spewers[psi].particle_spew_anim.first_frame < 0) {
|
|
particle_create(&output_pos, &output_vel, wip->particle_spewers[psi].particle_spew_lifetime, wip->particle_spewers[psi].particle_spew_radius, PARTICLE_SMOKE);
|
|
} else {
|
|
particle_create(&output_pos, &output_vel, wip->particle_spewers[psi].particle_spew_lifetime, wip->particle_spewers[psi].particle_spew_radius, PARTICLE_BITMAP, wip->particle_spewers[psi].particle_spew_anim.first_frame);
|
|
}
|
|
}
|
|
} else if (wip->particle_spewers[psi].particle_spew_type == PSPEW_SPARKLER) { // sparkler
|
|
vec3d temp_vel;
|
|
output_vel = obj->phys_info.vel;
|
|
vm_vec_scale(&output_vel, wip->particle_spewers[psi].particle_spew_vel);
|
|
|
|
for (idx = 0; idx < wip->particle_spewers[psi].particle_spew_count; idx++) {
|
|
// create a random unit vector and scale it
|
|
vm_vec_rand_vec_quick(&input_vel);
|
|
vm_vec_scale(&input_vel, wip->particle_spewers[psi].particle_spew_scale);
|
|
|
|
if (wip->particle_spewers[psi].particle_spew_z_scale != 1.0f) { // don't do the extra math for spherical effect
|
|
temp_vel = input_vel;
|
|
temp_vel.xyz.z *= wip->particle_spewers[psi].particle_spew_z_scale; // for an ovoid particle effect to better combine with laser effects
|
|
vm_vec_unrotate(&input_vel, &temp_vel, &obj->orient); // so it has to be rotated
|
|
}
|
|
|
|
vm_vec_add2(&output_vel, &input_vel); // add to weapon velocity
|
|
output_pos = obj->pos;
|
|
|
|
// maybe add in offset and initial velocity
|
|
if (!vm_vec_same(&spawn_vel, &vmd_zero_vector)) { // add particle velocity if needed
|
|
vm_vec_add2(&output_vel, &spawn_vel);
|
|
}
|
|
if (!vm_vec_same(&spawn_pos, &vmd_zero_vector)) { // add offset if needed
|
|
vm_vec_add2(&output_pos, &spawn_pos);
|
|
}
|
|
|
|
// emit particles
|
|
if (wip->particle_spewers[psi].particle_spew_anim.first_frame < 0) {
|
|
particle_create(&output_pos, &output_vel, wip->particle_spewers[psi].particle_spew_lifetime, wip->particle_spewers[psi].particle_spew_radius, PARTICLE_SMOKE);
|
|
} else {
|
|
particle_create(&output_pos, &output_vel, wip->particle_spewers[psi].particle_spew_lifetime, wip->particle_spewers[psi].particle_spew_radius, PARTICLE_BITMAP, wip->particle_spewers[psi].particle_spew_anim.first_frame);
|
|
}
|
|
}
|
|
} else if (wip->particle_spewers[psi].particle_spew_type == PSPEW_RING) {
|
|
float inc = PI2 / wip->particle_spewers[psi].particle_spew_count;
|
|
|
|
for (float ir = 0; ir < PI2; ir += inc) { // use iterator for rotation
|
|
input_vel.xyz.x = sinf(ir) * wip->particle_spewers[psi].particle_spew_scale; // generate velocity from rotation data
|
|
input_vel.xyz.y = cosf(ir) * wip->particle_spewers[psi].particle_spew_scale;
|
|
input_vel.xyz.z = obj->phys_info.fspeed * wip->particle_spewers[psi].particle_spew_vel;
|
|
vm_vec_unrotate(&output_vel, &input_vel, &obj->orient); // rotate it to model
|
|
|
|
output_pos = obj->pos;
|
|
|
|
// maybe add in offset amd iitial velocity
|
|
if (!vm_vec_same(&spawn_vel, &vmd_zero_vector)) { // add particle velocity if needed
|
|
vm_vec_add2(&output_vel, &spawn_vel);
|
|
}
|
|
if (!vm_vec_same(&spawn_pos, &vmd_zero_vector)) { // add offset if needed
|
|
vm_vec_add2(&output_pos, &spawn_pos);
|
|
}
|
|
|
|
// emit particles
|
|
if (wip->particle_spewers[psi].particle_spew_anim.first_frame < 0) {
|
|
particle_create(&output_pos, &output_vel, wip->particle_spewers[psi].particle_spew_lifetime, wip->particle_spewers[psi].particle_spew_radius, PARTICLE_SMOKE);
|
|
} else {
|
|
particle_create(&output_pos, &output_vel, wip->particle_spewers[psi].particle_spew_lifetime, wip->particle_spewers[psi].particle_spew_radius, PARTICLE_BITMAP, wip->particle_spewers[psi].particle_spew_anim.first_frame);
|
|
}
|
|
}
|
|
} else if (wip->particle_spewers[psi].particle_spew_type == PSPEW_PLUME) {
|
|
float ang_rand, len_rand, sin_ang, cos_ang;
|
|
vec3d input_pos_l = ZERO_VECTOR;
|
|
|
|
for (int i = 0; i < wip->particle_spewers[psi].particle_spew_count; i++) {
|
|
// use polar coordinates to ensure a disk shaped spew plane
|
|
ang_rand = frand_range(-PI,PI);
|
|
len_rand = frand() * wip->particle_spewers[psi].particle_spew_scale;
|
|
sin_ang = sinf(ang_rand);
|
|
cos_ang = cosf(ang_rand);
|
|
// compute velocity
|
|
input_vel.xyz.x = wip->particle_spewers[psi].particle_spew_z_scale * -sin_ang;
|
|
input_vel.xyz.y = wip->particle_spewers[psi].particle_spew_z_scale * -cos_ang;
|
|
input_vel.xyz.z = obj->phys_info.fspeed * wip->particle_spewers[psi].particle_spew_vel;
|
|
vm_vec_unrotate(&output_vel, &input_vel, &obj->orient); // rotate it to model
|
|
// place particle on a disk prependicular to the weapon normal and rotate to model space
|
|
input_pos_l.xyz.x = sin_ang * len_rand;
|
|
input_pos_l.xyz.y = cos_ang * len_rand;
|
|
vm_vec_unrotate(&input_pos, &input_pos_l, &obj->orient); // rotate to world
|
|
vm_vec_sub(&output_pos, &obj->pos, &input_pos); // translate to world
|
|
|
|
// maybe add in offset amd iitial velocity
|
|
if (!vm_vec_same(&spawn_vel, &vmd_zero_vector)) { // add particle velocity if needed
|
|
vm_vec_add2(&output_vel, &spawn_vel);
|
|
}
|
|
if (!vm_vec_same(&spawn_pos, &vmd_zero_vector)) { // add offset if needed
|
|
vm_vec_add2(&output_pos, &spawn_pos);
|
|
}
|
|
|
|
//emit particles
|
|
if (wip->particle_spewers[psi].particle_spew_anim.first_frame < 0) {
|
|
particle_create(&output_pos, &output_vel, wip->particle_spewers[psi].particle_spew_lifetime, wip->particle_spewers[psi].particle_spew_radius, PARTICLE_SMOKE);
|
|
} else {
|
|
particle_create(&output_pos, &output_vel, wip->particle_spewers[psi].particle_spew_lifetime, wip->particle_spewers[psi].particle_spew_radius, PARTICLE_BITMAP, wip->particle_spewers[psi].particle_spew_anim.first_frame);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Debug console functionality
|
|
*/
|
|
void dcf_pspew();
|
|
DCF(pspew_count, "Number of particles spewed at a time")
|
|
{
|
|
if (dc_optional_string_either("help", "--help")) {
|
|
dcf_pspew();
|
|
return;
|
|
}
|
|
|
|
if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) {
|
|
dc_printf("Partical count is %i\n", Weapon_particle_spew_count);
|
|
return;
|
|
}
|
|
|
|
dc_stuff_int(&Weapon_particle_spew_count);
|
|
|
|
dc_printf("Partical count set to %i\n", Weapon_particle_spew_count);
|
|
}
|
|
|
|
DCF(pspew_time, "Time between particle spews")
|
|
{
|
|
if (dc_optional_string_either("help", "--help")) {
|
|
dcf_pspew();
|
|
return;
|
|
}
|
|
|
|
if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) {
|
|
dc_printf("Particle spawn period is %i\n", Weapon_particle_spew_time);
|
|
return;
|
|
}
|
|
|
|
dc_stuff_int(&Weapon_particle_spew_time);
|
|
|
|
dc_printf("Particle spawn period set to %i\n", Weapon_particle_spew_time);
|
|
}
|
|
|
|
DCF(pspew_vel, "Relative velocity of particles (0.0 - 1.0)")
|
|
{
|
|
if (dc_optional_string_either("help", "--help")) {
|
|
dcf_pspew();
|
|
return;
|
|
}
|
|
|
|
if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) {
|
|
dc_printf("Particle relative velocity is %f\n", Weapon_particle_spew_vel);
|
|
return;
|
|
}
|
|
|
|
dc_stuff_float(&Weapon_particle_spew_vel);
|
|
|
|
dc_printf("Particle relative velocity set to %f\n", Weapon_particle_spew_vel);
|
|
}
|
|
|
|
DCF(pspew_size, "Size of spewed particles")
|
|
{
|
|
if (dc_optional_string_either("help", "--help")) {
|
|
dcf_pspew();
|
|
return;
|
|
}
|
|
|
|
if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) {
|
|
dc_printf("Particle size is %f\n", Weapon_particle_spew_radius);
|
|
return;
|
|
}
|
|
|
|
dc_stuff_float(&Weapon_particle_spew_radius);
|
|
|
|
dc_printf("Particle size set to %f\n", Weapon_particle_spew_radius);
|
|
}
|
|
|
|
DCF(pspew_life, "Lifetime of spewed particles")
|
|
{
|
|
if (dc_optional_string_either("help", "--help")) {
|
|
dcf_pspew();
|
|
return;
|
|
}
|
|
|
|
if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) {
|
|
dc_printf("Particle lifetime is %f\n", Weapon_particle_spew_lifetime);
|
|
return;
|
|
}
|
|
|
|
dc_stuff_float(&Weapon_particle_spew_lifetime);
|
|
|
|
dc_printf("Particle lifetime set to %f\n", Weapon_particle_spew_lifetime);
|
|
}
|
|
|
|
DCF(pspew_scale, "How far away particles are from the weapon path")
|
|
{
|
|
if (dc_optional_string_either("help", "--help")) {
|
|
dcf_pspew();
|
|
return;
|
|
}
|
|
|
|
if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) {
|
|
dc_printf("Particle scale is %f\n", Weapon_particle_spew_scale);
|
|
}
|
|
|
|
dc_stuff_float(&Weapon_particle_spew_scale);
|
|
|
|
dc_printf("Particle scale set to %f\n", Weapon_particle_spew_scale);
|
|
}
|
|
|
|
// Help and Status provider
|
|
DCF(pspew, "Particle spew help and status provider")
|
|
{
|
|
if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) {
|
|
dc_printf("Particle spew settings\n\n");
|
|
|
|
dc_printf(" Count (pspew_count) : %d\n", Weapon_particle_spew_count);
|
|
dc_printf(" Time (pspew_time) : %d\n", Weapon_particle_spew_time);
|
|
dc_printf(" Velocity (pspew_vel) : %f\n", Weapon_particle_spew_vel);
|
|
dc_printf(" Size (pspew_size) : %f\n", Weapon_particle_spew_radius);
|
|
dc_printf(" Lifetime (pspew_life) : %f\n", Weapon_particle_spew_lifetime);
|
|
dc_printf(" Scale (psnew_scale) : %f\n", Weapon_particle_spew_scale);
|
|
return;
|
|
}
|
|
|
|
dc_printf("Available particlar spew commands:\n");
|
|
dc_printf("pspew_count : %s\n", dcmd_pspew_count.help);
|
|
dc_printf("pspew_time : %s\n", dcmd_pspew_time.help);
|
|
dc_printf("pspew_vel : %s\n", dcmd_pspew_vel.help);
|
|
dc_printf("pspew_size : %s\n", dcmd_pspew_size.help);
|
|
dc_printf("pspew_life : %s\n", dcmd_pspew_life.help);
|
|
dc_printf("pspew_scale : %s\n\n", dcmd_pspew_scale.help);
|
|
|
|
dc_printf("To view status of all pspew settings, type in 'pspew --status'.\n");
|
|
dc_printf("Passing '--status' as an argument to any of the individual spew commands will show the status of that variable only.\n\n");
|
|
|
|
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");
|
|
dc_printf("Generally, a large particle count with small size and scale will result in a nice dense particle spew.\n");
|
|
dc_printf("Be advised, this effect is applied to _ALL_ weapons, and as such may drastically reduce framerates on lower powered platforms.\n");
|
|
}
|
|
|
|
/**
|
|
* Return a scale factor for damage which should be applied for 2 collisions
|
|
*/
|
|
float weapon_get_damage_scale(weapon_info *wip, object *wep, object *target)
|
|
{
|
|
weapon *wp;
|
|
int from_player = 0;
|
|
float total_scale = 1.0f;
|
|
float hull_pct;
|
|
int is_big_damage_ship = 0;
|
|
|
|
// Goober5000 - additional sanity (target can be NULL)
|
|
Assert(wip);
|
|
Assert(wep);
|
|
|
|
// sanity
|
|
if((wip == NULL) || (wep == NULL) || (target == NULL)){
|
|
return 1.0f;
|
|
}
|
|
|
|
// don't scale any damage if its not a weapon
|
|
if((wep->type != OBJ_WEAPON) || (wep->instance < 0) || (wep->instance >= MAX_WEAPONS)){
|
|
return 1.0f;
|
|
}
|
|
wp = &Weapons[wep->instance];
|
|
|
|
// was the weapon fired by the player
|
|
from_player = 0;
|
|
if((wep->parent >= 0) && (wep->parent < MAX_OBJECTS) && (Objects[wep->parent].flags & OF_PLAYER_SHIP)){
|
|
from_player = 1;
|
|
}
|
|
|
|
// if this is a lockarm weapon, and it was fired unlocked
|
|
if((wip->wi_flags & WIF_LOCKARM) && !(wp->weapon_flags & WF_LOCKED_WHEN_FIRED)){
|
|
total_scale *= 0.1f;
|
|
}
|
|
|
|
// if the hit object was a ship and we're doing damage scaling
|
|
if ( (target->type == OBJ_SHIP) &&
|
|
!(The_mission.ai_profile->flags & AIPF_DISABLE_WEAPON_DAMAGE_SCALING) &&
|
|
!(Ship_info[Ships[target->instance].ship_info_index].flags2 & SIF2_DISABLE_WEAPON_DAMAGE_SCALING)
|
|
) {
|
|
ship_info *sip;
|
|
|
|
// get some info on the ship
|
|
Assert((target->instance >= 0) && (target->instance < MAX_SHIPS));
|
|
if((target->instance < 0) || (target->instance >= MAX_SHIPS)){
|
|
return total_scale;
|
|
}
|
|
sip = &Ship_info[Ships[target->instance].ship_info_index];
|
|
|
|
// get hull pct of the ship currently
|
|
hull_pct = get_hull_pct(target);
|
|
|
|
// if it has hit a supercap ship and is not a supercap class weapon
|
|
if((sip->flags & SIF_SUPERCAP) && !(wip->wi_flags & WIF_SUPERCAP)){
|
|
// if the supercap is around 3/4 damage, apply nothing
|
|
if(hull_pct <= 0.75f){
|
|
return 0.0f;
|
|
} else {
|
|
total_scale *= SUPERCAP_DAMAGE_SCALE;
|
|
}
|
|
}
|
|
|
|
// determine if this is a big damage ship
|
|
is_big_damage_ship = (sip->flags & SIF_BIG_DAMAGE);
|
|
|
|
// if this is a large ship, and is being hit by flak
|
|
if(is_big_damage_ship && (wip->wi_flags & WIF_FLAK)){
|
|
total_scale *= FLAK_DAMAGE_SCALE;
|
|
}
|
|
|
|
// if the weapon is a small weapon being fired at a big ship
|
|
if( is_big_damage_ship && !(wip->wi_flags & (WIF_HURTS_BIG_SHIPS)) ){
|
|
|
|
// if the player is firing it
|
|
if ( from_player && !(The_mission.ai_profile->flags2 & AIPF2_PLAYER_WEAPON_SCALE_FIX)) {
|
|
// if it's a laser weapon
|
|
if(wip->subtype == WP_LASER){
|
|
total_scale *= 0.01f;
|
|
} else {
|
|
total_scale *= 0.05f;
|
|
}
|
|
}
|
|
|
|
// scale based on hull
|
|
if(hull_pct > 0.1f){
|
|
total_scale *= hull_pct;
|
|
} else {
|
|
return 0.0f;
|
|
}
|
|
}
|
|
}
|
|
|
|
return total_scale;
|
|
}
|
|
|
|
void pause_in_flight_sounds()
|
|
{
|
|
for (int i = 0; i < MAX_WEAPONS; i++)
|
|
{
|
|
if (Weapons[i].objnum != -1)
|
|
{
|
|
weapon* wp = &Weapons[i];
|
|
|
|
if (wp->hud_in_flight_snd_sig >= 0 && snd_is_playing(wp->hud_in_flight_snd_sig))
|
|
{
|
|
// Stop sound, it will be restarted in the first frame after the game is unpaused
|
|
snd_stop(wp->hud_in_flight_snd_sig);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void weapon_pause_sounds()
|
|
{
|
|
// Pause all beam sounds
|
|
beam_pause_sounds();
|
|
|
|
// Pause in-flight sounds
|
|
pause_in_flight_sounds();
|
|
}
|
|
|
|
void weapon_unpause_sounds()
|
|
{
|
|
// Pause all beam sounds
|
|
beam_unpause_sounds();
|
|
}
|
|
|
|
void weapon_render(object* obj, draw_list *scene)
|
|
{
|
|
int num;
|
|
weapon_info *wip;
|
|
weapon *wp;
|
|
color c;
|
|
|
|
MONITOR_INC(NumWeaponsRend, 1);
|
|
|
|
Assert(obj->type == OBJ_WEAPON);
|
|
|
|
num = obj->instance;
|
|
wp = &Weapons[num];
|
|
wip = &Weapon_info[Weapons[num].weapon_info_index];
|
|
|
|
if (wip->wi_flags2 & WIF2_TRANSPARENT) {
|
|
if (wp->alpha_current == -1.0f) {
|
|
wp->alpha_current = wip->alpha_max;
|
|
} else if (wip->alpha_cycle > 0.0f) {
|
|
if (wp->alpha_backward) {
|
|
wp->alpha_current += wip->alpha_cycle;
|
|
|
|
if (wp->alpha_current > wip->alpha_max) {
|
|
wp->alpha_current = wip->alpha_max;
|
|
wp->alpha_backward = 0;
|
|
}
|
|
} else {
|
|
wp->alpha_current -= wip->alpha_cycle;
|
|
|
|
if (wp->alpha_current < wip->alpha_min) {
|
|
wp->alpha_current = wip->alpha_min;
|
|
wp->alpha_backward = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
switch (wip->render_type)
|
|
{
|
|
case WRT_LASER:
|
|
{
|
|
if(wip->laser_length < 0.0001f)
|
|
return;
|
|
|
|
int alpha = 255;
|
|
int framenum = 0;
|
|
|
|
if (wip->laser_bitmap.first_frame >= 0) {
|
|
gr_set_color_fast(&wip->laser_color_1);
|
|
|
|
if (wip->laser_bitmap.num_frames > 1) {
|
|
wp->laser_bitmap_frame += flFrametime;
|
|
|
|
// Sanity checks
|
|
if (wp->laser_bitmap_frame < 0.0f)
|
|
wp->laser_bitmap_frame = 0.0f;
|
|
if (wp->laser_bitmap_frame > 100.0f)
|
|
wp->laser_bitmap_frame = 0.0f;
|
|
|
|
while (wp->laser_bitmap_frame > wip->laser_bitmap.total_time)
|
|
wp->laser_bitmap_frame -= wip->laser_bitmap.total_time;
|
|
|
|
framenum = fl2i( (wp->laser_bitmap_frame * wip->laser_bitmap.num_frames) / wip->laser_bitmap.total_time );
|
|
|
|
CLAMP(framenum, 0, wip->laser_bitmap.num_frames-1);
|
|
}
|
|
|
|
if (wip->wi_flags2 & WIF2_TRANSPARENT)
|
|
alpha = fl2i(wp->alpha_current * 255.0f);
|
|
|
|
vec3d headp;
|
|
|
|
vm_vec_scale_add(&headp, &obj->pos, &obj->orient.vec.fvec, wip->laser_length);
|
|
wp->weapon_flags &= ~WF_CONSIDER_FOR_FLYBY_SOUND;
|
|
|
|
if ( batch_add_laser(wip->laser_bitmap.first_frame + framenum, &headp, wip->laser_head_radius, &obj->pos, wip->laser_tail_radius, alpha, alpha, alpha) ) {
|
|
wp->weapon_flags |= WF_CONSIDER_FOR_FLYBY_SOUND;
|
|
}
|
|
}
|
|
|
|
// maybe draw laser glow bitmap
|
|
if (wip->laser_glow_bitmap.first_frame >= 0) {
|
|
// get the laser color
|
|
weapon_get_laser_color(&c, obj);
|
|
|
|
// *Tail point "getting bigger" as well as headpoint isn't being taken into consideration, so
|
|
// it caused uneven glow between the head and tail, which really shows in big lasers. So...fixed! -Et1
|
|
vec3d headp2, tailp;
|
|
|
|
vm_vec_scale_add(&headp2, &obj->pos, &obj->orient.vec.fvec, wip->laser_length * weapon_glow_scale_l);
|
|
vm_vec_scale_add(&tailp, &obj->pos, &obj->orient.vec.fvec, wip->laser_length * (1 - weapon_glow_scale_l) );
|
|
|
|
framenum = 0;
|
|
|
|
if (wip->laser_glow_bitmap.num_frames > 1) {
|
|
wp->laser_glow_bitmap_frame += flFrametime;
|
|
|
|
// Sanity checks
|
|
if (wp->laser_glow_bitmap_frame < 0.0f)
|
|
wp->laser_glow_bitmap_frame = 0.0f;
|
|
if (wp->laser_glow_bitmap_frame > 100.0f)
|
|
wp->laser_glow_bitmap_frame = 0.0f;
|
|
|
|
while (wp->laser_glow_bitmap_frame > wip->laser_glow_bitmap.total_time)
|
|
wp->laser_glow_bitmap_frame -= wip->laser_glow_bitmap.total_time;
|
|
|
|
framenum = fl2i( (wp->laser_glow_bitmap_frame * wip->laser_glow_bitmap.num_frames) / wip->laser_glow_bitmap.total_time );
|
|
|
|
CLAMP(framenum, 0, wip->laser_glow_bitmap.num_frames-1);
|
|
}
|
|
|
|
if (wip->wi_flags2 & WIF2_TRANSPARENT) {
|
|
alpha = fl2i(wp->alpha_current * 255.0f);
|
|
alpha -= 38; // take 1.5f into account for the normal glow alpha
|
|
|
|
if (alpha < 0)
|
|
alpha = 0;
|
|
} else {
|
|
alpha = weapon_glow_alpha;
|
|
}
|
|
|
|
batch_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);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case WRT_POF:
|
|
{
|
|
model_render_params render_info;
|
|
|
|
uint render_flags = MR_NORMAL|MR_IS_MISSILE|MR_NO_LIGHTING|MR_NO_BATCH;
|
|
|
|
if (Cmdline_missile_lighting && !(wip->wi_flags2 & WIF2_MR_NO_LIGHTING))
|
|
render_flags &= ~MR_NO_LIGHTING;
|
|
|
|
if (wip->wi_flags2 & WIF2_TRANSPARENT) {
|
|
render_info.set_alpha(wp->alpha_current);
|
|
render_flags |= MR_ALL_XPARENT;
|
|
}
|
|
|
|
model_clear_instance(wip->model_num);
|
|
|
|
if ( (wip->wi_flags & WIF_THRUSTER) && ((wp->thruster_bitmap > -1) || (wp->thruster_glow_bitmap > -1)) ) {
|
|
float ft;
|
|
mst_info mst;
|
|
|
|
// Add noise to thruster geometry.
|
|
ft = 1.0f; // Always use 1.0f for missiles
|
|
ft *= (1.0f + frand()/5.0f - 1.0f/10.0f);
|
|
if (ft > 1.0f)
|
|
ft = 1.0f;
|
|
|
|
mst.length.xyz.x = ft;
|
|
mst.length.xyz.y = ft;
|
|
mst.length.xyz.z = ft;
|
|
|
|
mst.primary_bitmap = wp->thruster_bitmap;
|
|
mst.primary_glow_bitmap = wp->thruster_glow_bitmap;
|
|
mst.glow_rad_factor = wip->thruster_glow_factor;
|
|
mst.glow_noise = wp->thruster_glow_noise;
|
|
|
|
render_info.set_thruster_info(mst);
|
|
|
|
render_flags |= MR_SHOW_THRUSTERS;
|
|
}
|
|
|
|
|
|
//don't render local ssm's when they are still in subspace
|
|
if (wp->lssm_stage==3)
|
|
break;
|
|
|
|
int clip_plane=0;
|
|
|
|
// start a clip plane
|
|
if ( wp->lssm_stage == 2 ) {
|
|
object *wobj=&Objects[wp->lssm_warp_idx]; //warphole object
|
|
clip_plane=1;
|
|
|
|
render_info.set_clip_plane(wobj->pos, wobj->orient.vec.fvec);
|
|
}
|
|
|
|
render_info.set_flags(render_flags);
|
|
|
|
model_render_queue(&render_info, scene, wip->model_num, &obj->orient, &obj->pos);
|
|
wp->weapon_flags |= WF_CONSIDER_FOR_FLYBY_SOUND;
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
Warning(LOCATION, "Unknown weapon rendering type = %i for weapon %s\n", wip->render_type, wip->name);
|
|
}
|
|
}
|