Files
fs2open.github.com/code/ship/ship.cpp
T
2022-01-30 13:47:59 -05:00

20120 lines
647 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 <csetjmp>
#include <algorithm>
#include "ai/aibig.h"
#include "ai/aigoals.h"
#include "asteroid/asteroid.h"
#include "autopilot/autopilot.h"
#include "cmdline/cmdline.h"
#include "cmeasure/cmeasure.h"
#include "debugconsole/console.h"
#include "fireball/fireballs.h"
#include "freespace.h"
#include "gamesequence/gamesequence.h"
#include "gamesnd/eventmusic.h"
#include "gamesnd/gamesnd.h"
#include "globalincs/alphacolors.h"
#include "graphics/matrix.h"
#include "graphics/shadows.h"
#include "def_files/def_files.h"
#include "globalincs/linklist.h"
#include "hud/hud.h"
#include "hud/hudartillery.h"
#include "hud/hudets.h"
#include "hud/hudmessage.h"
#include "hud/hudshield.h"
#include "hud/hudsquadmsg.h"
#include "hud/hudtargetbox.h"
#include "hud/hudwingmanstatus.h"
#include "iff_defs/iff_defs.h"
#include "io/joy_ff.h"
#include "io/timer.h"
#include "jumpnode/jumpnode.h"
#include "lighting/lighting.h"
#include "localization/localize.h"
#include "math/fvi.h"
#include "math/staticrand.h"
#include "math/vecmat.h"
#include "mission/missioncampaign.h"
#include "mission/missionlog.h"
#include "mission/missionmessage.h"
#include "missionui/missionshipchoice.h"
#include "missionui/redalert.h"
#include "mod_table/mod_table.h"
#include "model/model.h"
#include "model/modelrender.h"
#include "nebula/neb.h"
#include "network/multimsgs.h"
#include "network/multiutil.h"
#include "object/deadobjectdock.h"
#include "object/objcollide.h"
#include "object/object.h"
#include "object/objectdock.h"
#include "object/objectshield.h"
#include "object/objectsnd.h"
#include "object/waypoint.h"
#include "parse/parselo.h"
#include "scripting/hook_api.h"
#include "scripting/scripting.h"
#include "particle/particle.h"
#include "playerman/player.h"
#include "radar/radar.h"
#include "radar/radarsetup.h"
#include "render/3d.h"
#include "render/batching.h"
#include "scripting/api/objs/vecmath.h"
#include "ship/afterburner.h"
#include "ship/ship.h"
#include "ship/shipcontrails.h"
#include "ship/shipfx.h"
#include "ship/shiphit.h"
#include "ship/subsysdamage.h"
#include "species_defs/species_defs.h"
#include "tracing/Monitor.h"
#include "tracing/tracing.h"
#include "utils/Random.h"
#include "weapon/beam.h"
#include "weapon/corkscrew.h"
#include "weapon/emp.h"
#include "weapon/flak.h" //phreak addded 11/05/02 for flak primaries
#include "weapon/shockwave.h"
#include "weapon/swarm.h"
#include "weapon/weapon.h"
using namespace Ship;
#ifdef MessageBox
#undef MessageBox
#endif
#define NUM_SHIP_SUBSYSTEMS_PER_SET 200 // Reduced from 1000 to 400 by MK on 4/1/98. DTP; bumped from 700 to 2100
// Reduced to 200 by taylor on 3/13/07 -- it's managed in dynamically allocated sets now
// Highest I saw was 164 in sm2-03a which Sandeep says has a lot of ships.
// JAS: sm3-01 needs 460. You cannot know this number until *all* ships
// have warped in. So I put code in the paging code which knows all ships
// that will warp in.
static int Num_ship_subsystems = 0;
static int Num_ship_subsystems_allocated = 0;
static SCP_vector<ship_subsys*> Ship_subsystems;
ship_subsys ship_subsys_free_list;
extern bool splodeing;
extern float splode_level;
extern int splodeingtexture;
#define SHIP_REPAIR_SUBSYSTEM_RATE 0.01f
// The minimum required fuel to engage afterburners
static const float DEFAULT_MIN_AFTERBURNER_FUEL_TO_ENGAGE = 10.0f;
int Ai_render_debug_flag=0;
#ifndef NDEBUG
int Ship_auto_repair = 1; // flag to indicate auto-repair of subsystem should occur
#endif
int Num_wings = 0;
int Num_reinforcements = 0;
ship Ships[MAX_SHIPS];
ship *Player_ship;
int *Player_cockpit_textures;
SCP_vector<cockpit_display> Player_displays;
wing Wings[MAX_WINGS];
bool Ships_inited = false;
bool Armor_inited = false;
int Starting_wings[MAX_STARTING_WINGS]; // wings player starts a mission with (-1 = none)
// Goober5000
int Squadron_wings[MAX_SQUADRON_WINGS];
int TVT_wings[MAX_TVT_WINGS];
// Goober5000
char Starting_wing_names[MAX_STARTING_WINGS][NAME_LENGTH];
char Squadron_wing_names[MAX_SQUADRON_WINGS][NAME_LENGTH];
bool Squadron_wing_names_found[MAX_SQUADRON_WINGS];
char TVT_wing_names[MAX_TVT_WINGS][NAME_LENGTH];
SCP_vector<engine_wash_info> Engine_wash_info;
static engine_wash_info *get_engine_wash_pointer(char* engine_wash_name);
static int subsys_set(int objnum, int ignore_subsys_info = 0);
static void ship_add_cockpit_display(cockpit_display_info *display, int cockpit_model_num);
static void ship_set_hud_cockpit_targets();
static int thruster_glow_anim_load(generic_anim *ga);
static int ship_get_exp_propagates(ship *sp);
static bool ship_subsys_is_fighterbay(ship_subsys *ss);
static int ship_template_lookup(const char *token);
static void ship_set_eye(object *obj, int eye_index);
static void ship_start_targeting_laser(ship *shipp);
static void ship_add_ship_type_kill_count(int ship_info_index);
static int ship_info_lookup_sub(const char *token);
void ship_reset_disabled_physics(object *objp, int ship_class);
// forward declaring for parse_ship()
static void parse_ship_values(ship_info* sip, const bool is_template, const bool first_time, const bool replace);
// information for ships which have exited the game
SCP_vector<exited_ship> Ships_exited;
SCP_vector<ship_registry_entry> Ship_registry;
SCP_unordered_map<SCP_string, int, SCP_string_lcase_hash, SCP_string_lcase_equal_to> Ship_registry_map;
const ship_registry_entry *ship_registry_get(const char *name)
{
auto ship_it = Ship_registry_map.find(name);
if (ship_it != Ship_registry_map.end())
return &Ship_registry[ship_it->second];
return nullptr;
}
int Num_engine_wash_types;
int Num_ship_subobj_types;
int Num_ship_subobjects;
int Player_ship_class; // needs to be player specific, move to player structure
#define SHIP_OBJ_USED (1<<0) // flag used in ship_obj struct
#define MAX_SHIP_OBJS MAX_SHIPS // max number of ships tracked in ship list
ship_obj Ship_objs[MAX_SHIP_OBJS]; // array used to store ship object indexes
ship_obj Ship_obj_list; // head of linked list of ship_obj structs
SCP_vector<ship_info> Ship_info;
reinforcements Reinforcements[MAX_REINFORCEMENTS];
SCP_vector<ship_info> Ship_templates;
SCP_vector<ship_type_info> Ship_types;
SCP_vector<ArmorType> Armor_types;
SCP_vector<DamageTypeStruct> Damage_types;
flag_def_list Armor_flags[] = {
{ "ignore subsystem armor", SAF_IGNORE_SS_ARMOR, 0 }
};
const int Num_armor_flags = sizeof(Armor_flags)/sizeof(flag_def_list);
flag_def_list_new<Thruster_Flags> Man_types[] = {
{ "Bank right", Thruster_Flags::Bank_right, true, false },
{ "Bank left", Thruster_Flags::Bank_left, true, false },
{ "Pitch up", Thruster_Flags::Pitch_up, true, false },
{ "Pitch down", Thruster_Flags::Pitch_down, true, false },
{ "Roll right", Thruster_Flags::Roll_right, true, false },
{ "Roll left", Thruster_Flags::Roll_left, true, false },
{ "Slide right",Thruster_Flags::Slide_right,true, false },
{ "Slide left", Thruster_Flags::Slide_left, true, false },
{ "Slide up", Thruster_Flags::Slide_up, true, false },
{ "Slide down", Thruster_Flags::Slide_down, true, false },
{ "Forward", Thruster_Flags::Forward, true, false },
{ "Reverse", Thruster_Flags::Reverse, true, false }
};
const size_t Num_man_types = sizeof(Man_types) / sizeof(flag_def_list_new<Thruster_Flags>);
// Use the last parameter here to tell the parser whether to stuff the flag into flags or flags2
flag_def_list_new<Model::Subsystem_Flags> Subsystem_flags[] = {
{ "untargetable", Model::Subsystem_Flags::Untargetable, true, false },
{ "carry no damage", Model::Subsystem_Flags::Carry_no_damage, true, false },
{ "use multiple guns", Model::Subsystem_Flags::Use_multiple_guns, true, false },
{ "fire down normals", Model::Subsystem_Flags::Fire_on_normal, true, false },
{ "check hull", Model::Subsystem_Flags::Turret_hull_check, true, false },
{ "fixed firingpoints", Model::Subsystem_Flags::Turret_fixed_fp, true, false },
{ "salvo mode", Model::Subsystem_Flags::Turret_salvo, true, false },
{ "no subsystem targeting", Model::Subsystem_Flags::No_ss_targeting, true, false },
{ "fire on target", Model::Subsystem_Flags::Fire_on_target, true, false },
{ "reset when idle", Model::Subsystem_Flags::Turret_reset_idle, true, false },
{ "carry shockwave", Model::Subsystem_Flags::Carry_shockwave, true, false },
{ "allow landing", Model::Subsystem_Flags::Allow_landing, true, false },
{ "target requires fov", Model::Subsystem_Flags::Fov_required, true, false },
{ "fov edge checks", Model::Subsystem_Flags::Fov_edge_check, true, false },
{ "no replace", Model::Subsystem_Flags::No_replace, true, false },
{ "no live debris", Model::Subsystem_Flags::No_live_debris, true, false },
{ "ignore if dead", Model::Subsystem_Flags::Ignore_if_dead, true, false },
{ "allow vanishing", Model::Subsystem_Flags::Allow_vanishing, true, false },
{ "damage as hull", Model::Subsystem_Flags::Damage_as_hull, true, false },
{ "starts locked", Model::Subsystem_Flags::Turret_locked, true, false },
{ "no aggregate", Model::Subsystem_Flags::No_aggregate, true, false },
{ "wait for animation", Model::Subsystem_Flags::Turret_anim_wait, true, false },
{ "play fire sound for player", Model::Subsystem_Flags::Player_turret_sound, true, false },
{ "only target if can fire", Model::Subsystem_Flags::Turret_only_target_if_can_fire, true, false },
{ "no disappear", Model::Subsystem_Flags::No_disappear, true, false },
{ "collide submodel", Model::Subsystem_Flags::Collide_submodel, true, false },
{ "allow destroyed rotation", Model::Subsystem_Flags::Destroyed_rotation, true, false },
{ "turret use ammo", Model::Subsystem_Flags::Turret_use_ammo, true, false },
{ "autorepair if disabled", Model::Subsystem_Flags::Autorepair_if_disabled, true, false },
{ "don't autorepair if disabled", Model::Subsystem_Flags::No_autorepair_if_disabled, true, false },
{ "share fire direction", Model::Subsystem_Flags::Share_fire_direction, true, false },
{ "no damage spew", Model::Subsystem_Flags::No_sparks, true, false },
{ "no impact debris", Model::Subsystem_Flags::No_impact_debris, true, false },
};
const size_t Num_subsystem_flags = sizeof(Subsystem_flags)/sizeof(flag_def_list_new<Model::Subsystem_Flags>);
flag_def_list_new<Info_Flags> Ship_flags[] = {
{ "no_collide", Info_Flags::No_collide, true, false },
{ "player_ship", Info_Flags::Player_ship, true, false },
{ "default_player_ship", Info_Flags::Default_player_ship, true, false },
{ "repair_rearm", Info_Flags::Support, true, false },
{ "cargo", Info_Flags::Cargo, true, false },
{ "fighter", Info_Flags::Fighter, true, false },
{ "bomber", Info_Flags::Bomber, true, false },
{ "transport", Info_Flags::Transport, true, false },
{ "freighter", Info_Flags::Freighter, true, false },
{ "capital", Info_Flags::Capital, true, false },
{ "supercap", Info_Flags::Supercap, true, false },
{ "drydock", Info_Flags::Drydock, true, false },
{ "cruiser", Info_Flags::Cruiser, true, false },
{ "navbuoy", Info_Flags::Navbuoy, true, false },
{ "sentrygun", Info_Flags::Sentrygun, true, false },
{ "escapepod", Info_Flags::Escapepod, true, false },
{ "stealth", Info_Flags::Stealth, true, false },
{ "no type", Info_Flags::No_ship_type, true, false },
{ "ship copy", Info_Flags::Ship_copy, true, false },
{ "in tech database", Info_Flags::In_tech_database, true, false },
{ "in tech database multi", Info_Flags::In_tech_database_m, true, false },
{ "don't collide invisible", Info_Flags::Ship_class_dont_collide_invis, true, false },
{ "big damage", Info_Flags::Big_damage, true, false },
{ "corvette", Info_Flags::Corvette, true, false },
{ "gas miner", Info_Flags::Gas_miner, true, false },
{ "awacs", Info_Flags::Awacs, true, false },
{ "knossos", Info_Flags::Knossos_device, true, false },
{ "no_fred", Info_Flags::No_fred, true, false },
{ "flash", Info_Flags::Flash, true, false },
{ "surface shields", Info_Flags::Surface_shields, true, false },
{ "show ship", Info_Flags::Show_ship_model, true, false },
{ "generate icon", Info_Flags::Generate_hud_icon, true, false },
{ "no weapon damage scaling", Info_Flags::Disable_weapon_damage_scaling, true, false },
{ "gun convergence", Info_Flags::Gun_convergence, true, false },
{ "no thruster geometry noise", Info_Flags::No_thruster_geo_noise, true, false },
{ "intrinsic no shields", Info_Flags::Intrinsic_no_shields, true, false },
{ "dynamic primary linking", Info_Flags::Dyn_primary_linking, true, false },
{ "no primary linking", Info_Flags::No_primary_linking, true, false },
{ "no pain flash", Info_Flags::No_pain_flash, true, false },
{ "no ets", Info_Flags::No_ets, true, false },
{ "no lighting", Info_Flags::No_lighting, true, false },
{ "auto spread shields", Info_Flags::Auto_spread_shields, true, false },
{ "model point shields", Info_Flags::Model_point_shields, true, false },
{ "repair disabled subsystems", Info_Flags::Subsys_repair_when_disabled, true, false},
{ "don't bank when turning", Info_Flags::Dont_bank_when_turning, true, false },
{ "don't clamp max velocity", Info_Flags::Dont_clamp_max_velocity, true, false },
{ "instantaneous acceleration", Info_Flags::Instantaneous_acceleration, true, false },
{ "large ship deathroll", Info_Flags::Large_ship_deathroll, true, false },
{ "no impact debris", Info_Flags::No_impact_debris, true, false },
// to keep things clean, obsolete options go last
{ "ballistic primaries", Info_Flags::Ballistic_primaries, false, false }
};
const size_t Num_ship_flags = sizeof(Ship_flags) / sizeof(flag_def_list_new<Info_Flags>);
/*
++Here be dragons.. err.. begins the section for the ai targeting revision
++ First flag_def_list (& its size) for object types (ship/asteroid/weapon)
++ List of reasonable object flags (from object.h)
++ List of potentially useful ship class flags
++ List of potentially useful weapon class flags
*/
flag_def_list ai_tgt_objects[] = {
{ "ship", OBJ_SHIP, 0 },
{ "asteroid", OBJ_ASTEROID, 0 },
{ "weapon", OBJ_WEAPON, 0 },
{ "debris", OBJ_DEBRIS, 0 },
};
const int num_ai_tgt_objects = sizeof(ai_tgt_objects) / sizeof(flag_def_list);
flag_def_list_new<Object::Object_Flags> ai_tgt_obj_flags[] = {
{ "no shields", Object::Object_Flags::No_shields, true, false },
{ "targetable as bomb", Object::Object_Flags::Targetable_as_bomb, true, false },
{ "renders", Object::Object_Flags::Renders, true, false },
{ "collides", Object::Object_Flags::Collides, true, false },
{ "physics", Object::Object_Flags::Physics, true, false },
{ "invulnerable", Object::Object_Flags::Invulnerable, true, false },
{ "player ship", Object::Object_Flags::Player_ship, true, false },
{ "special warpin", Object::Object_Flags::Special_warpin, true, false },
{ "immobile", Object::Object_Flags::Immobile, true, false },
};
const int num_ai_tgt_obj_flags = sizeof(ai_tgt_obj_flags) / sizeof(flag_def_list_new<Object::Object_Flags>);
flag_def_list_new<Ship::Info_Flags> ai_tgt_ship_flags[] = {
{ "afterburners", Info_Flags::Afterburner, true, false },
{ "big damage", Info_Flags::Big_damage, true, false },
{ "has awacs", Info_Flags::Has_awacs, true, false },
{ "no collide", Info_Flags::No_collide, true, false },
{ "player ship", Info_Flags::Player_ship, true, false },
{ "default player ship", Info_Flags::Default_player_ship, true, false },
{ "path fixup", Info_Flags::Path_fixup, true, false },
{ "support", Info_Flags::Support, true, false },
{ "cargo", Info_Flags::Cargo, true, false },
{ "fighter", Info_Flags::Fighter, true, false },
{ "bomber", Info_Flags::Bomber, true, false },
{ "cruiser", Info_Flags::Cruiser, true, false },
{ "freighter", Info_Flags::Freighter, true, false },
{ "capital", Info_Flags::Capital, true, false },
{ "transport", Info_Flags::Transport, true, false },
{ "navbuoy", Info_Flags::Navbuoy, true, false },
{ "sentrygun", Info_Flags::Sentrygun, true, false },
{ "escapepod", Info_Flags::Escapepod, true, false },
{ "no type", Info_Flags::No_ship_type, true, false },
{ "ship copy", Info_Flags::Ship_copy, true, false },
{ "in tech database", Info_Flags::In_tech_database, true, false },
{ "in tech database multi", Info_Flags::In_tech_database_m, true, false },
{ "stealth", Info_Flags::Stealth, true, false },
{ "supercap", Info_Flags::Supercap, true, false },
{ "drydock", Info_Flags::Drydock, true, false },
{ "dont collide invisible", Info_Flags::Ship_class_dont_collide_invis, true, false },
{ "corvette", Info_Flags::Corvette, true, false },
{ "gas miner", Info_Flags::Gas_miner, true, false },
{ "awacs", Info_Flags::Awacs, true, false },
{ "knossos", Info_Flags::Knossos_device, true, false },
{ "no fred", Info_Flags::No_fred, true, false },
{ "default in tech database", Info_Flags::Default_in_tech_database, true, false },
{ "default in tech database multi", Info_Flags::Default_in_tech_database_m, true, false },
{ "flash", Info_Flags::Flash, true, false },
{ "show ship", Info_Flags::Show_ship_model, true, false },
{ "surface shields", Info_Flags::Surface_shields, true, false },
{ "generate icon", Info_Flags::Generate_hud_icon, true, false },
{ "no weapon damage scaling", Info_Flags::Disable_weapon_damage_scaling, true, false },
{ "gun convergence", Info_Flags::Gun_convergence, true, false },
{ "no thruster geometry noise", Info_Flags::No_thruster_geo_noise, true, false },
{ "intrinsic no shields", Info_Flags::Intrinsic_no_shields, true, false },
{ "no primary linking", Info_Flags::No_primary_linking, true, false },
{ "no pain flash", Info_Flags::No_pain_flash, true, false },
{ "allow landings", Info_Flags::Allow_landings, true, false },
{ "no ets", Info_Flags::No_ets, true, false },
{ "no lighting", Info_Flags::No_lighting, true, false },
{ "dyn primary linking", Info_Flags::Dyn_primary_linking, true, false },
{ "auto spread shields", Info_Flags::Auto_spread_shields, true, false },
{ "draw weapon models", Info_Flags::Draw_weapon_models, true, false },
{ "model point shields", Info_Flags::Model_point_shields, true, false },
{ "repair disabled subsystems", Info_Flags::Subsys_repair_when_disabled, true, false },
};
const int num_ai_tgt_ship_flags = sizeof(ai_tgt_ship_flags) / sizeof(flag_def_list_new<Ship::Info_Flags>);
flag_def_list_new<Weapon::Info_Flags> ai_tgt_weapon_flags[] = {
{ "bomb", Weapon::Info_Flags::Bomb, true, false },
{ "huge damage", Weapon::Info_Flags::Huge, true, false },
{ "supercap damage", Weapon::Info_Flags::Supercap, true, false },
{ "bomber+", Weapon::Info_Flags::Bomber_plus, true, false },
{ "electronics", Weapon::Info_Flags::Electronics, true, false },
{ "puncture", Weapon::Info_Flags::Puncture, true, false },
{ "emp", Weapon::Info_Flags::Emp, true, false },
{ "heat seeking", Weapon::Info_Flags::Homing_heat, true, false },
{ "aspect seeking", Weapon::Info_Flags::Homing_aspect, true, false },
{ "engine seeking", Weapon::Info_Flags::Homing_javelin, true, false },
{ "pierce shields", Weapon::Info_Flags::Pierce_shields, true, false },
{ "local ssm", Weapon::Info_Flags::Local_ssm, true, false },
{ "capital+", Weapon::Info_Flags::Capital_plus, true, false },
{ "heat-seeking", Weapon::Info_Flags::Homing_heat, true, false },
{ "aspect-seeking", Weapon::Info_Flags::Homing_aspect, true, false },
{ "javelin", Weapon::Info_Flags::Homing_javelin, true, false },
{ "spawn", Weapon::Info_Flags::Spawn, true, false },
{ "remote detonate", Weapon::Info_Flags::Remote, true, false },
{ "countermeasure", Weapon::Info_Flags::Cmeasure, true, false },
{ "turns", Weapon::Info_Flags::Turns, true, false },
{ "swarm", Weapon::Info_Flags::Swarm, true, false },
{ "trail", Weapon::Info_Flags::Trail, true, false },
{ "big ship", Weapon::Info_Flags::Big_only, true, false },
{ "child", Weapon::Info_Flags::Child, true, false },
{ "no dumbfire", Weapon::Info_Flags::No_dumbfire, true, false },
{ "no doublefire", Weapon::Info_Flags::No_doublefire, true, false },
{ "thruster", Weapon::Info_Flags::Thruster, true, false },
{ "in tech database", Weapon::Info_Flags::In_tech_database, true, false },
{ "player allowed", Weapon::Info_Flags::Player_allowed, true, false },
{ "corkscrew", Weapon::Info_Flags::Corkscrew, true, false },
{ "particle spew", Weapon::Info_Flags::Particle_spew, true, false },
{ "esuck", Weapon::Info_Flags::Energy_suck, true, false },
{ "flak", Weapon::Info_Flags::Flak, true, false },
//{ "beam", Weapon::Info_Flags::Beam, true, false }, // Okay, this one probably doesn't make sense.
{ "tag", Weapon::Info_Flags::Tag, true, false },
{ "shudder", Weapon::Info_Flags::Shudder, true, false },
{ "lockarm", Weapon::Info_Flags::Lockarm, true, false },
{ "ballistic", Weapon::Info_Flags::Ballistic, true, false },
{ "default in tech database", Weapon::Info_Flags::Default_in_tech_database, true, false },
{ "tagged only", Weapon::Info_Flags::Tagged_only, true, false },
{ "cycle", Weapon::Info_Flags::Cycle, true, false },
{ "small only", Weapon::Info_Flags::Small_only, true, false },
{ "same turret cooldown", Weapon::Info_Flags::Same_turret_cooldown, true, false },
{ "apply no light", Weapon::Info_Flags::Mr_no_lighting, true, false },
{ "transparent", Weapon::Info_Flags::Transparent, true, false },
{ "training", Weapon::Info_Flags::Training, true, false },
{ "smart spawn", Weapon::Info_Flags::Smart_spawn, true, false },
{ "inherit parent target", Weapon::Info_Flags::Inherit_parent_target, true, false },
{ "no emp kill", Weapon::Info_Flags::No_emp_kill, true, false },
{ "variable lead homing", Weapon::Info_Flags::Variable_lead_homing, true, false },
{ "untargeted heat seeker", Weapon::Info_Flags::Untargeted_heat_seeker, true, false },
{ "no radius doubling", Weapon::Info_Flags::No_radius_doubling, true, false },
{ "no subsystem homing", Weapon::Info_Flags::Non_subsys_homing, true, false },
{ "no lifeleft penalty", Weapon::Info_Flags::No_life_lost_if_missed, true, false },
{ "custom seeker str", Weapon::Info_Flags::Custom_seeker_str, true, false },
{ "can be targeted", Weapon::Info_Flags::Can_be_targeted, true, false },
{ "show on radar", Weapon::Info_Flags::Shown_on_radar, true, false },
{ "show friendly on radar", Weapon::Info_Flags::Show_friendly, true, false },
{ "chain external model fps", Weapon::Info_Flags::External_weapon_fp, true, false },
{ "external model launcher", Weapon::Info_Flags::External_weapon_lnch, true, false },
{ "takes blast damage", Weapon::Info_Flags::Takes_blast_damage, true, false },
{ "takes shockwave damage", Weapon::Info_Flags::Takes_shockwave_damage, true, false },
{ "hide from radar", Weapon::Info_Flags::Dont_show_on_radar, true, false },
{ "render flak", Weapon::Info_Flags::Render_flak, true, false },
{ "ciws", Weapon::Info_Flags::Ciws, true, false },
{ "anti-subsystem beam", Weapon::Info_Flags::Antisubsysbeam, true, false },
{ "no primary linking", Weapon::Info_Flags::Nolink, true, false },
{ "same emp time for capships", Weapon::Info_Flags::Use_emp_time_for_capship_turrets, true, false },
{ "no primary linked penalty", Weapon::Info_Flags::No_linked_penalty, true, false },
{ "no homing speed ramp", Weapon::Info_Flags::No_homing_speed_ramp, true, false },
{ "pulls aspect seekers", Weapon::Info_Flags::Cmeasure_aspect_home_on, true, false },
{ "turret interceptable", Weapon::Info_Flags::Turret_Interceptable, true, false },
{ "fighter interceptable", Weapon::Info_Flags::Fighter_Interceptable, true, false },
{ "aoe electronics", Weapon::Info_Flags::Aoe_Electronics, true, false },
{ "apply recoil", Weapon::Info_Flags::Apply_Recoil, true, false },
{ "don't spawn if shot", Weapon::Info_Flags::Dont_spawn_if_shot, true, false },
{ "die on lost lock", Weapon::Info_Flags::Die_on_lost_lock, true, false },
{ "no impact spew", Weapon::Info_Flags::No_impact_spew, true, false },
{ "require exact los", Weapon::Info_Flags::Require_exact_los, true, false },
{ "multilock target dead subsys", Weapon::Info_Flags::Multilock_target_dead_subsys, true, false },
};
const int num_ai_tgt_weapon_info_flags = sizeof(ai_tgt_weapon_flags) / sizeof(flag_def_list_new<Weapon::Info_Flags>);
SCP_vector <ai_target_priority> Ai_tp_list;
// Constant for flag, Name of flag, In flags or flags2
// When adding new flags remember to bump MAX_SHIP_FLAG_NAMES in ship.h
ship_flag_name Ship_flag_names[] = {
{ Ship_Flags::Vaporize, "vaporize" },
{ Ship_Flags::Warp_broken, "break-warp" },
{ Ship_Flags::Warp_never, "never-warp" },
{ Ship_Flags::Scannable, "scannable" },
{ Ship_Flags::Cargo_revealed, "cargo-known" },
{ Ship_Flags::Hidden_from_sensors, "hidden-from-sensors" },
{ Ship_Flags::Stealth, "stealth" },
{ Ship_Flags::Friendly_stealth_invis, "friendly-stealth-invisible" },
{ Ship_Flags::Hide_ship_name, "hide-ship-name" },
{ Ship_Flags::Primitive_sensors, "primitive-sensors" },
{ Ship_Flags::Afterburner_locked, "afterburners-locked" },
{ Ship_Flags::Primaries_locked, "primaries-locked" },
{ Ship_Flags::Secondaries_locked, "secondaries-locked" },
{ Ship_Flags::No_subspace_drive, "no-subspace-drive" },
{ Ship_Flags::Dont_collide_invis, "don't-collide-invisible" },
{ Ship_Flags::No_ets, "no-ets" },
{ Ship_Flags::Toggle_subsystem_scanning, "toggle-subsystem-scanning" },
{ Ship_Flags::No_secondary_lockon, "no-secondary-lock-on" },
{ Ship_Flags::No_disabled_self_destruct, "no-disabled-self-destruct" },
{ Ship_Flags::Hide_mission_log, "hide-in-mission-log" },
{ Ship_Flags::No_passive_lightning, "no-ship-passive-lightning" },
};
static int Laser_energy_out_snd_timer; // timer so we play out of laser sound effect periodically
static int Missile_out_snd_timer; // timer so we play out of laser sound effect periodically
SCP_vector<ship_counts> Ship_type_counts;
SCP_vector<wing_formation> Wing_formations;
const std::shared_ptr<scripting::Hook> OnCountermeasureFireHook = scripting::Hook::Factory("On Countermeasure Fire",
"Called when a ship fires a countermeasure.",
{
{"Ship", "ship", "The ship that has fired the countermeasure."},
{"CountermeasuresLeft", "number", "The number of countermeasures left on the ship after firing the current countermeasure."},
{"Countermeasure", "weapon", "The countermeasure object that was just fired."},
});
// I don't want to do an AI cargo check every frame, so I made a global timer to limit check to
// every SHIP_CARGO_CHECK_INTERVAL ms. Didn't want to make a timer in each ship struct. Ensure
// inited to 1 at mission start.
static int Ship_cargo_check_timer;
static int Thrust_anim_inited = 0;
//Currently unused
//static int ship_get_subobj_model_num(ship_info* sip, char* subobj_name);
SCP_vector<ship_effect> Ship_effects;
int ship_render_mode = MODEL_RENDER_ALL;
/**
* Set the ship_obj struct fields to default values
*/
static void ship_obj_list_reset_slot(int index)
{
Ship_objs[index].flags = 0;
Ship_objs[index].next = NULL;
Ship_objs[index].prev = (ship_obj*)-1;
}
/**
* If the given ship is in my squadron wings
*/
static int ship_in_my_squadron(ship *shipp)
{
int i;
for (i=0; i<MAX_STARTING_WINGS; i++)
{
if (shipp->wingnum == Starting_wings[i])
return 1;
}
for (i=0; i<MAX_TVT_WINGS; i++)
{
if (shipp->wingnum == TVT_wings[i])
return 1;
}
// not in
return 0;
}
/**
* Initialise Ship_obj_list
*/
static void ship_obj_list_init()
{
int i;
list_init(&Ship_obj_list);
for ( i = 0; i < MAX_SHIP_OBJS; i++ ) {
ship_obj_list_reset_slot(i);
}
}
/**
* Function to add a node to the Ship_obj_list. Only
* called from ::ship_create()
*/
static int ship_obj_list_add(int objnum)
{
int i;
for ( i = 0; i < MAX_SHIP_OBJS; i++ ) {
if ( !(Ship_objs[i].flags & SHIP_OBJ_USED) )
break;
}
if ( i == MAX_SHIP_OBJS ) {
Error(LOCATION, "Fatal Error: Ran out of ship object nodes\n");
return -1;
}
Ship_objs[i].flags = 0;
Ship_objs[i].objnum = objnum;
list_append(&Ship_obj_list, &Ship_objs[i]);
Ship_objs[i].flags |= SHIP_OBJ_USED;
return i;
}
/**
* Function to remove a node from the Ship_obj_list. Only
* called from ::ship_delete()
*/
static void ship_obj_list_remove(int index)
{
Assert(index >= 0 && index < MAX_SHIP_OBJS);
list_remove( Ship_obj_list, &Ship_objs[index]);
ship_obj_list_reset_slot(index);
}
ship_obj *get_ship_obj_ptr_from_index(int index)
{
Assert(index >= 0 && index < MAX_SHIP_OBJS);
return &Ship_objs[index];
}
/**
* Return number of ships in the game.
*/
int ship_get_num_ships()
{
int count;
ship_obj *so;
count = 0;
for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) )
count++;
return count;
}
static void engine_wash_info_init(engine_wash_info *ewi)
{
ewi->name[0] = '\0';
ewi->angle = PI / 10.0f;
ewi->radius_mult = 1.0f;
ewi->length = 500.0f;
ewi->intensity = 1.0f;
}
/**
* Parse an engine wash info record
*/
static void parse_engine_wash(bool replace)
{
engine_wash_info ewt;
engine_wash_info_init(&ewt);
engine_wash_info *ewp;
bool create_if_not_found = true;
// name of engine wash info
required_string("$Name:");
stuff_string(ewt.name, F_NAME, NAME_LENGTH);
if(optional_string("+nocreate")) {
if(!replace) {
Warning(LOCATION, "+nocreate flag used for engine wash in non-modular table");
}
create_if_not_found = false;
}
//Does this engine wash exist already?
//If so, load this new info into it
//Otherwise, increment Num_engine_wash_types
ewp = get_engine_wash_pointer(ewt.name);
if(ewp != NULL)
{
if(replace)
{
nprintf(("Warning", "More than one version of engine wash %s exists; using newer version.", ewt.name));
}
else
{
error_display(1, "Error: Engine wash %s already exists. All engine wash names must be unique.", ewt.name);
}
}
else
{
//Don't create engine wash 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")) {
error_display(1, "Missing [#End] or [$Name] after engine wash %s", ewt.name);
}
return;
}
Engine_wash_info.push_back(ewt);
ewp = &Engine_wash_info[Num_engine_wash_types++];
}
// half angle of cone of wash from thruster
if(optional_string("$Angle:"))
{
stuff_float(&ewp->angle);
ewp->angle *= (PI / 180.0f);
}
// radius multiplier for hemisphere around thruster pt
if(optional_string("$Radius Mult:")) {
stuff_float(&ewp->radius_mult);
}
// length of cone
if(optional_string("$Length:")) {
stuff_float(&ewp->length);
}
// intensity inside hemisphere (or at 0 distance from frustated cone)
if(optional_string("$Intensity:")) {
stuff_float(&ewp->intensity);
}
}
static void parse_wing_formation(bool replace)
{
char name[NAME_LENGTH];
SCP_vector<vec3d> position_list;
required_string("$Name:");
stuff_string(name, F_NAME, NAME_LENGTH);
stuff_vec3d_list(position_list);
if (position_list.size() != MAX_SHIPS_PER_WING - 1)
{
error_display(0, "Wing formation %s did not have " SIZE_T_ARG " positions. Ignoring.", name, (size_t)(MAX_SHIPS_PER_WING - 1));
return;
}
if (!stricmp(name, "Retail") || !stricmp(name, "Default"))
{
error_display(0, "A wing formation cannot be named \"Retail\" or \"Default\". Overriding the default formation is not currently supported.");
return;
}
int idx = wing_formation_lookup(name);
if (idx < 0)
{
idx = (int)Wing_formations.size();
Wing_formations.emplace_back();
}
else if (!replace)
error_display(0, "Formation %s already exists. All formation names must be unique.", name);
strcpy_s(Wing_formations[idx].name, name);
std::copy_n(position_list.begin(), MAX_SHIPS_PER_WING - 1, Wing_formations[idx].positions.begin());
}
static const char *Lightning_types[] = {
"None",
"Default",
};
static int Num_lightning_types = sizeof(Lightning_types)/sizeof(char*);
static int lightningtype_match(char *p)
{
int i;
for(i = 0; i < Num_lightning_types; i++)
{
if(!stricmp(Lightning_types[i], p))
return i;
}
return -1;
}
// Kazan -- Volition had this set to 1500, Set it to 4K for WC Saga
#define SHIP_MULTITEXT_LENGTH 4096
#define DEFAULT_DELTA_BANK_CONST 0.5f
const float DEFAULT_ASK_HELP_SHIELD_PERCENT = 0.1f; // percent shields at which ship will ask for help
const float DEFAULT_ASK_HELP_HULL_PERCENT = 0.3f; // percent hull at which ship will ask for help
const float AWACS_HELP_HULL_HI = 0.75f; // percent hull at which ship will ask for help
const float AWACS_HELP_HULL_LOW = 0.25f; // percent hull at which ship will ask for help
#define CHECK_THEN_COPY(attribute) \
do {\
if (other.attribute != NULL)\
attribute = vm_strdup( other.attribute );\
} while(false)
void ship_info::clone(const ship_info& other)
{
strcpy_s(name, other.name);
strcpy_s(display_name, other.display_name);
strcpy_s(short_name, other.short_name);
species = other.species;
class_type = other.class_type;
CHECK_THEN_COPY(type_str);
CHECK_THEN_COPY(maneuverability_str);
CHECK_THEN_COPY(armor_str);
CHECK_THEN_COPY(manufacturer_str);
CHECK_THEN_COPY(desc);
CHECK_THEN_COPY(tech_desc);
strcpy_s(tech_title, other.tech_title);
CHECK_THEN_COPY(ship_length);
CHECK_THEN_COPY(gun_mounts);
CHECK_THEN_COPY(missile_banks);
strcpy_s(cockpit_pof_file, other.cockpit_pof_file);
cockpit_offset = other.cockpit_offset;
strcpy_s(pof_file, other.pof_file);
strcpy_s(pof_file_hud, other.pof_file_hud);
strcpy_s(pof_file_tech, other.pof_file_tech);
num_detail_levels = other.num_detail_levels;
memcpy(detail_distance, other.detail_distance, sizeof(int) * MAX_SHIP_DETAIL_LEVELS);
collision_lod = other.collision_lod;
// I'm not sure if these three are A) a good idea or B) relevant at all. -MageKing17
cockpit_model_num = other.cockpit_model_num;
model_num = other.model_num;
model_num_hud = other.model_num_hud;
hud_target_lod = other.hud_target_lod;
density = other.density;
damp = other.damp;
rotdamp = other.rotdamp;
delta_bank_const = other.delta_bank_const;
max_vel = other.max_vel;
min_vel = other.min_vel;
max_rotvel = other.max_rotvel;
rotation_time = other.rotation_time;
srotation_time = other.srotation_time;
max_rear_vel = other.max_rear_vel;
forward_accel = other.forward_accel;
forward_decel = other.forward_decel;
slide_accel = other.slide_accel;
slide_decel = other.slide_decel;
warpin_params_index = other.warpin_params_index;
warpout_params_index = other.warpout_params_index;
flags = other.flags;
ai_class = other.ai_class;
max_speed = other.max_speed;
min_speed = other.min_speed;
max_accel = other.max_accel;
collision_damage_type_idx = other.collision_damage_type_idx;
collision_physics = other.collision_physics;
memcpy(&shockwave, &other.shockwave, sizeof(shockwave_create_info));
explosion_propagates = other.explosion_propagates;
explosion_splits_ship = other.explosion_splits_ship;
big_exp_visual_rad = other.big_exp_visual_rad;
prop_exp_rad_mult = other.prop_exp_rad_mult;
death_roll_r_mult = other.death_roll_r_mult;
death_fx_r_mult = other.death_fx_r_mult;
death_roll_time_mult = other.death_roll_time_mult;
death_roll_rotation_mult = other.death_roll_rotation_mult;
death_roll_xrotation_cap = other.death_roll_xrotation_cap;
death_roll_yrotation_cap = other.death_roll_yrotation_cap;
death_roll_zrotation_cap = other.death_roll_zrotation_cap;
death_roll_base_time = other.death_roll_base_time;
death_fx_count = other.death_fx_count;
shockwave_count = other.shockwave_count;
explosion_bitmap_anims = other.explosion_bitmap_anims;
vaporize_chance = other.vaporize_chance;
impact_spew = other.impact_spew;
damage_spew = other.damage_spew;
split_particles = other.split_particles;
knossos_end_particles = other.knossos_end_particles;
regular_end_particles = other.regular_end_particles;
death_effect = other.death_effect;
debris_min_lifetime = other.debris_min_lifetime;
debris_max_lifetime = other.debris_max_lifetime;
debris_min_speed = other.debris_min_speed;
debris_max_speed = other.debris_max_speed;
debris_min_rotspeed = other.debris_min_rotspeed;
debris_max_rotspeed = other.debris_max_rotspeed;
debris_damage_type_idx = other.debris_damage_type_idx;
debris_min_hitpoints = other.debris_min_hitpoints;
debris_max_hitpoints = other.debris_max_hitpoints;
debris_damage_mult = other.debris_damage_mult;
debris_arc_percent = other.debris_arc_percent;
debris_ambient_sound = other.debris_ambient_sound;
debris_collision_sound_light = other.debris_collision_sound_light;
debris_collision_sound_heavy = other.debris_collision_sound_heavy;
debris_explosion_sound = other.debris_explosion_sound;
strcpy_s(generic_debris_pof_file, other.generic_debris_pof_file);
generic_debris_model_num = other.generic_debris_model_num;
generic_debris_num_submodels = other.generic_debris_num_submodels;
generic_debris_spew_num = other.generic_debris_spew_num;
if ( other.n_subsystems > 0 ) {
if( n_subsystems < 1 ) {
subsystems = new model_subsystem[other.n_subsystems];
} else {
delete[] subsystems;
subsystems = new model_subsystem[other.n_subsystems];
}
Assert(subsystems != nullptr);
for ( int i = 0; i < other.n_subsystems; i++ ) {
subsystems[i] = other.subsystems[i];
}
}
n_subsystems = other.n_subsystems;
animations = other.animations;
power_output = other.power_output;
max_overclocked_speed = other.max_overclocked_speed;
max_weapon_reserve = other.max_weapon_reserve;
max_shield_regen_per_second = other.max_shield_regen_per_second;
shield_regen_hit_delay = other.shield_regen_hit_delay;
max_weapon_regen_per_second = other.max_weapon_regen_per_second;
shield_weap_amount = other.shield_weap_amount;
shield_weap_efficiency = other.shield_weap_efficiency;
shield_weap_speed = other.shield_weap_speed;
weap_shield_amount = other.weap_shield_amount;
weap_shield_efficiency = other.weap_shield_efficiency;
weap_shield_speed = other.weap_shield_speed;
afterburner_max_vel = other.afterburner_max_vel;
afterburner_forward_accel = other.afterburner_forward_accel;
afterburner_fuel_capacity = other.afterburner_fuel_capacity;
afterburner_burn_rate = other.afterburner_burn_rate;
afterburner_recover_rate = other.afterburner_recover_rate;
afterburner_max_reverse_vel = other.afterburner_max_reverse_vel;
afterburner_reverse_accel = other.afterburner_reverse_accel;
afterburner_min_start_fuel = other.afterburner_min_start_fuel;
afterburner_min_fuel_to_burn = other.afterburner_min_fuel_to_burn;
afterburner_cooldown_time = other.afterburner_cooldown_time;
cmeasure_type = other.cmeasure_type;
cmeasure_max = other.cmeasure_max;
num_primary_banks = other.num_primary_banks;
memcpy(primary_bank_weapons, other.primary_bank_weapons, sizeof(int) * MAX_SHIP_PRIMARY_BANKS);
memcpy(primary_bank_ammo_capacity, other.primary_bank_ammo_capacity, sizeof(int) * MAX_SHIP_PRIMARY_BANKS);
num_secondary_banks = other.num_secondary_banks;
memcpy(secondary_bank_weapons, other.secondary_bank_weapons, sizeof(int) * MAX_SHIP_SECONDARY_BANKS);
memcpy(secondary_bank_ammo_capacity, other.secondary_bank_ammo_capacity, sizeof(int) * MAX_SHIP_SECONDARY_BANKS);
memcpy(draw_primary_models, other.draw_primary_models, sizeof(bool) * MAX_SHIP_PRIMARY_BANKS);
memcpy(draw_secondary_models, other.draw_secondary_models, sizeof(bool) * MAX_SHIP_SECONDARY_BANKS);
weapon_model_draw_distance = other.weapon_model_draw_distance;
max_hull_strength = other.max_hull_strength;
ship_recoil_modifier = other.ship_recoil_modifier;
max_shield_strength = other.max_shield_strength;
max_shield_recharge = other.max_shield_recharge;
auto_shield_spread = other.auto_shield_spread;
auto_shield_spread_bypass = other.auto_shield_spread_bypass;
auto_shield_spread_from_lod = other.auto_shield_spread_from_lod;
auto_shield_spread_min_span = other.auto_shield_spread_min_span;
// ...Hmm. A memcpy() seems slightly overkill here, but I've settled into the pattern of "array gets memcpy'd", so... -MageKing17
memcpy(shield_point_augment_ctrls, other.shield_point_augment_ctrls, sizeof(int) * 4);
hull_repair_rate = other.hull_repair_rate;
subsys_repair_rate = other.subsys_repair_rate;
sup_hull_repair_rate = other.sup_hull_repair_rate;
sup_shield_repair_rate = other.sup_shield_repair_rate;
sup_subsys_repair_rate = other.sup_subsys_repair_rate;
closeup_pos = other.closeup_pos;
closeup_zoom = other.closeup_zoom;
closeup_pos_targetbox = other.closeup_pos_targetbox;
closeup_zoom_targetbox = other.closeup_zoom_targetbox;
chase_view_offset = other.chase_view_offset;
chase_view_rigidity = other.chase_view_rigidity;
allowed_weapons = other.allowed_weapons;
restricted_loadout_flag = other.restricted_loadout_flag;
allowed_bank_restricted_weapons = other.allowed_bank_restricted_weapons;
shield_icon_index = other.shield_icon_index;
strcpy_s(icon_filename, other.icon_filename);
model_icon_angles = other.model_icon_angles;
strcpy_s(anim_filename, other.anim_filename);
strcpy_s(overhead_filename, other.overhead_filename);
selection_effect = other.selection_effect;
wingmen_status_dot_override = other.wingmen_status_dot_override;
bii_index_ship = other.bii_index_ship;
bii_index_ship_with_cargo = other.bii_index_ship_with_cargo;
bii_index_wing = other.bii_index_wing;
bii_index_wing_with_cargo = other.bii_index_wing_with_cargo;
score = other.score;
scan_time = other.scan_time;
scan_range_normal = other.scan_range_normal;
scan_range_capital = other.scan_range_capital;
ask_help_shield_percent = other.ask_help_shield_percent;
ask_help_hull_percent = other.ask_help_hull_percent;
memcpy(ct_info, other.ct_info, sizeof(trail_info) * MAX_SHIP_CONTRAILS);
ct_count = other.ct_count;
memcpy(shield_color, other.shield_color, sizeof(ubyte) * 3);
uses_team_colors = other.uses_team_colors;
default_team_name = other.default_team_name;
afterburner_trail = other.afterburner_trail;
afterburner_trail_tex_stretch = other.afterburner_trail_tex_stretch;
afterburner_trail_width_factor = other.afterburner_trail_width_factor;
afterburner_trail_alpha_factor = other.afterburner_trail_alpha_factor;
afterburner_trail_alpha_end_factor = other.afterburner_trail_alpha_end_factor;
afterburner_trail_alpha_decay_exponent = other.afterburner_trail_alpha_decay_exponent;
afterburner_trail_life = other.afterburner_trail_life;
afterburner_trail_faded_out_sections = other.afterburner_trail_faded_out_sections;
afterburner_trail_spread = other.afterburner_trail_spread;
normal_thruster_particles = other.normal_thruster_particles;
afterburner_thruster_particles = other.afterburner_thruster_particles;
memcpy(&thruster_flame_info, &other.thruster_flame_info, sizeof(thrust_pair));
memcpy(&thruster_glow_info, &other.thruster_glow_info, sizeof(thrust_pair));
memcpy(&thruster_secondary_glow_info, &other.thruster_secondary_glow_info, sizeof(thrust_pair_bitmap));
memcpy(&thruster_tertiary_glow_info, &other.thruster_tertiary_glow_info, sizeof(thrust_pair_bitmap));
memcpy(&thruster_distortion_info, &other.thruster_distortion_info, sizeof(thrust_pair_bitmap));
thruster01_glow_rad_factor = other.thruster01_glow_rad_factor;
thruster02_glow_rad_factor = other.thruster02_glow_rad_factor;
thruster03_glow_rad_factor = other.thruster03_glow_rad_factor;
thruster02_glow_len_factor = other.thruster02_glow_len_factor;
thruster_dist_rad_factor = other.thruster_dist_rad_factor;
thruster_dist_len_factor = other.thruster_dist_len_factor;
thruster_glow_noise_mult = other.thruster_glow_noise_mult;
draw_distortion = other.draw_distortion;
splodeing_texture = other.splodeing_texture;
strcpy_s(splodeing_texture_name, other.splodeing_texture_name);
replacement_textures = other.replacement_textures;
armor_type_idx = other.armor_type_idx;
shield_armor_type_idx = other.shield_armor_type_idx;
can_glide = other.can_glide;
glide_cap = other.glide_cap;
glide_dynamic_cap = other.glide_dynamic_cap;
glide_accel_mult = other.glide_accel_mult;
use_newtonian_damp = other.use_newtonian_damp;
newtonian_damp_override = other.newtonian_damp_override;
autoaim_fov = other.autoaim_fov;
autoaim_lock_snd = other.autoaim_lock_snd;
autoaim_lost_snd = other.autoaim_lost_snd;
topdown_offset_def = other.topdown_offset_def;
topdown_offset = other.topdown_offset;
engine_snd = other.engine_snd;
min_engine_vol = other.min_engine_vol;
glide_start_snd = other.glide_start_snd;
glide_end_snd = other.glide_end_snd;
flyby_snd = other.flyby_snd;
ship_sounds = other.ship_sounds;
num_maneuvering = other.num_maneuvering;
for (int i = 0; i < MAX_MAN_THRUSTERS; ++i)
{
maneuvering[i] = other.maneuvering[i];
}
radar_image_2d_idx = other.radar_image_2d_idx;
radar_color_image_2d_idx = other.radar_color_image_2d_idx;
radar_image_size = other.radar_image_size;
radar_projection_size_mult = other.radar_projection_size_mult;
ship_iff_info = other.ship_iff_info;
aiming_flags = other.aiming_flags;
minimum_convergence_distance = other.minimum_convergence_distance;
convergence_distance = other.convergence_distance;
convergence_offset = other.convergence_offset;
emp_resistance_mod = other.emp_resistance_mod;
piercing_damage_draw_limit = other.piercing_damage_draw_limit;
damage_lightning_type = other.damage_lightning_type;
shield_impact_explosion_anim = other.shield_impact_explosion_anim;
// We can't copy the HUD gauge vector here since we can't construct a copy of every HUD gauge since the type
// information is not available anymore when we are at this point. Since this function is only needed before HUD
// gauge parsing it should be save to assume that the other HUD gauge vector is empty
Assertion(other.hud_gauges.empty(), "Ship_info cloning is only possible if there are no HUD gauges in the ship class.");
hud_gauges.clear();
hud_enabled = other.hud_enabled;
hud_retail = other.hud_retail;
displays = other.displays;
pathMetadata = other.pathMetadata;
ship_passive_arcs = other.ship_passive_arcs;
glowpoint_bank_override_map = other.glowpoint_bank_override_map;
}
void ship_info::move(ship_info&& other)
{
std::swap(name, other.name);
std::swap(display_name, other.display_name);
std::swap(short_name, other.short_name);
species = other.species;
class_type = other.class_type;
std::swap(type_str, other.type_str);
std::swap(maneuverability_str, other.maneuverability_str);
std::swap(armor_str, other.armor_str);
std::swap(manufacturer_str, other.manufacturer_str);
std::swap(desc, other.desc);
std::swap(tech_desc, other.tech_desc);
std::swap(tech_title, other.tech_title);
std::swap(ship_length, other.ship_length);
std::swap(gun_mounts, other.gun_mounts);
std::swap(missile_banks, other.missile_banks);
std::swap(cockpit_pof_file, other.cockpit_pof_file);
std::swap(cockpit_offset, other.cockpit_offset);
std::swap(pof_file, other.pof_file);
std::swap(pof_file_hud, other.pof_file_hud);
std::swap(pof_file_tech, other.pof_file_tech);
num_detail_levels = other.num_detail_levels;
std::swap(detail_distance, other.detail_distance);
collision_lod = other.collision_lod;
cockpit_model_num = other.cockpit_model_num;
model_num = other.model_num;
model_num_hud = other.model_num_hud;
hud_target_lod = other.hud_target_lod;
density = other.density;
damp = other.damp;
rotdamp = other.rotdamp;
delta_bank_const = other.delta_bank_const;
std::swap(max_vel, other.max_vel);
std::swap(min_vel, other.min_vel);
std::swap(max_rotvel, other.max_rotvel);
std::swap(rotation_time, other.rotation_time);
srotation_time = other.srotation_time;
max_rear_vel = other.max_rear_vel;
forward_accel = other.forward_accel;
forward_decel = other.forward_decel;
slide_accel = other.slide_accel;
slide_decel = other.slide_decel;
warpin_params_index = other.warpin_params_index;
warpout_params_index = other.warpout_params_index;
flags = other.flags;
ai_class = other.ai_class;
max_speed = other.max_speed;
min_speed = other.min_speed;
max_accel = other.max_accel;
collision_damage_type_idx = other.collision_damage_type_idx;
std::swap(collision_physics, other.collision_physics);
std::swap(shockwave, other.shockwave);
explosion_propagates = other.explosion_propagates;
explosion_splits_ship = other.explosion_splits_ship;
big_exp_visual_rad = other.big_exp_visual_rad;
prop_exp_rad_mult = other.prop_exp_rad_mult;
death_roll_r_mult = other.death_roll_r_mult;
death_fx_r_mult = other.death_fx_r_mult;
death_roll_time_mult = other.death_roll_time_mult;
death_roll_rotation_mult = other.death_roll_rotation_mult;
death_roll_xrotation_cap = other.death_roll_xrotation_cap;
death_roll_yrotation_cap = other.death_roll_yrotation_cap;
death_roll_zrotation_cap = other.death_roll_zrotation_cap;
death_roll_base_time = other.death_roll_base_time;
death_fx_count = other.death_fx_count;
shockwave_count = other.shockwave_count;
std::swap(explosion_bitmap_anims, other.explosion_bitmap_anims);
vaporize_chance = other.vaporize_chance;
std::swap(impact_spew, other.impact_spew);
std::swap(damage_spew, other.damage_spew);
std::swap(split_particles, other.split_particles);
std::swap(knossos_end_particles, other.knossos_end_particles);
std::swap(regular_end_particles, other.regular_end_particles);
std::swap(death_effect, other.death_effect);
debris_min_lifetime = other.debris_min_lifetime;
debris_max_lifetime = other.debris_max_lifetime;
debris_min_speed = other.debris_min_speed;
debris_max_speed = other.debris_max_speed;
debris_min_rotspeed = other.debris_min_rotspeed;
debris_max_rotspeed = other.debris_max_rotspeed;
debris_damage_type_idx = other.debris_damage_type_idx;
debris_min_hitpoints = other.debris_min_hitpoints;
debris_max_hitpoints = other.debris_max_hitpoints;
debris_damage_mult = other.debris_damage_mult;
debris_arc_percent = other.debris_arc_percent;
debris_ambient_sound = other.debris_ambient_sound;
debris_collision_sound_light = other.debris_collision_sound_light;
debris_collision_sound_heavy = other.debris_collision_sound_heavy;
debris_explosion_sound = other.debris_explosion_sound;
strcpy_s(generic_debris_pof_file, other.generic_debris_pof_file);
generic_debris_model_num = other.generic_debris_model_num;
generic_debris_num_submodels = other.generic_debris_num_submodels;
generic_debris_spew_num = other.generic_debris_spew_num;
std::swap(subsystems, other.subsystems);
std::swap(n_subsystems, other.n_subsystems);
power_output = other.power_output;
max_overclocked_speed = other.max_overclocked_speed;
max_weapon_reserve = other.max_weapon_reserve;
max_shield_regen_per_second = other.max_shield_regen_per_second;
shield_regen_hit_delay = other.shield_regen_hit_delay;
max_weapon_regen_per_second = other.max_weapon_regen_per_second;
shield_weap_amount = other.shield_weap_amount;
shield_weap_efficiency = other.shield_weap_efficiency;
shield_weap_speed = other.shield_weap_speed;
weap_shield_amount = other.weap_shield_amount;
weap_shield_efficiency = other.weap_shield_efficiency;
weap_shield_speed = other.weap_shield_speed;
std::swap(afterburner_max_vel, other.afterburner_max_vel);
afterburner_forward_accel = other.afterburner_forward_accel;
afterburner_fuel_capacity = other.afterburner_fuel_capacity;
afterburner_burn_rate = other.afterburner_burn_rate;
afterburner_recover_rate = other.afterburner_recover_rate;
afterburner_max_reverse_vel = other.afterburner_max_reverse_vel;
afterburner_reverse_accel = other.afterburner_reverse_accel;
afterburner_min_start_fuel = other.afterburner_min_start_fuel;
afterburner_min_fuel_to_burn = other.afterburner_min_fuel_to_burn;
afterburner_cooldown_time = other.afterburner_cooldown_time;
cmeasure_type = other.cmeasure_type;
cmeasure_max = other.cmeasure_max;
num_primary_banks = other.num_primary_banks;
std::swap(primary_bank_weapons, other.primary_bank_weapons);
std::swap(primary_bank_ammo_capacity, other.primary_bank_ammo_capacity);
num_secondary_banks = other.num_secondary_banks;
std::swap(secondary_bank_weapons, other.secondary_bank_weapons);
std::swap(secondary_bank_ammo_capacity, other.secondary_bank_ammo_capacity);
std::swap(draw_primary_models, other.draw_primary_models);
std::swap(draw_secondary_models, other.draw_secondary_models);
weapon_model_draw_distance = other.weapon_model_draw_distance;
max_hull_strength = other.max_hull_strength;
ship_recoil_modifier = other.ship_recoil_modifier;
max_shield_strength = other.max_shield_strength;
max_shield_recharge = other.max_shield_recharge;
auto_shield_spread = other.auto_shield_spread;
auto_shield_spread_bypass = other.auto_shield_spread_bypass;
auto_shield_spread_from_lod = other.auto_shield_spread_from_lod;
auto_shield_spread_min_span = other.auto_shield_spread_min_span;
std::swap(shield_point_augment_ctrls, other.shield_point_augment_ctrls);
hull_repair_rate = other.hull_repair_rate;
subsys_repair_rate = other.subsys_repair_rate;
sup_hull_repair_rate = other.sup_hull_repair_rate;
sup_shield_repair_rate = other.sup_shield_repair_rate;
sup_subsys_repair_rate = other.sup_subsys_repair_rate;
std::swap(closeup_pos, other.closeup_pos);
closeup_zoom = other.closeup_zoom;
std::swap(closeup_pos_targetbox, other.closeup_pos_targetbox);
closeup_zoom_targetbox = other.closeup_zoom_targetbox;
std::swap(chase_view_offset, other.chase_view_offset);
chase_view_rigidity = other.chase_view_rigidity;
std::swap(allowed_weapons, other.allowed_weapons);
std::swap(restricted_loadout_flag, other.restricted_loadout_flag);
std::swap(allowed_bank_restricted_weapons, other.allowed_bank_restricted_weapons);
shield_icon_index = other.shield_icon_index;
std::swap(icon_filename, other.icon_filename);
model_icon_angles = other.model_icon_angles;
std::swap(anim_filename, other.anim_filename);
std::swap(overhead_filename, other.overhead_filename);
selection_effect = other.selection_effect;
wingmen_status_dot_override = other.wingmen_status_dot_override;
bii_index_ship = other.bii_index_ship;
bii_index_ship_with_cargo = other.bii_index_ship_with_cargo;
bii_index_wing = other.bii_index_wing;
bii_index_wing_with_cargo = other.bii_index_wing_with_cargo;
score = other.score;
scan_time = other.scan_time;
scan_range_normal = other.scan_range_normal;
scan_range_capital = other.scan_range_capital;
ask_help_shield_percent = other.ask_help_shield_percent;
ask_help_hull_percent = other.ask_help_hull_percent;
std::swap(ct_info, other.ct_info);
ct_count = other.ct_count;
std::swap(shield_color, other.shield_color);
uses_team_colors = other.uses_team_colors;
std::swap(default_team_name, other.default_team_name);
std::swap(afterburner_trail, other.afterburner_trail);
afterburner_trail_tex_stretch = other.afterburner_trail_tex_stretch;
afterburner_trail_width_factor = other.afterburner_trail_width_factor;
afterburner_trail_alpha_factor = other.afterburner_trail_alpha_factor;
afterburner_trail_alpha_end_factor = other.afterburner_trail_alpha_end_factor;
afterburner_trail_alpha_decay_exponent = other.afterburner_trail_alpha_decay_exponent;
afterburner_trail_life = other.afterburner_trail_life;
afterburner_trail_faded_out_sections = other.afterburner_trail_faded_out_sections;
afterburner_trail_spread = other.afterburner_trail_spread;
std::swap(normal_thruster_particles, other.normal_thruster_particles);
std::swap(afterburner_thruster_particles, other.afterburner_thruster_particles);
std::swap(thruster_flame_info, other.thruster_flame_info);
std::swap(thruster_glow_info, other.thruster_glow_info);
std::swap(thruster_secondary_glow_info, other.thruster_secondary_glow_info);
std::swap(thruster_tertiary_glow_info, other.thruster_tertiary_glow_info);
std::swap(thruster_distortion_info, other.thruster_distortion_info);
thruster01_glow_rad_factor = other.thruster01_glow_rad_factor;
thruster02_glow_rad_factor = other.thruster02_glow_rad_factor;
thruster03_glow_rad_factor = other.thruster03_glow_rad_factor;
thruster02_glow_len_factor = other.thruster02_glow_len_factor;
thruster_dist_rad_factor = other.thruster_dist_rad_factor;
thruster_dist_len_factor = other.thruster_dist_len_factor;
thruster_glow_noise_mult = other.thruster_glow_noise_mult;
draw_distortion = other.draw_distortion;
splodeing_texture = other.splodeing_texture;
std::swap(splodeing_texture_name, other.splodeing_texture_name);
std::swap(replacement_textures, other.replacement_textures);
armor_type_idx = other.armor_type_idx;
shield_armor_type_idx = other.shield_armor_type_idx;
can_glide = other.can_glide;
glide_cap = other.glide_cap;
glide_dynamic_cap = other.glide_dynamic_cap;
glide_accel_mult = other.glide_accel_mult;
use_newtonian_damp = other.use_newtonian_damp;
newtonian_damp_override = other.newtonian_damp_override;
autoaim_fov = other.autoaim_fov;
autoaim_lock_snd = other.autoaim_lock_snd;
autoaim_lost_snd = other.autoaim_lost_snd;
topdown_offset_def = other.topdown_offset_def;
std::swap(topdown_offset, other.topdown_offset);
engine_snd = other.engine_snd;
min_engine_vol = other.min_engine_vol;
glide_start_snd = other.glide_start_snd;
glide_end_snd = other.glide_end_snd;
flyby_snd = other.flyby_snd;
std::swap(ship_sounds, other.ship_sounds);
num_maneuvering = other.num_maneuvering;
std::swap(maneuvering, other.maneuvering);
radar_image_2d_idx = other.radar_image_2d_idx;
radar_color_image_2d_idx = other.radar_color_image_2d_idx;
radar_image_size = other.radar_image_size;
radar_projection_size_mult = other.radar_projection_size_mult;
ship_iff_info = other.ship_iff_info;
aiming_flags = other.aiming_flags;
minimum_convergence_distance = other.minimum_convergence_distance;
convergence_distance = other.convergence_distance;
std::swap(convergence_offset, other.convergence_offset);
emp_resistance_mod = other.emp_resistance_mod;
piercing_damage_draw_limit = other.piercing_damage_draw_limit;
damage_lightning_type = other.damage_lightning_type;
shield_impact_explosion_anim = other.shield_impact_explosion_anim;
std::swap(hud_gauges, other.hud_gauges);
hud_enabled = other.hud_enabled;
hud_retail = other.hud_retail;
std::swap(displays, other.displays);
std::swap(pathMetadata, other.pathMetadata);
std::swap(ship_passive_arcs, other.ship_passive_arcs);
std::swap(glowpoint_bank_override_map, other.glowpoint_bank_override_map);
animations = std::move(other.animations);
}
#define CHECK_THEN_FREE(attribute) \
do {\
if (attribute != NULL) {\
vm_free(attribute);\
attribute = NULL;\
}\
} while(false)
void ship_info::free_strings()
{
CHECK_THEN_FREE(type_str);
CHECK_THEN_FREE(maneuverability_str);
CHECK_THEN_FREE(armor_str);
CHECK_THEN_FREE(manufacturer_str);
CHECK_THEN_FREE(desc);
CHECK_THEN_FREE(tech_desc);
CHECK_THEN_FREE(ship_length);
CHECK_THEN_FREE(gun_mounts);
CHECK_THEN_FREE(missile_banks);
}
ship_info &ship_info::operator= (ship_info&& other) noexcept
{
if (this != &other) {
move(std::move(other));
}
return *this;
}
ship_info::ship_info(ship_info&& other) noexcept
{
// MageKing17 - Initialize these pointers to NULL because otherwise move() will leave them uninitialized.
type_str = NULL;
maneuverability_str = NULL;
armor_str = NULL;
manufacturer_str = NULL;
desc = NULL;
tech_desc = NULL;
ship_length = NULL;
gun_mounts = NULL;
missile_banks = NULL;
subsystems = NULL;
n_subsystems = 0;
// Then we swap everything (well, everything that matters to the deconstructor).
move(std::move(other));
}
ship_info::ship_info()
{
int i;
name[0] = '\0';
display_name[0] = '\0';
sprintf(short_name, "ShipClass%d", ship_info_size());
species = 0;
class_type = -1;
type_str = maneuverability_str = armor_str = manufacturer_str = NULL;
desc = tech_desc = NULL;
tech_title[0] = 0;
ship_length = NULL;
gun_mounts = NULL;
missile_banks = NULL;
cockpit_pof_file[0] = '\0';
vm_vec_zero(&cockpit_offset);
pof_file[0] = '\0';
pof_file_hud[0] = '\0';
pof_file_tech[0] = '\0';
num_detail_levels = 1;
detail_distance[0] = 0;
collision_lod = -1;
cockpit_model_num = -1;
model_num = -1;
model_num_hud = -1;
hud_target_lod = -1;
density = 1.0f;
damp = 0.0f;
rotdamp = 0.0f;
delta_bank_const = DEFAULT_DELTA_BANK_CONST;
vm_vec_zero(&max_vel);
vm_vec_zero(&min_vel);
vm_vec_zero(&max_rotvel);
vm_vec_zero(&rotation_time);
srotation_time = 0.0f;
max_rear_vel = 0.0f;
forward_accel = 0.0f;
forward_decel = 0.0f;
slide_accel = 0.0f;
slide_decel = 0.0f;
warpin_params_index = -1;
warpout_params_index = -1;
flags.reset();
ai_class = 0;
max_speed = 0.0f;
min_speed = 0.0f;
max_accel = 0.0f;
collision_damage_type_idx = -1;
// Retail default collision physics and default landing parameters
collision_physics = ship_collision_physics();
collision_physics.both_small_bounce = 5.0;
collision_physics.bounce = 5.0;
collision_physics.friction = COLLISION_FRICTION_FACTOR;
collision_physics.rotation_factor = COLLISION_ROTATION_FACTOR;
collision_physics.reorient_mult = 1.0f;
collision_physics.landing_sound_idx = gamesnd_id();
collision_physics.collision_sound_light_idx = gamesnd_id();
collision_physics.collision_sound_heavy_idx = gamesnd_id();
collision_physics.collision_sound_shielded_idx = gamesnd_id();
shockwave_create_info_init(&shockwave);
explosion_propagates = 0;
explosion_splits_ship = false;
big_exp_visual_rad = -1.0f;
prop_exp_rad_mult = 1.0f;
death_roll_r_mult = 1.0f;
death_fx_r_mult = 1.0f;
death_roll_time_mult = 1.0f;
death_roll_rotation_mult = 1.0f;
death_roll_xrotation_cap = 0.75f*DEATHROLL_ROTVEL_CAP;
death_roll_yrotation_cap = 0.75f*DEATHROLL_ROTVEL_CAP;
death_roll_zrotation_cap = 0.75f*DEATHROLL_ROTVEL_CAP;
death_roll_base_time = 3000;
death_fx_count = 6;
shockwave_count = 1;
explosion_bitmap_anims.clear();
vaporize_chance = 0;
// default values from shipfx.cpp
impact_spew.n_high = 30;
impact_spew.n_low = 25;
impact_spew.max_rad = 0.5f;
impact_spew.min_rad = 0.2f;
impact_spew.max_life = 0.55f;
impact_spew.min_life = 0.05f;
impact_spew.max_vel = 12.0f;
impact_spew.min_vel = 2.0f;
impact_spew.variance = 1.0f;
// default values from shipfx.cpp
damage_spew.n_high = 1; // 1 is used here to trigger retail behaviour
damage_spew.n_low = 0;
damage_spew.max_rad = 1.3f;
damage_spew.min_rad = 0.7f;
damage_spew.max_life = 0.0f;
damage_spew.min_life = 0.0f;
damage_spew.max_vel = 12.0f;
damage_spew.min_vel = 3.0f;
damage_spew.variance = 0.0f;
split_particles.n_high = 80;
split_particles.n_low = 40;
split_particles.max_rad = 0.0f;
split_particles.min_rad = 0.0f;
split_particles.max_life = 0.0f;
split_particles.min_life = 0.0f;
split_particles.max_vel = 0.0f;
split_particles.min_vel = 0.0f;
split_particles.variance = 2.0f;
knossos_end_particles.n_high = 30;
knossos_end_particles.n_low = 15;
knossos_end_particles.max_rad = 100.0f;
knossos_end_particles.min_rad = 30.0f;
knossos_end_particles.max_life = 12.0f;
knossos_end_particles.min_life = 2.0f;
knossos_end_particles.max_vel = 350.0f;
knossos_end_particles.min_vel = 50.0f;
knossos_end_particles.variance = 2.0f;
regular_end_particles.n_high = 100;
regular_end_particles.n_low = 50;
regular_end_particles.max_rad = 1.5f;
regular_end_particles.min_rad = 0.1f;
regular_end_particles.max_life = 4.0f;
regular_end_particles.min_life = 0.5f;
regular_end_particles.max_vel = 20.0f;
regular_end_particles.min_vel = 0.0f;
regular_end_particles.variance = 2.0f;
death_effect = particle::ParticleEffectHandle::invalid();
debris_min_lifetime = -1.0f;
debris_max_lifetime = -1.0f;
debris_min_speed = -1.0f;
debris_max_speed = -1.0f;
debris_min_rotspeed = -1.0f;
debris_max_rotspeed = -1.0f;
debris_damage_type_idx = -1;
debris_min_hitpoints = -1.0f;
debris_max_hitpoints = -1.0f;
debris_damage_mult = 1.0f;
debris_arc_percent = 0.5f;
debris_ambient_sound = GameSounds::DEBRIS;
debris_collision_sound_light = gamesnd_id();
debris_collision_sound_heavy = gamesnd_id();
debris_explosion_sound = GameSounds::MISSILE_IMPACT1;
generic_debris_pof_file[0] = '\0';
generic_debris_model_num = -1;
generic_debris_num_submodels = -1;
generic_debris_spew_num = 20;
n_subsystems = 0;
subsystems = NULL;
power_output = 0.0f;
max_overclocked_speed = 0.0f;
max_weapon_reserve = 0.0f;
max_shield_regen_per_second = 0.0f;
shield_regen_hit_delay = 0.0f;
max_weapon_regen_per_second = 0.0f;
shield_weap_amount = 0.0f;
shield_weap_efficiency = 0.0f;
shield_weap_speed = 0.0f;
weap_shield_amount = 0.0f;
weap_shield_efficiency = 0.0f;
weap_shield_speed = 0.0f;
vm_vec_zero(&afterburner_max_vel);
afterburner_forward_accel = 0.0f;
afterburner_fuel_capacity = 0.0f;
afterburner_burn_rate = 0.0f;
afterburner_recover_rate = 0.0f;
afterburner_max_reverse_vel = 0.0f;
afterburner_reverse_accel = 0.0f;
afterburner_min_start_fuel = DEFAULT_MIN_AFTERBURNER_FUEL_TO_ENGAGE;
afterburner_min_fuel_to_burn = 0.0f;
afterburner_cooldown_time = 0.0f;
cmeasure_type = Default_cmeasure_index;
cmeasure_max = 0;
num_primary_banks = 0;
for ( i = 0; i < MAX_SHIP_PRIMARY_BANKS; i++ )
{
primary_bank_weapons[i] = -1;
draw_primary_models[i] = false;
primary_bank_ammo_capacity[i] = 0;
}
num_secondary_banks = 0;
for ( i = 0; i < MAX_SHIP_SECONDARY_BANKS; i++ )
{
secondary_bank_weapons[i] = -1;
draw_secondary_models[i] = false;
secondary_bank_ammo_capacity[i] = 0;
}
weapon_model_draw_distance = 200.0f;
ship_recoil_modifier = 1.0f;
max_hull_strength = 100.0f;
max_shield_strength = 0.0f;
max_shield_recharge = 1.0f;
auto_shield_spread = 0.0f;
auto_shield_spread_bypass = false;
auto_shield_spread_from_lod = -1;
auto_shield_spread_min_span = -1.0f;
for (i = 0; i < 4; i++)
{
shield_point_augment_ctrls[i] = -1;
}
hull_repair_rate = 0.0f;
//-2 represents not set, in which case the default is used for the ship (if it is small)
subsys_repair_rate = -2.0f;
sup_hull_repair_rate = 0.15f;
sup_shield_repair_rate = 0.20f;
sup_subsys_repair_rate = 0.15f; // The retail default is 0.10, but it actually used the hull rate of 0.15
vm_vec_zero(&closeup_pos);
closeup_zoom = 0.5f;
vm_vec_zero(&closeup_pos_targetbox);
closeup_zoom_targetbox = 0.5f;
vm_vec_zero(&chase_view_offset);
chase_view_rigidity = 5.0f;
allowed_weapons.clear();
restricted_loadout_flag.clear();
allowed_bank_restricted_weapons.clear();
shield_icon_index = 255; // stored as ubyte
icon_filename[0] = '\0';
memset(&model_icon_angles, 0, sizeof(angles));
anim_filename[0] = '\0';
overhead_filename[0] = '\0';
selection_effect = Default_ship_select_effect;
wingmen_status_dot_override = -1;
bii_index_ship = -1;
bii_index_ship_with_cargo = -1;
bii_index_wing = -1;
bii_index_wing_with_cargo = -1;
score = 0;
scan_time = 2000;
scan_range_normal = CARGO_REVEAL_MIN_DIST;
scan_range_capital = CAP_CARGO_REVEAL_MIN_DIST;
ask_help_shield_percent = DEFAULT_ASK_HELP_SHIELD_PERCENT;
ask_help_hull_percent = DEFAULT_ASK_HELP_HULL_PERCENT;
memset(&ct_info, 0, sizeof(trail_info) * MAX_SHIP_CONTRAILS);
ct_count = 0;
shield_color[0] = 255;
shield_color[1] = 255;
shield_color[2] = 255;
// Team colors
uses_team_colors = false;
default_team_name = "";
generic_bitmap_init(&afterburner_trail, NULL);
afterburner_trail_tex_stretch = 1.0f;
afterburner_trail_width_factor = 1.0f;
afterburner_trail_alpha_factor = 1.0f;
afterburner_trail_alpha_end_factor = 0.0f;
afterburner_trail_alpha_decay_exponent = 1.0f;
afterburner_trail_life = 5.0f;
afterburner_trail_spread = 0.0f;
afterburner_trail_faded_out_sections = 0;
normal_thruster_particles.clear();
afterburner_thruster_particles.clear();
// Bobboau's thruster stuff
generic_anim_init( &thruster_flame_info.normal );
generic_anim_init( &thruster_flame_info.afterburn );
generic_anim_init( &thruster_glow_info.normal );
generic_anim_init( &thruster_glow_info.afterburn );
generic_bitmap_init( &thruster_secondary_glow_info.normal );
generic_bitmap_init( &thruster_secondary_glow_info.afterburn );
generic_bitmap_init( &thruster_tertiary_glow_info.normal );
generic_bitmap_init( &thruster_tertiary_glow_info.afterburn );
generic_bitmap_init( &thruster_distortion_info.normal );
generic_bitmap_init( &thruster_distortion_info.afterburn );
// Bobboau's thruster stuff
thruster01_glow_rad_factor = 1.0f;
thruster02_glow_rad_factor = 1.0f;
thruster03_glow_rad_factor = 1.0f;
thruster02_glow_len_factor = 1.0f;
thruster_dist_rad_factor = 2.0f;
thruster_dist_len_factor = 2.0f;
thruster_glow_noise_mult = 1.0f;
draw_distortion = true;
splodeing_texture = -1;
strcpy_s(splodeing_texture_name, "boom");
replacement_textures.clear();
armor_type_idx = -1;
shield_armor_type_idx = -1;
can_glide = false;
glide_cap = 0.0f;
glide_dynamic_cap = false;
glide_accel_mult = 0.0f;
use_newtonian_damp = false;
newtonian_damp_override = false;
autoaim_fov = 0.0f;
autoaim_lock_snd = gamesnd_id();
autoaim_lost_snd = gamesnd_id();
topdown_offset_def = false;
vm_vec_zero(&topdown_offset);
engine_snd = gamesnd_id();
min_engine_vol = -1.0f;
glide_start_snd = gamesnd_id();
glide_end_snd = gamesnd_id();
flyby_snd = gamesnd_id();
ship_sounds.clear();
num_maneuvering = 0;
for (i = 0; i < MAX_MAN_THRUSTERS; i++)
{
maneuvering[i].reset();
}
radar_image_2d_idx = -1;
radar_color_image_2d_idx = -1;
radar_image_size = -1;
radar_projection_size_mult = 1.0f;
ship_iff_info.clear();
aiming_flags.reset();
minimum_convergence_distance = 0.0f;
convergence_distance = 100.0f;
vm_vec_zero(&convergence_offset);
emp_resistance_mod = 0.0f;
piercing_damage_draw_limit = 0.10f;
damage_lightning_type = SLT_DEFAULT;
shield_impact_explosion_anim = -1;
hud_gauges.clear();
hud_enabled = false;
hud_retail = false;
displays.clear();
pathMetadata.clear();
glowpoint_bank_override_map.clear();
ship_passive_arcs.clear();
}
ship_info::~ship_info()
{
if ( subsystems != NULL ) {
delete[] subsystems;
subsystems = nullptr;
}
free_strings();
}
const char* ship_info::get_display_name() const
{
if (has_display_name())
return display_name;
else
return name;
}
bool ship_info::has_display_name() const
{
return flags[Ship::Info_Flags::Has_display_name];
}
ubyte allowed_weapon_bank::find_flags(int weapon_info_index) const
{
for (auto &wf : weapon_and_flags)
if (wf.first == weapon_info_index)
return wf.second;
// since we can return 0 here, we can't make the return type 'const ubyte &'
return 0;
}
void allowed_weapon_bank::set_flag(int weapon_info_index, ubyte flag)
{
for (auto &wf : weapon_and_flags)
{
if (wf.first == weapon_info_index)
{
wf.second |= flag;
return;
}
}
// if we couldn't find it, add a new entry with that flag
weapon_and_flags.emplace_back(weapon_info_index, flag);
}
void allowed_weapon_bank::clear_flag(int weapon_info_index, ubyte flag)
{
// clear the flag from all matching weapons, and remove any that end up with a flag of 0
weapon_and_flags.erase(
std::remove_if(weapon_and_flags.begin(), weapon_and_flags.end(), [weapon_info_index, flag](std::pair<int, ubyte> &wf)
{
if (weapon_info_index < 0 || weapon_info_index == wf.first)
{
wf.second &= ~flag;
return (wf.second == 0);
}
else
return false;
}),
weapon_and_flags.end()
);
}
void allowed_weapon_bank::clear_flag(ubyte flag)
{
clear_flag(-1, flag);
}
void allowed_weapon_bank::clear()
{
weapon_and_flags.clear();
}
// read-only!
// (because we don't want to fill up our member vector with a bunch of zeros)
ubyte allowed_weapon_bank::operator[](int index) const
{
return find_flags(index);
}
ubyte allowed_weapon_bank::operator[](size_t index) const
{
return find_flags(static_cast<int>(index));
}
static SCP_vector<SCP_string> Removed_ships;
/**
* Parse the information for a specific ship type.
*/
static void parse_ship(const char *filename, bool replace)
{
char fname[NAME_LENGTH];
ship_info *sip = nullptr;
bool first_time = false;
bool create_if_not_found = true;
bool remove_ship = false;
bool new_name = false;
required_string("$Name:");
stuff_string(fname, F_NAME, NAME_LENGTH);
diag_printf("Ship name -- %s\n", fname);
if (optional_string("+nocreate")) {
if (!replace) {
Warning(LOCATION, "+nocreate flag used for ship in non-modular table");
}
create_if_not_found = false;
}
// check first if this is on the remove blacklist
auto it = std::find(Removed_ships.begin(), Removed_ships.end(), fname);
if (it != Removed_ships.end()) {
remove_ship = true;
}
// we might have a remove tag
if (optional_string("+remove")) {
if (!replace) {
Warning(LOCATION, "+remove flag used for ship in non-modular table");
}
if (!remove_ship) {
Removed_ships.push_back(fname);
remove_ship = true;
}
}
//Remove @ symbol
//these used to denote weapons that would
//only be parsed in demo builds
if ( fname[0] == '@' ) {
backspace(fname);
}
//Check if ship exists already
int ship_id = ship_info_lookup_sub(fname);
// maybe remove it
if (remove_ship)
{
if (ship_id >= 0) {
mprintf(("Removing previously parsed ship '%s'\n", fname));
Ship_info.erase(Ship_info.begin() + ship_id);
}
if (!skip_to_start_of_string_either("$Name:", "#End")) {
error_display(1, "Missing [#End] or [$Name] after ship class %s", fname);
}
return;
}
// an entry for this ship exists
if (ship_id >= 0)
{
sip = &Ship_info[ship_id];
if (!replace)
{
Warning(LOCATION, "Error: Ship name %s already exists in %s. All ship class names must be unique.", fname, filename);
if (!skip_to_start_of_string_either("$Name:", "#End")) {
error_display(1, "Missing [#End] or [$Name] after duplicate ship class %s", fname);
}
return;
}
}
// an entry does not exist
else
{
// Don't create ship 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")) {
error_display(1, "Missing [#End] or [$Name] after ship class %s", fname);
}
return;
}
// Check if there are too many ship classes
if (Ship_info.size() >= MAX_SHIP_CLASSES) {
Error(LOCATION, "Too many ship classes before '%s'; maximum is %d.\n", fname, MAX_SHIP_CLASSES);
}
Ship_info.push_back(ship_info());
sip = &Ship_info.back();
first_time = true;
strcpy_s(sip->name, fname);
new_name = true;
}
// Use a template for this ship.
if( optional_string( "+Use Template:" ) ) {
if ( !create_if_not_found ) {
Warning(LOCATION, "Both '+nocreate' and '+Use Template:' were specified for ship class '%s', ignoring '+Use Template:'\n", fname);
}
else {
char template_name[SHIP_MULTITEXT_LENGTH];
stuff_string(template_name, F_NAME, SHIP_MULTITEXT_LENGTH);
int template_id = ship_template_lookup(template_name);
if ( template_id != -1 ) {
first_time = false;
sip->clone(Ship_templates[template_id]);
strcpy_s(sip->name, fname);
new_name = true;
}
else {
Warning(LOCATION, "Unable to find ship template '%s' requested by ship class '%s', ignoring template request...", template_name, fname);
}
}
}
if (new_name) {
if (!sip->flags[Ship::Info_Flags::Has_display_name]) {
// if this name has a hash, create a default display name
if (get_pointer_to_first_hash_symbol(sip->name)) {
strcpy_s(sip->display_name, sip->name);
end_string_at_first_hash_symbol(sip->display_name);
sip->flags.set(Ship::Info_Flags::Has_display_name);
}
}
sip->animations.changeShipName(sip->name);
}
parse_ship_values(sip, false, first_time, replace);
}
/**
* Parse the information for a specific ship type template.
*/
static void parse_ship_template()
{
char buf[SHIP_MULTITEXT_LENGTH];
ship_info *sip;
bool first_time = true;
required_string("$Template:");
stuff_string(buf, F_NAME, SHIP_MULTITEXT_LENGTH);
if( optional_string("+nocreate") ) {
Warning(LOCATION, "+nocreate flag used on ship template. Ship templates can not be modified. Ignoring +nocreate.");
}
diag_printf ("Ship template name -- %s\n", buf);
//Check if the template exists already
int template_id;
template_id = ship_template_lookup( buf );
if( template_id != -1 ) {
sip = &Ship_templates[template_id];
Warning(LOCATION, "WARNING: Ship template %s already exists. All ship template names must be unique.", sip->name);
if (!skip_to_start_of_string_either("$Template:", "#End")) {
error_display(1, "Missing [#End] or [$Template] after duplicate ship template %s", sip->name);
}
return;
}
else {
Ship_templates.push_back(ship_info());
sip = &Ship_templates.back();
strcpy_s(sip->name, buf);
//Use another template for this template. This allows for template hierarchies. - Turey
if( optional_string("+Use Template:") ) {
char template_name[SHIP_MULTITEXT_LENGTH];
stuff_string(template_name, F_NAME, SHIP_MULTITEXT_LENGTH);
template_id = ship_template_lookup( template_name);
if ( template_id != -1 ) {
first_time = false;
sip->clone(Ship_templates[template_id]);
strcpy_s(sip->name, buf);
sip->animations.changeShipName(sip->name);
}
else {
Warning(LOCATION, "Unable to find ship template '%s' requested by ship template '%s', ignoring template request...", template_name, buf);
}
}
}
parse_ship_values( sip, true, first_time, false );
}
static void parse_ship_sound(const char *name, GameSounds id, ship_info *sip)
{
Assert( name != NULL );
gamesnd_id temp_index;
if (parse_game_sound(name, &temp_index))
sip->ship_sounds.insert(std::make_pair(id, temp_index));
}
static void parse_ship_sounds(ship_info *sip)
{
parse_ship_sound("$CockpitEngineSnd:", GameSounds::ENGINE, sip);
parse_ship_sound("$FullThrottleSnd:", GameSounds::FULL_THROTTLE, sip);
parse_ship_sound("$ZeroThrottleSnd:", GameSounds::ZERO_THROTTLE, sip);
parse_ship_sound("$ThrottleUpSnd:", GameSounds::THROTTLE_UP, sip);
parse_ship_sound("$ThrottleDownSnd:", GameSounds::THROTTLE_DOWN, sip);
parse_ship_sound("$AfterburnerSnd:", GameSounds::ABURN_LOOP, sip);
parse_ship_sound("$AfterburnerEngageSnd:", GameSounds::ABURN_ENGAGE, sip);
parse_ship_sound("$AfterburnerFailedSnd:", GameSounds::ABURN_FAIL, sip);
parse_ship_sound("$MissileTrackingSnd:", GameSounds::MISSILE_TRACKING, sip);
parse_ship_sound("$MissileLockedSnd:", GameSounds::MISSILE_LOCK, sip);
parse_ship_sound("$PrimaryCycleSnd:", GameSounds::PRIMARY_CYCLE, sip);
parse_ship_sound("$SecondaryCycleSnd:", GameSounds::SECONDARY_CYCLE, sip);
parse_ship_sound("$TargetAcquiredSnd:", GameSounds::TARGET_ACQUIRE, sip);
parse_ship_sound("$PrimaryFireFailedSnd:", GameSounds::OUT_OF_WEAPON_ENERGY, sip);
parse_ship_sound("$SecondaryFireFailedSnd:", GameSounds::OUT_OF_MISSLES, sip);
parse_ship_sound("$HeatSeekerLaunchWarningSnd:", GameSounds::HEATLOCK_WARN, sip);
parse_ship_sound("$AspectSeekerLaunchWarningSnd:", GameSounds::ASPECTLOCK_WARN, sip);
parse_ship_sound("$MissileLockWarningSnd:", GameSounds::THREAT_FLASH, sip);
parse_ship_sound("$HeatSeekerProximityWarningSnd:", GameSounds::PROXIMITY_WARNING, sip);
parse_ship_sound("$AspectSeekerProximityWarningSnd:", GameSounds::PROXIMITY_ASPECT_WARNING, sip);
parse_ship_sound("$MissileEvadedSnd:", GameSounds::MISSILE_EVADED_POPUP, sip);
parse_ship_sound("$CargoScanningSnd:", GameSounds::CARGO_SCAN, sip);
parse_ship_sound("$DeathRollSnd:", GameSounds::DEATH_ROLL, sip);
parse_ship_sound("$ExplosionSnd:", GameSounds::SHIP_EXPLODE_1, sip);
parse_ship_sound("$SubsysExplosionSnd:", GameSounds::SUBSYS_EXPLODE, sip);
}
static void parse_ship_particle_effect(ship_info* sip, particle_effect* pe, const char *id_string)
{
float tempf;
int temp;
if(optional_string("+Max particles:"))
{
stuff_int(&temp);
if (temp < 0) {
Warning(LOCATION,"Bad value %i, defined as %s particle number (max) in ship '%s'.\nValue should be a non-negative integer.\n", temp, id_string, sip->name);
} else {
pe->n_high = temp;
if (pe->n_high == 0) {
// notification for disabling the particles
mprintf(("Particle effect for %s disabled on ship '%s'.\n", id_string, sip->name));
}
}
}
if(optional_string("+Min particles:"))
{
stuff_int(&temp);
if (temp < 0) {
Warning(LOCATION,"Bad value %i, defined as %s particle number (min) in ship '%s'.\nValue should be a non-negative integer.\n", temp, id_string, sip->name);
} else {
pe->n_low = temp;
}
}
if (pe->n_low > pe->n_high)
pe->n_low = pe->n_high;
if(optional_string("+Max Radius:"))
{
stuff_float(&tempf);
if (tempf <= 0.0f) {
Warning(LOCATION,"Bad value %f, defined as %s particle radius (max) in ship '%s'.\nValue should be a positive float.\n", tempf, id_string, sip->name);
} else {
pe->max_rad = tempf;
}
}
if(optional_string("+Min Radius:"))
{
stuff_float(&tempf);
if (tempf < 0.0f) {
Warning(LOCATION,"Bad value %f, defined as %s particle radius (min) in ship '%s'.\nValue should be a non-negative float.\n", tempf, id_string, sip->name);
} else {
pe->min_rad = tempf;
}
}
if (pe->min_rad > pe->max_rad)
pe->min_rad = pe->max_rad;
if(optional_string("+Max Lifetime:"))
{
stuff_float(&tempf);
if (tempf <= 0.0f) {
Warning(LOCATION,"Bad value %f, defined as %s particle lifetime (max) in ship '%s'.\nValue should be a positive float.\n", tempf, id_string, sip->name);
} else {
pe->max_life = tempf;
}
}
if(optional_string("+Min Lifetime:"))
{
stuff_float(&tempf);
if (tempf < 0.0f) {
Warning(LOCATION,"Bad value %f, defined as %s particle lifetime (min) in ship '%s'.\nValue should be a non-negative float.\n", tempf, id_string, sip->name);
} else {
pe->min_life = tempf;
}
}
if (pe->min_life > pe->max_life)
pe->min_life = pe->max_life;
if(optional_string("+Max Velocity:"))
{
stuff_float(&tempf);
if (tempf < 0.0f) {
Warning(LOCATION,"Bad value %f, defined as %s particle velocity (max) in ship '%s'.\nValue should be a non-negative float.\n", tempf, id_string, sip->name);
} else {
pe->max_vel = tempf;
}
}
if(optional_string("+Min Velocity:"))
{
stuff_float(&tempf);
if (tempf < 0.0f) {
Warning(LOCATION,"Bad value %f, defined as %s particle velocity (min) in ship '%s'.\nValue should be a non-negative float.\n", tempf, id_string, sip->name);
} else {
pe->min_vel = tempf;
}
}
if (pe->min_vel > pe->max_vel)
pe->min_vel = pe->max_vel;
if(optional_string("+Normal Variance:"))
{
stuff_float(&tempf);
if ((tempf >= 0.0f) && (tempf <= 2.0f)) {
pe->variance = tempf;
} else {
Warning(LOCATION,"Bad value %f, defined as %s particle normal variance in ship '%s'.\nValue should be a float from 0.0 to 2.0.\n", tempf, id_string, sip->name);
}
}
}
static void parse_allowed_weapons(ship_info *sip, const bool is_primary, const bool is_dogfight, const bool first_time)
{
int i, num_allowed;
int allowed_weapons[MAX_WEAPON_TYPES];
const int max_banks = (is_primary ? MAX_SHIP_PRIMARY_BANKS : MAX_SHIP_SECONDARY_BANKS);
const ubyte weapon_type = (is_dogfight ? DOGFIGHT_WEAPON : REGULAR_WEAPON);
const int offset = (is_primary ? 0 : MAX_SHIP_PRIMARY_BANKS);
const char *allowed_banks_str = is_primary ? (is_dogfight ? "$Allowed Dogfight PBanks:" : "$Allowed PBanks:")
: (is_dogfight ? "$Allowed Dogfight SBanks:" : "$Allowed SBanks:");
const char *bank_type_str = is_primary ? "primary" : "secondary";
// Goober5000 - fixed Bobboau's implementation of restricted banks
int bank;
// Set the weapons filter used in weapons loadout
if (optional_string(allowed_banks_str))
{
// this ship has loadout stuff, so make room
if (sip->restricted_loadout_flag.empty())
sip->restricted_loadout_flag.resize(MAX_SHIP_WEAPONS);
if (sip->allowed_bank_restricted_weapons.empty())
sip->allowed_bank_restricted_weapons.resize(MAX_SHIP_WEAPONS);
// MageKing17 - We need to make modular tables replace bank restrictions by default, instead of adding to them.
if (!first_time && !(optional_string("+noreplace"))) { // Only makes sense for modular tables.
// clear allowed weapons so the modular table can define new ones
for (bank = 0; bank < max_banks; bank++) {
sip->allowed_bank_restricted_weapons[offset+bank].clear_flag(weapon_type);
sip->restricted_loadout_flag[offset+bank] &= ~weapon_type;
}
}
bank = -1;
while (check_for_string("("))
{
bank++;
// make sure we don't specify more than we have banks for
if (bank >= max_banks)
{
Warning(LOCATION, "%s bank-specific loadout for %s exceeds permissible number of %s banks. Ignoring the rest...", allowed_banks_str, sip->name, bank_type_str);
bank--;
break;
}
num_allowed = (int)stuff_int_list(allowed_weapons, MAX_WEAPON_TYPES, WEAPON_LIST_TYPE);
// actually say which weapons are allowed
for ( i = 0; i < num_allowed; i++ )
{
if ( allowed_weapons[i] >= 0 ) // MK, Bug fix, 9/6/99. Used to be "allowed_weapons" not "allowed_weapons[i]".
{
sip->allowed_bank_restricted_weapons[offset+bank].set_flag(allowed_weapons[i], weapon_type);
}
}
}
// set flags if need be
if (bank > 0) // meaning there was a restricted bank table entry
{
for (i=0; i<=bank; i++)
{
sip->restricted_loadout_flag[offset+i] |= weapon_type;
}
}
}
}
/**
* Common method for parsing ship/subsystem primary/secondary weapons so that the parser doesn't flip out in the event of a problem.
*
*/
static void parse_weapon_bank(ship_info *sip, bool is_primary, int *num_banks, int *bank_default_weapons, int *bank_capacities)
{
Assert(sip != NULL);
Assert(bank_default_weapons != NULL);
Assert(bank_capacities != NULL);
const int max_banks = is_primary ? MAX_SHIP_PRIMARY_BANKS : MAX_SHIP_SECONDARY_BANKS;
const char *default_banks_str = is_primary ? "$Default PBanks:" : "$Default SBanks:";
const char *bank_capacities_str = is_primary ? "$PBank Capacity:" : "$SBank Capacity:";
// we initialize to the previous parse, which presumably worked
int num_bank_capacities = num_banks != NULL ? *num_banks : 0;
if (optional_string(default_banks_str))
{
// get weapon list
if (num_banks != NULL)
*num_banks = (int)stuff_int_list(bank_default_weapons, max_banks, WEAPON_LIST_TYPE);
else
stuff_int_list(bank_default_weapons, max_banks, WEAPON_LIST_TYPE);
}
if (optional_string(bank_capacities_str))
{
// get capacity list
num_bank_capacities = (int)stuff_int_list(bank_capacities, max_banks, RAW_INTEGER_TYPE);
}
// num_banks can be null if we're parsing weapons for a turret
if ((num_banks != NULL) && (*num_banks != num_bank_capacities))
{
// okay for a ship to have 0 primary capacities, since it won't be ammo-enabled
if (is_primary && num_bank_capacities != 0)
{
Warning(LOCATION, "Ship class '%s' has %d primary banks, but %d primary capacities... fix this!!", sip->name, *num_banks, num_bank_capacities);
}
// secondaries have no excuse!
if (!is_primary)
{
Warning(LOCATION, "Ship class '%s' has %d secondary banks, but %d secondary capacities... fix this!!", sip->name, *num_banks, num_bank_capacities);
}
}
}
/**
* Common method for parsing briefing icon info.
*/
static int parse_and_add_briefing_icon_info()
{
int bii_index = -1;
size_t icon;
char regular_temp[MAX_FILENAME_LEN];
char fade_temp[MAX_FILENAME_LEN];
char highlight_temp[MAX_FILENAME_LEN];
required_string("+Regular:");
stuff_string(regular_temp, F_NAME, MAX_FILENAME_LEN);
required_string("+Fade:");
stuff_string(fade_temp, F_NAME, MAX_FILENAME_LEN);
required_string("+Highlight:");
stuff_string(highlight_temp, F_NAME, MAX_FILENAME_LEN);
// search among our existing icons
for (icon = 0; icon < Briefing_icon_info.size(); icon++)
{
if ( !stricmp(regular_temp, Briefing_icon_info[icon].regular.filename)
&& !stricmp(fade_temp, Briefing_icon_info[icon].fade.filename)
&& !stricmp(highlight_temp, Briefing_icon_info[icon].highlight.filename) )
{
bii_index = (int) icon;
break;
}
}
// icon not found: create new one
if (bii_index < 0)
{
briefing_icon_info bii;
generic_anim_init(&bii.regular, regular_temp);
hud_anim_init(&bii.fade, 0, 0, fade_temp);
hud_anim_init(&bii.highlight, 0, 0, highlight_temp);
bii_index = (int) Briefing_icon_info.size();
Briefing_icon_info.push_back(bii);
}
return bii_index;
}
/**
* Determines the warp parameters for this ship class (or ship).
*
* If we are creating a ship, we want to inherit the parameters of the ship class, then override on a field-by-field basis.
*/
int parse_warp_params(const WarpParams *inherit_from, WarpDirection direction, const char *info_type_name, const char *info_name)
{
Assert(info_type_name != nullptr);
Assert(info_name != nullptr);
// for parsing
const char *prefix = (direction == WarpDirection::WARP_IN) ? "$Warpin" : "$Warpout";
char str[NAME_LENGTH];
WarpParams params;
if (inherit_from != nullptr)
params = *inherit_from;
params.direction = direction;
sprintf(str, "%s type:", prefix);
if (optional_string(str))
{
char buf[NAME_LENGTH];
stuff_string(buf, F_NAME, NAME_LENGTH);
int j = warptype_match(buf);
if (j >= 0) {
params.warp_type = j;
}
else {
// try to match the warp type with one of our fireballs
j = fireball_info_lookup(buf);
if (j >= 0) {
params.warp_type = j | WT_DEFAULT_WITH_FIREBALL;
}
else {
Warning(LOCATION, "Invalid %s '%s' specified for %s '%s'", str, buf, info_type_name, info_name);
params.warp_type = WT_DEFAULT;
}
}
}
sprintf(str, "%s Start Sound:", prefix);
parse_game_sound(str, &params.snd_start);
sprintf(str, "%s End Sound:", prefix);
parse_game_sound(str, &params.snd_end);
if (direction == WarpDirection::WARP_OUT)
{
sprintf(str, "%s engage time:", prefix);
if (optional_string(str))
{
float t_time;
stuff_float(&t_time);
if (t_time > 0.0f)
params.warpout_engage_time = fl2i(t_time*1000.0f);
else
Warning(LOCATION, "%s specified as 0 or less on %s '%s'; value ignored", str, info_type_name, info_name);
}
}
sprintf(str, "%s speed:", prefix);
if (optional_string(str))
{
float speed;
stuff_float(&speed);
if (speed > 0.0f)
params.speed = speed;
else
Warning(LOCATION, "%s specified as 0 or less on %s '%s'; value ignored", str, info_type_name, info_name);
}
sprintf(str, "%s time:", prefix);
if (optional_string(str))
{
float t_time;
stuff_float(&t_time);
if (t_time > 0.0f)
params.time = fl2i(t_time*1000.0f);
else
Warning(LOCATION, "%s specified as 0 or less on %s '%s'; value ignored", str, info_type_name, info_name);
}
sprintf(str, "%s %s exp:", prefix, direction == WarpDirection::WARP_IN ? "decel" : "accel");
if (optional_string(str))
{
float accel_exp;
stuff_float(&accel_exp);
if (accel_exp >= 0.0f)
params.accel_exp = accel_exp;
else
Warning(LOCATION, "%s specified as less than 0 on %s '%s'; value ignored", str, info_type_name, info_name);
}
sprintf(str, "%s radius:", prefix);
if (optional_string(str))
{
float rad;
stuff_float(&rad);
if (rad > 0.0f)
params.radius = rad;
else
Warning(LOCATION, "%s specified as 0 or less on %s '%s'; value ignored", str, info_type_name, info_name);
}
sprintf(str, "%s animation:", prefix);
if (optional_string(str))
{
stuff_string(params.anim, F_NAME, MAX_FILENAME_LEN);
}
if (direction == WarpDirection::WARP_OUT)
{
sprintf(str, "$Player warpout speed:");
if (optional_string(str))
{
float speed;
stuff_float(&speed);
if (speed > 0.0f)
params.warpout_player_speed = speed;
else
Warning(LOCATION, "%s specified as 0 or less on %s '%s'; value ignored", str, info_type_name, info_name);
}
}
// get the index of these warp params, which may be a new index
return find_or_add_warp_params(params);
}
/**
* Puts values into a ship_info.
*/
static void parse_ship_values(ship_info* sip, const bool is_template, const bool first_time, const bool replace)
{
char buf[SHIP_MULTITEXT_LENGTH];
const char* info_type_name;
const char* type_name;
char name_tmp[NAME_LENGTH];
if ( ! is_template ) {
info_type_name = "Ship Class";
type_name = "$Name";
}
else {
info_type_name = "Ship Template";
type_name = "$Template";
}
if (optional_string("$Alt name:") || optional_string("$Display Name:"))
{
stuff_string(sip->display_name, F_NAME, NAME_LENGTH);
end_string_at_first_hash_symbol(sip->display_name, true);
consolidate_double_characters(sip->display_name, '#');
sip->flags.set(Ship::Info_Flags::Has_display_name);
}
if(optional_string("$Short name:"))
stuff_string(sip->short_name, F_NAME, NAME_LENGTH);
else if (first_time)
{
char *srcpos, *srcend, *destpos;
srcpos = sip->name;
destpos = sip->short_name;
srcend = srcpos + strlen(sip->name);
while(srcpos <= srcend)
{
if(*srcpos != ' ')
*destpos++ = *srcpos++;
else
srcpos++;
}
}
diag_printf ("Ship short name -- %s\n", sip->short_name);
if (optional_string("$Species:")) {
char temp[NAME_LENGTH];
stuff_string(temp, F_NAME, NAME_LENGTH);
int i_species = 0;
bool found = false;
for (SCP_vector<species_info>::iterator sii = Species_info.begin(); sii != Species_info.end(); ++sii, ++i_species) {
if (!stricmp(temp, sii->species_name)) {
sip->species = i_species;
found = true;
break;
}
}
if (!found) {
Error(LOCATION, "Invalid Species %s defined in table entry for ship %s.\n", temp, sip->name);
}
}
diag_printf ("Ship species -- %s\n", Species_info[sip->species].species_name);
if (optional_string("+Type:")) {
stuff_malloc_string(&sip->type_str, F_MESSAGE);
}
if (optional_string("+Maneuverability:")) {
stuff_malloc_string(&sip->maneuverability_str, F_MESSAGE);
}
if (optional_string("+Armor:")) {
stuff_malloc_string(&sip->armor_str, F_MESSAGE);
}
if (optional_string("+Manufacturer:")) {
stuff_malloc_string(&sip->manufacturer_str, F_MESSAGE);
}
if (optional_string("+Description:")) {
stuff_malloc_string(&sip->desc, F_MULTITEXT, NULL);
}
if (optional_string("+Tech Title:")) {
stuff_string(sip->tech_title, F_NAME, NAME_LENGTH);
}
if (optional_string("+Tech Description:")) {
stuff_malloc_string(&sip->tech_desc, F_MULTITEXT, NULL);
}
if (optional_string("+Length:")) {
stuff_malloc_string(&sip->ship_length, F_MESSAGE);
}
if (optional_string("+Gun Mounts:")) {
stuff_malloc_string(&sip->gun_mounts, F_MESSAGE);
}
if (optional_string("+Missile Banks:")) {
stuff_malloc_string(&sip->missile_banks, F_MESSAGE);
}
// Ship fadein effect, used when no ani is specified or ship_select_3d is active
if(optional_string("$Selection Effect:")) {
char effect[NAME_LENGTH];
stuff_string(effect, F_NAME, NAME_LENGTH);
if (!stricmp(effect, "FS2"))
sip->selection_effect = 2;
else if (!stricmp(effect, "FS1"))
sip->selection_effect = 1;
else if (!stricmp(effect, "off"))
sip->selection_effect = 0;
}
if(optional_string( "$Cockpit POF file:" ))
{
char temp[MAX_FILENAME_LEN];
stuff_string(temp, F_NAME, MAX_FILENAME_LEN);
// assume we're using this file name
bool valid = true;
// Goober5000 - if this is a modular table, and we're replacing an existing file name, and the file doesn't exist, don't replace it
if (replace)
if (sip->cockpit_pof_file[0] != '\0')
if (!cf_exists_full(temp, CF_TYPE_MODELS))
valid = false;
if (valid)
strcpy_s(sip->cockpit_pof_file, temp);
else
WarningEx(LOCATION, "Ship %s\nCockpit POF file \"%s\" invalid!", sip->name, temp);
}
if(optional_string( "+Cockpit offset:" ))
{
stuff_vec3d(&sip->cockpit_offset);
}
while(optional_string( "$Cockpit Display:" ))
{
cockpit_display_info display;
display.bg_filename[0] = 0;
display.fg_filename[0] = 0;
display.filename[0] = 0;
display.name[0] = 0;
display.offset[0] = 0;
display.offset[1] = 0;
required_string("+Texture:");
stuff_string(display.filename, F_NAME, MAX_FILENAME_LEN);
if ( optional_string("+Offsets:") ) {
stuff_int_list(display.offset, 2);
}
required_string("+Size:");
stuff_int_list(display.size, 2);
if ( optional_string("+Background:") ) {
stuff_string(display.bg_filename, F_NAME, MAX_FILENAME_LEN);
}
if ( optional_string("+Foreground:") ) {
stuff_string(display.fg_filename, F_NAME, MAX_FILENAME_LEN);
}
required_string("+Display Name:");
stuff_string(display.name, F_NAME, MAX_FILENAME_LEN);
if ( display.offset[0] < 0 || display.offset[1] < 0 ) {
Warning(LOCATION, "Negative display offsets given for cockpit display on %s, skipping entry", sip->name);
continue;
}
if( display.size[0] <= 0 || display.size[1] <= 0 ) {
Warning(LOCATION, "Negative or zero display size given for cockpit display on %s, skipping entry", sip->name);
continue;
}
sip->displays.push_back(display);
}
if(optional_string( "$POF file:" ))
{
char temp[MAX_FILENAME_LEN];
stuff_string(temp, F_NAME, MAX_FILENAME_LEN);
// assume we're using this file name
bool valid = true;
// Goober5000 - if this is a modular table, and we're replacing an existing file name, and the file doesn't exist, don't replace it
if (replace)
if (sip->pof_file[0] != '\0')
if (!cf_exists_full(temp, CF_TYPE_MODELS))
valid = false;
if (valid)
strcpy_s(sip->pof_file, temp);
else
WarningEx(LOCATION, "Ship %s\nPOF file \"%s\" invalid!", sip->name, temp);
}
if(optional_string( "$POF file Techroom:" ))
{
char temp[MAX_FILENAME_LEN];
stuff_string(temp, F_NAME, MAX_FILENAME_LEN);
// assume we're using this file name
bool valid = true;
// if this is a modular table, and we're replacing an existing file name, and the file doesn't exist, don't replace it
if (replace)
if (sip->pof_file_tech[0] != '\0')
if (!cf_exists_full(temp, CF_TYPE_MODELS))
valid = false;
if (valid)
strcpy_s(sip->pof_file_tech, temp);
else
WarningEx(LOCATION, "Ship %s\nTechroom POF file \"%s\" invalid!", sip->name, temp);
}
// ship class texture replacement - Goober5000
// don't clear the vector because we could be parsing a TBM
if (optional_string("$Texture Replace:"))
{
texture_replace tr;
char *p;
tr.from_table = true;
while (optional_string("+old:"))
{
strcpy_s(tr.ship_name, sip->name);
tr.new_texture_id = -1;
stuff_string(tr.old_texture, F_NAME, MAX_FILENAME_LEN);
required_string("+new:");
stuff_string(tr.new_texture, F_NAME, MAX_FILENAME_LEN);
// get rid of extensions
p = strchr(tr.old_texture, '.');
if (p)
{
mprintf(("Extraneous extension found on replacement texture %s!\n", tr.old_texture));
*p = 0;
}
p = strchr(tr.new_texture, '.');
if (p)
{
mprintf(("Extraneous extension found on replacement texture %s!\n", tr.new_texture));
*p = 0;
}
// add it if we aren't over the limit
if (sip->replacement_textures.size() < MAX_MODEL_TEXTURES)
sip->replacement_textures.push_back(tr);
else
mprintf(("Too many replacement textures specified for ship '%s'!\n", sip->name));
}
}
// optional hud targeting model
if(optional_string( "$POF target file:"))
{
char temp[MAX_FILENAME_LEN];
stuff_string(temp, F_NAME, MAX_FILENAME_LEN);
// assume we're using this file name
bool valid = true;
// Goober5000 - if this is a modular table, and we're replacing an existing file name, and the file doesn't exist, don't replace it
if (replace)
if (sip->pof_file_hud[0] != '\0')
if (!cf_exists_full(temp, CF_TYPE_MODELS))
valid = false;
if (valid)
strcpy_s(sip->pof_file_hud, temp);
else
WarningEx(LOCATION, "Ship \"%s\" POF target file \"%s\" invalid!", sip->name, temp);
}
// optional hud target LOD if not using special hud model
if (optional_string( "$POF target LOD:" )) {
stuff_int(&sip->hud_target_lod);
}
if(optional_string("$Detail distance:")) {
sip->num_detail_levels = (int)stuff_int_list(sip->detail_distance, MAX_SHIP_DETAIL_LEVELS, RAW_INTEGER_TYPE);
}
if(optional_string("$Collision LOD:")) {
stuff_int(&sip->collision_lod);
// Cap to sane values, just in case
sip->collision_lod = MAX(0, MIN(sip->collision_lod, sip->num_detail_levels));
}
// check for optional pixel colors
// This is dummied out; we no longer support palettized rendering modes
while(optional_string("$ND:")){
ubyte nr, ng, nb;
stuff_ubyte(&nr);
stuff_ubyte(&ng);
stuff_ubyte(&nb);
}
if (optional_string("$Enable Team Colors:")) {
stuff_boolean(&sip->uses_team_colors);
sip->default_team_name = "None";
}
if (optional_string("$Default Team:")) {
char temp[NAME_LENGTH];
stuff_string(temp, F_NAME, NAME_LENGTH);
SCP_string name = temp;
if (!stricmp(temp, "none")) {
sip->uses_team_colors = true;
} else {
if (Team_Colors.find(name) != Team_Colors.end()) {
sip->default_team_name = name;
sip->uses_team_colors = true;
} else {
Warning(LOCATION, "Team name %s is invalid. Teams must be defined in colors.tbl.\n", temp);
}
}
}
if(optional_string("$Show damage:"))
{
int bogus_bool;
stuff_boolean(&bogus_bool);
}
if(optional_string("$Damage Lightning Type:"))
{
stuff_string(buf, F_NAME, SHIP_MULTITEXT_LENGTH);
auto j = lightningtype_match(buf);
if(j >= 0) {
sip->damage_lightning_type = j;
} else {
Warning(LOCATION, "Invalid lightning type '%s' specified for ship '%s'", buf, sip->name);
sip->damage_lightning_type = SLT_DEFAULT;
}
}
if(optional_string("$Impact:"))
{
if(optional_string("+Damage Type:"))
{
stuff_string(buf, F_NAME, NAME_LENGTH);
sip->collision_damage_type_idx = damage_type_add(buf);
}
}
//HACK -
//This should really be reworked so that all particle fields
//are settable, but erg, just not happening right now -C
if(optional_string("$Impact Spew:"))
{
parse_ship_particle_effect(sip, &sip->impact_spew, "impact spew");
}
if(optional_string("$Damage Spew:"))
{
parse_ship_particle_effect(sip, &sip->damage_spew, "damage spew");
}
if(optional_string("$Collision Physics:"))
{
if(optional_string("+Bounce:")) {
stuff_float(&sip->collision_physics.bounce);
}
if(optional_string("+Both Small Bounce:")) {
stuff_float(&sip->collision_physics.both_small_bounce);
}
if(optional_string("+Friction:")) {
stuff_float(&sip->collision_physics.friction);
}
if(optional_string("+Rotation Factor:")) {
stuff_float(&sip->collision_physics.rotation_factor);
}
if(optional_string("+Landing Max Forward Vel:")) {
stuff_float(&sip->collision_physics.landing_max_z);
}
if(optional_string("+Landing Min Forward Vel:")) {
stuff_float(&sip->collision_physics.landing_min_z);
}
if(optional_string("+Landing Max Descent Vel:")) {
stuff_float(&sip->collision_physics.landing_min_y);
sip->collision_physics.landing_min_y *= -1;
}
if(optional_string("+Landing Max Horizontal Vel:")) {
stuff_float(&sip->collision_physics.landing_max_x);
}
if(optional_string("+Landing Max Angle:")) {
float degrees;
stuff_float(&degrees);
sip->collision_physics.landing_max_angle = cosf(fl_radians(90.0f - degrees));
}
if(optional_string("+Landing Min Angle:")) {
float degrees;
stuff_float(&degrees);
sip->collision_physics.landing_min_angle = cosf(fl_radians(90.0f - degrees));
}
if(optional_string("+Landing Max Rotate Angle:")) {
float degrees;
stuff_float(&degrees);
sip->collision_physics.landing_max_rot_angle = cosf(fl_radians(90.0f - degrees));
}
if(optional_string("+Reorient Max Forward Vel:")) {
stuff_float(&sip->collision_physics.reorient_max_z);
}
if(optional_string("+Reorient Min Forward Vel:")) {
stuff_float(&sip->collision_physics.reorient_min_z);
}
if(optional_string("+Reorient Max Descent Vel:")) {
stuff_float(&sip->collision_physics.reorient_min_y);
sip->collision_physics.reorient_min_y *= -1;
}
if(optional_string("+Reorient Max Horizontal Vel:")) {
stuff_float(&sip->collision_physics.reorient_max_x);
}
if(optional_string("+Reorient Max Angle:")) {
float degrees;
stuff_float(&degrees);
sip->collision_physics.reorient_max_angle = cosf(fl_radians(90.0f - degrees));
}
if(optional_string("+Reorient Min Angle:")) {
float degrees;
stuff_float(&degrees);
sip->collision_physics.reorient_min_angle = cosf(fl_radians(90.0f - degrees));
}
if(optional_string("+Reorient Max Rotate Angle:")) {
float degrees;
stuff_float(&degrees);
sip->collision_physics.reorient_max_rot_angle = cosf(fl_radians(90.0f - degrees));
}
if(optional_string("+Reorient Speed Mult:")) {
stuff_float(&sip->collision_physics.reorient_mult);
}
if(optional_string("+Landing Rest Angle:")) {
float degrees;
stuff_float(&degrees);
sip->collision_physics.landing_rest_angle = cosf(fl_radians(90.0f - degrees));
}
parse_game_sound("+Landing Sound:", &sip->collision_physics.landing_sound_idx);
parse_game_sound("+Collision Sound Light:", &sip->collision_physics.collision_sound_light_idx);
parse_game_sound("+Collision Sound Heavy:", &sip->collision_physics.collision_sound_heavy_idx);
parse_game_sound("+Collision Sound Shielded:", &sip->collision_physics.collision_sound_shielded_idx);
}
if(optional_string("$Debris:"))
{
if(optional_string("+Min Lifetime:")) {
stuff_float(&sip->debris_min_lifetime);
if(sip->debris_min_lifetime < 0.0f)
Warning(LOCATION, "Debris min lifetime on %s '%s' is below 0 and will be ignored", info_type_name, sip->name);
}
if(optional_string("+Max Lifetime:")) {
stuff_float(&sip->debris_max_lifetime);
if(sip->debris_max_lifetime < 0.0f)
Warning(LOCATION, "Debris max lifetime on %s '%s' is below 0 and will be ignored", info_type_name, sip->name);
}
if(optional_string("+Min Speed:")) {
stuff_float(&sip->debris_min_speed);
if(sip->debris_min_speed < 0.0f)
Warning(LOCATION, "Debris min speed on %s '%s' is below 0 and will be ignored", info_type_name, sip->name);
}
if(optional_string("+Max Speed:")) {
stuff_float(&sip->debris_max_speed);
if(sip->debris_max_speed < 0.0f)
Warning(LOCATION, "Debris max speed on %s '%s' is below 0 and will be ignored", info_type_name, sip->name);
}
if(optional_string("+Min Rotation speed:")) {
stuff_float(&sip->debris_min_rotspeed);
if(sip->debris_min_rotspeed < 0.0f)
Warning(LOCATION, "Debris min speed on %s '%s' is below 0 and will be ignored", info_type_name, sip->name);
}
if(optional_string("+Max Rotation speed:")) {
stuff_float(&sip->debris_max_rotspeed);
if(sip->debris_max_rotspeed < 0.0f)
Warning(LOCATION, "Debris max speed on %s '%s' is below 0 and will be ignored", info_type_name, sip->name);
}
if(optional_string("+Damage Type:")) {
stuff_string(buf, F_NAME, NAME_LENGTH);
sip->debris_damage_type_idx = damage_type_add(buf);
}
if(optional_string("+Min Hitpoints:")) {
stuff_float(&sip->debris_min_hitpoints);
if(sip->debris_min_hitpoints < 0.0f)
Warning(LOCATION, "Debris min hitpoints on %s '%s' is below 0 and will be ignored", info_type_name, sip->name);
}
if(optional_string("+Max Hitpoints:")) {
stuff_float(&sip->debris_max_hitpoints);
if(sip->debris_max_hitpoints < 0.0f)
Warning(LOCATION, "Debris max hitpoints on %s '%s' is below 0 and will be ignored", info_type_name, sip->name);
}
if(optional_string("+Damage Multiplier:")) {
stuff_float(&sip->debris_damage_mult);
if(sip->debris_damage_mult < 0.0f)
Warning(LOCATION, "Debris damage multiplier on %s '%s' is below 0 and will be ignored", info_type_name, sip->name);
}
if(optional_string("+Lightning Arc Percent:")) {
stuff_float(&sip->debris_arc_percent);
if(sip->debris_arc_percent < 0.0f || sip->debris_arc_percent > 100.0f) {
Warning(LOCATION, "Lightning Arc Percent on %s '%s' should be between 0 and 100.0 (read %f). Entry will be ignored.", info_type_name, sip->name, sip->debris_arc_percent);
sip->debris_arc_percent = 50.0;
}
//Percent is nice for modders, but here in the code we want it betwwen 0 and 1.0
sip->debris_arc_percent /= 100.0;
}
gamesnd_id ambient_snd, collision_snd_light, collision_snd_heavy, explosion_snd;
if (parse_game_sound("+Ambient Sound:", &ambient_snd))
sip->debris_ambient_sound = ambient_snd;
if (parse_game_sound("+Collision Sound Light:", &collision_snd_light))
sip->debris_collision_sound_light = collision_snd_light;
if (parse_game_sound("+Collision Sound Heavy:", &collision_snd_heavy))
sip->debris_collision_sound_heavy = collision_snd_heavy;
if (parse_game_sound("+Explosion Sound:", &explosion_snd))
sip->debris_explosion_sound = explosion_snd;
if (optional_string("+Generic Debris POF file:"))
{
char temp[MAX_FILENAME_LEN];
stuff_string(temp, F_NAME, MAX_FILENAME_LEN);
bool valid = true;
if (replace)
if (sip->generic_debris_pof_file[0] != '\0')
if (!cf_exists_full(temp, CF_TYPE_MODELS))
valid = false;
if (valid)
strcpy_s(sip->generic_debris_pof_file, temp);
else
WarningEx(LOCATION, "Ship %s\nGeneric Debris POF file \"%s\" invalid!", sip->name, temp);
}
if (optional_string("+Generic Debris Spew Num:")) {
stuff_int(&(sip->generic_debris_spew_num));
if (sip->generic_debris_spew_num < 0) {
Warning(LOCATION, "Generic Debris Spew Num for %s '%s' cannot be below 0.", info_type_name, sip->name);
sip->generic_debris_spew_num = 0;
}
}
}
//WMC - sanity checking
if(sip->debris_min_speed > sip->debris_max_speed && sip->debris_max_speed >= 0.0f) {
Warning(LOCATION, "Debris min speed (%f) on %s '%s' is greater than debris max speed (%f), and will be set to debris max speed.", sip->debris_min_speed, info_type_name, sip->name, sip->debris_max_speed);
sip->debris_min_speed = sip->debris_max_speed;
}
if(sip->debris_min_rotspeed > sip->debris_max_rotspeed && sip->debris_max_rotspeed >= 0.0f) {
Warning(LOCATION, "Debris min rotation speed (%f) on %s '%s' is greater than debris max rotation speed (%f), and will be set to debris max rotation speed.", sip->debris_min_rotspeed, info_type_name, sip->name, sip->debris_max_rotspeed);
sip->debris_min_rotspeed = sip->debris_max_rotspeed;
}
if(sip->debris_min_lifetime > sip->debris_max_lifetime && sip->debris_max_lifetime >= 0.0f) {
Warning(LOCATION, "Debris min lifetime (%f) on %s '%s' is greater than debris max lifetime (%f), and will be set to debris max lifetime.", sip->debris_min_lifetime, info_type_name, sip->name, sip->debris_max_lifetime);
sip->debris_min_lifetime = sip->debris_max_lifetime;
}
if(sip->debris_min_hitpoints > sip->debris_max_hitpoints && sip->debris_max_hitpoints >= 0.0f) {
Warning(LOCATION, "Debris min hitpoints (%f) on %s '%s' is greater than debris max hitpoints (%f), and will be set to debris max hitpoints.", sip->debris_min_hitpoints, info_type_name, sip->name, sip->debris_max_hitpoints);
sip->debris_min_hitpoints = sip->debris_max_hitpoints;
}
if(optional_string("$Density:"))
stuff_float( &(sip->density) );
diag_printf ("Ship density -- %7.3f\n", sip->density);
if(optional_string("$Damp:"))
stuff_float( &(sip->damp) );
diag_printf ("Ship damp -- %7.3f\n", sip->damp);
if(optional_string("$Rotdamp:"))
stuff_float( &(sip->rotdamp) );
diag_printf ("Ship rotdamp -- %7.3f\n", sip->rotdamp);
if(optional_string("$Banking Constant:"))
stuff_float( &(sip->delta_bank_const) );
diag_printf ("%s '%s' delta_bank_const -- %7.3f\n", info_type_name, sip->name, sip->delta_bank_const);
if(optional_string("$Max Velocity:"))
{
stuff_vec3d(&sip->max_vel);
sip->max_accel = sip->max_vel.xyz.z;
}
// calculate the max speed from max_velocity
sip->max_speed = sip->max_vel.xyz.z;
if(optional_string("$Player Minimum Velocity:"))
stuff_vec3d(&sip->min_vel);
if(optional_string("$Rotation Time:"))
{
stuff_vec3d(&sip->rotation_time);
// div/0 safety check.
if ((sip->rotation_time.xyz.x == 0) || (sip->rotation_time.xyz.y == 0) || (sip->rotation_time.xyz.z == 0))
Warning(LOCATION, "Rotation time must have non-zero values in each of the three variables.\nFix this in %s %s\n", info_type_name, sip->name);
sip->srotation_time = (sip->rotation_time.xyz.x + sip->rotation_time.xyz.y)/2.0f;
sip->max_rotvel.xyz.x = (2 * PI) / sip->rotation_time.xyz.x;
sip->max_rotvel.xyz.y = (2 * PI) / sip->rotation_time.xyz.y;
sip->max_rotvel.xyz.z = (2 * PI) / sip->rotation_time.xyz.z;
}
// get the backwards velocity;
if(optional_string("$Rear Velocity:"))
{
stuff_float(&sip->max_rear_vel);
sip->min_speed = -sip->max_rear_vel;
}
// get the accelerations
if(optional_string("$Forward accel:"))
stuff_float(&sip->forward_accel );
if(optional_string("$Forward decel:"))
stuff_float(&sip->forward_decel );
if(optional_string("$Slide accel:"))
stuff_float(&sip->slide_accel );
if(optional_string("$Slide decel:"))
stuff_float(&sip->slide_decel );
if(optional_string("$Glide:"))
{
stuff_boolean(&sip->can_glide);
}
if(sip->can_glide == true)
{
if(optional_string("+Dynamic Glide Cap:"))
stuff_boolean(&sip->glide_dynamic_cap);
if(optional_string("+Max Glide Speed:"))
stuff_float(&sip->glide_cap );
if(optional_string("+Glide Accel Mult:"))
stuff_float(&sip->glide_accel_mult);
}
if(optional_string("$Use Newtonian Dampening:")) {
sip->newtonian_damp_override = true;
stuff_boolean(&sip->use_newtonian_damp);
}
if(optional_string("$Autoaim FOV:"))
{
float fov_temp;
stuff_float(&fov_temp);
// Make sure it is a reasonable value
if (fov_temp < 0.0f)
fov_temp = 0.0f;
if (fov_temp > 180.0f)
fov_temp = 180.0f;
sip->aiming_flags.set(Ship::Aiming_Flags::Autoaim);
sip->autoaim_fov = fov_temp * PI / 180.0f;
if(optional_string("+Converging Autoaim"))
sip->aiming_flags.set(Ship::Aiming_Flags::Autoaim_convergence);
if(optional_string("+Minimum Distance:"))
stuff_float(&sip->minimum_convergence_distance);
parse_game_sound("+Autoaim Lock Snd:", &sip->autoaim_lock_snd);
parse_game_sound("+Autoaim Lost Snd:", &sip->autoaim_lost_snd);
}
if(optional_string("$Convergence:"))
{
if(optional_string("+Automatic"))
{
sip->aiming_flags.set(Ship::Aiming_Flags::Auto_convergence);
if(optional_string("+Minimum Distance:"))
stuff_float(&sip->minimum_convergence_distance);
}
if(optional_string("+Standard"))
{
sip->aiming_flags.set(Ship::Aiming_Flags::Std_convergence);
if(required_string("+Distance:"))
stuff_float(&sip->convergence_distance);
}
if(optional_string("+Offset:")) {
stuff_vec3d(&sip->convergence_offset);
sip->aiming_flags.set(Ship::Aiming_Flags::Convergence_offset, !IS_VEC_NULL(&sip->convergence_offset));
}
}
// get ship parameters for warpin and warpout
// Note: if the index is not -1, we must have already assigned warp parameters, probably because we are now
// parsing a TBM. In that case, inherit from ourselves.
sip->warpin_params_index = parse_warp_params(sip->warpin_params_index >= 0 ? &Warp_params[sip->warpin_params_index] : nullptr, WarpDirection::WARP_IN, info_type_name, sip->name);
sip->warpout_params_index = parse_warp_params(sip->warpout_params_index >= 0 ? &Warp_params[sip->warpout_params_index] : nullptr, WarpDirection::WARP_OUT, info_type_name, sip->name);
// get ship explosion info
shockwave_create_info *sci = &sip->shockwave;
if(optional_string("$Expl inner rad:")){
stuff_float(&sci->inner_rad);
}
if(optional_string("$Expl outer rad:")){
stuff_float(&sci->outer_rad);
}
if(optional_string("$Expl damage:")){
stuff_float(&sci->damage);
}
if(optional_string("$Expl blast:")){
stuff_float(&sci->blast);
}
if(optional_string("$Expl Propagates:")){
stuff_boolean(&sip->explosion_propagates);
}
if(optional_string("$Expl Splits Ship:")){
stuff_boolean(&sip->explosion_splits_ship);
}
else if (first_time) {
sip->explosion_splits_ship = sip->explosion_propagates == 1;
}
if(optional_string("$Propagating Expl Radius Multiplier:")){
stuff_float(&sip->prop_exp_rad_mult);
if(sip->prop_exp_rad_mult <= 0) {
// on invalid value return to default setting
Warning(LOCATION, "Propagating explosion radius multiplier was set to non-positive value.\nDefaulting multiplier to 1.0 on %s '%s'.\n", info_type_name, sip->name);
sip->prop_exp_rad_mult = 1.0f;
}
}
if(optional_string("$Expl Visual Rad:")){
stuff_float(&sip->big_exp_visual_rad);
}
if(optional_string("$Base Death-Roll Time:")){
stuff_int(&sip->death_roll_base_time);
if (sip->death_roll_base_time < 2)
sip->death_roll_base_time = 2;
}
if(optional_string("$Death-Roll Explosion Radius Mult:")){
stuff_float(&sip->death_roll_r_mult);
if (sip->death_roll_r_mult < 0)
sip->death_roll_r_mult = 0;
}
if(optional_string("$Death-Roll Explosion Intensity Mult:")){
stuff_float(&sip->death_roll_time_mult);
if (sip->death_roll_time_mult <= 0)
sip->death_roll_time_mult = 1.0f;
}
if(optional_string("$Death FX Explosion Radius Mult:")){
stuff_float(&sip->death_fx_r_mult);
if (sip->death_fx_r_mult < 0)
sip->death_fx_r_mult = 0;
}
if(optional_string("$Death FX Explosion Count:")){
stuff_int(&sip->death_fx_count);
if (sip->death_fx_count < 0)
sip->death_fx_count = 0;
}
if (optional_string("$Death Roll Rotation Multiplier:")) {
stuff_float(&sip->death_roll_rotation_mult);
}
if(optional_string("$Death Roll X rotation Cap:")){
stuff_float(&sip->death_roll_xrotation_cap);
if (sip->death_roll_xrotation_cap < 0.0)
sip->death_roll_xrotation_cap = 0.0;
}
if(optional_string("$Death Roll Y rotation Cap:")){
stuff_float(&sip->death_roll_yrotation_cap);
if (sip->death_roll_yrotation_cap < 0.0)
sip->death_roll_yrotation_cap = 0.0;
}
if(optional_string("$Death Roll Z rotation Cap:")){
stuff_float(&sip->death_roll_zrotation_cap);
if (sip->death_roll_zrotation_cap < 0.0)
sip->death_roll_zrotation_cap = 0.0;
}
if(optional_string("$Ship Splitting Particles:"))
{
parse_ship_particle_effect(sip, &sip->split_particles, "ship split spew");
}
if (optional_string("$Ship Death Effect:"))
{
sip->death_effect = particle::util::parseEffect(sip->name);
}
else if(optional_string("$Ship Death Particles:"))
{
parse_ship_particle_effect(sip, &sip->regular_end_particles, "normal death spew");
}
if(optional_string("$Alternate Death Particles:"))
{
parse_ship_particle_effect(sip, &sip->knossos_end_particles, "knossos death spew");
}
if(optional_string("$Vaporize Percent Chance:")){
stuff_float(&sip->vaporize_chance);
if (sip->vaporize_chance < 0.0f || sip->vaporize_chance > 100.0f) {
sip->vaporize_chance = 0.0f;
Warning(LOCATION, "$Vaporize Percent Chance should be between 0 and 100.0 (read %f). Setting to 0.", sip->vaporize_chance);
}
//Percent is nice for modders, but here in the code we want it betwwen 0 and 1.0
sip->vaporize_chance /= 100.0;
}
if(optional_string("$Shockwave Damage Type:")) {
stuff_string(buf, F_NAME, SHIP_MULTITEXT_LENGTH);
sci->damage_type_idx_sav = damage_type_add(buf);
sci->damage_type_idx = sci->damage_type_idx_sav;
}
if(optional_string("$Shockwave Speed:")){
stuff_float( &sci->speed );
}
if(optional_string("$Shockwave Count:")){
stuff_int(&sip->shockwave_count);
}
if(optional_string("$Shockwave model:")){
stuff_string( sci->pof_name, F_NAME, MAX_FILENAME_LEN);
}
if(optional_string("$Shockwave name:")) {
stuff_string( sci->name, F_NAME, NAME_LENGTH);
}
if(optional_string("$Explosion Animations:")){
int temp[MAX_FIREBALL_TYPES];
auto parsed_ints = stuff_int_list(temp, MAX_FIREBALL_TYPES, RAW_INTEGER_TYPE);
sip->explosion_bitmap_anims.clear();
sip->explosion_bitmap_anims.insert(sip->explosion_bitmap_anims.begin(), temp, temp+parsed_ints);
}
if (optional_string("$Weapon Model Draw Distance:")) {
stuff_float( &sip->weapon_model_draw_distance );
}
// Set the weapons filter used in weapons loadout (for primary weapons)
parse_allowed_weapons(sip, true, false, first_time);
parse_allowed_weapons(sip, true, true, first_time);
// Get primary bank weapons
parse_weapon_bank(sip, true, &sip->num_primary_banks, sip->primary_bank_weapons, sip->primary_bank_ammo_capacity);
if(optional_string("$Show Primary Models:"))
{
sip->flags.set(Ship::Info_Flags::Draw_weapon_models);
stuff_bool_list(sip->draw_primary_models, sip->num_primary_banks);
}
// Set the weapons filter used in weapons loadout (for secondary weapons)
parse_allowed_weapons(sip, false, false, first_time);
parse_allowed_weapons(sip, false, true, first_time);
// Get secondary bank weapons
parse_weapon_bank(sip, false, &sip->num_secondary_banks, sip->secondary_bank_weapons, sip->secondary_bank_ammo_capacity);
if(optional_string("$Show Secondary Models:"))
{
sip->flags.set(Ship::Info_Flags::Draw_weapon_models);
stuff_bool_list(sip->draw_secondary_models, sip->num_secondary_banks);
}
if (optional_string("$Ship Recoil Modifier:")){
stuff_float(&sip->ship_recoil_modifier);
}
if(optional_string("$Shields:")) {
stuff_float(&sip->max_shield_strength);
if(optional_string("+Auto Spread:")) {
stuff_float(&sip->auto_shield_spread);
}
if(optional_string("+Minimum Weapon Span:")) {
stuff_float(&sip->auto_shield_spread_min_span);
}
if(optional_string("+Allow Bypass:")) {
stuff_boolean(&sip->auto_shield_spread_bypass);
}
if(optional_string("+Spread From LOD:")) {
int temp;
stuff_int(&temp);
if (temp > sip->num_detail_levels)
Warning(LOCATION, "+Spread From LOD for %s was %i whereas ship only has %i detail levels, ignoring...", sip->name, temp, sip->num_detail_levels);
else
sip->auto_shield_spread_from_lod = temp;
}
}
if(optional_string("$Model Point Shield Controls:")) {
SCP_vector<SCP_string> ctrl_strings;
stuff_string_list(ctrl_strings);
// Init all to -1 in case some aren't supplied...
sip->shield_point_augment_ctrls[FRONT_QUAD] = -1;
sip->shield_point_augment_ctrls[REAR_QUAD] = -1;
sip->shield_point_augment_ctrls[LEFT_QUAD] = -1;
sip->shield_point_augment_ctrls[RIGHT_QUAD] = -1;
for (int i = 0; i < (int)ctrl_strings.size(); i++) {
const char *str = ctrl_strings[i].c_str();
if (!stricmp(str, "front"))
sip->shield_point_augment_ctrls[FRONT_QUAD] = i;
else if (!stricmp(str, "rear"))
sip->shield_point_augment_ctrls[REAR_QUAD] = i;
else if (!stricmp(str, "left"))
sip->shield_point_augment_ctrls[LEFT_QUAD] = i;
else if (!stricmp(str, "right"))
sip->shield_point_augment_ctrls[RIGHT_QUAD] = i;
else if (!stricmp(str, "none"))
;
else
Warning(LOCATION, "Unrecognized value \"%s\" passed to $Model Point Shield Controls, ignoring...", str);
}
}
// optional shield color
if(optional_string("$Shield Color:")){
stuff_ubyte(&sip->shield_color[0]);
stuff_ubyte(&sip->shield_color[1]);
stuff_ubyte(&sip->shield_color[2]);
}
if(optional_string("$Shield Impact Explosion:")) {
char fname[MAX_NAME_LEN];
stuff_string(fname, F_NAME, NAME_LENGTH);
if ( VALID_FNAME(fname) )
sip->shield_impact_explosion_anim = Weapon_explosions.Load(fname);
}
if(optional_string("$Max Shield Recharge:")){
stuff_float(&sip->max_shield_recharge);
CLAMP(sip->max_shield_recharge, 0.0f, 1.0f);
}
// The next five fields are used for the ETS
if (optional_string("$Power Output:"))
stuff_float(&sip->power_output);
// Goober5000
if (optional_string("$Shield Regeneration Rate:"))
stuff_float(&sip->max_shield_regen_per_second);
else if (first_time)
sip->max_shield_regen_per_second = 0.02f;
if (optional_string("+Shield Regen Hit Delay:")) {
stuff_float(&sip->shield_regen_hit_delay);
if (sip->shield_regen_hit_delay < 0.0f) {
Warning(LOCATION, "Shield Regen Hit Delay on ship '%s' cannot be less than 0.\n", sip->name);
sip->shield_regen_hit_delay = 0.0f;
}
}
// Support ship hull shield rate - if allowed
if(optional_string("$Support Shield Repair Rate:"))
{
stuff_float(&sip->sup_shield_repair_rate);
sip->sup_shield_repair_rate *= 0.01f;
CLAMP(sip->sup_shield_repair_rate, 0.0f, 1.0f);
}
// Goober5000
if (optional_string("$Weapon Regeneration Rate:"))
stuff_float(&sip->max_weapon_regen_per_second);
else if (first_time)
sip->max_weapon_regen_per_second = 0.04f;
if (optional_string("$Shield to Weapon Transfer Quantity:"))
stuff_float(&sip->shield_weap_amount);
else if (first_time)
sip->shield_weap_amount = 0.2f;
if (optional_string("$Shield to Weapon Transfer Efficiency:"))
stuff_float(&sip->shield_weap_efficiency);
else if (first_time)
sip->shield_weap_efficiency = 1.0f;
if (optional_string("$Shield to Weapon Transfer Speed:"))
stuff_float(&sip->shield_weap_speed);
else if (first_time)
sip->shield_weap_speed = 0.04f;
if (optional_string("$Weapon to Shield Transfer Quantity:"))
stuff_float(&sip->weap_shield_amount);
else if (first_time)
sip->weap_shield_amount = 0.1f;
if (optional_string("$Weapon to Shield Transfer Efficiency:"))
stuff_float(&sip->weap_shield_efficiency);
else if (first_time)
sip->weap_shield_efficiency = 1.0f;
if (optional_string("$Weapon to Shield Transfer Speed:"))
stuff_float(&sip->weap_shield_speed);
else if (first_time)
sip->weap_shield_speed = 0.04f;
if (optional_string("$Max Oclk Speed:") || optional_string("$Max Overclock Speed:"))
stuff_float(&sip->max_overclocked_speed);
else if (first_time)
sip->max_overclocked_speed = sip->max_vel.xyz.z * 1.5f;
if (optional_string("$Max Weapon Eng:") || optional_string("$Max Weapon Energy:"))
stuff_float(&sip->max_weapon_reserve);
if(optional_string("$Hitpoints:"))
{
stuff_float(&sip->max_hull_strength);
if (sip->max_hull_strength < 0.0f)
{
Warning(LOCATION, "Max hull strength on %s '%s' cannot be less than 0. Defaulting to 100.\n", info_type_name, sip->name);
sip->max_hull_strength = 100.0f;
}
}
//Hull rep rate
if(optional_string("$Hull Repair Rate:"))
{
stuff_float(&sip->hull_repair_rate);
sip->hull_repair_rate *= 0.01f;
//Sanity checking
if(sip->hull_repair_rate > 1.0f)
sip->hull_repair_rate = 1.0f;
else if(sip->hull_repair_rate < -1.0f)
sip->hull_repair_rate = -1.0f;
}
// Support ship hull repair rate - if allowed
if(optional_string("$Support Hull Repair Rate:"))
{
stuff_float(&sip->sup_hull_repair_rate);
sip->sup_hull_repair_rate *= 0.01f;
CLAMP(sip->sup_hull_repair_rate, 0.0f, 1.0f);
}
//Subsys rep rate
if(optional_string("$Subsystem Repair Rate:"))
{
stuff_float(&sip->subsys_repair_rate);
sip->subsys_repair_rate *= 0.01f;
//Sanity checking
if(sip->subsys_repair_rate > 1.0f)
sip->subsys_repair_rate = 1.0f;
else if(sip->subsys_repair_rate < -1.0f)
sip->subsys_repair_rate = -1.0f;
}
// Support ship hull repair rate
if(optional_string("$Support Subsystem Repair Rate:"))
{
stuff_float(&sip->sup_subsys_repair_rate);
sip->sup_subsys_repair_rate *= 0.01f;
CLAMP(sip->sup_subsys_repair_rate, 0.0f, 1.0f);
}
if(optional_string("$Armor Type:"))
{
stuff_string(buf, F_NAME, SHIP_MULTITEXT_LENGTH);
sip->armor_type_idx = armor_type_get_idx(buf);
if(sip->armor_type_idx == -1)
Warning(LOCATION,"Invalid armor name %s specified for hull in %s '%s'", buf, info_type_name, sip->name);
}
if(optional_string("$Shield Armor Type:"))
{
stuff_string(buf, F_NAME, SHIP_MULTITEXT_LENGTH);
sip->shield_armor_type_idx = armor_type_get_idx(buf);
if(sip->shield_armor_type_idx == -1)
Warning(LOCATION,"Invalid armor name %s specified for shield in %s '%s'", buf, info_type_name, sip->name);
}
if (optional_string("$Flags:"))
{
// we'll assume the list will contain no more than 20 distinct tokens
char ship_strings[20][NAME_LENGTH];
int num_strings = (int)stuff_string_list(ship_strings, 20);
int ship_type_index = -1;
if (!optional_string("+noreplace")) {
// clear flags since we might have a modular table
// clear only those which are actually set in the flags
bool has_afterburner = sip->flags[Ship::Info_Flags::Afterburner];
bool draw_weapon_models = sip->flags[Ship::Info_Flags::Draw_weapon_models];
bool has_display_name = sip->flags[Ship::Info_Flags::Has_display_name];
sip->flags.reset();
sip->flags.set(Ship::Info_Flags::Afterburner, has_afterburner);
sip->flags.set(Ship::Info_Flags::Draw_weapon_models, draw_weapon_models);
sip->flags.set(Ship::Info_Flags::Has_display_name, has_display_name);
}
for (auto i = 0; i < num_strings; i++)
{
// get ship type from ship flags
const char *ship_type = ship_strings[i];
bool flag_found = false;
// look it up in the object types table
ship_type_index = ship_type_name_lookup(ship_type);
// set ship class type
if ((ship_type_index >= 0) && (sip->class_type < 0))
sip->class_type = ship_type_index;
// check various ship flags
for (size_t idx = 0; idx < Num_ship_flags; idx++) {
if ( !stricmp(Ship_flags[idx].name, ship_strings[i]) ) {
flag_found = true;
if (!Ship_flags[idx].in_use)
Warning(LOCATION, "Use of '%s' flag for %s '%s' - this flag is no longer needed.", Ship_flags[idx].name, info_type_name, sip->name);
else
sip->flags.set(Ship_flags[idx].def);
break;
}
}
// catch typos or deprecations
if (!stricmp(ship_strings[i], "no-collide") || !stricmp(ship_strings[i], "no_collide")) {
flag_found = true;
sip->flags.set(Ship::Info_Flags::No_collide);
}
if (!stricmp(ship_strings[i], "dont collide invisible")) {
flag_found = true;
sip->flags.set(Ship::Info_Flags::Ship_class_dont_collide_invis);
}
if (!stricmp(ship_strings[i], "dont bank when turning")) {
flag_found = true;
sip->flags.set(Ship::Info_Flags::Dont_bank_when_turning);
}
if (!stricmp(ship_strings[i], "dont clamp max velocity")) {
flag_found = true;
sip->flags.set(Ship::Info_Flags::Dont_clamp_max_velocity);
}
if ( !flag_found && (ship_type_index < 0) )
Warning(LOCATION, "Bogus string in ship flags: %s\n", ship_strings[i]);
}
// set original status of tech database flags - Goober5000
if (sip->flags[Info_Flags::In_tech_database])
sip->flags.set(Info_Flags::Default_in_tech_database);
if (sip->flags[Info_Flags::In_tech_database_m])
sip->flags.set(Info_Flags::Default_in_tech_database_m);
}
// Goober5000 - ensure number of banks checks out
if (sip->num_primary_banks > MAX_SHIP_PRIMARY_BANKS)
{
Error(LOCATION, "%s '%s' has too many primary banks (%d). Maximum for ships is currently %d.\n", info_type_name, sip->name, sip->num_primary_banks, MAX_SHIP_PRIMARY_BANKS);
}
sip->allowed_weapons.clear();
// copy to regular allowed_weapons array
if (!sip->allowed_bank_restricted_weapons.empty())
{
for (auto i = 0; i < MAX_SHIP_WEAPONS; i++)
{
for (auto &wf : sip->allowed_bank_restricted_weapons[i].weapon_and_flags)
{
if (wf.second & REGULAR_WEAPON)
sip->allowed_weapons.set_flag(wf.first, REGULAR_WEAPON);
if (wf.second & DOGFIGHT_WEAPON)
sip->allowed_weapons.set_flag(wf.first, DOGFIGHT_WEAPON);
}
}
}
find_and_stuff_optional("$AI Class:", &sip->ai_class, F_NAME, Ai_class_names, Num_ai_classes, "AI class names");
// Get Afterburner information
// Be aware that if $Afterburner is not 1, the other Afterburner fields are not read in
int has_afterburner = 0;
if(optional_string("$Afterburner:"))
stuff_boolean(&has_afterburner);
if ( has_afterburner == 1 )
{
sip->flags.set(Ship::Info_Flags::Afterburner);
if(optional_string("+Aburn Max Vel:")) {
stuff_vec3d(&sip->afterburner_max_vel);
}
if(optional_string("+Aburn For accel:")) {
stuff_float(&sip->afterburner_forward_accel);
}
// SparK: added reverse burner capability
if(optional_string("+Aburn Max Reverse Vel:")) {
stuff_float(&sip->afterburner_max_reverse_vel);
}
if(optional_string("+Aburn Rev accel:")) {
stuff_float(&sip->afterburner_reverse_accel);
}
if(optional_string("+Aburn Fuel:")) {
stuff_float(&sip->afterburner_fuel_capacity);
}
if(optional_string("+Aburn Burn Rate:")) {
stuff_float(&sip->afterburner_burn_rate);
}
if(optional_string("+Aburn Rec Rate:")) {
stuff_float(&sip->afterburner_recover_rate);
}
if (optional_string("+Aburn Minimum Start Fuel:")) {
stuff_float(&sip->afterburner_min_start_fuel);
}
if (optional_string("+Aburn Minimum Fuel to Burn:")) {
stuff_float(&sip->afterburner_min_fuel_to_burn);
}
if (optional_string("+Aburn Cooldown Time:")) {
stuff_float(&sip->afterburner_cooldown_time);
}
if (!(sip->afterburner_fuel_capacity) ) {
Warning(LOCATION, "%s '%s' has an afterburner but has no afterburner fuel. Setting fuel to 1", info_type_name, sip->name);
sip->afterburner_fuel_capacity = 1.0f;
}
}
if ( optional_string("$Trails:") ) {
bool trails_warning = true;
if (optional_string("+Bitmap:") ) {
trails_warning = false;
generic_bitmap_init(&sip->afterburner_trail, NULL);
stuff_string(sip->afterburner_trail.filename, F_NAME, MAX_FILENAME_LEN);
}
if (optional_string("+Bitmap Stretch:")) {
trails_warning = false;
stuff_float(&sip->afterburner_trail_tex_stretch);
if (sip->afterburner_trail_tex_stretch == 0.0f) {
Warning(LOCATION, "Trail bitmap stretch of ship %s cannot be 0. Setting to 1.\n", sip->name);
sip->afterburner_trail_tex_stretch = 1.0f;
}
}
if ( optional_string("+Width:") ) {
trails_warning = false;
stuff_float(&sip->afterburner_trail_width_factor);
}
if ( optional_string("+Alpha:") ) {
trails_warning = false;
stuff_float(&sip->afterburner_trail_alpha_factor);
}
if (optional_string("+Alpha End:")) {
trails_warning = false;
stuff_float(&sip->afterburner_trail_alpha_end_factor);
}
if (optional_string("+Alpha Decay Exponent:")) {
trails_warning = false;
stuff_float(&sip->afterburner_trail_alpha_decay_exponent);
if (sip->afterburner_trail_alpha_decay_exponent < 0.0f) {
Warning(LOCATION, "Trail Alpha Decay Exponent of ship %s cannot be negative. Resetting to 1.\n", sip->name);
sip->afterburner_trail_alpha_decay_exponent = 1.0f;
}
}
if ( optional_string("+Life:") ) {
trails_warning = false;
stuff_float(&sip->afterburner_trail_life);
}
if (optional_string("+Spread:")) {
trails_warning = false;
stuff_float(&sip->afterburner_trail_spread);
}
if ( optional_string("+Faded Out Sections:") ) {
trails_warning = false;
stuff_int(&sip->afterburner_trail_faded_out_sections);
}
if (trails_warning)
Warning(LOCATION, "%s %s entry has $Trails field specified, but no properties given.", info_type_name, sip->name);
}
if (optional_string("$Countermeasure type:")) {
stuff_string(buf, F_NAME, SHIP_MULTITEXT_LENGTH);
int res = weapon_info_lookup(buf);
if (res < 0) {
Warning(LOCATION, "Could not find weapon type '%s' to use as countermeasure on %s '%s'", buf, info_type_name, sip->name);
} else if (Weapon_info[res].wi_flags[Weapon::Info_Flags::Beam]) {
Warning(LOCATION, "Attempt made to set a beam weapon as a countermeasure on %s '%s'", info_type_name, sip->name);
} else {
sip->cmeasure_type = res;
}
} else if (first_time && Species_info[sip->species].cmeasure_index >= 0) {
sip->cmeasure_type = Species_info[sip->species].cmeasure_index;
}
if(optional_string("$Countermeasures:"))
stuff_int(&sip->cmeasure_max);
if(optional_string("$Scan time:"))
stuff_int(&sip->scan_time);
if(optional_string("$Scan range Normal:"))
stuff_float(&sip->scan_range_normal);
if(optional_string("$Scan range Capital:"))
stuff_float(&sip->scan_range_capital);
if (optional_string("$Ask Help Shield Percent:")) {
float help_shield_val;
stuff_float(&help_shield_val);
if (help_shield_val > 0.0f && help_shield_val <= 1.0f) {
sip->ask_help_shield_percent = help_shield_val;
} else {
error_display(0,"Ask Help Shield Percent for ship class %s is %f. This value is not within range of 0-1.0."
"Assuming default value of %f.", sip->name, help_shield_val, DEFAULT_ASK_HELP_SHIELD_PERCENT);
}
}
if (optional_string("$Ask Help Hull Percent:")) {
float help_hull_val;
stuff_float(&help_hull_val);
if (help_hull_val > 0.0f && help_hull_val <= 1.0f) {
sip->ask_help_shield_percent = help_hull_val;
} else {
error_display(0,"Ask Help Hull Percent for ship class %s is %f. This value is not within range of 0-1.0."
"Assuming default value of %f.", sip->name, help_hull_val, DEFAULT_ASK_HELP_HULL_PERCENT);
}
}
//Parse the engine sound
parse_game_sound("$EngineSnd:", &sip->engine_snd);
if(optional_string("$Minimum Engine Volume:"))
stuff_float(&sip->min_engine_vol);
//Parse optional sound to be used for beginning of a glide
parse_game_sound("$GlideStartSnd:", &sip->glide_start_snd);
//Parse optional sound to be used for end of a glide
parse_game_sound("$GlideEndSnd:", &sip->glide_end_snd);
// Parse optional sound to be used for bfly-by sound
parse_game_sound("$Flyby Sound:", &sip->flyby_snd);
parse_ship_sounds(sip);
if(optional_string("$Closeup_pos:"))
{
stuff_vec3d(&sip->closeup_pos);
}
else if (first_time && strlen(sip->pof_file))
{
//Calculate from the model file. This is inefficient, but whatever
int model_idx = model_load(sip->pof_file, 0, NULL);
polymodel *pm = model_get(model_idx);
//Go through, find best
sip->closeup_pos.xyz.z = fabsf(pm->maxs.xyz.z);
float temp = fabsf(pm->mins.xyz.z);
if(temp > sip->closeup_pos.xyz.z)
sip->closeup_pos.xyz.z = temp;
//Now multiply by 2
sip->closeup_pos.xyz.z *= -2.0f;
//We're done with the model.
model_unload(model_idx);
}
if (optional_string("$Closeup_zoom:")) {
stuff_float(&sip->closeup_zoom);
if (sip->closeup_zoom <= 0.0f) {
mprintf(("Warning! Ship '%s' has a $Closeup_zoom value that is less than or equal to 0 (%f). Setting to default value.\n", sip->name, sip->closeup_zoom));
sip->closeup_zoom = 0.5f;
}
}
if(optional_string("$Closeup_pos_targetbox:"))
{
stuff_vec3d(&sip->closeup_pos_targetbox);
}
else if (first_time)
{
sip->closeup_pos_targetbox = sip->closeup_pos;
}
if (optional_string("$Closeup_zoom_targetbox:")) {
stuff_float(&sip->closeup_zoom_targetbox);
if (sip->closeup_zoom_targetbox <= 0.0f) {
mprintf(("Warning! Ship '%s' has a $Closeup_zoom_targetbox value that is less than or equal to 0 (%f). Setting to default value.\n", sip->name, sip->closeup_zoom_targetbox));
sip->closeup_zoom_targetbox = 0.5f;
}
}
else if (first_time)
{
sip->closeup_zoom_targetbox = sip->closeup_zoom;
}
if(optional_string("$Topdown offset:")) {
sip->topdown_offset_def = true;
stuff_vec3d(&sip->topdown_offset);
}
if (optional_string("$Chase View Offset:")) {
stuff_vec3d(&sip->chase_view_offset);
}
if (optional_string("$Chase View Rigidity:")) {
stuff_float(&sip->chase_view_rigidity);
if (sip->chase_view_rigidity <= 0) {
Warning(LOCATION, "Ship \'%s\' must have a Chase View Rigidity greater than 0.", sip->name);
sip->chase_view_rigidity = 5.0f;
}
}
if (optional_string("$Shield_icon:")) {
stuff_string(name_tmp, F_NAME, sizeof(name_tmp));
hud_shield_assign_info(sip, name_tmp);
}
// read in filename for icon that is used in ship selection
if ( optional_string("$Ship_icon:") ) {
stuff_string(sip->icon_filename, F_NAME, MAX_FILENAME_LEN);
}
if ( optional_string("$Model Icon Direction:") ) {
char str[NAME_LENGTH];
stuff_string(str, F_NAME, NAME_LENGTH);
angles model_icon_angles = vmd_zero_angles;
if (!stricmp(str, "top")) {
model_icon_angles.p = -PI_2;
} else if (!stricmp(str, "bottom")) {
model_icon_angles.p = -PI_2;
model_icon_angles.b = 2 * PI_2;
} else if (!stricmp(str, "front")) {
model_icon_angles.h = 2 * PI_2;
} else if (!stricmp(str, "back")) {
model_icon_angles.h = 4 * PI_2;
} else if (!stricmp(str, "left")) {
model_icon_angles.h = -PI_2;
} else if (!stricmp(str, "right")) {
model_icon_angles.h = PI_2;
} else {
Warning(LOCATION, "Unrecognized value \"%s\" passed to $Model Icon Direction, ignoring...", str);
}
sip->model_icon_angles = model_icon_angles;
}
// read in filename for animation that is used in ship selection
if ( optional_string("$Ship_anim:") ) {
stuff_string(sip->anim_filename, F_NAME, MAX_FILENAME_LEN);
}
// read in filename for animation that is used in ship selection
if ( optional_string("$Ship_overhead:") ) {
stuff_string(sip->overhead_filename, F_NAME, MAX_FILENAME_LEN);
}
// read in filename for optional animation to use for wingmen dot status --wookieejedi
if (optional_string("$Wingmen Gauge Dot Override:")) {
stuff_string(name_tmp, F_NAME, sizeof(name_tmp));
int wm_dot_idx = -1;
int wm_dot_num_frames = 0;
wm_dot_idx = bm_load_animation(name_tmp, &wm_dot_num_frames);
if ((wm_dot_idx > -1) && (wm_dot_num_frames == 2)) {
sip->wingmen_status_dot_override = wm_dot_idx;
} else {
// two things could have gone wrong, so tell the modder
if (wm_dot_idx < 0) {
Warning(LOCATION, "Error loading '%s' animation for $Wingmen Gauge Dot Override, "
"ignoring and using default dot animation from HUD table.", name_tmp);
}
if (wm_dot_num_frames != 2) {
Warning(LOCATION, "Error, number of frames in '%s' animation for $Wingmen Gauge Dot Override is %i and not 2, "
"ignoring and using default dot animation from HUD table.", name_tmp, wm_dot_num_frames);
}
}
}
// read in briefing stuff
if ( optional_string("$Briefing icon:") )
sip->bii_index_ship = parse_and_add_briefing_icon_info();
if ( optional_string("$Briefing icon with cargo:") )
sip->bii_index_ship_with_cargo = parse_and_add_briefing_icon_info();
if ( optional_string("$Briefing wing icon:") )
sip->bii_index_wing = parse_and_add_briefing_icon_info();
if ( optional_string("$Briefing wing icon with cargo:") )
sip->bii_index_wing_with_cargo = parse_and_add_briefing_icon_info();
// check for inconsistencies
if ((sip->bii_index_wing_with_cargo >= 0) && (sip->bii_index_wing < 0 || sip->bii_index_ship_with_cargo < 0))
Warning(LOCATION, "%s '%s' has a wing-with-cargo briefing icon but is missing a wing briefing icon or a ship-with-cargo briefing icon!", info_type_name, sip->name);
if ((sip->bii_index_wing_with_cargo < 0) && (sip->bii_index_wing >= 0) && (sip->bii_index_ship_with_cargo >= 0))
Warning(LOCATION, "%s '%s' has both a wing briefing icon and a ship-with-cargo briefing icon but does not have a wing-with-cargo briefing icon!", info_type_name, sip->name);
if ( optional_string("$Score:") ){
stuff_int( &sip->score );
}
if (first_time)
{
species_info *species = &Species_info[sip->species];
sip->thruster_flame_info = species->thruster_info.flames;
sip->thruster_glow_info = species->thruster_info.glow;
sip->thruster_secondary_glow_info = species->thruster_secondary_glow_info;
sip->thruster_tertiary_glow_info = species->thruster_tertiary_glow_info;
sip->thruster_distortion_info = species->thruster_distortion_info;
}
if ( optional_string("$Thruster Normal Flame:") ) {
stuff_string( name_tmp, F_NAME, sizeof(name_tmp) );
if ( VALID_FNAME(name_tmp) )
generic_anim_init( &sip->thruster_flame_info.normal, name_tmp );
}
if ( optional_string("$Thruster Afterburner Flame:") ) {
stuff_string( name_tmp, F_NAME, sizeof(name_tmp) );
if ( VALID_FNAME(name_tmp) )
generic_anim_init( &sip->thruster_flame_info.afterburn, name_tmp );
}
if ( optional_string("$Thruster Bitmap 1:") ) {
stuff_string( name_tmp, F_NAME, sizeof(name_tmp) );
if ( VALID_FNAME(name_tmp) ) {
strcpy_s(sip->thruster_glow_info.normal.filename, name_tmp);
thruster_glow_anim_load( &sip->thruster_glow_info.normal );
}
}
if ( optional_string("$Thruster Bitmap 1a:") ) {
stuff_string( name_tmp, F_NAME, sizeof(name_tmp) );
if ( VALID_FNAME(name_tmp) ) {
strcpy_s(sip->thruster_glow_info.afterburn.filename, name_tmp);
thruster_glow_anim_load( &sip->thruster_glow_info.afterburn );
}
}
if ( optional_string("$Thruster01 Radius factor:") ) {
stuff_float(&sip->thruster01_glow_rad_factor);
}
if ( optional_string("$Thruster Bitmap 2:") ) {
stuff_string( name_tmp, F_NAME, sizeof(name_tmp) );
if ( VALID_FNAME(name_tmp) )
generic_bitmap_init( &sip->thruster_secondary_glow_info.normal, name_tmp );
}
if ( optional_string("$Thruster Bitmap 2a:") ) {
stuff_string( name_tmp, F_NAME, sizeof(name_tmp) );
if ( VALID_FNAME(name_tmp) )
generic_bitmap_init( &sip->thruster_secondary_glow_info.afterburn, name_tmp );
}
if ( optional_string("$Thruster02 Radius factor:") ) {
stuff_float(&sip->thruster02_glow_rad_factor);
}
if ( optional_string("$Thruster01 Length factor:") ) {
stuff_float(&sip->thruster02_glow_len_factor);
Warning(LOCATION, "Deprecated spelling: \"$Thruster01 Length factor:\". Use \"$Thruster02 Length factor:\" instead.");
}
if ( optional_string("$Thruster02 Length factor:") ) {
stuff_float(&sip->thruster02_glow_len_factor);
}
if ( optional_string("$Thruster Bitmap 3:") ) {
stuff_string( name_tmp, F_NAME, sizeof(name_tmp) );
if ( VALID_FNAME(name_tmp) )
generic_bitmap_init( &sip->thruster_tertiary_glow_info.normal, name_tmp );
}
if ( optional_string("$Thruster Bitmap 3a:") ) {
stuff_string( name_tmp, F_NAME, sizeof(name_tmp) );
if ( VALID_FNAME(name_tmp) )
generic_bitmap_init( &sip->thruster_tertiary_glow_info.afterburn, name_tmp );
}
if ( optional_string("$Thruster03 Radius factor:") ) {
stuff_float(&sip->thruster03_glow_rad_factor);
}
// Valathil - Custom Thruster Distortion
if ( optional_string("$Thruster Bitmap Distortion:") ) {
stuff_string( name_tmp, F_NAME, sizeof(name_tmp) );
if ( VALID_FNAME(name_tmp) )
generic_bitmap_init( &sip->thruster_distortion_info.normal, name_tmp );
}
if ( optional_string("$Thruster Bitmap Distortion a:") ) {
stuff_string( name_tmp, F_NAME, sizeof(name_tmp) );
if ( VALID_FNAME(name_tmp) )
generic_bitmap_init( &sip->thruster_distortion_info.afterburn, name_tmp );
}
if ( optional_string("$Thruster Distortion Radius factor:") ) {
stuff_float(&sip->thruster_dist_rad_factor);
}
if ( optional_string("$Thruster Distortion Length factor:") ) {
stuff_float(&sip->thruster_dist_len_factor);
}
if ( optional_string("$Thruster Distortion:") ) {
stuff_boolean(&sip->draw_distortion);
}
if ( optional_string("$Thruster Glow Noise Mult:") ) {
stuff_float(&sip->thruster_glow_noise_mult);
}
while ( optional_string("$Thruster Particles:") ) {
bool afterburner = false;
thruster_particles tpart;
if ( optional_string("$Thruster Particle Bitmap:") )
afterburner = false;
else if ( optional_string("$Afterburner Particle Bitmap:") )
afterburner = true;
else
Error( LOCATION, "formatting error in the thruster's particle section for %s '%s'\n", info_type_name, sip->name );
generic_anim_init(&tpart.thruster_bitmap, NULL);
stuff_string(tpart.thruster_bitmap.filename, F_NAME, MAX_FILENAME_LEN);
required_string("$Min Radius:");
stuff_float(&tpart.min_rad);
required_string("$Max Radius:");
stuff_float(&tpart.max_rad);
required_string("$Min created:");
stuff_int(&tpart.n_low);
required_string("$Max created:");
stuff_int(&tpart.n_high);
required_string("$Variance:");
stuff_float(&tpart.variance);
if (afterburner) {
sip->afterburner_thruster_particles.push_back( tpart );
} else {
sip->normal_thruster_particles.push_back( tpart );
}
}
// if the ship is a stealth ship
if ( optional_string("$Stealth:") ) {
sip->flags.set(Ship::Info_Flags::Stealth);
}
else if ( optional_string("$Stealth") ) {
Warning(LOCATION, "%s '%s' is missing the colon after \"$Stealth\". Note that you may also use the ship flag \"stealth\".", info_type_name, sip->name);
sip->flags.set(Ship::Info_Flags::Stealth);
}
if ( optional_string("$max decals:") ){
int bogus;
stuff_int(&bogus);
WarningEx(LOCATION, "The decal system has been deactivated in FSO builds. Entries will be discarded.\n");
mprintf(("WARNING: The decal system has been deactivated in FSO builds. Entries will be discarded.\n"));
//Do nothing, left in for compatibility.
}
// parse contrail info
while ( optional_string("$Trail:") ) {
// setting '+ClearAll' resets the trails
if ( optional_string("+ClearAll")) {
memset(&sip->ct_info, 0, sizeof(trail_info) * MAX_SHIP_CONTRAILS);
sip->ct_count = 0;
}
// this means you've reached the max # of contrails for a ship
if (sip->ct_count >= MAX_SHIP_CONTRAILS) {
Warning(LOCATION, "%s '%s' has more contrails than the max of %d", info_type_name, sip->name, MAX_SHIP_CONTRAILS);
break;
}
trail_info *ci = &sip->ct_info[sip->ct_count++];
required_string("+Offset:");
stuff_vec3d(&ci->pt);
required_string("+Start Width:");
stuff_float(&ci->w_start);
required_string("+End Width:");
stuff_float(&ci->w_end);
required_string("+Start Alpha:");
stuff_float(&ci->a_start);
required_string("+End Alpha:");
stuff_float(&ci->a_end);
if (optional_string("+Alpha Decay Exponent:")) {
stuff_float(&ci->a_decay_exponent);
if (ci->a_decay_exponent < 0.0f) {
Warning(LOCATION, "Trail Alpha Decay Exponent of ship %s cannot be negative. Resetting to 1.\n", sip->name);
ci->a_decay_exponent = 1.0f;
}
}
else if (first_time)
ci->a_decay_exponent = 1.0f;
required_string("+Max Life:");
stuff_float(&ci->max_life);
if (optional_string("+Spread:"))
stuff_float(&ci->spread);
required_string("+Spew Time:");
stuff_int(&ci->stamp);
required_string("+Bitmap:");
stuff_string(name_tmp, F_NAME, NAME_LENGTH);
generic_bitmap_init(&ci->texture, name_tmp);
generic_bitmap_load(&ci->texture);
if (optional_string("+Bitmap Stretch:")) {
stuff_float(&ci->texture_stretch);
if (ci->texture_stretch == 0.0f) {
Warning(LOCATION, "Trail bitmap stretch of ship %s cannot be 0. Setting to 1.\n", sip->name);
ci->texture_stretch = 1.0f;
}
}
else if (first_time)
ci->texture_stretch = 1.0f;
if (optional_string("+Faded Out Sections:") ) {
stuff_int(&ci->n_fade_out_sections);
}
}
man_thruster *mtp = NULL;
man_thruster manwich;
while(optional_string("$Thruster:"))
{
int idx = -1;
if(optional_string("+index:")) {
stuff_int(&idx);
}
if(idx >= 0 && idx < sip->num_maneuvering) {
mtp = &sip->maneuvering[idx];
} else if(idx < 0) {
if(sip->num_maneuvering < MAX_MAN_THRUSTERS) {
mtp = &sip->maneuvering[sip->num_maneuvering++];
} else {
Warning(LOCATION, "Too many maneuvering thrusters on %s '%s'; maximum is %d", info_type_name, sip->name, MAX_MAN_THRUSTERS);
}
} else {
mtp = &manwich;
Warning(LOCATION, "Invalid index (%d) specified for maneuvering thruster on %s '%s'", idx, info_type_name, sip->name);
}
if(optional_string("+Used for:")) {
parse_string_flag_list(mtp->use_flags, Man_types, Num_man_types, NULL);
}
if(optional_string("+Position:")) {
stuff_float_list(mtp->pos.a1d, 3);
}
if(optional_string("+Normal:")) {
stuff_float_list(mtp->norm.a1d, 3);
}
if(optional_string("+Texture:"))
{
stuff_string(name_tmp, F_NAME, sizeof(name_tmp));
int tex_fps=0, tex_nframes=0, tex_id=-1;;
try {
tex_id = bm_load_animation(name_tmp, &tex_nframes, &tex_fps, nullptr, nullptr, true, CF_TYPE_ANY, true);
} catch (const std::exception& e) {
mprintf(("Could not load thruster texture %s as animation (%s). Attempting to load as static image.\n",
name_tmp,
e.what()));
tex_id = -1;
}
if(tex_id < 0)
tex_id = bm_load(name_tmp);
if(tex_id >= 0)
{
if(mtp->tex_id >= 0) {
bm_unload(mtp->tex_id);
}
mtp->tex_id = tex_id;
mtp->tex_fps = tex_fps;
mtp->tex_nframes = tex_nframes;
} else {
mprintf(("Failed to load thruster texture %s\n", name_tmp));
}
}
if(optional_string("+Radius:")) {
stuff_float(&mtp->radius);
}
if(optional_string("+Length:")) {
stuff_float(&mtp->length);
}
parse_game_sound("+StartSnd:", &mtp->start_snd);
parse_game_sound("+LoopSnd:", &mtp->loop_snd);
parse_game_sound("+StopSnd:", &mtp->stop_snd);
}
if (optional_string("$Glowpoint overrides:")) {
SCP_vector<SCP_string> tokens;
tokens.clear();
stuff_string_list(tokens);
for(SCP_vector<SCP_string>::iterator token = tokens.begin(); token != tokens.end(); ++token) {
SCP_string name, banks;
size_t seppos;
seppos = token->find_first_of(':');
if(seppos == SCP_string::npos) {
Warning(LOCATION, "Couldn't find ':' seperator in Glowpoint override for ship %s ignoring token", sip->name);
continue;
}
name = token->substr(0, seppos);
banks = token->substr(seppos+1);
SCP_vector<glow_point_bank_override>::iterator gpo = get_glowpoint_bank_override_by_name(name.data());
if(gpo == glowpoint_bank_overrides.end()){
Warning(LOCATION, "Couldn't find preset %s in glowpoints.tbl when parsing ship: %s", name.data(), sip->name);
continue;
}
if(banks == "*") {
sip->glowpoint_bank_override_map[-1] = (void*)(&(*gpo));
continue;
}
SCP_string banktoken;
size_t start = 0;
size_t end;
do {
end = banks.find_first_of(',', start);
banktoken = banks.substr(start, end);
start = end + 1;
size_t fromtopos;
fromtopos = banktoken.find_first_of('-');
if(fromtopos != SCP_string::npos) {
SCP_string from, to;
int ifrom, ito;
from = banktoken.substr(0, fromtopos);
to = banktoken.substr(fromtopos+1);
ifrom = atoi(from.data()) - 1;
ito = atoi(to.data()) - 1;
for(int bank = ifrom; bank <= ito; ++bank) {
sip->glowpoint_bank_override_map[bank] = (void*)(&(*gpo));
}
} else {
int bank = atoi(banktoken.data()) - 1;
sip->glowpoint_bank_override_map[bank] = (void*)(&(*gpo));
}
} while(end !=SCP_string::npos);
}
}
if (optional_string("$Radar Image 2D:"))
{
stuff_string(name_tmp, F_NAME, NAME_LENGTH);
sip->radar_image_2d_idx = bm_load(name_tmp);
if ( optional_string("$Radar Color Image 2D:") ) {
stuff_string(name_tmp, F_NAME, NAME_LENGTH);
sip->radar_color_image_2d_idx = bm_load(name_tmp);
}
if (optional_string("$Radar Image Size:"))
stuff_int(&sip->radar_image_size);
if (optional_string("$3D Radar Blip Size Multiplier:"))
stuff_float(&sip->radar_projection_size_mult);
}
// Alternate - per ship class - IFF colors
while((optional_string("$Ship IFF Colors:")) || (optional_string("$Ship IFF Colours:")))
{
char iff_1[NAME_LENGTH];
char iff_2[NAME_LENGTH];
int iff_color_data[3];
int iff_data[2];
// Get the iff strings and get the iff indexes
required_string("+Seen By:");
stuff_string(iff_1, F_NAME, NAME_LENGTH);
required_string("+When IFF Is:");
stuff_string(iff_2, F_NAME, NAME_LENGTH);
iff_data[0] = iff_lookup(iff_1);
iff_data[1] = iff_lookup(iff_2);
if (iff_data[0] == -1)
WarningEx(LOCATION, "%s '%s'\nIFF colour seen by \"%s\" invalid!", info_type_name, sip->name, iff_1);
if (iff_data[1] == -1)
WarningEx(LOCATION, "%s '%s'\nIFF colour when IFF is \"%s\" invalid!", info_type_name, sip->name, iff_2);
// Set the color
required_string("+As Color:");
stuff_int_list(iff_color_data, 3, RAW_INTEGER_TYPE);
sip->ship_iff_info[{iff_data[0],iff_data[1]}] = iff_init_color(iff_color_data[0], iff_color_data[1], iff_color_data[2]);
}
if (optional_string("$Target Priority Groups:") ) {
SCP_vector<SCP_string> target_group_strings;
stuff_string_list(target_group_strings);
size_t num_groups = Ai_tp_list.size();
bool override_strings = false;
if (optional_string("+Override")) {
override_strings = true;
}
for(size_t j = 0; j < target_group_strings.size(); j++) {
size_t i;
for(i = 0; i < num_groups; i++) {
if ( !stricmp(target_group_strings[j].c_str(), Ai_tp_list[i].name) ) {
//so now the string from the list above as well as the ai priority group name match
//clear it if override has been set
if (override_strings) {
Ai_tp_list[i].ship_class.clear();
override_strings = false;
}
for (auto it = Ship_info.cbegin(); it != Ship_info.cend(); ++it) {
//find the index number of the current ship info type
if (it->name == sip->name) {
Ai_tp_list[i].ship_class.push_back((int)std::distance(Ship_info.cbegin(), it));
break;
}
}
// found something, try next string
break;
}
}
if (i == num_groups) {
Warning(LOCATION,"Unidentified priority group '%s' set for %s '%s'\n", target_group_strings[j].c_str(), info_type_name, sip->name);
}
}
}
if (optional_string("$EMP Resistance Modifier:")) {
stuff_float(&sip->emp_resistance_mod);
}
if (optional_string("$Piercing Damage Draw Limit:")) {
float tempf;
stuff_float(&tempf);
sip->piercing_damage_draw_limit = tempf / 100.0f;
}
while(optional_string("$Path Metadata:"))
{
char path_name[64];
stuff_string(path_name, F_NAME, sizeof(path_name));
path_metadata metadata;
init_path_metadata(metadata);
//Get +departure and arrival rvec and store on the path_metadata object
if (optional_string("+arrival rvec:"))
{
stuff_vec3d(&metadata.arrival_rvec);
}
if (optional_string("+departure rvec:"))
{
stuff_vec3d(&metadata.departure_rvec);
}
if (optional_string("+arrive speed multiplier:"))
{
stuff_float(&metadata.arrive_speed_mult);
}
if (optional_string("+depart speed multiplier:"))
{
stuff_float(&metadata.depart_speed_mult);
}
//Add the new path_metadata to sip->pathMetadata keyed by path name
SCP_string pathName(path_name);
sip->pathMetadata[pathName] = metadata;
}
while (optional_string("$Passive Lightning Arcs:")) {
ship_passive_arc_info new_info;
SCP_string temp1, temp2;
vec3d pos1, pos2;
required_string("+Submodel 1:");
stuff_string(temp1, F_NAME);
required_string("+Position 1:");
stuff_vec3d(&pos1);
required_string("+Submodel 2:");
stuff_string(temp2, F_NAME);
required_string("+Position 2:");
stuff_vec3d(&pos2);
new_info.pos = std::make_pair(pos1, pos2);
new_info.submodel_strings = std::make_pair(temp1, temp2);
new_info.submodels = std::make_pair(-1, -1); // this will be filled later once we have the model
required_string("+Duration:");
stuff_float(&new_info.duration);
required_string("+Frequency:");
stuff_float(&new_info.frequency);
sip->ship_passive_arcs.push_back(new_info);
}
if (optional_string("$Animations:")) {
animation::ModelAnimationParseHelper::parseAnimsetInfo(sip->animations, sip);
}
if (optional_string("$Animation Moveables:")) {
animation::ModelAnimationParseHelper::parseMoveablesetInfo(sip->animations);
}
if (optional_string("$Custom data:"))
{
parse_string_map(sip->custom_data, "$end_custom_data", "+Val:");
}
int n_subsystems = 0;
int cont_flag = 1;
model_subsystem subsystems[MAX_MODEL_SUBSYSTEMS] = {}; // see model.h for max_model_subsystems
for (auto i=0; i<MAX_MODEL_SUBSYSTEMS; i++) {
subsystems[i].reset();
}
float hull_percentage_of_hits = 100.0f;
//If the ship already has subsystem entries (ie this is a modular table)
//make sure hull_percentage_of_hits is set properly
for(auto i=0; i < sip->n_subsystems; i++) {
hull_percentage_of_hits -= sip->subsystems[i].max_subsys_strength / sip->max_hull_strength;
}
while (cont_flag) {
int r = required_string_one_of(3, "#End", "$Subsystem:", type_name);
switch (r) {
case 0:
cont_flag = 0;
break;
case 1:
{
float turning_rate;
float percentage_of_hits;
bool turret_has_base_fov = false;
model_subsystem *sp = NULL; // to append on the ships list of subsystems
int sfo_return;
required_string("$Subsystem:");
stuff_string(name_tmp, F_NAME, sizeof(name_tmp), ",");
Mp++;
for(auto i = 0;i < sip->n_subsystems; i++)
{
if(!subsystem_stricmp(sip->subsystems[i].subobj_name, name_tmp))
sp = &sip->subsystems[i];
}
if(sp == NULL)
{
if( sip->n_subsystems + n_subsystems >= MAX_MODEL_SUBSYSTEMS )
{
Warning(LOCATION, "Number of subsystems for %s '%s' (%d) exceeds max of %d; only the first %d will be used", info_type_name, sip->name, sip->n_subsystems, n_subsystems, MAX_MODEL_SUBSYSTEMS);
break;
}
sp = &subsystems[n_subsystems++]; // subsystems a local -- when done, we will malloc and copy
strcpy_s(sp->subobj_name, name_tmp);
//Init blank values
sp->max_subsys_strength = 0.0f;
sp->turret_turning_rate = 0.0f;
sp->weapon_rotation_pbank = -1;
memset(sp->alt_sub_name, 0, sizeof(sp->alt_sub_name));
memset(sp->alt_dmg_sub_name, 0, sizeof(sp->alt_dmg_sub_name));
for (auto i=0; i<MAX_SHIP_PRIMARY_BANKS; i++) {
sp->primary_banks[i] = -1;
sp->primary_bank_capacity[i] = 0;
}
for (auto i=0; i<MAX_SHIP_SECONDARY_BANKS; i++) {
sp->secondary_banks[i] = -1;
sp->secondary_bank_capacity[i] = 0;
}
sp->engine_wash_pointer = NULL;
sp->alive_snd = gamesnd_id();
sp->dead_snd = gamesnd_id();
sp->rotation_snd = gamesnd_id();
sp->turret_gun_rotation_snd = gamesnd_id();
sp->turret_gun_rotation_snd_mult = 1.0f;
sp->turret_base_rotation_snd = gamesnd_id();
sp->turret_base_rotation_snd_mult = 1.0f;
sp->flags.reset();
sp->model_num = -1; // init value for later sanity checking!!
sp->armor_type_idx = -1;
sp->path_num = -1;
sp->turret_max_fov = 1.0f;
sp->turret_reset_delay = 2000;
sp->num_target_priorities = 0;
for (auto i = 0; i < 32; i++) {
sp->target_priority[i] = -1;
}
sp->optimum_range = 0.0f;
sp->favor_current_facing = 0.0f;
sp->turret_rof_scaler = 1.0f;
sp->turret_max_bomb_ownage = -1;
sp->turret_max_target_ownage = -1;
}
sfo_return = stuff_float_optional(&percentage_of_hits);
if(sfo_return==2)
{
hull_percentage_of_hits -= percentage_of_hits;
sp->max_subsys_strength = sip->max_hull_strength * (percentage_of_hits / 100.0f);
sp->type = SUBSYSTEM_UNKNOWN;
}
if(sfo_return > 0)
{
if(stuff_float_optional(&turning_rate)==2)
{
// specified as how long to turn 360 degrees in ships.tbl
if ( turning_rate > 0.0f ){
sp->turret_turning_rate = PI2 / turning_rate;
} else {
sp->turret_turning_rate = 0.0f;
}
}
else
{
Error(LOCATION, "Malformed $Subsystem entry '%s' in %s '%s'.\n\n"
"Specify a turning rate or remove the trailing comma.",
sp->subobj_name, info_type_name, sip->name);
}
}
if(optional_string("$Alt Subsystem Name:")) {
stuff_string(buf, F_NAME, SHIP_MULTITEXT_LENGTH);
strcpy_s(sp->alt_sub_name, buf);
}
if(optional_string("$Alt Damage Popup Subsystem Name:")) {
stuff_string(buf, F_NAME, SHIP_MULTITEXT_LENGTH);
strcpy_s(sp->alt_dmg_sub_name, buf);
}
if(optional_string("$Armor Type:")) {
stuff_string(buf, F_NAME, SHIP_MULTITEXT_LENGTH);
sp->armor_type_idx = armor_type_get_idx(buf);
if (sp->armor_type_idx == -1)
WarningEx(LOCATION, "%s '%s', subsystem %s\nInvalid armor type %s!", info_type_name, sip->name, sp->subobj_name, buf);
}
// Get primary bank weapons
parse_weapon_bank(sip, true, NULL, sp->primary_banks, sp->primary_bank_capacity);
// Get secondary bank weapons
parse_weapon_bank(sip, false, NULL, sp->secondary_banks, sp->secondary_bank_capacity);
// Get optional engine wake info
if (optional_string("$Engine Wash:")) {
stuff_string(name_tmp, F_NAME, sizeof(name_tmp));
// get and set index
sp->engine_wash_pointer = get_engine_wash_pointer(name_tmp);
if(sp->engine_wash_pointer == NULL)
WarningEx(LOCATION,"Invalid engine wash name %s specified for subsystem %s in %s '%s'", name_tmp, sp->subobj_name, info_type_name, sip->name);
}
parse_game_sound("$AliveSnd:", &sp->alive_snd);
parse_game_sound("$DeadSnd:", &sp->dead_snd);
parse_game_sound("$RotationSnd:", &sp->rotation_snd);
parse_game_sound("$Turret Base RotationSnd:", &sp->turret_base_rotation_snd);
parse_game_sound("$Turret Gun RotationSnd:", &sp->turret_gun_rotation_snd);
if (optional_string("$Turret BaseSnd Volume:"))
stuff_float(&sp->turret_base_rotation_snd_mult);
if (optional_string("$Turret GunSnd Volume:"))
stuff_float(&sp->turret_gun_rotation_snd_mult);
// Get any AWACS info
sp->awacs_intensity = 0.0f;
if(optional_string("$AWACS:")){
sfo_return = stuff_float_optional(&sp->awacs_intensity);
if(sfo_return > 0)
stuff_float_optional(&sp->awacs_radius);
sip->flags.set(Ship::Info_Flags::Has_awacs);
}
if(optional_string("$Maximum Barrel Elevation:")){
int value;
stuff_int(&value);
CAP(value, 0, 90);
float angle = fl_radians(90 - value);
sp->turret_max_fov = cosf(angle);
}
if(optional_string("$Turret Base FOV:")) {
int value;
stuff_int(&value);
CAP(value, 0, 359);
float angle = fl_radians(value)/2.0f;
sp->turret_base_fov = cosf(angle);
turret_has_base_fov = true;
}
if (optional_string("$Turret Reset Delay:"))
stuff_int(&sp->turret_reset_delay);
if (optional_string("$Turret Optimum Range:"))
stuff_float(&sp->optimum_range);
if (optional_string("$Turret Direction Preference:")) {
int temp;
stuff_int(&temp);
if (temp == 0) {
sp->favor_current_facing = 0.0f;
} else {
CAP(temp, 1, 100);
sp->favor_current_facing = 1.0f + (((float) (100 - temp)) / 10.0f);
}
}
if (optional_string("$Target Priority:")) {
SCP_vector <SCP_string> tgt_priorities;
stuff_string_list(tgt_priorities);
sp->num_target_priorities = 0;
if (tgt_priorities.size() > 32)
tgt_priorities.resize(32);
size_t num_groups = Ai_tp_list.size();
for (size_t i = 0; i < tgt_priorities.size(); ++i) {
size_t j;
for(j = 0; j < num_groups; j++) {
if ( !stricmp(Ai_tp_list[j].name, tgt_priorities[i].c_str())) {
sp->target_priority[i] = (int)j;
sp->num_target_priorities++;
break;
}
}
if (j == num_groups) {
Warning(LOCATION, "Unidentified target priority '%s' set for\nsubsystem '%s' in %s '%s'.", tgt_priorities[i].c_str(), sp->subobj_name, info_type_name, sip->name);
}
}
}
if (optional_string("$Max Turrets per Bomb:")) {
stuff_int(&sp->turret_max_bomb_ownage);
}
if (optional_string("$Max Turrets per Target:")) {
stuff_int(&sp->turret_max_target_ownage);
}
if (optional_string("$ROF:")) {
if (optional_string("+Use firingpoints")) {
sp->turret_rof_scaler = 0;
} else {
if (optional_string("+Multiplier:")) {
float tempf;
stuff_float(&tempf);
if (tempf < 0) {
mprintf(("RoF multiplier clamped to 0 for subsystem '%s' in %s '%s'.\n", sp->subobj_name, info_type_name, sip->name));
sp->turret_rof_scaler = 0;
} else {
sp->turret_rof_scaler = tempf;
}
} else {
Warning(LOCATION, "RoF multiplier not set for subsystem\n'%s' in %s '%s'.", sp->subobj_name, info_type_name, sip->name);
}
}
}
if (optional_string("$Flags:")) {
SCP_vector<SCP_string> errors;
flagset<Model::Subsystem_Flags> tmp_flags;
parse_string_flag_list(tmp_flags, Subsystem_flags, Num_subsystem_flags, &errors);
if (optional_string("+noreplace")) {
sp->flags |= tmp_flags;
}
else {
sp->flags = tmp_flags;
}
if (!errors.empty()) {
for (auto const &error : errors) {
Warning(LOCATION, "Bogus string in subsystem flags: %s\n", error.c_str());
}
}
//If we've set any subsystem as landable, set a ship-info flag as a shortcut for later
if (sp->flags[Model::Subsystem_Flags::Allow_landing])
sip->flags.set(Ship::Info_Flags::Allow_landings);
}
if (turret_has_base_fov)
sp->flags.set(Model::Subsystem_Flags::Turret_restricted_fov);
if (optional_string("+non-targetable")) {
Warning(LOCATION, "Grammar error in table file. Please change \"+non-targetable\" to \"+untargetable\".");
sp->flags.set(Model::Subsystem_Flags::Untargetable);
}
bool old_flags = false;
if (optional_string("+untargetable")) {
sp->flags.set(Model::Subsystem_Flags::Untargetable);
old_flags = true;
}
if (optional_string("+carry-no-damage")) {
sp->flags.set(Model::Subsystem_Flags::Carry_no_damage);
old_flags = true;
}
if (optional_string("+use-multiple-guns")) {
sp->flags.set(Model::Subsystem_Flags::Use_multiple_guns);
old_flags = true;
}
if (optional_string("+fire-down-normals")) {
sp->flags.set(Model::Subsystem_Flags::Fire_on_normal);
old_flags = true;
}
if ((sp->flags[Model::Subsystem_Flags::Turret_fixed_fp]) && !(sp->flags[Model::Subsystem_Flags::Use_multiple_guns])) {
Warning(LOCATION, "\"fixed firingpoints\" flag used without \"use multiple guns\" flag on a subsystem on %s '%s'.\n\"use multiple guns\" flags added by default\n", info_type_name, sip->name);
sp->flags.set(Model::Subsystem_Flags::Use_multiple_guns);
}
if ((sp->flags[Model::Subsystem_Flags::Autorepair_if_disabled]) && (sp->flags[Model::Subsystem_Flags::No_autorepair_if_disabled])) {
Warning(LOCATION, "\"autorepair if disabled\" flag used with \"don't autorepair if disabled\" flag on a subsystem on %s '%s'.\nWhichever flag would be default behavior anyway for this ship has been removed.\n", info_type_name, sip->name);
if (sip->flags[Ship::Info_Flags::Subsys_repair_when_disabled]){
sp->flags.remove(Model::Subsystem_Flags::Autorepair_if_disabled);
} else {
sp->flags.remove(Model::Subsystem_Flags::No_autorepair_if_disabled);
}
}
if (old_flags) {
mprintf(("Use of deprecated subsystem syntax. Please use the $Flags: field for subsystem flags.\n\n" \
"At least one of the following tags was used on %s '%s', subsystem %s:\n" \
"\t+untargetable\n" \
"\t+carry-no-damage\n" \
"\t+use-multiple-guns\n" \
"\t+fire-down-normals\n", info_type_name, sip->name, sp->subobj_name));
}
while(optional_string("$animation:"))
{
stuff_string(name_tmp, F_NAME, sizeof(name_tmp));
if(!stricmp(name_tmp, "triggered"))
{
animation::ModelAnimationParseHelper::parseLegacyAnimationTable(sp, sip);
}
else if(!stricmp(name_tmp, "linked"))
{
mprintf(("TODO: set up linked animation\n"));
}
}
sp->beam_warmdown_program = actions::ProgramSet::parseProgramSet("$On Beam Warmdown:",
{actions::ProgramContextFlags::HasObject, actions::ProgramContextFlags::HasSubobject});
}
break;
case 2:
cont_flag = 0;
break;
case -1: // Possible return value if -noparseerrors is used
break;
default:
UNREACHABLE("This should never happen.\n"); // Impossible return value from required_string_one_of.
}
}
// must be > 0//no it doesn't :P -Bobboau
// yes it does! - Goober5000
// (we don't want a div-0 error)
if (hull_percentage_of_hits <= 0.0f )
{
//Warning(LOCATION, "The subsystems defined for the %s can take more (or the same) combined damage than the ship itself. Adjust the tables so that the percentages add up to less than 100", sip->name);
}
// when done reading subsystems, malloc and copy the subsystem data to the ship info structure
int orig_n_subsystems = sip->n_subsystems;
if ( n_subsystems > 0 ) {
// Let's make sure that n_subsystems is not negative
Assertion(sip->n_subsystems >= 0, "Invalid n_subsystems detected!");
auto new_n = sip->n_subsystems + n_subsystems;
std::unique_ptr<model_subsystem[]> subsys_storage(new model_subsystem[new_n]);
if(sip->n_subsystems <= 0) {
sip->n_subsystems = n_subsystems;
} else {
// This used realloc originally so we need to copy the existing subsystems to the new storage
std::copy(sip->subsystems, sip->subsystems + sip->n_subsystems, subsys_storage.get());
// done with old storage
delete[] sip->subsystems;
sip->n_subsystems += n_subsystems;
}
sip->subsystems = subsys_storage.release();
Assert(sip->subsystems != NULL);
}
for ( int i = 0; i < n_subsystems; i++ ){
sip->subsystems[orig_n_subsystems+i] = subsystems[i];
}
}
static engine_wash_info *get_engine_wash_pointer(char *engine_wash_name)
{
for(int i = 0; i < Num_engine_wash_types; i++)
{
if(!stricmp(engine_wash_name, Engine_wash_info[i].name))
{
return &Engine_wash_info[i];
}
}
//Didn't find anything.
return NULL;
}
static void parse_ship_type(const char *filename, const bool replace)
{
char name_buf[NAME_LENGTH];
bool create_if_not_found = true;
ship_type_info *stp = nullptr;
required_string("$Name:");
stuff_string(name_buf, F_NAME, NAME_LENGTH);
if(optional_string("+nocreate")) {
if(!replace) {
Warning(LOCATION, "+nocreate flag used for ship type '%s' in non-modular table", name_buf);
}
create_if_not_found = false;
}
bool first_time;
int idx = ship_type_name_lookup(name_buf);
if (idx >= 0)
{
if (!replace)
{
Warning(LOCATION, "ship type '%s' already exists in %s; ship type names must be unique.", name_buf, filename);
if (!skip_to_start_of_string_either("$Name:", "#End")) {
error_display(1, "Missing [#End] or [$Name] after duplicate ship type %s", name_buf);
}
return;
}
stp = &Ship_types[idx];
first_time = false;
}
else
{
if (!create_if_not_found && replace)
{
if (!skip_to_start_of_string_either("$Name:", "#End")) {
error_display(1, "Missing [#End] or [$Name] after ship type %s", name_buf);
}
return;
}
Ship_types.push_back(ship_type_info());
stp = &Ship_types.back();
strcpy_s(stp->name, name_buf);
first_time = true;
}
const char *ship_type = NULL;
if (!stricmp(stp->name, "sentrygun")) {
ship_type = "sentry gun";
} else if (!stricmp(stp->name, "escapepod")) {
ship_type = "escape pod";
} else if (!stricmp(stp->name, "repair_rearm")) {
ship_type = "support";
} else if (!stricmp(stp->name, "supercap")) {
ship_type = "super cap";
} else if (!stricmp(stp->name, "knossos")) {
ship_type = "knossos device";
}
if (ship_type != NULL) {
Warning(LOCATION, "Bad ship type name in %s\n\nUsed ship type is redirected to another ship type.\nReplace \"%s\" with \"%s\"\nin %s to fix this.\n", filename, stp->name, ship_type, filename);
}
bool big_ship = false;
bool huge_ship = false;
if (stricmp(stp->name, "cruiser") == 0 || stricmp(stp->name, "freighter") == 0 || stricmp(stp->name, "transport") == 0 ||
stricmp(stp->name, "corvette") == 0 || stricmp(stp->name, "gas miner") == 0 || stricmp(stp->name, "awacs") == 0)
big_ship = true;
if (stricmp(stp->name, "capital") == 0 || stricmp(stp->name, "super cap") == 0 || stricmp(stp->name, "knossos device") == 0 || stricmp(stp->name, "drydock") == 0)
huge_ship = true;
//Okay, now we should have the values to parse
//But they aren't here!! :O
//Now they are!! Whee fogging!!
//AI turret targeting priority setup
if (optional_string("$Target Priority Groups:") ) {
SCP_vector <SCP_string> target_group_strings;
stuff_string_list(target_group_strings);
auto num_strings = target_group_strings.size();
auto num_groups = Ai_tp_list.size();
bool override_strings = false;
if (optional_string("+Override")) {
override_strings = true;
}
for(size_t j = 0; j < num_strings; j++) {
size_t i;
for(i = 0; i < num_groups; i++) {
if ( !stricmp(target_group_strings[j].c_str(), Ai_tp_list[i].name) ) {
//so now the string from the list above as well as the ai priority group name match
//clear it if override has been set
if (override_strings) {
Ai_tp_list[i].ship_type.clear();
override_strings = false;
}
//find the index number of the current ship info type
Ai_tp_list[i].ship_type.push_back(ship_type_name_lookup(name_buf));
break;
}
}
if (i == num_groups) {
Warning(LOCATION,"Unidentified priority group '%s' set for ship type '%s' in %s\n", target_group_strings[j].c_str(), stp->name, filename);
}
}
}
if(optional_string("$Counts for Alone:")) {
stuff_boolean_flag(stp->flags, Ship::Type_Info_Flags::Counts_for_alone);
}
if(optional_string("$Praise Destruction:")) {
stuff_boolean_flag(stp->flags, Ship::Type_Info_Flags::Praise_destruction);
}
if(optional_string("$On Hotkey list:")) {
stuff_boolean_flag(stp->flags, Ship::Type_Info_Flags::Hotkey_on_list);
}
if(optional_string("$Target as Threat:")) {
stuff_boolean_flag(stp->flags, Ship::Type_Info_Flags::Target_as_threat);
}
if(optional_string("$Show Attack Direction:")) {
stuff_boolean_flag(stp->flags, Ship::Type_Info_Flags::Show_attack_direction);
}
if(optional_string("$Scannable:")) {
stuff_boolean_flag(stp->flags, Ship::Type_Info_Flags::Scannable);
}
if(optional_string("$Warp Pushes:")) {
stuff_boolean_flag(stp->flags, Ship::Type_Info_Flags::Warp_pushes);
}
if(optional_string("$Warp Pushable:")) {
stuff_boolean_flag(stp->flags, Ship::Type_Info_Flags::Warp_pushable);
}
if(optional_string("$Turrets prioritize ship target:")) {
stuff_boolean_flag(stp->flags, Ship::Type_Info_Flags::Turret_tgt_ship_tgt);
}
if(optional_string("$Max Debris Speed:")) {
stuff_float(&stp->debris_max_speed);
}
if(optional_string("$FF Multiplier:")) {
stuff_float(&stp->ff_multiplier);
}
if(optional_string("$EMP Multiplier:")) {
stuff_float(&stp->emp_multiplier);
}
if(optional_string("$Beams Easily Hit:")) {
stuff_boolean_flag(stp->flags, Ship::Type_Info_Flags::Beams_easily_hit);
}
if(optional_string("$Protected on cripple:")) {
stuff_boolean_flag(stp->flags, Ship::Type_Info_Flags::AI_protected_on_cripple);
}
if(optional_string("$No Huge Beam Impact Effects:")) {
stuff_boolean_flag(stp->flags, Ship::Type_Info_Flags::No_huge_impact_eff);
}
if(optional_string("$Don't display class in briefing:")) {
stuff_boolean_flag(stp->flags, Ship::Type_Info_Flags::No_class_display);
}
if(optional_string("$Fog:"))
{
if(optional_string("+Start dist:")) {
stuff_float(&stp->fog_start_dist);
}
if(optional_string("+Compl dist:")) {
stuff_float(&stp->fog_complete_dist);
}
}
if(optional_string("$AI:"))
{
if(optional_string("+Valid goals:")) {
parse_string_flag_list(&stp->ai_valid_goals, Ai_goal_names, Num_ai_goals);
}
if(optional_string("+Accept Player Orders:")) {
stuff_boolean_flag(stp->flags, Ship::Type_Info_Flags::AI_accept_player_orders);
}
if(optional_string("+Player Orders:")) {
SCP_vector<SCP_string> slp;
stuff_string_list(slp);
for(size_t i = 0; i < Player_orders.size(); i++){
if(std::find(slp.begin(), slp.end(), Player_orders[i].parse_name) != slp.end()){
stp->ai_player_orders.insert(i);
}
}
}
if(optional_string("+Auto attacks:")) {
stuff_boolean_flag(stp->flags, Ship::Type_Info_Flags::AI_auto_attacks);
}
if(optional_string("+Attempt broadside:")) {
stuff_boolean_flag(stp->flags, Ship::Type_Info_Flags::AI_attempt_broadside);
}
if(optional_string("+Actively Pursues:")) {
stuff_string_list(stp->ai_actively_pursues_temp);
}
if(optional_string("+Guards attack this:")) {
stuff_boolean_flag(stp->flags, Ship::Type_Info_Flags::AI_guards_attack);
}
if(optional_string("+Turrets attack this:")) {
stuff_boolean_flag(stp->flags, Ship::Type_Info_Flags::AI_turrets_attack);
}
if(optional_string("+Can form wing:")) {
stuff_boolean_flag(stp->flags, Ship::Type_Info_Flags::AI_can_form_wing);
}
if(optional_string("+Active docks:")) {
parse_string_flag_list(&stp->ai_active_dock, Dock_type_names, Num_dock_type_names);
}
if(optional_string("+Passive docks:")) {
parse_string_flag_list(&stp->ai_passive_dock, Dock_type_names, Num_dock_type_names);
}
if(optional_string("+Ignored on cripple by:")) {
stuff_string_list(stp->ai_cripple_ignores_temp);
}
if (optional_string("+Targeted by 'Huge' weapons and Ignored by 'small only' weapons:")) {
stuff_boolean_flag(stp->flags, Ship::Type_Info_Flags::Targeted_by_huge_Ignored_by_small_only);
} else if (first_time && (big_ship || huge_ship)) {
stp->flags.set(Ship::Type_Info_Flags::Targeted_by_huge_Ignored_by_small_only);
}
}
if(optional_string("$Explosion Animations:"))
{
int temp[MAX_FIREBALL_TYPES];
auto parsed_ints = stuff_int_list(temp, MAX_FIREBALL_TYPES, RAW_INTEGER_TYPE);
stp->explosion_bitmap_anims.clear();
stp->explosion_bitmap_anims.insert(stp->explosion_bitmap_anims.begin(), temp, temp+parsed_ints);
}
if(optional_string("$Vaporize Percent Chance:")) {
stuff_float(&stp->vaporize_chance);
if (stp->vaporize_chance < 0.0f || stp->vaporize_chance > 100.0f) {
stp->vaporize_chance = 0.0f;
Warning(LOCATION, "$Vaporize Percent Chance should be between 0 and 100.0 (read %f) for ship type '%s' in %s. Setting to 0.", stp->vaporize_chance, stp->name, filename);
}
//Percent is nice for modders, but here in the code we want it betwwen 0 and 1.0
stp->vaporize_chance /= 100.0;
}
}
static void parse_shiptype_tbl(const char *filename)
{
try
{
if (filename != NULL)
read_file_text(filename, CF_TYPE_TABLES);
else
read_file_text_from_default(defaults_get_file("objecttypes.tbl"));
reset_parse();
if (optional_string("#Target Priorities"))
{
while (required_string_either("#End", "$Name:"))
parse_ai_target_priorities();
required_string("#End");
}
if (optional_string("#Weapon Targeting Priorities"))
{
while (required_string_either("#End", "$Name:"))
parse_weapon_targeting_priorities();
required_string("#End");
}
if (optional_string("#Ship Types"))
{
while (required_string_either("#End", "$Name:"))
parse_ship_type(filename ? filename : "built-in objecttypes.tbl", Parsing_modular_table);
required_string("#End");
}
}
catch (const parse::ParseException& e)
{
mprintf(("TABLES: Unable to parse '%s'! Error message = %s.\n", filename, e.what()));
return;
}
}
// The E - Simple lookup function for FRED.
int get_default_player_ship_index()
{
if (strlen(default_player_ship))
{
for (auto it = Ship_info.cbegin(); it != Ship_info.cend(); ++it)
{
if (stricmp(default_player_ship, it->name) == 0)
return (int)std::distance(Ship_info.cbegin(), it);
}
return 0;
} else
return 0;
}
// Goober5000 - this works better in its own function
static void ship_set_default_player_ship()
{
// already have one
if(strlen(default_player_ship))
return;
// find the first with the default flag
for (auto it = Ship_info.cbegin(); it != Ship_info.end(); ++it)
{
if(it->flags[Ship::Info_Flags::Default_player_ship])
{
strcpy_s(default_player_ship, it->name);
return;
}
}
// find the first player ship
for (auto it = Ship_info.cbegin(); it != Ship_info.end(); ++it)
{
if(it->flags[Ship::Info_Flags::Player_ship])
{
strcpy_s(default_player_ship, it->name);
return;
}
}
// find the first ship
if(!Ship_info.empty())
{
strcpy_s(default_player_ship, Ship_info[0].name);
}
}
static void parse_shiptbl(const char *filename)
{
try
{
read_file_text(filename, CF_TYPE_TABLES);
reset_parse();
// parse default ship
//Override default player ship
if (optional_string("#Default Player Ship"))
{
required_string("$Name:");
stuff_string(default_player_ship, F_NAME, sizeof(default_player_ship));
required_string("#End");
}
//Add engine washes
//This will override if they already exist
if (optional_string("#Engine Wash Info"))
{
while (required_string_either("#End", "$Name:"))
parse_engine_wash(Parsing_modular_table);
required_string("#End");
}
if (optional_string("#Ship Templates"))
{
while (required_string_either("#End", "$Template:"))
parse_ship_template();
required_string("#End");
}
//Add ship classes
if (optional_string("#Ship Classes"))
{
while (required_string_either("#End", "$Name:"))
parse_ship(filename, Parsing_modular_table);
required_string("#End");
}
//Add formations
if (optional_string("#Wing Formations"))
{
while (required_string_either("#End", "$Name:"))
parse_wing_formation(Parsing_modular_table);
required_string("#End");
}
//Set default player ship
ship_set_default_player_ship();
}
catch (const parse::ParseException& e)
{
mprintf(("TABLES: Unable to parse '%s'! Error message = %s.\n", filename, e.what()));
return;
}
}
int ship_show_velocity_dot = 0;
DCF_BOOL( show_velocity_dot, ship_show_velocity_dot )
static bool ballistic_possible_for_this_ship(const ship_info *sip)
{
// has no weapons!
if (sip->num_primary_banks < 1)
return false;
for (int i = 0; i < MAX_SHIP_PRIMARY_BANKS; i++)
{
// check default weapons
if (sip->primary_bank_weapons[i] >= 0 && Weapon_info[sip->primary_bank_weapons[i]].wi_flags[Weapon::Info_Flags::Ballistic])
return true;
// check allowed weapons
if (!sip->allowed_bank_restricted_weapons.empty()) {
for (int j = 0; j < weapon_info_size(); ++j)
{
if (sip->allowed_bank_restricted_weapons[i][j] && (Weapon_info[j].wi_flags[Weapon::Info_Flags::Ballistic]))
return true;
}
}
}
return false;
}
/**
* Clean up ship entries, making sure various flags and settings are correct
*/
static void ship_parse_post_cleanup()
{
int j;
char name_tmp[NAME_LENGTH];
for (auto sip = Ship_info.begin(); sip != Ship_info.end(); ++sip)
{
// ballistic primary fixage...
{
bool pbank_capacity_specified = false;
// determine whether this ship had primary capacities specified for it
for (j = 0; j < sip->num_primary_banks; j++) {
if (sip->primary_bank_ammo_capacity[j] > 0) {
pbank_capacity_specified = true;
break;
}
}
// be friendly; ensure ballistic flags check out
if (!pbank_capacity_specified) {
if ( ballistic_possible_for_this_ship(&(*sip)) ) {
Warning(LOCATION, "Pbank capacity not specified for ballistic-primary-enabled ship %s.\nDefaulting to capacity of 1 per bank.\n", sip->name);
for (j = 0; j < MAX_SHIP_PRIMARY_BANKS; j++) {
sip->primary_bank_ammo_capacity[j] = 1;
}
}
}
}
// ultra stupid compatbility handling for the once broken "generate hud" flag.
// it was previously testing the afterburner flag, so that's what we check for that
if ( (sip->shield_icon_index == 255) && (sip->flags[Ship::Info_Flags::Afterburner])
&& !(sip->flags[Ship::Info_Flags::Generate_hud_icon]) && (sip->flags[Ship::Info_Flags::Player_ship]) )
{
Warning(LOCATION, "Compatibility warning:\nNo shield icon specified for '%s' but the \"generate icon\" flag is not specified.\nEnabling flag by default.\n", sip->name);
sip->flags.set(Ship::Info_Flags::Generate_hud_icon);
}
// if we have a ship copy, then check to be sure that our base ship exists
if (sip->flags[Ship::Info_Flags::Ship_copy])
{
strcpy_s(name_tmp, sip->name);
if (end_string_at_first_hash_symbol(name_tmp))
{
if (ship_info_lookup(name_tmp) < 0)
{
Warning(LOCATION, "Ship %s is a copy, but base ship %s couldn't be found.", sip->name, name_tmp);
sip->flags.remove(Ship::Info_Flags::Ship_copy);
}
}
else
{
Warning(LOCATION, "Ship %s is defined as a copy (ship flag 'ship copy' is set), but is not named like one (no '#').\n", sip->name);
sip->flags.remove(Ship::Info_Flags::Ship_copy);
}
}
// very low rotational velocity values disable rotational collisions
// warn early rather than ambush the modder @ runtime (unless the ship is also no-collide!)
// the 2nd part of this check is duplicated from collideshipship.cpp:ship_ship_check_collision()
if (!(sip->flags[Ship::Info_Flags::No_collide]) && (vm_vec_mag_squared( &sip->max_rotvel ) * .04) >= (PI*PI/4))
{
Warning(LOCATION, "$Rotation time: too low; this will disable rotational collisions. All three variables should be >= 1.39.\nFix this in ship '%s'\n", sip->name);
}
// ensure player min velocity makes sense
if (sip->min_vel.xyz.x > sip->max_vel.xyz.x || sip->min_vel.xyz.x < 0.0f)
{
error_display(0, "$Player Minimum Velocity X-value (%f) is negative or greater than max velocity X-value (%f), setting to zero\nFix for ship '%s'\n",
sip->min_vel.xyz.x, sip->max_vel.xyz.x, sip->name);
sip->min_vel.xyz.x = 0.0f;
}
if (sip->min_vel.xyz.y > sip->max_vel.xyz.y || sip->min_vel.xyz.y < 0.0f)
{
error_display(0, "$Player Minimum Velocity Y-value (%f) is negative or greater than max velocity Y-value (%f), setting to zero\nFix for ship '%s'\n",
sip->min_vel.xyz.y, sip->max_vel.xyz.y, sip->name);
sip->min_vel.xyz.y = 0.0f;
}
if (sip->min_vel.xyz.z > sip->max_vel.xyz.z || sip->min_vel.xyz.z < 0.0f)
{
error_display(0, "$Player Minimum Velocity Z-value (%f) is negative or greater than max velocity Z-value (%f), setting to zero\nFix for ship '%s'\n",
sip->min_vel.xyz.z, sip->max_vel.xyz.z, sip->name);
sip->min_vel.xyz.z = 0.0f;
}
}
// check also target groups here
size_t n_tgt_groups = Ai_tp_list.size();
if (n_tgt_groups > 0) {
for(size_t i = 0; i < n_tgt_groups; i++) {
if (!(Ai_tp_list[i].obj_flags.any_set() || Ai_tp_list[i].sif_flags.any_set() || Ai_tp_list[i].wif_flags.any_set())) {
//had none of these, check next
if (Ai_tp_list[i].obj_type == -1) {
//didn't have this one
if (!(Ai_tp_list[i].ship_class.size() || Ai_tp_list[i].ship_type.size() || Ai_tp_list[i].weapon_class.size())) {
// had nothing - time to issue a warning
Warning(LOCATION, "Target priority group '%s' had no targeting rules issued for it.\n", Ai_tp_list[i].name);
}
}
}
}
}
// Clear out ship templates, since they're no longer needed. -MageKing17
Ship_templates.clear();
}
/**
* Called once at the beginning of the game to parse ships.tbl and stuff the ::Ship_info
* vector
*/
void ship_init()
{
if ( !Ships_inited )
{
//Initialize Ignore_List for targeting
set_default_ignore_list();
//Parse main TBL first
if (cf_exists_full("objecttypes.tbl", CF_TYPE_TABLES))
parse_shiptype_tbl("objecttypes.tbl");
else
parse_shiptype_tbl(NULL);
//Then other ones
parse_modular_table(NOX("*-obt.tbm"), parse_shiptype_tbl);
// DO ALL THE STUFF WE NEED TO DO AFTER LOADING Ship_types
ship_type_info *stp;
uint i,j;
int idx;
for(i = 0; i < Ship_types.size(); i++)
{
stp = &Ship_types[i];
//Handle active pursuit
for(j = 0; j < stp->ai_actively_pursues_temp.size(); j++)
{
idx = ship_type_name_lookup((char*)stp->ai_actively_pursues_temp[j].c_str());
if(idx >= 0) {
stp->ai_actively_pursues.push_back(idx);
}
}
stp->ai_actively_pursues_temp.clear();
//Handle disabled/disarmed behaviour
for(j = 0; j < stp->ai_cripple_ignores_temp.size(); j++) {
idx = ship_type_name_lookup((char*)stp->ai_cripple_ignores_temp[j].c_str());
if(idx >= 0) {
stp->ai_cripple_ignores.push_back(idx);
}
}
stp->ai_cripple_ignores_temp.clear();
}
//ships.tbl
{
Num_engine_wash_types = 0;
strcpy_s(default_player_ship, "");
//Parse main TBL first
Removed_ships.clear();
Ship_info.clear();
parse_shiptbl("ships.tbl");
//Then other ones
parse_modular_table(NOX("*-shp.tbm"), parse_shiptbl);
ship_parse_post_cleanup();
Ships_inited = true;
}
// We shouldn't already have any subsystem pointers at this point.
Assertion(Ship_subsystems.empty(), "Some pre-allocated subsystems didn't get cleared out: " SIZE_T_ARG " batches present during ship_init(); get a coder!\n", Ship_subsystems.size());
}
ship_level_init(); // needed for FRED
}
static int Man_thruster_reset_timestamp = 0;
static void ship_clear_subsystems()
{
for (auto it = Ship_subsystems.begin(); it != Ship_subsystems.end(); ++it) {
delete[] *it;
}
Ship_subsystems.clear();
Num_ship_subsystems = 0;
Num_ship_subsystems_allocated = 0;
}
static int ship_allocate_subsystems(int num_so, bool page_in = false)
{
int i;
int num_subsystems_save = 0;
// "0" itself is safe
if (num_so < 0) {
Int3();
return 0;
}
// allow a page-in thingy, so that we can grab as much as possible before mission
// start, but without messing up our count for future things
if (page_in)
num_subsystems_save = Num_ship_subsystems;
Num_ship_subsystems += num_so;
// bail if we don't actually need any more
if ( Num_ship_subsystems < Num_ship_subsystems_allocated )
return 1;
mprintf(("Allocating space for at least %i new ship subsystems ... ", num_so));
// we might need more than one set worth of new subsystems, so make as many as required
do {
ship_subsys* new_batch = new ship_subsys[NUM_SHIP_SUBSYSTEMS_PER_SET];
Ship_subsystems.push_back(new_batch);
// append the new set to our free list
for (i = 0; i < NUM_SHIP_SUBSYSTEMS_PER_SET; i++)
list_append( &ship_subsys_free_list, &new_batch[i] );
Num_ship_subsystems_allocated += NUM_SHIP_SUBSYSTEMS_PER_SET;
} while ( (Num_ship_subsystems - Num_ship_subsystems_allocated) > 0 );
if (page_in)
Num_ship_subsystems = num_subsystems_save;
mprintf(("a total of %i is now available (%i in-use).\n", Num_ship_subsystems_allocated, Num_ship_subsystems));
return 1;
}
/**
* This will get called at the start of each level.
*/
void ship_level_init()
{
int i;
// Reset everything between levels
Ships_exited.clear();
Ships_exited.reserve(100);
for (i=0; i<MAX_SHIPS; i++ )
{
Ships[i].ship_name[0] = '\0';
Ships[i].objnum = -1;
}
Num_wings = 0;
for (i = 0; i < MAX_WINGS; i++ )
{
Wings[i].num_waves = -1;
Wings[i].wing_squad_filename[0] = '\0';
Wings[i].wing_insignia_texture = -1; // Goober5000 - default to no wing insignia
// don't worry about releasing textures because
// they are released automatically when the model
// is unloaded (because they are part of the model)
}
for (i=0; i<MAX_STARTING_WINGS; i++)
Starting_wings[i] = -1;
for (i=0; i<MAX_SQUADRON_WINGS; i++)
Squadron_wings[i] = -1;
for (i=0; i<MAX_TVT_WINGS; i++)
TVT_wings[i] = -1;
// Goober5000
// set starting wing names to default
strcpy_s(Starting_wing_names[0], "Alpha");
strcpy_s(Starting_wing_names[1], "Beta");
strcpy_s(Starting_wing_names[2], "Gamma");
// set squadron wing names to default
strcpy_s(Squadron_wing_names[0], "Alpha");
strcpy_s(Squadron_wing_names[1], "Beta");
strcpy_s(Squadron_wing_names[2], "Gamma");
strcpy_s(Squadron_wing_names[3], "Delta");
strcpy_s(Squadron_wing_names[4], "Epsilon");
// set tvt wing names to default
strcpy_s(TVT_wing_names[0], "Alpha");
strcpy_s(TVT_wing_names[1], "Zeta");
// clear out ship registry
Ship_registry.clear();
Ship_registry_map.clear();
// Empty the subsys list
ship_clear_subsystems();
list_init( &ship_subsys_free_list );
Laser_energy_out_snd_timer = 1;
Missile_out_snd_timer = 1;
ship_obj_list_init();
Ship_cargo_check_timer = 1;
shipfx_large_blowup_level_init();
Man_thruster_reset_timestamp = timestamp(0);
}
/**
* Add a ship onto the exited ships list.
*
* The reason parameter tells us why the ship left the mission (i.e. departed or destroyed)
*/
void ship_add_exited_ship( ship *sp, Ship::Exit_Flags reason )
{
exited_ship entry;
strcpy_s(entry.ship_name, sp->ship_name );
entry.display_name = sp->get_display_name();
entry.obj_signature = Objects[sp->objnum].signature;
entry.ship_class = sp->ship_info_index;
entry.team = sp->team;
entry.flags += reason;
// if ship is red alert, flag as such
if (sp->flags[Ship_Flags::Red_alert_store_status]) {
entry.flags.set(Ship::Exit_Flags::Red_alert_carry);
}
entry.time = Missiontime;
entry.hull_strength = int(Objects[sp->objnum].hull_strength);
entry.cargo1 = sp->cargo1;
entry.time_cargo_revealed = (fix)0;
if ( sp->flags[Ship_Flags::Cargo_revealed] )
{
entry.flags.set(Ship::Exit_Flags::Cargo_known);
entry.time_cargo_revealed = sp->time_cargo_revealed;
}
if (sp->time_first_tagged > 0) {
entry.flags.set(Ship::Exit_Flags::Been_tagged);
}
//copy across the damage_ship arrays
for (int i = 0; i < MAX_DAMAGE_SLOTS ; i++) {
entry.damage_ship_id[i] = sp->damage_ship_id[i] ;
entry.damage_ship[i] = sp->damage_ship[i] ;
}
// record this in the ship registry
auto ship_it = Ship_registry_map.find(sp->ship_name);
if (ship_it != Ship_registry_map.end())
Ship_registry[ship_it->second].exited_index = static_cast<int>(Ships_exited.size());
Ships_exited.push_back(entry);
}
/**
* Attempt to find information about an exited ship based on shipname
*/
int ship_find_exited_ship_by_name( const char *name )
{
int i;
for (i = 0; i < (int)Ships_exited.size(); i++) {
if ( !stricmp(name, Ships_exited[i].ship_name) )
return i;
}
return -1;
}
/**
* Attempt to find information about an exited ship based on signature
*/
int ship_find_exited_ship_by_signature( int signature )
{
int i;
for (i = 0; i < (int)Ships_exited.size(); i++) {
if ( signature == Ships_exited[i].obj_signature )
return i;
}
return -1;
}
void physics_ship_init(object *objp)
{
ship_info *sinfo = &Ship_info[Ships[objp->instance].ship_info_index];
physics_info *pi = &objp->phys_info;
polymodel *pm = model_get(sinfo->model_num);
// use mass and I_body_inv from POF read into polymodel
physics_init(pi);
if (sinfo->density == 0) {
sinfo->density = 1;
nprintf(("Physics", "pi->density==0.0f. setting to 1\n"));
Warning(LOCATION, "%s has 0 density! setting to 1", sinfo->name);
}
if (pm->mass==0.0f)
{
// make a guess for the ship's mass
vec3d size;
vm_vec_sub(&size,&pm->maxs,&pm->mins);
float vmass=size.xyz.x*size.xyz.y*size.xyz.z;
float amass=4.65f*(float)pow(vmass,(2.0f/3.0f));
nprintf(("Physics", "pi->mass==0.0f. setting to %f\n",amass));
Warning(LOCATION, "%s (%s) has no mass! setting to %f", sinfo->name, sinfo->pof_file, amass);
pm->mass=amass;
pi->mass=amass * sinfo->density;
}
else
pi->mass = pm->mass * sinfo->density;
// it was print-worthy back in read_model_file() in modelread.cpp, but now that its being used for an actual ship the user should be warned.
if (IS_VEC_NULL(&pm->moment_of_inertia.vec.fvec)
&& IS_VEC_NULL(&pm->moment_of_inertia.vec.uvec)
&& IS_VEC_NULL(&pm->moment_of_inertia.vec.rvec))
Warning(LOCATION, "%s (%s) has a null moment of inertia!", sinfo->name, sinfo->pof_file);
// if invalid they were already warned about this in read_model_file() in modelread.cpp, so now we just need to try and sweep it under the rug
if (!is_valid_matrix(&pm->moment_of_inertia))
{
// TODO: generate MOI properly
vm_mat_zero(&pi->I_body_inv);
}
// it's valid, so we can use it
else
pi->I_body_inv = pm->moment_of_inertia;
// scale inverse moment of inertia value by inverse density
vm_vec_scale( &pi->I_body_inv.vec.rvec, 1/sinfo->density );
vm_vec_scale( &pi->I_body_inv.vec.uvec, 1/sinfo->density );
vm_vec_scale( &pi->I_body_inv.vec.fvec, 1/sinfo->density );
pi->center_of_mass = pm->center_of_mass;
pi->side_slip_time_const = sinfo->damp;
pi->delta_bank_const = sinfo->delta_bank_const;
pi->rotdamp = sinfo->rotdamp;
pi->max_vel = sinfo->max_vel;
pi->afterburner_max_vel = sinfo->afterburner_max_vel;
pi->max_rotvel = sinfo->max_rotvel;
pi->max_rear_vel = sinfo->max_rear_vel;
pi->flags |= PF_ACCELERATES;
pi->flags &= ~PF_GLIDING; //Turn off glide
pi->flags &= ~PF_FORCE_GLIDE;
pi->forward_accel_time_const=sinfo->forward_accel;
pi->afterburner_forward_accel_time_const=sinfo->afterburner_forward_accel;
pi->forward_decel_time_const=sinfo->forward_decel;
pi->slide_accel_time_const=sinfo->slide_accel;
pi->slide_decel_time_const=sinfo->slide_decel;
if ( (pi->max_vel.xyz.x > 0.000001f) || (pi->max_vel.xyz.y > 0.000001f) )
pi->flags |= PF_SLIDE_ENABLED;
pi->cur_glide_cap = pi->max_vel.xyz.z; //Init dynamic glide cap stuff to the max vel.
if ( sinfo->glide_cap > 0.000001f || sinfo->glide_cap < -0.000001f ) //Backslash
pi->glide_cap = sinfo->glide_cap;
else
pi->glide_cap = MAX(MAX(pi->max_vel.xyz.z, sinfo->max_overclocked_speed), pi->afterburner_max_vel.xyz.z);
// If there's not a value for +Max Glide Speed set in the table, we want this cap to default to the fastest speed the ship can go.
// However, a negative value means we want no cap, thus allowing nearly infinite maximum gliding speeds.
//SUSHI: If we are using dynamic glide capping, force the glide cap to 0 (understood by physics.cpp to mean the cap should be dynamic)
if (sinfo->glide_dynamic_cap)
pi->glide_cap = 0;
pi->glide_accel_mult = sinfo->glide_accel_mult;
//SUSHI: This defaults to the AI_Profile value, and is only optionally overridden
if (The_mission.ai_profile->flags[AI::Profile_Flags::Use_newtonian_dampening])
pi->flags |= PF_NEWTONIAN_DAMP;
if (sinfo->newtonian_damp_override)
{
if (sinfo->use_newtonian_damp)
pi->flags |= PF_NEWTONIAN_DAMP;
else
pi->flags &= ~PF_NEWTONIAN_DAMP;
}
vm_vec_zero(&pi->vel);
vm_vec_zero(&pi->rotvel);
pi->speed = 0.0f;
pi->heading = 0.0f;
vm_set_identity(&pi->last_rotmat);
//SparK: setting the reverse burners
pi->afterburner_max_reverse_vel = sinfo->afterburner_max_reverse_vel;
pi->afterburner_reverse_accel = sinfo->afterburner_reverse_accel;
}
/**
* Get the type of the given ship as a string
*/
int ship_get_type(char* output, ship_info *sip)
{
if(sip->class_type < 0) {
strcpy(output, "Unknown");
return 0;
}
strcpy(output, Ship_types[sip->class_type].name);
return 1;
}
/**
* Set the orders allowed for a ship -- based on ship type.
*
* This value might get overridden by a value in the mission file.
*/
const std::set<size_t>& ship_get_default_orders_accepted( ship_info *sip )
{
if(sip->class_type >= 0) {
return Ship_types[sip->class_type].ai_player_orders;
} else {
static std::set<size_t> inv_class_set;
return inv_class_set;
}
}
vec3d get_submodel_offset(int model, int submodel){
polymodel*pm = model_get(model);
if(pm->submodel[submodel].parent == -1)
return pm->submodel[submodel].offset;
vec3d ret = pm->submodel[submodel].offset;
vec3d v = get_submodel_offset(model,pm->submodel[submodel].parent);
vm_vec_add2(&ret, &v);
return ret;
}
// Reset all ship values to empty/unused.
void ship::clear()
{
int i;
objnum = -1;
ai_index = -1;
ship_info_index = -1;
hotkey = -1;
escort_priority = 0;
score = 0;
assist_score_pct = 0.0f;
respawn_priority = 0;
pre_death_explosion_happened = 0;
wash_killed = 0; // serenity lies
cargo1 = 0; // "Nothing"
wing_status_wing_index = -1;
wing_status_wing_pos = -1;
alt_type_index = -1;
callsign_index = -1;
targeting_laser_bank = -1;
targeting_laser_objnum = -1;
num_corkscrew_to_fire = 0;
corkscrew_missile_bank = -1;
next_corkscrew_fire = timestamp(0);
final_death_time = timestamp(-1);
death_time = timestamp(-1);
end_death_time = timestamp(-1);
really_final_death_time = timestamp(-1);
deathroll_rotvel = vmd_zero_vector;
if (warpin_effect != nullptr)
delete warpin_effect;
if (warpout_effect != nullptr)
delete warpout_effect;
warpin_effect = nullptr;
warpout_effect = nullptr;
warpin_params_index = -1;
warpout_params_index = -1;
next_fireball = timestamp(-1);
next_hit_spark = timestamp(-1);
num_hits = 0;
memset(sparks, 0, MAX_SHIP_HITS * sizeof(ship_spark));
use_special_explosion = false;
special_exp_damage = -1;
special_exp_blast = -1;
special_exp_inner = -1;
special_exp_outer = -1;
use_shockwave = false;
special_exp_shockwave_speed = 0;
special_exp_deathroll_time = 0;
special_hitpoints = 0;
special_shield = -1;
shield_points.clear();
ship_max_shield_strength = 0.0f;
ship_max_hull_strength = 0.0f;
ship_guardian_threshold = 0;
ship_name[0] = 0;
display_name.clear();
team = 0;
time_cargo_revealed = 0;
arrival_location = 0;
arrival_distance = 0;
arrival_anchor = -1;
arrival_path_mask = 0;
arrival_cue = -1;
arrival_delay = 0;
departure_location = 0;
departure_anchor = -1;
departure_path_mask = 0;
departure_cue = -1;
departure_delay = 0;
wingnum = -1;
orders_accepted.clear();
subsys_list.clear();
// since these aren't cleared by clear()
subsys_list.next = NULL;
subsys_list.prev = NULL;
memset(&subsys_info, 0, SUBSYSTEM_MAX * sizeof(ship_subsys_info));
memset(last_targeted_subobject, 0, MAX_PLAYERS * sizeof(ship_subsys *));
shield_recharge_index = INTIAL_SHIELD_RECHARGE_INDEX;
weapon_recharge_index = INTIAL_WEAPON_RECHARGE_INDEX;
engine_recharge_index = INTIAL_ENGINE_RECHARGE_INDEX;
weapon_energy = 0;
current_max_speed = 0.0f;
next_manage_ets = timestamp(0);
flags.reset();
reinforcement_index = -1;
afterburner_fuel = 0.0f;
afterburner_last_engage_fuel = 0.0f;
afterburner_last_end_time = 0;
cmeasure_count = 0;
current_cmeasure = -1;
cmeasure_fire_stamp = timestamp(0);
target_shields_delta = 0.0f;
target_weapon_energy_delta = 0.0f;
weapons.clear();
// ---------- special weapons init that isn't setting things to 0
for (i = 0; i < MAX_SHIP_PRIMARY_BANKS; i++)
{
// not part of weapons!
primary_rotate_rate[i] = 0.0f;
primary_rotate_ang[i] = 0.0f;
last_fired_point[i] = 0;
// for fighter beams
was_firing_last_frame[i] = 0;
}
secondary_point_reload_pct.init(0, 0, 0.0f);
// ---------- done with weapons init
shield_hits = 0;
wash_intensity = 0.0f;
wash_rot_axis = vmd_zero_vector;
wash_timestamp = timestamp(0);
num_swarm_missiles_to_fire = 0;
next_swarm_fire = timestamp(0);
num_turret_swarm_info = 0;
swarm_missile_bank = -1;
group = -1;
death_roll_snd = sound_handle::invalid();
ship_list_index = -1;
thruster_bitmap = -1;
thruster_frame = 0.0f;
thruster_glow_bitmap = -1;
thruster_glow_frame = 0.0f;
thruster_glow_noise = 1.0f;
thruster_secondary_glow_bitmap = -1;
thruster_tertiary_glow_bitmap = -1;
thruster_distortion_bitmap = -1;
next_engine_stutter = timestamp(0);
base_texture_anim_frametime = 0;
total_damage_received = 0.0f;
memset(&damage_ship, 0, MAX_DAMAGE_SLOTS * sizeof(float));
for(i = 0; i < MAX_DAMAGE_SLOTS; i++)
damage_ship_id[i] = -1;
persona_index = -1;
subsys_disrupted_flags = 0;
subsys_disrupted_check_timestamp = timestamp(0);
create_time = 0;
ts_index = -1;
large_ship_blowup_index = -1;
for (i = 0; i < NUM_SUB_EXPL_HANDLES; i++)
sub_expl_sound_handle[i] = sound_handle::invalid();
memset(&arc_pts, 0, MAX_SHIP_ARCS * 2 * sizeof(vec3d));
for (i = 0; i < MAX_SHIP_ARCS; i++)
arc_timestamp[i] = timestamp(-1);
memset(&arc_type, 0, MAX_SHIP_ARCS * sizeof(ubyte));
arc_next_time = timestamp(-1);
emp_intensity = -1.0f;
emp_decr = 0.0f;
memset(trail_ptr, 0, MAX_SHIP_CONTRAILS * sizeof(trail *));
tag_total = 0.0f;
tag_left = -1.0f;
time_first_tagged = 0;
level2_tag_total = 0.0f;
level2_tag_left = -1.0f;
lightning_stamp = timestamp(-1);
// set awacs warning flags so awacs ship only asks for help once at each level
awacs_warning_flag.reset();
special_warpin_objnum = -1;
special_warpout_objnum = -1;
fighter_beam_turret_data.clear();
beam_sys_info.reset();
primitive_sensor_range = DEFAULT_SHIP_PRIMITIVE_SENSOR_RANGE;
ship_replacement_textures = NULL;
current_viewpoint = -1;
for (i = 0; i < MAX_SHIP_CONTRAILS; i++)
ABtrail_ptr[i] = NULL;
memset(&ab_info, 0, MAX_SHIP_CONTRAILS * sizeof(trail_info));
ab_count = 0;
glow_point_bank_active.clear();
shader_effect_num = 0;
shader_effect_duration = 0;
shader_effect_start_time = 0;
shader_effect_active = false;
alpha_mult = 1.0f;
last_fired_turret = NULL;
bay_doors_anim_done_time = 0;
bay_doors_status = MA_POS_NOT_SET;
bay_doors_wanting_open = 0;
bay_doors_launched_from = 0;
bay_doors_need_open = false;
bay_doors_parent_shipnum = -1;
for(i = 0; i < MAX_MAN_THRUSTERS; i++)
{
thrusters_start[i] = timestamp(-1);
thrusters_sounds[i] = -1;
}
s_alt_classes.clear();
ship_iff_color.clear();
ammo_low_complaint_count = 0;
armor_type_idx = -1;
shield_armor_type_idx = -1;
collision_damage_type_idx = -1;
debris_damage_type_idx = -1;
debris_net_sig = 0;
model_instance_num = -1;
time_created = 0;
radar_visible_since = -1;
radar_last_contact = -1;
radar_last_status = VISIBLE;
radar_current_status = VISIBLE;
team_name = "";
secondary_team_name = "";
team_change_timestamp = timestamp(-1);
team_change_time = 0;
autoaim_fov = 0.0f;
passive_arc_next_times.clear();
}
bool ship::has_display_name() const {
return flags[Ship::Ship_Flags::Has_display_name];
}
const char* ship::get_display_name() const {
if (has_display_name()) {
return display_name.c_str();
} else {
return ship_name;
}
}
void ship::apply_replacement_textures(SCP_vector<texture_replace> &replacements)
{
if (!replacements.empty())
{
ship_replacement_textures = (int *) vm_malloc( MAX_REPLACEMENT_TEXTURES * sizeof(int));
for (auto i = 0; i < MAX_REPLACEMENT_TEXTURES; i++)
ship_replacement_textures[i] = -1;
}
// now fill them in
for (auto tr : replacements)
{
auto pm = model_get(Ship_info[ship_info_index].model_num);
// look for textures
for (auto j = 0; j < pm->n_textures; j++)
{
texture_map *tmap = &pm->maps[j];
int tnum = tmap->FindTexture(tr.old_texture);
if(tnum > -1)
ship_replacement_textures[j * TM_NUM_TYPES + tnum] = tr.new_texture_id;
}
}
}
void ship_weapon::clear()
{
flags.reset();
num_primary_banks = 0;
num_secondary_banks = 0;
num_tertiary_banks = 0;
current_primary_bank = 0;
current_secondary_bank = 0;
current_tertiary_bank = 0;
previous_primary_bank = 0;
previous_secondary_bank = 0;
next_tertiary_fire_stamp = timestamp(0);
for (int i = 0; i < MAX_SHIP_PRIMARY_BANKS; i++)
{
primary_bank_weapons[i] = -1;
primary_bank_external_model_instance[i] = -1;
primary_bank_model_instance_check[i] = false;
next_primary_fire_stamp[i] = timestamp(0);
last_primary_fire_stamp[i] = timestamp(-1);
last_primary_fire_sound_stamp[i] = timestamp(0);
primary_bank_slot_count[i] = 1;
primary_bank_rearm_time[i] = timestamp(0);
primary_animation_done_time[i] = timestamp(0);
primary_bank_ammo[i] = 0;
primary_bank_start_ammo[i] = 0;
primary_bank_capacity[i] = 0;
primary_next_slot[i] = 0;
primary_bank_fof_cooldown[i] = 0;
primary_animation_position[i] = EModelAnimationPosition::MA_POS_NOT_SET;
primary_bank_pattern_index[i] = 0;
burst_counter[i] = 0;
burst_seed[i] = Random::next();
external_model_fp_counter[i] = 0;
}
for (int i = 0; i < MAX_SHIP_SECONDARY_BANKS; i++)
{
secondary_bank_weapons[i] = -1;
next_secondary_fire_stamp[i] = timestamp(0);
last_secondary_fire_stamp[i] = timestamp(-1);
secondary_bank_rearm_time[i] = timestamp(0);
secondary_animation_done_time[i] = timestamp(0);
secondary_bank_ammo[i] = 0;
secondary_bank_start_ammo[i] = 0;
secondary_bank_capacity[i] = 0;
secondary_next_slot[i] = 0;
secondary_animation_position[i] = EModelAnimationPosition::MA_POS_NOT_SET;
secondary_bank_pattern_index[i] = 0;
burst_counter[i + MAX_SHIP_PRIMARY_BANKS] = 0;
external_model_fp_counter[i + MAX_SHIP_PRIMARY_BANKS] = 0;
}
tertiary_bank_ammo = 0;
tertiary_bank_start_ammo = 0;
tertiary_bank_capacity = 0;
tertiary_bank_rearm_time = timestamp(0);
detonate_weapon_time = 0;
ai_class = 0;
remote_detonaters_active = 0;
per_burst_rot = 0.f;
}
ship_weapon::ship_weapon() {
clear();
}
// NOTE: Now that the clear() member function exists, this function only sets the stuff associated with the object and ship class.
static void ship_set(int ship_index, int objnum, int ship_type)
{
int i;
ship *shipp = &Ships[ship_index];
object *objp = &Objects[objnum];
ship_info *sip = &(Ship_info[ship_type]);
ship_weapon *swp = &shipp->weapons;
polymodel *pm = model_get(sip->model_num);
Assert(strlen(shipp->ship_name) <= NAME_LENGTH - 1);
shipp->ship_info_index = ship_type;
shipp->objnum = objnum;
shipp->score = sip->score;
ai_object_init(objp, shipp->ai_index);
physics_ship_init(objp);
if (Fred_running){
shipp->ship_max_hull_strength = 100.0f;
} else {
shipp->ship_max_hull_strength = sip->max_hull_strength;
}
objp->hull_strength = shipp->ship_max_hull_strength;
shipp->max_shield_recharge = sip->max_shield_recharge;
if (sip->flags[Ship::Info_Flags::Model_point_shields]) {
objp->n_quadrants = (int)pm->shield_points.size();
shipp->shield_points = pm->shield_points;
objp->shield_quadrant.resize(objp->n_quadrants);
}
if (Fred_running) {
shipp->ship_max_shield_strength = 100.0f;
objp->shield_quadrant[0] = 100.0f;
} else {
shipp->ship_max_shield_strength = sip->max_shield_strength;
shield_set_strength(objp, shield_get_max_strength(objp));
}
shipp->orders_accepted = ship_get_default_orders_accepted( sip );
if (!subsys_set(objnum))
{
char err_msg[512];
sprintf (err_msg, "Unable to allocate ship subsystems, which shouldn't be possible anymore. Current allocation is %d (%d in use). No subsystems have been assigned to %s.", Num_ship_subsystems_allocated, Num_ship_subsystems, shipp->ship_name);
if (Fred_running)
os::dialogs::Message(os::dialogs::MESSAGEBOX_ERROR, err_msg);
else
Error(LOCATION, "%s", err_msg);
}
ets_init_ship(objp); // init ship fields that are used for the ETS
shipp->current_max_speed = Ship_info[ship_type].max_speed;
shipp->flags.set(Ship_Flags::Engines_on);
// set certain flags that used to be in ship_info - Goober5000
if (sip->flags[Ship::Info_Flags::Stealth])
shipp->flags.set(Ship_Flags::Stealth);
if (sip->flags[Ship::Info_Flags::Ship_class_dont_collide_invis])
shipp->flags.set(Ship_Flags::Dont_collide_invis);
auto obj_flags = objp->flags;
if (sip->flags[Ship::Info_Flags::No_collide])
obj_flags.remove(Object::Object_Flags::Collides);
else
obj_flags.set(Object::Object_Flags::Collides);
obj_set_flags(objp, obj_flags);
if (sip->flags[Ship::Info_Flags::No_ets])
shipp->flags.set(Ship_Flags::No_ets);
shipp->afterburner_fuel = sip->afterburner_fuel_capacity;
shipp->cmeasure_count = sip->cmeasure_max;
shipp->current_cmeasure = sip->cmeasure_type;
if (sip->num_primary_banks == 0 || swp->primary_bank_weapons[0] < 0) {
swp->current_primary_bank = -1;
swp->previous_primary_bank = -1;
}
if (sip->num_secondary_banks == 0 || swp->secondary_bank_weapons[0] < 0) {
swp->current_secondary_bank = -1;
swp->previous_secondary_bank = -1;
}
swp->current_tertiary_bank = -1;
swp->ai_class = Ai_info[shipp->ai_index].ai_class;
// handle ballistic primaries - kinda hackish; is this actually necessary?
// because I think it's not needed - when I accidentally left this unreachable
// it didn't cause any problems - Goober5000
for ( i = 0; i < sip->num_primary_banks; i++ )
{
float weapon_size = Weapon_info[sip->primary_bank_weapons[i]].cargo_size;
if ( weapon_size > 0.0f )
{
if (Fred_running)
swp->primary_bank_ammo[i] = 100;
else
swp->primary_bank_ammo[i] = (int)std::lround(sip->primary_bank_ammo_capacity[i] / weapon_size);
}
}
for ( i = 0; i < sip->num_secondary_banks; i++ )
{
if (Weapon_info[swp->secondary_bank_weapons[i]].wi_flags[Weapon::Info_Flags::SecondaryNoAmmo]) {
swp->secondary_bank_ammo[i] = 0;
continue;
}
float weapon_size = Weapon_info[sip->secondary_bank_weapons[i]].cargo_size;
Assertion( weapon_size > 0.0f, "Cargo size for secondary weapon %s is invalid, must be greater than 0.\n", Weapon_info[sip->secondary_bank_weapons[i]].name );
if (Fred_running)
swp->secondary_bank_ammo[i] = 100;
else
swp->secondary_bank_ammo[i] = (int)std::lround(sip->secondary_bank_ammo_capacity[i] / weapon_size);
}
// set initial reload percentages... used to be done in ship::clear()
int max_points_per_bank = 0;
for (i = 0; i < sip->num_secondary_banks; ++i)
{
int slots = pm->missile_banks[i].num_slots;
if (slots > max_points_per_bank)
max_points_per_bank = slots;
}
shipp->secondary_point_reload_pct.init(sip->num_secondary_banks, max_points_per_bank, 1.0f);
shipp->armor_type_idx = sip->armor_type_idx;
shipp->shield_armor_type_idx = sip->shield_armor_type_idx;
shipp->collision_damage_type_idx = sip->collision_damage_type_idx;
shipp->debris_damage_type_idx = sip->debris_damage_type_idx;
shipp->warpin_params_index = sip->warpin_params_index;
shipp->warpout_params_index = sip->warpout_params_index;
if(pm != NULL && pm->n_view_positions > 0)
ship_set_eye(objp, 0);
else
ship_set_eye(objp, -1);
sip->shockwave.damage_type_idx = sip->shockwave.damage_type_idx_sav;
// Team colors
shipp->team_name.assign( sip->default_team_name);
shipp->secondary_team_name = "none";
shipp->autoaim_fov = sip->autoaim_fov;
shipp->max_shield_regen_per_second = sip->max_shield_regen_per_second;
shipp->max_weapon_regen_per_second = sip->max_weapon_regen_per_second;
for (i = 0; i < (int)sip->ship_passive_arcs.size(); i++)
shipp->passive_arc_next_times.push_back(timestamp(0));
}
/**
* Recalculates the overall strength of subsystems.
*
* Needed because several places in FreeSpace change subsystem strength and all
* this data needs to be kept up to date.
*/
void ship_recalc_subsys_strength( ship *shipp )
{
int i;
ship_subsys *ship_system;
// fill in the subsys_info fields for all particular types of subsystems
// make the current strength be 1.0. If there are initial conditions on the ship, then
// the mission parse code should take care of setting that.
for (i = 0; i < SUBSYSTEM_MAX; i++) {
shipp->subsys_info[i].type_count = 0;
shipp->subsys_info[i].aggregate_max_hits = 0.0f;
shipp->subsys_info[i].aggregate_current_hits = 0.0f;
}
// count all of the subsystems of a particular type. For each generic type of subsystem, we store the
// total count of hits. (i.e. for 3 engines, we store the sum of the max_hits for each engine)
for ( ship_system = GET_FIRST(&shipp->subsys_list); ship_system != END_OF_LIST(&shipp->subsys_list); ship_system = GET_NEXT(ship_system) ) {
if (!(ship_system->flags[Ship::Subsystem_Flags::No_aggregate])) {
int type = ship_system->system_info->type;
Assert ( (type >= 0) && (type < SUBSYSTEM_MAX) );
shipp->subsys_info[type].type_count++;
shipp->subsys_info[type].aggregate_max_hits += ship_system->max_hits;
shipp->subsys_info[type].aggregate_current_hits += ship_system->current_hits;
}
//Get rid of any persistent sounds on the subsystem
//This is inefficient + sloppy but there's not really an easy way to handle things
//if a subsystem is brought back from the dead, other than this
if(ship_system->current_hits > 0.0f)
{
if (ship_system->subsys_snd_flags[Ship::Subsys_Sound_Flags::Dead])
{
obj_snd_delete_type(shipp->objnum, ship_system->system_info->dead_snd, ship_system);
ship_system->subsys_snd_flags.set(Ship::Subsys_Sound_Flags::Dead, false);
}
if ((ship_system->system_info->alive_snd.isValid()) && !(ship_system->subsys_snd_flags[Ship::Subsys_Sound_Flags::Alive]))
{
obj_snd_assign(shipp->objnum, ship_system->system_info->alive_snd, &ship_system->system_info->pnt, OS_SUBSYS_ALIVE, ship_system);
ship_system->subsys_snd_flags.set(Ship::Subsys_Sound_Flags::Alive);
}
if (!(ship_system->subsys_snd_flags[Ship::Subsys_Sound_Flags::Turret_rotation]))
{
if (ship_system->system_info->turret_base_rotation_snd.isValid())
{
obj_snd_assign(shipp->objnum, ship_system->system_info->turret_base_rotation_snd, &ship_system->system_info->pnt, OS_TURRET_BASE_ROTATION, ship_system);
ship_system->subsys_snd_flags.set(Ship::Subsys_Sound_Flags::Turret_rotation);
}
if (ship_system->system_info->turret_gun_rotation_snd.isValid())
{
obj_snd_assign(shipp->objnum, ship_system->system_info->turret_gun_rotation_snd, &ship_system->system_info->pnt, OS_TURRET_GUN_ROTATION, ship_system);
ship_system->subsys_snd_flags.set(Ship::Subsys_Sound_Flags::Turret_rotation);
}
}
if ((ship_system->flags[Ship::Subsystem_Flags::Rotates]) && (ship_system->system_info->rotation_snd.isValid()) && !(ship_system->subsys_snd_flags[Ship::Subsys_Sound_Flags::Rotate]))
{
obj_snd_assign(shipp->objnum, ship_system->system_info->rotation_snd, &ship_system->system_info->pnt, OS_SUBSYS_ROTATION, ship_system);
ship_system->subsys_snd_flags.set(Ship::Subsys_Sound_Flags::Rotate);
}
}
else
{
if (ship_system->subsys_snd_flags[Ship::Subsys_Sound_Flags::Alive])
{
obj_snd_delete_type(shipp->objnum, ship_system->system_info->alive_snd, ship_system);
ship_system->subsys_snd_flags.set(Ship::Subsys_Sound_Flags::Alive, false);;
}
if (ship_system->subsys_snd_flags[Ship::Subsys_Sound_Flags::Turret_rotation])
{
obj_snd_delete_type(shipp->objnum, ship_system->system_info->turret_base_rotation_snd, ship_system);
obj_snd_delete_type(shipp->objnum, ship_system->system_info->turret_gun_rotation_snd, ship_system);
ship_system->subsys_snd_flags.set(Ship::Subsys_Sound_Flags::Turret_rotation, false);
}
if (ship_system->subsys_snd_flags[Ship::Subsys_Sound_Flags::Rotate])
{
obj_snd_delete_type(shipp->objnum, ship_system->system_info->rotation_snd, ship_system);
ship_system->subsys_snd_flags.set(Ship::Subsys_Sound_Flags::Rotate, false);
}
if ((ship_system->system_info->dead_snd.isValid()) && !(ship_system->subsys_snd_flags[Ship::Subsys_Sound_Flags::Dead]))
{
obj_snd_assign(shipp->objnum, ship_system->system_info->dead_snd, &ship_system->system_info->pnt, OS_SUBSYS_DEAD, ship_system);
ship_system->subsys_snd_flags.set(Ship::Subsys_Sound_Flags::Dead, false);
}
}
}
// set any ship flags which should be set. unset the flags since we might be repairing a subsystem
// through sexpressions.
if ( (shipp->subsys_info[SUBSYSTEM_ENGINE].type_count > 0) && (shipp->subsys_info[SUBSYSTEM_ENGINE].aggregate_current_hits <= 0.0f) ) {
shipp->flags.set(Ship_Flags::Disabled);
} else {
shipp->flags.remove(Ship_Flags::Disabled);
ship_reset_disabled_physics( &Objects[shipp->objnum], shipp->ship_info_index );
}
}
/**
* Fixup the model subsystem information for this ship pointer.
* Needed when ships share the same model.
*/
static void ship_copy_subsystem_fixup(ship_info *sip)
{
int model_num;
model_num = sip->model_num;
// no point copying the subsystem data if the ship in question has none...
// mark that the ship (cargo container) has the path fixup done.
if (sip->n_subsystems == 0) {
sip->flags.set(Ship::Info_Flags::Path_fixup);
return;
}
// if we need to get information for all our subsystems, we need to find another ship with the same model
// number as our own and that has the model information
for ( auto it = Ship_info.cbegin(); it != Ship_info.cend(); ++it ) {
model_subsystem *source_msp, *dest_msp;
if ( (it->model_num != model_num) || (&(*it) == sip) ){
continue;
}
// see if this ship has subsystems and a model for the subsystems. We only need check the first
// subsystem since previous error checking would have trapped its loading as an error.
Assert( it->n_subsystems == sip->n_subsystems );
source_msp = &(it->subsystems[0]);
dest_msp = &(sip->subsystems[0]);
if (source_msp->model_num != -1) {
model_copy_subsystems( sip->n_subsystems, dest_msp, source_msp );
} else if (dest_msp->model_num != -1) {
model_copy_subsystems( sip->n_subsystems, source_msp, dest_msp );
} else {
// if none were found try finding a another ship to copy the data from
continue;
}
sip->flags.set(Ship::Info_Flags::Path_fixup);
break;
}
}
// as with object, don't set next and prev to NULL because they keep the object on the free and used lists
void ship_subsys::clear()
{
int i;
system_info = NULL;
parent_objnum = -1;
sub_name[0] = 0;
current_hits = max_hits = 0.0f;
flags.reset();
subsys_guardian_threshold = 0;
armor_type_idx = -1;
turret_last_fire_direction = vmd_zero_vector;
turret_next_enemy_check_stamp = timestamp(0);
turret_next_fire_stamp = timestamp(0);
turret_enemy_objnum = -1;
turret_enemy_sig = 0;
turret_next_fire_pos = 0;
turret_time_enemy_in_range = 0.0f;
turret_inaccuracy = 0.0f;
for (i = 0; i < NUM_TURRET_ORDER_TYPES; i++)
turret_targeting_order[i] = -1;
optimum_range = 0.0f;
favor_current_facing = 0.0f;
targeted_subsys = NULL;
scripting_target_override = false;
last_fired_weapon_info_index = -1;
turret_pick_big_attack_point_timestamp = timestamp(0);
turret_big_attack_point = vmd_zero_vector;
turret_animation_position = MA_POS_NOT_SET;
turret_animation_done_time = 0;
for (i = 0; i < MAX_TFP; i++)
turret_swarm_info_index[i] = -1;
turret_swarm_num = 0;
awacs_intensity = 0.0f;
awacs_radius = 0.0f;
weapons.clear();
submodel_instance_1 = nullptr;
submodel_instance_2 = nullptr;
disruption_timestamp = timestamp(0);
subsys_cargo_name = 0;
time_subsys_cargo_revealed = 0;
triggered_rotation_index = -1;
points_to_target = 0.0f;
base_rotation_rate_pct = 0.0f;
gun_rotation_rate_pct = 0.0f;
subsys_snd_flags.reset();
rotation_timestamp = timestamp(0);
for (i = 0; i < 32; i++)
target_priority[i] = -1;
num_target_priorities = 0;
next_aim_pos_time = 0;
last_aim_enemy_pos = vmd_zero_vector;
last_aim_enemy_vel = vmd_zero_vector;
rof_scaler = 1.0f;
turret_max_bomb_ownage = -1;
turret_max_target_ownage = -1;
}
/**
* Set subsystem
*
* @param objnum Object number (used as index into Objects[])
* @param ignore_subsys_info Default parameter with value of 0. This is only set to 1 by the save/restore code
*/
static int subsys_set(int objnum, int ignore_subsys_info)
{
ship *shipp = &Ships[Objects[objnum].instance];
ship_info *sinfo = &Ship_info[Ships[Objects[objnum].instance].ship_info_index];
model_subsystem *model_system;
ship_subsys *ship_system;
int i, j, k;
// set up the subsystems for this ship. walk through list of subsystems in the ship-info array.
// for each subsystem, get a new ship_subsys instance and set up the pointers and other values
list_init ( &shipp->subsys_list ); // initialize the ship's list of subsystems
// make sure to have allocated the number of subsystems we require
if (!ship_allocate_subsystems( sinfo->n_subsystems )) {
return 0;
}
// make sure we set up the model instance properly
// (we need this to have been done already so we can link the submodels with the subsystems)
Assert(shipp->model_instance_num >= 0);
polymodel_instance *pmi = model_get_instance(shipp->model_instance_num);
Assert(pmi->model_num == sinfo->model_num);
polymodel* pm = model_get(pmi->model_num);
for ( i = 0; i < sinfo->n_subsystems; i++ )
{
model_system = &(sinfo->subsystems[i]);
if (model_system->model_num < 0) {
Warning (LOCATION, "Invalid subobj_num or model_num in subsystem '%s' on ship type '%s'.\nNot linking into ship!\n\n(This warning means that a subsystem was present in the table entry and not present in the model\nit should probably be removed from the table or added to the model.)\n", model_system->subobj_name, sinfo->name );
continue;
}
// set up the linked list
ship_system = GET_FIRST( &ship_subsys_free_list ); // get a new element from the ship_subsystem array
Assert ( ship_system != &ship_subsys_free_list ); // shouldn't have the dummy element
list_remove( ship_subsys_free_list, ship_system ); // remove the element from the array
list_append( &shipp->subsys_list, ship_system ); // link the element into the ship
ship_system->clear(); // initialize it to a known blank slate
ship_system->system_info = model_system; // set the system_info pointer to point to the data read in from the model
ship_system->parent_objnum = objnum;
// link the submodel instance info
if (model_system->subobj_num >= 0) {
ship_system->submodel_instance_1 = &pmi->submodel[model_system->subobj_num];
}
if ((model_system->subobj_num != model_system->turret_gun_sobj) && (model_system->turret_gun_sobj >= 0)) {
ship_system->submodel_instance_2 = &pmi->submodel[model_system->turret_gun_sobj];
}
// if the table has set an alt name copy it
if (ship_system->system_info->alt_sub_name[0] != '\0') {
strcpy_s(ship_system->sub_name, ship_system->system_info->alt_sub_name);
}
else {
memset(ship_system->sub_name, '\0', sizeof(ship_system->sub_name));
}
// copy subsystem target priorities stuff
ship_system->num_target_priorities = ship_system->system_info->num_target_priorities;
for (j = 0; j < 32; j++) {
ship_system->target_priority[j] = ship_system->system_info->target_priority[j];
}
ship_system->rof_scaler = ship_system->system_info->turret_rof_scaler;
// zero flags
ship_system->flags.reset();
ship_system->weapons.flags.reset();
ship_system->subsys_snd_flags.reset();
// Goober5000
if (model_system->flags[Model::Subsystem_Flags::Untargetable])
ship_system->flags.set(Ship::Subsystem_Flags::Untargetable);
// Wanderer
if (model_system->flags[Model::Subsystem_Flags::No_ss_targeting])
ship_system->flags.set(Ship::Subsystem_Flags::No_SS_targeting);
if ((The_mission.ai_profile->flags[AI::Profile_Flags::Advanced_turret_fov_edge_checks]) || (model_system->flags[Model::Subsystem_Flags::Fov_edge_check]))
ship_system->flags.set(Ship::Subsystem_Flags::FOV_edge_check);
if ((The_mission.ai_profile->flags[AI::Profile_Flags::Require_turret_to_have_target_in_fov]) || (model_system->flags[Model::Subsystem_Flags::Fov_required]))
ship_system->flags.set(Ship::Subsystem_Flags::FOV_Required);
if (model_system->flags[Model::Subsystem_Flags::No_replace])
ship_system->flags.set(Ship::Subsystem_Flags::No_replace);
if (model_system->flags[Model::Subsystem_Flags::No_live_debris])
ship_system->flags.set(Ship::Subsystem_Flags::No_live_debris);
if (model_system->flags[Model::Subsystem_Flags::Ignore_if_dead])
ship_system->flags.set(Ship::Subsystem_Flags::Missiles_ignore_if_dead);
if (model_system->flags[Model::Subsystem_Flags::Allow_vanishing])
ship_system->flags.set(Ship::Subsystem_Flags::Vanished);
if (model_system->flags[Model::Subsystem_Flags::Damage_as_hull])
ship_system->flags.set(Ship::Subsystem_Flags::Damage_as_hull);
if (model_system->flags[Model::Subsystem_Flags::No_aggregate])
ship_system->flags.set(Ship::Subsystem_Flags::No_aggregate);
if (model_system->flags[Model::Subsystem_Flags::Rotates])
ship_system->flags.set(Ship::Subsystem_Flags::Rotates);
if (model_system->flags[Model::Subsystem_Flags::Player_turret_sound])
ship_system->flags.set(Ship::Subsystem_Flags::Play_sound_for_player);
if (model_system->flags[Model::Subsystem_Flags::No_disappear])
ship_system->flags.set(Ship::Subsystem_Flags::No_disappear);
if (model_system->flags[Model::Subsystem_Flags::Autorepair_if_disabled])
ship_system->flags.set(Ship::Subsystem_Flags::Autorepair_if_disabled);
if (model_system->flags[Model::Subsystem_Flags::No_autorepair_if_disabled])
ship_system->flags.set(Ship::Subsystem_Flags::No_autorepair_if_disabled);
if (model_system->flags[Model::Subsystem_Flags::Turret_locked])
ship_system->weapons.flags.set(Ship::Weapon_Flags::Turret_Lock);
// check the mission flag to possibly free all beam weapons - Goober5000, taken from SEXP.CPP, and moved to subsys_set() by Asteroth
if (The_mission.flags[Mission::Mission_Flags::Beam_free_all_by_default])
ship_system->weapons.flags.set(Ship::Weapon_Flags::Beam_Free);
// Goober5000 - this has to be moved outside back to parse_create_object, because
// a lot of the ship creation code is duplicated in several points and overwrites
// previous things... ugh.
ship_system->max_hits = model_system->max_subsys_strength; // * shipp->ship_max_hull_strength / sinfo->max_hull_strength;
if ( !Fred_running ) {
ship_system->current_hits = ship_system->max_hits; // set the current hits
} else {
ship_system->current_hits = 0.0f; // Jason wants this to be 0 in Fred.
}
ship_system->subsys_guardian_threshold = 0;
ship_system->armor_type_idx = model_system->armor_type_idx;
ship_system->turret_next_fire_stamp = timestamp(0);
ship_system->turret_next_enemy_check_stamp = timestamp(0);
ship_system->turret_enemy_objnum = -1;
ship_system->turret_next_fire_stamp = timestamp((int) frand_range(1.0f, 500.0f)); // next time this turret can fire
ship_system->turret_last_fire_direction = model_system->turret_norm;
ship_system->turret_next_fire_pos = 0;
ship_system->turret_time_enemy_in_range = 0.0f;
ship_system->disruption_timestamp=timestamp(0);
ship_system->turret_pick_big_attack_point_timestamp = timestamp(0);
ship_system->scripting_target_override = false;
vm_vec_zero(&ship_system->turret_big_attack_point);
for(j = 0; j < NUM_TURRET_ORDER_TYPES; j++)
{
//WMC - Set targeting order to default.
ship_system->turret_targeting_order[j] = j;
}
ship_system->optimum_range = model_system->optimum_range;
ship_system->favor_current_facing = model_system->favor_current_facing;
ship_system->subsys_cargo_name = 0;
ship_system->time_subsys_cargo_revealed = 0;
j = 0;
int number_of_weapons = 0;
for (k=0; k<MAX_SHIP_PRIMARY_BANKS; k++){
if (model_system->primary_banks[k] != -1) {
ship_system->weapons.primary_bank_weapons[j] = model_system->primary_banks[k];
ship_system->weapons.primary_bank_capacity[j] = model_system->primary_bank_capacity[k]; // added by Goober5000
ship_system->weapons.next_primary_fire_stamp[j] = timestamp(0);
ship_system->weapons.last_primary_fire_stamp[j++] = -1;
}
ship_system->weapons.burst_counter[k] = 0;
}
ship_system->weapons.num_primary_banks = j;
number_of_weapons += j;
j = 0;
for (k=0; k<MAX_SHIP_SECONDARY_BANKS; k++){
if (model_system->secondary_banks[k] != -1) {
ship_system->weapons.secondary_bank_weapons[j] = model_system->secondary_banks[k];
ship_system->weapons.secondary_bank_capacity[j] = model_system->secondary_bank_capacity[k];
ship_system->weapons.next_secondary_fire_stamp[j] = timestamp(0);
ship_system->weapons.last_secondary_fire_stamp[j++] = -1;
}
ship_system->weapons.burst_counter[k + MAX_SHIP_PRIMARY_BANKS] = 0;
}
ship_system->weapons.num_secondary_banks = j;
number_of_weapons += j;
ship_system->weapons.current_primary_bank = -1;
ship_system->weapons.current_secondary_bank = -1;
ship_system->next_aim_pos_time = 0;
ship_system->turret_max_bomb_ownage = model_system->turret_max_bomb_ownage;
ship_system->turret_max_target_ownage = model_system->turret_max_target_ownage;
// Make turret flag checks and warnings
if ((ship_system->system_info->flags[Model::Subsystem_Flags::Turret_salvo]) && (ship_system->system_info->flags[Model::Subsystem_Flags::Turret_fixed_fp]))
{
Warning (LOCATION, "\"salvo mode\" flag used with \"fixed firingpoints\" flag\nsubsystem '%s' on ship type '%s'.\n\"salvo mode\" flag is ignored\n", model_system->subobj_name, sinfo->name );
ship_system->system_info->flags.remove(Model::Subsystem_Flags::Turret_salvo);
}
if ((ship_system->system_info->flags[Model::Subsystem_Flags::Turret_salvo]) && (model_system->turret_num_firing_points < 2))
{
Warning (LOCATION, "\"salvo mode\" flag used with turret which has less than two firingpoints\nsubsystem '%s' on ship type '%s'.\n\"salvo mode\" flag is ignored\n", model_system->subobj_name, sinfo->name );
ship_system->system_info->flags.remove(Model::Subsystem_Flags::Turret_salvo);
}
if ((ship_system->system_info->flags[Model::Subsystem_Flags::Turret_fixed_fp]) && (model_system->turret_num_firing_points < 2))
{
Warning (LOCATION, "\"fixed firingpoints\" flag used with turret which has less than two firingpoints\nsubsystem '%s' on ship type '%s'.\n\"fixed firingpoints\" flag is ignored\n", model_system->subobj_name, sinfo->name );
ship_system->system_info->flags.remove(Model::Subsystem_Flags::Turret_fixed_fp);
}
if ((ship_system->system_info->flags[Model::Subsystem_Flags::Turret_salvo]) && (ship_system->system_info->flags[Model::Subsystem_Flags::Use_multiple_guns]))
{
Warning (LOCATION, "\"salvo mode\" flag used with \"use multiple guns\" flag\nsubsystem '%s' on ship type '%s'.\n\"use multiple guns\" flag is ignored\n", model_system->subobj_name, sinfo->name );
ship_system->system_info->flags.remove(Model::Subsystem_Flags::Use_multiple_guns);
}
if ((ship_system->system_info->flags[Model::Subsystem_Flags::Turret_fixed_fp]) && !(ship_system->system_info->flags[Model::Subsystem_Flags::Use_multiple_guns]))
{
Warning (LOCATION, "\"fixed firingpoints\" flag used without \"use multiple guns\" flag\nsubsystem '%s' on ship type '%s'.\n\"use multiple guns\" guns added by default\n", model_system->subobj_name, sinfo->name );
ship_system->system_info->flags.set(Model::Subsystem_Flags::Use_multiple_guns);
}
if ((ship_system->system_info->flags[Model::Subsystem_Flags::Turret_salvo]) && (number_of_weapons > 1))
{
mprintf(("\"salvo mode\" flag used with turret which has more than one weapon defined for it\nsubsystem '%s' on ship type '%s'.\nonly single weapon will be used\n", model_system->subobj_name, sinfo->name));
}
if ((ship_system->system_info->flags[Model::Subsystem_Flags::Turret_fixed_fp]) && (number_of_weapons > model_system->turret_num_firing_points))
{
mprintf(("\"fixed firingpoint\" flag used with turret which has more weapons defined for it than it has firingpoints\nsubsystem '%s' on ship type '%s'.\nweapons will share firingpoints\n", model_system->subobj_name, sinfo->name));
}
if ((ship_system->system_info->flags[Model::Subsystem_Flags::Turret_fixed_fp]) && (number_of_weapons < model_system->turret_num_firing_points))
{
mprintf(("\"fixed firingpoint\" flag used with turret which has less weapons defined for it than it has firingpoints\nsubsystem '%s' on ship type '%s'.\nsome of the firingpoints will be left unused\n", model_system->subobj_name, sinfo->name));
}
if ((ship_system->system_info->flags[Model::Subsystem_Flags::Share_fire_direction]) && (!(ship_system->system_info->flags[Model::Subsystem_Flags::Turret_salvo]) && !(ship_system->system_info->flags[Model::Subsystem_Flags::Use_multiple_guns])))
{
Warning(LOCATION, "\"share fire direction\" flag used with turret which does not have the \"salvo mode\" or \"use multiple guns\" flag set\nsubsystem '%s' on ship type '%s'.\n\"share fire direction\" flag is ignored\n", model_system->subobj_name, sinfo->name);
ship_system->system_info->flags.remove(Model::Subsystem_Flags::Share_fire_direction);
}
for (k=0; k<ship_system->weapons.num_secondary_banks; k++) {
if (Weapon_info[ship_system->weapons.secondary_bank_weapons[k]].wi_flags[Weapon::Info_Flags::SecondaryNoAmmo])
{
ship_system->weapons.secondary_bank_ammo[k] = 0;
ship_system->weapons.secondary_next_slot[k] = 0;
continue;
}
float weapon_size = Weapon_info[ship_system->weapons.secondary_bank_weapons[k]].cargo_size;
Assertion( weapon_size > 0.0f, "Cargo size for secondary weapon %s is invalid, must be greater than 0.\n", Weapon_info[ship_system->weapons.secondary_bank_weapons[k]].name );
ship_system->weapons.secondary_bank_ammo[k] = (Fred_running ? 100 : (int)std::lround(ship_system->weapons.secondary_bank_capacity[k] / weapon_size));
ship_system->weapons.secondary_next_slot[k] = 0;
}
// Goober5000
for (k=0; k<ship_system->weapons.num_primary_banks; k++)
{
float weapon_size = Weapon_info[ship_system->weapons.primary_bank_weapons[k]].cargo_size;
if (weapon_size > 0.0f) { // Non-ballistic primaries are supposed to have a cargo_size of 0
ship_system->weapons.primary_bank_ammo[k] = (Fred_running ? 100 : (int)std::lround(ship_system->weapons.primary_bank_capacity[k] / weapon_size));
}
}
ship_system->weapons.remote_detonaters_active = 0;
ship_system->weapons.detonate_weapon_time = -1;
ship_system->weapons.ai_class = sinfo->ai_class; // assume ai class of ship for turret
// rapid fire (swarm) stuff
for (k = 0; k < MAX_TFP; k++)
ship_system->turret_swarm_info_index[k] = -1;
ship_system->turret_swarm_num = 0;
// AWACS stuff
ship_system->awacs_intensity = model_system->awacs_intensity;
ship_system->awacs_radius = model_system->awacs_radius;
if (ship_system->awacs_intensity > 0) {
ship_system->system_info->flags.set(Model::Subsystem_Flags::Awacs);
}
// turn_rate, turn_accel
if (ship_system->submodel_instance_1 != nullptr)
model_set_submodel_instance_motion_info(&pm->submodel[model_system->subobj_num], ship_system->submodel_instance_1);
}
if ( !ignore_subsys_info ) {
ship_recalc_subsys_strength( shipp );
}
return 1;
}
/**
* Modify the matrix orient by the slew angles a.
*/
void compute_slew_matrix(matrix *orient, angles *a)
{
matrix tmp, tmp2;
angles t1, t2;
t1 = t2 = *a;
t1.h = 0.0f; t1.b = 0.0f;
t2.p = 0.0f; t2.b = 0.0f;
// put in p & b like normal
vm_angles_2_matrix(&tmp, &t2 ); // Changed the order of axis rotations. First pitch, then yaw (Swifty)
vm_matrix_x_matrix( &tmp2, orient, &tmp);
// Put in heading separately
vm_angles_2_matrix(&tmp, &t1 );
vm_matrix_x_matrix( orient, &tmp2, &tmp );
vm_orthogonalize_matrix(orient);
}
int Ship_shadows = 0;
DCF_BOOL( ship_shadows, Ship_shadows )
MONITOR( NumShipsRend )
int Show_shield_hits = 0;
DCF_BOOL( show_shield_hits, Show_shield_hits )
int Show_tnorms = 0;
DCF_BOOL( show_tnorms, Show_tnorms )
int Show_paths = 0;
DCF_BOOL( show_paths, Show_paths )
int Show_fpaths = 0;
DCF_BOOL( show_fpaths, Show_fpaths )
static void ship_find_warping_ship_helper(object *objp, dock_function_info *infop)
{
// only check ships
if (objp->type != OBJ_SHIP)
return;
// am I arriving or departing by warp?
if (Ships[objp->instance].is_arriving(ship::warpstage::BOTH, true) || Ships[objp->instance].flags[Ship_Flags::Depart_warp])
{
#ifndef NDEBUG
// in debug builds, make sure only one of the docked objects has these flags set
if (infop->maintained_variables.bool_value)
{
//WMC - This is annoying and triggered in sm2-10
//Warning(LOCATION, "Ship %s and its docked ship %s are arriving or departing at the same time.\n",
//Ships[infop->maintained_variables.objp_value->instance].ship_name, Ships[objp->instance].ship_name);
}
#endif
// we found someone
infop->maintained_variables.bool_value = true;
infop->maintained_variables.objp_value = objp;
#ifdef NDEBUG
// return early in release builds
infop->early_return_condition = true;
#endif
}
}
//WMC - used for FTL and maneuvering thrusters
extern bool Rendering_to_shadow_map;
void ship_render_cockpit(object *objp)
{
if(objp->type != OBJ_SHIP || objp->instance < 0)
return;
ship *shipp = &Ships[objp->instance];
ship_info *sip = &Ship_info[shipp->ship_info_index];
if(sip->cockpit_model_num < 0)
return;
polymodel *pm = model_get(sip->cockpit_model_num);
Assert(pm != NULL);
//Setup
gr_reset_clip();
hud_save_restore_camera_data(1);
matrix eye_ori = vmd_identity_matrix;
vec3d eye_pos = vmd_zero_vector;
ship_get_eye(&eye_pos, &eye_ori, objp, false, true);
vec3d pos = vmd_zero_vector;
vm_vec_unrotate(&pos, &sip->cockpit_offset, &eye_ori);
bool shadow_override_backup = Shadow_override;
//Deal with the model
model_clear_instance(sip->cockpit_model_num);
if (Shadow_quality != ShadowQuality::Disabled) {
gr_reset_clip();
Shadow_override = false;
shadows_start_render(&Eye_matrix, &leaning_position, Proj_fov, gr_screen.clip_aspect, std::get<0>(Shadow_distances_cockpit), std::get<1>(Shadow_distances_cockpit), std::get<2>(Shadow_distances_cockpit), std::get<3>(Shadow_distances_cockpit));
model_render_params shadow_render_info;
shadow_render_info.set_detail_level_lock(0);
shadow_render_info.set_flags(MR_NO_TEXTURING | MR_NO_LIGHTING);
model_render_immediate(&shadow_render_info, sip->cockpit_model_num, &eye_ori, &pos);
shadows_end_render();
gr_clear_states();
}
gr_set_proj_matrix(Proj_fov, gr_screen.clip_aspect, 0.02f, 10.0f * pm->rad);
gr_set_view_matrix(&leaning_position, &Eye_matrix);
uint render_flags = MR_NORMAL;
render_flags |= MR_NO_FOGGING;
if (shipp->flags[Ship::Ship_Flags::Glowmaps_disabled]) {
render_flags |= MR_NO_GLOWMAPS;
}
model_render_params render_info;
render_info.set_detail_level_lock(0);
render_info.set_flags(render_flags);
render_info.set_replacement_textures(Player_cockpit_textures);
model_render_immediate(&render_info, sip->cockpit_model_num, &eye_ori, &pos);
gr_end_view_matrix();
gr_end_proj_matrix();
hud_save_restore_camera_data(0);
//Restore the Shadow_override
if (Shadow_quality != ShadowQuality::Disabled)
Shadow_override = shadow_override_backup;
}
void ship_render_show_ship_cockpit(object *objp)
{
if (objp->type != OBJ_SHIP || objp->instance < 0)
return;
vec3d cockpit_eye_pos;
matrix dummy;
ship_get_eye(&cockpit_eye_pos, &dummy, objp, true, false); // Get cockpit eye position
gr_end_view_matrix();
gr_end_proj_matrix();
gr_set_proj_matrix(Proj_fov, gr_screen.clip_aspect, 0.05f, Max_draw_distance);
gr_set_view_matrix(&cockpit_eye_pos, &Eye_matrix); // Set Camera to cockpit eye position
Glowpoint_override = true; // Turn off glowpoints so they dont get rendered fixed at origin
model_render_params render_info;
render_info.set_object_number(OBJ_INDEX(objp));
render_info.set_replacement_textures(Ships[objp->instance].ship_replacement_textures);
render_info.set_detail_level_lock(0);
model_render_immediate(&render_info, Ship_info[Ships[objp->instance].ship_info_index].model_num, &objp->orient,
&objp->pos); // Render ship model with fixed detail level 0 so its not switching LOD when
// moving away from origin
Glowpoint_override = false;
gr_end_view_matrix();
gr_end_proj_matrix();
gr_set_proj_matrix(Proj_fov, gr_screen.clip_aspect, Min_draw_distance, Max_draw_distance);
gr_set_view_matrix(&Eye_position, &Eye_matrix); // Reset Camera to normal
}
void ship_init_cockpit_displays(ship *shipp)
{
ship_info *sip = &Ship_info[shipp->ship_info_index];
int cockpit_model_num = sip->cockpit_model_num;
// don't bother creating cockpit texture replacements if this ship has no cockpit
if ( cockpit_model_num < 0 ) {
return;
}
// check if we even have cockpit displays
if ( sip->displays.empty() ) {
return;
}
if ( Player_cockpit_textures != NULL) {
return;
}
// ship's cockpit texture replacements haven't been setup yet, so do it.
Player_cockpit_textures = (int *) vm_malloc(MAX_REPLACEMENT_TEXTURES * sizeof(int));
int i;
for ( i = 0; i < MAX_REPLACEMENT_TEXTURES; i++ ) {
Player_cockpit_textures[i] = -1;
}
for ( i = 0; i < (int)sip->displays.size(); i++ ) {
ship_add_cockpit_display(&sip->displays[i], cockpit_model_num);
}
ship_set_hud_cockpit_targets();
}
void ship_clear_cockpit_displays()
{
for ( int i = 0; i < (int)Player_displays.size(); i++ ) {
if ( Player_displays[i].background >= 0 ) {
bm_release(Player_displays[i].background);
}
if ( Player_displays[i].foreground >= 0 ) {
bm_release(Player_displays[i].foreground);
}
if ( Player_displays[i].target >= 0 ) {
bm_release(Player_displays[i].target);
}
}
Player_displays.clear();
if ( Player_cockpit_textures != NULL ) {
vm_free(Player_cockpit_textures);
Player_cockpit_textures = NULL;
}
}
static void ship_add_cockpit_display(cockpit_display_info *display, int cockpit_model_num)
{
if ( strlen(display->filename) <= 0 ) {
return;
}
if( cockpit_model_num < 0 ) {
return;
}
int i, tm_num, glow_target = -1, glow_handle = -1, diffuse_handle = -1;
int w, h;
cockpit_display new_display;
// if no texture target has been found yet, find one.
polymodel *pm = model_get(cockpit_model_num);
for ( i = 0; i < pm->n_textures; i++ )
{
tm_num = pm->maps[i].FindTexture(display->filename);
if ( tm_num >= 0 ) {
glow_target = i*TM_NUM_TYPES+TM_GLOW_TYPE;
diffuse_handle = pm->maps[i].textures[TM_BASE_TYPE].GetTexture();
glow_handle = pm->maps[i].textures[TM_GLOW_TYPE].GetTexture();
break;
}
}
// create a render target for this cockpit texture
if ( Player_cockpit_textures[glow_target] < 0) {
bm_get_info(diffuse_handle, &w, &h);
Player_cockpit_textures[glow_target] = bm_make_render_target(w, h, BMP_FLAG_RENDER_TARGET_DYNAMIC);
// if no render target was made, bail
if ( Player_cockpit_textures[glow_target] < 0 ) {
return;
}
}
new_display.background = -1;
if ( display->bg_filename[0] != '\0' ) {
new_display.background = bm_load(display->bg_filename);
if ( new_display.background < 0 ) {
Warning(LOCATION, "Unable to load background %s for cockpit display %s", display->bg_filename, display->name);
}
}
new_display.foreground = -1;
if ( display->fg_filename[0] != '\0' ) {
new_display.foreground = bm_load(display->fg_filename);
if ( new_display.foreground < 0 ) {
Warning(LOCATION, "Unable to load background %s for cockpit display %s", display->fg_filename, display->name);
}
}
strcpy_s(new_display.name, display->name);
new_display.offset[0] = display->offset[0];
new_display.offset[1] = display->offset[1];
new_display.size[0] = display->size[0];
new_display.size[1] = display->size[1];
new_display.source = glow_handle;
new_display.target = Player_cockpit_textures[glow_target];
Player_displays.push_back(new_display);
}
static void ship_set_hud_cockpit_targets()
{
if ( !Ship_info[Player_ship->ship_info_index].hud_enabled ) {
return;
}
auto& hud = Ship_info[Player_ship->ship_info_index].hud_gauges;
for ( int i = 0; i < (int)hud.size(); i++ ) {
for ( int j = 0; j < (int)Player_displays.size(); j++ ) {
hud[i]->setCockpitTarget(&Player_displays[j]);
}
}
}
int ship_start_render_cockpit_display(size_t cockpit_display_num)
{
// make sure this thing even has a cockpit
if ( Ship_info[Player_ship->ship_info_index].cockpit_model_num < 0 ) {
return -1;
}
if ( Player_cockpit_textures == NULL ) {
return -1;
}
// check sanity of the cockpit display handle
if ( cockpit_display_num >= Player_displays.size()) {
return -1;
}
cockpit_display* display = &Player_displays[cockpit_display_num];
if ( display->target < 0 ) {
return -1;
}
if ( !bm_set_render_target(display->target) ) {
return -1;
}
gr_push_debug_group("Render cockpit display");
int cull = gr_set_cull(0);
gr_clear();
if ( display->source >= 0 ) {
gr_set_bitmap(display->source);
gr_bitmap(0, 0, GR_RESIZE_NONE);
}
if ( display->background >= 0 ) {
gr_set_bitmap(display->background);
gr_bitmap_ex(display->offset[0], display->offset[1], display->size[0], display->size[1], 0, 0, GR_RESIZE_NONE);
}
gr_set_cull(cull);
return display->target;
}
void ship_end_render_cockpit_display(size_t cockpit_display_num)
{
// make sure this thing even has a cockpit
if ( Ship_info[Player_ship->ship_info_index].cockpit_model_num < 0 ) {
return;
}
if ( Player_cockpit_textures == NULL ) {
return;
}
// check sanity of the cockpit display handle
if ( cockpit_display_num >= Player_displays.size()) {
return;
}
cockpit_display* display = &Player_displays[cockpit_display_num];
int cull = gr_set_cull(0);
if ( display->foreground >= 0 ) {
gr_reset_clip();
gr_set_bitmap(display->foreground);
gr_bitmap_ex(display->offset[0], display->offset[1], display->size[0], display->size[1], 0, 0, GR_RESIZE_NONE);
}
gr_set_cull(cull);
bm_set_render_target(-1);
gr_pop_debug_group();
}
static void ship_subsystems_delete(ship *shipp)
{
if ( NOT_EMPTY(&shipp->subsys_list) )
{
ship_subsys *systemp, *temp;
systemp = GET_FIRST( &shipp->subsys_list );
while ( systemp != END_OF_LIST(&shipp->subsys_list) ) {
temp = GET_NEXT( systemp ); // use temporary since pointers will get screwed with next operation
list_remove( shipp->subsys_list, systemp ); // remove the element
list_append( &ship_subsys_free_list, systemp ); // and place back onto free list
Num_ship_subsystems--; // subtract from our in-use total
systemp = temp; // use the temp variable to move right along
}
}
}
void ship_delete( object * obj )
{
ship *shipp;
int num, objnum __UNUSED;
num = obj->instance;
Assert( num >= 0);
objnum = OBJ_INDEX(obj);
Assert( Ships[num].objnum == objnum );
shipp = &Ships[num];
if (shipp->ai_index != -1){
ai_free_slot(shipp->ai_index);
}
// free up the list of subsystems of this ship. walk through list and move remaining subsystems
// on ship back to the free list for other ships to use.
ship_subsystems_delete(&Ships[num]);
shipp->objnum = -1;
animation::ModelAnimationSet::stopAnimations(model_get_instance(shipp->model_instance_num));
if (shipp->ship_replacement_textures != NULL) {
vm_free(shipp->ship_replacement_textures);
shipp->ship_replacement_textures = NULL;
}
// glow point banks
shipp->glow_point_bank_active.clear();
if ( shipp->ship_list_index != -1 ) {
ship_obj_list_remove(shipp->ship_list_index);
shipp->ship_list_index = -1;
}
free_sexp2(shipp->arrival_cue);
free_sexp2(shipp->departure_cue);
// call the contrail system
ct_ship_delete(shipp);
model_delete_instance(shipp->model_instance_num);
// free up any weapon model instances
for (int i = 0; i < shipp->weapons.num_primary_banks; ++i)
{
if (shipp->weapons.primary_bank_external_model_instance[i] >= 0)
model_delete_instance(shipp->weapons.primary_bank_external_model_instance[i]);
}
}
/**
* Used by ::ship_cleanup which is called if the ship is in a wing.
*
* This function updates the ship_index list (i.e. removes its entry in the list)
* and packs the array accordingly.
*/
void ship_wing_cleanup( int shipnum, wing *wingp )
{
int i, index = -1, team = Ships[shipnum].team;
// find this ship's position within its wing
for (i = 0; i < wingp->current_count; i++)
{
if (wingp->ship_index[i] == shipnum)
{
index = i;
break;
}
}
// this can happen in multiplayer (dogfight, ingame join specifically)
if (index == -1)
return;
// compress the ship_index array and mark the last entry with a -1
for (i = index; i < wingp->current_count - 1; i++)
wingp->ship_index[i] = wingp->ship_index[i+1];
wingp->current_count--;
Assert ( wingp->current_count >= 0 );
wingp->ship_index[wingp->current_count] = -1;
// adjust the special ship if necessary
if (wingp->special_ship > 0 && wingp->special_ship >= index)
wingp->special_ship--;
// if the current count is 0, check to see if the wing departed or was destroyed.
if (wingp->current_count == 0)
{
// if this wing was ordered to depart by the player, set the current_wave equal to the total
// waves so we can mark the wing as gone and no other ships arrive
// Goober5000 - also if it's departing... this is sort of, but not exactly, what :V: did;
// but it seems to be consistent with how it should behave
if (wingp->flags[Ship::Wing_Flags::Departing, Ship::Wing_Flags::Departure_ordered])
wingp->current_wave = wingp->num_waves;
// Goober5000 - some changes for clarity and closing holes
// make sure to flag the wing as gone if all of its member ships are gone and no more can arrive
if ((wingp->current_wave == wingp->num_waves) && (wingp->total_destroyed + wingp->total_departed + wingp->total_vanished == wingp->total_arrived_count))
{
// mark the wing as gone
wingp->flags.set(Ship::Wing_Flags::Gone);
wingp->time_gone = Missiontime;
// if all ships were destroyed, log it as destroyed
if (wingp->total_destroyed == wingp->total_arrived_count)
{
// first, be sure to mark a wing destroyed event if all members of wing were destroyed and on
// the last wave. This circumvents a problem where the wing could be marked as departed and
// destroyed if the last ships were destroyed after the wing's departure cue became true.
mission_log_add_entry(LOG_WING_DESTROYED, wingp->name, NULL, team);
}
// if some ships escaped, log it as departed
else if (wingp->total_vanished != wingp->total_arrived_count)
{
// if the wing wasn't destroyed, and it is departing, then mark it as departed -- in this
// case, there had better be ships in this wing with departure entries in the log file. The
// logfile code checks for this case.
mission_log_add_entry(LOG_WING_DEPARTED, wingp->name, NULL, team);
}
#ifndef NDEBUG
//WMC - Ships can depart too, besides being destroyed :P
if ((wingp->total_destroyed + wingp->total_departed + wingp->total_vanished) != wingp->total_arrived_count)
{
// apparently, there have been reports of ships still present in the mission when this log
// entry if written. Do a sanity check here to find out for sure.
for (ship_obj *so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so))
{
// skip the player -- stupid special case.
if (&Objects[so->objnum] == Player_obj)
continue;
if ((Game_mode & GM_MULTIPLAYER) && (Net_player->flags & NETINFO_FLAG_INGAME_JOIN))
continue;
if ((Ships[Objects[so->objnum].instance].wingnum == WING_INDEX(wingp)) && !(Ships[Objects[so->objnum].instance].is_dying_or_departing()))
{
// TODO: I think this Int3() is triggered when a wing whose ships are all docked to ships of another
// wing departs. It can be reliably seen in TVWP chapter 1 mission 7, when Torino and Iota wing depart.
// Not sure how to fix this. -- Goober5000
Int3();
}
}
}
#endif
}
}
}
// functions to do management, like log entries and wing cleanup after a ship has been destroyed
// Goober5000
static void ship_actually_depart_helper(object *objp, dock_function_info *infop)
{
// do standard departure stuff first
objp->flags.set(Object::Object_Flags::Should_be_dead);
if (objp->type == OBJ_SHIP)
ship_cleanup(objp->instance, infop->parameter_variables.bool_value ? SHIP_VANISHED : SHIP_DEPARTED);
// do the end-mission stuff if it's the player ship
if (objp == Player_obj)
gameseq_post_event(GS_EVENT_PLAYER_WARPOUT_DONE);
}
/**
* Used to actually remove a ship, plus all the ships it's docked to, from the mission
*/
void ship_actually_depart(int shipnum, int method)
{
dock_function_info dfi;
dfi.parameter_variables.bool_value = (method == SHIP_VANISHED ? true:false);
dock_evaluate_all_docked_objects(&Objects[Ships[shipnum].objnum], &dfi, ship_actually_depart_helper);
// in a couple of cases we'll need to send a packet to update clients
if (MULTIPLAYER_MASTER && ((method == SHIP_DEPARTED_BAY) || (method == SHIP_VANISHED)) ) {
send_ship_depart_packet(&Objects[Ships[shipnum].objnum], method);
}
}
// no destruction effects, not for player destruction and multiplayer, only self-destruction
void ship_destroy_instantly(object *ship_objp, bool with_debris)
{
Assert(ship_objp->type == OBJ_SHIP);
Assert(!(ship_objp == Player_obj));
// add scripting hook for 'On Ship Death Started' -- Goober5000
// hook is placed at the beginning of this function to allow the scripter to
// actually have access to the ship before any death routines (such as mission logging) are executed
OnShipDeathStartedHook->run(scripting::hook_param_list(scripting::hook_param("Ship", 'o', ship_objp)));
// undocking and death preparation
ship_stop_fire_primary(ship_objp);
ai_deathroll_start(ship_objp);
if (with_debris)
shipfx_blow_up_model(ship_objp, 0, 0, &ship_objp->pos);
mission_log_add_entry(LOG_SELF_DESTRUCTED, Ships[ship_objp->instance].ship_name, NULL );
// scripting stuff
if (Script_system.IsActiveAction(CHA_DEATH)) {
Script_system.SetHookObjects(2, "Self", ship_objp, "Ship", ship_objp);
Script_system.RunCondition(CHA_DEATH, ship_objp);
Script_system.RemHookVars({"Self", "Ship"});
}
ship_objp->flags.set(Object::Object_Flags::Should_be_dead);
ship_cleanup(ship_objp->instance, SHIP_DESTROYED);
}
// convert the departure int method to a string --wookieejedi
static const char* get_departure_name(int method)
{
const char* departname;
switch (method) {
case SHIP_DEPARTED_WARP: {
departname = "SHIP_DEPARTED_WARP";
break;
};
case SHIP_DEPARTED_BAY: {
departname = "SHIP_DEPARTED_BAY";
break;
};
case SHIP_VANISHED: {
departname = "SHIP_VANISHED";
break;
};
case SHIP_DEPARTED_REDALERT: {
departname = "SHIP_DEPARTED_REDALERT";
break;
};
// assume SHIP_DEPARTED
default:
departname = "SHIP_DEPARTED";
};
return departname;
}
/**
* Merge ship_destroyed and ship_departed and ship_vanished
*/
void ship_cleanup(int shipnum, int cleanup_mode)
{
Assert(shipnum >= 0 && shipnum < MAX_SHIPS);
Assert(cleanup_mode &
(SHIP_DESTROYED | SHIP_DEPARTED | SHIP_VANISHED | SHIP_DESTROYED_REDALERT | SHIP_DEPARTED_REDALERT));
Assert(Objects[Ships[shipnum].objnum].type == OBJ_SHIP);
Assert(Objects[Ships[shipnum].objnum].flags[Object::Object_Flags::Should_be_dead]);
ship *shipp = &Ships[shipnum];
object *objp = &Objects[shipp->objnum];
const char *jumpnode_name = nullptr;
// this should never happen
Assertion(Ship_registry_map.find(shipp->ship_name) != Ship_registry_map.end(), "Ship %s was destroyed, but was never stored in the ship registry!", shipp->ship_name);
// Goober5000 - handle ship registry
auto entry = &Ship_registry[Ship_registry_map[shipp->ship_name]];
entry->status = ShipStatus::EXITED;
entry->objp = nullptr;
entry->shipp = nullptr;
entry->cleanup_mode = cleanup_mode;
// add the information to the exited ship list
switch (cleanup_mode) {
case SHIP_DESTROYED:
ship_add_exited_ship(shipp, Ship::Exit_Flags::Destroyed);
break;
case SHIP_DEPARTED:
case SHIP_DEPARTED_WARP:
case SHIP_DEPARTED_BAY:
ship_add_exited_ship(shipp, Ship::Exit_Flags::Departed);
break;
case SHIP_DESTROYED_REDALERT:
case SHIP_DEPARTED_REDALERT:
// Ship was removed in previous mission. Mark as "player deleted" for this mission
ship_add_exited_ship(shipp, Ship::Exit_Flags::Player_deleted);
break;
case SHIP_VANISHED:
// Do nothing
break;
default:
// Can't Happen
UNREACHABLE("Unknown cleanup_mode '%i' passed to ship_cleanup!", cleanup_mode);
break;
}
// record kill?
if (cleanup_mode == SHIP_DESTROYED) {
// determine if we need to count this ship as a kill in counting number of kills per ship type
// look at the ignore flag for the ship (if not in a wing), or the ignore flag for the wing
// (if the ship is in a wing), and add to the kill count if the flags are not set
if ( !(shipp->flags[Ship_Flags::Ignore_count]) || ((shipp->wingnum != -1) && !(Wings[shipp->wingnum].flags[Ship::Wing_Flags::Ignore_count])) )
ship_add_ship_type_kill_count( shipp->ship_info_index );
// let the event music system know an enemy was destroyed (important for deciding when to transition from battle to normal music)
if (Player_ship != NULL && iff_x_attacks_y(Player_ship->team, shipp->team))
event_music_hostile_ship_destroyed();
}
// add mission log entry?
// (vanished ships and red-alert deleted ships have no log, and destroyed ships are logged in ship_hit_kill)
if ((cleanup_mode == SHIP_DEPARTED_WARP) || (cleanup_mode == SHIP_DEPARTED_BAY) || (cleanup_mode == SHIP_DEPARTED)) {
// see if this ship departed within the radius of a jump node -- if so, put the node name into
// the secondary mission log field
auto jnp = jumpnode_get_which_in(objp);
if (jnp)
jumpnode_name = jnp->GetName();
mission_log_add_entry(LOG_SHIP_DEPARTED, shipp->ship_name, jumpnode_name, shipp->wingnum);
}
// run "On Ship Depart" conditional hook variable that accounts for all departure types
// the hook is not limited to only warping --wookieejedi
if ((cleanup_mode == SHIP_DEPARTED_WARP) || (cleanup_mode == SHIP_DEPARTED_BAY) ||
(cleanup_mode == SHIP_DEPARTED) || (cleanup_mode == SHIP_DEPARTED_REDALERT) ||
(cleanup_mode == SHIP_VANISHED)) {
const char* departmethod = get_departure_name(cleanup_mode);
if (Script_system.IsActiveAction(CHA_ONSHIPDEPART)) {
Script_system.SetHookObject("Ship", objp);
Script_system.SetHookVar("Method", 's', departmethod);
Script_system.SetHookVar("JumpNode", 's', jumpnode_name);
Script_system.RunCondition(CHA_ONSHIPDEPART);
Script_system.RemHookVars({"Ship", "Method", "JumpNode"});
}
}
#ifndef NDEBUG
// this isn't posted to the mission log, so log it here
if (cleanup_mode == SHIP_VANISHED) {
float mission_time = f2fl(Missiontime);
int minutes = (int)(mission_time / 60);
int seconds = (int)mission_time % 60;
nprintf(("missionlog", "MISSION LOG: %s vanished at %02d:%02d\n", shipp->ship_name, minutes, seconds));
}
#endif
// update wingman status gauge
if ( (shipp->wing_status_wing_index >= 0) && (shipp->wing_status_wing_pos >= 0) ) {
switch (cleanup_mode) {
case SHIP_DESTROYED:
case SHIP_DESTROYED_REDALERT:
hud_set_wingman_status_dead(shipp->wing_status_wing_index, shipp->wing_status_wing_pos);
break;
case SHIP_DEPARTED:
case SHIP_DEPARTED_WARP:
case SHIP_DEPARTED_BAY:
case SHIP_DEPARTED_REDALERT:
hud_set_wingman_status_departed(shipp->wing_status_wing_index, shipp->wing_status_wing_pos);
break;
case SHIP_VANISHED:
hud_set_wingman_status_none(shipp->wing_status_wing_index, shipp->wing_status_wing_pos);
break;
default:
// Can't Happen, but we should've already caught this
UNREACHABLE("Unknown cleanup_mode '%i' passed to ship_cleanup!", cleanup_mode);
break;
}
}
// if ship belongs to a wing, do the wing cleanup
if ( shipp->wingnum != -1 ) {
wing *wingp = &Wings[shipp->wingnum];
switch (cleanup_mode) {
case SHIP_DESTROYED:
case SHIP_DESTROYED_REDALERT:
wingp->total_destroyed++;
break;
case SHIP_DEPARTED:
case SHIP_DEPARTED_WARP:
case SHIP_DEPARTED_BAY:
case SHIP_DEPARTED_REDALERT:
wingp->total_departed++;
break;
case SHIP_VANISHED:
wingp->total_vanished++;
break;
default:
// Can't Happen, but we should've already caught this
UNREACHABLE("Unknown cleanup_mode '%i' passed to ship_cleanup!", cleanup_mode);
break;
}
ship_wing_cleanup(shipnum, wingp);
}
// Note, this call to ai_ship_destroy() must come after ship_wing_cleanup for guarded wings to
// properly note the destruction of a ship in their wing.
ai_ship_destroy(shipnum); // Do AI stuff for destruction/leave of ship.
// Goober5000 - lastly, clear out the dead-docked list, per Mantis #2294
// (for exploding ships, this list should have already been cleared by now, via
// do_dying_undock_physics, except in the case of the destroy-instantly sexp)
dock_dead_undock_all(objp);
}
/**
* Calculates the blast and damage applied to a ship from another ship blowing up.
*
* @param pos1 ship explosion position
* @param pos2 other ship position
* @param inner_rad distance from ship center for which full damage is applied
* @param outer_rad distance from ship center for which no damage is applied
* @param max_damage maximum damage applied
* @param max_blast maximum impulse applied from blast
* @param damage damage applied
* @param blast impulse applied from blast
*/
int ship_explode_area_calc_damage( vec3d *pos1, vec3d *pos2, float inner_rad, float outer_rad, float max_damage, float max_blast, float *damage, float *blast )
{
float dist;
dist = vm_vec_dist_quick( pos1, pos2 );
// check outside outer radius
if ( dist > outer_rad )
return -1;
if ( dist < inner_rad ) {
// check insider inner radius
*damage = max_damage;
*blast = max_blast;
} else {
// between inner and outer
float fraction = 1.0f - (dist - inner_rad) / (outer_rad - inner_rad);
*damage = fraction * max_damage;
*blast = fraction * max_blast;
}
return 1;
}
static const float MAX_SHOCK_ANGLE_RANGE = 1.99f * PI;
/**
* Applies damage to ship close to others when a ship dies and blows up
*
* @param exp_objp ship object pointers
*/
static void ship_blow_up_area_apply_blast( object *exp_objp)
{
ship *shipp;
ship_info *sip;
float inner_rad, outer_rad, max_damage, max_blast, shockwave_speed;
// No area explosion in training missions.
if (The_mission.game_type & MISSION_TYPE_TRAINING){
return;
}
Assert( exp_objp != NULL );
Assert( exp_objp->type == OBJ_SHIP );
Assert( exp_objp->instance >= 0 );
shipp = &Ships[exp_objp->instance];
sip = &Ship_info[shipp->ship_info_index];
Assert( (shipp != NULL) && (sip != NULL) );
if ((exp_objp->hull_strength <= KAMIKAZE_HULL_ON_DEATH) && (Ai_info[Ships[exp_objp->instance].ai_index].ai_flags[AI::AI_Flags::Kamikaze]) && (shipp->special_exp_damage == -1)) {
int override = Ai_info[shipp->ai_index].kamikaze_damage;
inner_rad = exp_objp->radius*2.0f;
outer_rad = exp_objp->radius*4.0f; // + (override * 0.3f);
max_damage = i2fl(override);
max_blast = override * 5.0f;
shockwave_speed = 100.0f;
} else {
if (shipp->use_special_explosion) {
inner_rad = i2fl(shipp->special_exp_inner);
outer_rad = i2fl(shipp->special_exp_outer);
max_damage = i2fl(shipp->special_exp_damage);
max_blast = i2fl(shipp->special_exp_blast);
shockwave_speed = i2fl(shipp->special_exp_shockwave_speed);
} else {
inner_rad = sip->shockwave.inner_rad;
outer_rad = sip->shockwave.outer_rad;
max_damage = sip->shockwave.damage;
max_blast = sip->shockwave.blast;
shockwave_speed = sip->shockwave.speed;
}
}
// account for ships that give no damage when they blow up.
if ( (max_damage < 0.1f) && (max_blast < 0.1f) ){
return;
}
if ( shockwave_speed > 0 ) {
shockwave_create_info sci = sip->shockwave;
sci.inner_rad = inner_rad;
sci.outer_rad = outer_rad;
sci.blast = max_blast;
sci.damage = max_damage;
sci.speed = shockwave_speed;
sci.rot_angles.p = frand_range(0.0f, MAX_SHOCK_ANGLE_RANGE);
sci.rot_angles.b = frand_range(0.0f, MAX_SHOCK_ANGLE_RANGE);
sci.rot_angles.h = frand_range(0.0f, MAX_SHOCK_ANGLE_RANGE);
shipfx_do_shockwave_stuff(shipp, &sci);
} else {
object *objp;
float blast = 0.0f;
float damage = 0.0f;
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) ) {
continue;
}
if ( objp == exp_objp ){
continue;
}
// don't blast navbuoys
if ( objp->type == OBJ_SHIP ) {
if ( ship_get_SIF(objp->instance)[Ship::Info_Flags::Navbuoy] ) {
continue;
}
}
if ( ship_explode_area_calc_damage( &exp_objp->pos, &objp->pos, inner_rad, outer_rad, max_damage, max_blast, &damage, &blast ) == -1 ){
continue;
}
switch ( objp->type ) {
case OBJ_SHIP:
ship_apply_global_damage( objp, exp_objp, &exp_objp->pos, damage, sip->shockwave.damage_type_idx);
vec3d force, vec_ship_to_impact;
vm_vec_sub( &vec_ship_to_impact, &objp->pos, &exp_objp->pos );
vm_vec_copy_normalize( &force, &vec_ship_to_impact );
vm_vec_scale( &force, blast );
ship_apply_whack( &force, &exp_objp->pos, objp );
break;
case OBJ_ASTEROID:
asteroid_hit(objp, NULL, NULL, damage);
break;
default:
Int3();
break;
}
} // end for
}
}
/**
* Only ever called once for any ship that dies
*
* This function relies on the "dead dock" list, which replaces the dock_objnum_when_dead
* used in retail.
*/
static void do_dying_undock_physics(object *dying_objp, ship *dying_shipp)
{
// this function should only be called for an object that was docked...
// no harm in calling it if it wasn't, but we want to enforce this
Assert(object_is_dead_docked(dying_objp));
object *docked_objp;
float damage;
float impulse_mag;
vec3d impulse_norm, impulse_vec, pos;
// damage applied to each docked object
damage = 0.2f * dying_shipp->ship_max_hull_strength;
// Goober5000 - as with ai_deathroll_start, we can't simply iterate through the dock list while we're
// unlinking things. So just repeatedly unlink the first object.
while (object_is_dead_docked(dying_objp))
{
docked_objp = dock_get_first_dead_docked_object(dying_objp);
ship *docked_shipp = &Ships[docked_objp->instance];
int dockee_index = dock_find_dead_dockpoint_used_by_object(docked_objp, dying_objp);
// undo all the docking animations for the docked ship only
ship_info* docked_sip = &Ship_info[docked_shipp->ship_info_index];
docked_sip->animations.startAll(model_get_instance(docked_shipp->model_instance_num), animation::ModelAnimationTriggerType::Docked, animation::ModelAnimationDirection::RWD, false, false, false, dockee_index);
docked_sip->animations.startAll(model_get_instance(docked_shipp->model_instance_num), animation::ModelAnimationTriggerType::Docking_Stage3, animation::ModelAnimationDirection::RWD, false, false, false, dockee_index);
docked_sip->animations.startAll(model_get_instance(docked_shipp->model_instance_num), animation::ModelAnimationTriggerType::Docking_Stage2, animation::ModelAnimationDirection::RWD, false, false, false, dockee_index);
docked_sip->animations.startAll(model_get_instance(docked_shipp->model_instance_num), animation::ModelAnimationTriggerType::Docking_Stage1, animation::ModelAnimationDirection::RWD, false, false, false, dockee_index);
// only consider the mass of these two objects, not the whole assembly
// (this is inaccurate, but the alternative is a huge mess of extra code for a very small gain in realism)
float docked_mass = dying_objp->phys_info.mass + docked_objp->phys_info.mass;
// damage this docked object
ship_apply_global_damage(docked_objp, dying_objp, &dying_objp->pos, damage, -1);
// do physics
vm_vec_sub(&impulse_norm, &docked_objp->pos, &dying_objp->pos);
vm_vec_normalize(&impulse_norm);
// set for relative separation velocity of ~30
impulse_mag = 50.f * docked_objp->phys_info.mass * dying_objp->phys_info.mass / docked_mass;
vm_vec_copy_scale(&impulse_vec, &impulse_norm, impulse_mag);
vm_vec_rand_vec_quick(&pos);
vm_vec_scale(&pos, docked_objp->radius);
vm_vec_add2(&pos, &docked_objp->pos);
// apply whack to docked object
ship_apply_whack(&impulse_vec, &pos, docked_objp);
// enhance rotation of the docked object
vm_vec_scale(&docked_objp->phys_info.rotvel, 2.0f);
// apply whack to dying object
vm_vec_negate(&impulse_vec);
vm_vec_rand_vec_quick(&pos);
vm_vec_scale(&pos, dying_objp->radius);
vm_vec_add2(&pos, &dying_objp->pos);
ship_apply_whack(&impulse_vec, &pos, dying_objp);
// unlink the two objects, since dying_objp has blown up
dock_dead_undock_objects(dying_objp, docked_objp);
}
}
/**
* Do the stuff we do in a frame for a ship that's in its death throes.
*/
static void ship_dying_frame(object *objp, int ship_num)
{
ship *shipp = &Ships[ship_num];
if ( shipp->flags[Ship_Flags::Dying] ) {
ship_info *sip = &Ship_info[shipp->ship_info_index];
bool knossos_ship = (sip->flags[Ship::Info_Flags::Knossos_device]);
// bash hull value toward 0 (from self destruct)
if (objp->hull_strength > 0) {
int time_left = timestamp_until(shipp->final_death_time);
float hits_left = objp->hull_strength;
objp->hull_strength -= hits_left * (1000.0f * flFrametime) / time_left;
}
// special case of VAPORIZE
if (shipp->flags[Ship_Flags::Vaporize]) {
if (timestamp_elapsed(shipp->final_death_time)) {
// play death sound
snd_play_3d( gamesnd_get_game_sound(GameSounds::VAPORIZED), &objp->pos, &View_position, objp->radius, NULL, 0, 1.0f, SND_PRIORITY_MUST_PLAY );
// do joystick effect
if (objp == Player_obj) {
joy_ff_explode();
}
// if dying ship is docked, do damage to docked and physics
if (object_is_dead_docked(objp)) {
do_dying_undock_physics(objp, shipp);
}
// do all accounting for respawning client and server side here.
if (objp == Player_obj) {
gameseq_post_event(GS_EVENT_DEATH_BLEW_UP);
}
// mark object as dead
objp->flags.set(Object::Object_Flags::Should_be_dead);
// Don't blow up model. Only use debris shards.
// call ship function to clean up after the ship is destroyed.
ship_cleanup(ship_num, SHIP_DESTROYED);
return;
} else {
return;
}
}
// bash the desired rotvel
objp->phys_info.desired_rotvel = shipp->deathroll_rotvel;
// Do fireballs for Big ship with propagating explostion, but not Kamikaze
if (!(Ai_info[shipp->ai_index].ai_flags[AI::AI_Flags::Kamikaze]) && ship_get_exp_propagates(shipp) && (sip->death_roll_r_mult > 0.0f)) {
if ( timestamp_elapsed(shipp->next_fireball)) {
vec3d outpnt, pnt1, pnt2;
polymodel *pm = model_get(sip->model_num);
// Gets two random points on the surface of a submodel
submodel_get_two_random_points_better(pm->id, pm->detail[0], &pnt1, &pnt2);
model_instance_find_world_point(&outpnt, &pnt1, shipp->model_instance_num, pm->detail[0], &objp->orient, &objp->pos );
float rad = objp->radius*0.1f;
if (sip->death_roll_r_mult != 1.0f)
rad *= sip->death_roll_r_mult;
int fireball_type = fireball_ship_explosion_type(sip);
if(fireball_type < 0) {
fireball_type = FIREBALL_EXPLOSION_LARGE1 + Random::next(FIREBALL_NUM_LARGE_EXPLOSIONS);
}
fireball_create( &outpnt, fireball_type, FIREBALL_LARGE_EXPLOSION, OBJ_INDEX(objp), rad, false, &objp->phys_info.vel );
// start the next fireball up in the next 50 - 200 ms (2-3 per frame)
int min_time = 333;
int max_time = 500;
if (sip->death_roll_time_mult != 1.0f) {
min_time = (int) (min_time / sip->death_roll_time_mult);
max_time = (int) (max_time / sip->death_roll_time_mult);
}
shipp->next_fireball = timestamp_rand(min_time,max_time);
// do sound - maybe start a random sound, if it has played far enough.
do_sub_expl_sound(objp->radius, &outpnt, shipp->sub_expl_sound_handle.data());
}
}
// create little fireballs for knossos as it dies
if (knossos_ship) {
if ( timestamp_elapsed(shipp->next_fireball)) {
vec3d rand_vec, outpnt; // [0-.7 rad] in plane
vm_vec_rand_vec_quick(&rand_vec);
float scale = -vm_vec_dot(&objp->orient.vec.fvec, &rand_vec) * (0.9f + 0.2f * frand());
vm_vec_scale_add2(&rand_vec, &objp->orient.vec.fvec, scale);
vm_vec_normalize_quick(&rand_vec);
scale = objp->radius * frand() * 0.717f;
vm_vec_scale(&rand_vec, scale);
vm_vec_add(&outpnt, &objp->pos, &rand_vec);
float rad = objp->radius*0.2f;
int fireball_type = fireball_ship_explosion_type(sip);
if(fireball_type < 0) {
fireball_type = FIREBALL_EXPLOSION_LARGE1 + Random::next(FIREBALL_NUM_LARGE_EXPLOSIONS);
}
fireball_create( &outpnt, fireball_type, FIREBALL_LARGE_EXPLOSION, OBJ_INDEX(objp), rad, false, &objp->phys_info.vel );
// start the next fireball up in the next 50 - 200 ms (2-3 per frame)
shipp->next_fireball = timestamp_rand(333,500);
// emit particles
particle::particle_emitter pe;
particle_effect pef = sip->knossos_end_particles;
pe.num_low = pef.n_low; // Lowest number of particles to create
pe.num_high = pef.n_high; // Highest number of particles to create
pe.pos = outpnt; // Where the particles emit from
pe.vel = objp->phys_info.vel; // Initial velocity of all the particles
pe.min_life = pef.min_life; // How long the particles live
pe.max_life = pef.max_life; // How long the particles live
pe.normal = objp->orient.vec.uvec; // What normal the particle emit around
pe.normal_variance = pef.variance; // How close they stick to that normal 0=on normal, 1=180, 2=360 degree
pe.min_vel = pef.min_vel;
pe.max_vel = pef.max_vel;
pe.min_rad = pef.min_rad; // * objp->radius;
pe.max_rad = pef.max_rad; // * objp->radius;
if (pe.num_high > 0) {
particle::emit( &pe, particle::PARTICLE_SMOKE2, 0, 50 );
}
// do sound - maybe start a random sound, if it has played far enough.
do_sub_expl_sound(objp->radius, &outpnt, shipp->sub_expl_sound_handle.data());
}
}
int time_until_minor_explosions = timestamp_until(shipp->final_death_time);
// Wait until just before death and set off some explosions
// If it is less than 1/2 second until large explosion, but there is
// at least 1/10th of a second left, then create 5 small explosions
if ( ((time_until_minor_explosions < 500) && (time_until_minor_explosions > 100) && (!shipp->pre_death_explosion_happened))
// If we're already exploding and missed our chance, then do it anyway; better late than never
|| ((time_until_minor_explosions <= 0) && (!shipp->pre_death_explosion_happened)) )
{
shipp->next_fireball = timestamp(-1); // never time out again
shipp->pre_death_explosion_happened=1; // Mark this event as having occurred
polymodel *pm = model_get(sip->model_num);
polymodel_instance *pmi = model_get_instance(shipp->model_instance_num);
// Start shockwave for ship with propagating explosion, do now for timing
if ( ship_get_exp_propagates(shipp) ) {
ship_blow_up_area_apply_blast( objp );
}
int zz_max = sip->death_fx_count;
for (int zz=0; zz<zz_max; zz++ ) {
// don't make sequence of fireballs for knossos
if (knossos_ship) {
break;
}
if (sip->death_fx_r_mult <= 0.0f) {
break;
}
// Find two random vertices on the model, then average them
// and make the piece start there.
vec3d tmp, outpnt, pnt1, pnt2;
// Gets two random points on the surface of a submodel [KNOSSOS]
submodel_get_two_random_points_better(pm->id, pm->detail[0], &pnt1, &pnt2);
vm_vec_avg( &tmp, &pnt1, &pnt2 );
model_instance_find_world_point(&outpnt, &tmp, pm, pmi, pm->detail[0], &objp->orient, &objp->pos );
float rad = objp->radius*0.40f;
rad *= sip->death_fx_r_mult;
int fireball_type = fireball_ship_explosion_type(sip);
if(fireball_type < 0) {
fireball_type = FIREBALL_EXPLOSION_MEDIUM;
}
fireball_create( &outpnt, fireball_type, FIREBALL_MEDIUM_EXPLOSION, OBJ_INDEX(objp), rad, false, &objp->phys_info.vel );
}
}
if ( timestamp_elapsed(shipp->final_death_time)) {
shipp->death_time = shipp->final_death_time;
shipp->final_death_time = timestamp(-1); // never time out again
// play ship explosion sound effect, pick appropriate explosion sound
gamesnd_id sound_index;
if (ship_has_sound(objp, GameSounds::SHIP_EXPLODE_1))
{
sound_index = ship_get_sound(objp, GameSounds::SHIP_EXPLODE_1);
}
else
{
if (sip->flags[Info_Flags::Capital] || sip->flags[Info_Flags::Knossos_device]) {
sound_index=GameSounds::CAPSHIP_EXPLODE;
} else {
if ( OBJ_INDEX(objp) & 1 ) {
sound_index=GameSounds::SHIP_EXPLODE_1;
} else {
sound_index=GameSounds::SHIP_EXPLODE_2;
}
}
}
if (sound_index.isValid())
snd_play_3d(gamesnd_get_game_sound(sound_index), &objp->pos, &View_position, objp->radius, nullptr, 0, 1.0f, SND_PRIORITY_MUST_PLAY);
if (objp == Player_obj)
joy_ff_explode();
if (shipp->death_roll_snd.isValid()) {
snd_stop(shipp->death_roll_snd);
shipp->death_roll_snd = sound_handle::invalid();
}
// if dying ship is docked, do damage to docked and physics
if (object_is_dead_docked(objp)) {
do_dying_undock_physics(objp, shipp);
}
if (!knossos_ship){
if (sip->death_effect.isValid()) {
// Use the new particle effect
auto source = particle::ParticleManager::get()->createSource(sip->death_effect);
// Use the position since the ship is going to be invalid soon
source.moveTo(&objp->pos);
source.setVelocity(&objp->phys_info.vel);
source.finish();
} else {
// play a random explosion
particle::particle_emitter pe;
particle_effect pef = sip->regular_end_particles;
pe.num_low = pef.n_low; // Lowest number of particles to create
pe.num_high = pef.n_high; // Highest number of particles to create
pe.pos = objp->pos; // Where the particles emit from
pe.vel = objp->phys_info.vel; // Initial velocity of all the particles
pe.min_life = pef.min_life; // How long the particles live
pe.max_life = pef.max_life; // How long the particles live
pe.normal = objp->orient.vec.uvec; // What normal the particle emit around
pe.normal_variance = pef.variance; // How close they stick to that normal 0=on normal, 1=180, 2=360 degree
pe.min_vel = pef.min_vel; // How fast the slowest particle can move
pe.max_vel = pef.max_vel; // How fast the fastest particle can move
pe.min_rad = pef.min_rad; // Min radius
pe.max_rad = pef.max_rad; // Max radius
if (pe.num_high > 0) {
particle::emit( &pe, particle::PARTICLE_SMOKE2, 0 );
}
}
}
// If this is a splitting explosion, set it split up.
if ( sip->explosion_splits_ship ) {
if (Ai_info[shipp->ai_index].ai_flags[AI::AI_Flags::Kamikaze]) {
ship_blow_up_area_apply_blast( objp );
}
shipfx_large_blowup_init(shipp);
// need to timeout immediately to keep physics in sync
shipp->really_final_death_time = timestamp(0);
polymodel *pm = model_get(sip->model_num);
shipp->end_death_time = timestamp((int) pm->core_radius);
} else {
// else, just a single big fireball
float big_rad;
int fireball_objnum, fireball_type, default_fireball_type;
float explosion_life;
big_rad = objp->radius*1.75f;
default_fireball_type = FIREBALL_EXPLOSION_LARGE1 + Random::next(FIREBALL_NUM_LARGE_EXPLOSIONS);
if (knossos_ship) {
big_rad = objp->radius * 1.2f;
default_fireball_type = FIREBALL_EXPLOSION_LARGE1;
}
//SUSHI: Option to override radius of big fireball
if (Ship_info[shipp->ship_info_index].big_exp_visual_rad >= 0)
big_rad = Ship_info[shipp->ship_info_index].big_exp_visual_rad;
fireball_type = fireball_ship_explosion_type(sip);
if(fireball_type < 0) {
fireball_type = default_fireball_type;
}
fireball_objnum = fireball_create( &objp->pos, fireball_type, FIREBALL_LARGE_EXPLOSION, OBJ_INDEX(objp), big_rad, false, &objp->phys_info.vel );
if ( fireball_objnum >= 0 ) {
explosion_life = fireball_lifeleft(&Objects[fireball_objnum]);
} else {
explosion_life = 0.0f;
}
// JAS: I put in all this code because of an item on my todo list that
// said that the ship destroyed debris shouldn't pop in until the
// big explosion is 30% done. I did this on Oct24 and me & Adam
// thought it looked dumb since the explosion didn't move with the
// ship, so instead of just taking this code out, since we might need
// it in the future, I disabled it. You can reenable it by changing
// the commenting on the following two lines.
shipp->end_death_time = shipp->really_final_death_time = timestamp( fl2i(explosion_life*1000.0f)/5 ); // Wait till 30% of vclip time before breaking the ship up.
}
shipp->flags.set(Ship_Flags::Exploded);
if ( !(ship_get_exp_propagates(shipp)) ) {
// apply area of effect blast damage from ship explosion
ship_blow_up_area_apply_blast( objp );
}
}
if ( timestamp_elapsed(shipp->really_final_death_time)) {
// Copied from lock all turrets sexp
// Locks all turrets on ship that is about to split.
ship_subsys *subsys;
subsys = GET_FIRST(&shipp->subsys_list);
while ( subsys != END_OF_LIST(&shipp->subsys_list) )
{
// just mark all turrets as locked
if (subsys->system_info->type == SUBSYSTEM_TURRET) {
subsys->weapons.flags.set(Ship::Weapon_Flags::Turret_Lock);
}
subsys = GET_NEXT(subsys);
}
// do large_ship_split and explosion
if ( shipp->large_ship_blowup_index >= 0 ) {
if ( shipfx_large_blowup_do_frame(shipp, flFrametime) ) {
// do all accounting for respawning client and server side here.
if(objp == Player_obj) {
gameseq_post_event(GS_EVENT_DEATH_BLEW_UP);
}
objp->flags.set(Object::Object_Flags::Should_be_dead);
ship_cleanup(ship_num, SHIP_DESTROYED); // call ship function to clean up after the ship is destroyed.
}
return;
}
shipfx_blow_up_model(objp, 0, sip->generic_debris_spew_num, &objp->pos);
// do all accounting for respawning client and server side here.
if(objp == Player_obj) {
gameseq_post_event(GS_EVENT_DEATH_BLEW_UP);
}
objp->flags.set(Object::Object_Flags::Should_be_dead);
ship_cleanup(ship_num, SHIP_DESTROYED); // call ship function to clean up after the ship is destroyed.
shipp->really_final_death_time = timestamp( -1 ); // Never time out again!
}
// If a ship is dying (and not a capital or big ship) then stutter the engine sound
if ( timestamp_elapsed(shipp->next_engine_stutter) ) {
if ( !(sip->is_big_or_huge()) ) {
shipp->flags.toggle(Ship_Flags::Engines_on); // toggle state of engines
shipp->next_engine_stutter = timestamp_rand(50, 250);
}
}
}
}
// Trickle buffered energy from weapon<->shield transfers into the main banks
static void ship_move_ets_transfer_buffers(ship *shipp, object *obj, float frametime)
{
float delta;
ship_info *sip;
if (shipp->flags[Ship_Flags::Dying])
return;
sip = &Ship_info[shipp->ship_info_index];
delta = frametime * shipp->ship_max_shield_strength * sip->weap_shield_speed;
// Chase target_shields and target_weapon_energy
if (shipp->target_shields_delta > 0.0f) {
if (delta > shipp->target_shields_delta)
delta = shipp->target_shields_delta;
shield_add_strength(obj, delta);
shipp->target_shields_delta -= delta;
} else if (shipp->target_shields_delta < 0.0f) {
//if (delta > -shipp->target_shields_delta)
delta = -shipp->target_shields_delta;
shield_add_strength(obj, -delta);
shipp->target_shields_delta += delta;
}
delta = frametime * sip->max_weapon_reserve * sip->shield_weap_speed;
if (shipp->target_weapon_energy_delta > 0.0f) {
if (delta > shipp->target_weapon_energy_delta)
delta = shipp->target_weapon_energy_delta;
shipp->weapon_energy += delta;
shipp->target_weapon_energy_delta -= delta;
} else if (shipp->target_weapon_energy_delta < 0.0f) {
//if (delta > -shipp->target_weapon_energy_delta)
delta = -shipp->target_weapon_energy_delta;
shipp->weapon_energy -= delta;
shipp->target_weapon_energy_delta += delta;
}
}
static int thruster_glow_anim_load(generic_anim *ga)
{
if ( !VALID_FNAME(ga->filename) )
return -1;
int fps = 15;
ga->first_frame = bm_load(ga->filename);
if (ga->first_frame < 0)
{
Warning(LOCATION, "Couldn't load thruster glow animation '%s'\nPrimary glow type effect does not accept .EFF or .ANI effects", ga->filename);
return -1;
}
ga->num_frames = NOISE_NUM_FRAMES;
Assert(fps != 0);
ga->total_time = i2fl(ga->num_frames)/fps;
return 0;
}
/**
* Loads the animations for ship's afterburners
*/
static void ship_init_thrusters()
{
if ( Thrust_anim_inited == 1 )
return;
for (size_t i = 0; i < Species_info.size(); i++)
{
species_info *species = &Species_info[i];
generic_anim_load(&species->thruster_info.flames.normal);
generic_anim_load(&species->thruster_info.flames.afterburn);
// Bobboau's extra thruster stuff
{
generic_bitmap_load(&species->thruster_secondary_glow_info.normal);
generic_bitmap_load(&species->thruster_secondary_glow_info.afterburn);
generic_bitmap_load(&species->thruster_tertiary_glow_info.normal);
generic_bitmap_load(&species->thruster_tertiary_glow_info.afterburn);
generic_bitmap_load(&species->thruster_distortion_info.normal);
generic_bitmap_load(&species->thruster_distortion_info.afterburn);
}
// glows are handled a bit strangely
thruster_glow_anim_load(&species->thruster_info.glow.normal);
thruster_glow_anim_load(&species->thruster_info.glow.afterburn);
}
Thrust_anim_inited = 1;
}
/**
* Figure out which thruster bitmap will get rendered next time around.
*
* ::ship_render() needs to have shipp->thruster_bitmap set to
* a valid bitmap number, or -1 if we shouldn't render thrusters.
*/
static void ship_do_thruster_frame( ship *shipp, object *objp, float frametime )
{
float rate;
int framenum;
int secondary_glow_bitmap, tertiary_glow_bitmap, distortion_bitmap;
generic_anim *flame_anim, *glow_anim;
ship_info *sinfo = &Ship_info[shipp->ship_info_index];
if ( !Thrust_anim_inited ) {
ship_init_thrusters();
}
if (objp->phys_info.flags & PF_AFTERBURNER_ON) {
flame_anim = &sinfo->thruster_flame_info.afterburn; // select afterburner flame
glow_anim = &sinfo->thruster_glow_info.afterburn; // select afterburner glow
secondary_glow_bitmap = sinfo->thruster_secondary_glow_info.afterburn.bitmap_id;
tertiary_glow_bitmap = sinfo->thruster_tertiary_glow_info.afterburn.bitmap_id;
distortion_bitmap = sinfo->thruster_distortion_info.afterburn.bitmap_id;
rate = 1.5f; // go at 1.5x faster when afterburners on
} else if (objp->phys_info.flags & PF_BOOSTER_ON) {
flame_anim = &sinfo->thruster_flame_info.afterburn; // select afterburner flame
glow_anim = &sinfo->thruster_glow_info.afterburn; // select afterburner glow
secondary_glow_bitmap = sinfo->thruster_secondary_glow_info.afterburn.bitmap_id;
tertiary_glow_bitmap = sinfo->thruster_tertiary_glow_info.afterburn.bitmap_id;
distortion_bitmap = sinfo->thruster_distortion_info.afterburn.bitmap_id;
rate = 2.5f; // go at 2.5x faster when boosters on
} else {
flame_anim = &sinfo->thruster_flame_info.normal; // select normal flame
glow_anim = &sinfo->thruster_glow_info.normal; // select normal glow
secondary_glow_bitmap = sinfo->thruster_secondary_glow_info.normal.bitmap_id;
tertiary_glow_bitmap = sinfo->thruster_tertiary_glow_info.normal.bitmap_id;
distortion_bitmap = sinfo->thruster_distortion_info.normal.bitmap_id;
// If thrust at 0, go at half as fast, full thrust; full framerate
// so set rate from 0.67 to 1.67, depending on thrust from 0 to 1
rate = 0.67f * (1.0f + objp->phys_info.forward_thrust);
}
Assert( frametime > 0.0f );
// add primary thruster effects ...
if (flame_anim->first_frame >= 0) {
shipp->thruster_frame += frametime * rate;
framenum = bm_get_anim_frame(flame_anim->first_frame, shipp->thruster_frame, flame_anim->total_time, true);
// Get the bitmap for this frame
shipp->thruster_bitmap = flame_anim->first_frame + framenum;
} else {
shipp->thruster_frame = 0.0f;
shipp->thruster_bitmap = -1;
}
// primary glows ...
if (glow_anim->first_frame >= 0) {
shipp->thruster_glow_frame += frametime * rate;
framenum = bm_get_anim_frame(glow_anim->first_frame, shipp->thruster_glow_frame, glow_anim->total_time, true);
// Get the bitmap for this frame
shipp->thruster_glow_bitmap = glow_anim->first_frame; // + framenum;
shipp->thruster_glow_noise = Noise[framenum];
} else {
shipp->thruster_glow_frame = 0.0f;
shipp->thruster_glow_bitmap = -1;
shipp->thruster_glow_noise = 1.0f;
}
// add extra thruster effects
shipp->thruster_secondary_glow_bitmap = secondary_glow_bitmap;
shipp->thruster_tertiary_glow_bitmap = tertiary_glow_bitmap;
shipp->thruster_distortion_bitmap = distortion_bitmap;
}
/**
* Figure out which thruster bitmap will get rendered next time around.
*
* ship_render needs to have shipp->thruster_bitmap set to
* a valid bitmap number, or -1 if we shouldn't render thrusters.
*
* This does basically the same thing as ship_do_thruster_frame, except it
* operates on a weapon. This is in the ship code because it needs
* the same thruster animation info as the ship stuff, and I would
* rather extern this one function than all the thruster animation stuff.
*/
void ship_do_weapon_thruster_frame( weapon *weaponp, object *objp, float frametime )
{
float rate;
int framenum;
generic_anim *flame_anim, *glow_anim;
if (!Thrust_anim_inited)
ship_init_thrusters();
species_info *species = &Species_info[weaponp->species];
weapon_info *wip = &Weapon_info[weaponp->weapon_info_index];
// If thrust at 0, go at half as fast, full thrust; full framerate
// so set rate from 0.67 to 1.67, depending on thrust from 0 to 1
rate = 0.67f * (1.0f + objp->phys_info.forward_thrust);
if (wip->thruster_flame.first_frame >= 0)
flame_anim = &wip->thruster_flame;
else
flame_anim = &species->thruster_info.flames.normal;
if (wip->thruster_glow.first_frame >= 0)
glow_anim = &wip->thruster_glow;
else
glow_anim = &species->thruster_info.glow.normal;
Assert( frametime > 0.0f );
if (flame_anim->first_frame >= 0) {
weaponp->thruster_frame += frametime * rate;
framenum = bm_get_anim_frame(flame_anim->first_frame, weaponp->thruster_frame, flame_anim->total_time, true);
// Get the bitmap for this frame
weaponp->thruster_bitmap = flame_anim->first_frame + framenum;
} else {
weaponp->thruster_frame = 0.0f;
weaponp->thruster_bitmap = -1;
}
// Do it for glow bitmaps
if (glow_anim->first_frame >= 0) {
weaponp->thruster_glow_frame += frametime * rate;
framenum = bm_get_anim_frame(glow_anim->first_frame, weaponp->thruster_glow_frame, glow_anim->total_time, true);
// Get the bitmap for this frame
weaponp->thruster_glow_bitmap = glow_anim->first_frame; // + framenum;
weaponp->thruster_glow_noise = Noise[framenum];
} else {
weaponp->thruster_glow_frame = 0.0f;
weaponp->thruster_glow_bitmap = -1;
weaponp->thruster_glow_noise = 1.0f;
}
}
// Repair damaged subsystems for a ship, called for each ship once per frame.
// TODO: optimize by only calling ever N seconds and keeping track of elapsed time
//
// NOTE: need to update current_hits in the sp->subsys_list element, and the sp->subsys_info[]
// element.
#define SHIP_REPAIR_SUBSYSTEM_RATE 0.01f // percent repair per second for a subsystem
#define SUBSYS_REPAIR_THRESHOLD 0.1 // only repair subsystems that have > 10% strength
static void ship_auto_repair_frame(int shipnum, float frametime)
{
ship_subsys *ssp;
ship_subsys_info *ssip;
ship *sp;
ship_info *sip;
object *objp;
float real_repair_rate;
#ifndef NDEBUG
if ( !Ship_auto_repair ) // only repair subsystems if Ship_auto_repair flag is set
return;
#endif
Assert( shipnum >= 0 && shipnum < MAX_SHIPS);
sp = &Ships[shipnum];
sip = &Ship_info[sp->ship_info_index];
objp = &Objects[sp->objnum];
if (sp->flags[Ship::Ship_Flags::Dying]) // do not repair if already dead
return;
//Repair the hull...or maybe unrepair?
if(sip->hull_repair_rate != 0.0f)
{
objp->hull_strength += sp->ship_max_hull_strength * sip->hull_repair_rate * frametime;
if (objp->hull_strength > sp->ship_max_hull_strength)
objp->hull_strength = sp->ship_max_hull_strength;
else if (objp->hull_strength < 0)
ship_hit_kill(objp, nullptr, nullptr, 0, true);
}
// only allow for the auto-repair of subsystems on small ships
//...NOT. Check if var has been changed from def -C
if ( !(sip->is_small_ship()) && sip->subsys_repair_rate == -2.0f)
return;
if(sip->subsys_repair_rate == -2.0f)
real_repair_rate = SHIP_REPAIR_SUBSYSTEM_RATE;
else
real_repair_rate = sip->subsys_repair_rate;
// AL 3-14-98: only allow auto-repair if power output not zero
if (sip->power_output <= 0)
return;
// iterate through subsystems, repair as needed based on elapsed frametime
for ( ssp = GET_FIRST(&sp->subsys_list); ssp != END_OF_LIST(&sp->subsys_list); ssp = GET_NEXT(ssp) ) {
Assert(ssp->system_info->type >= 0 && ssp->system_info->type < SUBSYSTEM_MAX);
ssip = &sp->subsys_info[ssp->system_info->type];
if ( ssp->current_hits < ssp->max_hits ) {
// only repair those subsystems which are not destroyed
if ( ssp->max_hits <= 0 )
continue;
if ( ssp->current_hits <= 0 ) {
if (sip->flags[Ship::Info_Flags::Subsys_repair_when_disabled]) {
if (ssp->flags[Ship::Subsystem_Flags::No_autorepair_if_disabled]) {
continue;
}
} else if (!(ssp->flags[Ship::Subsystem_Flags::Autorepair_if_disabled])) {
continue;
}
}
// do incremental repair on the subsystem
// check for overflow of current_hits
ssp->current_hits += ssp->max_hits * real_repair_rate * frametime;
if ( ssp->current_hits > ssp->max_hits ) {
ssp->current_hits = ssp->max_hits;
}
// aggregate repair
if (!(ssp->flags[Ship::Subsystem_Flags::No_aggregate])) {
ssip->aggregate_current_hits += ssip->aggregate_max_hits * real_repair_rate * frametime;
if ( ssip->aggregate_current_hits > ssip->aggregate_max_hits ) {
ssip->aggregate_current_hits = ssip->aggregate_max_hits;
}
}
// check to see if this subsystem was totally non functional before -- if so, then
// reset the flags
if ( (ssp->system_info->type == SUBSYSTEM_ENGINE) && (sp->flags[Ship_Flags::Disabled]) ) {
sp->flags.remove(Ship_Flags::Disabled);
ship_reset_disabled_physics(objp, sp->ship_info_index);
}
}
} // end for
}
// this function checks to see how far the player has strayed from his starting location (should be
// single player only). Issues a warning at some distance. Makes mission end if he keeps flying away
// 3 strikes and you're out or too far away
#define PLAYER_MAX_DIST_WARNING 700000 // distance in KM at which player gets warning to return to battle
#define PLAYER_DISTANCE_MAX_WARNINGS 3 // maximum number of warnings player can receive before mission ends
#define PLAYER_MAX_DIST_END 750000 // distance from starting loc at which we end mission
#define PLAYER_WARN_DELTA_TIME 10000 //ms
#define PLAYER_DEATH_DELTA_TIME 5000 //ms
static void ship_check_player_distance_sub(player *p, int multi_target=-1)
{
// only check distance for ships
if ( p->control_mode != PCM_NORMAL ) {
// already warping out... don't bother checking anymore
return;
}
float dist = vm_vec_dist_quick(&Objects[p->objnum].pos, &vmd_zero_vector);
int give_warning_to_player = 0;
if ( dist > PLAYER_MAX_DIST_WARNING ) {
if (p->distance_warning_count == 0) {
give_warning_to_player = 1;
} else {
if (timestamp_until(p->distance_warning_time) < 0) {
give_warning_to_player = 1;
}
}
}
if ( give_warning_to_player ) {
// increase warning count
p->distance_warning_count++;
// set timestamp unless player PLAYER_FLAGS_DIST_TO_BE_KILLED flag is set
if ( !(p->flags & PLAYER_FLAGS_DIST_TO_BE_KILLED) ) {
p->distance_warning_time = timestamp(PLAYER_WARN_DELTA_TIME);
}
// issue up to max warnings
if (p->distance_warning_count <= PLAYER_DISTANCE_MAX_WARNINGS) {
message_send_builtin_to_player( MESSAGE_STRAY_WARNING, NULL, MESSAGE_PRIORITY_HIGH, MESSAGE_TIME_SOON, 0, 0, multi_target, -1 );
}
if (p->distance_warning_count > PLAYER_DISTANCE_MAX_WARNINGS) {
p->flags |= PLAYER_FLAGS_DIST_WARNING;
}
}
if ( !(p->flags & PLAYER_FLAGS_FORCE_MISSION_OVER) && ((p->distance_warning_count > PLAYER_DISTANCE_MAX_WARNINGS) || (dist > PLAYER_MAX_DIST_END)) ) {
// DKA 5/17/99 - DON'T force warpout. Won't work multiplayer. Blow up ship.
if ( !(p->flags & PLAYER_FLAGS_DIST_TO_BE_KILLED) ) {
message_send_builtin_to_player( MESSAGE_STRAY_WARNING_FINAL, NULL, MESSAGE_PRIORITY_HIGH, MESSAGE_TIME_IMMEDIATE, 0, 0, multi_target, -1 );
p->flags |= PLAYER_FLAGS_DIST_TO_BE_KILLED;
p->distance_warning_time = timestamp(PLAYER_DEATH_DELTA_TIME);
}
// get hull strength and blow up
if ( (p->flags & PLAYER_FLAGS_DIST_TO_BE_KILLED) && (timestamp_until(p->distance_warning_time) < 0) ) {
p->flags |= PLAYER_FLAGS_FORCE_MISSION_OVER;
float damage = 10.0f * Objects[p->objnum].hull_strength;
ship_apply_global_damage(&Objects[p->objnum], &Objects[p->objnum], NULL, damage, -1);
}
}
// see if player has moved back into "bounds"
if ( (dist < PLAYER_MAX_DIST_WARNING) && (p->flags & PLAYER_FLAGS_DIST_WARNING) && !(p->flags & PLAYER_FLAGS_DIST_TO_BE_KILLED) ) {
p->flags &= ~PLAYER_FLAGS_DIST_WARNING;
p->distance_warning_count = 1;
}
}
static void ship_check_player_distance()
{
int idx;
// multiplayer
if (Game_mode & GM_MULTIPLAYER) {
// if I'm the server, check all non-observer players including myself
if (MULTIPLAYER_MASTER) {
// warn all players
for (idx=0; idx<MAX_PLAYERS; idx++) {
if (MULTI_CONNECTED(Net_players[idx]) && !MULTI_STANDALONE(Net_players[idx]) && !MULTI_OBSERVER(Net_players[idx]) && (Objects[Net_players[idx].m_player->objnum].type != OBJ_GHOST) ) {
// if bad, blow him up
ship_check_player_distance_sub(Net_players[idx].m_player, idx);
}
}
}
}
// single player
else {
// maybe blow him up
ship_check_player_distance_sub(Player);
}
}
void observer_process_post(object *objp)
{
Assert(objp != NULL);
if (objp == NULL)
return;
Assert(objp->type == OBJ_OBSERVER);
if (Game_mode & GM_MULTIPLAYER) {
// if I'm just an observer
if (MULTI_OBSERVER(Net_players[MY_NET_PLAYER_NUM])) {
float dist = vm_vec_dist_quick(&Player_obj->pos, &vmd_zero_vector);
// if beyond max dist, reset to 0
if (dist > PLAYER_MAX_DIST_END) {
// set me to zero
if ((Player_obj != NULL) && (Player_obj->type != OBJ_GHOST)) {
Player_obj->pos = vmd_zero_vector;
}
}
}
}
}
/**
* Reset some physics info when ship's engines goes from disabled->enabled
*/
void ship_reset_disabled_physics(object *objp, int ship_class)
{
Assert(objp != NULL);
if (objp == NULL)
return;
objp->phys_info.flags &= ~(PF_REDUCED_DAMP | PF_DEAD_DAMP);
objp->phys_info.side_slip_time_const = Ship_info[ship_class].damp;
}
/**
* Clear/set the subsystem disrupted flags
*/
static void ship_subsys_disrupted_check(ship *sp)
{
ship_subsys *ss;
int engines_disabled=0;
if ( sp->subsys_disrupted_flags & (1<<SUBSYSTEM_ENGINE) ) {
engines_disabled=1;
}
sp->subsys_disrupted_flags=0;
ss = GET_FIRST(&sp->subsys_list);
while ( ss != END_OF_LIST( &sp->subsys_list ) ) {
if ( !timestamp_elapsed(ss->disruption_timestamp) ) {
sp->subsys_disrupted_flags |= (1<<ss->system_info->type);
}
ss = GET_NEXT( ss );
}
if ( engines_disabled ) {
if ( !(sp->subsys_disrupted_flags & (1<<SUBSYSTEM_ENGINE)) ) {
if ( !(sp->flags[Ship_Flags::Disabled]) ) {
ship_reset_disabled_physics(&Objects[sp->objnum], sp->ship_info_index);
}
}
}
}
/**
* Maybe check ship subsystems for disruption, and set/clear flags
*/
static void ship_subsys_disrupted_maybe_check(ship *shipp)
{
if ( timestamp_elapsed(shipp->subsys_disrupted_check_timestamp) ) {
ship_subsys_disrupted_check(shipp);
shipp->subsys_disrupted_check_timestamp=timestamp(250);
}
}
/**
* Determine if a given subsystem is disrupted (ie inoperable)
*
* @param ss pointer to ship subsystem
* @return 1 if subsystem is disrupted, 0 if subsystem is not disrupted
*/
int ship_subsys_disrupted(ship_subsys *ss)
{
if ( !ss ) {
Int3(); // should never happen, get Alan if it does.
return 0;
}
if ( timestamp_elapsed(ss->disruption_timestamp) ) {
return 0;
} else {
return 1;
}
}
/**
* Disrupt a subsystem (ie make it inoperable for a time)
*
* @param ss pointer to ship subsystem to be disrupted
* @param time time in ms that subsystem should be disrupted
*/
void ship_subsys_set_disrupted(ship_subsys *ss, int time)
{
int time_left=0;
if ( !ss ) {
Int3(); // should never happen, get Alan if it does.
return;
}
time_left=timestamp_until(ss->disruption_timestamp);
if ( time_left < 0 ) {
time_left=0;
}
ss->disruption_timestamp = timestamp(time+time_left);
}
/**
* Determine if a given type of subsystem is disrupted (i.e. inoperable)
*
* @param sp pointer to ship containing subsystem
* @param type type of subsystem (SUBSYSTEM_*)
* @return 1 if subsystem is disrupted, 0 if subsystem is not disrupted
*/
int ship_subsys_disrupted(ship *sp, int type)
{
Assert ( sp != NULL );
Assert ( type >= 0 && type < SUBSYSTEM_MAX );
// Bogus pointer to ship to check for disrupted subsystem
if (sp == NULL)
return 0;
if ( sp->subsys_disrupted_flags & (1<<type) ) {
return 1;
} else {
return 0;
}
}
float Decay_rate = 1.0f / 120.0f;
DCF(lethality_decay, "Sets ship lethality_decay, or the time in sec to go from 100 to 0 health (default is 1/120)")
{
if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) {
dc_printf("Decay rate is currently %f\n", Decay_rate);
return;
}
dc_stuff_float(&Decay_rate);
}
static void lethality_decay(ai_info *aip)
{
float decay_rate = Decay_rate;
aip->lethality -= 100.0f * decay_rate * flFrametime;
aip->lethality = MAX(-10.0f, aip->lethality);
#ifndef NDEBUG
if (Objects[Ships[aip->shipnum].objnum].flags[Object::Object_Flags::Player_ship]) {
if (Framecount % 10 == 0) {
int num_turrets = 0;
if ((aip->target_objnum != -1) && (Objects[aip->target_objnum].type == OBJ_SHIP)) {
//TODO: put this where it belongs, this would involve recompiling *everything* right now
//-WMC
int num_turrets_attacking(object *turret_parent, int target_objnum);
num_turrets = num_turrets_attacking(&Objects[aip->target_objnum], Ships[aip->shipnum].objnum);
}
nprintf(("lethality", "Player lethality: %.1f, num turrets targeting player: %d\n", aip->lethality, num_turrets));
}
}
#endif
}
// moved out of ship_process_post() so it can be called from either -post() or -pre() depending on Framerate_independent_turning
void ship_evaluate_ai(object* obj, float frametime) {
int num = obj->instance;
Assertion(obj->type == OBJ_SHIP, "Non-ship object passed to ship_evaluate_ai");
Assertion(num >= 0 && num < MAX_SHIPS, "Invalid ship instance num in ship_evaluate_ai");
ship* shipp = &Ships[num];
Assertion(shipp->objnum == OBJ_INDEX(obj), "Ship objnum does not match its num in OBJ_INDEX in ship_evaluate_ai");
// seems important
if (shipp->ai_index < 0)
return;
// this prevents AI stuff, according to original Volition code
if (physics_paused || ai_paused)
return;
// update ship lethality
lethality_decay(&Ai_info[shipp->ai_index]);
// if we are a player ship and not under AI control, all we do is process our subobjects
if ((obj->flags[Object::Object_Flags::Player_ship]) && !Player_use_ai)
{
// AL 2-19-98: Fire turret for player if it exists
//WMC - changed this to call ai_process_subobjects
ai_process_subobjects(OBJ_INDEX(obj));
}
// otherwise we run the full AI pipeline
else
{
ai_process( obj, shipp->ai_index, frametime );
}
}
void ship_process_pre(object *obj, float frametime)
{
// If Framerate_independent_turning is false everything following is evaluated in ship_process_post()
// Also only multi masters do ai
if ( (obj == nullptr) || !frametime || MULTIPLAYER_CLIENT || !Framerate_independent_turning)
return;
if (obj->type != OBJ_SHIP) {
nprintf(("AI", "Ignoring non-ship object in ship_process_pre()\n"));
return;
}
int num = obj->instance;
Assert(num >= 0 && num < MAX_SHIPS);
Assert(Ships[num].objnum == OBJ_INDEX(obj));
ship* shipp = &Ships[num];
if ((!(shipp->is_arriving()) || (Ai_info[shipp->ai_index].mode == AIM_BAY_EMERGE)
|| ((Warp_params[shipp->warpin_params_index].warp_type == WT_IN_PLACE_ANIM) && (shipp->flags[Ship_Flags::Arriving_stage_2])))
&& !(shipp->flags[Ship_Flags::Depart_warp]))
{
ship_evaluate_ai(obj, frametime);
}
}
MONITOR( NumShips )
static void ship_radar_process( object * obj, ship * shipp, ship_info * sip )
{
Assert( obj != NULL);
Assert( shipp != NULL );
Assert( sip != NULL);
shipp->radar_last_status = shipp->radar_current_status;
RadarVisibility visibility = radar_is_visible(obj);
if (visibility == NOT_VISIBLE)
{
if (shipp->radar_last_contact < 0 && shipp->radar_visible_since < 0)
{
shipp->radar_visible_since = -1;
shipp->radar_last_contact = -1;
}
else
{
shipp->radar_visible_since = -1;
shipp->radar_last_contact = Missiontime;
}
}
else if (visibility == VISIBLE || visibility == DISTORTED)
{
if (shipp->radar_visible_since < 0)
{
shipp->radar_visible_since = Missiontime;
}
shipp->radar_last_contact = Missiontime;
}
shipp->radar_current_status = visibility;
}
/**
* Player ship uses this code, but does a quick out after doing a few things.
*
* When adding code to this function, decide whether or not a client in a multiplayer game
* needs to execute the code you are adding. Code which moves things, creates things, etc
* probably doesn't need to be called. If you don't know -- find Allender!!!
*/
void ship_process_post(object * obj, float frametime)
{
int num;
ship *shipp;
ship_info *sip;
if(obj->type != OBJ_SHIP){
nprintf(("General","Ignoring non-ship object in ship_process_post()\n"));
return;
}
MONITOR_INC( NumShips, 1 );
num = obj->instance;
Assert( num >= 0 && num < MAX_SHIPS);
Assert( obj->type == OBJ_SHIP );
Assert( Ships[num].objnum == OBJ_INDEX(obj));
shipp = &Ships[num];
sip = &Ship_info[shipp->ship_info_index];
shipp->shield_hits = 0;
update_ets(obj, frametime);
afterburners_update(obj, frametime);
ship_subsys_disrupted_maybe_check(shipp);
ship_dying_frame(obj, num);
ship_move_ets_transfer_buffers(shipp, obj, frametime);
// AL 1-6-98: record the initial ammo counts for ships, which is used as the max limit for rearming
// Goober5000 - added ballistics support
if ( !(shipp->flags[Ship_Flags::Ammo_count_recorded]) )
{
int max_missiles;
for ( int i=0; i<MAX_SHIP_SECONDARY_BANKS; i++ ) {
if ( red_alert_mission() )
{
max_missiles = get_max_ammo_count_for_bank(shipp->ship_info_index, i, shipp->weapons.secondary_bank_weapons[i]);
shipp->weapons.secondary_bank_start_ammo[i] = max_missiles;
}
else
{
shipp->weapons.secondary_bank_start_ammo[i] = shipp->weapons.secondary_bank_ammo[i];
}
}
for ( int i=0; i<MAX_SHIP_PRIMARY_BANKS; i++ )
{
if ( red_alert_mission() )
{
max_missiles = get_max_ammo_count_for_primary_bank(shipp->ship_info_index, i, shipp->weapons.primary_bank_weapons[i]);
shipp->weapons.primary_bank_start_ammo[i] = max_missiles;
}
else
{
shipp->weapons.primary_bank_start_ammo[i] = shipp->weapons.primary_bank_ammo[i];
}
}
shipp->flags.set(Ship_Flags::Ammo_count_recorded);
}
if(!(Game_mode & GM_STANDALONE_SERVER)) {
// Plot ship on the radar. What about multiplayer ships?
if ( obj != Player_obj && Game_mode & GM_IN_MISSION ) // don't plot myself.
radar_plot_object( obj );
// MWA -- move the spark code to before the check for multiplayer master
// Do ship sparks. Don't do sparks on my ship (since I cannot see it). This
// code will do sparks on other ships in multiplayer though.
// JAS: Actually in external view, you can see sparks, so I don't do sparks
// on the Viewer_obj, not Player_obj.
if ( (obj != Viewer_obj) && timestamp_elapsed(Ships[num].next_hit_spark) ) {
shipfx_emit_spark(num,-1); // -1 means choose random spark location
}
if ( obj != Viewer_obj ) {
shipfx_do_lightning_arcs_frame( shipp );
}
// JAS - flicker the thruster bitmaps
ship_do_thruster_frame(shipp,obj,frametime);
}
ship_auto_repair_frame(num, frametime);
shipfx_do_lightning_frame(shipp);
// if the ship has an EMP effect active, process it
emp_process_ship(shipp);
// call the contrail system
ct_ship_process(shipp);
// process engine wash
void engine_wash_ship_process(ship *shipp);
engine_wash_ship_process(shipp);
// update TAG info
if(shipp->tag_left > 0.0f){
shipp->tag_left -= flFrametime;
if(shipp->tag_left <= 0.000001f){
shipp->tag_left = -1.0f;
mprintf(("Killing TAG for %s\n", shipp->ship_name));
}
}
// update level 2 TAG info
if(shipp->level2_tag_left > 0.0f){
shipp->level2_tag_left -= flFrametime;
if(shipp->level2_tag_left <= 0.000001f){
shipp->level2_tag_left = -1.0f;
mprintf(("Killing level 2 TAG for %s\n", shipp->ship_name));
}
}
if ( shipp->is_arriving(ship::warpstage::BOTH, true) && Ai_info[shipp->ai_index].mode != AIM_BAY_EMERGE ) {
// JAS -- if the ship is warping in, just move it forward at a speed
// fast enough to move 2x its radius in SHIP_WARP_TIME seconds.
shipfx_warpin_frame( obj, frametime );
} else if ( shipp->flags[Ship_Flags::Depart_warp] ) {
// JAS -- if the ship is warping out, just move it forward at a speed
// fast enough to move 2x its radius in SHIP_WARP_TIME seconds.
shipfx_warpout_frame( obj, frametime );
}
// update radar status of the ship
ship_radar_process(obj, shipp, sip);
if ( (!(shipp->is_arriving()) || (Ai_info[shipp->ai_index].mode == AIM_BAY_EMERGE)
|| ((Warp_params[shipp->warpin_params_index].warp_type == WT_IN_PLACE_ANIM) && (shipp->flags[Ship_Flags::Arriving_stage_2])) )
&& !(shipp->flags[Ship_Flags::Depart_warp]))
{
// Do AI.
// for multiplayer people. return here if in multiplay and not the host
if ( MULTIPLAYER_CLIENT ) {
return;
}
// MWA -- moved the code to maybe fire swarm missiles to after the check for
// multiplayer master. Only single player and multi server needs to do this code
// this code might call ship_fire_secondary which will send the fire packets
swarm_maybe_fire_missile(num);
// maybe fire turret swarm missiles
void turret_swarm_maybe_fire_missile(int num);
turret_swarm_maybe_fire_missile(num);
// maybe fire a corkscrew missile (just like swarmers)
cscrew_maybe_fire_missile(num);
if (obj == Player_obj) {
ship_check_player_distance();
}
// If Framerate_independent_turning is true this is evaluated in ship_process_pre()
if (!Framerate_independent_turning)
ship_evaluate_ai(obj, frametime);
}
}
/**
* Set the ship level weapons based on the information contained in the ship info.
*
* Weapon assignments are checked against the model to ensure the models
* and the ship info weapon data are in synch.
*/
static void ship_set_default_weapons(ship *shipp, ship_info *sip)
{
int i, j;
polymodel *pm;
ship_weapon *swp = &shipp->weapons;
weapon_info *wip;
// Copy primary and secondary weapons from ship_info to ship.
// Later, this will happen in the weapon loadout screen.
for (i=0; i < MAX_SHIP_PRIMARY_BANKS; i++){
swp->primary_bank_weapons[i] = sip->primary_bank_weapons[i];
swp->primary_bank_slot_count[i] = 1; // RSAXVC DYN LINK CODE
}
for (i=0; i < MAX_SHIP_SECONDARY_BANKS; i++){
swp->secondary_bank_weapons[i] = sip->secondary_bank_weapons[i];
}
// Copy the number of primary and secondary banks to ship, and verify that
// model is in synch
pm = model_get( sip->model_num );
// Primary banks
if ( pm->n_guns > sip->num_primary_banks ) {
Assert(pm->n_guns <= MAX_SHIP_PRIMARY_BANKS);
Error(LOCATION, "There are %d primary banks in the model file,\nbut only %d primary banks specified for %s.\nThis must be fixed, as it will cause crashes.\n", pm->n_guns, sip->num_primary_banks, sip->name);
for ( i = sip->num_primary_banks; i < pm->n_guns; i++ ) {
// Make unspecified weapon for bank be a laser
for ( j = 0; j < Num_player_weapon_precedence; j++ ) {
Assertion((Player_weapon_precedence[j] > 0), "Error reading player weapon precedence list. Check weapons.tbl for $Player Weapon Precedence entry, and correct as necessary.\n");
int weapon_id = Player_weapon_precedence[j];
if ( (Weapon_info[weapon_id].subtype == WP_LASER) || (Weapon_info[weapon_id].subtype == WP_BEAM) ) {
swp->primary_bank_weapons[i] = weapon_id;
break;
}
}
Assert(swp->primary_bank_weapons[i] >= 0);
}
sip->num_primary_banks = pm->n_guns;
}
else if ( pm->n_guns < sip->num_primary_banks ) {
Warning(LOCATION, "There are %d primary banks specified for %s\nbut only %d primary banks in the model\n", sip->num_primary_banks, sip->name, pm->n_guns);
sip->num_primary_banks = pm->n_guns;
}
// Secondary banks
if ( pm->n_missiles > sip->num_secondary_banks ) {
Assert(pm->n_missiles <= MAX_SHIP_SECONDARY_BANKS);
Error(LOCATION, "There are %d secondary banks in the model file,\nbut only %d secondary banks specified for %s.\nThis must be fixed, as it will cause crashes.\n", pm->n_missiles, sip->num_secondary_banks, sip->name);
for ( i = sip->num_secondary_banks; i < pm->n_missiles; i++ ) {
// Make unspecified weapon for bank be a missile
for ( j = 0; j < Num_player_weapon_precedence; j++ ) {
Assertion((Player_weapon_precedence[j] > 0), "Error reading player weapon precedence list. Check weapons.tbl for $Player Weapon Precedence entry, and correct as necessary.\n");
int weapon_id = Player_weapon_precedence[j];
if (Weapon_info[weapon_id].subtype == WP_MISSILE) {
swp->secondary_bank_weapons[i] = weapon_id;
break;
}
}
Assert(swp->secondary_bank_weapons[i] >= 0);
}
sip->num_secondary_banks = pm->n_missiles;
}
else if ( pm->n_missiles < sip->num_secondary_banks ) {
Warning(LOCATION, "There are %d secondary banks specified for %s,\n but only %d secondary banks in the model.\n", sip->num_secondary_banks, sip->name, pm->n_missiles);
sip->num_secondary_banks = pm->n_missiles;
}
// added ballistic primary support - Goober5000
swp->num_primary_banks = sip->num_primary_banks;
for ( i = 0; i < swp->num_primary_banks; i++ )
{
wip = &Weapon_info[swp->primary_bank_weapons[i]];
if ( wip->wi_flags[Weapon::Info_Flags::Ballistic] )
{
if (Fred_running){
swp->primary_bank_ammo[i] = 100;
}
else
{
float capacity, size;
capacity = (float) sip->primary_bank_ammo_capacity[i];
size = (float) wip->cargo_size;
swp->primary_bank_ammo[i] = (int)std::lround(capacity / size);
swp->primary_bank_start_ammo[i] = swp->primary_bank_ammo[i];
}
swp->primary_bank_capacity[i] = sip->primary_bank_ammo_capacity[i];
}
}
swp->num_secondary_banks = sip->num_secondary_banks;
for ( i = 0; i < swp->num_secondary_banks; i++ ) {
if (Weapon_info[swp->secondary_bank_weapons[i]].wi_flags[Weapon::Info_Flags::SecondaryNoAmmo]) {
swp->secondary_bank_ammo[i] = 0;
swp->secondary_bank_start_ammo[i] = swp->secondary_bank_ammo[i];
swp->secondary_bank_capacity[i] = sip->secondary_bank_ammo_capacity[i];
}
if (Fred_running){
swp->secondary_bank_ammo[i] = 100;
} else {
wip = &Weapon_info[swp->secondary_bank_weapons[i]];
float size = (float) wip->cargo_size;
swp->secondary_bank_ammo[i] = fl2i(sip->secondary_bank_ammo_capacity[i]/size);
// Karajorma - Support ships will use the wrong values if we don't set this.
swp->secondary_bank_start_ammo[i] = swp->secondary_bank_ammo[i];
}
swp->secondary_bank_capacity[i] = sip->secondary_bank_ammo_capacity[i];
}
for ( i = 0; i < MAX_SHIP_PRIMARY_BANKS; i++ ){
swp->next_primary_fire_stamp[i] = timestamp(0);
swp->last_primary_fire_stamp[i] = -1;
swp->burst_counter[i] = 0;
swp->burst_seed[i] = Random::next();
swp->last_primary_fire_sound_stamp[i] = timestamp(0);
}
for ( i = 0; i < MAX_SHIP_SECONDARY_BANKS; i++ ){
swp->next_secondary_fire_stamp[i] = timestamp(0);
swp->last_secondary_fire_stamp[i] = -1;
swp->burst_counter[i + MAX_SHIP_PRIMARY_BANKS] = 0;
swp->burst_seed[i + MAX_SHIP_PRIMARY_BANKS] = Random::next();
}
//Countermeasures
shipp->current_cmeasure = sip->cmeasure_type;
}
/**
* Faster version of ship_check_collision that does not do checking at the polygon
* level. Just checks to see if a vector will intersect a sphere.
*/
int ship_check_collision_fast( object * obj, object * other_obj, vec3d * hitpos)
{
int num;
mc_info mc;
Assert( obj->type == OBJ_SHIP );
Assert( obj->instance >= 0 );
num = obj->instance;
mc_info_init(&mc);
mc.model_instance_num = Ships[num].model_instance_num;
mc.model_num = Ship_info[Ships[num].ship_info_index].model_num; // Fill in the model to check
mc.orient = &obj->orient; // The object's orient
mc.pos = &obj->pos; // The object's position
mc.p0 = &other_obj->last_pos; // Point 1 of ray to check
mc.p1 = &other_obj->pos; // Point 2 of ray to check
mc.flags = MC_ONLY_SPHERE; // flags
model_collide(&mc);
if (mc.num_hits)
*hitpos = mc.hit_point_world;
return mc.num_hits;
}
/**
* Ensure create time for ship is unique
*/
static void ship_make_create_time_unique(ship *shipp)
{
static int last_smctu_initial_time = -1;
static int last_smctu_final_time = -1;
int sanity_counter = 0, collision;
ship *compare_shipp;
ship_obj *so;
uint new_create_time;
new_create_time = shipp->create_time;
while (1) {
collision = 0;
for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) {
compare_shipp = &Ships[Objects[so->objnum].instance];
if ( compare_shipp == shipp ) {
continue;
}
if ( compare_shipp->create_time == new_create_time )
{
if((unsigned int)sanity_counter == 0 && (unsigned int)last_smctu_initial_time == shipp->create_time)
{
//WMC: If we're creating a whole bunch of ships at once, we can
//shortcut this process by looking at the last call to this function
//This fixes a bug when more than 50 ships are created at once.
new_create_time = last_smctu_final_time + 1;
}
else
{
new_create_time++;
}
collision = 1;
break;
}
}
if ( !collision ) {
last_smctu_initial_time = shipp->create_time;
last_smctu_final_time = new_create_time;
shipp->create_time = new_create_time;
break;
}
if ( sanity_counter++ > MAX_SHIPS ) {
Int3();
break;
}
}
}
int Ship_subsys_hwm = 0;
static void show_ship_subsys_count()
{
object *objp;
int count = 0;
int o_type = 0;
for ( objp = GET_FIRST(&obj_used_list); objp != END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) ) {
o_type = (int)objp->type;
if (o_type == OBJ_SHIP) {
count += Ship_info[Ships[objp->instance].ship_info_index].n_subsystems;
}
}
if (count > Ship_subsys_hwm) {
Ship_subsys_hwm = count;
}
}
static void ship_init_afterburners(ship *shipp)
{
Assert( shipp );
shipp->ab_count = 0;
if (shipp->ship_info_index < 0) {
Int3();
return;
}
ship_info *sip = &Ship_info[shipp->ship_info_index];
Assert( sip->model_num >= 0 );
polymodel *pm = model_get(sip->model_num);
Assert( pm != NULL );
if ( !(sip->flags[Ship::Info_Flags::Afterburner]) ) {
return;
}
if (sip->afterburner_trail.bitmap_id < 0) {
return;
}
for (int i = 0; i < pm->n_thrusters; i++) {
thruster_bank *bank = &pm->thrusters[i];
for (int j = 0; j < bank->num_points; j++) {
// this means you've reached the max # of AB trails for a ship
if (shipp->ab_count >= MAX_SHIP_CONTRAILS) {
Int3();
break;
}
trail_info *ci = &shipp->ab_info[shipp->ab_count];
if (bank->points[j].norm.xyz.z > -0.5f) {
continue; // only make ab trails for thrusters that are pointing backwards
}
ci->pt = bank->points[j].pnt; //offset
ci->w_start = bank->points[j].radius * sip->afterburner_trail_width_factor; // width * table loaded width factor
ci->w_end = 0.05f; //end width
ci->a_start = sip->afterburner_trail_alpha_factor; // start alpha * table loaded alpha factor
ci->a_end = sip->afterburner_trail_alpha_end_factor; //end alpha
ci->a_decay_exponent = sip->afterburner_trail_alpha_decay_exponent;
ci->max_life = sip->afterburner_trail_life; // table loaded max life
ci->stamp = 60; //spew time???
ci->spread = sip->afterburner_trail_spread; // table loaded spread speed
ci->n_fade_out_sections = sip->afterburner_trail_faded_out_sections; // initial fade out
ci->texture.bitmap_id = sip->afterburner_trail.bitmap_id; // table loaded bitmap used on this ships burner trails
ci->texture_stretch = sip->afterburner_trail_tex_stretch;
nprintf(("AB TRAIL", "AB trail point #%d made for '%s'\n", shipp->ab_count, shipp->ship_name));
shipp->ab_count++;
}
}
}
/**
* Returns object index of ship.
* @return -1 means failed.
*/
int ship_create(matrix* orient, vec3d* pos, int ship_type, const char* ship_name)
{
int i, n, objnum, j, k, t;
ship_info *sip;
ship *shipp;
t = ship_get_num_ships();
// The following check caps the number of ships that can be created. Because Fred needs
// to create all the ships, regardless of when they arrive/depart, it needs a higher
// limit than FreeSpace. On release, however, we will reduce it, thus FreeSpace needs
// to check against what this limit will be, otherwise testing the missions before
// release could work fine, yet not work anymore once a release build is made.
if (Fred_running) {
if (t >= MAX_SHIPS)
return -1;
} else {
if (t >= SHIPS_LIMIT) {
Error(LOCATION, XSTR("There is a limit of %d ships in the mission at once. Please be sure that you do not have more than %d ships present in the mission at the same time.", 1495), SHIPS_LIMIT, SHIPS_LIMIT );
return -1;
}
}
for (n=0; n<MAX_SHIPS; n++){
if (Ships[n].objnum == -1){
break;
}
}
if (n == MAX_SHIPS){
return -1;
}
Assertion((ship_type >= 0) && (ship_type < ship_info_size()), "Invalid ship_type %d passed to ship_create() (expected value in the range 0-%d)\n", ship_type, ship_info_size()-1);
sip = &(Ship_info[ship_type]);
shipp = &Ships[n];
shipp->clear();
sip->model_num = model_load(sip->pof_file, sip->n_subsystems, &sip->subsystems[0]); // use the highest detail level
if(strlen(sip->cockpit_pof_file))
{
sip->cockpit_model_num = model_load(sip->cockpit_pof_file, 0, NULL);
}
// maybe load an optional hud target model
if(strlen(sip->pof_file_hud)){
// check to see if a "real" ship uses this model. if so, load it up for him so that subsystems are setup properly
for(auto it = Ship_info.begin(); it != Ship_info.end(); ++it){
if(!stricmp(it->pof_file, sip->pof_file_hud)){
it->model_num = model_load(it->pof_file, it->n_subsystems, &it->subsystems[0]);
}
}
// mow load it for me with no subsystems
sip->model_num_hud = model_load(sip->pof_file_hud, 0, NULL);
}
if (strlen(sip->generic_debris_pof_file)) {
sip->generic_debris_model_num = model_load(sip->generic_debris_pof_file, 0, nullptr);
if (sip->generic_debris_model_num >= 0) {
polymodel* pm = model_get(sip->generic_debris_model_num);
sip->generic_debris_num_submodels = pm->n_models;
}
}
polymodel *pm = model_get(sip->model_num);
ship_copy_subsystem_fixup(sip);
show_ship_subsys_count();
if ( sip->num_detail_levels != pm->n_detail_levels )
{
if ( !Is_standalone )
{
// just log to file for standalone servers
Warning(LOCATION, "For ship '%s', detail level\nmismatch. Table has %d,\nPOF has %d.", sip->name, sip->num_detail_levels, pm->n_detail_levels );
}
else
{
nprintf(("Warning", "For ship '%s', detail level mismatch. Table has %d, POF has %d.", sip->name, sip->num_detail_levels, pm->n_detail_levels ));
}
}
for ( i=0; i<pm->n_detail_levels; i++ )
pm->detail_depth[i] = (i < sip->num_detail_levels) ? i2fl(sip->detail_distance[i]) : 0.0f;
flagset<Object::Object_Flags> default_ship_object_flags;
default_ship_object_flags.set(Object::Object_Flags::Renders);
default_ship_object_flags.set(Object::Object_Flags::Physics);
// JAS: Nav buoys don't need to do collisions!
// G5K: Corrected to apply specifically for ships with the no-collide flag. (In retail, navbuoys already have this flag, so this doesn't break anything.)
default_ship_object_flags.set(Object::Object_Flags::Collides, !sip->flags[Ship::Info_Flags::No_collide]);
objnum = obj_create(OBJ_SHIP, -1, n, orient, pos, model_get_radius(sip->model_num), default_ship_object_flags);
Assert( objnum >= 0 );
shipp->model_instance_num = model_create_instance(objnum, sip->model_num);
shipp->ai_index = ai_get_slot(n);
Assert( shipp->ai_index >= 0 );
// Goober5000 - if no ship name specified, or if specified ship already exists,
// or if specified ship has exited, use a default name
// Cyborg17 - The final check here was supposed to prevent duplicate names from being on the mission log and causing chaos,
// but it breaks multi, so there will just be a warning on debug instead.
if ((ship_name == nullptr) || (ship_name_lookup(ship_name) >= 0) /*|| (ship_find_exited_ship_by_name(ship_name) >= 0)*/) {
char suffix[NAME_LENGTH];
sprintf(suffix, NOX(" %d"), n);
// ensure complete ship name doesn't overflow the buffer
auto name_len = std::min(NAME_LENGTH - strlen(suffix) - 1, strlen(Ship_info[ship_type].name));
Assert(name_len > 0);
strncpy(shipp->ship_name, Ship_info[ship_type].name, name_len);
strcpy(shipp->ship_name + name_len, suffix);
} else {
if (ship_find_exited_ship_by_name(ship_name) >= 0 && !(Game_mode & GM_MULTIPLAYER)) {
Warning(LOCATION, "Newly-arrived ship %s has been given the same name as a ship previously destroyed in-mission. This can cause unpredictable SEXP behavior. Correct your mission file or scripts to prevent duplicates.", ship_name);
}
strcpy_s(shipp->ship_name, ship_name);
}
ship_set_default_weapons(shipp, sip); // Moved up here because ship_set requires that weapon info be valid. MK, 4/28/98
ship_set(n, objnum, ship_type);
init_ai_object(objnum);
ai_clear_ship_goals( &Ai_info[Ships[n].ai_index] ); // only do this one here. Can't do it in init_ai because it might wipe out goals in mission file
// Bump the object radius to ensure that collision detection works right
// even when spread shields extend outside the model's natural radius
if (sip->flags[Ship::Info_Flags::Auto_spread_shields]) {
Objects[objnum].radius += sip->auto_shield_spread;
}
// allocate memory for keeping glow point bank status (enabled/disabled)
{
bool val = true; // default value, enabled
if (pm->n_glow_point_banks)
shipp->glow_point_bank_active.resize( pm->n_glow_point_banks, val );
}
// fix up references into paths for this ship's model to point to a ship_subsys entry instead
// of a submodel index. The ship_subsys entry should be the same for *all* instances of the
// same ship.
if (!(sip->flags[Ship::Info_Flags::Path_fixup]))
{
for ( i = 0; i < pm->n_paths; i++ )
{
for ( j = 0; j < pm->paths[i].nverts; j++ )
{
for ( k = 0; k < pm->paths[i].verts[j].nturrets; k++ )
{
int ptindex = pm->paths[i].verts[j].turret_ids[k]; // this index is a submodel number (ala bspgen)
int index;
ship_subsys *ss;
// iterate through the ship_subsystems looking for an id that matches
index = 0;
ss = GET_FIRST(&Ships[n].subsys_list);
while ( ss != END_OF_LIST( &Ships[n].subsys_list ) ) {
if ( ss->system_info->subobj_num == ptindex ) { // when these are equal, fix up the ref
pm->paths[i].verts[j].turret_ids[k] = index; // in path structure to index a ship_subsys
break;
}
index++;
ss = GET_NEXT( ss );
}
if ( ss == END_OF_LIST(&Ships[n].subsys_list) )
Warning(LOCATION, "Couldn't fix up turret indices in spline path\n\nModel: %s\nPath: %s\nVertex: %d\nTurret model id:%d\n\nThis probably means that the turret was not specified in the ship table(s).", sip->pof_file, pm->paths[i].name, j, ptindex );
}
}
}
sip->flags.set(Ship::Info_Flags::Path_fixup);
}
// this used to be done in parse_create_object_sub
if (!Fred_running)
ship_assign_sound(shipp);
// first try at ABtrails -Bobboau
ship_init_afterburners(shipp);
// call the contrail system
ct_ship_create(shipp);
animation::anim_set_initial_states(shipp);
// Add this ship to Ship_obj_list
shipp->ship_list_index = ship_obj_list_add(objnum);
// Goober5000 - update the ship registry
// (since scripts and sexps can create ships, the entry may not yet exist)
auto ship_it = Ship_registry_map.find(shipp->ship_name);
if (ship_it == Ship_registry_map.end())
{
ship_registry_entry entry(shipp->ship_name);
entry.status = ShipStatus::PRESENT;
entry.objp = &Objects[objnum];
entry.shipp = shipp;
Ship_registry.push_back(entry);
Ship_registry_map[shipp->ship_name] = static_cast<int>(Ship_registry.size() - 1);
}
else
{
auto entry = &Ship_registry[ship_it->second];
entry->status = ShipStatus::PRESENT;
entry->objp = &Objects[objnum];
entry->shipp = shipp;
}
// Start up stracking for this ship in multi.
if (Game_mode & (GM_MULTIPLAYER)) {
multi_ship_record_add_ship(objnum);
}
// Set time when ship is created
shipp->create_time = timer_get_milliseconds();
ship_make_create_time_unique(shipp);
shipp->time_created = Missiontime;
return objnum;
}
/**
* Change the ship model for a ship to that for ship class 'ship_type'
*
* @param n index of ship in ::Ships[] array
* @param ship_type ship class (index into ::Ship_info vector)
*/
static void ship_model_change(int n, int ship_type)
{
int i;
ship_info *sip;
ship *sp;
polymodel * pm;
object *objp;
Assert( n >= 0 && n < MAX_SHIPS );
sp = &Ships[n];
sip = &(Ship_info[ship_type]);
objp = &Objects[sp->objnum];
//Stop Animation on the old model
animation::ModelAnimationSet::stopAnimations(model_get_instance(sp->model_instance_num));
// get new model
if (sip->model_num == -1) {
sip->model_num = model_load(sip->pof_file, sip->n_subsystems, &sip->subsystems[0]);
}
if ( sip->cockpit_model_num == -1 ) {
if ( strlen(sip->cockpit_pof_file) ) {
sip->cockpit_model_num = model_load(sip->cockpit_pof_file, 0, NULL);
}
}
pm = model_get(sip->model_num);
Objects[sp->objnum].radius = model_get_radius(pm->id);
// page in nondims in game
if ( !Fred_running )
model_page_in_textures(sip->model_num, ship_type);
// allocate memory for keeping glow point bank status (enabled/disabled)
{
bool val = true; // default value, enabled
// clear out any old gpb's first, then add new ones if needed
sp->glow_point_bank_active.clear();
if (pm->n_glow_point_banks)
sp->glow_point_bank_active.resize( pm->n_glow_point_banks, val );
}
ship_copy_subsystem_fixup(sip);
if ( sip->num_detail_levels != pm->n_detail_levels )
{
if ( !Is_standalone )
{
// just log to file for standalone servers
Warning(LOCATION, "For ship '%s', detail level\nmismatch. Table has %d,\nPOF has %d.", sip->name, sip->num_detail_levels, pm->n_detail_levels );
}
else
{
nprintf(("Warning", "For ship '%s', detail level mismatch. Table has %d, POF has %d.", sip->name, sip->num_detail_levels, pm->n_detail_levels ));
}
}
for ( i=0; i<pm->n_detail_levels; i++ )
pm->detail_depth[i] = (i < sip->num_detail_levels) ? i2fl(sip->detail_distance[i]) : 0.0f;
if (sip->flags[Ship::Info_Flags::Model_point_shields]) {
objp->n_quadrants = (int)pm->shield_points.size();
sp->shield_points = pm->shield_points;
} else {
objp->n_quadrants = DEFAULT_SHIELD_SECTIONS;
}
objp->shield_quadrant.resize(objp->n_quadrants);
// reset texture animations
sp->base_texture_anim_frametime = game_get_overall_frametime();
}
/**
* Change the ship class on a ship, and changing all required information
* for consistency (ie textures, subsystems, weapons, physics)
*
* @param n index of ship in ::Ships[] array
* @param ship_type ship class (index into ::Ship_info vector)
* @param by_sexp SEXP reference
*/
void change_ship_type(int n, int ship_type, int by_sexp)
{
int i;
ship_info *sip;
ship_info *sip_orig;
ship *sp;
ship_weapon *swp;
ship_subsys *ss;
object *objp;
p_object *p_objp;
float hull_pct, shield_pct;
physics_info ph_inf;
Assert( n >= 0 && n < MAX_SHIPS );
sp = &Ships[n];
// do a quick out if we're already using the new ship class
if (sp->ship_info_index == ship_type)
return;
int objnum = sp->objnum;
swp = &sp->weapons;
sip = &(Ship_info[ship_type]);
sip_orig = &Ship_info[sp->ship_info_index];
objp = &Objects[objnum];
p_objp = mission_parse_get_parse_object(sp->ship_name);
ph_inf = objp->phys_info;
// MageKing17 - See if any AIs are doing anything with subsystems of this ship (targeting, goal to destroy)
// keep track of those subsystems and transfer the target/goal if the subsystem still exists, delete otherwise
SCP_list<int> target_matches; // tells us to check this Ai_info index during subsystem traversal
SCP_list<ivec3> subsystem_matches; // Ai_info index, goal index (or -1 for targeted), subsystem index
// not having a subsystem index at first is why we have the target_matches list:
// while traversing the subsystem list, a match can be compared directly to the
// subsystem object, and its index added to subsystem_matches.
SCP_list<weapon*> homing_matches; // any missiles locked on to a subsystem from this ship
SCP_list<std::pair<weapon*, int>> homing_subsystem_matches; // When homing_matches find matches, they put them here
SCP_list<weapon*> weapon_turret_matches; // projectiles that were fired from turrets on this ship
SCP_list<std::pair<weapon*, int>> weapon_turret_subsystem_matches;
SCP_list<int> last_targeted_matches;
SCP_list<std::pair<int, int>> last_targeted_subsystem_matches;
if (!(Fred_running) && (Game_mode & GM_IN_MISSION)) { // Doing this effort only makes sense in the middle of a mission.
// Delete ship sparks if the model changed
if (sip_orig->model_num != sip->model_num) {
memset(sp->sparks, 0, MAX_SHIP_HITS * sizeof(ship_spark));
sp->num_hits = 0;
}
for (i = 0; i < MAX_AI_INFO; i++) {
if (Ai_info[i].shipnum > -1) {
if (Ai_info[i].targeted_subsys && Ai_info[i].targeted_subsys->parent_objnum == objnum) {
target_matches.push_back(i);
}
ai_goal* goals = Ai_info[i].goals;
for (int j = 0; j < MAX_AI_GOALS; j++) {
// POSSIBLE OPTIMIZATION: goals[0] should be the active goal, so we might be able to reuse target_subsys_parent for that instead of doing ship_name_lookup()
if (goals[j].ai_mode == AI_GOAL_DESTROY_SUBSYSTEM && !(goals[j].flags[AI::Goal_Flags::Subsys_needs_fixup])) { // If the subsystem name hasn't been parsed yet, we're fine.
int sindex = ship_name_lookup(goals[j].target_name);
if (sindex > -1 && Ships[sindex].objnum == objnum) {
ivec3 temp = {i, j, goals[j].ai_submode};
subsystem_matches.push_back(temp);
}
}
}
}
}
for (i = 0; i < MAX_WEAPONS; i++) {
if (Weapons[i].objnum == -1) {
continue;
}
weapon* wp = &Weapons[i];
if (wp->homing_subsys && wp->homing_subsys->parent_objnum == objnum) {
homing_matches.push_back(wp);
}
if (wp->turret_subsys && wp->turret_subsys->parent_objnum == objnum) {
weapon_turret_matches.push_back(wp);
}
}
for (i = 0; i < MAX_PLAYERS; i++) {
if (sp->last_targeted_subobject[i]) {
last_targeted_matches.push_back(i);
}
}
}
// Goober5000 - we can't copy the ship object because the tree of structs contains at least
// one class without a copy constructor. So let's just save the information we need.
// these are wiped by ets_init_ship
int orig_wep_rechg_idx = sp->weapon_recharge_index;
int orig_shd_rechg_idx = sp->shield_recharge_index;
int orig_eng_rechg_idx = sp->engine_recharge_index;
// Goober5000 - maintain the original hull, shield, and subsystem percentages... gah
// ...except when in FRED, because this stuff is handled in the missionparse/missionsave part. The E
if (!Fred_running) {
// hull
if (sp->special_hitpoints) {
hull_pct = objp->hull_strength / sp->ship_max_hull_strength;
} else {
Assert( Ship_info[sp->ship_info_index].max_hull_strength > 0.0f );
hull_pct = objp->hull_strength / Ship_info[sp->ship_info_index].max_hull_strength;
}
// extra check
CLAMP(hull_pct, 0.01f, 1.0f);
// shield
if (sp->special_shield > 0) {
shield_pct = shield_get_strength(objp) / shield_get_max_strength(objp);
} else if (Ship_info[sp->ship_info_index].max_shield_strength > 0.0f) {
shield_pct = shield_get_strength(objp) / (sip_orig->max_shield_strength * sip_orig->max_shield_recharge);
} else {
shield_pct = 0.0f;
}
// extra check
Assert(shield_pct >= 0.0f && shield_pct <= 1.0f);
CLAMP(shield_pct, 0.0f, 1.0f);
} else {
shield_pct = hull_pct = 1.0f;
}
// subsystems
int num_saved_subsystems = 0;
char **subsys_names = new char *[sip_orig->n_subsystems];
float *subsys_pcts = new float[sip_orig->n_subsystems];
ss = GET_FIRST(&sp->subsys_list);
while ( ss != END_OF_LIST(&sp->subsys_list) )
{
if (num_saved_subsystems == sip_orig->n_subsystems)
{
Error(LOCATION, "Subsystem mismatch while changing ship class from '%s' to '%s'!", sip_orig->name, sip->name);
break;
}
// MageKing17 - Update subsystem pointers if changing classes mid-mission
if (!(Fred_running) && (Game_mode & GM_IN_MISSION)) {
// If any of our AI info objects targeting a subsystem on this ship are targeting this specific
// subsystem, add them to the subsystem_matches vector and remove them from our "to be checked" list.
{
auto it = target_matches.begin();
while (it != target_matches.end()) {
ai_info* aip = &Ai_info[*it];
if (aip->targeted_subsys == ss) {
ivec3 temp = {*it, -1, num_saved_subsystems}; // -1 for the "goal" index means targeted, not actually a goal
subsystem_matches.push_back(temp);
aip->targeted_subsys = NULL; // Clear this so that aip->last_subsys_target won't point to a subsystem from the old list later
aip->targeted_subsys_parent = -1;
auto erasor = it;
++it;
target_matches.erase(erasor);
} else {
++it;
}
}
}
// Fix any weapons homing in on this subsystem.
{
auto it = homing_matches.begin();
while (it != homing_matches.end()) {
if ((*it)->homing_subsys == ss) {
homing_subsystem_matches.push_back(std::make_pair(*it, num_saved_subsystems));
auto erasor = it;
++it;
homing_matches.erase(erasor);
} else {
++it;
}
}
}
// Fix any projectiles fired from this subsystem.
{
auto it = weapon_turret_matches.begin();
while (it != weapon_turret_matches.end()) {
if ((*it)->turret_subsys == ss) {
weapon_turret_subsystem_matches.push_back(std::make_pair(*it, num_saved_subsystems));
auto erasor = it;
++it;
weapon_turret_matches.erase(erasor);
} else {
++it;
}
}
}
// Fix any subsystems in last_targeted_matches[].
{
auto it = last_targeted_matches.begin();
while (it != last_targeted_matches.end()) {
if (sp->last_targeted_subobject[*it] == ss) {
last_targeted_subsystem_matches.push_back(std::make_pair(*it, num_saved_subsystems));
auto erasor = it;
++it;
last_targeted_matches.erase(erasor);
} else {
++it;
}
}
}
}
// save subsys information
subsys_names[num_saved_subsystems] = new char[NAME_LENGTH];
strcpy(subsys_names[num_saved_subsystems], ss->system_info->subobj_name);
if (ss->max_hits > 0.0f)
subsys_pcts[num_saved_subsystems] = ss->current_hits / ss->max_hits;
else
subsys_pcts[num_saved_subsystems] = ss->max_hits;
// extra check
Assert(subsys_pcts[num_saved_subsystems] >= 0.0f && subsys_pcts[num_saved_subsystems] <= 1.0f);
CLAMP(subsys_pcts[num_saved_subsystems], 0.0f, 1.0f);
num_saved_subsystems++;
ss = GET_NEXT(ss);
}
Assertion(target_matches.empty(), "Failed to find matches for every currently-targeted subsystem on ship %s in change_ship_type(); get a coder!\n", sp->ship_name);
Assertion(homing_matches.empty(), "Failed to find matches for every subsystem being homed in on ship %s in change_ship_type(); get a coder!\n", sp->ship_name);
Assertion(weapon_turret_matches.empty(), "Failed to find matches for every turret a projectile was fired from on ship %s in change_ship_type(); get a coder!\n", sp->ship_name);
Assertion(last_targeted_matches.empty(), "Somehow failed to find every subsystem a player was previously targeting on ship %s in change_ship_type(); get a coder!\n", sp->ship_name);
// point to new ship data
ship_model_change(n, ship_type);
sp->ship_info_index = ship_type;
// create new model instance data
// note: this is needed for both subsystem stuff and submodel animation stuff
sp->model_instance_num = model_create_instance(objnum, sip->model_num);
// if we have the same warp parameters as the ship class, we will need to update them to point to the new class
if (sp->warpin_params_index == sip_orig->warpin_params_index) {
sp->warpin_params_index = sip->warpin_params_index;
}
if (sp->warpout_params_index == sip_orig->warpout_params_index) {
sp->warpout_params_index = sip->warpout_params_index;
}
if (!Fred_running) {
//WMC - set warp effects
ship_set_warp_effects(objp);
}
// set the correct hull strength
if (Fred_running) {
sp->ship_max_hull_strength = 100.0f;
objp->hull_strength = 100.0f;
} else {
if (sp->special_hitpoints > 0) {
sp->ship_max_hull_strength = (float)sp->special_hitpoints;
} else {
sp->ship_max_hull_strength = sip->max_hull_strength;
}
objp->hull_strength = hull_pct * sp->ship_max_hull_strength;
}
sp->max_shield_recharge = sip->max_shield_recharge;
// set the correct shield strength
if (Fred_running) {
if (sp->ship_max_shield_strength)
sp->ship_max_shield_strength = 100.0f;
objp->shield_quadrant[0] = 100.0f;
} else {
if (sp->special_shield >= 0) {
sp->ship_max_shield_strength = (float)sp->special_shield;
} else {
sp->ship_max_shield_strength = sip->max_shield_strength;
}
shield_set_strength(objp, shield_pct * shield_get_max_strength(objp));
}
// Goober5000: div-0 checks
Assert(sp->ship_max_hull_strength > 0.0f);
Assert(objp->hull_strength > 0.0f);
// Mantis 2763: moved down to have access to the right ship_max_shield_strength value
// make sure that shields are disabled/enabled if they need to be - Chief1983
if (!Fred_running) {
if ((p_objp != nullptr && p_objp->flags[Mission::Parse_Object_Flags::OF_Force_shields_on]) && (sp->ship_max_shield_strength > 0.0f)) {
objp->flags.remove(Object::Object_Flags::No_shields);
}
else if ((p_objp != nullptr && p_objp->flags[Mission::Parse_Object_Flags::OF_No_shields]) || (sp->ship_max_shield_strength == 0.0f)) {
objp->flags.set(Object::Object_Flags::No_shields);
// Since there's not a mission flag set to be adjusting this, see if there was a change from a ship that normally has shields to one that doesn't, and vice versa
}
else if (!(sip_orig->flags[Info_Flags::Intrinsic_no_shields]) && (sip->flags[Info_Flags::Intrinsic_no_shields])) {
objp->flags.set(Object::Object_Flags::No_shields);
}
else if ((sip_orig->flags[Info_Flags::Intrinsic_no_shields]) && !(sip->flags[Info_Flags::Intrinsic_no_shields]) && (sp->ship_max_shield_strength > 0.0f)) {
objp->flags.remove(Object::Object_Flags::No_shields);
}
}
// niffiwan: set new armor types
sp->armor_type_idx = sip->armor_type_idx;
sp->shield_armor_type_idx = sip->shield_armor_type_idx;
sp->collision_damage_type_idx = sip->collision_damage_type_idx;
sp->debris_damage_type_idx = sip->debris_damage_type_idx;
// subsys stuff done only after hull stuff is set
// if the subsystem list is not currently empty, then we need to clear it out first.
ship_subsystems_delete(sp);
// fix up the subsystems
subsys_set( sp->objnum );
// Goober5000 - restore the subsystem percentages
ss = GET_FIRST(&sp->subsys_list);
int ss_index = 0;
while ( ss != END_OF_LIST(&sp->subsys_list) )
{
for (i = 0; i < num_saved_subsystems; i++)
{
if (!subsystem_stricmp(ss->system_info->subobj_name, subsys_names[i]))
{
ss->current_hits = ss->max_hits * subsys_pcts[i];
// MageKing17 - Every AI doing something with this subsystem must transfer to the new one.
{
auto it = subsystem_matches.begin();
while (it != subsystem_matches.end()) {
if (it->z == i) { // the subsystem is a match
int goalnum = it->y;
ai_info* aip = &Ai_info[it->x];
if (goalnum == -1) {
set_targeted_subsys(aip, ss, n);
} else {
aip->goals[goalnum].ai_submode = ss_index;
}
auto erasor = it;
++it;
subsystem_matches.erase(erasor);
} else {
++it;
}
}
}
// also weapons homing in on this subsystem
{
auto it = homing_subsystem_matches.begin();
while (it != homing_subsystem_matches.end()) {
if (it->second == i) { // the subsystem is a match
it->first->homing_subsys = ss;
auto erasor = it;
++it;
homing_subsystem_matches.erase(erasor);
} else {
++it;
}
}
}
// also weapons fired from this subsystem
{
auto it = weapon_turret_subsystem_matches.begin();
while (it != weapon_turret_subsystem_matches.end()) {
if (it->second == i) {
it->first->turret_subsys = ss;
auto erasor = it;
++it;
weapon_turret_subsystem_matches.erase(erasor);
} else {
++it;
}
}
}
// and finally, fix previously-targeted subsystems
{
auto it = last_targeted_subsystem_matches.begin();
while (it != last_targeted_subsystem_matches.end()) {
if (it->second == i) { // the subsystem is a match
sp->last_targeted_subobject[it->first] = ss;
auto erasor = it;
++it;
last_targeted_subsystem_matches.erase(erasor);
} else {
++it;
}
}
}
break;
}
}
ss_index++;
ss = GET_NEXT(ss);
}
ship_recalc_subsys_strength(sp);
for (auto cit = subsystem_matches.cbegin(); cit != subsystem_matches.cend(); ++cit) {
int goalnum = cit->y;
ai_info* aip = &Ai_info[cit->x];
if (goalnum == -1) {
if (aip == Player_ai) {
hud_cease_subsystem_targeting(0);
} else {
set_targeted_subsys(aip, nullptr, -1);
}
} else {
ai_remove_ship_goal(aip, goalnum);
}
}
subsystem_matches.clear(); // We don't need these anymore, so may as well clear them now.
for (auto cit = homing_subsystem_matches.cbegin(); cit != homing_subsystem_matches.cend(); ++cit) {
cit->first->homing_subsys = nullptr;
}
homing_subsystem_matches.clear();
for (auto cit = weapon_turret_subsystem_matches.cbegin(); cit != weapon_turret_subsystem_matches.cend(); ++cit) {
cit->first->turret_subsys = nullptr;
}
weapon_turret_subsystem_matches.clear();
for (auto cit = last_targeted_subsystem_matches.cbegin(); cit != last_targeted_subsystem_matches.cend(); ++cit) {
sp->last_targeted_subobject[cit->first] = nullptr;
}
last_targeted_subsystem_matches.clear();
// now free the memory
for (i = 0; i < sip_orig->n_subsystems; i++)
delete[] subsys_names[i];
delete [] subsys_names;
delete [] subsys_pcts;
sp->afterburner_fuel = MAX(0, sip->afterburner_fuel_capacity - (sip_orig->afterburner_fuel_capacity - sp->afterburner_fuel));
sp->cmeasure_count = MAX(0, sip->cmeasure_max - (sip_orig->cmeasure_max - sp->cmeasure_count));
// avoid cases where either of these are 0
if (sp->current_max_speed != 0 && sip_orig->max_speed != 0) {
sp->current_max_speed = sip->max_speed * (sp->current_max_speed / sip_orig->max_speed);
}
else {
sp->current_max_speed = sip->max_speed;
}
ship_set_default_weapons(sp, sip);
physics_ship_init(&Objects[sp->objnum]);
ets_init_ship(&Objects[sp->objnum]);
// Reset physics to previous values
if (by_sexp) {
Objects[sp->objnum].phys_info.desired_rotvel = ph_inf.desired_rotvel;
Objects[sp->objnum].phys_info.desired_vel = ph_inf.desired_vel;
Objects[sp->objnum].phys_info.forward_thrust = ph_inf.forward_thrust;
Objects[sp->objnum].phys_info.fspeed = ph_inf.fspeed;
Objects[sp->objnum].phys_info.heading = ph_inf.heading;
Objects[sp->objnum].phys_info.last_rotmat = ph_inf.last_rotmat;
Objects[sp->objnum].phys_info.prev_fvec = ph_inf.prev_fvec;
Objects[sp->objnum].phys_info.prev_ramp_vel = ph_inf.prev_ramp_vel;
Objects[sp->objnum].phys_info.reduced_damp_decay = ph_inf.reduced_damp_decay;
Objects[sp->objnum].phys_info.rotvel = ph_inf.rotvel;
Objects[sp->objnum].phys_info.shockwave_decay = ph_inf.shockwave_decay;
Objects[sp->objnum].phys_info.shockwave_shake_amp = ph_inf.shockwave_shake_amp;
Objects[sp->objnum].phys_info.side_thrust = ph_inf.side_thrust;
Objects[sp->objnum].phys_info.speed = ph_inf.speed;
Objects[sp->objnum].phys_info.vel = ph_inf.vel;
Objects[sp->objnum].phys_info.vert_thrust = ph_inf.vert_thrust;
Objects[sp->objnum].phys_info.ai_desired_orient = ph_inf.ai_desired_orient;
}
ship_set_new_ai_class(sp, sip->ai_class);
//======================================================
// Bobboau's thruster stuff again
if (sip->afterburner_trail.bitmap_id < 0)
generic_bitmap_load(&sip->afterburner_trail);
sp->ab_count = 0;
if (sip->flags[Ship::Info_Flags::Afterburner])
{
polymodel *pm = model_get(sip->model_num);
for (int h = 0; h < pm->n_thrusters; h++)
{
for (int j = 0; j < pm->thrusters[h].num_points; j++)
{
// this means you've reached the max # of AB trails for a ship
Assert(sip->ct_count <= MAX_SHIP_CONTRAILS);
trail_info *ci = &sp->ab_info[sp->ab_count];
// only make ab trails for thrusters that are pointing backwards
if (pm->thrusters[h].points[j].norm.xyz.z > -0.5)
continue;
ci->pt = pm->thrusters[h].points[j].pnt; //offset
ci->w_start = pm->thrusters[h].points[j].radius * sip->afterburner_trail_width_factor; // width * table loaded width factor
ci->w_end = 0.05f;//end width
ci->a_start = sip->afterburner_trail_alpha_factor; // start alpha * table loaded alpha factor
ci->a_end = sip->afterburner_trail_alpha_end_factor;//end alpha
ci->a_decay_exponent = sip->afterburner_trail_alpha_decay_exponent;
ci->max_life = sip->afterburner_trail_life; // table loaded max life
ci->spread = sip->afterburner_trail_spread; // table loaded spread speed
ci->stamp = 60; //spew time???
ci->n_fade_out_sections = sip->afterburner_trail_faded_out_sections; // table loaded n sections to be faded out
ci->texture.bitmap_id = sip->afterburner_trail.bitmap_id; // table loaded bitmap used on this ships burner trails
ci->texture_stretch = sip->afterburner_trail_tex_stretch;
nprintf(("AB TRAIL", "AB trail point #%d made for '%s'\n", sp->ab_count, sp->ship_name));
sp->ab_count++;
Assert(MAX_SHIP_CONTRAILS > sp->ab_count);
}
}
}//end AB trails -Bobboau
// Goober5000 - check other class-specific flags too
if (sip->flags[Ship::Info_Flags::Stealth]) // changing TO a stealthy ship class
sp->flags.set(Ship_Flags::Stealth);
else if (sip_orig->flags[Ship::Info_Flags::Stealth]) // changing FROM a stealthy ship class
sp->flags.remove(Ship_Flags::Stealth);
if (sip->flags[Ship::Info_Flags::Ship_class_dont_collide_invis]) // changing TO a don't-collide-invisible ship class
sp->flags.set(Ship_Flags::Dont_collide_invis);
else if (sip_orig->flags[Ship::Info_Flags::Ship_class_dont_collide_invis]) // changing FROM a don't-collide-invisible ship class
sp->flags.remove(Ship_Flags::Dont_collide_invis);
if (sip->flags[Ship::Info_Flags::No_collide]) // changing TO a no-collision ship class
obj_set_flags(objp, objp->flags - Object::Object_Flags::Collides);
else if (sip_orig->flags[Ship::Info_Flags::No_collide]) // changing FROM a no-collision ship class
obj_set_flags(objp, objp->flags + Object::Object_Flags::Collides);
if (sip->flags[Ship::Info_Flags::No_ets])
sp->flags.set(Ship_Flags::No_ets);
else if (sip_orig->flags[Ship::Info_Flags::No_ets])
sp->flags.remove(Ship_Flags::No_ets);
// Chief1983: Make sure that when changing to a new ship with secondaries, you switch to bank 0. They still won't
// fire if the SF2_SECONDARIES_LOCKED flag is on as this should have carried over.
if ( swp->num_secondary_banks > 0 && swp->current_secondary_bank == -1 ){
swp->current_secondary_bank = 0;
}
// Bobboau's animation fixup
for( i = 0; i<MAX_SHIP_PRIMARY_BANKS;i++){
swp->primary_animation_position[i] = MA_POS_NOT_SET;
}
for( i = 0; i<MAX_SHIP_SECONDARY_BANKS;i++){
swp->secondary_animation_position[i] = MA_POS_NOT_SET;
}
animation::anim_set_initial_states(sp);
//Reassign sound stuff
if (!Fred_running)
ship_assign_sound(sp);
// Valathil - Reinitialize collision checks
obj_remove_collider(OBJ_INDEX(objp));
obj_add_collider(OBJ_INDEX(objp));
// The E - If we're switching during gameplay, make sure we get valid primary/secondary selections
if ( by_sexp ) {
if (sip_orig->num_primary_banks > sip->num_primary_banks) {
sp->weapons.current_primary_bank = 0;
sp->weapons.previous_primary_bank = 0;
}
if (sip_orig->num_secondary_banks > sip->num_secondary_banks) {
sp->weapons.current_secondary_bank = 0;
sp->weapons.previous_secondary_bank = 0;
}
// While we're at it, let's copy over the ETS settings too
sp->weapon_recharge_index = orig_wep_rechg_idx;
sp->shield_recharge_index = orig_shd_rechg_idx;
sp->engine_recharge_index = orig_eng_rechg_idx;
}
// zookeeper - If we're switching in the loadout screen, make sure we retain initial velocity set in FRED
if (!(Game_mode & GM_IN_MISSION) && !(Fred_running)) {
Objects[sp->objnum].phys_info.speed = (float) p_objp->initial_velocity * sip->max_speed / 100.0f;
// prev_ramp_vel needs to be in local coordinates
// set z of prev_ramp_vel to initial velocity
vm_vec_zero(&Objects[sp->objnum].phys_info.prev_ramp_vel);
Objects[sp->objnum].phys_info.prev_ramp_vel.xyz.z = Objects[sp->objnum].phys_info.speed;
// convert to global coordinates and set to ship velocity and desired velocity
vm_vec_unrotate(&Objects[sp->objnum].phys_info.vel, &Objects[sp->objnum].phys_info.prev_ramp_vel, &Objects[sp->objnum].orient);
Objects[sp->objnum].phys_info.desired_vel = Objects[sp->objnum].phys_info.vel;
}
// Goober5000 - if we're changing to a ship class that has a different default set of orders, update the orders
// (this avoids wiping the orders if we're e.g. changing between fighter classes)
if (Fred_running)
{
const std::set<size_t>& old_defaults = ship_get_default_orders_accepted(sip_orig);
const std::set<size_t>& new_defaults = ship_get_default_orders_accepted(sip);
if (old_defaults != new_defaults)
sp->orders_accepted = new_defaults;
}
// Goober5000 - deal with texture replacement by re-applying the same code we used during parsing
if (sp->ship_replacement_textures != nullptr)
{
// clear them out because the new positions may be different
for (i = 0; i < MAX_REPLACEMENT_TEXTURES; i++)
sp->ship_replacement_textures[i] = -1;
if (p_objp != nullptr)
{
// now fill them in according to texture name
for (const auto &tr : p_objp->replacement_textures)
{
int j;
polymodel* pm = model_get(sip->model_num);
// look for textures
for (j = 0; j < pm->n_textures; j++)
{
texture_map* tmap = &pm->maps[j];
int tnum = tmap->FindTexture(tr.old_texture);
if (tnum > -1)
sp->ship_replacement_textures[j * TM_NUM_TYPES + tnum] = tr.new_texture_id;
}
}
}
}
if (sip->uses_team_colors)
{
sp->team_name = sip->default_team_name;
}
}
/**
* Launch countermeasures from object *objp.
*
* @param objp object from which to launch countermeasure
* @param rand_val is used in multiplayer to ensure that all clients in the game fire countermeasure the same way
*/
int ship_launch_countermeasure(object *objp, int rand_val)
{
if(!Countermeasures_enabled) {
return 0;
}
int check_count, cmeasure_count;
int cobjnum=-1;
vec3d pos;
ship *shipp;
ship_info *sip;
shipp = &Ships[objp->instance];
sip = &Ship_info[shipp->ship_info_index];
int arand;
if(rand_val < 0) {
arand = Random::next();
} else {
arand = rand_val;
}
// in the case where the server is an observer, he can launch countermeasures unless we do this.
if( objp->type == OBJ_OBSERVER){
return 0;
}
if ( !timestamp_elapsed(shipp->cmeasure_fire_stamp) ){
return 0;
}
shipp->cmeasure_fire_stamp = timestamp(Weapon_info[shipp->current_cmeasure].cmeasure_firewait); // Can launch every cmeasure wait
#ifndef NDEBUG
if (Weapon_energy_cheat) {
shipp->cmeasure_count++;
}
#endif
// we might check the count of countermeasures left depending on game state. Multiplayer clients
// do not need to check any objects other than themselves for the count
check_count = 1;
if ( MULTIPLAYER_CLIENT && (objp != Player_obj) ){
check_count = 0;
}
if ( check_count && ((shipp->cmeasure_count <= 0) || (sip->cmeasure_type < 0)) ) {
if ( objp == Player_obj ) {
if(sip->cmeasure_max < 1 || sip->cmeasure_type < 0) {
//TODO: multi-lingual support
HUD_sourced_printf(HUD_SOURCE_HIDDEN, "%s", XSTR( "Not equipped with countermeasures", 1633));
} else if(shipp->current_cmeasure < 0) {
//TODO: multi-lingual support
HUD_sourced_printf(HUD_SOURCE_HIDDEN, "%s", XSTR( "No countermeasures selected", 1634));
} else if(shipp->cmeasure_count <= 0) {
HUD_sourced_printf(HUD_SOURCE_HIDDEN, "%s", XSTR( "No more countermeasure charges.", 485));
}
snd_play( gamesnd_get_game_sound(ship_get_sound(Player_obj, GameSounds::OUT_OF_MISSLES)), 0.0f );
}
// if we have a player ship, then send the fired packet anyway so that the player
// who fired will get his 'out of countermeasures' sound
cmeasure_count = 0;
if (objp->flags[Object::Object_Flags::Player_ship]){
// the new way of doing things
if (Game_mode & GM_MULTIPLAYER){
send_NEW_countermeasure_fired_packet(objp, cmeasure_count, -1);
}
return 0;
}
return 0;
}
cmeasure_count = shipp->cmeasure_count;
shipp->cmeasure_count--;
vm_vec_scale_add(&pos, &objp->pos, &objp->orient.vec.fvec, -objp->radius/2.0f);
cobjnum = weapon_create(&pos, &objp->orient, shipp->current_cmeasure, OBJ_INDEX(objp));
if (cobjnum >= 0)
{
cmeasure_set_ship_launch_vel(&Objects[cobjnum], objp, arand);
nprintf(("Network", "Cmeasure created by %s\n", shipp->ship_name));
// Play sound effect for counter measure launch
Assert(shipp->current_cmeasure < weapon_info_size());
if ( Weapon_info[shipp->current_cmeasure].launch_snd.isValid() ) {
snd_play_3d( gamesnd_get_game_sound(Weapon_info[shipp->current_cmeasure].launch_snd), &pos, &View_position );
}
// the new way of doing things
if(Game_mode & GM_MULTIPLAYER){
send_NEW_countermeasure_fired_packet(objp, cmeasure_count, Objects[cobjnum].net_signature);
}
// add scripting hook for 'On Countermeasure Fire' --wookieejedi
OnCountermeasureFireHook->run(scripting::hook_param_list(scripting::hook_param("Ship", 'o', objp),
scripting::hook_param("CountermeasuresLeft", 'i', shipp->cmeasure_count),
scripting::hook_param("Countermeasure", 'o', &Objects[cobjnum])));
}
return (cobjnum >= 0); // return 0 if not fired, 1 otherwise
}
/**
* See if enough time has elapsed to play fail sound again
*/
void ship_maybe_do_primary_fail_sound_hud(bool depleted_energy)
{
ship_weapon *swp = &Player_ship->weapons;
int stampval;
hud_start_flash_weapon(swp->current_primary_bank, depleted_energy);
if ( timestamp_elapsed(Laser_energy_out_snd_timer) )
{
// check timestamp according to ballistics
if (Weapon_info[swp->primary_bank_weapons[swp->current_primary_bank]].wi_flags[Weapon::Info_Flags::Ballistic])
{
stampval = 500;
}
else
{
stampval = 50;
}
Laser_energy_out_snd_timer = timestamp(stampval);
snd_play( gamesnd_get_game_sound(ship_get_sound(Player_obj, GameSounds::OUT_OF_WEAPON_ENERGY)));
}
}
/**
* See if enough time has elapsed to play secondary fail sound again
*/
static int ship_maybe_do_secondary_fail_sound_hud(weapon_info *wip, bool depleted_energy)
{
hud_start_flash_weapon(Player_ship->weapons.num_primary_banks + Player_ship->weapons.current_secondary_bank, depleted_energy);
if ( timestamp_elapsed(Missile_out_snd_timer) ) {
if ( wip->wi_flags[Weapon::Info_Flags::Swarm] ) {
Missile_out_snd_timer = timestamp(500);
} else {
Missile_out_snd_timer = timestamp(50);
}
snd_play( gamesnd_get_game_sound(ship_get_sound(Player_obj, GameSounds::OUT_OF_MISSLES)) );
return 1;
}
return 0;
}
/**
* See if weapon for ship can fire based on weapons subystem strength.
*
* @return 1 if weapon failed to fire, 0 if weapon can fire
*/
static int ship_weapon_maybe_fail(ship *sp)
{
int rval;
float weapons_subsys_str;
// If playing on lowest skill level, weapons will not fail due to subsystem damage
if ( Game_skill_level == 0 ){
return 0;
}
rval = 0;
weapons_subsys_str = ship_get_subsystem_strength( sp, SUBSYSTEM_WEAPONS );
if ( weapons_subsys_str < SUBSYS_WEAPONS_STR_FIRE_FAIL ) {
rval = 1;
}
else if ( weapons_subsys_str < SUBSYS_WEAPONS_STR_FIRE_OK ) {
// chance to fire depends on weapons subsystem strength
if ( (frand()-0.2f) > weapons_subsys_str )
rval = 1;
}
if (!rval) {
// is subsystem disrupted?
if ( ship_subsys_disrupted(sp, SUBSYSTEM_WEAPONS) ) {
rval=1;
}
}
return rval;
}
// create a moving tracer based upon a weapon which just fired
float t_rad = 0.5f;
float t_len = 10.0f;
float t_vel = 0.2f;
float t_min = 150.0f;
float t_max = 300.0f;
DCF(t_rad, "Sets weapon tracer radius")
{
if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) {
dc_printf("t_rad : %f\n", t_rad);
return;
}
dc_stuff_float(&t_rad);
}
DCF(t_len, "Sets weapon tracer length")
{
if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) {
dc_printf("t_len : %f\n", t_len);
return;
}
dc_stuff_float(&t_len);
}
DCF(t_vel, "Sets weapon tracer velocity")
{
if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) {
dc_printf("t_vel : %f\n", t_vel);
return;
}
dc_stuff_float(&t_vel);
}
/*
TODO: These two DCF's (and variables) are unused
DCF(t_min, "")
{
dc_stuff_float(&t_min);
}
DCF(t_max, "")
{
dc_stuff_float(&t_max);
}
*/
/**
* Stops a single primary bank
*/
static int ship_stop_fire_primary_bank(object * obj, int bank_to_stop)
{
ship *shipp;
ship_weapon *swp;
if(obj == NULL){
return 0;
}
if(obj->type != OBJ_SHIP){
return 0;
}
shipp = &Ships[obj->instance];
swp = &shipp->weapons;
if(shipp->primary_rotate_rate[bank_to_stop] > 0.0f)
shipp->primary_rotate_rate[bank_to_stop] -= Weapon_info[swp->primary_bank_weapons[bank_to_stop]].weapon_submodel_rotate_accell*flFrametime;
if(shipp->primary_rotate_rate[bank_to_stop] < 0.0f)
shipp->primary_rotate_rate[bank_to_stop] = 0.0f;
if(Ship_info[shipp->ship_info_index].draw_primary_models[bank_to_stop]){
shipp->primary_rotate_ang[bank_to_stop] += shipp->primary_rotate_rate[bank_to_stop]*flFrametime;
if(shipp->primary_rotate_ang[bank_to_stop] > PI2)
shipp->primary_rotate_ang[bank_to_stop] -= PI2;
if(shipp->primary_rotate_ang[bank_to_stop] < 0.0f)
shipp->primary_rotate_ang[bank_to_stop] += PI2;
}
if(shipp->was_firing_last_frame[bank_to_stop] == 0)
return 0;
shipp->was_firing_last_frame[bank_to_stop] = 0;
return 1;
}
/**
* Stuff to do when the ship has stoped fireing all primary weapons
*/
int ship_stop_fire_primary(object * obj)
{
int i, num_primary_banks = 0, bank_to_stop = 0;
ship *shipp;
ship_weapon *swp;
if(obj == NULL){
return 0;
}
if(obj->type != OBJ_SHIP){
return 0;
}
shipp = &Ships[obj->instance];
swp = &shipp->weapons;
bank_to_stop = swp->current_primary_bank;
if ( shipp->flags[Ship_Flags::Primary_linked] ) {
num_primary_banks = swp->num_primary_banks;
} else {
num_primary_banks = MIN(1, swp->num_primary_banks);
}
for ( i = 0; i < num_primary_banks; i++ ) {
// Goober5000 - allow more than two banks
bank_to_stop = (swp->current_primary_bank+i) % swp->num_primary_banks;
//only stop if it was fireing last frame
ship_stop_fire_primary_bank(obj, bank_to_stop);
}
for(i = 0; i<swp->num_primary_banks+num_primary_banks;i++)
ship_stop_fire_primary_bank(obj, i%swp->num_primary_banks);
return 1;
}
int tracers[MAX_SHIPS][4][4];
/**
* Checks whether a ship would use autoaim if it were to fire its primaries at
* the given object, taking lead into account.
*
* @param shipp the ship to check
* @param bank_to_fire the assumed primary bank
* @param obj the object to check; must be the ship's current target
*/
bool in_autoaim_fov(ship *shipp, int bank_to_fire, object *obj)
{
// Most of the code of this function has been duplicated from
// ship_fire_primary(), because it is not easy to encapsulate cleanly.
ship_info *sip;
ai_info *aip;
ship_weapon *swp;
bool has_autoaim, has_converging_autoaim;
float autoaim_fov = 0;
float dist_to_target = 0;
vec3d plr_to_target_vec;
vec3d player_forward_vec = Objects[shipp->objnum].orient.vec.fvec;
vec3d target_position;
sip = &Ship_info[shipp->ship_info_index];
aip = &Ai_info[shipp->ai_index];
swp = &shipp->weapons;
int weapon_idx = swp->primary_bank_weapons[bank_to_fire];
weapon_info* winfo_p = &Weapon_info[weapon_idx];
has_converging_autoaim = ((sip->aiming_flags[Ship::Aiming_Flags::Autoaim_convergence] || (The_mission.ai_profile->player_autoaim_fov[Game_skill_level] > 0.0f && !( Game_mode & GM_MULTIPLAYER ))) && aip->target_objnum != -1);
has_autoaim = ((has_converging_autoaim || (sip->aiming_flags[Ship::Aiming_Flags::Autoaim])) && aip->target_objnum != -1);
if (!has_autoaim) {
return false;
}
autoaim_fov = MAX(shipp->autoaim_fov, The_mission.ai_profile->player_autoaim_fov[Game_skill_level]);
if (aip->targeted_subsys != nullptr) {
get_subsystem_world_pos(&Objects[aip->target_objnum], aip->targeted_subsys, &target_position);
}
else {
target_position = obj->pos;
}
dist_to_target = vm_vec_dist_quick(&Objects[shipp->objnum].pos, &target_position);
hud_calculate_lead_pos(&plr_to_target_vec, &target_position, obj, winfo_p, dist_to_target);
vm_vec_sub2(&plr_to_target_vec, &Objects[shipp->objnum].pos);
float angle_to_target = vm_vec_delta_ang(&player_forward_vec, &plr_to_target_vec, nullptr);
if (angle_to_target <= autoaim_fov) {
return true;
}
else {
return false;
}
}
// fires a primary weapon for the given object. It also handles multiplayer cases.
// in multiplayer, the starting network signature, and number of banks fired are sent
// to all the clients in the game. All the info is passed to send_primary at the end of
// the function. The check_energy parameter (defaults to 1) tells us whether or not
// we should check the energy. It will be 0 when a multiplayer client is firing an AI
// primary.
int ship_fire_primary(object * obj, int force, bool rollback_shot)
{
vec3d gun_point, pnt, firing_pos, target_position, target_velocity_vec;
int n = obj->instance;
ship *shipp;
ship_weapon *swp;
ship_info *sip;
ai_info *aip;
int weapon_idx, i, j, w, v, weapon_objnum;
int bank_to_fire, num_fired = 0;
int banks_fired; // used for multiplayer to help determine whether or not to send packet
banks_fired = 0; // used in multiplayer -- bitfield of banks that were fired
bool has_fired = false; // used to determine whether we should fire the scripting hook
bool has_autoaim, has_converging_autoaim, needs_target_pos; // used to flag weapon/ship as having autoaim
float autoaim_fov = 0; // autoaim limit
float dist_to_target = 0; // distance to target, for autoaim & automatic convergence
gamesnd_id sound_played; // used to track what sound is played. If the player is firing two banks
// of the same laser, we only want to play one sound
Assert( obj != NULL );
if(obj == NULL){
return 0;
}
// in the case where the server is an observer, he can fire (which) would be bad - unless we do this.
if( obj->type == OBJ_OBSERVER){
return 0;
}
Assert( obj->type == OBJ_SHIP );
Assert( n >= 0 );
Assert( Ships[n].objnum == OBJ_INDEX(obj));
if((obj->type != OBJ_SHIP) || (n < 0) || (n >= MAX_SHIPS) || (Ships[n].objnum != OBJ_INDEX(obj))){
return 0;
}
shipp = &Ships[n];
swp = &shipp->weapons;
// bogus
if((shipp->ship_info_index < 0) || (shipp->ship_info_index >= ship_info_size())){
return 0;
}
if((shipp->ai_index < 0) || (shipp->ai_index >= MAX_AI_INFO)){
return 0;
}
sip = &Ship_info[shipp->ship_info_index];
aip = &Ai_info[shipp->ai_index];
if ( swp->num_primary_banks <= 0 ) {
return 0;
}
if ( swp->current_primary_bank < 0 ){
return 0;
}
// If the primaries have been locked, bail
if (shipp->flags[Ship_Flags::Primaries_locked])
{
return 0;
}
sound_played = gamesnd_id();
// Fire the correct primary bank. If primaries are linked (SF_PRIMARY_LINKED set), then fire
// both primary banks.
int num_primary_banks;
if ( shipp->flags[Ship_Flags::Primary_linked] ) {
num_primary_banks = swp->num_primary_banks;
} else {
num_primary_banks = MIN(1, swp->num_primary_banks);
}
Assert(num_primary_banks > 0);
if (num_primary_banks < 1){
return 0;
}
if(num_primary_banks == 1)
for(i = 0; i<swp->num_primary_banks; i++){
if(i!=swp->current_primary_bank)ship_stop_fire_primary_bank(obj, i);
}
// lets start gun convergence / autoaim code from here - Wanderer
has_converging_autoaim = ((sip->aiming_flags[Ship::Aiming_Flags::Autoaim_convergence] || (The_mission.ai_profile->player_autoaim_fov[Game_skill_level] > 0.0f && !( Game_mode & GM_MULTIPLAYER ))) && aip->target_objnum != -1);
has_autoaim = ((has_converging_autoaim || (sip->aiming_flags[Ship::Aiming_Flags::Autoaim])) && aip->target_objnum != -1);
needs_target_pos = ((has_autoaim || (sip->aiming_flags[Ship::Aiming_Flags::Auto_convergence])) && aip->target_objnum != -1);
if (needs_target_pos) {
if (has_autoaim) {
autoaim_fov = MAX(shipp->autoaim_fov, The_mission.ai_profile->player_autoaim_fov[Game_skill_level]);
}
// If a subsystem is targeted, fire in that direction instead
if (aip->targeted_subsys != NULL)
{
get_subsystem_world_pos(&Objects[aip->target_objnum], aip->targeted_subsys, &target_position);
}
else
{
target_position = Objects[aip->target_objnum].pos;
}
dist_to_target = vm_vec_dist_quick(&target_position, &obj->pos);
}
polymodel* pm = model_get(sip->model_num);
for ( i = 0; i < num_primary_banks; i++ ) {
// Goober5000 - allow more than two banks
bank_to_fire = (swp->current_primary_bank+i) % swp->num_primary_banks;
weapon_idx = swp->primary_bank_weapons[bank_to_fire];
// why would a ship try to fire a weapon that doesn't exist?
Assert( weapon_idx >= 0 && weapon_idx < weapon_info_size());
if ( (weapon_idx < 0) || (weapon_idx >= weapon_info_size()) ) {
continue;
}
if (swp->primary_animation_position[bank_to_fire] == MA_POS_SET) {
if ( timestamp_elapsed(swp->primary_animation_done_time[bank_to_fire]) )
swp->primary_animation_position[bank_to_fire] = MA_POS_READY;
else
continue;
}
weapon_info* winfo_p = &Weapon_info[weapon_idx];
if (needs_target_pos) {
target_velocity_vec = Objects[aip->target_objnum].phys_info.vel;
if (The_mission.ai_profile->flags[AI::Profile_Flags::Use_additive_weapon_velocity])
vm_vec_scale_sub2(&target_velocity_vec, &obj->phys_info.vel, winfo_p->vel_inherit_amount);
}
if (winfo_p->weapon_submodel_rotate_vel > 0.0f) {
if (shipp->primary_rotate_rate[bank_to_fire] < winfo_p->weapon_submodel_rotate_vel)
shipp->primary_rotate_rate[bank_to_fire] += winfo_p->weapon_submodel_rotate_accell*flFrametime;
if (shipp->primary_rotate_rate[bank_to_fire] > winfo_p->weapon_submodel_rotate_vel)
shipp->primary_rotate_rate[bank_to_fire] = winfo_p->weapon_submodel_rotate_vel;
if (sip->draw_primary_models[bank_to_fire]) {
shipp->primary_rotate_ang[bank_to_fire] += shipp->primary_rotate_rate[bank_to_fire]*flFrametime;
if (shipp->primary_rotate_ang[bank_to_fire] > PI2)
shipp->primary_rotate_ang[bank_to_fire] -= PI2;
if (shipp->primary_rotate_ang[bank_to_fire] < 0.0f)
shipp->primary_rotate_ang[bank_to_fire] += PI2;
}
if (shipp->primary_rotate_rate[bank_to_fire] < winfo_p->weapon_submodel_rotate_vel)
continue;
}
// if this is a targeting laser, start it up ///- only targeting laser if it is tag-c, otherwise it's a fighter beam -Bobboau
if((winfo_p->wi_flags[Weapon::Info_Flags::Beam]) && (winfo_p->tag_level == 3) && (shipp->flags[Ship_Flags::Trigger_down]) && (winfo_p->b_info.beam_type == BeamType::TARGETING) ){
ship_start_targeting_laser(shipp);
continue;
}
// Cyborg17 - In rollback mode we don't need to worry timestamps, because we are recreating an exact shot, anyway.
if (!rollback_shot) {
// only non-multiplayer clients (single, multi-host) need to do timestamp checking
if ( !timestamp_elapsed(swp->next_primary_fire_stamp[bank_to_fire]) ) {
continue;
}
}
// if weapons are linked and this is a nolink weapon, skip it
if (shipp->flags[Ship_Flags::Primary_linked] && winfo_p->wi_flags[Weapon::Info_Flags::Nolink]) {
continue;
}
int num_slots = pm->gun_banks[bank_to_fire].num_slots;
// do timestamp stuff for next firing time
float next_fire_delay;
bool fast_firing = false;
// reset the burst if applicable
if (winfo_p->burst_flags[Weapon::Burst_Flags::Resets]) {
// a bit of an oversimplification but the reset time doesnt have to be super accurate
int reset_time = (int)(swp->last_primary_fire_stamp[bank_to_fire] + (winfo_p->fire_wait * 1000.f));
if (timestamp_elapsed(reset_time)) {
swp->burst_counter[bank_to_fire] = 0;
}
}
int old_burst_counter = swp->burst_counter[bank_to_fire];
int old_burst_seed = swp->burst_seed[bank_to_fire];
// should subtract 1 from num_slots to match behavior of winfo_p->burst_shots
int burst_shots = (winfo_p->burst_flags[Weapon::Burst_Flags::Num_firepoints_burst_shots] ? num_slots - 1 : winfo_p->burst_shots);
if (burst_shots > swp->burst_counter[bank_to_fire]) {
next_fire_delay = winfo_p->burst_delay * 1000.0f;
swp->burst_counter[bank_to_fire]++;
if (winfo_p->burst_flags[Weapon::Burst_Flags::Fast_firing])
fast_firing = true;
} else {
if (winfo_p->max_delay != 0.0f && winfo_p->min_delay != 0.0f) // Random fire delay (DahBlount)
next_fire_delay = frand_range(winfo_p->min_delay, winfo_p->max_delay) * 1000.0f;
else
next_fire_delay = winfo_p->fire_wait * 1000.0f;
swp->burst_counter[bank_to_fire] = 0;
swp->burst_seed[bank_to_fire] = Random::next();
// only used by type 5 beams
if (swp->burst_counter[bank_to_fire] == 0) {
swp->per_burst_rot += winfo_p->b_info.t5info.per_burst_rot;
if (swp->per_burst_rot > PI2)
swp->per_burst_rot -= PI2;
else if (swp->per_burst_rot < -PI2)
swp->per_burst_rot += PI2;
}
}
//doing that time scale thing on enemy fighter is just ugly with beams, especaly ones that have careful timeing
if (!((obj->flags[Object::Object_Flags::Player_ship]) || fast_firing || winfo_p->wi_flags[Weapon::Info_Flags::Beam])) {
// When testing weapons fire in the lab, we do not have a player object available.
if ((Game_mode & GM_LAB) || shipp->team == Ships[Player_obj->instance].team){
next_fire_delay *= aip->ai_ship_fire_delay_scale_friendly;
} else {
next_fire_delay *= aip->ai_ship_fire_delay_scale_hostile;
}
}
// Goober5000 (thanks to _argv[-1] for the original idea)
if ( (num_primary_banks > 1) && !(winfo_p->wi_flags[Weapon::Info_Flags::No_linked_penalty]) && !(The_mission.ai_profile->flags[AI::Profile_Flags::Disable_linked_fire_penalty]) )
{
int effective_primary_banks = 0;
for (int it = 0; it < num_primary_banks; it++)
{
if ((it == bank_to_fire) || !(Weapon_info[swp->primary_bank_weapons[it]].wi_flags[Weapon::Info_Flags::Nolink, Weapon::Info_Flags::No_linked_penalty]))
effective_primary_banks++;
}
Assert(effective_primary_banks >= 1);
next_fire_delay *= 1.0f + (effective_primary_banks - 1) * 0.5f; // 50% time penalty if banks linked
}
if (winfo_p->fof_spread_rate > 0.0f)
{
//Adjust the primary_bank_fof_cooldown based on how long it's been since the last shot.
float reset_amount = (timestamp_until(swp->last_primary_fire_stamp[bank_to_fire]) / 1000.0f) * winfo_p->fof_reset_rate;
swp->primary_bank_fof_cooldown[bank_to_fire] += winfo_p->fof_spread_rate + reset_amount;
CLAMP(swp->primary_bank_fof_cooldown[bank_to_fire], 0.0f, 1.0f);
}
// MK, 2/4/98: Since you probably were allowed to fire earlier, but couldn't fire until your frame interval
// rolled around, subtract out up to half the previous frametime.
// Note, unless we track whether the fire button has been held down, and not tapped, it's hard to
// know how much time to subtract off. It could be this fire is "late" because the user didn't want to fire.
if ((next_fire_delay > 0.0f)) {
if (obj->flags[Object::Object_Flags::Player_ship]) {
int t = timestamp_until(swp->next_primary_fire_stamp[bank_to_fire]);
if (t < 0) {
float tx;
tx = (float) t/-1000.0f;
if (tx > flFrametime/2.0f){
tx = 1000.0f * flFrametime * 0.7f;
}
next_fire_delay -= tx;
}
if ((int) next_fire_delay < 1){
next_fire_delay = 1.0f;
}
}
swp->next_primary_fire_stamp[bank_to_fire] = timestamp((int)(next_fire_delay));
swp->last_primary_fire_stamp[bank_to_fire] = timestamp();
}
if (sip->flags[Ship::Info_Flags::Dyn_primary_linking] ) {
Assert(pm->gun_banks[bank_to_fire].num_slots != 0);
swp->next_primary_fire_stamp[bank_to_fire] = timestamp((int)(next_fire_delay * ( swp->primary_bank_slot_count[ bank_to_fire ] ) / pm->gun_banks[bank_to_fire].num_slots ) );
swp->last_primary_fire_stamp[bank_to_fire] = timestamp();
} else if (winfo_p->wi_flags[Weapon::Info_Flags::Cycle]) {
Assert(pm->gun_banks[bank_to_fire].num_slots != 0);
swp->next_primary_fire_stamp[bank_to_fire] = timestamp((int)(next_fire_delay / pm->gun_banks[bank_to_fire].num_slots));
swp->last_primary_fire_stamp[bank_to_fire] = timestamp();
//to maintain balance of fighters with more fire points they will fire faster than ships with fewer points
}else{
swp->next_primary_fire_stamp[bank_to_fire] = timestamp((int)(next_fire_delay));
swp->last_primary_fire_stamp[bank_to_fire] = timestamp();
}
// Here is where we check if weapons subsystem is capable of firing the weapon.
// Note that we can have partial bank firing, if the weapons subsystem is partially
// functional, which should be cool.
if ( ship_weapon_maybe_fail(shipp) && !force) {
if ( obj == Player_obj ) {
ship_maybe_do_primary_fail_sound_hud(false);
}
ship_stop_fire_primary_bank(obj, bank_to_fire);
continue;
}
if ( pm->n_guns > 0 ) {
vec3d predicted_target_pos, plr_to_target_vec;
vec3d player_forward_vec = obj->orient.vec.fvec;
bool in_automatic_aim_fov = false;
float dist_to_aim = 0;
// more autoaim stuff here - Wanderer
// needs weapon speed
if (needs_target_pos) {
float time_to_target, angle_to_target;
vec3d last_delta_vec;
time_to_target = 0.0f;
if (winfo_p->max_speed != 0)
{
time_to_target = dist_to_target / winfo_p->max_speed;
}
// This is to circumvent a nine-year-old bug that prevents autoaim from working in multi. In most cases polish_predicted_target_pos needs
// the Player object, but in multi it needs the shooting object. Also, additional conditions would need to be added in the future if you
// want to apply autoaim to AI, for whatever reason.
object* object_to_send = (obj->flags[Object::Object_Flags::Player_ship]) ? obj : Player_obj;
vm_vec_scale_add(&predicted_target_pos, &target_position, &target_velocity_vec, time_to_target);
polish_predicted_target_pos(winfo_p, &Objects[aip->target_objnum], &target_position, &predicted_target_pos, dist_to_target, &last_delta_vec, 1, object_to_send);
vm_vec_sub(&plr_to_target_vec, &predicted_target_pos, &obj->pos);
if (has_autoaim) {
angle_to_target = vm_vec_delta_ang(&player_forward_vec, &plr_to_target_vec, NULL);
if (angle_to_target < autoaim_fov)
in_automatic_aim_fov = true;
}
dist_to_aim = vm_vec_mag_quick(&plr_to_target_vec);
// minimum convergence distance
if (sip->minimum_convergence_distance > dist_to_aim) {
float dist_mult;
dist_mult = sip->minimum_convergence_distance / dist_to_aim;
vm_vec_scale_add(&predicted_target_pos, &obj->pos, &plr_to_target_vec, dist_mult);
dist_to_aim = sip->minimum_convergence_distance;
}
}
if(winfo_p->wi_flags[Weapon::Info_Flags::Beam]){ // the big change I made for fighter beams, if there beams fill out the Fire_Info for a targeting laser then fire it, for each point in the weapon bank -Bobboau
beam_fire_info fbfire_info;
int points;
if (winfo_p->b_info.beam_shots){
if (winfo_p->b_info.beam_shots > num_slots){
points = num_slots;
}else{
points = winfo_p->b_info.beam_shots;
}
} else if (winfo_p->wi_flags[Weapon::Info_Flags::Cycle]) {
points = 1;
} else {
points = num_slots;
}
bool no_energy = shipp->weapon_energy < points * winfo_p->energy_consumed * flFrametime;
if (no_energy || (winfo_p->wi_flags[Weapon::Info_Flags::Ballistic] && shipp->weapons.primary_bank_ammo[bank_to_fire] <= 0))
{
swp->next_primary_fire_stamp[bank_to_fire] = timestamp((int)(next_fire_delay));
if ( obj == Player_obj )
{
ship_maybe_do_primary_fail_sound_hud(no_energy);
}
ship_stop_fire_primary_bank(obj, bank_to_fire);
continue;
}
shipp->beam_sys_info.turret_norm.xyz.x = 0.0f;
shipp->beam_sys_info.turret_norm.xyz.y = 0.0f;
shipp->beam_sys_info.turret_norm.xyz.z = 1.0f;
shipp->beam_sys_info.model_num = sip->model_num;
shipp->beam_sys_info.turret_gun_sobj = pm->detail[0];
shipp->beam_sys_info.turret_num_firing_points = 1; // dummy turret info is used per firepoint
shipp->beam_sys_info.turret_fov = cosf((winfo_p->field_of_fire != 0.0f)?winfo_p->field_of_fire:180);
shipp->fighter_beam_turret_data.disruption_timestamp = timestamp(0);
shipp->fighter_beam_turret_data.turret_next_fire_pos = 0;
shipp->fighter_beam_turret_data.current_hits = 1.0;
shipp->fighter_beam_turret_data.system_info = &shipp->beam_sys_info;
fbfire_info.target_subsys = Ai_info[shipp->ai_index].targeted_subsys;
fbfire_info.beam_info_index = shipp->weapons.primary_bank_weapons[bank_to_fire];
fbfire_info.beam_info_override = NULL;
fbfire_info.shooter = &Objects[shipp->objnum];
if (aip->target_objnum >= 0) {
fbfire_info.target = &Objects[aip->target_objnum];
} else {
fbfire_info.target = NULL;
}
fbfire_info.turret = &shipp->fighter_beam_turret_data;
fbfire_info.bfi_flags = BFIF_IS_FIGHTER_BEAM;
fbfire_info.bank = bank_to_fire;
fbfire_info.burst_index = old_burst_counter;
fbfire_info.burst_seed = old_burst_seed;
fbfire_info.per_burst_rotation = swp->per_burst_rot;
for ( v = 0; v < points; v++ ){
if(winfo_p->b_info.beam_shots || winfo_p->wi_flags[Weapon::Info_Flags::Cycle]){
j = (shipp->last_fired_point[bank_to_fire]+1)%num_slots;
shipp->last_fired_point[bank_to_fire] = j;
}else{
j=v;
}
fbfire_info.local_fire_postion = pm->gun_banks[bank_to_fire].pnt[j];
shipp->beam_sys_info.pnt = pm->gun_banks[bank_to_fire].pnt[j];
shipp->beam_sys_info.turret_firing_point[0] = pm->gun_banks[bank_to_fire].pnt[j];
fbfire_info.point = j;
fbfire_info.fire_method = BFM_FIGHTER_FIRED;
beam_fire(&fbfire_info);
has_fired = true;
num_fired++;
}
}
else //if this isn't a fighter beam, do it normally -Bobboau
{
int points = 0, numtimes = 1;
// ok if this is a cycling weapon use shots as the number of points to fire from at a time
// otherwise shots is the number of times all points will be fired (used mostly for the 'shotgun' effect)
if ( sip->flags[Ship::Info_Flags::Dyn_primary_linking] ) {
numtimes = 1;
points = MIN( num_slots, swp->primary_bank_slot_count[ bank_to_fire ] );
} else if ( winfo_p->wi_flags[Weapon::Info_Flags::Cycle] ) {
numtimes = 1;
points = MIN(num_slots, winfo_p->shots);
} else {
numtimes = winfo_p->shots;
points = num_slots;
}
// The energy-consumption code executes even for ballistic primaries, because
// there may be a reason why you want to have ballistics consume energy. Perhaps
// you can't fire too many too quickly or they'll overheat. If not, just set
// the weapon's energy_consumed to 0 and it'll work just fine. - Goober5000
// fail unless we're forcing (energy based primaries)
bool no_energy = shipp->weapon_energy < points * numtimes * winfo_p->energy_consumed; //was num_slots
if ( no_energy && !force ) {
swp->next_primary_fire_stamp[bank_to_fire] = timestamp((int)(next_fire_delay));
if ( obj == Player_obj )
{
ship_maybe_do_primary_fail_sound_hud(no_energy);
}
ship_stop_fire_primary_bank(obj, bank_to_fire);
continue;
}
// moved the above to here to use points instead of num_slots for energy consumption check
// ballistics support for primaries - Goober5000
if ( winfo_p->wi_flags[Weapon::Info_Flags::Ballistic] )
{
// If ship is being repaired/rearmed, it cannot fire ballistics
if ( aip->ai_flags[AI::AI_Flags::Being_repaired] )
{
continue;
}
// duplicated from the secondaries firing routine...
// determine if there is enough ammo left to fire weapons on this bank. As with primary
// weapons, we might or might not check ammo counts depending on game mode, who is firing,
// and if I am a client in multiplayer
int check_ammo = 1;
if ( MULTIPLAYER_CLIENT && (obj != Player_obj) )
{
check_ammo = 0;
}
// not enough ammo
if ( check_ammo && ( swp->primary_bank_ammo[bank_to_fire] <= 0) )
{
if ( obj == Player_obj )
{
ship_maybe_do_primary_fail_sound_hud(false);
}
else
{
// TODO: AI switch primary weapon / re-arm?
}
continue;
}
// deplete ammo
if ( !Weapon_energy_cheat )
{
swp->primary_bank_ammo[bank_to_fire] -= points*numtimes;
// make sure we don't go below zero; any such error is excusable
// because it only happens when the bank is depleted in one shot
if (swp->primary_bank_ammo[bank_to_fire] < 0)
{
swp->primary_bank_ammo[bank_to_fire] = 0;
}
}
}
// now handle the energy as usual
// deplete the weapon reserve energy by the amount of energy used to fire the weapon
// Only subtract the energy amount required for equipment operation once
shipp->weapon_energy -= points*numtimes * winfo_p->energy_consumed;
// note for later: option for fuel!
// Mark all these weapons as in the same group
int new_group_id = weapon_create_group_id();
vec3d total_impulse;
vec3d *firepoint_list;
size_t current_firepoint = 0;
if (winfo_p->wi_flags[Weapon::Info_Flags::Apply_Recoil]){
firepoint_list = new vec3d[numtimes * points];
vm_vec_zero(&total_impulse);
} else {
firepoint_list = nullptr;
}
for ( w = 0; w < numtimes; w++ ) {
polymodel *weapon_model = NULL;
if(winfo_p->external_model_num >= 0)
weapon_model = model_get(winfo_p->external_model_num);
if (weapon_model)
if ((weapon_model->n_guns <= swp->external_model_fp_counter[bank_to_fire]) || (swp->external_model_fp_counter[bank_to_fire] < 0))
swp->external_model_fp_counter[bank_to_fire] = 0;
for ( j = 0; j < points; j++ ) {
int pt; //point
if ( (winfo_p->wi_flags[Weapon::Info_Flags::Cycle]) || (sip->flags[Ship::Info_Flags::Dyn_primary_linking]) ){
pt = (shipp->last_fired_point[bank_to_fire]+1)%num_slots;
}else{
pt = j;
}
int sub_shots = 1;
// Use 0 instead of bank_to_fire as index when checking the number of external weapon model firingpoints
if (weapon_model && weapon_model->n_guns)
if (!(winfo_p->wi_flags[Weapon::Info_Flags::External_weapon_fp]))
sub_shots = weapon_model->gun_banks[0].num_slots;
for(int s = 0; s<sub_shots; s++){
pnt = pm->gun_banks[bank_to_fire].pnt[pt];
// Use 0 instead of bank_to_fire as index to external weapon model firingpoints
if (weapon_model && weapon_model->n_guns) {
if (winfo_p->wi_flags[Weapon::Info_Flags::External_weapon_fp]) {
vm_vec_add2(&pnt, &weapon_model->gun_banks[0].pnt[swp->external_model_fp_counter[bank_to_fire]]);
} else {
vm_vec_add2(&pnt, &weapon_model->gun_banks[0].pnt[s]);
}
}
vm_vec_unrotate(&gun_point, &pnt, &obj->orient);
vm_vec_add(&firing_pos, &gun_point, &obj->pos);
matrix firing_orient;
/* I AIM autoaim convergence
II AIM autoaim
III AIM auto convergence
IV AIM std convergence
V SIF convergence
no convergence or autoaim
*/
if (has_autoaim && in_automatic_aim_fov) {
vec3d firing_vec;
if (has_converging_autoaim) {
// converging autoaim
vm_vec_sub(&firing_vec, &predicted_target_pos, &firing_pos);
} else {
// autoaim
vm_vec_sub(&firing_vec, &predicted_target_pos, &obj->pos);
}
vm_vector_2_matrix(&firing_orient, &firing_vec, NULL, NULL);
} else if ((sip->aiming_flags[Ship::Aiming_Flags::Std_convergence]) || ((sip->aiming_flags[Ship::Aiming_Flags::Auto_convergence]) && (aip->target_objnum != -1))) {
// std & auto convergence
vec3d target_vec, firing_vec, convergence_offset;
// make sure vector is of the set length
vm_vec_copy_normalize(&target_vec, &player_forward_vec);
if ((sip->aiming_flags[Ship::Aiming_Flags::Auto_convergence]) && (aip->target_objnum != -1)) {
// auto convergence
vm_vec_scale(&target_vec, dist_to_aim);
} else {
// std convergence
vm_vec_scale(&target_vec, sip->convergence_distance);
}
// if there is convergence offset then make use of it)
if (sip->aiming_flags[Ship::Aiming_Flags::Convergence_offset]) {
vm_vec_unrotate(&convergence_offset, &sip->convergence_offset, &obj->orient);
vm_vec_add2(&target_vec, &convergence_offset);
}
vm_vec_add2(&target_vec, &obj->pos);
vm_vec_sub(&firing_vec, &target_vec, &firing_pos);
// set orientation
vm_vector_2_matrix(&firing_orient, &firing_vec, NULL, NULL);
} else if (sip->flags[Ship::Info_Flags::Gun_convergence]) {
// model file defined convergence
vec3d firing_vec;
vm_vec_unrotate(&firing_vec, &pm->gun_banks[bank_to_fire].norm[pt], &obj->orient);
vm_vector_2_matrix(&firing_orient, &firing_vec, NULL, NULL);
} else {
// no autoaim or convergence
firing_orient = obj->orient;
}
if (winfo_p->wi_flags[Weapon::Info_Flags::Apply_Recoil]){ // Function to add recoil functionality - DahBlount
vec3d local_impulse = firing_orient.vec.fvec;
float recoil_force = (winfo_p->mass * winfo_p->max_speed * winfo_p->recoil_modifier * sip->ship_recoil_modifier);
firepoint_list[current_firepoint++] = firing_pos;
vm_vec_scale(&local_impulse, (-1 * recoil_force));
vm_vec_add2(&total_impulse, &local_impulse);
}
// create the weapon -- the network signature for multiplayer is created inside
// of weapon_create
weapon_objnum = weapon_create( &firing_pos, &firing_orient, weapon_idx, OBJ_INDEX(obj), new_group_id,
0, 0, swp->primary_bank_fof_cooldown[bank_to_fire] );
if (weapon_objnum == -1) {
// Weapon most likely failed to fire
if (obj == Player_obj) {
ship_maybe_do_primary_fail_sound_hud(false);
}
continue;
}
winfo_p = &Weapon_info[Weapons[Objects[weapon_objnum].instance].weapon_info_index];
has_fired = true;
weapon_set_tracking_info(weapon_objnum, OBJ_INDEX(obj), aip->target_objnum, aip->current_target_is_locked, aip->targeted_subsys);
if (winfo_p->wi_flags[Weapon::Info_Flags::Flak])
{
object *target;
vec3d predicted_pos;
float flak_range=(winfo_p->lifetime)*(winfo_p->max_speed);
float range_to_target = flak_range;
float wepstr=ship_get_subsystem_strength(shipp, SUBSYSTEM_WEAPONS);
if (aip->target_objnum != -1) {
target = &Objects[aip->target_objnum];
} else {
target = NULL;
}
if (target != NULL) {
set_predicted_enemy_pos(&predicted_pos, obj, &target->pos, &target->phys_info.vel, aip);
range_to_target=vm_vec_dist(&predicted_pos, &obj->pos);
}
//if we have a target and its in range
if ( (target != NULL) && (range_to_target < flak_range) )
{
//set flak range to range of ship
flak_pick_range(&Objects[weapon_objnum], &firing_pos, &predicted_pos,wepstr);
}
else
{
flak_set_range(&Objects[weapon_objnum], flak_range - winfo_p->untargeted_flak_range_penalty);
}
}
// create the muzzle flash effect
shipfx_flash_create( obj, sip->model_num, &pnt, &obj->orient.vec.fvec, 1, weapon_idx );
// maybe shudder the ship - if its me
if((winfo_p->wi_flags[Weapon::Info_Flags::Shudder]) && (obj == Player_obj) && !(Game_mode & GM_STANDALONE_SERVER)){
// calculate some arbitrary value between 100
// (mass * velocity) / 10
game_shudder_apply(500, (winfo_p->mass * winfo_p->max_speed) * 0.1f);
}
num_fired++;
shipp->last_fired_point[bank_to_fire] = (shipp->last_fired_point[bank_to_fire] + 1) % num_slots;
// maybe add this weapon to the list of those we need to roll forward
if ((Game_mode & (GM_MULTIPLAYER | GM_STANDALONE_SERVER )) && rollback_shot) {
multi_ship_record_add_rollback_wep(weapon_objnum);
}
}
}
swp->external_model_fp_counter[bank_to_fire]++;
}
if (winfo_p->wi_flags[Weapon::Info_Flags::Apply_Recoil]){
vec3d avg_firepoint;
vm_vec_avg_n(&avg_firepoint, (int)current_firepoint, firepoint_list);
ship_apply_whack(&total_impulse, &avg_firepoint, obj);
delete[] firepoint_list;
}
}
CLAMP(shipp->weapon_energy, 0.0f, sip->max_weapon_reserve);
banks_fired |= (1<<bank_to_fire); // mark this bank as fired.
}
// Only play the weapon fired sound if it hasn't been played yet. This is to
// avoid playing the same sound multiple times when banks are linked with the
// same weapon.
if (!(winfo_p->wi_flags[Weapon::Info_Flags::Beam])){ // not a beam weapon?
if ( sound_played != winfo_p->launch_snd ) {
sound_played = winfo_p->launch_snd;
if ( obj == Player_obj ) {
if ( winfo_p->launch_snd.isValid() ) {
weapon_info *wip;
ship_weapon *sw_pl;
//Update the last timestamp until continous fire is over, so we have the timestamp of the cease-fire.
if (shipp->was_firing_last_frame[bank_to_fire] == 1) {
swp->last_primary_fire_sound_stamp[bank_to_fire] = timestamp();
}
//Check for pre-launch sound and play if relevant
if( (winfo_p->pre_launch_snd.isValid()) //If this weapon type has a pre-fire sound
&& ((timestamp() - swp->last_primary_fire_sound_stamp[bank_to_fire]) >= winfo_p->pre_launch_snd_min_interval) //and if we're past our minimum delay from the last cease-fire
&& (shipp->was_firing_last_frame[bank_to_fire] == 0) //and if we are at the beginning of a firing stream
){
snd_play( gamesnd_get_game_sound(winfo_p->pre_launch_snd), 0.0f, 1.0f, SND_PRIORITY_MUST_PLAY); //play it
} else { //Otherwise, play normal firing sounds
// HACK
if(winfo_p->launch_snd == gamesnd_id(GameSounds::AUTOCANNON_SHOT)){
snd_play( gamesnd_get_game_sound(winfo_p->launch_snd), 0.0f, 1.0f, SND_PRIORITY_TRIPLE_INSTANCE );
} else {
snd_play( gamesnd_get_game_sound(winfo_p->launch_snd), 0.0f, 1.0f, SND_PRIORITY_MUST_PLAY );
}
}
sw_pl = &Player_ship->weapons;
if (sw_pl->current_primary_bank >= 0)
{
wip = &Weapon_info[sw_pl->primary_bank_weapons[sw_pl->current_primary_bank]];
int force_level = (int) ((wip->armor_factor + wip->shield_factor * 0.2f) * (wip->damage * wip->damage - 7.5f) * 0.45f + 0.6f) * 10 + 2000;
// modify force feedback for ballistics: make it stronger
if (wip->wi_flags[Weapon::Info_Flags::Ballistic])
joy_ff_play_primary_shoot(force_level * 2);
// no ballistics
else
joy_ff_play_primary_shoot(force_level);
}
}
}else {
if ( winfo_p->launch_snd.isValid() ) {
snd_play_3d( gamesnd_get_game_sound(winfo_p->launch_snd), &obj->pos, &View_position );
}
}
}
}
shipp->was_firing_last_frame[bank_to_fire] = 1;
} // end for (go to next primary bank)
// if multiplayer and we're client-side firing, send the packet
if(Game_mode & GM_MULTIPLAYER){
// if I'm a Host send a primary fired packet packet if it's brand new
if(MULTIPLAYER_MASTER && !rollback_shot) {
send_NEW_primary_fired_packet(shipp, banks_fired);
// or if I'm a client, and it is my ship send it for rollback on the server.
} else if (MULTIPLAYER_CLIENT && (shipp == Player_ship)) {
send_non_homing_fired_packet(shipp, banks_fired);
}
}
// STATS
if (obj->flags[Object::Object_Flags::Player_ship]) {
// in multiplayer -- only the server needs to keep track of the stats. Call the cool
// function to find the player given the object *. It had better return a valid player
// or our internal structure as messed up.
if( Game_mode & GM_MULTIPLAYER ) {
if ( Net_player->flags & NETINFO_FLAG_AM_MASTER ) {
int player_num;
player_num = multi_find_player_by_object ( obj );
Assert ( player_num != -1 );
Net_players[player_num].m_player->stats.mp_shots_fired += num_fired;
}
} else {
Player->stats.mp_shots_fired += num_fired;
}
}
if (has_fired) {
object *objp = &Objects[shipp->objnum];
object* target;
if (Ai_info[shipp->ai_index].target_objnum != -1)
target = &Objects[Ai_info[shipp->ai_index].target_objnum];
else
target = NULL;
if (objp == Player_obj && Player_ai->target_objnum != -1)
target = &Objects[Player_ai->target_objnum];
for (int bank = 0; bank < MAX_SHIP_PRIMARY_BANKS; bank++) {
if(banks_fired & (1 << bank))
//Start Animation in Forced mode: Always restart it from its initial position rather than just flip it to FWD motion if it was still moving. This is to make it work best for uses like recoil.
sip->animations.startAll(model_get_instance(shipp->model_instance_num), animation::ModelAnimationTriggerType::PrimaryFired, animation::ModelAnimationDirection::FWD, true, false, false, bank);
}
if (Script_system.IsActiveAction(CHA_ONWPFIRED) || Script_system.IsActiveAction(CHA_PRIMARYFIRE)) {
Script_system.SetHookObjects(2, "User", objp, "Target", target);
Script_system.RunCondition(CHA_ONWPFIRED, objp, 1);
Script_system.RunCondition(CHA_PRIMARYFIRE, objp);
Script_system.RemHookVars({"User", "Target"});
}
}
return num_fired;
}
static void ship_start_targeting_laser(ship *shipp)
{
int bank0_laser = 0;
int bank1_laser = 0;
// determine if either of our banks have a targeting laser
if((shipp->weapons.primary_bank_weapons[0] >= 0) && (Weapon_info[shipp->weapons.primary_bank_weapons[0]].wi_flags[Weapon::Info_Flags::Beam]) && (Weapon_info[shipp->weapons.primary_bank_weapons[0]].b_info.beam_type == BeamType::TARGETING)){
bank0_laser = 1;
}
if((shipp->weapons.primary_bank_weapons[1] >= 0) && (Weapon_info[shipp->weapons.primary_bank_weapons[1]].wi_flags[Weapon::Info_Flags::Beam]) && (Weapon_info[shipp->weapons.primary_bank_weapons[1]].b_info.beam_type == BeamType::TARGETING)){
bank1_laser = 1;
}
// if primary banks are linked
if(shipp->flags[Ship_Flags::Primary_linked]){
if(bank0_laser){
shipp->targeting_laser_bank = 0;
return;
}
if(bank1_laser){
shipp->targeting_laser_bank = 1;
return;
}
}
// if we only have 1 bank selected
else {
if(bank0_laser && (shipp->weapons.current_primary_bank == 0)){
shipp->targeting_laser_bank = 0;
return;
}
if(bank1_laser && (shipp->weapons.current_primary_bank == 1)){
shipp->targeting_laser_bank = 1;
return;
}
}
}
static void ship_stop_targeting_laser(ship *shipp)
{
shipp->targeting_laser_bank = -1;
shipp->targeting_laser_objnum = -1; // erase old laser obj num if it has any -Bobboau
}
void ship_process_targeting_lasers()
{
fighter_beam_fire_info fire_info;
ship_obj *so;
ship *shipp;
polymodel *m;
// interate over all ships
for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) {
// sanity checks
if(so->objnum < 0){
continue;
}
if(Objects[so->objnum].type != OBJ_SHIP){
continue;
}
if(Objects[so->objnum].instance < 0){
continue;
}
shipp = &Ships[Objects[so->objnum].instance];
// if our trigger is no longer down, switch it off
if(!(shipp->flags[Ship_Flags::Trigger_down])){
ship_stop_targeting_laser(shipp);
continue;
}
// if we have a bank to fire - fire it
if((shipp->targeting_laser_bank >= 0) && (shipp->targeting_laser_bank < 2)){
// try and get the model
m = model_get(Ship_info[shipp->ship_info_index].model_num);
if(m == NULL){
continue;
}
// fire a targeting laser
fire_info.life_left = 0.0; //for fighter beams
fire_info.life_total = 0.0f; //for fighter beams
fire_info.warmdown_stamp = -1; //for fighter beams
fire_info.warmup_stamp = -1; //for fighter beams
fire_info.accuracy = 0.0f;
fire_info.beam_info_index = shipp->weapons.primary_bank_weapons[(int)shipp->targeting_laser_bank];
fire_info.beam_info_override = NULL;
fire_info.shooter = &Objects[shipp->objnum];
fire_info.target = NULL;
fire_info.target_subsys = NULL;
fire_info.turret = NULL;
fire_info.local_fire_postion = m->gun_banks[shipp->targeting_laser_bank].pnt[0];
shipp->targeting_laser_objnum = beam_fire_targeting(&fire_info);
// hmm, why didn't it fire?
if(shipp->targeting_laser_objnum < 0){
Int3();
ship_stop_targeting_laser(shipp);
}
}
}}
/**
* Maybe detonate secondary weapon that's already out.
* @return Return true if we detonate it, false if not.
*/
static bool ship_fire_secondary_detonate(object *obj, ship_weapon *swp)
{
if (swp->remote_detonaters_active > 0) {
if (timestamp_elapsed(swp->detonate_weapon_time)) {
missile_obj *mo;
// check for currently locked missiles (highest precedence)
for ( mo = GET_FIRST(&Missile_obj_list); mo != END_OF_LIST(&Missile_obj_list); mo = GET_NEXT(mo) ) {
object *mobjp;
Assert(mo->objnum >= 0 && mo->objnum < MAX_OBJECTS);
mobjp = &Objects[mo->objnum];
if (mobjp->parent_sig == obj->parent_sig && Weapon_info[Weapons[mobjp->instance].weapon_info_index].wi_flags[Weapon::Info_Flags::Remote]) {
// dont detonate if this guy just spawned i.e. we just spawned him in a previous iteration of this loop
if (Missiontime - Weapons[mobjp->instance].creation_time > fl2f(0.01)) {
weapon_detonate(mobjp);
}
}
}
return true;
}
}
return false;
}
extern void ai_maybe_announce_shockwave_weapon(object *firing_objp, int weapon_index);
// Object *obj fires its secondary weapon, if it can.
// If its most recently fired weapon is a remotely detonatable weapon, detonate it.
// Returns number of weapons fired. Note, for swarmers, returns 1 if it is allowed
// to fire the missiles when allow_swarm is NOT set. They don't actually get fired on a call here unless allow_swarm is set.
// When you want to fire swarmers, you call this function with allow_swarm NOT set and frame interval
// code comes aruond and fires it.
// allow_swarm -> default value is 0... since swarm missiles are fired over several frames,
// need to avoid firing when normally called
int ship_fire_secondary( object *obj, int allow_swarm, bool rollback_shot )
{
int n, weapon_idx, j, bank, bank_adjusted, starting_bank_count = -1, num_fired;
ushort starting_sig = 0;
ship *shipp;
ship_weapon *swp;
ship_info *sip;
weapon_info *wip;
ai_info *aip;
polymodel *pm;
vec3d missile_point, pnt, firing_pos;
bool has_fired = false; // Used to determine whether to fire the scripting hook
Assert( obj != NULL );
// in the case where the server is an observer, he can fire (which would be bad) - unless we do this.
if( obj->type == OBJ_OBSERVER ){
return 0;
}
// in the case where the object is a ghost (a delayed fire packet from right before he died, for instance)
if( (obj->type == OBJ_GHOST) || (obj->type == OBJ_NONE) ){
return 0;
}
Assert( obj->type == OBJ_SHIP );
if(obj->type != OBJ_SHIP){
return 0;
}
n = obj->instance;
Assert( n >= 0 && n < MAX_SHIPS );
if((n < 0) || (n >= MAX_SHIPS)){
return 0;
}
Assert( Ships[n].objnum == OBJ_INDEX(obj));
if(Ships[n].objnum != OBJ_INDEX(obj)){
return 0;
}
shipp = &Ships[n];
swp = &shipp->weapons;
sip = &Ship_info[shipp->ship_info_index];
aip = &Ai_info[shipp->ai_index];
// if no secondary weapons are present on ship, return
if ( swp->num_secondary_banks <= 0 ){
return 0;
}
// If the secondaries have been locked, bail
if (shipp->flags[Ship_Flags::Secondaries_locked])
{
return 0;
}
// If ship is being repaired/rearmed, it cannot fire missiles
if ( aip->ai_flags[AI::AI_Flags::Being_repaired] ) {
return 0;
}
num_fired = 0; // tracks how many missiles actually fired
// niffiwan: allow swarm/corkscrew bank to keep firing if current bank changes
if (shipp->swarm_missile_bank != -1 && allow_swarm) {
bank = shipp->swarm_missile_bank;
} else if (shipp->corkscrew_missile_bank != -1 && allow_swarm) {
bank = shipp->corkscrew_missile_bank;
} else {
bank = swp->current_secondary_bank;
}
if ( bank < 0 || bank >= sip->num_secondary_banks ) {
return 0;
}
bank_adjusted = MAX_SHIP_PRIMARY_BANKS + bank;
if (swp->secondary_animation_position[bank] == MA_POS_SET) {
if ( timestamp_elapsed(swp->secondary_animation_done_time[bank]) )
swp->secondary_animation_position[bank] = MA_POS_READY;
else
return 0;
}
weapon_idx = swp->secondary_bank_weapons[bank];
// It's possible for banks to be empty without issue
// but indices outside the weapon_info range are a problem
Assert(swp->secondary_bank_weapons[bank] < weapon_info_size());
if((swp->secondary_bank_weapons[bank] < 0) || (swp->secondary_bank_weapons[bank] >= weapon_info_size())){
return 0;
}
wip = &Weapon_info[weapon_idx];
if ( MULTIPLAYER_MASTER ) {
starting_sig = multi_get_next_network_signature( MULTI_SIG_NON_PERMANENT );
starting_bank_count = swp->secondary_bank_ammo[bank];
}
if (ship_fire_secondary_detonate(obj, swp)) {
// in multiplayer, master sends a secondary fired packet with starting signature of -1 -- indicates
// to client code to set the detonate timer to 0.
if ( MULTIPLAYER_MASTER ) {
send_secondary_fired_packet( shipp, 0, starting_bank_count, 1, allow_swarm );
}
// For all banks, if ok to fire a weapon, make it wait a bit.
// Solves problem of fire button likely being down next frame and
// firing weapon despite fire causing detonation of existing weapon.
if (swp->current_secondary_bank >= 0) {
if (timestamp_elapsed(swp->next_secondary_fire_stamp[bank])){
swp->next_secondary_fire_stamp[bank] = timestamp(MAX((int) flFrametime*3000, 250));
}
}
return 0;
}
// niffiwan: 04/03/12: duplicate of a check approx 100 lines above - not needed?
if ( bank < 0 ){
return 0;
}
if ( !timestamp_elapsed(swp->next_secondary_fire_stamp[bank]) && !allow_swarm) {
if (timestamp_until(swp->next_secondary_fire_stamp[bank]) > 60000){
swp->next_secondary_fire_stamp[bank] = timestamp(1000);
}
goto done_secondary;
}
// Ensure if this is a "require-lock" missile, that a lock actually exists
if ( wip->wi_flags[Weapon::Info_Flags::No_dumbfire] ) {
if (!aip->current_target_is_locked && !ship_lock_present(shipp) && shipp->missile_locks_firing.empty() && aip->ai_missile_locks_firing.empty()) {
if (obj == Player_obj) {
if (!Weapon_energy_cheat) {
float max_dist;
max_dist = wip->lifetime * wip->max_speed;
if (wip->wi_flags[Weapon::Info_Flags::Local_ssm]) {
max_dist = wip->lssm_lock_range;
}
if ((aip->target_objnum != -1) &&
(vm_vec_dist_quick(&obj->pos, &Objects[aip->target_objnum].pos) > max_dist)) {
HUD_sourced_printf(HUD_SOURCE_HIDDEN, "%s", XSTR("Too far from target to acquire lock", 487));
} else {
HUD_sourced_printf(HUD_SOURCE_HIDDEN, XSTR("Cannot fire %s without a lock", 488), wip->get_display_name());
}
snd_play(gamesnd_get_game_sound(ship_get_sound(Player_obj, GameSounds::OUT_OF_MISSLES)));
swp->next_secondary_fire_stamp[bank] = timestamp(800); // to avoid repeating messages
return 0;
}
} else {
// multiplayer clients should always fire the weapon here, so return only if not
// a multiplayer client.
if (!MULTIPLAYER_CLIENT) {
return 0;
}
}
}
}
if (wip->wi_flags[Weapon::Info_Flags::Tagged_only])
{
if (!ship_is_tagged(&Objects[aip->target_objnum]))
{
if (obj==Player_obj || (MULTIPLAYER_MASTER && obj->flags[Object::Object_Flags::Player_ship]))
{
if ( !Weapon_energy_cheat )
{
HUD_sourced_printf(HUD_SOURCE_HIDDEN, NOX("Cannot fire %s if target is not tagged"), wip->get_display_name());
snd_play( gamesnd_get_game_sound(ship_get_sound(Player_obj, GameSounds::OUT_OF_MISSLES)) );
swp->next_secondary_fire_stamp[bank] = timestamp(800); // to avoid repeating messages
return 0;
}
}
else
{
if ( !MULTIPLAYER_CLIENT )
{
return 0;
}
}
}
}
if ( !allow_swarm && (obj == Player_obj || (MULTIPLAYER_MASTER && obj->flags[Object::Object_Flags::Player_ship] ))) {
ship_queue_missile_locks(shipp);
}
// if trying to fire a swarm missile, make sure being called from right place
if ( (wip->wi_flags[Weapon::Info_Flags::Swarm]) && !allow_swarm ) {
Assert(wip->swarm_count > 0);
if (wip->multi_lock) {
if(obj == Player_obj || (MULTIPLAYER_MASTER && obj->flags[Object::Object_Flags::Player_ship]))
shipp->num_swarm_missiles_to_fire = (int)shipp->missile_locks_firing.size();
else // AI ships
shipp->num_swarm_missiles_to_fire = (int)aip->ai_missile_locks_firing.size();
} else if(wip->swarm_count <= 0){
shipp->num_swarm_missiles_to_fire = SWARM_DEFAULT_NUM_MISSILES_FIRED;
} else {
shipp->num_swarm_missiles_to_fire = wip->swarm_count;
}
shipp->swarm_missile_bank = bank;
return 1; // Note: Missiles didn't get fired, but the frame interval code will fire them.
}
// if trying to fire a corkscrew missile, make sure being called from right place
if ( (wip->wi_flags[Weapon::Info_Flags::Corkscrew]) && !allow_swarm ) {
//phreak 11-9-02
//changed this from 4 to custom number defined in tables
if (wip->multi_lock) {
if (obj == Player_obj || (MULTIPLAYER_MASTER && obj->flags[Object::Object_Flags::Player_ship]))
shipp->num_corkscrew_to_fire = (ubyte)shipp->missile_locks_firing.size();
else // AI ships
shipp->num_corkscrew_to_fire = (ubyte)aip->ai_missile_locks_firing.size();
} else {
shipp->num_corkscrew_to_fire = (ubyte)(shipp->num_corkscrew_to_fire + (ubyte)wip->cs_num_fired);
}
shipp->corkscrew_missile_bank = bank;
return 1; // Note: Missiles didn't get fired, but the frame interval code will fire them.
}
float t;
if (Weapon_info[weapon_idx].burst_shots > swp->burst_counter[bank_adjusted]) {
t = Weapon_info[weapon_idx].burst_delay;
swp->burst_counter[bank_adjusted]++;
} else {
t = Weapon_info[weapon_idx].fire_wait; // They can fire 5 times a second
swp->burst_counter[bank_adjusted] = 0;
swp->burst_seed[bank_adjusted] = Random::next();
}
swp->next_secondary_fire_stamp[bank] = timestamp((int)(t * 1000.0f));
swp->last_secondary_fire_stamp[bank] = timestamp();
// Here is where we check if weapons subsystem is capable of firing the weapon.
// do only in single player or if I am the server of a multiplayer game
if ( !(Game_mode & GM_MULTIPLAYER) || MULTIPLAYER_MASTER ) {
if ( ship_weapon_maybe_fail(shipp) ) {
if ( obj == Player_obj )
if ( ship_maybe_do_secondary_fail_sound_hud(wip, false) ) {
HUD_sourced_printf(HUD_SOURCE_HIDDEN, XSTR( "Cannot fire %s due to weapons system damage", 489), Weapon_info[weapon_idx].get_display_name());
}
goto done_secondary;
}
}
pm = model_get( sip->model_num );
if ( pm->n_missiles > 0 ) {
int check_ammo; // used to tell if we should check ammo counts or not
int num_slots;
if ( bank > pm->n_missiles ) {
nprintf(("WARNING","WARNING ==> Tried to fire bank %d, but ship has only %d banks\n", bank+1, pm->n_missiles));
return 0; // we can make a quick out here!!!
}
int target_objnum;
ship_subsys *target_subsys;
int locked;
if ( obj == Player_obj || ( MULTIPLAYER_MASTER && obj->flags[Object::Object_Flags::Player_ship] ) ) {
// use missile lock slots
if ( !shipp->missile_locks_firing.empty() ) {
lock_info lock_data = shipp->missile_locks_firing.back();
/* The conditional here might seem complicated but it is required to ensure that each use case is handled correctly.
* While all missiles need to have their locks cleared, swarms and corkscrews need them cleared only after the last missile is going to be
* or has been fired. (DahBlount)
**/
if ( wip->multi_lock ||
!(wip->wi_flags[Weapon::Info_Flags::Corkscrew] || wip->wi_flags[Weapon::Info_Flags::Swarm]) ||
(shipp->num_corkscrew_to_fire <= 1 && shipp->num_swarm_missiles_to_fire <= 1) ) {
shipp->missile_locks_firing.pop_back();
}
target_objnum = OBJ_INDEX(lock_data.obj);
target_subsys = lock_data.subsys;
locked = 1;
} else if (wip->wi_flags[Weapon::Info_Flags::Homing_heat]) {
target_objnum = aip->target_objnum;
target_subsys = aip->targeted_subsys;
locked = aip->current_target_is_locked;
} else {
target_objnum = -1;
target_subsys = nullptr;
locked = 0;
}
} else if (wip->multi_lock && !aip->ai_missile_locks_firing.empty()) {
target_objnum = aip->ai_missile_locks_firing.back().first;
target_subsys = aip->ai_missile_locks_firing.back().second;
locked = 1;
aip->ai_missile_locks_firing.pop_back();
} else {
target_objnum = aip->target_objnum;
target_subsys = aip->targeted_subsys;
locked = aip->current_target_is_locked;
}
num_slots = pm->missile_banks[bank].num_slots;
// determine if there is enough ammo left to fire weapons on this bank. As with primary
// weapons, we might or might not check ammo counts depending on game mode, who is firing,
// and if I am a client in multiplayer
check_ammo = 1;
if ( MULTIPLAYER_CLIENT && (obj != Player_obj) ){
check_ammo = 0;
}
bool no_ammo_needed = Weapon_info[swp->secondary_bank_weapons[bank]].wi_flags[Weapon::Info_Flags::SecondaryNoAmmo];
bool no_energy = shipp->weapon_energy < wip->energy_consumed;
if ( check_ammo && ((swp->secondary_bank_ammo[bank] <= 0 && !no_ammo_needed) || no_energy)) {
if ( shipp->objnum == OBJ_INDEX(Player_obj) ) {
if (ship_maybe_do_secondary_fail_sound_hud(wip, no_energy) ) {
// HUD_sourced_printf(HUD_SOURCE_HIDDEN, "No %s missiles left in bank", Weapon_info[swp->secondary_bank_weapons[bank]].name);
}
}
else {
// TODO: AI switch secondary weapon / re-arm?
}
goto done_secondary;
}
// Handle the optional disabling of dual fire
// dual fire/doublefire can be disabled for individual weapons, for players, or for AIs
// if any of these apply to the current weapon, unset the dual fire flag on the ship
// then proceed as normal.
// This is only handled at firing time so dualfire isn't lost when cycling through weapons
if (shipp->flags[Ship_Flags::Secondary_dual_fire] &&
( wip->wi_flags[Weapon::Info_Flags::No_doublefire] ||
( The_mission.ai_profile->flags[AI::Profile_Flags::Disable_ai_secondary_doublefire] &&
shipp->objnum != OBJ_INDEX(Player_obj) ) ||
( The_mission.ai_profile->flags[AI::Profile_Flags::Disable_player_secondary_doublefire] &&
shipp->objnum == OBJ_INDEX(Player_obj) ))
) {
shipp->flags.remove(Ship_Flags::Secondary_dual_fire);
}
int start_slot, end_slot;
no_energy = shipp->weapon_energy < 2 * wip->energy_consumed; // whether there's enough energy for at least 1 shot was checked above
if ( shipp->flags[Ship_Flags::Secondary_dual_fire] && num_slots > 1) {
start_slot = swp->secondary_next_slot[bank];
// AL 11-19-97: Ensure enough ammo remains when firing linked secondary weapons
if ( check_ammo && ((swp->secondary_bank_ammo[bank] < 2 && !no_ammo_needed) || no_energy) ) {
end_slot = start_slot;
} else {
end_slot = start_slot+1;
}
} else {
// de-set the flag just in case dual-fire was set but couldn't be used
// because there's less than two firepoints
shipp->flags.remove(Ship_Flags::Secondary_dual_fire);
start_slot = swp->secondary_next_slot[bank];
end_slot = start_slot;
}
int pnt_index=start_slot;
//If this is a tertiary weapon, only subtract one piece of ammo
for ( j = start_slot; j <= end_slot; j++ ) {
int weapon_num;
swp->secondary_next_slot[bank]++;
if ( swp->secondary_next_slot[bank] > (num_slots-1) ){
swp->secondary_next_slot[bank] = 0;
}
if ( pnt_index >= num_slots ){
pnt_index = 0;
}
shipp->secondary_point_reload_pct.set(bank, pnt_index, 0.0f);
pnt = pm->missile_banks[bank].pnt[pnt_index++];
polymodel *weapon_model = NULL;
if(wip->external_model_num >= 0){
weapon_model = model_get(wip->external_model_num);
}
if (weapon_model && weapon_model->n_guns) {
int external_bank = bank + MAX_SHIP_PRIMARY_BANKS;
if (wip->wi_flags[Weapon::Info_Flags::External_weapon_fp]) {
if ((weapon_model->n_guns <= swp->external_model_fp_counter[external_bank]) || (swp->external_model_fp_counter[external_bank] < 0))
swp->external_model_fp_counter[external_bank] = 0;
vm_vec_add2(&pnt, &weapon_model->gun_banks[0].pnt[swp->external_model_fp_counter[external_bank]]);
swp->external_model_fp_counter[external_bank]++;
} else {
// make it use the 0 index slot
vm_vec_add2(&pnt, &weapon_model->gun_banks[0].pnt[0]);
}
}
vm_vec_unrotate(&missile_point, &pnt, &obj->orient);
vm_vec_add(&firing_pos, &missile_point, &obj->pos);
if ( Game_mode & GM_MULTIPLAYER ) {
Assert( Weapon_info[weapon_idx].subtype == WP_MISSILE );
}
matrix firing_orient;
if(!(sip->flags[Ship::Info_Flags::Gun_convergence]))
{
firing_orient = obj->orient;
}
else
{
vec3d firing_vec;
vm_vec_unrotate(&firing_vec, &pm->missile_banks[bank].norm[pnt_index-1], &obj->orient);
vm_vector_2_matrix(&firing_orient, &firing_vec, NULL, NULL);
}
// create the weapon -- for multiplayer, the net_signature is assigned inside
// of weapon_create
weapon_num = weapon_create( &firing_pos, &firing_orient, weapon_idx, OBJ_INDEX(obj), -1, locked);
if (weapon_num == -1) {
// Weapon most likely failed to fire
if (obj == Player_obj) {
ship_maybe_do_secondary_fail_sound_hud(wip, false);
}
continue;
}
if (weapon_num >= 0) {
weapon_idx = Weapons[Objects[weapon_num].instance].weapon_info_index;
weapon_set_tracking_info(weapon_num, OBJ_INDEX(obj), target_objnum, locked, target_subsys);
has_fired = true;
// create the muzzle flash effect
shipfx_flash_create(obj, sip->model_num, &pnt, &obj->orient.vec.fvec, 0, weapon_idx);
if((wip->wi_flags[Weapon::Info_Flags::Shudder]) && (obj == Player_obj) && !(Game_mode & GM_STANDALONE_SERVER)){
// calculate some arbitrary value between 100
// (mass * velocity) / 10
game_shudder_apply(500, (wip->mass * wip->max_speed) * 0.1f);
}
num_fired++;
swp->detonate_weapon_time = timestamp((int)(DEFAULT_REMOTE_DETONATE_TRIGGER_WAIT * 1000));; // Can detonate 1/2 second later.
if (Weapon_info[weapon_idx].wi_flags[Weapon::Info_Flags::Remote])
swp->remote_detonaters_active++;
// possibly add this to the rollback vector
if ((Game_mode & (GM_MULTIPLAYER | GM_STANDALONE_SERVER)) && rollback_shot){
multi_ship_record_add_rollback_wep(weapon_num);
}
// subtract the number of missiles fired
if ( !Weapon_energy_cheat ){
if(!Weapon_info[swp->secondary_bank_weapons[bank]].wi_flags[Weapon::Info_Flags::SecondaryNoAmmo])
swp->secondary_bank_ammo[bank]--;
shipp->weapon_energy -= wip->energy_consumed;
}
}
}
}
if ( obj == Player_obj ) {
if ( Weapon_info[weapon_idx].launch_snd.isValid() ) {
snd_play( gamesnd_get_game_sound(Weapon_info[weapon_idx].launch_snd), 0.0f, 1.0f, SND_PRIORITY_MUST_PLAY );
swp = &Player_ship->weapons;
if (bank >= 0) {
wip = &Weapon_info[swp->secondary_bank_weapons[bank]];
if (Player_ship->flags[Ship_Flags::Secondary_dual_fire]){
joy_ff_play_secondary_shoot((int) (wip->cargo_size * 2.0f));
} else {
joy_ff_play_secondary_shoot((int) wip->cargo_size);
}
}
}
} else {
if ( Weapon_info[weapon_idx].launch_snd.isValid() ) {
snd_play_3d( gamesnd_get_game_sound(Weapon_info[weapon_idx].launch_snd), &obj->pos, &View_position );
}
}
done_secondary:
if(num_fired > 0){
// if I am the master of a multiplayer game, send a secondary fired packet along with the
// first network signatures for the newly created weapons.
// Cyborg17 - If this is a rollback shot, the server will let the player know within the packet.
if ( MULTIPLAYER_MASTER ) {
Assert(starting_sig != 0);
send_secondary_fired_packet( shipp, starting_sig, starting_bank_count, num_fired, allow_swarm );
}
// Handle Player only stuff, including stats and client secondary packets
if (obj->flags[Object::Object_Flags::Player_ship]) {
// in multiplayer -- only the server needs to keep track of the stats. Call the cool
// function to find the player given the object *. It had better return a valid player
// or our internal structure as messed up.
if( Game_mode & GM_MULTIPLAYER ) {
if ( Net_player->flags & NETINFO_FLAG_AM_MASTER ) {
int player_num;
player_num = multi_find_player_by_object ( obj );
Assert ( player_num != -1 );
Net_players[player_num].m_player->stats.ms_shots_fired += num_fired;
} else if (MULTIPLAYER_CLIENT) {
if (!wip->is_homing())
send_non_homing_fired_packet(shipp, num_fired, true);
}
} else {
Player->stats.ms_shots_fired += num_fired;
}
}
// maybe announce a shockwave weapon
ai_maybe_announce_shockwave_weapon(obj, weapon_idx);
}
// if we are out of ammo in this bank then don't carry over firing swarm/corkscrew
// missiles to a new bank
if (!ship_secondary_has_ammo(swp, bank) || shipp->weapon_energy < wip->energy_consumed) {
// NOTE: these are set to 1 since they will get reduced by 1 in the
// swarm/corkscrew code once this function returns
if (shipp->num_swarm_missiles_to_fire > 1) {
shipp->num_swarm_missiles_to_fire = 1;
shipp->swarm_missile_bank = -1;
}
if (shipp->num_corkscrew_to_fire > 1) {
shipp->num_corkscrew_to_fire = 1;
shipp->corkscrew_missile_bank = -1;
}
}
if (has_fired) {
object *objp = &Objects[shipp->objnum];
object* target;
if (Ai_info[shipp->ai_index].target_objnum != -1)
target = &Objects[Ai_info[shipp->ai_index].target_objnum];
else
target = NULL;
if (objp == Player_obj && Player_ai->target_objnum != -1)
target = &Objects[Player_ai->target_objnum];
//Start Animation in Forced mode: Always restart it from its initial position rather than just flip it to FWD motion if it was still moving. This is to make it work best for uses like recoil.
sip->animations.startAll(model_get_instance(shipp->model_instance_num), animation::ModelAnimationTriggerType::SecondaryFired, animation::ModelAnimationDirection::FWD, true, false, false, bank);
if (Script_system.IsActiveAction(CHA_ONWPFIRED) || Script_system.IsActiveAction(CHA_SECONDARYFIRE)) {
Script_system.SetHookObjects(2, "User", objp, "Target", target);
Script_system.RunCondition(CHA_ONWPFIRED, objp);
Script_system.RunCondition(CHA_SECONDARYFIRE, objp);
Script_system.RemHookVars({"User", "Target"});
}
}
// AL 3-7-98: Move to next valid secondary bank if out of ammo
//
//21-07-02 01:24 DTP; COMMENTED OUT some of the mistakes
//this bug was made by AL, when he assumed he had to take the next fire_wait time remaining and add 250 ms of delay to it,
//and put it in the next valid bank. for the player to have a 250 ms of penalty
//
//what that caused was that the next valid bank inherited the current valid banks FULL fire delay. since he used the Weapon_info struct that has
// no information / member that stores the next valids banks remaning fire_wait delay.
//
//what he should have done was to check of the next valid bank had any fire delay that had elapsed, if it had elapsed,
//then it would have no firedelay. and then add 250 ms of delay. in effect, this way there is no penalty if there is any firedelay remaning in
//the next valid bank. the delay is there to prevent things like Trible/Quad Fire Trebuchets.
//
// niffiwan: only try to switch banks if object has multiple banks, and firing bank is the current bank
if ( (obj->flags[Object::Object_Flags::Player_ship]) && !ship_secondary_has_ammo(swp, bank) && (swp->num_secondary_banks >= 2) && (bank == swp->current_secondary_bank) ) {
if (ship_select_next_secondary(obj) ) { //DTP here we switch to the next valid bank, but we can't call weapon_info on next fire_wait
if ( timestamp_elapsed(shipp->weapons.next_secondary_fire_stamp[shipp->weapons.current_secondary_bank]) ) { //DTP, this is simply a copy of the manual cycle functions
shipp->weapons.next_secondary_fire_stamp[shipp->weapons.current_secondary_bank] = timestamp(1000); //Bumped from 250 to 1000 because some people seem to be to triggerhappy :).
shipp->weapons.last_secondary_fire_stamp[shipp->weapons.current_secondary_bank] = timestamp();
}
if ( obj == Player_obj ) {
snd_play( gamesnd_get_game_sound(ship_get_sound(Player_obj, GameSounds::SECONDARY_CYCLE)) );
}
}
}
return num_fired;
}
// Goober5000
static int primary_out_of_ammo(ship_weapon *swp, int bank)
{
// true if both ballistic and ammo <= 0,
// false if not ballistic or if ballistic and ammo > 0
if ( Weapon_info[swp->primary_bank_weapons[bank]].wi_flags[Weapon::Info_Flags::Ballistic] )
{
if (swp->primary_bank_ammo[bank] <= 0)
{
return 1;
}
}
// note: never out of ammo if not ballistic
return 0;
}
static bool ship_has_a_ballistic_primary(const ship *shipp)
{
const ship_weapon *swp = &shipp->weapons;
for (int i = 0; i < swp->num_primary_banks; i++)
{
if (Weapon_info[swp->primary_bank_weapons[i]].wi_flags[Weapon::Info_Flags::Ballistic])
return true;
}
return false;
}
/**
* Return true if a new index gets selected.
*
* @param objp pointer to object for ship cycling primary
* @param direction forward == CYCLE_PRIMARY_NEXT, backward == CYCLE_PRIMARY_PREV
*
* NOTE: This code can be called for any arbitrary ship. HUD messages and sounds are only used
* for the player ship.
*/
int ship_select_next_primary(object *objp, int direction)
{
ship *shipp;
ship_info *sip;
ship_weapon *swp;
int new_bank;
int original_bank;
int i;
Assert(objp != NULL);
Assert(objp->type == OBJ_SHIP);
Assert(objp->instance >= 0 && objp->instance < MAX_SHIPS);
shipp = &Ships[objp->instance];
sip = &Ship_info[shipp->ship_info_index];
swp = &shipp->weapons;
Assert(direction == CYCLE_PRIMARY_NEXT || direction == CYCLE_PRIMARY_PREV);
original_bank = swp->current_primary_bank;
auto original_link_flag = shipp->flags[Ship_Flags::Primary_linked];
// redid case structure to possibly add more primaries in the future - Goober5000
if ( swp->num_primary_banks == 0 )
{
if ( objp == Player_obj )
{
HUD_sourced_printf(HUD_SOURCE_HIDDEN, "%s", XSTR( "This ship has no primary weapons", 490));
gamesnd_play_error_beep();
}
return 0;
}
else if ( swp->num_primary_banks == 1 )
{
if ( objp == Player_obj )
{
HUD_sourced_printf(HUD_SOURCE_HIDDEN, XSTR( "This ship has only one primary weapon: %s", 491),Weapon_info[swp->primary_bank_weapons[swp->current_primary_bank]].get_display_name(), swp->current_primary_bank + 1);
gamesnd_play_error_beep();
}
return 0;
}
else if ( swp->num_primary_banks > MAX_SHIP_PRIMARY_BANKS )
{
Int3();
return 0;
}
else
{
Assert((swp->current_primary_bank >= 0) && (swp->current_primary_bank < swp->num_primary_banks));
// first check if linked
if ( shipp->flags[Ship_Flags::Ship_selective_linking] )
{
printf("npb:%i\n", swp->num_primary_banks );
}
if ( shipp->flags[Ship_Flags::Primary_linked] )
{
shipp->flags.remove(Ship_Flags::Primary_linked);
if ( direction == CYCLE_PRIMARY_NEXT )
{
swp->current_primary_bank = 0;
}
else
{
swp->current_primary_bank = swp->num_primary_banks - 1;
}
}
// now handle when not linked: cycle and constrain
else
{
if ( direction == CYCLE_PRIMARY_NEXT )
{
if ( swp->current_primary_bank < swp->num_primary_banks - 1 )
{
swp->current_primary_bank++;
}
else if( sip->flags[Ship::Info_Flags::No_primary_linking] )
{
swp->current_primary_bank = 0;
}
else
{
shipp->flags.set(Ship_Flags::Primary_linked);
}
}
else
{
if ( swp->current_primary_bank > 0 )
{
swp->current_primary_bank--;
}
else if( sip->flags[Ship::Info_Flags::No_primary_linking] )
{
swp->current_primary_bank = swp->num_primary_banks - 1;
}
else
{
shipp->flags.set(Ship_Flags::Primary_linked);
}
}
}
}
// test for ballistics - Goober5000
if (ship_has_a_ballistic_primary(shipp))
{
// if we can't link, disengage primary linking and change to next available bank
if (shipp->flags[Ship_Flags::Primary_linked])
{
for (i = 0; i < swp->num_primary_banks; i++)
{
if (!Weapon_info[swp->primary_bank_weapons[i]].wi_flags[Weapon::Info_Flags::Nolink] && primary_out_of_ammo(swp, i)) // We only care if linkable primaries are out of ammo.
{
shipp->flags.remove(Ship_Flags::Primary_linked);
if (direction == CYCLE_PRIMARY_NEXT)
{
swp->current_primary_bank = 0;
}
else
{
swp->current_primary_bank = shipp->weapons.num_primary_banks-1;
}
break;
}
}
}
// check to see if we keep cycling...we have to if we're out of ammo in the current bank
if ( primary_out_of_ammo(swp, swp->current_primary_bank) )
{
// cycle around until we find ammunition...
// we land on the original bank if all banks fail
Assert(swp->current_primary_bank < swp->num_primary_banks);
new_bank = swp->current_primary_bank;
for (i = 1; i < swp->num_primary_banks; i++)
{
// cycle in the proper direction
if ( direction == CYCLE_PRIMARY_NEXT )
{
new_bank = (swp->current_primary_bank + i) % swp->num_primary_banks;
}
else
{
new_bank = (swp->current_primary_bank + swp->num_primary_banks - i) % swp->num_primary_banks;
}
// check to see if this is a valid bank
if (!primary_out_of_ammo(swp, new_bank))
{
break;
}
}
// set the new bank; defaults to resetting to the old bank if we completed a full iteration
swp->current_primary_bank = new_bank;
}
// make sure we're okay
Assert((swp->current_primary_bank >= 0) && (swp->current_primary_bank < swp->num_primary_banks));
} // end of ballistics implementation
swp->previous_primary_bank = original_bank;
// check to make sure we actually changed banks (which can fail to happen if e.g. ballistic weapons ran out of ammo)
if ( (swp->current_primary_bank != original_bank) || ((shipp->flags[Ship_Flags::Primary_linked]) != original_link_flag) )
{
if ( objp == Player_obj )
{
snd_play( gamesnd_get_game_sound(ship_get_sound(objp, GameSounds::PRIMARY_CYCLE)), 0.0f );
}
ship_primary_changed(shipp);
objp = &Objects[shipp->objnum];
object* target;
if (Ai_info[shipp->ai_index].target_objnum != -1)
target = &Objects[Ai_info[shipp->ai_index].target_objnum];
else
target = NULL;
if (objp == Player_obj && Player_ai->target_objnum != -1)
target = &Objects[Player_ai->target_objnum];
if (Script_system.IsActiveAction(CHA_ONWPSELECTED) || Script_system.IsActiveAction(CHA_ONWPDESELECTED)) {
Script_system.SetHookObjects(2, "User", objp, "Target", target);
Script_system.RunCondition(CHA_ONWPSELECTED, objp);
Script_system.RunCondition(CHA_ONWPDESELECTED, objp);
Script_system.RemHookVars({"User", "Target"});
}
return 1;
}
// could not select new weapon:
if ( objp == Player_obj )
{
gamesnd_play_error_beep();
}
return 0;
}
// ------------------------------------------------------------------------------
// ship_select_next_secondary() selects the next secondary bank with missles
//
// returns: 1 => The secondary bank was switched
// 0 => The secondary bank stayed the same
//
// If a secondary bank has no missles left, it is skipped.
//
// NOTE: This can be called for an arbitrary ship. HUD messages and sounds are only used
// for the player ship.
int ship_select_next_secondary(object *objp)
{
Assert(objp != NULL);
Assert(objp->type == OBJ_SHIP);
Assert(objp->instance >= 0 && objp->instance < MAX_SHIPS);
int original_bank, new_bank, i;
ship *shipp;
ship_weapon *swp;
shipp = &Ships[objp->instance];
swp = &shipp->weapons;
// redid the switch structure to allow additional seconary banks if added later - Goober5000
if ( swp->num_secondary_banks == 0 )
{
if ( objp == Player_obj )
{
HUD_sourced_printf(HUD_SOURCE_HIDDEN, "%s", XSTR( "This ship has no secondary weapons", 492));
gamesnd_play_error_beep();
}
return 0;
}
else if ( swp->num_secondary_banks == 1 )
{
if ( objp == Player_obj )
{
HUD_sourced_printf(HUD_SOURCE_HIDDEN, XSTR( "This ship has only one secondary weapon: %s", 493), Weapon_info[swp->secondary_bank_weapons[swp->current_secondary_bank]].get_display_name(), swp->current_secondary_bank + 1);
gamesnd_play_error_beep();
}
return 0;
}
else if ( swp->num_secondary_banks > MAX_SHIP_SECONDARY_BANKS )
{
Int3();
return 0;
}
else
{
Assert((swp->current_secondary_bank >= 0) && (swp->current_secondary_bank < swp->num_secondary_banks));
original_bank = swp->current_secondary_bank;
for ( i = 1; i < swp->num_secondary_banks; i++ ) {
new_bank = (swp->current_secondary_bank+i) % swp->num_secondary_banks;
if ( !ship_secondary_has_ammo(swp, new_bank))
continue;
swp->current_secondary_bank = new_bank;
break;
}
if ( swp->current_secondary_bank != original_bank )
{
swp->previous_secondary_bank = original_bank;
if ( objp == Player_obj )
{
snd_play( gamesnd_get_game_sound(ship_get_sound(Player_obj, GameSounds::SECONDARY_CYCLE)), 0.0f );
}
ship_secondary_changed(shipp);
// Clear missile locks when banks are switched
for (auto& missile_lock : shipp->missile_locks) {
ship_clear_lock(&missile_lock);
}
shipp->missile_locks_firing.clear();
objp = &Objects[shipp->objnum];
object* target;
if (Ai_info[shipp->ai_index].target_objnum != -1)
target = &Objects[Ai_info[shipp->ai_index].target_objnum];
else
target = NULL;
if (objp == Player_obj && Player_ai->target_objnum != -1)
target = &Objects[Player_ai->target_objnum];
if (Script_system.IsActiveAction(CHA_ONWPSELECTED) || Script_system.IsActiveAction(CHA_ONWPDESELECTED)) {
Script_system.SetHookObjects(2, "User", objp, "Target", target);
Script_system.RunCondition(CHA_ONWPSELECTED, objp);
Script_system.RunCondition(CHA_ONWPDESELECTED, objp);
Script_system.RemHookVars({"User", "Target"});
}
return 1;
}
} // end if
// If we've reached this point, must have failed
if ( objp == Player_obj )
{
gamesnd_play_error_beep();
}
return 0;
}
// Goober5000 - copied from secondary routine
// Stuff list of weapon indices for object *objp in list *outlist.
// Return number of weapons in list.
int get_available_primary_weapons(object *objp, int *outlist, int *outbanklist)
{
int count = 0;
int i;
ship *shipp;
Assert(objp->type == OBJ_SHIP);
Assert((objp->instance >= 0) && (objp->instance < MAX_SHIPS));
shipp = &Ships[objp->instance];
for (i=0; i<shipp->weapons.num_primary_banks; i++)
{
if (!primary_out_of_ammo(&(shipp->weapons), i))
{
outbanklist[count] = i;
outlist[count++] = shipp->weapons.primary_bank_weapons[i];
}
}
return count;
}
/**
* Stuff list of weapon indices for object *objp in list *outlist.
* @return number of weapons in list.
*/
int get_available_secondary_weapons(object *objp, int *outlist, int *outbanklist)
{
int count = 0;
int i;
ship *shipp;
weapon_info *wepp;
Assert(objp->type == OBJ_SHIP);
Assert((objp->instance >= 0) && (objp->instance < MAX_SHIPS));
shipp = &Ships[objp->instance];
Assert(shipp->ai_index >= 0 && shipp->ai_index < MAX_AI_INFO);
ai_info* aip = &Ai_info[shipp->ai_index];
float target_range, weapon_range_max, weapon_range_min;
target_range = 0.0f;
if (The_mission.ai_profile->ai_range_aware_secondary_select_mode!= AI_RANGE_AWARE_SEC_SEL_MODE_RETAIL) {
vec3d our_position = objp->pos;
vec3d target_position;
object *target = &Objects[Ai_info[shipp->ai_index].target_objnum];
if (target->type == OBJ_SHIP) {
if (aip->targeted_subsys != nullptr) {
get_subsystem_pos(&target_position, target, aip->targeted_subsys);
}
else if (Ship_info[shipp->ship_info_index].is_big_or_huge() ){
ai_big_pick_attack_point(target, objp, &target_position, 0.8f);
}
else {
target_position = target->pos;
}
}
else {
target_position = target->pos;
}
target_range = vm_vec_dist_quick(&our_position, &target_position);
}
for (i=0; i<shipp->weapons.num_secondary_banks; i++)
if (ship_secondary_has_ammo(&shipp->weapons, i)) {
if (The_mission.ai_profile->ai_range_aware_secondary_select_mode != AI_RANGE_AWARE_SEC_SEL_MODE_RETAIL) {
wepp = &Weapon_info[shipp->weapons.secondary_bank_weapons[i]];
weapon_range_min = wepp->weapon_min_range;
weapon_range_max = wepp->weapon_range;
//If weapon range is not set in the weapon info, derive it
if (weapon_range_max >= WEAPON_DEFAULT_TABLED_MAX_RANGE) {
if (wepp->subtype == WP_BEAM) {
weapon_range_max = wepp->b_info.range;
}
else {
weapon_range_max = wepp->lifetime * wepp->max_speed;
}
}
if (target_range <= weapon_range_max && target_range >= weapon_range_min) {
outbanklist[count] = i;
outlist[count++] = shipp->weapons.secondary_bank_weapons[i];
}
}
else {
outbanklist[count] = i;
outlist[count++] = shipp->weapons.secondary_bank_weapons[i];
}
}
return count;
}
void wing_bash_ship_name(char *ship_name, const char *wing_name, int index, bool *needs_display_name)
{
if (needs_display_name)
*needs_display_name = false;
// if wing name has a hash symbol, create the ship name a particular way
// (but don't do this for names that have the hash as the first or last character)
const char *p = get_pointer_to_first_hash_symbol(wing_name);
if ((p != NULL) && (p != wing_name) && (*(p+1) != '\0'))
{
size_t len = (p - wing_name);
strncpy(ship_name, wing_name, len);
sprintf(ship_name + len, NOX(" %d"), index);
strcat(ship_name, p);
if (needs_display_name)
*needs_display_name = true;
}
// most of the time we should create the name the standard retail way
else
sprintf(ship_name, NOX("%s %d"), wing_name, index);
}
/**
* Return the object index of the ship with name *name.
*/
int wing_name_lookup(const char *name, int ignore_count)
{
int i, wing_limit;
Assertion(name != nullptr, "NULL name passed to wing_name_lookup");
if ( Fred_running )
wing_limit = MAX_WINGS;
else
wing_limit = Num_wings;
if (Fred_running || ignore_count ) { // current_count not used for Fred..
for (i=0; i<wing_limit; i++)
if (Wings[i].wave_count && !stricmp(Wings[i].name, name))
return i;
} else {
for (i=0; i<wing_limit; i++)
if (Wings[i].current_count && !stricmp(Wings[i].name, name))
return i;
}
return -1;
}
bool wing_has_yet_to_arrive(const wing *wingp)
{
return (wingp != nullptr) && (wingp->num_waves >= 0) && (wingp->total_arrived_count == 0);
}
/**
* Needed in addition to wing_name_lookup because it does a straight lookup without
* caring about how many ships are in the wing, etc.
*/
int wing_lookup(const char *name)
{
Assertion(name != nullptr, "NULL name passed to wing_lookup");
for(int idx=0;idx<Num_wings;idx++)
if(stricmp(Wings[idx].name,name)==0)
return idx;
return -1;
}
int wing_formation_lookup(const char *formation_name)
{
Assertion(formation_name != nullptr, "NULL formation name passed to wing_formation_lookup");
for (int idx = 0; idx < (int)Wing_formations.size(); ++idx)
if (stricmp(Wing_formations[idx].name, formation_name) == 0)
return idx;
return -1;
}
/**
* Return the index of Ship_info[].name that is *token.
*/
static int ship_info_lookup_sub(const char *token)
{
Assertion(token != nullptr, "NULL token passed to ship_info_lookup_sub");
for (auto it = Ship_info.cbegin(); it != Ship_info.cend(); ++it)
if (!stricmp(token, it->name))
return (int)std::distance(Ship_info.cbegin(), it);
return -1;
}
/**
* Return the index of Ship_templates[].name that is *token.
*/
static int ship_template_lookup(const char *token)
{
Assertion(token != nullptr, "NULL token passed to ship_template_lookup");
for ( auto it = Ship_templates.cbegin(); it != Ship_templates.cend(); ++it ) {
if ( !stricmp(token, it->name) ) {
return (int)std::distance(Ship_templates.cbegin(), it);
}
}
return -1;
}
// Goober5000
int ship_info_lookup(const char *token)
{
int idx;
const char *p;
char name[NAME_LENGTH], temp1[NAME_LENGTH], temp2[NAME_LENGTH];
Assertion(token != nullptr, "NULL token passed to ship_info_lookup");
// first try a straightforward lookup
idx = ship_info_lookup_sub(token);
if (idx >= 0)
return idx;
// ship copy types might be mismatched
p = get_pointer_to_first_hash_symbol(token);
if (p == NULL)
return -1;
// conversion from FS1 missions
if (!stricmp(token, "GTD Orion#1 (Galatea)"))
{
idx = ship_info_lookup_sub("GTD Orion#Galatea");
if (idx >= 0)
return idx;
idx = ship_info_lookup_sub("GTD Orion (Galatea)");
if (idx >= 0)
return idx;
return -1;
}
else if (!stricmp(token, "GTD Orion#2 (Bastion)"))
{
idx = ship_info_lookup_sub("GTD Orion#Bastion");
if (idx >= 0)
return idx;
idx = ship_info_lookup_sub("GTD Orion (Bastion)");
if (idx >= 0)
return idx;
return -1;
}
else if (!stricmp(token, "SF Dragon#2 (weakened)"))
{
idx = ship_info_lookup_sub("SF Dragon#weakened");
if (idx >= 0)
return idx;
idx = ship_info_lookup_sub("SF Dragon (weakened)");
if (idx >= 0)
return idx;
return -1;
}
else if (!stricmp(token, "SF Dragon#3 (Player)"))
{
idx = ship_info_lookup_sub("SF Dragon#Terrans");
if (idx >= 0)
return idx;
idx = ship_info_lookup_sub("SF Dragon (Terrans)");
if (idx >= 0)
return idx;
return -1;
}
else if (!stricmp(token, "GTSC Faustus#2 (big blast)"))
{
idx = ship_info_lookup_sub("GTSC Faustus#bigblast");
if (idx >= 0)
return idx;
return -1;
}
else if (!stricmp(token, "GTF Loki (stealth)"))
{
idx = ship_info_lookup_sub("GTF Loki#stealth");
if (idx >= 0)
return idx;
return -1;
}
// get first part of new string
strcpy_s(temp1, token);
end_string_at_first_hash_symbol(temp1);
// get second part
strcpy_s(temp2, p + 1);
// found a hash
if (*p == '#')
{
if (strlen(token) > NAME_LENGTH-3) {
// If the below sprintf would exceed NAME_LENGTH (taking \0 terminator into account), give a warning and return.
Warning(LOCATION, "Token [%s] is too long to be parenthesized by ship_info_lookup()!\n", token);
return -1;
}
// assemble using parentheses
sprintf_safe(name, "%s (%s)", temp1, temp2);
}
// found a parenthesis
else if (*p == '(')
{
// chop off right parenthesis (it exists because otherwise the left wouldn't have been flagged)
char *p2 = strchr(temp2, ')');
*p2 = '\0';
// assemble using hash
sprintf_safe(name, "%s#%s", temp1, temp2);
}
// oops
else
{
Warning(LOCATION, "Unrecognized hash symbol. Contact a programmer!");
return -1;
}
// finally check the new name
return ship_info_lookup_sub(name);
}
/**
* Return the ship index of the ship with name *name.
*/
int ship_name_lookup(const char *name, int inc_players)
{
Assertion(name != nullptr, "NULL name passed to ship_name_lookup");
for (int i=0; i<MAX_SHIPS; i++){
if (Ships[i].objnum >= 0){
if (Objects[Ships[i].objnum].type == OBJ_SHIP || (Objects[Ships[i].objnum].type == OBJ_START && inc_players)){
if (!stricmp(name, Ships[i].ship_name)){
return i;
}
}
}
}
// couldn't find it
return -1;
}
int ship_type_name_lookup_sub(const char *name)
{
Assertion(name != nullptr, "NULL name passed to ship_type_name_lookup");
//Look through Ship_types array
for (size_t idx = 0; idx < Ship_types.size(); ++idx)
if (!stricmp(name, Ship_types[idx].name))
return (int)idx;
// couldn't find it
return -1;
}
int ship_type_name_lookup(const char *name)
{
// try the normal lookup
auto idx = ship_type_name_lookup_sub(name);
if (idx >= 0)
return idx;
// Goober5000 - in retail FreeSpace, some ship classes were specified differently
// in ships.tbl and the ship type array; this patches those differences so that
// the ship type lookup will work properly
if (!stricmp(name, "sentrygun"))
name = "sentry gun";
else if (!stricmp(name, "escapepod"))
name = "escape pod";
else if (!stricmp(name, "repair_rearm"))
name = "support";
else if (!stricmp(name, "supercap"))
name = "super cap";
else if (!stricmp(name, "knossos"))
name = "knossos device";
// try it again
return ship_type_name_lookup_sub(name);
}
// Finds the world position of a subsystem.
// Return true/false for subsystem found/not found.
// Stuff vector *pos with absolute position.
// subsysp is a pointer to the subsystem.
int get_subsystem_pos(vec3d* pos, object* objp, ship_subsys* subsysp)
{
if (subsysp == NULL) {
*pos = objp->pos;
return 0;
}
Assertion(objp->type == OBJ_SHIP, "Only ships can have subsystems!");
model_subsystem* mss = subsysp->system_info;
if (mss->subobj_num == -1) {
// If it's a special point subsys, we can use its offset directly
vm_vec_unrotate(pos, &subsysp->system_info->pnt, &objp->orient);
vm_vec_add2(pos, &objp->pos);
} else {
// Submodel subsystems may require a more complicated calculation
auto pmi = model_get_instance(Ships[objp->instance].model_instance_num);
auto pm = model_get(pmi->model_num);
find_submodel_instance_world_point(pos, pm, pmi, mss->subobj_num, &objp->orient, &objp->pos);
}
return 1;
}
/**
* Makes sure all submodel instances for this ship are kept in sync
*/
void ship_model_replicate_submodels(object *objp)
{
model_subsystem *psub;
ship *shipp;
ship_subsys *pss;
flagset<Ship::Subsystem_Flags> empty;
Assert(objp != NULL);
Assert(objp->instance >= 0);
Assert(objp->type == OBJ_SHIP);
shipp = &Ships[objp->instance];
polymodel_instance *pmi = model_get_instance(shipp->model_instance_num);
polymodel *pm = model_get(pmi->model_num);
// Keep submodels belonging to subsystems in sync
// (This needs to be done from the subsystem perspective because subsystem flags may affect things)
for ( pss = GET_FIRST(&shipp->subsys_list); pss != END_OF_LIST(&shipp->subsys_list); pss = GET_NEXT(pss) ) {
psub = pss->system_info;
if ( psub->subobj_num >= 0 ) {
model_replicate_submodel_instance(pm, pmi, psub->subobj_num, pss->flags );
}
if ( (psub->subobj_num != psub->turret_gun_sobj) && (psub->turret_gun_sobj >= 0) ) {
model_replicate_submodel_instance(pm, pmi, psub->turret_gun_sobj, pss->flags );
}
}
// Keep other movable submodels in sync
model_iterate_submodel_tree(pm, pm->detail[0], [&](int submodel, int /*level*/, bool /*isLeaf*/)
{
auto sm = &pm->submodel[submodel];
// skip submodels that belong to subsystems since we already updated them above
// (Turrets are tricky because they have two submodels per subsystem, but because the subsystem index is copied
// when the model is read, both submodels are correctly skipped here.)
if (sm->subsys_num >= 0)
return;
// the only non-subsystem submodels that need to be updated are the ones that can move
if (!sm->flags[Model::Submodel_flags::Can_move])
return;
model_replicate_submodel_instance(pm, pmi, submodel, empty);
});
}
/**
* Finds the number of crew points in a ship
*/
int ship_find_num_crewpoints(object *objp)
{
int n = 0;
model_subsystem *psub;
ship *shipp;
ship_subsys *pss;
shipp = &Ships[objp->instance];
// Go through all subsystems and record the model angles for all
// the subsystems that need it.
for ( pss = GET_FIRST(&shipp->subsys_list); pss != END_OF_LIST(&shipp->subsys_list); pss = GET_NEXT(pss) ) {
psub = pss->system_info;
switch (psub->type) {
case SUBSYSTEM_TURRET:
if ( psub->flags[Model::Subsystem_Flags::Crewpoint] )
n++; // fall through
case SUBSYSTEM_RADAR:
case SUBSYSTEM_NAVIGATION:
case SUBSYSTEM_COMMUNICATION:
case SUBSYSTEM_UNKNOWN:
case SUBSYSTEM_ENGINE:
case SUBSYSTEM_GAS_COLLECT:
case SUBSYSTEM_ACTIVATION:
break;
default:
Error(LOCATION, "Illegal subsystem type.\n");
}
}
return n;
}
/**
* Finds the number of turrets in a ship
*/
int ship_find_num_turrets(object *objp)
{
int n = 0;
model_subsystem *psub;
ship *shipp;
ship_subsys *pss;
shipp = &Ships[objp->instance];
// Go through all subsystems and record the model angles for all
// the subsystems that need it.
for ( pss = GET_FIRST(&shipp->subsys_list); pss != END_OF_LIST(&shipp->subsys_list); pss = GET_NEXT(pss) ) {
psub = pss->system_info;
switch (psub->type) {
case SUBSYSTEM_TURRET:
n++; // drop through
case SUBSYSTEM_RADAR:
case SUBSYSTEM_NAVIGATION:
case SUBSYSTEM_COMMUNICATION:
case SUBSYSTEM_UNKNOWN:
case SUBSYSTEM_ENGINE:
case SUBSYSTEM_GAS_COLLECT:
case SUBSYSTEM_ACTIVATION:
break;
default:
Error(LOCATION, "Illegal subsystem type.\n");
}
}
return n;
}
//WMC
static void ship_set_eye( object *obj, int eye_index)
{
if(obj->type != OBJ_SHIP)
return;
ship *shipp = &Ships[obj->instance];
if(eye_index < 0)
{
shipp->current_viewpoint = -1;
return;
}
ship_info *sip = &Ship_info[shipp->ship_info_index];
if(sip->model_num < 0)
return;
polymodel *pm = model_get(sip->model_num);
if(pm == NULL || eye_index > pm->n_view_positions)
return;
shipp->current_viewpoint = eye_index;
}
// calculates the eye position for this ship in the global reference frame. Uses the
// view_positions array in the model. The 0th element is the normal viewing position.
// the vector of the eye is returned in the parameter 'eye'. The orientation of the
// eye is returned in orient. (NOTE: this is kind of bogus for now since non 0th element
// eyes have no defined up vector)
void ship_get_eye( vec3d *eye_pos, matrix *eye_orient, object *obj, bool do_slew , bool from_origin)
{
Assertion(obj->type == OBJ_SHIP, "Only ships can have eye positions!");
ship *shipp = &Ships[obj->instance];
auto pmi = model_get_instance(shipp->model_instance_num);
auto pm = model_get(pmi->model_num);
// check to be sure that we have a view eye to look at.....spit out nasty debug message
if ( shipp->current_viewpoint < 0 || pm->n_view_positions == 0 || shipp->current_viewpoint > pm->n_view_positions) {
*eye_pos = obj->pos;
*eye_orient = obj->orient;
return;
}
// eye points are stored in an array -- the normal viewing position for a ship is the current_eye_index
// element.
eye *ep = &(pm->view_positions[shipp->current_viewpoint]);
if (ep->parent >= 0 && pm->submodel[ep->parent].flags[Model::Submodel_flags::Can_move]) {
find_submodel_instance_point_orient(eye_pos, eye_orient, pm, pmi, ep->parent, &ep->pnt, &vmd_identity_matrix);
vec3d tvec = *eye_pos;
vm_vec_unrotate(eye_pos, &tvec, &obj->orient);
vm_vec_add2(eye_pos, &obj->pos);
matrix tempmat = *eye_orient;
vm_matrix_x_matrix(eye_orient, &obj->orient, &tempmat);
} else {
model_instance_find_world_point( eye_pos, &ep->pnt, shipp->model_instance_num, ep->parent, &obj->orient, from_origin ? &vmd_zero_vector : &obj->pos );
*eye_orient = obj->orient;
}
// Modify the orientation based on head orientation.
if ( Viewer_obj == obj && do_slew) {
// Add the cockpit leaning translation offset
vm_vec_add2(eye_pos,&leaning_position);
compute_slew_matrix(eye_orient, &Viewer_slew_angles);
}
}
// of attackers to make this decision.
//
// NOTE: This function takes into account how many ships are attacking a subsystem, and will
// prefer an ignored subsystem over a subsystem that is in line of sight, if the in-sight
// subsystem is attacked by more than MAX_SUBSYS_ATTACKERS
// input:
// sp => ship pointer to parent of subsystem
// subsys_type => what kind of subsystem this is
// attacker_pos => the world coords of the attacker of this subsystem
//
// returns: pointer to subsystem if one found, NULL otherwise
#define MAX_SUBSYS_ATTACKERS 3
ship_subsys *ship_get_best_subsys_to_attack(ship *sp, int subsys_type, vec3d *attacker_pos)
{
ship_subsys *ss;
ship_subsys *best_in_sight_subsys, *lowest_attacker_subsys, *ss_return;
int lowest_num_attackers, lowest_in_sight_attackers, num_attackers;
vec3d gsubpos;
ship_obj *sop;
lowest_in_sight_attackers = lowest_num_attackers = 1000;
ss_return = best_in_sight_subsys = lowest_attacker_subsys = NULL;
for (ss = GET_FIRST(&sp->subsys_list); ss != END_OF_LIST(&sp->subsys_list); ss = GET_NEXT(ss) ) {
if ( (ss->system_info->type == subsys_type) && (ss->current_hits > 0) ) {
// get world pos of subsystem
vm_vec_unrotate(&gsubpos, &ss->system_info->pnt, &Objects[sp->objnum].orient);
vm_vec_add2(&gsubpos, &Objects[sp->objnum].pos);
// now find the number of ships attacking this subsystem by iterating through the ships list,
// and checking if aip->targeted_subsys matches the subsystem we're checking
num_attackers = 0;
sop = GET_FIRST(&Ship_obj_list);
while(sop != END_OF_LIST(&Ship_obj_list)){
if ( Ai_info[Ships[Objects[sop->objnum].instance].ai_index].targeted_subsys == ss ) {
num_attackers++;
}
sop = GET_NEXT(sop);
}
if ( num_attackers < lowest_num_attackers ) {
lowest_num_attackers = num_attackers;
lowest_attacker_subsys = ss;
}
if ( ship_subsystem_in_sight(&Objects[sp->objnum], ss, attacker_pos, &gsubpos) ) {
if ( num_attackers < lowest_in_sight_attackers ) {
lowest_in_sight_attackers = num_attackers;
best_in_sight_subsys = ss;
}
}
}
}
if ( best_in_sight_subsys == NULL ) {
// no subsystems are in sight, so return the subsystem with the lowest # of attackers
ss_return = lowest_attacker_subsys;
} else {
if ( lowest_in_sight_attackers > MAX_SUBSYS_ATTACKERS ) {
ss_return = lowest_attacker_subsys;
} else {
ss_return = best_in_sight_subsys;
}
}
return ss_return;
}
// function to return a pointer to the 'nth' ship_subsys structure in a ship's linked list
// of ship_subsys'.
// attacker_pos => world pos of attacker (default value NULL). If value is non-NULL, try
// to select the best subsystem to attack of that type (using line-of-sight)
// and based on the number of ships already attacking the subsystem
ship_subsys *ship_get_indexed_subsys( ship *sp, int index, vec3d *attacker_pos )
{
int count;
ship_subsys *ss;
// first, special code to see if the index < 0. If so, we are looking for one of several possible
// engines or one of several possible turrets. If we enter this if statement, we will always return
// something.
if ( index < 0 ) {
int subsys_type;
subsys_type = -index;
if ( sp->subsys_info[subsys_type].aggregate_current_hits <= 0.0f ) // if there are no hits, no subsystem to attack.
return NULL;
if ( attacker_pos != NULL ) {
ss = ship_get_best_subsys_to_attack(sp, subsys_type, attacker_pos);
return ss;
} else {
// next, scan the list of subsystems and search for the first subsystem of the particular
// type which has > 0 hits remaining.
for (ss = GET_FIRST(&sp->subsys_list); ss != END_OF_LIST(&sp->subsys_list); ss = GET_NEXT(ss) ) {
if ( (ss->system_info->type == subsys_type) && (ss->current_hits > 0) )
return ss;
}
}
// maybe we shouldn't get here, but with possible floating point rounding, I suppose we could
Warning(LOCATION, "Unable to get a nonspecific subsystem of index %d on ship %s!", index, sp->ship_name);
return NULL;
}
count = 0;
ss = GET_FIRST(&sp->subsys_list);
while ( ss != END_OF_LIST( &sp->subsys_list ) ) {
if ( count == index )
return ss;
count++;
ss = GET_NEXT( ss );
}
// get allender -- turret ref didn't fixup correctly!!!!
Warning(LOCATION, "In ship_get_indexed_subsys, unable to get a subsystem of index %d on ship %s, due to a broken subsystem reference! This is most likely due to a table/model mismatch.", index, sp->ship_name);
return NULL;
}
/**
* Given a pointer to a subsystem and an associated object, return the index.
*/
int ship_get_index_from_subsys(ship_subsys *ssp, int objnum)
{
if (ssp == NULL)
return -1;
else {
int count;
ship *shipp;
ship_subsys *ss;
Assert(objnum >= 0);
Assert(Objects[objnum].instance >= 0);
shipp = &Ships[Objects[objnum].instance];
count = 0;
ss = GET_FIRST(&shipp->subsys_list);
while ( ss != END_OF_LIST( &shipp->subsys_list ) ) {
if ( ss == ssp)
return count;
count++;
ss = GET_NEXT( ss );
}
return -1;
}
}
/**
* Returns the index number of the ship_subsys parameter
*/
int ship_get_subsys_index(ship *sp, const char* ss_name)
{
int count;
ship_subsys *ss;
count = 0;
ss = GET_FIRST(&sp->subsys_list);
while ( ss != END_OF_LIST( &sp->subsys_list ) ) {
if ( !subsystem_stricmp(ss->system_info->subobj_name, ss_name) )
return count;
count++;
ss = GET_NEXT( ss );
}
return -1;
}
/**
* Returns the index number of the ship_subsys parameter
*/
int ship_get_subsys_index(ship *shipp, ship_subsys *subsys)
{
int count;
ship_subsys *ss;
count = 0;
ss = GET_FIRST(&shipp->subsys_list);
while (ss != END_OF_LIST(&shipp->subsys_list)) {
if (ss == subsys)
return count;
count++;
ss = GET_NEXT(ss);
}
return -1;
}
// routine to return the strength of a subsystem. We keep a total hit tally for all subsystems
// which are similar (i.e. a total for all engines). These routines will return a number between
// 0.0 and 1.0 which is the relative combined strength of the given subsystem type. The number
// calculated for the engines is slightly different. Once an engine reaches < 15% of its hits, its
// output drops to that %. A dead engine has no output.
float ship_get_subsystem_strength( ship *shipp, int type, bool skip_dying_check )
{
float strength;
ship_subsys *ssp;
Assert ( (type >= 0) && (type < SUBSYSTEM_MAX) );
// For a dying ship, all subsystem strengths are zero.
if (Objects[shipp->objnum].hull_strength <= 0.0f && !skip_dying_check)
return 0.0f;
// short circuit 1
if (shipp->subsys_info[type].aggregate_max_hits <= 0.0f)
return 1.0f;
// short circuit 0
if (shipp->subsys_info[type].aggregate_current_hits <= 0.0f)
return 0.0f;
strength = shipp->subsys_info[type].aggregate_current_hits / shipp->subsys_info[type].aggregate_max_hits;
Assert( strength != 0.0f );
if ( (type == SUBSYSTEM_ENGINE) && (strength < 1.0f) ) {
float percent;
percent = 0.0f;
ssp = GET_FIRST(&shipp->subsys_list);
while ( ssp != END_OF_LIST( &shipp->subsys_list ) ) {
if ( ssp->system_info->type == SUBSYSTEM_ENGINE ) {
float ratio;
ratio = ssp->current_hits / ssp->max_hits;
if ( ratio < ENGINE_MIN_STR )
ratio = ENGINE_MIN_STR;
percent += ratio;
}
ssp = GET_NEXT( ssp );
}
strength = percent / (float)shipp->subsys_info[type].type_count;
}
return strength;
}
/**
* Set the strength of a subsystem on a given ship.
*
* The strength passed as a parameter is between 0.0 and 1.0
*
* NOTE: this function was made to be called by the debug function dcf_set_subsys(). If
* you want to use this, be sure that you test it for all cases.
*/
void ship_set_subsystem_strength( ship *shipp, int type, float strength )
{
ship_subsys *ssp;
Assert ( (type >= 0) && (type < SUBSYSTEM_MAX) );
CLAMP(strength, 0.0f, 1.0f);
ssp = GET_FIRST(&shipp->subsys_list);
while ( ssp != END_OF_LIST( &shipp->subsys_list ) ) {
if ( (ssp->system_info->type == type) && !(ssp->flags[Ship::Subsystem_Flags::No_aggregate]) ) {
ssp->current_hits = strength * ssp->max_hits;
// maybe blow up subsys
if (ssp->current_hits <= 0) {
do_subobj_destroyed_stuff(shipp, ssp, nullptr);
}
}
ssp = GET_NEXT( ssp );
}
// fix up the overall ship subsys status
ship_recalc_subsys_strength(shipp);
}
/**
* Calculates approximate time in seconds it would take to rearm and repair object.
*/
float ship_calculate_rearm_duration( object *objp )
{
ship* sp;
ship_info* sip;
ship_subsys* ssp;
ship_weapon* swp;
weapon_info* wip;
float shield_rep_time = 0;
float subsys_rep_time = 0;
float hull_rep_time = 0;
float prim_rearm_time = 0;
float sec_rearm_time = 0;
float max_hull_repair;
float max_subsys_repair;
int i;
int num_reloads;
bool found_first_empty;
Assert(objp->type == OBJ_SHIP);
sp = &Ships[objp->instance];
swp = &sp->weapons;
sip = &Ship_info[sp->ship_info_index];
//find out time to repair shields
float max_shields = shield_get_max_strength(objp);
if(sip->sup_shield_repair_rate > 0.0f && max_shields > 0.0f)
shield_rep_time = (max_shields - shield_get_strength(objp)) / (max_shields * sip->sup_shield_repair_rate);
max_hull_repair = sp->ship_max_hull_strength * (The_mission.support_ships.max_hull_repair_val * 0.01f);
if ((The_mission.flags[Mission::Mission_Flags::Support_repairs_hull]) && (max_hull_repair > objp->hull_strength) && (sip->sup_hull_repair_rate > 0.0f))
{
hull_rep_time = (max_hull_repair - objp->hull_strength) / (sp->ship_max_hull_strength * sip->sup_hull_repair_rate);
}
//caluclate subsystem repair time
ssp = GET_FIRST(&sp->subsys_list);
while (ssp != END_OF_LIST(&sp->subsys_list))
{
max_subsys_repair = ssp->max_hits * (The_mission.support_ships.max_subsys_repair_val * 0.01f);
if ((max_subsys_repair > ssp->current_hits) && (sip->sup_subsys_repair_rate > 0.0f))
{
subsys_rep_time += (max_subsys_repair - ssp->current_hits) / (ssp->max_hits * sip->sup_subsys_repair_rate);
}
ssp = GET_NEXT( ssp );
}
//now do the primary rearm time
found_first_empty = false;
for (i = 0; i < swp->num_primary_banks; i++)
{
wip = &Weapon_info[swp->primary_bank_weapons[i]];
if (wip->wi_flags[Weapon::Info_Flags::Ballistic])
{
//check how many full reloads we need
num_reloads = (swp->primary_bank_start_ammo[i] - swp->primary_bank_ammo[i]) / wip->reloaded_per_batch;
//take into account a fractional reload
if ((swp->primary_bank_start_ammo[i] - swp->primary_bank_ammo[i]) % wip->reloaded_per_batch != 0)
{
num_reloads++;
}
//don't factor in the time it takes for the first reload, since that is loaded instantly
num_reloads--;
if (num_reloads < 0) continue;
if (!found_first_empty && (swp->primary_bank_start_ammo[i] - swp->primary_bank_ammo[i]))
{
found_first_empty = true;
prim_rearm_time += gamesnd_get_max_duration(gamesnd_get_game_sound(GameSounds::MISSILE_START_LOAD)) / 1000.0f;
}
prim_rearm_time += num_reloads * wip->rearm_rate;
}
}
//and on to secondary rearm time
found_first_empty = false;
for (i = 0; i < swp->num_secondary_banks; i++)
{
wip = &Weapon_info[swp->secondary_bank_weapons[i]];
if (wip->wi_flags[Weapon::Info_Flags::SecondaryNoAmmo])
continue;
//check how many full reloads we need
num_reloads = (swp->secondary_bank_start_ammo[i] - swp->secondary_bank_ammo[i]) / wip->reloaded_per_batch;
//take into account a fractional reload
if ((swp->secondary_bank_start_ammo[i] - swp->secondary_bank_ammo[i]) % wip->reloaded_per_batch != 0)
{
num_reloads++;
}
//don't factor in the time it takes for the first reload, since that is loaded instantly
num_reloads--;
if (num_reloads < 0) continue;
if (!found_first_empty && (swp->secondary_bank_start_ammo[i] - swp->secondary_bank_ammo[i]))
{
found_first_empty = true;
sec_rearm_time += gamesnd_get_max_duration(gamesnd_get_game_sound(GameSounds::MISSILE_START_LOAD)) / 1000.0f;
}
sec_rearm_time += num_reloads * wip->rearm_rate;
}
//sum them up and you've got an estimated rearm time.
//add 1.2 to compensate for release delay
return shield_rep_time + hull_rep_time + subsys_rep_time + prim_rearm_time + sec_rearm_time + 1.2f;
}
// ==================================================================================
// ship_do_rearm_frame()
//
// function to rearm a ship. This function gets called from the ai code ai_do_rearm_frame (or
// some function of a similar name). Returns 1 when ship is fully repaired and rearmed, 0 otherwise
//
int ship_do_rearm_frame( object *objp, float frametime )
{
int i, banks_full, primary_banks_full, subsys_type, last_ballistic_idx = -1;
float shield_str, max_shield_str = 0.0f, repair_delta, repair_allocated, max_hull_repair = 0, max_subsys_repair;
ship *shipp;
ship_weapon *swp;
ship_info *sip;
ship_subsys *ssp;
ai_info *aip;
shipp = &Ships[objp->instance];
swp = &shipp->weapons;
sip = &Ship_info[shipp->ship_info_index];
aip = &Ai_info[shipp->ai_index];
// AL 10-31-97: Add missing primary weapons to the ship. This is required since designers
// want to have ships that start with no primaries, but can get them through
// rearm/repair
// plieblang - skip this if the ai profile flag is set
if ( swp->num_primary_banks < sip->num_primary_banks && !aip->ai_profile_flags[AI::Profile_Flags::Support_dont_add_primaries] ) {
for ( i = swp->num_primary_banks; i < sip->num_primary_banks; i++ ) {
swp->primary_bank_weapons[i] = sip->primary_bank_weapons[i];
}
swp->num_primary_banks = sip->num_primary_banks;
}
// AL 12-30-97: Repair broken warp drive
if ( shipp->flags[Ship_Flags::Warp_broken] ) {
// TODO: maybe do something here like informing player warp is fixed?
// like this? -- Goober5000
HUD_sourced_printf(HUD_SOURCE_HIDDEN, "%s", XSTR( "Subspace drive repaired.", 1635));
shipp->flags.remove(Ship_Flags::Warp_broken);
}
// AL 1-16-98: Replenish countermeasures
shipp->cmeasure_count = sip->cmeasure_max;
// Do shield repair here
if ( !(objp->flags[Object::Object_Flags::No_shields]) )
{
shield_str = shield_get_strength(objp);
max_shield_str = shield_get_max_strength(objp);
if ( shield_str < (max_shield_str) ) {
if ( objp == Player_obj ) {
player_maybe_start_repair_sound();
}
shield_str += shipp->ship_max_shield_strength * frametime * sip->sup_shield_repair_rate; // repair rate is unaffected by $Max Shield Recharge
if ( shield_str > max_shield_str ) {
shield_str = max_shield_str;
}
shield_set_strength(objp, shield_str);
}
}
// Repair the ship integrity (subsystems + hull). This works by applying the repair points
// to the subsystems. Ships integrity is stored in objp->hull_strength, so that always is
// incremented by repair_allocated
// AL 11-24-97: remove increase to hull integrity
// Comments removed by PhReAk; Note that this is toggled on/off with a mission flag
if (The_mission.flags[Mission::Mission_Flags::Support_repairs_hull])
{
//Figure out how much of the ship's hull we can repair
//Don't "reverse-repair" the hull if it's already above the max repair threshold
max_hull_repair = shipp->ship_max_hull_strength * (The_mission.support_ships.max_hull_repair_val * 0.01f);
if (objp->hull_strength < max_hull_repair)
{
objp->hull_strength += shipp->ship_max_hull_strength * frametime * sip->sup_hull_repair_rate;
if (objp->hull_strength > max_hull_repair)
objp->hull_strength = max_hull_repair;
}
}
// figure out repairs for subsystems
repair_allocated = shipp->ship_max_hull_strength * frametime * sip->sup_subsys_repair_rate;
// check the subsystems of the ship.
bool subsys_all_ok = true;
ssp = GET_FIRST(&shipp->subsys_list);
while ( ssp != END_OF_LIST( &shipp->subsys_list ) ) {
//Figure out how much we *can* repair the current subsystem -C
max_subsys_repair = ssp->max_hits * (The_mission.support_ships.max_subsys_repair_val * 0.01f);
if ( ssp->current_hits < max_subsys_repair && repair_allocated > 0 ) {
subsys_all_ok = false;
subsys_type = ssp->system_info->type;
if ( objp == Player_obj ) {
player_maybe_start_repair_sound();
}
repair_delta = max_subsys_repair - ssp->current_hits;
if ( repair_delta > repair_allocated ) {
repair_delta = repair_allocated;
}
repair_allocated -= repair_delta;
Assert(repair_allocated >= 0.0f);
// add repair to current strength of single subsystem
ssp->current_hits += repair_delta;
if ( ssp->current_hits > max_subsys_repair ) {
ssp->current_hits = max_subsys_repair;
}
// add repair to aggregate strength of subsystems of that type
if (!(ssp->flags[Ship::Subsystem_Flags::No_aggregate])) {
shipp->subsys_info[subsys_type].aggregate_current_hits += repair_delta;
if ( shipp->subsys_info[subsys_type].aggregate_current_hits > shipp->subsys_info[subsys_type].aggregate_max_hits )
shipp->subsys_info[subsys_type].aggregate_current_hits = shipp->subsys_info[subsys_type].aggregate_max_hits;
}
// check to see if this subsystem was totally non functional before -- if so, then
// reset the flags
if ( (ssp->system_info->type == SUBSYSTEM_ENGINE) && (shipp->flags[Ship_Flags::Disabled]) ) {
shipp->flags.remove(Ship_Flags::Disabled);
ship_reset_disabled_physics(objp, shipp->ship_info_index);
}
break;
}
ssp = GET_NEXT( ssp );
}
// now deal with rearming the player. All secondary weapons have a certain rate at which
// they can be rearmed. We can rearm multiple banks at once.
banks_full = 0;
primary_banks_full = 0;
if ( subsys_all_ok )
{
for (i = 0; i < swp->num_secondary_banks; i++ )
{
// Actual loading of missiles is preceded by a sound effect which is the missile
// loading equipment moving into place
if ( aip->rearm_first_missile == TRUE )
{
swp->secondary_bank_rearm_time[i] = timestamp((int)gamesnd_get_max_duration(gamesnd_get_game_sound(GameSounds::MISSILE_START_LOAD)));
}
if ( swp->secondary_bank_ammo[i] < swp->secondary_bank_start_ammo[i] )
{
float rearm_time;
if ( objp == Player_obj )
{
hud_gauge_popup_start(HUD_WEAPONS_GAUGE);
}
if ( timestamp_elapsed(swp->secondary_bank_rearm_time[i]) )
{
rearm_time = Weapon_info[swp->secondary_bank_weapons[i]].rearm_rate;
swp->secondary_bank_rearm_time[i] = timestamp((int)(rearm_time * 1000.0f));
snd_play_3d( gamesnd_get_game_sound(GameSounds::MISSILE_LOAD), &objp->pos, &View_position );
if (objp == Player_obj)
joy_ff_play_reload_effect();
swp->secondary_bank_ammo[i] += Weapon_info[swp->secondary_bank_weapons[i]].reloaded_per_batch;
if ( swp->secondary_bank_ammo[i] > swp->secondary_bank_start_ammo[i] )
{
swp->secondary_bank_ammo[i] = swp->secondary_bank_start_ammo[i];
}
}
else
{
}
}
else
{
banks_full++;
}
if ((aip->rearm_first_missile == TRUE) && (i == swp->num_secondary_banks - 1))
{
if ((banks_full != swp->num_secondary_banks))
snd_play_3d( gamesnd_get_game_sound(GameSounds::MISSILE_START_LOAD), &objp->pos, &View_position );
aip->rearm_first_missile = FALSE;
}
} // end for
// rearm ballistic primaries - Goober5000
if ( aip->rearm_first_ballistic_primary == TRUE)
{
for (i = 0; i < swp->num_primary_banks; i++ )
{
if ( Weapon_info[swp->primary_bank_weapons[i]].wi_flags[Weapon::Info_Flags::Ballistic] )
last_ballistic_idx = i;
}
}
for (i = 0; i < swp->num_primary_banks; i++ )
{
if (Weapon_info[swp->primary_bank_weapons[i]].wi_flags[Weapon::Info_Flags::Ballistic])
{
// Actual loading of bullets is preceded by a sound effect which is the bullet
// loading equipment moving into place
if ( aip->rearm_first_ballistic_primary == TRUE )
{
// Goober5000
gamesnd_id sound_index;
if (gamesnd_game_sound_try_load(GameSounds::BALLISTIC_START_LOAD))
sound_index = GameSounds::BALLISTIC_START_LOAD;
else
sound_index = GameSounds::MISSILE_START_LOAD;
if (sound_index.isValid())
swp->primary_bank_rearm_time[i] = timestamp((int)gamesnd_get_max_duration(gamesnd_get_game_sound(sound_index)));
else
swp->primary_bank_rearm_time[i] = timestamp(0);
}
if ( swp->primary_bank_ammo[i] < swp->primary_bank_start_ammo[i] )
{
float rearm_time;
if ( objp == Player_obj )
{
hud_gauge_popup_start(HUD_WEAPONS_GAUGE);
}
if ( timestamp_elapsed(swp->primary_bank_rearm_time[i]) )
{
rearm_time = Weapon_info[swp->primary_bank_weapons[i]].rearm_rate;
swp->primary_bank_rearm_time[i] = timestamp( (int)(rearm_time * 1000.f) );
// Goober5000
gamesnd_id sound_index;
if (gamesnd_game_sound_try_load(GameSounds::BALLISTIC_LOAD))
sound_index = GameSounds::BALLISTIC_LOAD;
else
sound_index = GameSounds::MISSILE_LOAD;
if (sound_index.isValid())
snd_play_3d( gamesnd_get_game_sound(sound_index), &objp->pos, &View_position );
swp->primary_bank_ammo[i] += Weapon_info[swp->primary_bank_weapons[i]].reloaded_per_batch;
if ( swp->primary_bank_ammo[i] > swp->primary_bank_start_ammo[i] )
{
swp->primary_bank_ammo[i] = swp->primary_bank_start_ammo[i];
}
}
}
else
{
primary_banks_full++;
}
}
// if the bank is not a ballistic
else
{
primary_banks_full++;
}
if ((aip->rearm_first_ballistic_primary == TRUE) && (i == last_ballistic_idx))
{
if (primary_banks_full != swp->num_primary_banks)
{
// Goober5000
gamesnd_id sound_index;
if (gamesnd_game_sound_try_load(GameSounds::BALLISTIC_START_LOAD))
sound_index = GameSounds::BALLISTIC_START_LOAD;
else
sound_index = GameSounds::MISSILE_START_LOAD;
if (sound_index.isValid())
snd_play_3d( gamesnd_get_game_sound(sound_index), &objp->pos, &View_position );
}
aip->rearm_first_ballistic_primary = FALSE;
}
} // end for
} // end if (subsys_all_ok)
if ( banks_full == swp->num_secondary_banks )
{
aip->rearm_first_missile = TRUE;
}
if ( primary_banks_full == swp->num_primary_banks )
{
aip->rearm_first_ballistic_primary = TRUE;
}
int shields_full = false;
if ( (objp->flags[Object::Object_Flags::No_shields]) ) {
shields_full = true;
} else {
if ( shield_get_strength(objp) >= max_shield_str )
shields_full = true;
if (sip->sup_shield_repair_rate == 0.0f)
shields_full = true;
}
bool hull_ok = false;
if (!(The_mission.flags[Mission::Mission_Flags::Support_repairs_hull])) {
hull_ok = true;
} else {
if (objp->hull_strength >= max_hull_repair)
hull_ok = true;
if (sip->sup_hull_repair_rate == 0.0f)
hull_ok = true;
}
// return 1 if at end of subsystem list, hull damage at 0, and shields full and all secondary banks full.
if ( (subsys_all_ok && shields_full && (The_mission.flags[Mission::Mission_Flags::Support_repairs_hull]) && hull_ok ) || (subsys_all_ok && shields_full && !(The_mission.flags[Mission::Mission_Flags::Support_repairs_hull]) ) )
{
if ( objp == Player_obj ) {
player_stop_repair_sound();
}
if (!aip->rearm_release_delay)
aip->rearm_release_delay = timestamp(1200);
// check both primary and secondary banks are full
if ( (banks_full == swp->num_secondary_banks) && (primary_banks_full == swp->num_primary_banks) )
{
if ( timestamp_elapsed(aip->rearm_release_delay) )
return 1;
}
else
{
aip->rearm_release_delay = timestamp(1200);
}
}
if (objp == Player_obj)
Player_rearm_eta -= frametime;
return 0;
}
// Goober5000 - modified the logic to clarify the various states
// function which is used to find a repair ship to repair requester_obj. the way repair ships will work is:
// if no ships in the mission at all, return 0
// if a ship can immediately satisfy a repair request, return 1 and fill in the pointer
// if no ships can satisfy a request, but we haven't reached either the concurrent or cumulative limit, return 2
// if no ships can satisfy a request, and we've reached the limits, but a request can be queued, return 3 and fill in the pointer
// if no ships can satisfy a request, we've reached the limits, and we can't queue anything, we're out of luck -- return 4
int ship_find_repair_ship( object *requester_obj, object **ship_we_found )
{
object *objp;
int num_support_ships = 0;
float min_dist = -1.0f;
object *nearest_support_ship = NULL;
float min_time_till_available = -1.0f;
object *soonest_available_support_ship = NULL;
Assertion(requester_obj->type == OBJ_SHIP, "requester_obj not a ship. Has type of %08x", requester_obj->type);
Assertion((requester_obj->instance >= 0) && (requester_obj->instance < MAX_SHIPS),
"requester_obj does not have a valid pointer to a ship. Pointer is %d, which is smaller than 0 or bigger than %d",
requester_obj->instance, MAX_SHIPS);
ship *requester_ship = &Ships[requester_obj->instance];
for ( objp = GET_FIRST(&obj_used_list); objp !=END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) )
{
if ((objp->type == OBJ_SHIP) && !(objp->flags[Object::Object_Flags::Should_be_dead]))
{
ship *shipp;
ship_info *sip;
float dist;
Assertion((objp->instance >= 0) && (objp->instance < MAX_SHIPS),
"objp does not have a valid pointer to a ship. Pointer is %d, which is smaller than 0 or bigger than %d",
objp->instance, MAX_SHIPS);
shipp = &Ships[objp->instance];
if ( shipp->team != requester_ship->team ) {
continue;
}
Assertion((shipp->ship_info_index >= 0) && (shipp->ship_info_index < ship_info_size()),
"Ship '%s' does not have a valid pointer to a ship class. Pointer is %d, which is smaller than 0 or bigger than %d",
shipp->ship_name, shipp->ship_info_index, ship_info_size());
sip = &Ship_info[shipp->ship_info_index];
if ( !(sip->flags[Ship::Info_Flags::Support]) ) {
continue;
}
// tally how many support ships actually exist
num_support_ships++;
// don't deal with dying or departing support ships
if ( shipp->is_dying_or_departing() ) {
continue;
}
// Ship has been ordered to warpout but has not had a chance to process the order.
Assertion( (shipp->ai_index >= 0) && (shipp->ai_index < MAX_AI_INFO),
"Ship '%s' doesn't have a valid ai pointer. Pointer is %d, which is smaller than 0 or larger than %d",
shipp->ship_name, shipp->ai_index, MAX_AI_INFO);
ai_info* aip = &(Ai_info[shipp->ai_index]);
if ( ai_find_goal_index( aip->goals, AI_GOAL_WARP ) != -1 ) {
continue;
}
dist = vm_vec_dist_quick(&objp->pos, &requester_obj->pos);
if (aip->ai_flags[AI::AI_Flags::Repairing, AI::AI_Flags::Awaiting_repair, AI::AI_Flags::Being_repaired])
{
// support ship is already busy, track the one that will be
// done soonest by estimating how many seconds it will take for the support ship
// to reach the requester.
// The estimate is calculated by calculating how many seconds it will take the
// support ship to travel from its current location to the requester at max velocity
// We assume that every leg of the support ships journey will take the amount of time
// for the support ship to fly from its current location to the requester. This is
// a bit hacky but it penalizes further away support ships, so a futher support ship
// will only be called if the closer ones are really busy. This is just a craps shoot
// anyway because everything is moving around.
float howlong = 0;
for( int i = 0; i < MAX_AI_GOALS; i++ ) {
if ( aip->goals[i].ai_mode == AI_GOAL_REARM_REPAIR ) {
howlong += dist * objp->phys_info.max_vel.xyz.z;
}
}
if ( min_time_till_available < 0.0f || howlong < min_time_till_available ) {
min_time_till_available = howlong;
soonest_available_support_ship = objp;
}
}
else
{
// support ship not already busy, find the closest
if ( min_dist < 0.0f || dist < min_dist )
{
min_dist = dist;
nearest_support_ship = objp;
}
}
}
}
// no ships present?
// (be advised we may have an Arriving_support_ship in this case)
if (num_support_ships == 0) {
return 0;
}
// ship available?
else if (nearest_support_ship != NULL) {
// the nearest non-busy support ship is to service request
if (ship_we_found != NULL)
*ship_we_found = nearest_support_ship;
return 1;
}
// no ships available; are we below the limits? (can we bring another ship in? -- and btw an Arriving_support_ship counts as being able to bring one in)
else if ((num_support_ships < The_mission.support_ships.max_concurrent_ships)
&& ( (Arriving_support_ship == NULL && The_mission.support_ships.tally < The_mission.support_ships.max_support_ships)
|| (Arriving_support_ship != NULL && The_mission.support_ships.tally <= The_mission.support_ships.max_support_ships) ))
{
// We are allowed more support ships in the mission; request another ship
// to service this request.
return 2;
}
// we're at the limit, but maybe a ship will become available
else if (soonest_available_support_ship != NULL) {
// found more support ships than should be in mission, so I can't ask for more,
// instead I will give the player the ship that will be done soonest
if (ship_we_found != NULL)
*ship_we_found = soonest_available_support_ship;
return 3;
}
// none of the above; we're out of luck
else {
return 4;
}
}
/**
* Called in game_shutdown() to free malloced memory
*
* NOTE: do not call this function. It is only called from ::game_shutdown()
*/
void ship_close()
{
int i;
for (i=0; i<MAX_SHIPS; i++ ) {
ship *shipp = &Ships[i];
if (shipp->ship_replacement_textures != NULL) {
vm_free(shipp->ship_replacement_textures);
shipp->ship_replacement_textures = NULL;
}
if(shipp->warpin_effect != NULL)
delete shipp->warpin_effect;
shipp->warpin_effect = NULL;
if(shipp->warpout_effect != NULL)
delete shipp->warpout_effect;
shipp->warpout_effect = NULL;
}
// free this too! -- Goober5000
ship_clear_subsystems();
// free info from parsed table data
Ship_info.clear();
for (i = 0; i < (int)Ship_types.size(); i++) {
Ship_types[i].ai_actively_pursues.clear();
Ship_types[i].ai_actively_pursues_temp.clear();
}
Ship_types.clear();
}
/**
* Assign object-linked sound to a particular ship
*/
void ship_assign_sound(ship *sp)
{
ship_info *sip;
object *objp;
ship_subsys *moveup;
bool has_engine = false;
Assert( sp->objnum >= 0 );
if(sp->objnum < 0){
return;
}
objp = &Objects[sp->objnum];
sip = &Ship_info[sp->ship_info_index];
// Do subsystem sounds
moveup = GET_FIRST(&sp->subsys_list);
while(moveup != END_OF_LIST(&sp->subsys_list)) {
if (!strnicmp(moveup->system_info->name, "engine", 6)) {
has_engine = true;
}
// Check for any engine sounds
if(strstr(moveup->system_info->name, "enginelarge")){
obj_snd_assign(sp->objnum, GameSounds::ENGINE_LOOP_LARGE, &moveup->system_info->pnt);
} else if(strstr(moveup->system_info->name, "enginehuge")){
obj_snd_assign(sp->objnum, GameSounds::ENGINE_LOOP_HUGE, &moveup->system_info->pnt);
}
//Do any normal subsystem sounds
if(moveup->current_hits > 0.0f)
{
if(moveup->system_info->alive_snd.isValid())
{
obj_snd_assign(sp->objnum, moveup->system_info->alive_snd, &moveup->system_info->pnt, OS_SUBSYS_ALIVE, moveup);
moveup->subsys_snd_flags.set(Ship::Subsys_Sound_Flags::Alive);
}
if(moveup->system_info->turret_base_rotation_snd.isValid())
{
obj_snd_assign(sp->objnum, moveup->system_info->turret_base_rotation_snd, &moveup->system_info->pnt, OS_TURRET_BASE_ROTATION, moveup);
moveup->subsys_snd_flags.set(Ship::Subsys_Sound_Flags::Turret_rotation);
}
if(moveup->system_info->turret_gun_rotation_snd.isValid())
{
obj_snd_assign(sp->objnum, moveup->system_info->turret_gun_rotation_snd, &moveup->system_info->pnt, OS_TURRET_GUN_ROTATION, moveup);
moveup->subsys_snd_flags.set(Ship::Subsys_Sound_Flags::Turret_rotation);
}
if((moveup->system_info->rotation_snd.isValid()) && (moveup->flags[Ship::Subsystem_Flags::Rotates]))
{
obj_snd_assign(sp->objnum, moveup->system_info->rotation_snd, &moveup->system_info->pnt, OS_SUBSYS_ROTATION, moveup);
moveup->subsys_snd_flags.set(Ship::Subsys_Sound_Flags::Rotate);
}
}
else
{
if(moveup->system_info->dead_snd.isValid())
{
obj_snd_assign(sp->objnum, moveup->system_info->dead_snd, &moveup->system_info->pnt, OS_SUBSYS_DEAD, moveup);
moveup->subsys_snd_flags.set(Ship::Subsys_Sound_Flags::Dead);
}
}
// next
moveup = GET_NEXT(moveup);
}
if (sip->engine_snd.isValid()) {
vec3d engine_pos;
// Only put the engine sound near the back of the ship if it has an engine. Otherwise put it in the center.
if (has_engine) {
vm_vec_copy_scale(&engine_pos, &objp->orient.vec.fvec, -objp->radius / 2.0f);
} else {
engine_pos = vmd_zero_vector;
}
obj_snd_assign(sp->objnum, sip->engine_snd, &engine_pos, OS_MAIN);
}
}
/**
* Assign object-linked sounds to all ships currently in the obj_used_list
*/
void ship_assign_sound_all()
{
object *objp;
size_t idx;
int has_sounds;
if ( !Sound_enabled )
return;
for ( objp = GET_FIRST(&obj_used_list); objp !=END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) ) {
if ( objp->type == OBJ_SHIP && Player_obj != objp) {
has_sounds = 0;
// check to make sure this guy hasn't got sounds already assigned to him
for(idx=0; idx<objp->objsnd_num.size(); idx++){
if(objp->objsnd_num[idx] != -1){
// skip
has_sounds = 1;
break;
}
}
// actually assign the sound
if(!has_sounds){
ship_assign_sound(&Ships[objp->instance]);
}
}
}
}
/**
* Debug console function to set the shield for the player ship
*/
DCF(set_shield,"Change player ship shield strength")
{
float value;
if (dc_optional_string_either("help", "--help")) {
dc_printf ("Usage: set_shield [num]\n");
dc_printf ("[num] -- shield percentage 0.0 -> 1.0 of max\n");
return;
}
if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) {
dc_printf( "Shields are currently %.2f", shield_get_strength(Player_obj) );
return;
}
dc_stuff_float(&value);
CLAMP(value, 0.0f, 1.0f);
shield_set_strength(Player_obj, value * shield_get_max_strength(Player_obj));
dc_printf("Shields set to %.2f\n", shield_get_strength(Player_obj) );
}
/**
* Debug console function to set the hull for the player ship
*/
DCF(set_hull, "Change player ship hull strength")
{
float value;
if (dc_optional_string_either("help", "--help")) {
dc_printf ("Usage: set_hull [num]\n");
dc_printf ("[num] -- hull percentage 0.0 -> 1.0 of max\n");
return;
}
if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) {
dc_printf( "Hull is currently %.2f", Player_obj->hull_strength );
return;
}
dc_stuff_float(&value);
CLAMP(value, 0.0f, 1.0f);
Player_obj->hull_strength = value * Player_ship->ship_max_hull_strength;
dc_printf("Hull set to %.2f\n", Player_obj->hull_strength );
}
/**
* Debug console function to set the strength of a particular subsystem
*/
//XSTR:OFF
DCF(set_subsys, "Set the strength of a particular subsystem on player ship" )
{
SCP_string arg;
int subsystem = SUBSYSTEM_NONE;
float val_f;
if (dc_optional_string_either("help", "--help")) {
dc_printf( "Usage: set_subsys <type> [--status] <strength>\n");
dc_printf("<type> is any of the following:\n");
dc_printf("\tweapons\n");
dc_printf("\tengine\n");
dc_printf("\tsensors\n");
dc_printf("\tcommunication\n");
dc_printf("\tnavigation\n");
dc_printf("\tradar\n\n");
dc_printf("[--status] will display status of that subsystem\n\n");
dc_printf("<strength> is any value between 0 and 1.0\n");
return;
}
dc_stuff_string_white(arg);
if (arg == "weapons") {
subsystem = SUBSYSTEM_WEAPONS;
} else if (arg == "engine") {
subsystem = SUBSYSTEM_ENGINE;
} else if (arg == "sensors") {
subsystem = SUBSYSTEM_SENSORS;
} else if (arg == "communication") {
subsystem = SUBSYSTEM_COMMUNICATION;
} else if (arg == "navigation") {
subsystem = SUBSYSTEM_NAVIGATION;
} else if (arg == "radar") {
subsystem = SUBSYSTEM_RADAR;
} else if ((arg == "status") || (arg == "--status") || (arg == "?") || (arg == "--?")) {
dc_printf("Error: Must specify a subsystem.\n");
return;
} else {
dc_printf("Error: Unknown argument '%s'\n", arg.c_str());
return;
}
if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) {
dc_printf("Subsystem '%s' is at %f strength\n", arg.c_str(), ship_get_subsystem_strength(Player_ship, subsystem));
} else {
// Set the subsystem strength
dc_stuff_float(&val_f);
CLAMP(val_f, 0.0, 1.0);
ship_set_subsystem_strength( Player_ship, subsystem, val_f );
if (subsystem == SUBSYSTEM_ENGINE) {
// If subsystem is an engine, set/clear the disabled flag
Player_ship->flags.set(Ship_Flags::Disabled, (val_f < ENGINE_MIN_STR));
}
}
}
//XSTR:ON
// console function to toggle whether auto-repair for subsystems is active
#ifndef NDEBUG
DCF_BOOL( auto_repair, Ship_auto_repair )
#endif
// two functions to keep track of counting ships of particular types. Maybe we should be rolling this
// thing into the stats section?? The first function adds a ship of a particular type to the overall
// count of ships of that type (called from MissionParse.cpp). The second function adds to the kill total
// of ships of a particular type. Note that we use the ship_info flags structure member to determine
// what is happening.
//WMC - ALERT!!!!!!!!!!!
//These two functions did something weird with fighters/bombers. I don't
//think that not doing this will break anything, but it might.
//If it does, get me. OR someone smart.
//G5K - Someone smart to the rescue! Fixed the functions so they don't
//accidentally overwrite all the information.
void ship_clear_ship_type_counts()
{
// resize if we need to
Ship_type_counts.resize(Ship_types.size());
// clear all the stats
for (size_t i = 0; i < Ship_type_counts.size(); i++)
{
Ship_type_counts[i].killed = 0;
Ship_type_counts[i].total = 0;
}
}
void ship_add_ship_type_count( int ship_info_index, int num )
{
int type = ship_class_query_general_type(ship_info_index);
//Ship has no type or something
if(type < 0) {
return;
}
//Add it
Ship_type_counts[type].total += num;
}
static void ship_add_ship_type_kill_count( int ship_info_index )
{
int type = ship_class_query_general_type(ship_info_index);
//Ship has no type or something
if(type < 0) {
return;
}
//Add it if we are actually in gameplay
if (Ship_type_counts.size() > static_cast<size_t>(type))
Ship_type_counts[type].killed++;
}
int ship_query_general_type(int ship)
{
return ship_query_general_type(&Ships[ship]);
}
int ship_query_general_type(ship *shipp)
{
return ship_class_query_general_type(shipp->ship_info_index);
}
int ship_class_query_general_type(int ship_class)
{
//This is quick
return Ship_info[ship_class].class_type;
}
/**
* Returns true
*/
int ship_docking_valid(int /*docker*/, int /*dockee*/)
{
// Goober5000
// So many people have asked for this function to be extended that it's making less
// and less sense to keep it around. We should probably just let any ship type
// dock with any other ship type and assume the mission designer is smart enough not to
// mess things up.
return 1;
}
// function to return a random ship in a starting player wing. Returns -1 if a suitable
// one cannot be found
// input: max_dist => OPTIONAL PARAMETER (default value 0.0f) max range ship can be from player
// input: persona => OPTIONAL PARAMETER (default to -1) which persona to get
int ship_get_random_player_wing_ship( int flags, float max_dist, int persona_index, int get_first, int multi_team )
{
const int MAX_SIZE = MAX_SHIPS_PER_WING * MAX_SQUADRON_WINGS;
int i, j, ship_index, count;
int slist[MAX_SIZE], which_one;
// iterate through starting wings of player. Add ship indices of ships which meet
// given criteria
count = 0;
for (i = 0; i < Num_wings; i++ ) {
if (count >= MAX_SIZE)
break;
int wingnum = -1;
// multi-team?
if(multi_team >= 0){
if( i == TVT_wings[multi_team] ) {
wingnum = i;
} else {
continue;
}
} else {
// first check for a player starting wing
for ( j = 0; j < MAX_STARTING_WINGS; j++ ) {
if ( i == Starting_wings[j] ) {
wingnum = i;
break;
}
}
// if not found, then check all squad wings (Goober5000)
if ( wingnum == -1 ) {
for ( j = 0; j < MAX_SQUADRON_WINGS; j++ ) {
if ( i == Squadron_wings[j] ) {
wingnum = i;
break;
}
}
}
if ( wingnum == -1 ){
continue;
}
}
for ( j = 0; j < Wings[wingnum].current_count; j++ ) {
if (count >= MAX_SIZE)
break;
ship_index = Wings[wingnum].ship_index[j];
Assert( ship_index != -1 );
if ( Ships[ship_index].flags[Ship_Flags::Dying] ) {
continue;
}
// see if ship meets our criteria
if ( (flags == SHIP_GET_NO_PLAYERS || flags == SHIP_GET_UNSILENCED) && (Objects[Ships[ship_index].objnum].flags[Object::Object_Flags::Player_ship]) ){
continue;
}
if (flags == SHIP_GET_UNSILENCED) {
if (Ships[ship_index].flags[Ship_Flags::No_builtin_messages])
continue;
if (The_mission.ai_profile->flags[AI::Profile_Flags::Check_comms_for_non_player_ships] && hud_communications_state(&Ships[ship_index]) <= COMM_DAMAGED)
continue;
}
// don't process ships on a different team
if(multi_team < 0){
if ( Player_ship->team != Ships[ship_index].team ){
continue;
}
}
// see if ship is within max_dist units
if ( (max_dist > 1.0f) && (multi_team < 0) ) {
float dist;
dist = vm_vec_dist_quick(&Objects[Ships[ship_index].objnum].pos, &Player_obj->pos);
if ( dist > max_dist ) {
continue;
}
}
// if we should be checking persona's, then don't add ships that don't have the proper persona
if ( persona_index != -1 ) {
if ( Ships[ship_index].persona_index != persona_index ){
continue;
}
}
// return the first ship with correct persona
if (get_first) {
return ship_index;
}
slist[count] = ship_index;
count++;
}
}
if ( count == 0 ){
return -1;
}
// now get a random one from the list
which_one = Random::next(count);
ship_index = slist[which_one];
Assert ( Ships[ship_index].objnum != -1 );
return ship_index;
}
// like above function, but returns a random ship in the given wing -- no restrictions
// input: max_dist => OPTIONAL PARAMETER (default value 0.0f) max range ship can be from player
int ship_get_random_ship_in_wing(int wingnum, int flags, float max_dist, int get_first)
{
int i, ship_index, slist[MAX_SHIPS_PER_WING], count, which_one;
count = 0;
for ( i = 0; i < Wings[wingnum].current_count; i++ ) {
ship_index = Wings[wingnum].ship_index[i];
Assert( ship_index != -1 );
if ( Ships[ship_index].flags[Ship_Flags::Dying] ) {
continue;
}
// see if ship meets our criterea
if ( (flags == SHIP_GET_NO_PLAYERS || flags == SHIP_GET_UNSILENCED) && (Objects[Ships[ship_index].objnum].flags[Object::Object_Flags::Player_ship]) )
continue;
if ( (flags == SHIP_GET_UNSILENCED) && (Ships[ship_index].flags[Ship_Flags::No_builtin_messages]) )
{
continue;
}
// see if ship is within max_dist units
if ( max_dist > 0 ) {
float dist;
dist = vm_vec_dist_quick(&Objects[Ships[ship_index].objnum].pos, &Player_obj->pos);
if ( dist > max_dist ) {
continue;
}
}
// return the first ship in wing
if (get_first) {
return ship_index;
}
slist[count] = ship_index;
count++;
}
if ( count == 0 ) {
return -1;
}
// now get a random one from the list
which_one = Random::next(count);
ship_index = slist[which_one];
Assert ( Ships[ship_index].objnum != -1 );
return ship_index;
}
// this function returns a random index into the Ship array of a ship of the given team
// cargo containers are not counted as ships for the purposes of this function. Why???
// because now it is only used for getting a random ship for a message and cargo containers
// can't send mesages. This function is an example of kind of bad coding :-(
// input: max_dist => OPTIONAL PARAMETER (default value 0.0f) max range ship can be from player
int ship_get_random_team_ship(int team_mask, int flags, float max_dist )
{
int num, which_one;
object *objp, *obj_list[MAX_SHIPS];
// for any allied, go through the ships list and find all of the ships on that team
num = 0;
for ( objp = GET_FIRST(&obj_used_list); objp != END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) ) {
if ( objp->type != OBJ_SHIP )
continue;
// series of conditionals one per line for easy reading
// don't process ships on wrong team
// don't process cargo's or navbuoys
// don't process player ships if flags are set
if (!iff_matches_mask(Ships[objp->instance].team, team_mask))
continue;
else if ( !Ship_info[Ships[objp->instance].ship_info_index].is_flyable() )
continue;
else if ( (flags == SHIP_GET_NO_PLAYERS) && (objp->flags[Object::Object_Flags::Player_ship]) )
continue;
else if ( (flags == SHIP_GET_ONLY_PLAYERS) && !(objp->flags[Object::Object_Flags::Player_ship]) )
continue;
if ( Ships[objp->instance].flags[Ship_Flags::Dying] ) {
continue;
}
// see if ship is within max_dist units
if ( max_dist > 0 ) {
float dist;
dist = vm_vec_dist_quick(&objp->pos, &Player_obj->pos);
if ( dist > max_dist ) {
continue;
}
}
obj_list[num] = objp;
num++;
}
if ( num == 0 )
return -1;
which_one = Random::next(num);
objp = obj_list[which_one];
Assert ( objp->instance != -1 );
return objp->instance;
}
// -----------------------------------------------------------------------
// ship_secondary_bank_has_ammo()
//
// check if currently selected secondary bank has ammo
//
// input: shipnum => index into Ships[] array for ship to check
//
int ship_secondary_bank_has_ammo(int shipnum)
{
ship_weapon *swp;
Assert(shipnum >= 0 && shipnum < MAX_SHIPS);
swp = &Ships[shipnum].weapons;
int current_bank = swp->current_secondary_bank;
if (current_bank == -1 )
return 0;
Assert(current_bank >= 0 && current_bank < MAX_SHIP_SECONDARY_BANKS );
if (swp->secondary_bank_weapons[current_bank] < 0)
return 0;
if (Weapon_info[swp->secondary_bank_weapons[current_bank]].wi_flags[Weapon::Info_Flags::SecondaryNoAmmo])
return 1;
if ( swp->secondary_bank_ammo[current_bank] <= 0 )
return 0;
return 1;
}
// see if there is enough engine power to allow the ship to warp
// returns 1 if ship is able to warp, otherwise return 0
int ship_engine_ok_to_warp(ship *sp)
{
// disabled ships can't warp
if (sp->flags[Ship_Flags::Disabled])
return 0;
if (sp->flags[Ship_Flags::Warp_broken] || sp->flags[Ship_Flags::Warp_never])
return 0;
float engine_strength = ship_get_subsystem_strength(sp, SUBSYSTEM_ENGINE);
// if at 0% strength, can't warp
if (engine_strength <= 0.0f)
return 0;
// player ships playing above Very Easy can't warp when below a threshold
if ((sp == Player_ship) && (Game_skill_level > 0) && (engine_strength < SHIP_MIN_ENGINES_TO_WARP))
return 0;
// otherwise, warp is allowed
return 1;
}
// Goober5000
// see if there is enough navigation power to allow the ship to warp
// returns 1 if ship is able to warp, otherwise return 0
int ship_navigation_ok_to_warp(ship *sp)
{
// if not using the special flag, warp is always allowed
if (!(The_mission.ai_profile->flags[AI::Profile_Flags::Navigation_subsys_governs_warp]))
return 1;
float navigation_strength = ship_get_subsystem_strength(sp, SUBSYSTEM_NAVIGATION);
// if at 0% strength, can't warp
if (navigation_strength <= 0.0f)
return 0;
// player ships playing above Very Easy can't warp when below a threshold
if ((sp == Player_ship) && (Game_skill_level > 0) && (navigation_strength < SHIP_MIN_NAV_TO_WARP))
return 0;
// otherwise, warp is allowed
return 1;
}
// wookieejedi
// checks both the warp flags and ship_engine_ok_to_warp() and ship_navigation_ok_to_warp() --wookieejedi
// returns true if ship is able to warp, otherwise return false
bool ship_can_warp_full_check(ship* sp)
{
if (!(sp->cannot_warp_flags()) && ship_navigation_ok_to_warp(sp) && ship_engine_ok_to_warp(sp)) {
// there are no warp flags that are preventing us from warping and the nav and engines are good
// thus warp is allowed
return true;
} else {
// warp is not allowed since one of those conditions was not met
return false;
}
}
// wookieejedi
// check to see if a ship can depart via bay
// takes into account if the ship is in a wing
// returns true if the ship has a bay departure and the mothership is present, false otherwise
bool ship_can_bay_depart(ship* sp)
{
// if this ship belongs to a wing, then use the wing departure information
int departure_location;
int departure_anchor;
int departure_path_mask;
if (sp->wingnum >= 0)
{
wing *wingp = &Wings[sp->wingnum];
departure_location = wingp->departure_location;
departure_anchor = wingp->departure_anchor;
departure_path_mask = wingp->departure_path_mask;
} else {
departure_location = sp->departure_location;
departure_anchor = sp->departure_anchor;
departure_path_mask = sp->departure_path_mask;
}
if ( departure_location == DEPART_AT_DOCK_BAY )
{
Assertion( departure_anchor >= 0, "Ship %s must have a valid departure anchor", sp->ship_name );
int anchor_shipnum = ship_name_lookup(Parse_names[departure_anchor]);
if (anchor_shipnum >= 0 && ship_useful_for_departure(anchor_shipnum, departure_path_mask)) {
// can bay depart at this time
return true;
}
}
// cannot bay depart at this time
return false;
}
// Calculate the normal vector from a subsystem position and its first path point
// input: sp => pointer to ship that is parent of subsystem
// ss => pointer to subsystem of interest
// norm => output parameter... vector from subsys to first path point
//
// exit: 0 => a valid vector was placed in norm
// !0 => an path normal could not be calculated
//
int ship_return_subsys_path_normal(ship *shipp, ship_subsys *ss, vec3d *gsubpos, vec3d *norm)
{
if ( ss->system_info->path_num >= 0 ) {
polymodel *pm = NULL;
model_path *mp;
vec3d *path_point;
vec3d gpath_point;
pm = model_get(Ship_info[shipp->ship_info_index].model_num);
Assert( pm != NULL );
// possibly a bad model?
Assertion(ss->system_info->path_num <= pm->n_paths, "Too many paths in '%s'! Max is %i and the requested path was %i for subsystem '%s'!\n", pm->filename, pm->n_paths, ss->system_info->path_num, ss->system_info->subobj_name);
if (ss->system_info->path_num > pm->n_paths)
return 1;
mp = &pm->paths[ss->system_info->path_num];
if ( mp->nverts >= 2 ) {
path_point = &mp->verts[0].pos;
// get path point in world coords
vm_vec_unrotate(&gpath_point, path_point, &Objects[shipp->objnum].orient);
vm_vec_add2(&gpath_point, &Objects[shipp->objnum].pos);
// get unit vector pointing from subsys pos to first path point
vm_vec_normalized_dir(norm, &gpath_point, gsubpos);
return 0;
}
}
return 1;
}
// Determine if the subsystem can be viewed from eye_pos. The method is to check where the
// vector from eye_pos to the subsystem hits the ship. If distance from the hit position and
// the center of the subsystem is within a range (currently the subsystem radius) it is considered
// in view (return true). If not in view, return false.
//
// input: objp => object that is the ship with the subsystem on it
// subsys => pointer to the subsystem of interest
// eye_pos => world coord for the eye looking at the subsystem
// subsys_pos => world coord for the center of the subsystem of interest
// do_facing_check => OPTIONAL PARAMETER (default value is 1), do a dot product check to see if subsystem fvec is facing
// towards the eye position
// dot_out => OPTIONAL PARAMETER, output parameter, will return dot between subsys fvec and subsys_to_eye_vec
// (only filled in if do_facing_check is true)
// vec_out => OPTIONAL PARAMETER, vector from eye_pos to absolute subsys_pos. (only filled in if do_facing_check is true)
int ship_subsystem_in_sight(object* objp, ship_subsys* subsys, vec3d *eye_pos, vec3d* subsys_pos, int do_facing_check, float *dot_out, vec3d *vec_out)
{
float dist, dot;
mc_info mc;
vec3d terminus, eye_to_pos, subsys_fvec, subsys_to_eye_vec;
if (objp->type != OBJ_SHIP)
return 0;
// See if we are at least facing the subsystem
if ( do_facing_check ) {
if ( ship_return_subsys_path_normal(&Ships[objp->instance], subsys, subsys_pos, &subsys_fvec) ) {
// non-zero return value means that we couldn't generate a normal from path info... so use inaccurate method
vm_vec_normalized_dir(&subsys_fvec, subsys_pos, &objp->pos);
}
vm_vec_normalized_dir(&subsys_to_eye_vec, eye_pos, subsys_pos);
dot = vm_vec_dot(&subsys_fvec, &subsys_to_eye_vec);
if ( dot_out ) {
*dot_out = dot;
}
if (vec_out) {
*vec_out = subsys_to_eye_vec;
vm_vec_negate(vec_out);
}
if ( dot < 0 )
return 0;
}
// See if ray from eye to subsystem actually hits close enough to the subsystem position
vm_vec_normalized_dir(&eye_to_pos, subsys_pos, eye_pos);
vm_vec_scale_add(&terminus, eye_pos, &eye_to_pos, 100000.0f);
mc_info_init(&mc);
mc.model_instance_num = Ships[objp->instance].model_instance_num;
mc.model_num = Ship_info[Ships[objp->instance].ship_info_index].model_num; // Fill in the model to check
mc.orient = &objp->orient; // The object's orientation
mc.pos = &objp->pos; // The object's position
mc.p0 = eye_pos; // Point 1 of ray to check
mc.p1 = &terminus; // Point 2 of ray to check
mc.flags = MC_CHECK_MODEL;
model_collide(&mc);
if ( !mc.num_hits ) {
return 0;
}
// determine if hitpos is close enough to subsystem
dist = vm_vec_dist(&mc.hit_point_world, subsys_pos);
if ( dist <= subsys->system_info->radius ) {
return 1;
}
return 0;
}
/**
* Find a subsystem matching 'type' inside the ship, and that is not destroyed.
* @return If cannot find one, return NULL.
*/
ship_subsys *ship_return_next_subsys(ship *shipp, int type, vec3d *attacker_pos)
{
ship_subsys *ssp;
Assert ( type >= 0 && type < SUBSYSTEM_MAX );
// If aggregate total is 0, that means no subsystem is alive of that type
if ( shipp->subsys_info[type].aggregate_max_hits <= 0.0f )
return NULL;
// loop through all the subsystems, if we find a match that has some strength, return it
ssp = ship_get_best_subsys_to_attack(shipp, type, attacker_pos);
return ssp;
}
// Returns the closest subsystem of specified type that is in line of sight.
// Returns null if all subsystems of that type are destroyed or none is in sight.
ship_subsys *ship_get_closest_subsys_in_sight(ship *sp, int subsys_type, vec3d *attacker_pos)
{
Assert ( subsys_type >= 0 && subsys_type < SUBSYSTEM_MAX );
// If aggregate total is 0, that means no subsystem is alive of that type
if ( sp->subsys_info[subsys_type].aggregate_max_hits <= 0.0f )
return NULL;
ship_subsys *closest_in_sight_subsys;
ship_subsys *ss;
vec3d gsubpos;
float closest_dist;
float ss_dist;
closest_in_sight_subsys = NULL;
closest_dist = FLT_MAX;
for (ss = GET_FIRST(&sp->subsys_list); ss != END_OF_LIST(&sp->subsys_list); ss = GET_NEXT(ss) ) {
if ( (ss->system_info->type == subsys_type) && (ss->current_hits > 0) ) {
// get world pos of subsystem
vm_vec_unrotate(&gsubpos, &ss->system_info->pnt, &Objects[sp->objnum].orient);
vm_vec_add2(&gsubpos, &Objects[sp->objnum].pos);
if ( ship_subsystem_in_sight(&Objects[sp->objnum], ss, attacker_pos, &gsubpos) ) {
ss_dist = vm_vec_dist_squared(attacker_pos, &gsubpos);
if ( ss_dist < closest_dist ) {
closest_dist = ss_dist;
closest_in_sight_subsys = ss;
}
}
}
}
return closest_in_sight_subsys;
}
char *ship_subsys_get_name(ship_subsys *ss)
{
if( ss->sub_name[0] != '\0' )
return ss->sub_name;
else
return ss->system_info->name;
}
bool ship_subsys_has_instance_name(ship_subsys *ss)
{
if( ss->sub_name[0] != '\0' )
return true;
else
return false;
}
void ship_subsys_set_name(ship_subsys* ss, const char* n_name) { strncpy(ss->sub_name, n_name, NAME_LENGTH - 1); }
/**
* Return the shield strength of the specified quadrant on hit_objp
*
* @param hit_objp object pointer to ship getting hit
* @param quadrant_num shield quadrant that was hit
* @return strength of shields in the quadrant that was hit as a percentage, between 0 and 1.0
*/
float ship_quadrant_shield_strength(object *hit_objp, int quadrant_num)
{
float max_quadrant;
// If ship doesn't have shield mesh, then return
if ( hit_objp->flags[Object::Object_Flags::No_shields] ) {
return 0.0f;
}
// If shields weren't hit, return 0
if ( quadrant_num < 0 )
return 0.0f;
max_quadrant = shield_get_max_quad(hit_objp);
if ( max_quadrant <= 0 ) {
return 0.0f;
}
Assertion(quadrant_num < hit_objp->n_quadrants, "ship_quadrant_shield_strength() called with a quadrant of %d on a ship with %d quadrants; get a coder!\n", quadrant_num, hit_objp->n_quadrants);
if(hit_objp->shield_quadrant[quadrant_num] > max_quadrant)
mprintf(("Warning: \"%s\" has shield quadrant strength of %f out of %f\n",
Ships[hit_objp->instance].ship_name, hit_objp->shield_quadrant[quadrant_num], max_quadrant));
return hit_objp->shield_quadrant[quadrant_num]/max_quadrant;
}
// Determine if a ship is threatened by any dumbfire projectiles (laser or missile)
// input: sp => pointer to ship that might be threatened
// exit: 0 => no dumbfire threats
// 1 => at least one dumbfire threat
//
// NOTE: Currently this function is only called periodically from the HUD code for the
// player ship.
int ship_dumbfire_threat(ship *sp)
{
if ( (Game_mode & GM_MULTIPLAYER) && (Net_player->flags & NETINFO_FLAG_OBSERVER) ) {
return 0;
}
if (ai_endangered_by_weapon(&Ai_info[sp->ai_index]) > 0) {
return 1;
}
return 0;
}
// Return !0 if there is a missile in the air homing on shipp
static int ship_has_homing_missile_locked(ship *shipp)
{
object *locked_objp, *A;
weapon *wp;
weapon_info *wip;
missile_obj *mo;
Assert(shipp->objnum >= 0 && shipp->objnum < MAX_OBJECTS);
locked_objp = &Objects[shipp->objnum];
// check for currently locked missiles (highest precedence)
for ( mo = GET_NEXT(&Missile_obj_list); mo != END_OF_LIST(&Missile_obj_list); mo = GET_NEXT(mo) ) {
Assert(mo->objnum >= 0 && mo->objnum < MAX_OBJECTS);
A = &Objects[mo->objnum];
if (A->type != OBJ_WEAPON)
continue;
Assert((A->instance >= 0) && (A->instance < MAX_WEAPONS));
wp = &Weapons[A->instance];
wip = &Weapon_info[wp->weapon_info_index];
if ( wip->subtype != WP_MISSILE )
continue;
if ( !(wip->is_homing() ) )
continue;
if (wp->homing_object == locked_objp) {
return 1;
}
} // end for
return 0;
}
// Return !0 if there is some ship attempting to lock onto shipp
static int ship_is_getting_locked(ship *shipp)
{
ship_obj *so;
object *objp;
ai_info *aip;
for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) {
objp = &Objects[so->objnum];
aip = &Ai_info[Ships[objp->instance].ai_index];
if ( aip->target_objnum == shipp->objnum ) {
if ( aip->aspect_locked_time > 0.1f ) {
float dist, wep_range;
dist = vm_vec_dist_quick(&objp->pos, &Objects[shipp->objnum].pos);
wep_range = ship_get_secondary_weapon_range(&Ships[objp->instance]);
if ( wep_range > dist ) {
nprintf(("Alan","AI ship is seeking lock\n"));
return 1;
}
}
}
}
return 0;
}
// Determine if a ship is threatened by attempted lock or actual lock
// input: sp => pointer to ship that might be threatened
// exit: 0 => no lock threats of any kind
// 1 => at least one attempting lock (no actual locks)
// 2 => at least one lock (possible other attempting locks)
//
// NOTE: Currently this function is only called periodically from the HUD code for the
// player ship.
int ship_lock_threat(ship *sp)
{
if ( ship_has_homing_missile_locked(sp) ) {
return 2;
}
if ( ship_is_getting_locked(sp) ) {
return 1;
}
return 0;
}
// converts a bitmask, such as 0x08, into the bit number this would be (3 in this case)
// NOTE: Should move file to something like Math_utils.
int bitmask_2_bitnum(int num)
{
int i;
for (i=0; i<32; i++)
if (num & (1 << i))
return i;
return -1;
}
static const char* ship_get_ai_target_display_name(int goal, const char* name)
{
switch (goal) {
// These goals refer to ships so we need to retrieve their display name
case AI_GOAL_FORM_ON_WING:
case AI_GOAL_CHASE:
case AI_GOAL_DOCK:
case AI_GOAL_UNDOCK:
case AI_GOAL_GUARD:
case AI_GOAL_DISABLE_SHIP:
case AI_GOAL_DISARM_SHIP:
case AI_GOAL_EVADE_SHIP:
case AI_GOAL_REARM_REPAIR:
case AI_GOAL_FLY_TO_SHIP:
case AI_GOAL_DESTROY_SUBSYSTEM: {
auto ship = ship_name_lookup(name);
if (ship < 0) {
return name;
}
return Ships[ship].get_display_name();
}
// These goals need no special handling
case AI_GOAL_CHASE_WING:
case AI_GOAL_CHASE_SHIP_CLASS:
case AI_GOAL_GUARD_WING:
case AI_GOAL_WAYPOINTS:
case AI_GOAL_WAYPOINTS_ONCE:
default:
return name;
}
}
// Get a text description of a ships orders.
//
// input: outbuf => buffer to hold orders string
// sp => ship pointer to extract orders from
//
// exit: NULL => printable orders are not applicable
// non-NULL => pointer to string that was passed in originally
//
// This function is called from HUD code to get a text description
// of what a ship's orders are. Feel free to use this function if
// it suits your needs for something.
//
SCP_string ship_return_orders(ship* sp)
{
ai_info* aip;
ai_goal* aigp;
Assert(sp->ai_index >= 0);
aip = &Ai_info[sp->ai_index];
// The active goal is always in the first element of aip->goals[]
aigp = &aip->goals[0];
if (aigp->ai_mode < 0)
return SCP_string();
auto order_text = Ai_goal_text(aigp->ai_mode);
if (order_text == nullptr)
return SCP_string();
SCP_string outbuf = order_text;
const char *target_name;
if (aigp->target_name) {
target_name = ship_get_ai_target_display_name(aigp->ai_mode, aigp->target_name);
} else {
target_name = "";
}
switch (aigp->ai_mode) {
case AI_GOAL_FORM_ON_WING:
case AI_GOAL_GUARD_WING:
case AI_GOAL_CHASE_WING:
if (aigp->target_name) {
outbuf += target_name;
outbuf += XSTR(" wing", 494);
} else {
outbuf += XSTR("no orders", 495);
}
break;
case AI_GOAL_CHASE_SHIP_CLASS:
if (aigp->target_name) {
outbuf += XSTR("any ", -1);
outbuf += target_name;
} else {
outbuf += XSTR("no orders", 495);
}
break;
case AI_GOAL_CHASE:
case AI_GOAL_DOCK:
case AI_GOAL_UNDOCK:
case AI_GOAL_GUARD:
case AI_GOAL_DISABLE_SHIP:
case AI_GOAL_DISARM_SHIP:
case AI_GOAL_EVADE_SHIP:
case AI_GOAL_REARM_REPAIR:
case AI_GOAL_FLY_TO_SHIP:
if (aigp->target_name) {
outbuf += target_name;
} else {
outbuf += XSTR("no orders", 495);
}
break;
case AI_GOAL_DESTROY_SUBSYSTEM: {
if (aip->targeted_subsys != nullptr) {
char subsys_name[NAME_LENGTH];
strcpy_s(subsys_name, aip->targeted_subsys->system_info->subobj_name);
hud_targetbox_truncate_subsys_name(subsys_name);
sprintf(outbuf, XSTR("atk %s %s", 496), target_name, subsys_name);
} else {
outbuf += XSTR("no orders", 495);
}
break;
}
case AI_GOAL_WAYPOINTS:
case AI_GOAL_WAYPOINTS_ONCE:
// don't do anything, all info is in order_text
break;
default:
return SCP_string();
}
return outbuf;
}
// return the amount of time until ship reaches its goal (in MM:SS format)
// input: outbuf => buffer to hold orders string
// sp => ship pointer to extract orders from
//
// exit: NULL => printable orders are not applicable
// non-NULL => pointer to string that was passed in originally
//
// This function is called from HUD code to get a text description
// of what a ship's orders are. Feel free to use this function if
// it suits your needs for something.
char *ship_return_time_to_goal(char *outbuf, ship *sp)
{
int minutes, seconds;
int time = ship_return_seconds_to_goal(sp);
if ( time >= 0 ) {
minutes = time/60;
seconds = time%60;
if ( minutes > 99 ) {
minutes = 99;
seconds = 99;
}
sprintf(outbuf, NOX("%02d:%02d"), minutes, seconds);
} else if ( time == -1 ) {
strcpy( outbuf, XSTR( "Unknown", 497) );
} else {
// we don't want to display anything on the HUD
return nullptr;
}
return outbuf;
}
// Returns the estimated time, in seconds, of the given ship to reach its goal
//
// @param[in] sp Pointer to ship to check
//
// @returns Time of the ship to reach its goal, or
// @returns 0, if it has met its goal, or
// @returns -1, if the ship is not moving or can not move, or
// @returns -2, if the ship does not have a valid goal
//
// @note tcrayford: split out of ship_return_time_to_goal
int ship_return_seconds_to_goal(ship *sp)
{
ai_info *aip;
int time;
float dist = 0.0f;
object *objp;
float min_speed, max_speed;
objp = &Objects[sp->objnum];
aip = &Ai_info[sp->ai_index];
min_speed = objp->phys_info.speed;
// Goober5000 - handle cap
if (aip->waypoint_speed_cap > 0)
max_speed = MIN(sp->current_max_speed, aip->waypoint_speed_cap);
else
max_speed = sp->current_max_speed;
if ( aip->mode == AIM_WAYPOINTS ) {
// Is traveling a waypoint path
min_speed = 0.9f * max_speed;
if (aip->wp_list != NULL) {
Assert(aip->wp_index != INVALID_WAYPOINT_POSITION);
dist += vm_vec_dist_quick(&objp->pos, aip->wp_list->get_waypoints()[aip->wp_index].get_pos());
SCP_vector<waypoint>::iterator ii;
vec3d *prev_vec = NULL;
for (ii = (aip->wp_list->get_waypoints().begin() + aip->wp_index); ii != aip->wp_list->get_waypoints().end(); ++ii) {
if (prev_vec != NULL) {
dist += vm_vec_dist_quick(ii->get_pos(), prev_vec);
}
prev_vec = ii->get_pos();
}
}
if ( dist < 1.0f) {
// Already there
time = 0;
} else if ( (Objects[sp->objnum].phys_info.speed <= 0) || (max_speed <= 0.0f) ) {
// Is not moving or can not move
time = -1;
} else {
// On the way!
float speed;
speed = objp->phys_info.speed;
if (speed < min_speed)
speed = min_speed;
time = fl2i(dist/speed);
}
} else if ( (aip->mode == AIM_DOCK) && (aip->submode < AIS_DOCK_4) ) {
// Is traveling a docking path
time = hud_get_dock_time( objp );
} else {
// Not a waypoint or docking goal
time = -2;
}
return time;
}
/* Karajorma - V decided not to use this function so I've commented it out so it isn't confused with code
+that is actually in use. Someone might want to get it working using AI_Profiles at some point so I didn't
+simply delete it.
// Called to check if any AI ships might reveal the cargo of any cargo containers.
//
// This is called once a frame, but a global timer 'Ship_cargo_check_timer' will limit this
// function to being called every SHIP_CARGO_CHECK_INTERVAL ms. I think that should be sufficient.
//
// NOTE: This function uses CARGO_REVEAL_DISTANCE from the HUD code... which is a multiple of
// the ship radius that is used to determine when cargo is detected. AI ships do not
// have to have the ship targeted to reveal cargo. The player is ignored in this function.
#define SHIP_CARGO_CHECK_INTERVAL 1000
void ship_check_cargo_all()
{
object *cargo_objp;
ship_obj *cargo_so, *ship_so;
ship *cargo_sp, *ship_sp;
float dist_squared, limit_squared;
// I don't want to do this check every frame, so I made a global timer to limit check to
// every SHIP_CARGO_CHECK_INTERVAL ms.
if ( !timestamp_elapsed(Ship_cargo_check_timer) ) {
return;
} else {
Ship_cargo_check_timer = timestamp(SHIP_CARGO_CHECK_INTERVAL);
}
// Check all friendly fighter/bombers against all non-friendly cargo containers that don't have
// cargo revealed
// for now just locate a capital ship on the same team:
cargo_so = GET_FIRST(&Ship_obj_list);
while(cargo_so != END_OF_LIST(&Ship_obj_list)){
cargo_sp = &Ships[Objects[cargo_so->objnum].instance];
if ( (Ship_info[cargo_sp->ship_info_index].flags[Ship::Info_Flags::Cargo]) && (cargo_sp->team != Player_ship->team) ) {
// If the cargo is revealed, continue on to next hostile cargo
if ( cargo_sp->flags[Ship::Ship_Flags::Cargo_revealed] ) {
goto next_cargo;
}
// check against friendly fighter/bombers + cruiser/freighter/transport
// IDEA: could cull down to fighter/bomber if we want this to run a bit quicker
for ( ship_so=GET_FIRST(&Ship_obj_list); ship_so != END_OF_LIST(&Ship_obj_list); ship_so=GET_NEXT(ship_so) )
{
ship_sp = &Ships[Objects[ship_so->objnum].instance];
// only consider friendly ships
if (ship_sp->team != Player_ship->team) {
continue;
}
// ignore the player
if ( ship_so->objnum == OBJ_INDEX(Player_obj) ) {
continue;
}
// if this ship is a small or big ship
if ( Ship_info[ship_sp->ship_info_index].flags & (SIF_SMALL_SHIP|SIF_BIG_SHIP) ) {
cargo_objp = &Objects[cargo_sp->objnum];
// use square of distance, faster than getting real distance (which will use sqrt)
dist_squared = vm_vec_dist_squared(&cargo_objp->pos, &Objects[ship_sp->objnum].pos);
limit_squared = (cargo_objp->radius+CARGO_RADIUS_DELTA)*(cargo_objp->radius+CARGO_RADIUS_DELTA);
if ( dist_squared <= MAX(limit_squared, CARGO_REVEAL_MIN_DIST*CARGO_REVEAL_MIN_DIST) ) {
ship_do_cargo_revealed( cargo_sp );
break; // break out of for loop, move on to next hostile cargo
}
}
} // end for
}
next_cargo:
cargo_so = GET_NEXT(cargo_so);
} // end while
}
*/
// Maybe warn player about this attacking ship. This is called once per frame, and the
// information about the closest attacking ship comes for free, since this function is called
// from HUD code which has already determined the closest enemy attacker and the distance.
//
// input: enemy_sp => ship pointer to the TEAM_ENEMY ship attacking the player
// dist => the distance of the enemy to the player
//
// NOTE: there are no filters on enemy_sp, so it could be any ship type
//
#define PLAYER_CHECK_WARN_INTERVAL 300 // how often we check for warnings
#define PLAYER_MIN_WARN_DIST 100 // minimum distance attacking ship can be from player and still allow warning
#define PLAYER_MAX_WARN_DIST 1000 // maximum distance attacking ship can be from plyaer and still allow warning
void ship_maybe_warn_player(ship *enemy_sp, float dist)
{
float fdot; //, rdot, udot;
vec3d vec_to_target;
int msg_type; //, on_right;
// First check if the player has reached the maximum number of warnings for a mission
if ((Builtin_messages[MESSAGE_CHECK_6].max_count > -1) && ( Player->warn_count >= Builtin_messages[MESSAGE_CHECK_6].max_count )) {
return;
}
// Check if enough time has elapsed since last warning, if not - leave
if ( !timestamp_elapsed(Player->allow_warn_timestamp) ) {
return;
}
// Check to see if check timer has elapsed. Necessary, since we don't want to check each frame
if ( !timestamp_elapsed(Player->check_warn_timestamp ) ) {
return;
}
Player->check_warn_timestamp = timestamp(PLAYER_CHECK_WARN_INTERVAL);
// only allow warnings if within a certain distance range
if ( dist < PLAYER_MIN_WARN_DIST || dist > PLAYER_MAX_WARN_DIST ) {
return;
}
// only warn if a fighter or bomber is attacking the player
if ( !(Ship_info[enemy_sp->ship_info_index].is_small_ship()) ) {
return;
}
// get vector from player to target
vm_vec_normalized_dir(&vec_to_target, &Objects[enemy_sp->objnum].pos, &Eye_position);
// ensure that enemy fighter is oriented towards player
fdot = vm_vec_dot(&Objects[enemy_sp->objnum].orient.vec.fvec, &vec_to_target);
if ( fdot > -0.7 ) {
return;
}
fdot = vm_vec_dot(&Player_obj->orient.vec.fvec, &vec_to_target);
msg_type = -1;
// check if attacking ship is on six. return if not far enough behind player.
if ( fdot > -0.7 )
return;
msg_type = MESSAGE_CHECK_6;
if ( msg_type != -1 ) {
int ship_index;
// multiplayer tvt - this is client side.
if(MULTI_TEAM && (Net_player != NULL)){
ship_index = ship_get_random_player_wing_ship( SHIP_GET_UNSILENCED, 0.0f, -1, 0, Net_player->p_info.team );
} else {
ship_index = ship_get_random_player_wing_ship( SHIP_GET_UNSILENCED );
}
if ( ship_index >= 0 ) {
// multiplayer - make sure I just send to myself
if(Game_mode & GM_MULTIPLAYER){
message_send_builtin_to_player(msg_type, &Ships[ship_index], MESSAGE_PRIORITY_HIGH, MESSAGE_TIME_IMMEDIATE, 0, 0, MY_NET_PLAYER_NUM, -1);
} else {
message_send_builtin_to_player(msg_type, &Ships[ship_index], MESSAGE_PRIORITY_HIGH, MESSAGE_TIME_IMMEDIATE, 0, 0, -1, -1);
}
Player->allow_warn_timestamp = timestamp(Builtin_messages[MESSAGE_CHECK_6].min_delay);
Player->warn_count++;
}
}
}
// player has just killed a ship, maybe offer send a 'good job' message
void ship_maybe_praise_player(ship *deader_sp)
{
if (Random::flip_coin()) {
return;
}
// First check if the player has reached the maximum number of praises for a mission
if ((Builtin_messages[MESSAGE_PRAISE].max_count > -1) && (Player->praise_count >= Builtin_messages[MESSAGE_PRAISE].max_count )) {
return;
}
// Check if enough time has elapsed since last praise, if not - leave
if ( !timestamp_elapsed(Player->allow_praise_timestamp) ) {
return;
}
// make sure player is not a traitor
if (Player_ship->team == Iff_traitor) {
return;
}
// only praise if killing an enemy!
if ( deader_sp->team == Player_ship->team ) {
return;
}
// don't praise the destruction of navbuoys, cargo or other non-flyable ship types
if ((Ship_info[deader_sp->ship_info_index].class_type > 0) && !(Ship_types[Ship_info[deader_sp->ship_info_index].class_type].flags[Ship::Type_Info_Flags::Praise_destruction])) {
return;
}
// There is already a praise pending
if ( Player->praise_delay_timestamp ) {
return;
}
// We don't want to praise the player right away.. it is more realistic to wait a moment
Player->praise_delay_timestamp = timestamp_rand(1000, 2000);
}
void ship_maybe_praise_self(ship *deader_sp, ship *killer_sp)
{
int j;
bool wingman = false;
if ( (int)(frand()*100) > Builtin_messages[MESSAGE_PRAISE_SELF].occurrence_chance ) {
return;
}
if (Game_mode & GM_MULTIPLAYER) {
return;
}
if ((Builtin_messages[MESSAGE_PRAISE_SELF].max_count > -1) && (Player->praise_self_count >= Builtin_messages[MESSAGE_PRAISE_SELF].max_count)) {
return;
}
if (!timestamp_elapsed(Player->praise_self_timestamp)) {
return;
}
// only praise if killing an enemy so check they both attack each other!
if (!((iff_x_attacks_y(deader_sp->team, killer_sp->team)) && (iff_x_attacks_y(killer_sp->team, deader_sp->team ))) ) {
return;
}
// only send messages from the player's wingmen
if (killer_sp->wingnum == -1) {
return;
}
for ( j = 0; j < MAX_STARTING_WINGS; j++ ) {
if ( Starting_wings[j] == killer_sp->wingnum) {
wingman = true;
break;
}
}
if (!wingman) {
return;
}
// don't praise the destruction of navbuoys, cargo or other non-flyable ship types
if ( (Ship_info[deader_sp->ship_info_index].class_type > 0) && !(Ship_types[Ship_info[deader_sp->ship_info_index].class_type].flags[Ship::Type_Info_Flags::Praise_destruction]) ) {
return;
}
// ensure the ship isn't silenced
if ( killer_sp->flags[Ship_Flags::No_builtin_messages] ) {
return;
}
if (The_mission.ai_profile->flags[AI::Profile_Flags::Check_comms_for_non_player_ships] && hud_communications_state(killer_sp) != COMM_OK) {
return;
}
message_send_builtin_to_player(MESSAGE_PRAISE_SELF, killer_sp, MESSAGE_PRIORITY_HIGH, MESSAGE_TIME_SOON, 0, 0, -1, -1);
Player->praise_self_timestamp = timestamp(Builtin_messages[MESSAGE_PRAISE_SELF].min_delay);
Player->praise_self_count++;
}
// -----------------------------------------------------------------------------
static void awacs_maybe_ask_for_help(ship *sp, int multi_team_filter)
{
// Goober5000 - bail if not in main fs2 campaign
// (stupid coders... it's the FREDder's responsibility to add this message)
if (stricmp(Campaign.filename, "freespace2") != 0 || !(Game_mode & GM_CAMPAIGN_MODE))
return;
object *objp;
int message = -1;
objp = &Objects[sp->objnum];
if ( objp->hull_strength < ( (AWACS_HELP_HULL_LOW + 0.01f *(static_rand(OBJ_INDEX(objp)) & 5)) * sp->ship_max_hull_strength) ) {
// awacs ship below 25 + (0-4) %
if (!(sp->awacs_warning_flag[Ship::Awacs_Warning_Flags::Warn_25])) {
message = MESSAGE_AWACS_25;
sp->awacs_warning_flag.set(Ship::Awacs_Warning_Flags::Warn_25);
}
} else if ( objp->hull_strength < ( (AWACS_HELP_HULL_HI + 0.01f*(static_rand(OBJ_INDEX(objp)) & 5)) * sp->ship_max_hull_strength) ) {
// awacs ship below 75 + (0-4) %
if (!(sp->awacs_warning_flag[Ship::Awacs_Warning_Flags::Warn_75])) {
message = MESSAGE_AWACS_75;
sp->awacs_warning_flag.set(Ship::Awacs_Warning_Flags::Warn_75);
}
}
if (message >= 0) {
message_send_builtin_to_player(message, sp, MESSAGE_PRIORITY_HIGH, MESSAGE_TIME_IMMEDIATE, 0, 0, -1, multi_team_filter);
Player->allow_ask_help_timestamp = timestamp(Builtin_messages[MESSAGE_HELP].min_delay);
Player->ask_help_count++;
}
}
// -----------------------------------------------------------------------------
void ship_maybe_ask_for_help(ship *sp)
{
object *objp;
int multi_team_filter = -1;
ship_info* sip = &Ship_info[sp->ship_info_index];
// First check if the player has reached the maximum number of ask_help's for a mission
if ((Builtin_messages[MESSAGE_HELP].max_count > -1) && (Player->ask_help_count >= Builtin_messages[MESSAGE_HELP].max_count))
return;
// Check if enough time has elapsed since last help request, if not - leave
if (!timestamp_elapsed(Player->allow_ask_help_timestamp))
return;
// make sure player is on their team and not a traitor
if ((Player_ship->team != sp->team) || (Player_ship->team == Iff_traitor))
return;
objp = &Objects[sp->objnum];
// don't let the player ask for help!
if (objp->flags[Object::Object_Flags::Player_ship])
return;
// determine team filter if TvT
if(MULTI_TEAM)
multi_team_filter = sp->team;
// handle awacs ship as a special case
if (Ship_info[sp->ship_info_index].flags[Ship::Info_Flags::Has_awacs])
{
awacs_maybe_ask_for_help(sp, multi_team_filter);
return;
}
// for now, only have wingman ships request help
if (!(sp->flags[Ship_Flags::From_player_wing]))
return;
// first check if hull is at a critical level
if (objp->hull_strength < sip->ask_help_hull_percent * sp->ship_max_hull_strength)
goto play_ask_help;
// check if shields are near critical level
if (objp->flags[Object::Object_Flags::No_shields])
return; // no shields on ship, no don't check shield levels
if (shield_get_strength(objp) > (sip->ask_help_shield_percent * shield_get_max_strength(objp)))
return;
play_ask_help:
if (!(Ship_info[sp->ship_info_index].is_fighter_bomber())) //If we're still here, only continue if we're a fighter or bomber.
return;
// only unsilenced ships should ask for help
if (sp->flags[Ship_Flags::No_builtin_messages])
return;
if (The_mission.ai_profile->flags[AI::Profile_Flags::Check_comms_for_non_player_ships] && hud_communications_state(&Ships[objp->instance]) <= COMM_DAMAGED)
return;
message_send_builtin_to_player(MESSAGE_HELP, sp, MESSAGE_PRIORITY_HIGH, MESSAGE_TIME_IMMEDIATE, 0, 0, -1, multi_team_filter);
Player->allow_ask_help_timestamp = timestamp(Builtin_messages[MESSAGE_HELP].min_delay);
// prevent overlap with death message
if (timestamp_until(Player->allow_scream_timestamp) < 15000)
Player->allow_scream_timestamp = timestamp(15000);
Player->ask_help_count++;
}
/**
* The player has just entered death roll, maybe have wingman mourn the loss of the player
*/
void ship_maybe_lament()
{
int ship_index;
// no. because in multiplayer, its funny
if (Game_mode & GM_MULTIPLAYER)
return;
if (Random::next(4) == 0)
{
ship_index = ship_get_random_player_wing_ship(SHIP_GET_UNSILENCED);
if (ship_index >= 0)
message_send_builtin_to_player(MESSAGE_PLAYER_DIED, &Ships[ship_index], MESSAGE_PRIORITY_HIGH, MESSAGE_TIME_IMMEDIATE, 0, 0, -1, -1);
}
}
/**
* Play a death scream for a ship
*/
void ship_scream(ship *sp)
{
int multi_team_filter = -1;
// bogus
if (sp == NULL)
return;
// multiplayer tvt
if (MULTI_TEAM)
multi_team_filter = sp->team;
// Bail if the ship is silenced
if (sp->flags[Ship_Flags::No_builtin_messages])
return;
if (The_mission.ai_profile->flags[AI::Profile_Flags::Check_comms_for_non_player_ships] && hud_communications_state(sp, true) <= COMM_DAMAGED)
return;
message_send_builtin_to_player(MESSAGE_WINGMAN_SCREAM, sp, MESSAGE_PRIORITY_HIGH, MESSAGE_TIME_IMMEDIATE, 0, 0, -1, multi_team_filter);
Player->allow_scream_timestamp = timestamp(Builtin_messages[MESSAGE_WINGMAN_SCREAM].min_delay);
Player->scream_count++;
sp->flags.set(Ship_Flags::Ship_has_screamed);
// prevent overlap with help messages
if (timestamp_until(Player->allow_ask_help_timestamp) < 15000)
Player->allow_ask_help_timestamp = timestamp(15000);
}
/**
* Ship has just died, maybe play a scream.
*/
void ship_maybe_scream(ship *sp)
{
// bail if screaming is disabled
if (sp->flags[Ship_Flags::No_death_scream])
return;
// if screaming is enabled, skip all checks
if (!(sp->flags[Ship_Flags::Always_death_scream]))
{
// only scream x% of the time
if ( (int)(frand()*100) > Builtin_messages[MESSAGE_WINGMAN_SCREAM].occurrence_chance ) {
return;
}
// check if enough time has elapsed since last scream; if not, leave
if (!timestamp_elapsed(Player->allow_scream_timestamp))
return;
// for WCSaga, only do a subset of the checks
if (!(The_mission.ai_profile->flags[AI::Profile_Flags::Perform_fewer_scream_checks]))
{
// bail if this ship isn't from the player wing
if (!(sp->flags[Ship_Flags::From_player_wing]))
return;
// first check if the player has reached the maximum number of screams for a mission
if ((Builtin_messages[MESSAGE_WINGMAN_SCREAM].max_count > -1) && (Player->scream_count >= Builtin_messages[MESSAGE_WINGMAN_SCREAM].max_count)) {
return;
}
// if on different teams (i.e. team v. team games in multiplayer), no scream
if (Player_ship->team != sp->team)
return;
}
}
ship_scream(sp);
}
// maybe tell player that we're running low on ammo
#define PLAYER_LOW_AMMO_MSG_INTERVAL 250000
#define PLAYER_REQUEST_REPAIR_MSG_INTERVAL 240000
#define PLAYER_MAX_LOW_AMMO_MSGS 5
/**
* This function is only for notifying the player that the ship's ammo is low, without necessarily requesting support (e.g. if support
* is disallowed for this mission). It is not called if support was successfully requested in maybe_request_support.
*/
void ship_maybe_tell_about_low_ammo(ship *sp)
{
weapon_info *wip;
int i;
ship_weapon *swp;
int multi_team_filter = -1;
// we don't want a ship complaining about low ammo after it has just complained about needing support
if (!timestamp_elapsed(Player->request_repair_timestamp))
return;
if (!timestamp_elapsed(Player->allow_ammo_timestamp))
return;
if (Player_ship->team == Iff_traitor)
return;
// Silent ships should remain just that
if (sp->flags[Ship_Flags::No_builtin_messages]) {
return;
}
if (The_mission.ai_profile->flags[AI::Profile_Flags::Check_comms_for_non_player_ships] && hud_communications_state(sp) <= COMM_DAMAGED)
return;
// don't mention low ammo if we're docked, for the same reason as in maybe_request_support
if (object_is_docked(&Objects[sp->objnum]))
return;
// for now, each ship can only complain about low ammo once a mission to stop it getting repetitive
if (sp->ammo_low_complaint_count) {
return;
}
if (Player->low_ammo_complaint_count >= PLAYER_MAX_LOW_AMMO_MSGS) {
return;
}
swp = &sp->weapons;
// stole the code for this from ship_maybe_tell_about_rearm()
for (i = 0; i < swp->num_primary_banks; i++)
{
wip = &Weapon_info[swp->primary_bank_weapons[i]];
if (wip->wi_flags[Weapon::Info_Flags::Ballistic])
{
if (swp->primary_bank_start_ammo[i] > 0)
{
if ((float)swp->primary_bank_ammo[i] / (float)swp->primary_bank_start_ammo[i] < 0.3f)
{
// multiplayer tvt
if(MULTI_TEAM) {
multi_team_filter = sp->team;
}
message_send_builtin_to_player(MESSAGE_PRIMARIES_LOW, sp, MESSAGE_PRIORITY_NORMAL, MESSAGE_TIME_SOON, 0, 0, -1, multi_team_filter);
Player->allow_ammo_timestamp = timestamp(PLAYER_LOW_AMMO_MSG_INTERVAL);
// better reset this one too
Player->request_repair_timestamp = timestamp(PLAYER_REQUEST_REPAIR_MSG_INTERVAL);
Player->low_ammo_complaint_count++;
sp->ammo_low_complaint_count++;
break;
}
}
}
}
}
/**
* Tell player that we've requested a support ship
*/
void ship_maybe_tell_about_rearm(ship *sp)
{
weapon_info *wip;
if (!timestamp_elapsed(Player->request_repair_timestamp))
return;
if (Player_ship->team == Iff_traitor)
return;
// Silent ships should remain just that
if (sp->flags[Ship_Flags::No_builtin_messages])
return;
if (The_mission.ai_profile->flags[AI::Profile_Flags::Check_comms_for_non_player_ships] && hud_communications_state(sp) <= COMM_DAMAGED)
return;
// AL 1-4-98: If ship integrity is low, tell player you want to get repaired. Otherwise, tell
// the player you want to get re-armed.
int message_type = -1;
int heavily_damaged = (get_hull_pct(&Objects[sp->objnum]) < 0.4);
if (heavily_damaged || (sp->flags[Ship_Flags::Disabled]))
{
message_type = MESSAGE_REPAIR_REQUEST;
}
else
{
int i;
ship_weapon *swp;
swp = &sp->weapons;
for (i = 0; i < swp->num_secondary_banks; i++)
{
if (swp->secondary_bank_start_ammo[i] > 0 && !Weapon_info[swp->secondary_bank_weapons[i]].wi_flags[Weapon::Info_Flags::SecondaryNoAmmo])
{
if ((float)swp->secondary_bank_ammo[i] / (float)swp->secondary_bank_start_ammo[i] < 0.5f)
{
message_type = MESSAGE_REARM_REQUEST;
break;
}
}
}
// also check ballistic primaries - Goober5000
for (i = 0; i < swp->num_primary_banks; i++)
{
wip = &Weapon_info[swp->primary_bank_weapons[i]];
if (wip->wi_flags[Weapon::Info_Flags::Ballistic])
{
if (swp->primary_bank_start_ammo[i] > 0)
{
if ((float)swp->primary_bank_ammo[i] / (float)swp->primary_bank_start_ammo[i] < 0.3f)
{
message_type = MESSAGE_REARM_PRIMARIES;
break;
}
}
}
}
}
int multi_team_filter = -1;
// multiplayer tvt
if(MULTI_TEAM)
multi_team_filter = sp->team;
if (message_type >= 0)
{
if (Random::flip_coin())
message_send_builtin_to_player(message_type, sp, MESSAGE_PRIORITY_NORMAL, MESSAGE_TIME_SOON, 0, 0, -1, multi_team_filter);
Player->request_repair_timestamp = timestamp(PLAYER_REQUEST_REPAIR_MSG_INTERVAL);
}
}
// The current primary weapon or link status for a ship has changed.. notify clients if multiplayer
//
// input: sp => pointer to ship that modified primaries
void ship_primary_changed(ship *sp)
{
int i;
ship_weapon *swp;
swp = &sp->weapons;
if (sp->flags[Ship_Flags::Primary_linked]) {
// if we are linked now find any body who is down and flip them up
for (i = 0; i < MAX_SHIP_PRIMARY_BANKS; i++) {
if (swp->primary_animation_position[i] == MA_POS_NOT_SET) {
if ( Ship_info[sp->ship_info_index].animations.startAll(model_get_instance(sp->model_instance_num), animation::ModelAnimationTriggerType::PrimaryBank, animation::ModelAnimationDirection::FWD, false, false, false, i) ) {
swp->primary_animation_done_time[i] = Ship_info[sp->ship_info_index].animations.getTimeAll(model_get_instance(sp->model_instance_num), animation::ModelAnimationTriggerType::PrimaryBank, i);
swp->primary_animation_position[i] = MA_POS_SET;
} else {
swp->primary_animation_position[i] = MA_POS_READY;
}
}
}
} else {
// find anything that is up that shouldn't be
for (i = 0; i < MAX_SHIP_PRIMARY_BANKS; i++) {
if (i == swp->current_primary_bank) {
// if the current bank is down raise it up
if (swp->primary_animation_position[i] == MA_POS_NOT_SET) {
if (Ship_info[sp->ship_info_index].animations.startAll(model_get_instance(sp->model_instance_num), animation::ModelAnimationTriggerType::PrimaryBank, animation::ModelAnimationDirection::FWD, false, false, false, i)) {
swp->primary_animation_done_time[i] = Ship_info[sp->ship_info_index].animations.getTimeAll(model_get_instance(sp->model_instance_num), animation::ModelAnimationTriggerType::PrimaryBank, i);
swp->primary_animation_position[i] = MA_POS_SET;
} else {
swp->primary_animation_position[i] = MA_POS_READY;
}
}
} else {
// everyone else should be down, if they are not make them so
if (swp->primary_animation_position[i] != MA_POS_NOT_SET) {
Ship_info[sp->ship_info_index].animations.startAll(model_get_instance(sp->model_instance_num), animation::ModelAnimationTriggerType::PrimaryBank, animation::ModelAnimationDirection::RWD, false, false, false, i);
swp->primary_animation_position[i] = MA_POS_NOT_SET;
}
}
}
}
#if 0
// we only need to deal with multiplayer issues for now, so bail it not multiplayer
if ( !(Game_mode & GM_MULTIPLAYER) )
return;
Assert(sp);
if ( MULTIPLAYER_MASTER )
send_ship_weapon_change( sp, MULTI_PRIMARY_CHANGED, swp->current_primary_bank, (sp->flags[Ship::Ship_Flags::Primary_linked])?1:0 );
#endif
}
// The current secondary weapon or dual-fire status for a ship has changed.. notify clients if multiplayer
//
// input: sp => pointer to ship that modified secondaries
void ship_secondary_changed(ship *sp)
{
Assert( sp != NULL );
int i;
ship_weapon *swp = &sp->weapons;
// find anything that is up that shouldn't be
if (timestamp() > 10) {
for (i = 0; i < MAX_SHIP_SECONDARY_BANKS; i++) {
if (i == swp->current_secondary_bank) {
// if the current bank is down raise it up
if (swp->secondary_animation_position[i] == MA_POS_NOT_SET) {
if (Ship_info[sp->ship_info_index].animations.startAll(model_get_instance(sp->model_instance_num), animation::ModelAnimationTriggerType::SecondaryBank, animation::ModelAnimationDirection::FWD, false, false, false, i)) {
swp->secondary_animation_done_time[i] = Ship_info[sp->ship_info_index].animations.getTimeAll(model_get_instance(sp->model_instance_num), animation::ModelAnimationTriggerType::SecondaryBank, i);
swp->secondary_animation_position[i] = MA_POS_SET;
} else {
swp->secondary_animation_position[i] = MA_POS_READY;
}
}
} else {
// everyone else should be down, if they are not make them so
if (swp->secondary_animation_position[i] != MA_POS_NOT_SET) {
Ship_info[sp->ship_info_index].animations.startAll(model_get_instance(sp->model_instance_num), animation::ModelAnimationTriggerType::SecondaryBank, animation::ModelAnimationDirection::RWD, false, false, false, i);
swp->secondary_animation_position[i] = MA_POS_NOT_SET;
}
}
}
}
#if 0
// we only need to deal with multiplayer issues for now, so bail it not multiplayer
if ( !(Game_mode & GM_MULTIPLAYER) ){
return;
}
Assert(sp);
if ( MULTIPLAYER_MASTER )
send_ship_weapon_change( sp, MULTI_SECONDARY_CHANGED, swp->current_secondary_bank, (sp->flags[Ship::Ship_Flags::Secondary_dual_fire])?1:0 );
#endif
}
flagset<Ship::Info_Flags> ship_get_SIF(ship *shipp)
{
return Ship_info[shipp->ship_info_index].flags;
}
flagset<Ship::Info_Flags> ship_get_SIF(int sh)
{
return Ship_info[Ships[sh].ship_info_index].flags;
}
int ship_get_by_signature(int signature)
{
ship_obj *so;
for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) {
// if we found a matching ship object signature
if((Objects[so->objnum].signature == signature) && (Objects[so->objnum].type == OBJ_SHIP)){
return Objects[so->objnum].instance;
}
}
// couldn't find the ship
return -1;
}
ship_type_info *ship_get_type_info(object *objp)
{
Assert(objp != NULL);
Assert(objp->type == OBJ_SHIP);
Assert(objp->instance > -1);
Assert(Ships[objp->instance].ship_info_index > -1);
Assert(Ship_info[Ships[objp->instance].ship_info_index].class_type > -1);
return &Ship_types[Ship_info[Ships[objp->instance].ship_info_index].class_type];
}
/**
* Called when the cargo of a ship is revealed.
*
* Happens at two different locations (at least when this function was written), one for the player, and one for AI ships.
* Need to send stuff to clients in multiplayer game.
*/
void ship_do_cargo_revealed( ship *shipp, int from_network )
{
// don't do anything if we already know the cargo
if ( shipp->flags[Ship_Flags::Cargo_revealed] ){
return;
}
nprintf(("Network", "Revealing cargo for %s\n", shipp->ship_name));
// send the packet if needed
if ( (Game_mode & GM_MULTIPLAYER) && !from_network ){
send_cargo_revealed_packet( shipp );
}
shipp->flags.set(Ship_Flags::Cargo_revealed);
shipp->time_cargo_revealed = Missiontime;
// if the cargo is something other than "nothing", then make a log entry
if ( stricmp(Cargo_names[shipp->cargo1 & CARGO_INDEX_MASK], NOX("nothing")) != 0 ){
mission_log_add_entry(LOG_CARGO_REVEALED, shipp->ship_name, NULL, (shipp->cargo1 & CARGO_INDEX_MASK) );
}
}
void ship_do_cap_subsys_cargo_revealed( ship *shipp, ship_subsys *subsys, int from_network )
{
// don't do anything if we already know the cargo
if (subsys->flags[Ship::Subsystem_Flags::Cargo_revealed]) {
return;
}
nprintf(("Network", "Revealing cap ship subsys cargo for %s\n", shipp->ship_name));
// send the packet if needed
if ( (Game_mode & GM_MULTIPLAYER) && !from_network ){
int subsystem_index = ship_get_index_from_subsys(subsys, shipp->objnum);
send_subsystem_cargo_revealed_packet( shipp, subsystem_index );
}
subsys->flags.set(Ship::Subsystem_Flags::Cargo_revealed);
subsys->time_subsys_cargo_revealed = Missiontime;
// if the cargo is something other than "nothing", then make a log entry
if ( stricmp(Cargo_names[subsys->subsys_cargo_name & CARGO_INDEX_MASK], NOX("nothing")) != 0 ){
mission_log_add_entry(LOG_CAP_SUBSYS_CARGO_REVEALED, shipp->ship_name, subsys->system_info->subobj_name, (subsys->subsys_cargo_name & CARGO_INDEX_MASK) );
}
}
/**
* alled when the cargo of a ship is hidden by the sexp.
*
* Need to send stuff to clients in multiplayer game.
*/
void ship_do_cargo_hidden( ship *shipp, int from_network )
{
// don't do anything if the cargo is already hidden
if ( !(shipp->flags[Ship_Flags::Cargo_revealed]) )
{
return;
}
nprintf(("Network", "Hiding cargo for %s\n", shipp->ship_name));
// send the packet if needed
if ( (Game_mode & GM_MULTIPLAYER) && !from_network ){
send_cargo_hidden_packet( shipp );
}
shipp->flags.remove(Ship_Flags::Cargo_revealed);
// don't log that the cargo was hidden and don't reset the time cargo revealed
}
void ship_do_cap_subsys_cargo_hidden( ship *shipp, ship_subsys *subsys, int from_network )
{
// don't do anything if the cargo is already hidden
if (!(subsys->flags[Ship::Subsystem_Flags::Cargo_revealed]))
{
return;
}
nprintf(("Network", "Hiding cap ship subsys cargo for %s\n", shipp->ship_name));
// send the packet if needed
if ( (Game_mode & GM_MULTIPLAYER) && !from_network ){
int subsystem_index = ship_get_index_from_subsys(subsys, shipp->objnum);
send_subsystem_cargo_hidden_packet( shipp, subsystem_index );
}
subsys->flags.remove(Ship::Subsystem_Flags::Cargo_revealed);
// don't log that the cargo was hidden and don't reset the time cargo revealed
}
/**
* Return the range of the currently selected secondary weapon
*
* NOTE: If there is no missiles left in the current bank, range returned is 0
*
* @param shipp Pointer to ship from which currently selected secondary weapon will be ranged
*/
float ship_get_secondary_weapon_range(ship *shipp)
{
float srange=0.0f;
ship_weapon *swp;
swp = &shipp->weapons;
if ( swp->current_secondary_bank >= 0 ) {
weapon_info *wip;
int bank=swp->current_secondary_bank;
if (swp->secondary_bank_weapons[bank] >= 0) {
wip = &Weapon_info[swp->secondary_bank_weapons[bank]];
if ( swp->secondary_bank_ammo[bank] > 0 || wip->wi_flags[Weapon::Info_Flags::SecondaryNoAmmo]) {
srange = wip->max_speed * wip->lifetime;
}
}
}
return srange;
}
/**
* Determine the number of primary ammo units allowed max for a ship
*/
int get_max_ammo_count_for_primary_bank(int ship_class, int bank, int ammo_type)
{
Assertion(ship_class < ship_info_size(), "Invalid ship_class of %d is >= Ship_info.size() (%d); get a coder!\n", ship_class, ship_info_size());
Assertion(bank < MAX_SHIP_PRIMARY_BANKS, "Invalid primary bank of %d (max is %d); get a coder!\n", bank, MAX_SHIP_PRIMARY_BANKS - 1);
Assertion(ammo_type < weapon_info_size(), "Invalid ammo_type of %d is >= Weapon_info.size() (%d); get a coder!\n", ammo_type, weapon_info_size());
// Invalid and non-existent weapons, and non-ballistic weapons, have capacities of 0, per ship_weapon::clear()
if (ship_class < 0 || bank < 0 || ammo_type < 0 || !(Weapon_info[ammo_type].wi_flags[Weapon::Info_Flags::Ballistic]))
return 0;
float capacity = (float)Ship_info[ship_class].primary_bank_ammo_capacity[bank];
float size = (float)Weapon_info[ammo_type].cargo_size;
Assertion(size > 0.0f, "Weapon cargo size for %s must be greater than 0!", Weapon_info[ammo_type].name);
return (int)std::lround(capacity / size);
}
/**
* Determine the number of secondary ammo units (missile/bomb) allowed max for a ship
*/
int get_max_ammo_count_for_bank(int ship_class, int bank, int ammo_type)
{
Assertion(ship_class < ship_info_size(), "Invalid ship_class of %d is >= Ship_info.size() (%d); get a coder!\n", ship_class, ship_info_size());
Assertion(bank < MAX_SHIP_SECONDARY_BANKS, "Invalid secondary bank of %d (max is %d); get a coder!\n", bank, MAX_SHIP_SECONDARY_BANKS - 1);
Assertion(ammo_type < weapon_info_size(), "Invalid ammo_type of %d is >= Weapon_info.size() (%d); get a coder!\n", ammo_type, weapon_info_size());
// Invalid and non-existent weapons have capacities of 0, per ship_weapon::clear()
if (ship_class < 0 || bank < 0 || ammo_type < 0)
return 0;
float capacity = (float)Ship_info[ship_class].secondary_bank_ammo_capacity[bank];
float size = (float)Weapon_info[ammo_type].cargo_size;
Assertion(size > 0.0f, "Weapon cargo size for %s must be greater than 0!", Weapon_info[ammo_type].name);
return (int)std::lround(capacity / size);
}
/**
* The same as above, but for a specific turret's bank.
*/
int get_max_ammo_count_for_turret_bank(ship_weapon *swp, int bank, int ammo_type)
{
float capacity, size;
Assertion(bank < MAX_SHIP_SECONDARY_BANKS, "Invalid secondary bank of %d (max is %d); get a coder!\n", bank, MAX_SHIP_SECONDARY_BANKS - 1);
Assertion(ammo_type < weapon_info_size(), "Invalid ammo_type of %d is >= Weapon_info.size() (%d); get a coder!\n", ammo_type, weapon_info_size());
if (!swp || bank < 0 || ammo_type < 0) {
return 0;
} else {
capacity = (float) swp->secondary_bank_capacity[bank];
size = (float) Weapon_info[ammo_type].cargo_size;
return (int) (capacity / size);
}
}
/**
* Page in bitmaps for all the ships in this level
*/
void ship_page_in()
{
TRACE_SCOPE(tracing::ShipPageIn);
int i, j, k;
int num_subsystems_needed = 0;
int *ship_class_used = NULL;
ship_class_used = new int[Ship_info.size()];
Verify( ship_class_used != NULL );
// Mark all ship classes as not used
memset( ship_class_used, 0, Ship_info.size() * sizeof(int) );
// Mark any support ship types as used
for (auto sip = Ship_info.begin(); sip != Ship_info.end(); ++sip) {
if (sip->flags[Ship::Info_Flags::Support]) {
nprintf(( "Paging", "Found support ship '%s'\n", sip->name ));
i = (int)std::distance(Ship_info.begin(), sip);
ship_class_used[i]++;
num_subsystems_needed += sip->n_subsystems;
// load the darn model and page in textures
sip->model_num = model_load(sip->pof_file, sip->n_subsystems, &sip->subsystems[0]);
if (sip->model_num >= 0) {
model_page_in_textures(sip->model_num, i);
}
}
}
// Mark any ships in the mission as used
for (i = 0; i < MAX_SHIPS; i++) {
if (Ships[i].objnum < 0)
continue;
nprintf(( "Paging","Found ship '%s'\n", Ships[i].ship_name ));
ship_class_used[Ships[i].ship_info_index]++;
// mark any weapons as being used, saves memory and time if we don't load them all
ship_weapon *swp = &Ships[i].weapons;
for (j = 0; j < swp->num_primary_banks; j++)
weapon_mark_as_used(swp->primary_bank_weapons[j]);
for (j = 0; j < swp->num_secondary_banks; j++)
weapon_mark_as_used(swp->secondary_bank_weapons[j]);
// get weapons for all capship subsystems (turrets)
ship_subsys *ptr = GET_FIRST(&Ships[i].subsys_list);
while (ptr != END_OF_LIST(&Ships[i].subsys_list)) {
for (k = 0; k < MAX_SHIP_PRIMARY_BANKS; k++)
weapon_mark_as_used(ptr->weapons.primary_bank_weapons[k]);
for (k = 0; k < MAX_SHIP_SECONDARY_BANKS; k++)
weapon_mark_as_used(ptr->weapons.secondary_bank_weapons[k]);
ptr = GET_NEXT(ptr);
}
ship_info *sip = &Ship_info[Ships[i].ship_info_index];
// page in all of the textures if the model is already loaded
if (sip->model_num >= 0) {
nprintf(( "Paging", "Paging in textures for ship '%s'\n", Ships[i].ship_name ));
model_page_in_textures(sip->model_num, Ships[i].ship_info_index);
// need to make sure and do this again, after we are sure that all of the textures are ready
ship_init_afterburners( &Ships[i] );
}
//WMC - Since this is already in-mission, ignore the warpin effect.
Ships[i].warpout_effect->pageIn();
// don't need this one anymore, it's already been accounted for
// num_subsystems_needed += Ship_info[Ships[i].ship_info_index].n_subsystems;
}
// Mark any ships that might warp in in the future as used
for (p_object *p_objp = GET_FIRST(&Ship_arrival_list); p_objp != END_OF_LIST(&Ship_arrival_list); p_objp = GET_NEXT(p_objp)) {
nprintf(( "Paging", "Found future arrival ship '%s'\n", p_objp->name ));
ship_class_used[p_objp->ship_class]++;
// This will go through Subsys_index[] and grab all weapons: primary, secondary, and turrets
for (i = p_objp->subsys_index; i < (p_objp->subsys_index + p_objp->subsys_count); i++) {
for (j = 0; j < MAX_SHIP_PRIMARY_BANKS; j++) {
if (Subsys_status[i].primary_banks[j] >= 0)
weapon_mark_as_used(Subsys_status[i].primary_banks[j]);
}
for (j = 0; j < MAX_SHIP_SECONDARY_BANKS; j++) {
if (Subsys_status[i].secondary_banks[j] >= 0)
weapon_mark_as_used(Subsys_status[i].secondary_banks[j]);
}
}
// page in any replacement textures
if (Ship_info[p_objp->ship_class].model_num >= 0) {
nprintf(( "Paging", "Paging in textures for future arrival ship '%s'\n", p_objp->name ));
model_page_in_textures(Ship_info[p_objp->ship_class].model_num, p_objp->ship_class);
}
num_subsystems_needed += Ship_info[p_objp->ship_class].n_subsystems;
}
// pre-allocate the subsystems, this really only needs to happen for ships
// which don't exist yet (ie, ships NOT in Ships[])
if (!ship_allocate_subsystems(num_subsystems_needed, true)) {
Error(LOCATION, "Attempt to page in new subsystems subsystems failed, which shouldn't be possible anymore. Currently allocated %d subsystems (%d in use)", Num_ship_subsystems_allocated, Num_ship_subsystems);
}
mprintf(("About to page in ships!\n"));
// Page in all the ship classes that are used on this level
int num_ship_types_used = 0;
int test_id __UNUSED = -1;
memset( fireball_used, 0, sizeof(int) * MAX_FIREBALL_TYPES );
i = 0;
for (auto sip = Ship_info.begin(); sip != Ship_info.end(); i++, ++sip) {
if ( !ship_class_used[i] )
continue;
int model_previously_loaded = -1;
int ship_previously_loaded = -1;
num_ship_types_used++;
// Page in the small hud icons for each ship
hud_ship_icon_page_in(&(*sip));
// See if this model was previously loaded by another ship
for (auto it = Ship_info.begin(); it != Ship_info.end(); ++it) {
if ( (it->model_num > -1) && !stricmp(sip->pof_file, it->pof_file) ) {
// Model already loaded
model_previously_loaded = it->model_num;
if ((sip->n_subsystems > 0) && (sip->subsystems[0].model_num > -1)) {
ship_previously_loaded = (int)std::distance(Ship_info.begin(), it);
// It is possible in some cases for sip->model_num to change, and for subsystems->model_num
// to still point to the old model index; this makes sure it doesn't happen. -zookeeper
for (k = 0; k < sip->n_subsystems; k++) {
if (sip->model_num != sip->subsystems[k].model_num) {
mprintf(("Ship %s has model_num %i but its subsystem %s has model_num %i, fixing...\n", sip->name, sip->model_num, sip->subsystems[k].name, sip->subsystems[k].model_num));
sip->subsystems[k].model_num = sip->model_num;
}
}
}
// the model should already be loaded so this wouldn't take long, but
// we need to make sure that the load count for the model is correct
test_id = model_load(sip->pof_file, sip->n_subsystems, &sip->subsystems[0]);
Assert( test_id == model_previously_loaded );
break;
}
}
// If the model is previously loaded...
if (model_previously_loaded >= 0) {
// If previously loaded model isn't the same ship class...)
if (ship_previously_loaded != i) {
// update the model number.
sip->model_num = model_previously_loaded;
for (j = 0; j < sip->n_subsystems; j++)
sip->subsystems[j].model_num = -1;
ship_copy_subsystem_fixup(&(*sip));
#ifndef NDEBUG
for (j = 0; j < sip->n_subsystems; j++) {
if (sip->subsystems[j].model_num != sip->model_num) {
polymodel *sip_pm = (sip->model_num >= 0) ? model_get(sip->model_num) : NULL;
polymodel *subsys_pm = (sip->subsystems[j].model_num >= 0) ? model_get(sip->subsystems[j].model_num) : NULL;
Warning(LOCATION, "After ship_copy_subsystem_fixup, ship '%s' does not have subsystem '%s' linked into the model file, '%s'.\n\n(Ship_info model is '%s' and subsystem model is '%s'.)", sip->name, sip->subsystems[j].subobj_name, sip->pof_file, (sip_pm != NULL) ? sip_pm->filename : "NULL", (subsys_pm != NULL) ? subsys_pm->filename : "NULL");
}
}
#endif
} else {
// Just to be safe (I mean to check that my code works...)
Assert( sip->model_num >= 0 );
Assert( sip->model_num == model_previously_loaded );
#ifndef NDEBUG
for (j = 0; j < sip->n_subsystems; j++) {
if (sip->subsystems[j].model_num != sip->model_num) {
polymodel *sip_pm = (sip->model_num >= 0) ? model_get(sip->model_num) : NULL;
polymodel *subsys_pm = (sip->subsystems[j].model_num >= 0) ? model_get(sip->subsystems[j].model_num) : NULL;
Warning(LOCATION, "Without ship_copy_subsystem_fixup, ship '%s' does not have subsystem '%s' linked into the model file, '%s'.\n\n(Ship_info model is '%s' and subsystem model is '%s'.)", sip->name, sip->subsystems[j].subobj_name, sip->pof_file, (sip_pm != NULL) ? sip_pm->filename : "NULL", (subsys_pm != NULL) ? subsys_pm->filename : "NULL");
}
}
#endif
}
} else {
// Model not loaded, so load it
sip->model_num = model_load(sip->pof_file, sip->n_subsystems, &sip->subsystems[0]);
Assert( sip->model_num >= 0 );
#ifndef NDEBUG
// Verify that all the subsystem model numbers are updated
for (j = 0; j < sip->n_subsystems; j++)
Assertion( sip->subsystems[j].model_num == sip->model_num, "Model reference for subsystem %s (model num: %d) on model %s (model num: %d) is invalid.\n", sip->subsystems[j].name, sip->subsystems[j].model_num, sip->pof_file, sip->model_num ); // JAS
#endif
}
// more weapon marking, the weapon info in Ship_info[] is the default
// loadout which isn't specified by missionparse unless it's different
for (j = 0; j < sip->num_primary_banks; j++)
weapon_mark_as_used(sip->primary_bank_weapons[j]);
for (j = 0; j < sip->num_secondary_banks; j++)
weapon_mark_as_used(sip->secondary_bank_weapons[j]);
weapon_mark_as_used(sip->cmeasure_type);
for (j = 0; j < sip->n_subsystems; j++) {
model_subsystem *msp = &sip->subsystems[j];
for (k = 0; k < MAX_SHIP_PRIMARY_BANKS; k++)
weapon_mark_as_used(msp->primary_banks[k]);
for (k = 0; k < MAX_SHIP_SECONDARY_BANKS; k++)
weapon_mark_as_used(msp->secondary_banks[k]);
}
// Page in the shockwave stuff. -C
shockwave_create_info_load(&sip->shockwave);
if(!sip->explosion_bitmap_anims.empty()) {
int num_fireballs = (int)sip->explosion_bitmap_anims.size();
for(j = 0; j < num_fireballs; j++){
fireball_used[sip->explosion_bitmap_anims[j]] = 1;
}
} else if(sip->class_type >= 0 && !Ship_types[sip->class_type].explosion_bitmap_anims.empty()) {
int num_fireballs = (int)Ship_types[sip->class_type].explosion_bitmap_anims.size();
for(j = 0; j < num_fireballs; j++){
fireball_used[Ship_types[sip->class_type].explosion_bitmap_anims[j]] = 1;
}
}
}
nprintf(( "Paging", "There are %d ship classes used in this mission.\n", num_ship_types_used ));
// Page in the thruster effects
// Make sure thrusters are loaded
if (!Thrust_anim_inited)
ship_init_thrusters();
thrust_info *thruster;
for (i = 0; i < (int)Species_info.size(); i++) {
thruster = &Species_info[i].thruster_info;
bm_page_in_texture( thruster->flames.normal.first_frame, thruster->flames.normal.num_frames );
bm_page_in_texture( thruster->flames.afterburn.first_frame, thruster->flames.afterburn.num_frames );
// glows are really not anims
bm_page_in_texture( thruster->glow.normal.first_frame );
bm_page_in_texture( thruster->glow.afterburn.first_frame );
}
// page in insignia bitmaps
if(Game_mode & GM_MULTIPLAYER){
for(i=0; i<MAX_PLAYERS; i++){
if(MULTI_CONNECTED(Net_players[i]) && (Net_players[i].m_player != NULL) && (Net_players[i].m_player->insignia_texture >= 0)){
bm_page_in_xparent_texture(Net_players[i].m_player->insignia_texture);
}
}
} else {
if((Player != NULL) && (Player->insignia_texture >= 0)){
bm_page_in_xparent_texture(Player->insignia_texture);
}
}
// page in wing insignia bitmaps - Goober5000
for (i = 0; i < MAX_WINGS; i++)
{
if (Wings[i].wing_insignia_texture >= 0)
bm_page_in_xparent_texture(Wings[i].wing_insignia_texture);
}
// page in replacement textures - Goober5000
for (i = 0; i < MAX_SHIPS; i++)
{
// is this a valid ship?
if (Ships[i].objnum >= 0)
{
// do we have any textures?
if (Ships[i].ship_replacement_textures != NULL)
{
// page in replacement textures
for (j=0; j<MAX_REPLACEMENT_TEXTURES; j++)
{
if (Ships[i].ship_replacement_textures[j] > -1)
{
bm_page_in_texture( Ships[i].ship_replacement_textures[j] );
}
}
}
}
}
// should never be NULL, this entire function wouldn't work
delete[] ship_class_used;
ship_class_used = NULL;
}
// Goober5000 - called from ship_page_in()
void ship_page_in_textures(int ship_index)
{
int i;
ship_info *sip;
if ( (ship_index < 0) || (ship_index >= ship_info_size()) )
return;
sip = &Ship_info[ship_index];
// afterburner
if ( !generic_bitmap_load(&sip->afterburner_trail) )
bm_page_in_texture(sip->afterburner_trail.bitmap_id);
// Wanderer - just copying over Bobboau's code...
if ( !generic_anim_load(&sip->thruster_flame_info.normal) )
bm_page_in_texture(sip->thruster_flame_info.normal.first_frame);
if ( !generic_anim_load(&sip->thruster_flame_info.afterburn) )
bm_page_in_texture(sip->thruster_flame_info.afterburn.first_frame);
// Bobboau's thruster bitmaps
// the first set has to be loaded a special way
if ( !thruster_glow_anim_load(&sip->thruster_glow_info.normal) )
bm_page_in_texture(sip->thruster_glow_info.normal.first_frame);
if ( !thruster_glow_anim_load(&sip->thruster_glow_info.afterburn) )
bm_page_in_texture(sip->thruster_glow_info.afterburn.first_frame);
// everything else is loaded normally
if ( !generic_bitmap_load(&sip->thruster_secondary_glow_info.normal) )
bm_page_in_texture(sip->thruster_secondary_glow_info.normal.bitmap_id);
if ( !generic_bitmap_load(&sip->thruster_secondary_glow_info.afterburn) )
bm_page_in_texture(sip->thruster_secondary_glow_info.afterburn.bitmap_id);
if ( !generic_bitmap_load(&sip->thruster_tertiary_glow_info.normal) )
bm_page_in_texture(sip->thruster_tertiary_glow_info.normal.bitmap_id);
if ( !generic_bitmap_load(&sip->thruster_tertiary_glow_info.afterburn) )
bm_page_in_texture(sip->thruster_tertiary_glow_info.afterburn.bitmap_id);
// splodeing bitmap
if ( VALID_FNAME(sip->splodeing_texture_name) ) {
sip->splodeing_texture = bm_load(sip->splodeing_texture_name);
bm_page_in_texture(sip->splodeing_texture);
}
// thruster/particle bitmaps
for (i = 0; i < (int)sip->normal_thruster_particles.size(); i++) {
generic_anim_load(&sip->normal_thruster_particles[i].thruster_bitmap);
bm_page_in_texture(sip->normal_thruster_particles[i].thruster_bitmap.first_frame);
}
for (i = 0; i < (int)sip->afterburner_thruster_particles.size(); i++) {
generic_anim_load(&sip->afterburner_thruster_particles[i].thruster_bitmap);
bm_page_in_texture(sip->afterburner_thruster_particles[i].thruster_bitmap.first_frame);
}
}
#define PAGE_OUT_TEXTURE(x) { \
if ( (x) >= 0 ) { \
if (release) { \
bm_release( (x) ); \
(x) = -1; \
} else { \
bm_unload( (x) ); \
} \
} \
}
/**
* Unload all textures for a given ship
*/
void ship_page_out_textures(int ship_index, bool release)
{
int i;
ship_info *sip;
if ( (ship_index < 0) || (ship_index >= ship_info_size()) )
return;
sip = &Ship_info[ship_index];
// afterburner
PAGE_OUT_TEXTURE(sip->afterburner_trail.bitmap_id);
// thruster bitmaps
PAGE_OUT_TEXTURE(sip->thruster_flame_info.normal.first_frame);
PAGE_OUT_TEXTURE(sip->thruster_flame_info.afterburn.first_frame);
PAGE_OUT_TEXTURE(sip->thruster_glow_info.normal.first_frame);
PAGE_OUT_TEXTURE(sip->thruster_glow_info.afterburn.first_frame);
PAGE_OUT_TEXTURE(sip->thruster_secondary_glow_info.normal.bitmap_id);
PAGE_OUT_TEXTURE(sip->thruster_secondary_glow_info.afterburn.bitmap_id);
PAGE_OUT_TEXTURE(sip->thruster_tertiary_glow_info.normal.bitmap_id);
PAGE_OUT_TEXTURE(sip->thruster_tertiary_glow_info.afterburn.bitmap_id);
// slodeing bitmap
PAGE_OUT_TEXTURE(sip->splodeing_texture);
// thruster/particle bitmaps
for (i = 0; i < (int)sip->normal_thruster_particles.size(); i++)
PAGE_OUT_TEXTURE(sip->normal_thruster_particles[i].thruster_bitmap.first_frame);
for (i = 0; i < (int)sip->afterburner_thruster_particles.size(); i++)
PAGE_OUT_TEXTURE(sip->afterburner_thruster_particles[i].thruster_bitmap.first_frame);
}
void ship_replace_active_texture(int ship_index, const char* old_name, const char* new_name)
{
ship* shipp = &Ships[ship_index];
polymodel* pm = model_get(Ship_info[shipp->ship_info_index].model_num);
int final_index = -1;
for (int i = 0; i < pm->n_textures; i++)
{
int tm_num = pm->maps[i].FindTexture(old_name);
if (tm_num > -1)
{
final_index = i * TM_NUM_TYPES + tm_num;
break;
}
}
if (final_index >= 0)
{
int texture;
if (!stricmp(new_name, "invisible"))
texture = REPLACE_WITH_INVISIBLE;
else
texture = bm_load_either(new_name);
if (shipp->ship_replacement_textures == nullptr) {
shipp->ship_replacement_textures = (int*)vm_malloc(MAX_REPLACEMENT_TEXTURES * sizeof(int));
for (int i = 0; i < MAX_REPLACEMENT_TEXTURES; i++)
shipp->ship_replacement_textures[i] = -1;
}
shipp->ship_replacement_textures[final_index] = texture;
} else
Warning(LOCATION, "Invalid texture '%s' used for replacement texture", old_name);
}
// function to return true if support ships are allowed in the mission for the given object.
// In single player, must be friendly and not Shivan. (Goober5000 - Shivans can now have support)
// In multiplayer -- to be coded by Mark Allender after 5/4/98 -- MK, 5/4/98
int is_support_allowed(object *objp, bool do_simple_check)
{
int result = -1;
// check updated mission conditions to allow support
// If running under autopilot support is not allowed
if ( AutoPilotEngaged )
return 0;
// none allowed
if (The_mission.support_ships.max_support_ships == 0)
return 0;
// ship_find_repair_ship is a little expensive, so let's not do it every frame
if (!do_simple_check)
{
// check if all support ships are departing or dying
result = ship_find_repair_ship(objp);
if (result == 4) {
return 0;
}
// restricted number allowed
if (The_mission.support_ships.max_support_ships > 0)
{
// if all the allowed ships have been used up, can't rearm unless something's available in-mission or arriving
if ((The_mission.support_ships.tally >= The_mission.support_ships.max_support_ships))
{
// this shouldn't happen because we've reached one of the limits
Assert(result != 2);
// nothing arriving and no ships available in mission
if ((Arriving_support_ship == NULL) && (result == 0 || result == 3))
return 0;
}
}
}
ship *shipp = &Ships[objp->instance];
// this also looks a little more expensive
if (!do_simple_check)
{
// make sure, if exiting from bay, that parent ship is in the mission!
if ((result == 0 || result == 2) && (The_mission.support_ships.arrival_location == ARRIVE_FROM_DOCK_BAY))
{
Assert(The_mission.support_ships.arrival_anchor != -1);
// ensure it's in-mission
int temp = ship_name_lookup(Parse_names[The_mission.support_ships.arrival_anchor]);
if (temp < 0)
{
return 0;
}
// make sure it's not leaving or blowing up
if (Ships[temp].is_dying_or_departing())
{
return 0;
}
// also make sure that parent ship's fighterbay hasn't been destroyed
if (ship_fighterbays_all_destroyed(&Ships[temp]))
{
return 0;
}
}
}
if (Game_mode & GM_NORMAL)
{
if ( !(Iff_info[shipp->team].flags & IFFF_SUPPORT_ALLOWED) )
{
return 0;
}
}
else
{
// multiplayer version behaves differently. Depending on mode:
// 1) coop mode -- only available to friendly
// 2) team v team mode -- availble to either side
// 3) dogfight -- never
if(Netgame.type_flags & NG_TYPE_DOGFIGHT)
{
return 0;
}
if (IS_MISSION_MULTI_COOP)
{
if ( !(Iff_info[shipp->team].flags & IFFF_SUPPORT_ALLOWED) )
{
return 0;
}
}
}
// Goober5000 - extra check for existence of support ship
if ( (The_mission.support_ships.ship_class < 0) &&
!(The_mission.support_ships.support_available_for_species & (1 << Ship_info[shipp->ship_info_index].species)) )
{
return 0;
}
// this is also somewhat expensive
if (!do_simple_check)
{
// Goober5000 - extra check to make sure this guy has a rearming dockpoint
if (model_find_dock_index(Ship_info[shipp->ship_info_index].model_num, DOCK_TYPE_REARM) < 0)
{
static bool warned_about_rearm_dockpoint = false;
if (!warned_about_rearm_dockpoint)
{
Warning(LOCATION, "Support not allowed for %s because its model lacks a rearming dockpoint!", shipp->ship_name);
warned_about_rearm_dockpoint = true;
}
return 0;
}
}
// Goober5000 - if we got this far, we can request support
return 1;
}
// returns random index of a visible ship
// if no visible ships are generated in num_ships iterations, it returns -1
int ship_get_random_targetable_ship()
{
int rand_ship;
int idx = 0, target_list[MAX_SHIPS];
ship_obj *so;
for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) {
// make sure the instance is valid
if ( (Objects[so->objnum].instance < 0) || (Objects[so->objnum].instance >= MAX_SHIPS) )
continue;
// skip if we aren't supposed to target it
if (should_be_ignored(&Ships[Objects[so->objnum].instance]))
continue;
if (idx >= MAX_SHIPS) {
idx = MAX_SHIPS;
break;
}
target_list[idx] = Objects[so->objnum].instance;
idx++;
}
if (idx == 0)
return -1;
rand_ship = Random::next(idx);
return target_list[rand_ship];
}
/**
* Forcibly jettison cargo from a ship
*/
void object_jettison_cargo(object *objp, object *cargo_objp, float jettison_speed, bool jettison_new)
{
// make sure we are docked
Assert((objp != nullptr) && (cargo_objp != nullptr));
Assert(dock_check_find_direct_docked_object(objp, cargo_objp));
vec3d impulse, pos;
ship *shipp = &Ships[objp->instance];
ship *cargo_shipp = &Ships[cargo_objp->instance];
int docker_index = dock_find_dockpoint_used_by_object(objp, cargo_objp);
int dockee_index = dock_find_dockpoint_used_by_object(cargo_objp, objp);
// undo all the docking animations
ship_info* sip = &Ship_info[shipp->ship_info_index];
sip->animations.startAll(model_get_instance(shipp->model_instance_num), animation::ModelAnimationTriggerType::Docked, animation::ModelAnimationDirection::RWD, false, false, false, docker_index);
sip->animations.startAll(model_get_instance(shipp->model_instance_num), animation::ModelAnimationTriggerType::Docking_Stage3, animation::ModelAnimationDirection::RWD, false, false, false, docker_index);
sip->animations.startAll(model_get_instance(shipp->model_instance_num), animation::ModelAnimationTriggerType::Docking_Stage2, animation::ModelAnimationDirection::RWD, false, false, false, docker_index);
sip->animations.startAll(model_get_instance(shipp->model_instance_num), animation::ModelAnimationTriggerType::Docking_Stage1, animation::ModelAnimationDirection::RWD, false, false, false, docker_index);
ship_info* cargo_sip = &Ship_info[cargo_shipp->ship_info_index];
cargo_sip->animations.startAll(model_get_instance(cargo_shipp->model_instance_num), animation::ModelAnimationTriggerType::Docked, animation::ModelAnimationDirection::RWD, false, false, false, dockee_index);
cargo_sip->animations.startAll(model_get_instance(cargo_shipp->model_instance_num), animation::ModelAnimationTriggerType::Docking_Stage3, animation::ModelAnimationDirection::RWD, false, false, false, dockee_index);
cargo_sip->animations.startAll(model_get_instance(cargo_shipp->model_instance_num), animation::ModelAnimationTriggerType::Docking_Stage2, animation::ModelAnimationDirection::RWD, false, false, false, dockee_index);
cargo_sip->animations.startAll(model_get_instance(cargo_shipp->model_instance_num), animation::ModelAnimationTriggerType::Docking_Stage1, animation::ModelAnimationDirection::RWD, false, false, false, dockee_index);
// undock the objects
ai_do_objects_undocked_stuff(objp, cargo_objp);
// Goober5000 - add log
mission_log_add_entry(LOG_SHIP_UNDOCKED, shipp->ship_name, cargo_shipp->ship_name);
// physics stuff
if (jettison_new)
{
// new method uses dockpoint normals and user-specified force
extern void find_adjusted_dockpoint_info(vec3d *global_dock_point, matrix *dock_orient, object *objp, polymodel *pm, int submodel, int dock_index);
extern int find_parent_moving_submodel(polymodel *pm, int dock_index);
int model_num = Ship_info[shipp->ship_info_index].model_num;
polymodel *pm = model_get(model_num);
int docker_moving_submodel = find_parent_moving_submodel(pm, docker_index);
matrix dock_orient;
find_adjusted_dockpoint_info(&pos, &dock_orient, objp, pm, docker_moving_submodel, docker_index);
// set for relative separation speed (see also do_dying_undock_physics)
vm_vec_copy_scale(&impulse, &dock_orient.vec.fvec, jettison_speed * cargo_objp->phys_info.mass);
}
else
{
// the old method sends cargo in the wrong direction and with an impulse that depends on the size of the ship
vm_vec_sub(&pos, &cargo_objp->pos, &objp->pos);
impulse = pos;
vm_vec_scale(&impulse, 100.0f);
vm_vec_normalize(&pos);
vm_vec_add2(&pos, &cargo_objp->pos);
}
// whack the ship
ship_apply_whack(&impulse, &pos, cargo_objp);
}
float ship_get_exp_damage(object* objp)
{
Assert(objp->type == OBJ_SHIP);
float damage;
ship *shipp = &Ships[objp->instance];
if (shipp->special_exp_damage >= 0) {
damage = i2fl(shipp->special_exp_damage);
} else {
damage = Ship_info[shipp->ship_info_index].shockwave.damage;
}
return damage;
}
static int ship_get_exp_propagates(ship *sp)
{
return Ship_info[sp->ship_info_index].explosion_propagates;
}
float ship_get_exp_outer_rad(object *ship_objp)
{
float outer_rad;
Assert(ship_objp->type == OBJ_SHIP);
if (Ships[ship_objp->instance].special_exp_outer == -1) {
outer_rad = Ship_info[Ships[ship_objp->instance].ship_info_index].shockwave.outer_rad;
} else {
outer_rad = (float)Ships[ship_objp->instance].special_exp_outer;
}
return outer_rad;
}
/**
* Determine turret status of a given subsystem
*
* @return 0 for no turret, 1 for "fixed turret", 2 for "rotating" turret
*/
int ship_get_turret_type(ship_subsys *subsys)
{
// not a turret at all
if(subsys->system_info->type != SUBSYSTEM_TURRET){
return 0;
}
// if it rotates
if(subsys->system_info->turret_turning_rate > 0.0f){
return 2;
}
// if its fixed
return 1;
}
ship_subsys *ship_get_subsys(const ship *shipp, const char *subsys_name)
{
// sanity checks
if ((shipp == NULL) || (subsys_name == NULL)) {
return NULL;
}
ship_subsys *ss = GET_FIRST(&shipp->subsys_list);
while (ss != END_OF_LIST(&shipp->subsys_list)) {
// check subsystem name
if (!subsystem_stricmp(ss->system_info->subobj_name, subsys_name)) {
return ss;
}
// next
ss = GET_NEXT(ss);
}
// didn't find it
return NULL;
}
int ship_get_num_subsys(ship *shipp)
{
Assert(shipp != NULL);
return Ship_info[shipp->ship_info_index].n_subsystems;
}
// returns 0 if no conflict, 1 if conflict, -1 on some kind of error with wing struct
int wing_has_conflicting_teams(int wing_index)
{
int first_team, idx;
// sanity checks
Assert((wing_index >= 0) && (wing_index < Num_wings) && (Wings[wing_index].wave_count > 0));
if((wing_index < 0) || (wing_index >= Num_wings) || (Wings[wing_index].wave_count <= 0)){
return -1;
}
// check teams
Assert(Wings[wing_index].ship_index[0] >= 0);
if(Wings[wing_index].ship_index[0] < 0){
return -1;
}
first_team = Ships[Wings[wing_index].ship_index[0]].team;
for(idx=1; idx<Wings[wing_index].wave_count; idx++){
// more sanity checks
Assert(Wings[wing_index].ship_index[idx] >= 0);
if(Wings[wing_index].ship_index[idx] < 0){
return -1;
}
// if we've got a team conflict
if(first_team != Ships[Wings[wing_index].ship_index[idx]].team){
return 1;
}
}
// no conflict
return 0;
}
/**
* Get the team of a reinforcement item
*/
int ship_get_reinforcement_team(int r_index)
{
int wing_index;
p_object *p_objp;
// sanity checks
Assert((r_index >= 0) && (r_index < Num_reinforcements));
if ((r_index < 0) || (r_index >= Num_reinforcements))
return -1;
// if the reinforcement is a ship
p_objp = mission_parse_get_arrival_ship(Reinforcements[r_index].name);
if (p_objp != NULL)
return p_objp->team;
// if the reinforcement is a ship
wing_index = wing_lookup(Reinforcements[r_index].name);
if (wing_index >= 0)
{
// go through the ship arrival list and find any ship in this wing
for (p_objp = GET_FIRST(&Ship_arrival_list); p_objp != END_OF_LIST(&Ship_arrival_list); p_objp = GET_NEXT(p_objp))
{
// check by wingnum
if (p_objp->wingnum == wing_index)
return p_objp->team;
}
}
// no team ?
return -1;
}
// update artillery lock info
#define CLEAR_ARTILLERY_AND_CONTINUE() { if(aip != NULL){ aip->artillery_objnum = -1; aip->artillery_sig = -1; aip->artillery_lock_time = 0.0f;} continue; }
float artillery_dist = 10.0f;
DCF(art, "Sets artillery disance")
{
dc_stuff_float(&artillery_dist);
}
void ship_update_artillery_lock()
{
ai_info *aip = NULL;
mc_info *cinfo = NULL;
int c_objnum;
vec3d temp, local_hit;
ship *shipp;
ship_obj *so;
// update all ships
for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ){
// get the ship
if((so->objnum >= 0) && (Objects[so->objnum].type == OBJ_SHIP) && (Objects[so->objnum].instance >= 0)){
shipp = &Ships[Objects[so->objnum].instance];
} else {
continue;
}
// get ai info
if(shipp->ai_index >= 0){
aip = &Ai_info[shipp->ai_index];
}
// if the ship has no targeting laser firing
if((shipp->targeting_laser_objnum < 0) || (shipp->targeting_laser_bank < 0)){
CLEAR_ARTILLERY_AND_CONTINUE();
}
// if he didn't hit any objects this frame
if(beam_get_num_collisions(shipp->targeting_laser_objnum) <= 0){
CLEAR_ARTILLERY_AND_CONTINUE();
}
// get weapon info for the targeting laser he's firing
Assert((shipp->weapons.current_primary_bank >= 0) && (shipp->weapons.current_primary_bank < 2));
if((shipp->weapons.current_primary_bank < 0) || (shipp->weapons.current_primary_bank >= 2)){
continue;
}
Assert(shipp->weapons.primary_bank_weapons[shipp->weapons.current_primary_bank] >= 0);
if(shipp->weapons.primary_bank_weapons[shipp->weapons.current_primary_bank] < 0){
continue;
}
Assert((Weapon_info[shipp->weapons.primary_bank_weapons[shipp->weapons.current_primary_bank]].wi_flags[Weapon::Info_Flags::Beam]) && (Weapon_info[shipp->weapons.primary_bank_weapons[shipp->weapons.current_primary_bank]].b_info.beam_type == BeamType::TARGETING));
if(!(Weapon_info[shipp->weapons.primary_bank_weapons[shipp->weapons.current_primary_bank]].wi_flags[Weapon::Info_Flags::Beam]) || (Weapon_info[shipp->weapons.primary_bank_weapons[shipp->weapons.current_primary_bank]].b_info.beam_type != BeamType::TARGETING)){
continue;
}
// get collision info
if(!beam_get_collision(shipp->targeting_laser_objnum, 0, &c_objnum, &cinfo)){
CLEAR_ARTILLERY_AND_CONTINUE();
}
if((c_objnum < 0) || (cinfo == NULL)){
CLEAR_ARTILLERY_AND_CONTINUE();
}
// get the position we hit this guy with in his local coords
vm_vec_sub(&temp, &cinfo->hit_point_world, &Objects[c_objnum].pos);
vm_vec_rotate(&local_hit, &temp, &Objects[c_objnum].orient);
// if we are hitting a different guy now, reset the lock
if((c_objnum != aip->artillery_objnum) || (Objects[c_objnum].signature != aip->artillery_sig)){
aip->artillery_objnum = c_objnum;
aip->artillery_sig = Objects[c_objnum].signature;
aip->artillery_lock_time = 0.0f;
aip->artillery_lock_pos = local_hit;
// done
continue;
}
// otherwise we're hitting the same guy. check to see if we've strayed too far
if(vm_vec_dist_quick(&local_hit, &aip->artillery_lock_pos) > artillery_dist){
// hmmm. reset lock time, but don't reset the lock itself
aip->artillery_lock_time = 0.0f;
continue;
}
// finally - just increment the lock time
aip->artillery_lock_time += flFrametime;
// TEST CODE
if(aip->artillery_objnum >= 0 && aip->artillery_lock_time >= 2.0f){
ssm_create(&Objects[aip->artillery_objnum], &cinfo->hit_point_world, 0, NULL, shipp->team);
// reset the artillery
aip->artillery_lock_time = 0.0f;
}
}
}
/**
* Checks if a world point is inside the extended bounding box of a ship
*
* May not work if delta box is large and negative (ie, adjusted box crosses over on itself - min > max)
*/
int check_world_pt_in_expanded_ship_bbox(vec3d *world_pt, object *objp, float delta_box)
{
Assert(objp->type == OBJ_SHIP);
vec3d temp, ship_pt;
polymodel *pm;
vm_vec_sub(&temp, world_pt, &objp->pos);
vm_vec_rotate(&ship_pt, &temp, &objp->orient);
pm = model_get(Ship_info[Ships[objp->instance].ship_info_index].model_num);
return (
(ship_pt.xyz.x > pm->mins.xyz.x - delta_box) && (ship_pt.xyz.x < pm->maxs.xyz.x + delta_box)
&& (ship_pt.xyz.y > pm->mins.xyz.y - delta_box) && (ship_pt.xyz.y < pm->maxs.xyz.y + delta_box)
&& (ship_pt.xyz.z > pm->mins.xyz.z - delta_box) && (ship_pt.xyz.z < pm->maxs.xyz.z + delta_box)
);
}
/**
* Returns true when objp is ship and is tagged
*/
int ship_is_tagged(object *objp)
{
ship *shipp;
if (objp->type == OBJ_SHIP) {
shipp = &Ships[objp->instance];
if ( (shipp->tag_left > 0) || (shipp->level2_tag_left > 0) ) {
return 1;
}
}
return 0;
}
/**
* Get maximum ship speed (when not warping in or out)
*/
float ship_get_max_speed(ship *shipp)
{
float max_speed;
ship_info *sip = &Ship_info[shipp->ship_info_index];
// Goober5000 - maybe we're using cap-waypoint-speed
ai_info *aip = &Ai_info[shipp->ai_index];
if ((aip->mode == AIM_WAYPOINTS || aip->mode == AIM_FLY_TO_SHIP) && aip->waypoint_speed_cap > 0)
return i2fl(aip->waypoint_speed_cap);
// max overclock
max_speed = sip->max_overclocked_speed;
// normal max speed
max_speed = MAX(max_speed, sip->max_vel.xyz.z);
// afterburn if not locked
if (!(shipp->flags[Ship_Flags::Afterburner_locked])) {
max_speed = MAX(max_speed, sip->afterburner_max_vel.xyz.z);
}
return max_speed;
}
/**
* Determine warpout speed of ship
*/
float ship_get_warpout_speed(object *objp, ship_info *sip, float half_length, float warping_dist)
{
Assert(objp != nullptr && objp->type == OBJ_SHIP);
// certain places in the code don't precalculate these variables
if (sip == nullptr)
{
sip = &Ship_info[Ships[objp->instance].ship_info_index];
// c.f. WE_Default::warpStart()
// determine the half-length and the warping distance (which is actually the full length)
if (object_is_docked(objp))
{
// we need to get the longitudinal radius of our ship, so find the semilatus rectum along the Z-axis
half_length = dock_calc_max_semilatus_rectum_parallel_to_axis(objp, Z_AXIS);
warping_dist = 2.0f * half_length;
}
else
{
warping_dist = ship_class_get_length(sip);
half_length = 0.5f * warping_dist;
}
}
WarpParams *params = &Warp_params[Ships[objp->instance].warpout_params_index];
//WMC - Any speed is good for in place anims (aka BSG FTL effect)
//Asteroth - or for the sweeper (aka Homeworld FTL effect)
if ((params->warp_type == WT_IN_PLACE_ANIM || params->warp_type == WT_SWEEPER) && params->speed <= 0.0f)
{
return objp->phys_info.speed;
}
else if (params->warp_type == WT_SWEEPER || params->warp_type == WT_IN_PLACE_ANIM)
{
return params->speed;
}
else if (params->warp_type == WT_HYPERSPACE)
{
if (objp->phys_info.speed > params->speed)
return objp->phys_info.speed;
else
return params->speed;
}
return warping_dist / shipfx_calculate_warp_time(objp, WarpDirection::WARP_OUT, half_length, warping_dist);
}
/**
* Returns true if ship is beginning to speed up in warpout
*/
int ship_is_beginning_warpout_speedup(object *objp)
{
Assert(objp->type == OBJ_SHIP);
ai_info *aip;
aip = &Ai_info[Ships[objp->instance].ai_index];
if (aip->mode == AIM_WARP_OUT) {
if ( (aip->submode == AIS_WARP_3) || (aip->submode == AIS_WARP_4) || (aip->submode == AIS_WARP_5) ) {
return 1;
}
}
return 0;
}
/**
* Return the length of a ship
*/
float ship_class_get_length(const ship_info *sip)
{
Assert(sip != nullptr);
Assert(sip->model_num >= 0);
polymodel *pm = model_get(sip->model_num);
Assert(pm != nullptr);
float length = pm->maxs.xyz.z - pm->mins.xyz.z;
Assert(length > 0.0f);
return length;
}
/**
* Get the offset of the actual center of the ship model for the purposes of warping (which may not be the specified center)
*/
void ship_class_get_actual_center(const ship_info *sip, vec3d *center_pos)
{
Assert(sip != nullptr && center_pos != nullptr);
Assert(sip->model_num >= 0);
polymodel *pm = model_get(sip->model_num);
center_pos->xyz.x = (pm->maxs.xyz.x + pm->mins.xyz.x) * 0.5f;
center_pos->xyz.y = (pm->maxs.xyz.y + pm->mins.xyz.y) * 0.5f;
center_pos->xyz.z = (pm->maxs.xyz.z + pm->mins.xyz.z) * 0.5f;
}
// Goober5000
void ship_set_new_ai_class(ship *shipp, int new_ai_class)
{
Assert(shipp);
Assert(new_ai_class >= 0);
ai_info *aip = &Ai_info[shipp->ai_index];
// we hafta change a bunch of stuff here...
aip->ai_class = new_ai_class;
aip->behavior = AIM_NONE;
init_aip_from_class_and_profile(aip, &Ai_classes[new_ai_class], The_mission.ai_profile);
shipp->weapons.ai_class = new_ai_class;
// I think that's everything!
}
// Goober5000
void ship_subsystem_set_new_ai_class(ship *shipp, const char *subsystem, int new_ai_class)
{
Assert(shipp);
Assert(subsystem);
Assert(new_ai_class >= 0);
ship_subsys *ss;
// find the ship subsystem by searching ship's subsys_list
ss = GET_FIRST( &shipp->subsys_list );
while ( ss != END_OF_LIST( &shipp->subsys_list ) )
{
// if we found the subsystem
if ( !subsystem_stricmp(ss->system_info->subobj_name, subsystem))
{
// set ai class
ss->weapons.ai_class = new_ai_class;
return;
}
ss = GET_NEXT( ss );
}
}
// Goober5000 - will attempt to load an insignia bitmap and set it as active for the wing
// copied more or less from managepilot.cpp
void wing_load_squad_bitmap(wing *w)
{
// sanity check
if(w == NULL)
{
return;
}
// make sure one is not already set?!?
Assert (w->wing_insignia_texture == -1);
// try and set the new one
if( w->wing_squad_filename[0] != '\0' )
{
// load duplicate because it might be the same as the player's squad,
// and we don't want to overlap and breed nasty errors when we unload
w->wing_insignia_texture = bm_load_duplicate(w->wing_squad_filename);
// lock is as a transparent texture
if(w->wing_insignia_texture != -1)
{
bm_lock(w->wing_insignia_texture, 16, BMP_TEX_XPARENT);
bm_unlock(w->wing_insignia_texture);
}
}
}
// Goober5000 - needed by new hangar depart code
// check whether this ship has a docking bay
bool ship_has_dock_bay(int shipnum)
{
Assert(shipnum >= 0 && shipnum < MAX_SHIPS);
polymodel *pm;
pm = model_get( Ship_info[Ships[shipnum].ship_info_index].model_num );
Assert( pm );
return ( pm->ship_bay && (pm->ship_bay->num_paths > 0) );
}
// Goober5000
bool ship_useful_for_departure(int shipnum, int /*path_mask*/)
{
Assert( shipnum >= 0 && shipnum < MAX_SHIPS );
// not valid if dying or departing
if (Ships[shipnum].is_dying_or_departing())
return false;
// no dockbay, can't depart to it
if (!ship_has_dock_bay(shipnum))
return false;
// make sure that the bays are not all destroyed
if (ship_fighterbays_all_destroyed(&Ships[shipnum]))
return false;
// in the future, we might want to check bay paths against destroyed fighterbays,
// but that capability doesn't currently exist
// (note, a mask of 0 indicates all paths are valid)
//if (path_mask != 0)
//{
//}
// ship is valid
return true;
}
// Goober5000 - needed by new hangar depart code
// get first ship in ship list with docking bay
int ship_get_ship_for_departure(int team)
{
for (ship_obj *so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so))
{
Assert(so->objnum >= 0);
int shipnum = Objects[so->objnum].instance;
Assert(shipnum >= 0);
if ( (Ships[shipnum].team == team) && ship_useful_for_departure(shipnum) )
return shipnum;
}
// we didn't find anything
return -1;
}
// Goober5000 - check if all fighterbays on a ship have been destroyed
bool ship_fighterbays_all_destroyed(ship *shipp)
{
Assert(shipp);
ship_subsys *subsys;
int num_fighterbay_subsystems = 0;
// check all fighterbay systems
subsys = GET_FIRST(&shipp->subsys_list);
while(subsys != END_OF_LIST(&shipp->subsys_list))
{
// look for fighterbays
if (ship_subsys_is_fighterbay(subsys))
{
num_fighterbay_subsystems++;
// if fighterbay doesn't take damage, we're good
if (!ship_subsys_takes_damage(subsys))
return false;
// if fighterbay isn't destroyed, we're good
if (subsys->current_hits > 0)
return false;
}
// next item
subsys = GET_NEXT(subsys);
}
// if the ship has no fighterbay subsystems at all, it must be an unusual case,
// like the Faustus, so pretend it's okay...
if (num_fighterbay_subsystems == 0)
return false;
// if we got this far, the ship has at least one fighterbay subsystem,
// and all the ones it has are destroyed
return true;
}
// moved here by Goober5000
static bool ship_subsys_is_fighterbay(ship_subsys* ss)
{
Assert(ss);
return !strnicmp(NOX("fighter"), ss->system_info->name, 7);
}
// Goober5000
bool ship_subsys_takes_damage(ship_subsys *ss)
{
Assert(ss);
return (ss->max_hits > SUBSYS_MAX_HITS_THRESHOLD);
}
// Goober5000
void ship_do_submodel_rotation(ship *shipp, model_subsystem *psub, ship_subsys *pss)
{
Assert(shipp);
Assert(psub);
Assert(pss);
// check if we actually can rotate
if ( !(pss->flags[Ship::Subsystem_Flags::Rotates]) ){
return;
}
if (psub->flags[Model::Subsystem_Flags::Triggered]) {
//Triggered rotation is handled by animation stepping.
//The flag doesn't do anything at all anymore, except prevent other rotation types
return;
}
// check for rotating artillery
if ( psub->flags[Model::Subsystem_Flags::Artillery] )
{
ship_weapon *swp = &shipp->weapons;
// rotate only if trigger is down
if ( !(shipp->flags[Ship_Flags::Trigger_down]) )
return;
// check linked
if ( shipp->flags[Ship_Flags::Primary_linked] )
{
int i, ammo_tally = 0;
// calculate ammo
for (i=0; i<swp->num_primary_banks; i++)
ammo_tally += swp->primary_bank_ammo[i];
// do not rotate if out of ammo
if (ammo_tally <= 0)
return;
}
// check unlinked
else
{
// do not rotate if this is not the firing bank or if we have no ammo in this bank
if ((psub->weapon_rotation_pbank != swp->current_primary_bank) || (swp->primary_bank_ammo[swp->current_primary_bank] <= 0))
return;
}
}
// if we got this far, we can rotate - so choose which method to use
if (psub->flags[Model::Subsystem_Flags::Stepped_rotate] ) {
submodel_stepped_rotate(psub, pss->submodel_instance_1);
} else {
submodel_rotate(psub, pss->submodel_instance_1 );
}
}
void ship_move_subsystems(object *objp)
{
Assertion(objp->type == OBJ_SHIP, "ship_move_subsystems should only be called for ships! objp type = %d", objp->type);
auto shipp = &Ships[objp->instance];
for (auto pss = GET_FIRST(&shipp->subsys_list); pss != END_OF_LIST(&shipp->subsys_list); pss = GET_NEXT(pss))
{
auto psub = pss->system_info;
// Don't process destroyed objects (but allow subobjects with hitpoints disabled -nuke) (but also process subobjects that are allowed to rotate)
if (pss->max_hits > 0 && pss->current_hits <= 0.0f && !(psub->flags[Model::Subsystem_Flags::Destroyed_rotation]))
continue;
// do solar/radar/gas/activator rotation here
ship_do_submodel_rotation(shipp, psub, pss);
}
}
// Goober5000
int ship_has_energy_weapons(ship *shipp)
{
// (to avoid round-off errors, weapon reserve is not tested for zero)
return (Ship_info[shipp->ship_info_index].max_weapon_reserve > WEAPON_RESERVE_THRESHOLD);
}
// Goober5000
int ship_has_engine_power(ship *shipp)
{
return (Ship_info[shipp->ship_info_index].max_speed > 0 );
}
// Goober5000
int ship_starting_wing_lookup(const char *wing_name)
{
Assertion(wing_name != nullptr, "NULL wing_name passed to ship_starting_wing_lookup");
for (int i = 0; i < MAX_STARTING_WINGS; i++)
{
if (!stricmp(Starting_wing_names[i], wing_name))
return i;
}
return -1;
}
// Goober5000
int ship_squadron_wing_lookup(const char *wing_name)
{
Assertion(wing_name != nullptr, "NULL wing_name passed to ship_squadron_wing_lookup");
// TvT uses a different set of wing names from everything else
if (MULTI_TEAM)
{
for (int i = 0; i < MAX_TVT_WINGS; i++)
{
if (!stricmp(TVT_wing_names[i], wing_name))
return i;
}
}
else
{
// match duplicate wings, such as Epsilon and Epsilon#clone
auto ch = get_pointer_to_first_hash_symbol(wing_name);
size_t len = (ch != nullptr) ? (ch - wing_name) : strlen(wing_name);
for (int i = 0; i < MAX_SQUADRON_WINGS; i++)
{
if (!strnicmp(Squadron_wing_names[i], wing_name, len))
return i;
}
}
return -1;
}
// Goober5000
int ship_tvt_wing_lookup(const char *wing_name)
{
Assertion(wing_name != nullptr, "NULL wing_name passed to ship_tvt_wing_lookup");
for (int i = 0; i < MAX_TVT_WINGS; i++)
{
if (!stricmp(TVT_wing_names[i], wing_name))
return i;
}
return -1;
}
// Goober5000
static int ship_class_get_priority(int ship_class)
{
ship_info *sip = &Ship_info[ship_class];
// biggest to smallest
if (sip->flags[Info_Flags::Knossos_device])
return 1;
else if (sip->flags[Info_Flags::Supercap])
return 2;
else if (sip->flags[Info_Flags::Drydock])
return 3;
else if (sip->flags[Info_Flags::Capital])
return 4;
else if (sip->flags[Info_Flags::Corvette])
return 5;
else if (sip->flags[Info_Flags::Cruiser])
return 6;
else if (sip->flags[Info_Flags::Gas_miner])
return 7;
else if (sip->flags[Info_Flags::Awacs])
return 8;
else if (sip->flags[Info_Flags::Freighter])
return 9;
else if (sip->flags[Info_Flags::Transport])
return 10;
else if (sip->flags[Info_Flags::Bomber])
return 11;
else if (sip->flags[Info_Flags::Fighter] || sip->flags[Info_Flags::Stealth])
return 12;
else if (sip->flags[Info_Flags::Escapepod])
return 13;
else if (sip->flags[Info_Flags::Sentrygun])
return 14;
else if (sip->flags[Info_Flags::Cargo])
return 15;
else if (sip->flags[Info_Flags::Navbuoy])
return 16;
Warning(LOCATION, "Unknown priority for ship class '%s'!", sip->name);
return 17 + ship_class;
}
// Goober5000
int ship_class_compare(int ship_class_1, int ship_class_2)
{
// grab priorities
int priority1 = ship_class_get_priority(ship_class_1);
int priority2 = ship_class_get_priority(ship_class_2);
// standard compare
if (priority1 < priority2)
return -1;
else if (priority1 > priority2)
return 1;
else
return 0;
}
/**
* Gives the index into the Damage_types[] vector of a specified damage type name
* @return -1 if not found
*/
static int damage_type_get_idx(char *name)
{
//This should never be bigger than INT_MAX anyway
for(int i = 0; i < (int)Damage_types.size(); i++)
{
if(!stricmp(name, Damage_types[i].name))
return i;
}
return -1;
}
/**
* Either loads a new damage type, or returns the index of one with the same name as given
*/
int damage_type_add(char *name)
{
int i = damage_type_get_idx(name);
if(i != -1)
return i;
DamageTypeStruct dts;
strncpy(dts.name, name, NAME_LENGTH-1);
if(strlen(name) > NAME_LENGTH - 1)
{
Warning(LOCATION, "Damage type name '%s' is too long and has been truncated to '%s'", name, dts.name);
}
Damage_types.push_back(dts);
return (int)(Damage_types.size() - 1);
}
void ArmorDamageType::clear()
{
DamageTypeIndex = -1;
Calculations.clear();
Arguments.clear();
altArguments.clear(); // Nuke: don't forget to delete it
}
//************
// Wanderer - beam piercing type
//************
flag_def_list PiercingTypes[] = {
{ "none", SADTF_PIERCING_NONE, 0},
{ "default", SADTF_PIERCING_DEFAULT, 0},
{ "retail", SADTF_PIERCING_RETAIL, 0},
};
const int Num_piercing_effect_types = sizeof(PiercingTypes)/sizeof(flag_def_list);
static int piercing_type_get(char *str)
{
int i;
for(i = 0; i < Num_piercing_effect_types; i++)
{
if(!stricmp(PiercingTypes[i].name, str))
return PiercingTypes[i].def;
}
// default to retail
return SADTF_PIERCING_RETAIL;
}
// Nuke: handle difficulty scaling type
flag_def_list DifficultyScaleTypes[] = {
{ "first", ADT_DIFF_SCALE_FIRST, 0},
{ "last", ADT_DIFF_SCALE_LAST, 0},
{ "manual", ADT_DIFF_SCALE_MANUAL, 0},
};
const int Num_difficulty_scale_types = sizeof(DifficultyScaleTypes)/sizeof(flag_def_list);
static int difficulty_scale_type_get(char *str) {
int i;
for(i = 0; i < Num_difficulty_scale_types; i++){
if (!stricmp(DifficultyScaleTypes[i].name, str))
return DifficultyScaleTypes[i].def;
}
// indicate error
return ADT_DIFF_SCALE_BAD_VAL;
}
// Nuke: flag list for +constant: values
flag_def_list ArmorTypeConstants[] = {
{ "base damage", AT_CONSTANT_BASE_DMG, 0},
{ "current damage", AT_CONSTANT_CURRENT_DMG, 0},
{ "difficulty factor", AT_CONSTANT_DIFF_FACTOR, 0},
{ "random", AT_CONSTANT_RANDOM, 0},
{ "pi", AT_CONSTANT_PI, 0},
};
const int Num_armor_type_constants = sizeof(ArmorTypeConstants)/sizeof(flag_def_list);
static int armor_type_constants_get(char *str){
int i;
for (i = 0; i < Num_armor_type_constants; i++){
if (!stricmp(ArmorTypeConstants[i].name, str))
return ArmorTypeConstants[i].def;
}
// this shouldnt happen, but if it does theirs a define for that
return AT_CONSTANT_BAD_VAL;
}
//**************************************************************
//WMC - All the extra armor crap
//****************************Calculation type addition
//4 steps to add a new one
//Armor types
//STEP 1: Add a define
#define AT_TYPE_ADDITIVE 0
#define AT_TYPE_MULTIPLICATIVE 1
#define AT_TYPE_EXPONENTIAL 2
#define AT_TYPE_EXPONENTIAL_BASE 3
#define AT_TYPE_CUTOFF 4
#define AT_TYPE_REVERSE_CUTOFF 5
#define AT_TYPE_INSTANT_CUTOFF 6
#define AT_TYPE_INSTANT_REVERSE_CUTOFF 7
// Added by Nuke
#define AT_TYPE_CAP 8
#define AT_TYPE_INSTANT_CAP 9
#define AT_TYPE_SET 10
#define AT_TYPE_STORE 11
#define AT_TYPE_LOAD 12
#define AT_TYPE_RANDOM 13
// Nuke: this is the number of storage locations load/store calculations are allowed to use
#define AT_NUM_STORAGE_LOCATIONS 8
//STEP 2: Add the name string to the array
const char *TypeNames[] = {
"additive",
"multiplicative",
"exponential",
"exponential base",
"cutoff",
"reverse cutoff",
"instant cutoff",
"instant reverse cutoff",
// Added by Nuke
"cap",
"instant cap",
"set",
"load",
"store",
"random"
};
const int Num_armor_calculation_types = sizeof(TypeNames)/sizeof(char*);
int calculation_type_get(char *str)
{
for(int i = 0; i < Num_armor_calculation_types; i++)
{
if(!stricmp(TypeNames[i], str))
return i;
}
return -1;
}
//STEP 3: Add the calculation to the switch statement.
float ArmorType::GetDamage(float damage_applied, int in_damage_type_idx, float diff_dmg_scale, int is_beam) {
// Nuke: If the weapon has no damage type, just return damage
if (in_damage_type_idx < 0) {
// multiply by difficulty scaler now, since it is no longer done where this is called
return (damage_applied * diff_dmg_scale);
}
//Initialize vars
ArmorDamageType *adtp = NULL;
//Find the entry in the weapon that corresponds to the given weapon damage type
size_t num = DamageTypes.size();
for(size_t i = 0; i < num; i++)
{
if(DamageTypes[i].DamageTypeIndex == in_damage_type_idx)
{
adtp = &DamageTypes[i];
break;
}
}
//curr_arg is the current calculation type value
float curr_arg;
//Make sure that we _have_ an armor entry for this damage type
if(adtp != NULL)
{
//How many calculations do we have to do?
num = adtp->Calculations.size();
// Used for instant cutoffs/cap, to instantly end the loop
bool end_now = false;
// used for load/store operations
float storage[AT_NUM_STORAGE_LOCATIONS];
int storage_idx;
bool using_storage = false;
// constant related stuff
float constant_val;
float base_damage;
bool using_constant = false;
// set storage locations to zero
for (size_t i = 0; i < AT_NUM_STORAGE_LOCATIONS; i++) {
storage[i]=0.0f;
}
// check to see if we need to difficulty scale damage first
if (adtp->difficulty_scale_type == ADT_DIFF_SCALE_FIRST) {
damage_applied *= diff_dmg_scale;
}
// user may want to use base damage as a constant
base_damage = damage_applied;
// LOOP!
for (size_t i = 0; i < num; i++) {
storage_idx = adtp->altArguments[i];
//Set curr_arg
// use storage index at +Stored Value:
if ( (storage_idx >= 0) && (storage_idx < AT_NUM_STORAGE_LOCATIONS) ) {
curr_arg = storage[storage_idx];
using_storage = true;
// using +value: (or error cases caught at parse, where this holda a 0.0f)
} else if (storage_idx == AT_CONSTANT_NOT_USED) { // save time checking all possible constants when most of the time you will be using +value:
curr_arg = adtp->Arguments[i];
// maybe handle constants
} else if (storage_idx == AT_CONSTANT_BASE_DMG) {
curr_arg = base_damage;
using_constant = true;
} else if (storage_idx == AT_CONSTANT_CURRENT_DMG) {
curr_arg = damage_applied;
using_constant = true;
} else if (storage_idx == AT_CONSTANT_DIFF_FACTOR) {
curr_arg = diff_dmg_scale;
using_constant = true;
} else if (storage_idx == AT_CONSTANT_RANDOM) {
constant_val = frand();
curr_arg = constant_val;
using_constant = true;
} else if (storage_idx == AT_CONSTANT_PI) {
constant_val = PI;
curr_arg = constant_val;
using_constant = true;
} else { // fail
constant_val = 0.0f;
curr_arg = constant_val;
}
//face: terrible hack to work consistently with beams and additive damages
if (is_beam && ( adtp->Calculations[i] == AT_TYPE_ADDITIVE
|| adtp->Calculations[i] == AT_TYPE_CUTOFF
|| adtp->Calculations[i] == AT_TYPE_REVERSE_CUTOFF
|| adtp->Calculations[i] == AT_TYPE_INSTANT_CUTOFF
|| adtp->Calculations[i] == AT_TYPE_INSTANT_REVERSE_CUTOFF
|| adtp->Calculations[i] == AT_TYPE_CAP
|| adtp->Calculations[i] == AT_TYPE_INSTANT_CAP
|| adtp->Calculations[i] == AT_TYPE_SET
|| adtp->Calculations[i] == AT_TYPE_RANDOM)) {
curr_arg = curr_arg * (flFrametime * 1000.0f) / i2fl(BEAM_DAMAGE_TIME);
}
// new calcs go here
switch(adtp->Calculations[i])
{
case AT_TYPE_ADDITIVE:
damage_applied += curr_arg;
break;
case AT_TYPE_MULTIPLICATIVE:
damage_applied *= curr_arg;
break;
case AT_TYPE_EXPONENTIAL:
damage_applied = powf(damage_applied, curr_arg);
break;
case AT_TYPE_EXPONENTIAL_BASE:
damage_applied = powf(curr_arg, damage_applied);
break;
case AT_TYPE_CUTOFF:
if(damage_applied < curr_arg)
damage_applied = 0;
break;
case AT_TYPE_REVERSE_CUTOFF:
if(damage_applied > curr_arg)
damage_applied = 0;
break;
case AT_TYPE_INSTANT_CUTOFF:
if(damage_applied < curr_arg)
{
damage_applied = 0;
end_now = true;
}
break;
case AT_TYPE_INSTANT_REVERSE_CUTOFF:
if(damage_applied > curr_arg)
{
damage_applied = 0;
end_now = true;
}
break;
case AT_TYPE_CAP:
if (damage_applied > curr_arg)
damage_applied = curr_arg;
break;
case AT_TYPE_INSTANT_CAP:
if (damage_applied > curr_arg) {
damage_applied = curr_arg;
end_now = true;
}
break;
case AT_TYPE_SET:
damage_applied = curr_arg;
break;
case AT_TYPE_STORE:
if (using_storage || using_constant) {
Warning(LOCATION, "Cannot use +Stored Value: or +Constant: with +Store:, that would be bad. Skipping calculation.");
} else {
storage_idx = int(floorf(curr_arg));
// Nuke: idiotproof this, no segfault 4 u
if ( (storage_idx < 0) || (storage_idx >= AT_NUM_STORAGE_LOCATIONS) ) {
Warning(LOCATION, "+Value: for +Store: calculation out of range. Should be between 0 and %i. Read: %i, Skipping calculation.", AT_NUM_STORAGE_LOCATIONS, storage_idx);
storage_idx = 0;
} else {
storage[storage_idx] = damage_applied;
}
}
break;
case AT_TYPE_LOAD:
if (using_storage || using_constant) {
Warning(LOCATION, "Cannot use +Stored Value: or +Constant: with +Load:, that would be bad. Skipping calculation.");
} else {
storage_idx = int(floorf(curr_arg));
// Nuke: idiotproof this, no segfault 4 u
if ( (storage_idx < 0) || (storage_idx >= AT_NUM_STORAGE_LOCATIONS) ) {
Warning(LOCATION, "+Value: for +Load: calculation out of range. Should be between 0 and %i. Read: %i, Skipping calculation.", AT_NUM_STORAGE_LOCATIONS, storage_idx);
storage_idx = 0;
} else {
damage_applied = storage[storage_idx];
}
}
break;
case AT_TYPE_RANDOM: // Nuke: get a random number between damage_applied and +value:
if (damage_applied > curr_arg) {
damage_applied = frand_range( curr_arg, damage_applied );
} else {
damage_applied = frand_range( damage_applied, curr_arg );
}
break;
}
if(end_now)
break;
}
// Nuke: check to see if we need to difficulty scale damage last
if (adtp->difficulty_scale_type == ADT_DIFF_SCALE_LAST)
damage_applied *= diff_dmg_scale;
// Face: negative damages should not heal you!!!
if (damage_applied < 0.0f)
damage_applied = 0.0f;
return damage_applied;
}
// fail return is fail
return (damage_applied * diff_dmg_scale);
}
float ArmorType::GetShieldPiercePCT(int damage_type_idx)
{
if(damage_type_idx < 0)
return 0.0f;
//Initialize vars
ArmorDamageType *adtp = NULL;
//Find the entry in the weapon that corresponds to the given weapon damage type
size_t num = DamageTypes.size();
for(size_t i = 0; i < num; i++)
{
if(DamageTypes[i].DamageTypeIndex == damage_type_idx)
{
adtp = &DamageTypes[i];
break;
}
}
if(adtp != NULL){
return adtp->shieldpierce_pct;
}
return 0.0f;
}
int ArmorType::GetPiercingType(int damage_type_idx)
{
if(damage_type_idx < 0)
return 0;
//Initialize vars
ArmorDamageType *adtp = NULL;
//Find the entry in the weapon that corresponds to the given weapon damage type
size_t num = DamageTypes.size();
for(size_t i = 0; i < num; i++)
{
if(DamageTypes[i].DamageTypeIndex == damage_type_idx)
{
adtp = &DamageTypes[i];
break;
}
}
if(adtp != NULL){
return adtp->piercing_type;
}
return 0;
}
float ArmorType::GetPiercingLimit(int damage_type_idx)
{
if(damage_type_idx < 0)
return 0.0f;
//Initialize vars
ArmorDamageType *adtp = NULL;
//Find the entry in the weapon that corresponds to the given weapon damage type
size_t num = DamageTypes.size();
for(size_t i = 0; i < num; i++)
{
if(DamageTypes[i].DamageTypeIndex == damage_type_idx)
{
adtp = &DamageTypes[i];
break;
}
}
if(adtp != NULL){
return adtp->piercing_start_pct;
}
return 0.0f;
}
//***********************************Member functions
ArmorType::ArmorType(const char* in_name)
{
auto len = strlen(in_name);
if(len >= NAME_LENGTH) {
Warning(LOCATION, "Armor name %s is " SIZE_T_ARG " characters too long, and will be truncated", in_name, len - NAME_LENGTH);
}
strncpy(Name, in_name, NAME_LENGTH-1);
}
void ArmorType::ParseData()
{
ArmorDamageType adt;
char buf[NAME_LENGTH];
float temp_float;
int temp_int;
int calc_type = -1;
//Get the damage types
required_string("$Damage Type:");
do
{
//Get damage type name
stuff_string(buf, F_NAME, NAME_LENGTH);
//Clear the struct and set the index
adt.clear();
adt.DamageTypeIndex = damage_type_add(buf);
bool no_content = true;
//Get calculation and argument
while (optional_string("+Calculation:"))
{
//+Calculation
stuff_string(buf, F_NAME, NAME_LENGTH);
calc_type = calculation_type_get(buf);
//Make sure we have a valid calculation type
if(calc_type == -1)
{
Warning(LOCATION, "Armor '%s': Armor calculation type '%s' is invalid, and has been skipped", Name, buf);
// Nuke: guess we need to add this here too
if (optional_string("+Stored Value:")) {
stuff_int(&temp_int);
} else if (optional_string("+Constant:")) {
stuff_string(buf, F_NAME, NAME_LENGTH);
} else {
required_string("+Value:");
stuff_float(&temp_float);
}
}
else
{
adt.Calculations.push_back(calc_type);
// Nuke: maybe were using a stored location
if (optional_string("+Stored Value:")) {
stuff_int(&temp_int);
// Nuke: idiot-proof
if ( (temp_int < 0) || (temp_int >= AT_NUM_STORAGE_LOCATIONS) ) {
Error(LOCATION, "+Stored Value: is out of range. Should be between 0 and %i. Read: %i, Using value 0.", AT_NUM_STORAGE_LOCATIONS-1, temp_int);
temp_int = AT_CONSTANT_NOT_USED;
}
adt.altArguments.push_back(temp_int);
adt.Arguments.push_back(0.0f); // this isnt used in this case, just take up space so the indices lign up, also a fallback value in case of bad altArguments
} else if (optional_string("+Constant:")) { // use one of the pre-defined constants
stuff_string(buf, F_NAME, NAME_LENGTH);
temp_int = armor_type_constants_get(buf);
// Nuke: idiot proof some more
if (temp_int == AT_CONSTANT_BAD_VAL) {
Error(LOCATION, "Invalid +Constant: name, '%s'. Using value 0.", buf);
temp_int = AT_CONSTANT_NOT_USED;
}
adt.altArguments.push_back(temp_int);
adt.Arguments.push_back(0.0f); // this isnt used in this case, just take up space so the indices lign up, also a fallback value in case of bad altArguments
} else { // Nuke: +Value, only required if storage location or constant is not used -nuke
required_string("+Value:");
stuff_float(&temp_float);
adt.altArguments.push_back(AT_CONSTANT_NOT_USED); // set this to AT_CONSTANT_NOT_USED so we know to just use the value from adt.Arguments instead of constants/storage locations
adt.Arguments.push_back(temp_float);
}
no_content = false;
}
}
adt.shieldpierce_pct = 0.0f;
if(optional_string("+Shield Piercing Percentage:")) {
stuff_float(&temp_float);
CLAMP(temp_float, 0.0f, 1.0f);
adt.shieldpierce_pct = temp_float;
no_content = false;
}
adt.piercing_start_pct = 0.1f;
adt.piercing_type = -1;
if(optional_string("+Weapon Piercing Effect Start Limit:")) {
stuff_float(&temp_float);
CLAMP(temp_float, 0.0f, 100.0f);
temp_float /= 100.0f;
adt.piercing_start_pct = temp_float;
no_content = false;
}
if(optional_string("+Weapon Piercing Type:")) {
stuff_string(buf, F_NAME, NAME_LENGTH);
adt.piercing_type = piercing_type_get(buf);
no_content = false;
}
// Nuke: don't forget to init things
adt.difficulty_scale_type = ADT_DIFF_SCALE_FIRST;
if (optional_string("+Difficulty Scale Type:")) {
stuff_string(buf, F_NAME, NAME_LENGTH);
temp_int = difficulty_scale_type_get(buf);
if (temp_int == ADT_DIFF_SCALE_BAD_VAL) {
Error(LOCATION, "Invalid +Difficulty Scale Type: name: '%s'. Reverting to default behavior.", buf);
adt.difficulty_scale_type = ADT_DIFF_SCALE_FIRST;
} else {
adt.difficulty_scale_type = temp_int;
}
no_content = false;
}
//If we have calculations in this damage type, add it
if(!no_content)
{
if(adt.Calculations.size() != adt.Arguments.size())
{
Warning(LOCATION, "Armor '%s', damage type " SIZE_T_ARG ": Armor has a different number of calculation types than arguments (" SIZE_T_ARG ", " SIZE_T_ARG ")",
Name, DamageTypes.size(), adt.Calculations.size(), adt.Arguments.size());
}
DamageTypes.push_back(adt);
}
} while(optional_string("$Damage Type:"));
}
//********************************Global functions
int armor_type_get_idx(const char* name)
{
auto num = Armor_types.size();
for(size_t i = 0; i < num; i++)
{
if(Armor_types[i].IsName(name))
return (int)i;
}
//Didn't find anything.
return -1;
}
void parse_armor_type()
{
char name_buf[NAME_LENGTH];
ArmorType tat("");
required_string("$Name:");
stuff_string(name_buf, F_NAME, NAME_LENGTH);
tat = ArmorType(name_buf);
//now parse the actual table (damage type/armor type pairs)
tat.ParseData();
//rest of the parse data
if (optional_string("$Flags:"))
parse_string_flag_list((int*)&tat.flags, Armor_flags, Num_armor_flags);
//Add it to global armor types
Armor_types.push_back(tat);
}
void armor_parse_table(const char *filename)
{
try
{
read_file_text(filename, CF_TYPE_TABLES);
reset_parse();
//Enumerate through all the armor types and add them.
while (optional_string("#Armor Type")) {
while (required_string_either("#End", "$Name:")) {
parse_armor_type();
continue;
}
required_string("#End");
}
}
catch (const parse::ParseException& e)
{
mprintf(("TABLES: Unable to parse '%s'! Error message = %s.\n", filename, e.what()));
return;
}
}
void armor_init()
{
if (!Armor_inited) {
armor_parse_table("armor.tbl");
parse_modular_table(NOX("*-amr.tbm"), armor_parse_table);
Armor_inited = true;
}
}
//**************************************************************
// AI targeting priority functions
//**************************************************************
void parse_ai_target_priorities()
{
int i, j;
int n_entries = (int)Ai_tp_list.size();
SCP_vector <SCP_string> temp_strings;
bool first_time = false;
int already_exists = -1;
if (n_entries == 0)
first_time = true;
required_string("$Name:");
ai_target_priority temp_priority = init_ai_target_priorities();
stuff_string(temp_priority.name, F_NAME, NAME_LENGTH);
if (first_time == false) {
for (i = 0; i < n_entries; i++) {
if (!strnicmp(temp_priority.name, Ai_tp_list[i].name, NAME_LENGTH)) {
already_exists = i;
}
}
}
if (optional_string("+Object Type:") ) {
char tempname[NAME_LENGTH];
stuff_string(tempname, F_NAME, NAME_LENGTH);
for (j = 0; j < num_ai_tgt_objects; j++) {
if ( !stricmp(ai_tgt_objects[j].name, tempname) ) {
temp_priority.obj_type = ai_tgt_objects[j].def;
}
}
}
if (optional_string("+Weapon Class:") ) {
temp_strings.clear();
stuff_string_list(temp_strings);
for(i = 0; i < (int)temp_strings.size(); i++) {
for(j = 0; j < weapon_info_size(); ++j) {
if ( !stricmp(Weapon_info[j].name, temp_strings[i].c_str()) ) {
temp_priority.weapon_class.push_back(j);
break;
}
}
if (j == weapon_info_size()) {
Warning(LOCATION, "Unidentified weapon class '%s' set for target priority group '%s'\n", temp_strings[i].c_str(), temp_priority.name);
}
}
}
if (optional_string("+Object Flags:") ) {
temp_strings.clear();
stuff_string_list(temp_strings);
for (i = 0; i < (int)temp_strings.size(); i++) {
for (j = 0; j < num_ai_tgt_obj_flags; j++) {
if ( !stricmp(ai_tgt_obj_flags[j].name, temp_strings[i].c_str()) ) {
temp_priority.obj_flags |= ai_tgt_obj_flags[j].def;
break;
}
}
if (j == num_ai_tgt_obj_flags) {
Warning(LOCATION, "Unidentified object flag '%s' set for target priority group '%s'\n", temp_strings[i].c_str(), temp_priority.name);
}
}
}
if (optional_string("+Ship Class Flags:")) {
temp_strings.clear();
stuff_string_list(temp_strings);
for (i = 0; i < (int)temp_strings.size(); i++) {
for (j = 0; j < num_ai_tgt_ship_flags; j++) {
if (!stricmp(ai_tgt_ship_flags[j].name, temp_strings[i].c_str())) {
temp_priority.sif_flags.set(ai_tgt_ship_flags[j].def);
break;
}
}
if (j == num_ai_tgt_ship_flags) {
Warning(LOCATION, "Unidentified ship class flag '%s' set for target priority group '%s'\n", temp_strings[i].c_str(), temp_priority.name);
}
}
}
if (optional_string("+Weapon Class Flags:") ) {
temp_strings.clear();
stuff_string_list(temp_strings);
for (i = 0; i < (int)temp_strings.size(); i++) {
for (j = 0; j < num_ai_tgt_weapon_info_flags; j++) {
if ( !stricmp(ai_tgt_weapon_flags[j].name, temp_strings[i].c_str()) ) {
temp_priority.wif_flags |= ai_tgt_weapon_flags[j].def;
break;
}
}
if (j == num_ai_tgt_weapon_info_flags) {
Warning(LOCATION, "Unidentified weapon class flag '%s' set for target priority group '%s'\n", temp_strings[i].c_str(), temp_priority.name);
}
}
}
temp_strings.clear();
if (already_exists == -1) {
Ai_tp_list.push_back(temp_priority);
} else {
Ai_tp_list[already_exists] = temp_priority;
}
}
ai_target_priority init_ai_target_priorities()
{
ai_target_priority temp_priority;
//initialize the entries
temp_priority.obj_flags.reset();
temp_priority.obj_type = -1;
temp_priority.ship_class.clear();
temp_priority.ship_type.clear();
temp_priority.sif_flags.reset();
temp_priority.weapon_class.clear();
temp_priority.wif_flags.reset();
temp_priority.name[0] = '\0';
//return the initialized
return temp_priority;
}
void parse_weapon_targeting_priorities()
{
char tempname[NAME_LENGTH];
if (optional_string("$Name:")) {
stuff_string(tempname, F_NAME, NAME_LENGTH);
int k = weapon_info_lookup(tempname);
if (k < 0) {
error_display(0, "Unrecognized weapon '%s' found when setting weapon targeting priorities.\n", tempname);
if (optional_string("+Target Priority:")) { // consume the data to avoid parsing errors
SCP_vector<SCP_string> dummy;
stuff_string_list(dummy);
}
} else {
// reset the list
weapon_info *wip = &Weapon_info[k];
wip->num_targeting_priorities = 0;
if (optional_string("+Target Priority:")) {
SCP_vector <SCP_string> tgt_priorities;
stuff_string_list(tgt_priorities);
if (tgt_priorities.size() > 32)
tgt_priorities.resize(32);
auto num_groups = Ai_tp_list.size();
for(size_t i = 0; i < tgt_priorities.size(); i++) {
size_t j;
for(j = 0; j < num_groups; j++) {
if ( !stricmp(Ai_tp_list[j].name, tgt_priorities[i].c_str())) {
wip->targeting_priorities[i] = (int)j;
wip->num_targeting_priorities++;
break;
}
}
if(j == num_groups)
error_display(0, "Unrecognized string '%s' found when setting weapon targeting priorities.\n", tgt_priorities[i].c_str());
}
}
}
}
}
/* Currently unused
static int ship_get_subobj_model_num(ship_info* sip, char* subobj_name)
{
for (int i = 0; i < sip->n_subsystems; i++) {
if (!subsystem_stricmp(sip->subsystems[i].subobj_name, subobj_name))
return sip->subsystems[i].subobj_num;
}
return -1;
}*/
void init_path_metadata(path_metadata& metadata)
{
vm_vec_zero(&metadata.departure_rvec);
vm_vec_zero(&metadata.arrival_rvec);
metadata.arrive_speed_mult = FLT_MIN;
metadata.depart_speed_mult = FLT_MIN;
}
gamesnd_id ship_get_sound(object *objp, GameSounds id)
{
Assert( objp != nullptr);
Assert( gamesnd_game_sound_valid(gamesnd_id(id)) );
// It's possible that this gets called when an object (in most cases the player) is dead or an observer
if (objp->type == OBJ_OBSERVER || objp->type == OBJ_GHOST)
return gamesnd_id(id);
Assertion(objp->type == OBJ_SHIP, "Expected a ship, got '%s'.", Object_type_names[objp->type]);
ship *shipp = &Ships[objp->instance];
ship_info *sip = &Ship_info[shipp->ship_info_index];
SCP_map<GameSounds, gamesnd_id>::iterator element = sip->ship_sounds.find(id);
if (element == sip->ship_sounds.end())
return gamesnd_id(id);
else
return (*element).second;
}
bool ship_has_sound(object *objp, GameSounds id)
{
Assert( objp != nullptr );
Assert( gamesnd_game_sound_valid(id) );
// It's possible that this gets called when an object (in most cases the player) is dead or an observer
if (objp->type == OBJ_OBSERVER || objp->type == OBJ_GHOST)
return false;
Assertion(objp->type == OBJ_SHIP, "Expected a ship, got '%s'.", Object_type_names[objp->type]);
ship *shipp = &Ships[objp->instance];
ship_info *sip = &Ship_info[shipp->ship_info_index];
auto element = sip->ship_sounds.find(id);
if (element == sip->ship_sounds.end())
return false;
else
return true;
}
/**
* Given a ship with bounding box and a point, find the closest point on the bbox
*
* @param ship_objp Object that has the bounding box (should be a ship)
* @param start World position of the point being compared
* @param box_pt OUTPUT PARAMETER: closest point on the bbox to start
*
* @return point is inside bbox, TRUE/1
* @return point is outside bbox, FALSE/0
*/
int get_nearest_bbox_point(object *ship_objp, vec3d *start, vec3d *box_pt)
{
vec3d temp, rf_start;
polymodel *pm;
pm = model_get(Ship_info[Ships[ship_objp->instance].ship_info_index].model_num);
// get start in ship rf
vm_vec_sub(&temp, start, &ship_objp->pos);
vm_vec_rotate(&rf_start, &temp, &ship_objp->orient);
// find box_pt
int inside = project_point_onto_bbox(&pm->mins, &pm->maxs, &rf_start, &temp);
// get box_pt in world rf
vm_vec_unrotate(box_pt, &temp, &ship_objp->orient);
vm_vec_add2(box_pt, &ship_objp->pos);
return inside;
}
void ship_set_thruster_info(mst_info *mst, object *obj, ship *shipp, ship_info *sip)
{
mst->length.xyz.z = obj->phys_info.forward_thrust;
mst->length.xyz.x = obj->phys_info.side_thrust;
mst->length.xyz.y = obj->phys_info.vert_thrust;
// Maybe add noise to thruster geometry.
if (!(sip->flags[Ship::Info_Flags::No_thruster_geo_noise])) {
mst->length.xyz.z *= (1.0f + frand()/5.0f - 0.1f);
mst->length.xyz.y *= (1.0f + frand()/5.0f - 0.1f);
mst->length.xyz.x *= (1.0f + frand()/5.0f - 0.1f);
}
CLAMP(mst->length.xyz.z, -1.0f, 1.0f);
CLAMP(mst->length.xyz.y, -1.0f, 1.0f);
CLAMP(mst->length.xyz.x, -1.0f, 1.0f);
mst->primary_bitmap = shipp->thruster_bitmap;
mst->primary_glow_bitmap = shipp->thruster_glow_bitmap;
mst->secondary_glow_bitmap = shipp->thruster_secondary_glow_bitmap;
mst->tertiary_glow_bitmap = shipp->thruster_tertiary_glow_bitmap;
mst->distortion_bitmap = shipp->thruster_distortion_bitmap;
mst->use_ab = (obj->phys_info.flags & PF_AFTERBURNER_ON) || (obj->phys_info.flags & PF_BOOSTER_ON);
mst->glow_noise = shipp->thruster_glow_noise * sip->thruster_glow_noise_mult;
mst->rotvel = Objects[shipp->objnum].phys_info.rotvel;
mst->glow_rad_factor = sip->thruster01_glow_rad_factor;
mst->secondary_glow_rad_factor = sip->thruster02_glow_rad_factor;
mst->tertiary_glow_rad_factor = sip->thruster03_glow_rad_factor;
mst->glow_length_factor = sip->thruster02_glow_len_factor;
mst->distortion_length_factor = sip->thruster_dist_len_factor;
mst->distortion_rad_factor = sip->thruster_dist_rad_factor;
mst->draw_distortion = sip->draw_distortion;
}
void ship_render_batch_thrusters(object *obj)
{
int num = obj->instance;
ship *shipp = &Ships[num];
ship_info *sip = &Ship_info[Ships[num].ship_info_index];
if ( Rendering_to_shadow_map ) return;
physics_info *pi = &Objects[shipp->objnum].phys_info;
float render_amount;
for ( int i = 0; i < sip->num_maneuvering; i++ ) {
man_thruster *mtp = &sip->maneuvering[i];
render_amount = 0.0f;
//WMC - get us a steady value
vec3d des_vel;
vm_vec_rotate(&des_vel, &pi->desired_vel, &obj->orient);
if(pi->desired_rotvel.xyz.x < 0 && (mtp->use_flags[Ship::Thruster_Flags::Pitch_up])) {
render_amount = fl_abs(pi->desired_rotvel.xyz.x) / pi->max_rotvel.xyz.x;
} else if(pi->desired_rotvel.xyz.x > 0 && (mtp->use_flags[Ship::Thruster_Flags::Pitch_down])) {
render_amount = fl_abs(pi->desired_rotvel.xyz.x) / pi->max_rotvel.xyz.x;
} else if(pi->desired_rotvel.xyz.y < 0 && (mtp->use_flags[Ship::Thruster_Flags::Roll_right])) {
render_amount = fl_abs(pi->desired_rotvel.xyz.y) / pi->max_rotvel.xyz.y;
} else if(pi->desired_rotvel.xyz.y > 0 && (mtp->use_flags[Ship::Thruster_Flags::Roll_left])) {
render_amount = fl_abs(pi->desired_rotvel.xyz.y) / pi->max_rotvel.xyz.y;
} else if(pi->desired_rotvel.xyz.z < 0 && (mtp->use_flags[Ship::Thruster_Flags::Bank_right])) {
render_amount = fl_abs(pi->desired_rotvel.xyz.z) / pi->max_rotvel.xyz.z;
} else if(pi->desired_rotvel.xyz.z > 0 && (mtp->use_flags[Ship::Thruster_Flags::Bank_left])) {
render_amount = fl_abs(pi->desired_rotvel.xyz.z) / pi->max_rotvel.xyz.z;
}
//Backslash - show thrusters according to thrust amount, not speed
if(pi->side_thrust > 0 && (mtp->use_flags[Ship::Thruster_Flags::Slide_right])) {
render_amount = pi->side_thrust;
} else if(pi->side_thrust < 0 && (mtp->use_flags[Ship::Thruster_Flags::Slide_left])) {
render_amount = -pi->side_thrust;
} else if(pi->vert_thrust > 0 && (mtp->use_flags[Ship::Thruster_Flags::Slide_up])) {
render_amount = pi->vert_thrust;
} else if(pi->vert_thrust < 0 && (mtp->use_flags[Ship::Thruster_Flags::Slide_down])) {
render_amount = -pi->vert_thrust;
} else if(pi->forward_thrust > 0 && (mtp->use_flags[Ship::Thruster_Flags::Forward])) {
render_amount = pi->forward_thrust;
} else if(pi->forward_thrust < 0 && (mtp->use_flags[Ship::Thruster_Flags::Reverse])) {
render_amount = -pi->forward_thrust;
}
//Don't render small faraway thrusters (more than 10k * radius away)
if ( vm_vec_dist(&Eye_position, &obj->pos) > (10000.0f * mtp->radius) ) {
render_amount = 0.0f;
}
if ( render_amount > 0.0f ) {
//Handle sounds and stuff
if ( shipp->thrusters_start[i] <= 0 ) {
shipp->thrusters_start[i] = timestamp();
if(mtp->start_snd.isValid())
snd_play_3d( gamesnd_get_game_sound(mtp->start_snd), &mtp->pos, &Eye_position, 0.0f, &obj->phys_info.vel );
}
//Only assign looping sound if
//it is specified
//it isn't assigned already
//start sound doesn't exist or has finished
if (!Cmdline_freespace_no_sound) {
if(mtp->loop_snd.isValid()
&& shipp->thrusters_sounds[i] < 0
&& (!mtp->start_snd.isValid() || (gamesnd_get_max_duration(gamesnd_get_game_sound(mtp->start_snd)) < timestamp() - shipp->thrusters_start[i]))
)
{
shipp->thrusters_sounds[i] = obj_snd_assign(OBJ_INDEX(obj), mtp->loop_snd, &mtp->pos, OS_MAIN);
}
}
//Draw graphics
//Skip invalid ones
if ( mtp->tex_id >= 0 ) {
float rad = mtp->radius;
if(rad <= 0.0f)
rad = 1.0f;
float len = mtp->length;
if(len == 0.0f)
len = rad;
vec3d start, tmpend, end;
//Start
vm_vec_unrotate(&start, &mtp->pos, &obj->orient);
vm_vec_add2(&start, &obj->pos);
//End
vm_vec_scale_add(&tmpend, &mtp->pos, &mtp->norm, len * render_amount);
vm_vec_unrotate(&end, &tmpend, &obj->orient);
vm_vec_add2(&end, &obj->pos);
int bmap_frame = mtp->tex_id;
if(mtp->tex_nframes > 0)
bmap_frame += bm_get_anim_frame(mtp->tex_id, i2fl(timestamp() - shipp->thrusters_start[i]) / 1000.0f, 0.0f, true);
//man_thruster_renderer *mtr = man_thruster_get_slot(bmap_frame);
//mtr->man_batcher.add_allocate(1);
//mtr->man_batcher.draw_beam(&start, &end, rad, 1.0f);
batching_add_beam(bmap_frame, &start, &end, rad, 1.0f);
}
} else if ( shipp->thrusters_start[i] > 0 ) {
// We've stopped firing a thruster
shipp->thrusters_start[i] = 0;
if(shipp->thrusters_sounds[i] >= 0)
{
obj_snd_delete(obj, shipp->thrusters_sounds[i]);
shipp->thrusters_sounds[i] = -1;
}
if ( mtp->stop_snd.isValid() ) {
//Get world pos
vec3d start;
vm_vec_unrotate(&start, &mtp->pos, &obj->orient);
vm_vec_add2(&start, &obj->pos);
snd_play_3d( gamesnd_get_game_sound(mtp->stop_snd), &mtp->pos, &Eye_position, 0.0f, &obj->phys_info.vel );
}
}
}
}
void ship_render_weapon_models(model_render_params *ship_render_info, model_draw_list *scene, object *obj, int render_flags)
{
int num = obj->instance;
ship *shipp = &Ships[num];
ship_info *sip = &Ship_info[Ships[num].ship_info_index];
if ( !(sip->flags[Ship::Info_Flags::Draw_weapon_models]) || (shipp->flags[Ship_Flags::Cloaked]) || (shipp->flags[Ship_Flags::Render_without_weapons]) ) {
return;
}
int i,k;
ship_weapon *swp = &shipp->weapons;
auto ship_pm = model_get(sip->model_num);
scene->push_transform(&obj->pos, &obj->orient);
auto ship_render_flags = ship_render_info->get_model_flags();
render_flags &= ~MR_SHOW_THRUSTERS;
ship_render_info->set_flags(render_flags);
//primary weapons
for ( i = 0; i < swp->num_primary_banks; i++ ) {
auto wip = &Weapon_info[swp->primary_bank_weapons[i]];
if ( wip->external_model_num < 0 || !sip->draw_primary_models[i] ) {
continue;
}
// Lazily create the model instance here, if we need to. The ideal place to put this
// would be in parse_object_create_sub, but the player can alter the ship loadout
// after that function runs.
if (!swp->primary_bank_model_instance_check[i])
{
auto pm = model_get(wip->external_model_num);
// create a model instance only if at least one submodel has gun rotation
for (int mn = 0; mn < pm->n_models; mn++)
{
if (pm->submodel[mn].flags[Model::Submodel_flags::Gun_rotation])
{
swp->primary_bank_external_model_instance[i] = model_create_instance(-1, wip->external_model_num);
break;
}
}
swp->primary_bank_model_instance_check[i] = true;
}
auto bank = &ship_pm->gun_banks[i];
if ( swp->primary_bank_external_model_instance[i] >= 0 )
{
auto pmi = model_get_instance(swp->primary_bank_external_model_instance[i]);
auto pm = model_get(pmi->model_num);
// spin the submodels by the gun rotation
for (int mn = 0; mn < pm->n_models; ++mn)
{
if (pm->submodel[mn].flags[Model::Submodel_flags::Gun_rotation])
{
angles angs = vmd_zero_angles;
angs.b = shipp->primary_rotate_ang[i];
vm_angles_2_matrix(&pmi->submodel[mn].canonical_orient, &angs);
}
}
}
for ( k = 0; k < bank->num_slots; k++ ) {
// "Bank" the external model by the angle offset
angles angs = { 0.0f, bank->external_model_angle_offset[k], 0.0f };
matrix model_orient;
vm_angles_2_matrix(&model_orient, &angs);
model_render_queue(ship_render_info, scene, wip->external_model_num, swp->primary_bank_external_model_instance[i], &model_orient, &bank->pnt[k]);
}
}
//secondary weapons
int num_secondaries_rendered = 0;
vec3d secondary_weapon_pos;
for (i = 0; i < swp->num_secondary_banks; i++) {
auto wip = &Weapon_info[swp->secondary_bank_weapons[i]];
if ( wip->external_model_num == -1 || !sip->draw_secondary_models[i] ) {
continue;
}
auto bank = &ship_pm->missile_banks[i];
if (wip->wi_flags[Weapon::Info_Flags::External_weapon_lnch]) {
for(k = 0; k < bank->num_slots; k++) {
// "Bank" the external model by the angle offset
angles angs = { 0.0f, bank->external_model_angle_offset[k], 0.0f };
matrix model_orient;
vm_angles_2_matrix(&model_orient, &angs);
model_render_queue(ship_render_info, scene, wip->external_model_num, &model_orient, &bank->pnt[k]);
}
} else {
num_secondaries_rendered = 0;
for ( k = 0; k < bank->num_slots; k++ ) {
secondary_weapon_pos = bank->pnt[k];
if ( num_secondaries_rendered >= shipp->weapons.secondary_bank_ammo[i] ) {
break;
}
if ( shipp->secondary_point_reload_pct.get(i, k) <= 0.0 ) {
continue;
}
num_secondaries_rendered++;
vm_vec_scale_add2(&secondary_weapon_pos, &vmd_z_vector, -(1.0f-shipp->secondary_point_reload_pct.get(i, k)) * model_get(wip->external_model_num)->rad);
// "Bank" the external model by the angle offset
angles angs = { 0.0f, bank->external_model_angle_offset[k], 0.0f };
matrix model_orient;
vm_angles_2_matrix(&model_orient, &angs);
model_render_queue(ship_render_info, scene, wip->external_model_num, &model_orient, &secondary_weapon_pos);
}
}
}
ship_render_info->set_flags(ship_render_flags);
scene->pop_transform();
}
int ship_render_get_insignia(object* obj, ship* shipp)
{
if ( Rendering_to_shadow_map ) {
return -1;
}
if ( Game_mode & GM_MULTIPLAYER ) {
// if its any player's object
int np_index = multi_find_player_by_object( obj );
if ( (np_index >= 0) && (np_index < MAX_PLAYERS) && MULTI_CONNECTED(Net_players[np_index]) && (Net_players[np_index].m_player != NULL) ) {
return Net_players[np_index].m_player->insignia_texture;
}
}
// in single player, we want to render model insignias on all ships in alpha beta and gamma
// Goober5000 - and also on wings that have their logos set
// if its an object in my squadron
if ( ship_in_my_squadron(shipp) ) {
return Player->insignia_texture;
}
// maybe it has a wing squad logo - Goober5000
if ( shipp->wingnum >= 0 ) {
// don't override the player's wing
if ( shipp->wingnum != Player_ship->wingnum ) {
// if we have a logo texture
if ( Wings[shipp->wingnum].wing_insignia_texture >= 0 ) {
return Wings[shipp->wingnum].wing_insignia_texture;
}
}
}
return -1;
}
void ship_render_set_animated_effect(model_render_params *render_info, ship *shipp, uint * /*render_flags*/)
{
if ( !shipp->shader_effect_active || Rendering_to_shadow_map ) {
return;
}
float timer;
ship_effect* sep = &Ship_effects[shipp->shader_effect_num];
if ( sep->invert_timer ) {
timer = 1.0f - ((timer_get_milliseconds() - shipp->shader_effect_start_time) / (float)shipp->shader_effect_duration);
timer = MAX(timer,0.0f);
} else {
timer = ((timer_get_milliseconds() - shipp->shader_effect_start_time) / (float)shipp->shader_effect_duration);
}
render_info->set_animated_effect(sep->shader_effect, timer);
if ( sep->disables_rendering && (timer_get_milliseconds() > shipp->shader_effect_start_time + shipp->shader_effect_duration) ) {
shipp->flags.set(Ship_Flags::Cloaked);
shipp->shader_effect_active = false;
} else {
shipp->flags.remove(Ship_Flags::Cloaked);
if (timer_get_milliseconds() > shipp->shader_effect_start_time + shipp->shader_effect_duration) {
shipp->shader_effect_active = false;
}
}
}
void ship_render(object* obj, model_draw_list* scene)
{
int num = obj->instance;
ship *shipp = &Ships[num];
ship_info *sip = &Ship_info[Ships[num].ship_info_index];
ship *warp_shipp = NULL;
bool is_first_stage_arrival = false;
bool show_thrusters = (!shipp->flags[Ship_Flags::No_thrusters]) && !Rendering_to_shadow_map;
dock_function_info dfi;
MONITOR_INC( NumShipsRend, 1 );
// look for a warping ship, whether for me or for anybody I'm docked with
dock_evaluate_all_docked_objects(obj, &dfi, ship_find_warping_ship_helper);
// if any docked objects are set to stage 1 arrival then set bool
if ( dfi.maintained_variables.bool_value ) {
warp_shipp = &Ships[dfi.maintained_variables.objp_value->instance];
is_first_stage_arrival = warp_shipp->flags[Ship_Flags::Arriving_stage_1];
// This is a hack to make ships using the hyperspace warpin type to
// render even in stage 1, which is used for collision detection
// purposes -zookeeper
if ( Warp_params[warp_shipp->warpin_params_index].warp_type == WT_HYPERSPACE ) {
warp_shipp = NULL;
is_first_stage_arrival = false;
}
}
if ( is_first_stage_arrival ) {
//WMC - Draw animated warp effect (ie BSG thingy)
//WMC - based on Bobb's secondary thruster stuff
//which was in turn based on the beam code.
//I'm gonna need some serious acid to neutralize this base.
if(shipp->is_arriving(ship::warpstage::BOTH, true)) {
shipp->warpin_effect->warpShipRender();
} else if(shipp->flags[Ship_Flags::Depart_warp]) {
shipp->warpout_effect->warpShipRender();
}
return;
}
model_render_params render_info;
if ( obj == Viewer_obj && !Rendering_to_shadow_map ) {
if (!(Viewer_mode & VM_TOPDOWN))
{
render_info.set_object_number(OBJ_INDEX(obj));
//To allow the player ship to cast light from first person we must still handle it's glowpoints here.
model_render_only_glowpoint_lights(&render_info, sip->model_num, -1, &obj->orient, &obj->pos);
return;
}
}
auto pmi = model_get_instance(shipp->model_instance_num);
auto pm = model_get(pmi->model_num);
model_clear_instance(sip->model_num);
model_instance_clear_arcs(pm, pmi);
// Only render electrical arcs if within 500m of the eye (for a 10m piece)
if ( vm_vec_dist_quick( &obj->pos, &Eye_position ) < obj->radius*50.0f && !Rendering_to_shadow_map ) {
for ( int i = 0; i < MAX_SHIP_ARCS; i++ ) {
if ( timestamp_valid(shipp->arc_timestamp[i]) ) {
model_instance_add_arc(pm, pmi, -1, &shipp->arc_pts[i][0], &shipp->arc_pts[i][1], shipp->arc_type[i]);
}
}
}
uint render_flags = MR_NORMAL;
if ( shipp->large_ship_blowup_index >= 0 ) {
shipfx_large_blowup_queue_render(scene, shipp);
//WMC - Draw animated warp effect (ie BSG thingy)
//WMC - based on Bobb's secondary thruster stuff
//which was in turn based on the beam code.
//I'm gonna need some serious acid to neutralize this base.
if(shipp->is_arriving(ship::warpstage::BOTH, true)) {
shipp->warpin_effect->warpShipRender();
} else if(shipp->flags[Ship_Flags::Depart_warp]) {
shipp->warpout_effect->warpShipRender();
}
return;
}
ship_render_batch_thrusters(obj);
if ( !(shipp->flags[Ship_Flags::Disabled]) && !ship_subsys_disrupted(shipp, SUBSYSTEM_ENGINE) && show_thrusters) {
mst_info mst;
ship_set_thruster_info(&mst, obj, shipp, sip);
render_info.set_thruster_info(mst);
render_flags |= MR_SHOW_THRUSTERS;
}
if (shipp->flags[Ship_Flags::Render_with_alpha_mult]) {
render_info.set_alpha_mult(shipp->alpha_mult);
}
// Warp_shipp points to the ship that is going through a
// warp... either this ship or the ship it is docked with.
if ( warp_shipp != NULL ) {
if ( warp_shipp->is_arriving(ship::warpstage::BOTH, true) ) {
warp_shipp->warpin_effect->warpShipClip(&render_info);
} else if ( warp_shipp->flags[Ship_Flags::Depart_warp] ) {
warp_shipp->warpout_effect->warpShipClip(&render_info);
}
}
// maybe set squad logo bitmap
render_info.set_insignia_bitmap(ship_render_get_insignia(obj, shipp));
// Valathil - maybe do a scripting hook here to do some scriptable effects?
ship_render_set_animated_effect(&render_info, shipp, &render_flags);
if ( sip->uses_team_colors && !shipp->flags[Ship_Flags::Render_without_miscmap] ) {
team_color model_team_color;
bool team_color_set = model_get_team_color(&model_team_color, shipp->team_name, shipp->secondary_team_name, shipp->team_change_timestamp, shipp->team_change_time);
if ( team_color_set ) {
render_info.set_team_color(model_team_color);
}
}
if ( sip->flags[Ship::Info_Flags::No_lighting] ) {
render_flags |= MR_NO_LIGHTING;
}
if ( Rendering_to_shadow_map ) {
render_flags = MR_NO_TEXTURING | MR_NO_LIGHTING;
}
if (shipp->flags[Ship_Flags::Glowmaps_disabled]) {
render_flags |= MR_NO_GLOWMAPS;
}
if (shipp->flags[Ship_Flags::Draw_as_wireframe]) {
render_flags |= MR_SHOW_OUTLINE_HTL | MR_NO_POLYS | MR_NO_TEXTURING;
render_info.set_color(Wireframe_color);
}
if (shipp->flags[Ship_Flags::Render_full_detail]) {
render_flags |= MR_FULL_DETAIL;
}
if (shipp->flags[Ship_Flags::Render_without_light]) {
render_flags |= MR_NO_LIGHTING;
}
uint debug_flags = render_info.get_debug_flags();
if (shipp->flags[Ship_Flags::Render_without_diffuse]) {
debug_flags |= MR_DEBUG_NO_DIFFUSE;
}
if (shipp->flags[Ship_Flags::Render_without_glowmap]) {
debug_flags |= MR_DEBUG_NO_GLOW;
}
if (shipp->flags[Ship_Flags::Render_without_normalmap]) {
debug_flags |= MR_DEBUG_NO_NORMAL;
}
if (shipp->flags[Ship_Flags::Render_without_ambientmap]) {
debug_flags |= MR_DEBUG_NO_AMBIENT;
}
if (shipp->flags[Ship_Flags::Render_without_specmap]) {
debug_flags |= MR_DEBUG_NO_SPEC;
}
if (shipp->flags[Ship_Flags::Render_without_reflectmap]) {
debug_flags |= MR_DEBUG_NO_REFLECT;
}
render_info.set_flags(render_flags);
render_info.set_debug_flags(debug_flags);
//draw weapon models
ship_render_weapon_models(&render_info, scene, obj, render_flags);
render_info.set_object_number(OBJ_INDEX(obj));
render_info.set_replacement_textures(shipp->ship_replacement_textures);
// small ships
if ( !( shipp->flags[Ship_Flags::Cloaked] ) ) {
if ( ( The_mission.flags[Mission::Mission_Flags::Fullneb] ) && ( sip->is_small_ship() ) ) {
// force detail levels
float fog_val = neb2_get_fog_visibility(&obj->pos, 1.0f);
if ( fog_val <= 0.15f ) {
render_info.set_detail_level_lock(2);
}
}
model_render_queue(&render_info, scene, sip->model_num, &obj->orient, &obj->pos);
}
if (shipp->shield_hits && !Rendering_to_shadow_map) {
create_shield_explosion_all(obj);
shipp->shield_hits = 0;
}
//WMC - Draw animated warp effect (ie BSG thingy)
//WMC - based on Bobb's secondary thruster stuff
//which was in turn based on the beam code.
//I'm gonna need some serious acid to neutralize this base.
if(shipp->is_arriving(ship::warpstage::BOTH, true)) {
shipp->warpin_effect->warpShipRender();
} else if(shipp->flags[Ship_Flags::Depart_warp]) {
shipp->warpout_effect->warpShipRender();
}
}
flagset<Ship::Ship_Flags> Ignore_List;
void set_default_ignore_list() {
Ignore_List.reset();
Ignore_List.set(Ship::Ship_Flags::Exploded);
Ignore_List.set(Ship::Ship_Flags::Depart_warp);
Ignore_List.set(Ship::Ship_Flags::Dying);
Ignore_List.set(Ship::Ship_Flags::Arriving_stage_1);
Ignore_List.set(Ship::Ship_Flags::Arriving_stage_1_dock_follower);
Ignore_List.set(Ship::Ship_Flags::Hidden_from_sensors);
}
void toggle_ignore_list_flag(Ship::Ship_Flags flag) {
if (Ignore_List[flag])
Ignore_List.remove(flag);
else
Ignore_List.set(flag);
}
ship_subsys* ship_get_subsys_for_submodel(ship* shipp, int submodel)
{
ship_subsys* subsys;
if (submodel == -1) {
return nullptr;
}
for (subsys = GET_FIRST(&shipp->subsys_list); subsys != END_OF_LIST(&shipp->subsys_list);
subsys = GET_NEXT(subsys)) {
if (subsys->system_info->subobj_num == submodel) {
return subsys;
}
}
return nullptr;
}
/**
* Is the requested type of ship arriving in the requested stage?
*
* Dock leaders (plus single ships) & dock followers need to be handled
* separately such that leaders+singles handle the warp process, but
* followers need to behave as though they are warping with respect to
* HUD targeting, drawing thrusters, radar, AI targeting, etc
*
* @param stage Check one of stage1, stage2 or both
* @param dock_leader_or_single check dock-leaders+singles, or dock-followers?
*
* @return is the ship arriving, bool
*/
bool ship::is_arriving(ship::warpstage stage, bool dock_leader_or_single)
{
if (stage == ship::warpstage::BOTH) {
if (!dock_leader_or_single) {
return flags[Ship::Ship_Flags::Arriving_stage_1, Ship::Ship_Flags::Arriving_stage_1_dock_follower, Ship::Ship_Flags::Arriving_stage_2, Ship::Ship_Flags::Arriving_stage_2_dock_follower];
}
else {
return flags[Ship::Ship_Flags::Arriving_stage_1, Ship::Ship_Flags::Arriving_stage_2];
}
}
else if (stage == ship::warpstage::STAGE1) {
if (!dock_leader_or_single) {
return flags[Ship::Ship_Flags::Arriving_stage_1, Ship::Ship_Flags::Arriving_stage_1_dock_follower];
}
else {
return flags[Ship::Ship_Flags::Arriving_stage_1];
}
}
if (stage == ship::warpstage::STAGE2) {
if (!dock_leader_or_single) {
return flags[Ship::Ship_Flags::Arriving_stage_2, Ship::Ship_Flags::Arriving_stage_2_dock_follower];
}
else {
return flags[Ship::Ship_Flags::Arriving_stage_2];
}
}
// should never reach here
Assertion(false, "ship::is_arriving didn't handle all possible states; get a coder!");
return false;
}
void ship_clear_lock(lock_info *slot) {
vec3d zero_vec = ZERO_VECTOR;
slot->accumulated_x_pixels = 0;
slot->accumulated_y_pixels = 0;
slot->catch_up_distance = 0.0f;
slot->catching_up = 0;
slot->current_target_sx = -1;
slot->current_target_sy = -1;
slot->dist_to_lock = 0.0f;
slot->indicator_start_x = -1;
slot->indicator_start_y = -1;
slot->indicator_visible = false;
slot->indicator_x = -1;
slot->indicator_y = -1;
slot->last_dist_to_target = 0.0f;
slot->lock_anim_time_elapsed = 0.0f;
slot->lock_gauge_time_elapsed = 0.0f;
slot->maintain_lock_count = 0;
slot->need_new_start_pos = false;
slot->target_in_lock_cone = false;
slot->world_pos = zero_vec;
slot->locked = false;
slot->obj = nullptr;
slot->subsys = nullptr;
slot->time_to_lock = -1.0f;
}
void ship_queue_missile_locks(ship *shipp)
{
shipp->missile_locks_firing.clear();
// queue up valid missile locks
std::copy_if(shipp->missile_locks.begin(), shipp->missile_locks.end(), std::back_inserter(shipp->missile_locks_firing), [](lock_info lock) { return lock.locked; });
}
bool ship_lock_present(ship *shipp)
{
return std::any_of(shipp->missile_locks.begin(), shipp->missile_locks.end(), [](lock_info lock) { return lock.locked; });
}
bool ship_start_secondary_fire(object* objp)
{
Assert( objp != nullptr);
if ( objp->type != OBJ_SHIP ) {
return false;
}
int n = objp->instance;
Assert( n >= 0 && n < MAX_SHIPS );
Assert( Ships[n].objnum == OBJ_INDEX(objp) );
ship *shipp = &Ships[n];
ship_info *sip = &Ship_info[shipp->ship_info_index];
ship_weapon* swp = &shipp->weapons;
int bank = swp->current_secondary_bank;
if ( bank < 0 || bank >= sip->num_secondary_banks ) {
return false;
}
// It's possible for banks to be empty without issue
// but indices outside the weapon_info range are a problem
Assert( swp->secondary_bank_weapons[bank] < weapon_info_size() );
if ( (swp->secondary_bank_weapons[bank] < 0) || (swp->secondary_bank_weapons[bank] >= weapon_info_size()) ) {
return false;
}
weapon_info *wip = &Weapon_info[swp->secondary_bank_weapons[bank]];
if ( wip->trigger_lock ) {
swp->flags.set(Ship::Weapon_Flags::Trigger_Lock);
return true;
}
return false;
}
bool ship_stop_secondary_fire(object* objp)
{
Assert( objp != nullptr);
if ( objp->type != OBJ_SHIP ) {
return false;
}
int n = objp->instance;
if ( n < 0 || n >= MAX_SHIPS ) {
return false;
}
Assert( Ships[n].objnum == OBJ_INDEX(objp) );
ship *shipp = &Ships[n];
ship_info *sip = &Ship_info[shipp->ship_info_index];
ship_weapon *swp = &shipp->weapons;
int bank = swp->current_secondary_bank;
if ( bank < 0 || bank >= sip->num_secondary_banks ) {
return false;
}
// It's possible for banks to be empty without issue
// but indices outside the weapon_info range are a problem
Assert(swp->secondary_bank_weapons[bank] < weapon_info_size());
if ((swp->secondary_bank_weapons[bank] < 0) || (swp->secondary_bank_weapons[bank] >= weapon_info_size())) {
return false;
}
weapon_info *wip = &Weapon_info[swp->secondary_bank_weapons[bank]];
if ( wip->trigger_lock && swp->flags[Ship::Weapon_Flags::Trigger_Lock]) {
swp->flags.remove(Ship::Weapon_Flags::Trigger_Lock);
return true;
}
return false;
}
bool ship_secondary_has_ammo(ship_weapon* swp, int bank_index) {
return swp->secondary_bank_ammo[bank_index] > 0 || Weapon_info[swp->secondary_bank_weapons[bank_index]].wi_flags[Weapon::Info_Flags::SecondaryNoAmmo];
}