/* * 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 #include #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 "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 "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" #include "tracing/Monitor.h" #include "tracing/tracing.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_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 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; 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 Ships_exited; SCP_vector Ship_registry; SCP_unordered_map 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; reinforcements Reinforcements[MAX_REINFORCEMENTS]; SCP_vector Ship_templates; SCP_vector Ship_types; SCP_vector Armor_types; SCP_vector 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 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); // Goober5000 - I figured we should keep this separate // from Comm_orders, considering how I redid it :p // (and also because we may want to change either // the order text or the flag text in the future) flag_def_list Player_orders[] = { // common stuff { "attack ship", ATTACK_TARGET_ITEM, 0 }, { "disable ship", DISABLE_TARGET_ITEM, 0 }, { "disarm ship", DISARM_TARGET_ITEM, 0 }, { "disable subsys", DISABLE_SUBSYSTEM_ITEM, 0 }, { "guard ship", PROTECT_TARGET_ITEM, 0 }, { "ignore ship", IGNORE_TARGET_ITEM, 0 }, { "form on wing", FORMATION_ITEM, 0 }, { "cover me", COVER_ME_ITEM, 0 }, { "attack any", ENGAGE_ENEMY_ITEM, 0 }, // transports mostly { "dock", CAPTURE_TARGET_ITEM, 0 }, // support ships { "rearm me", REARM_REPAIR_ME_ITEM, 0 }, { "abort rearm", ABORT_REARM_REPAIR_ITEM, 0 }, // all ships { "depart", DEPART_ITEM, 0 }, // extra stuff for support { "stay near me", STAY_NEAR_ME_ITEM, 0 }, { "stay near ship", STAY_NEAR_TARGET_ITEM, 0 }, { "keep safe dist", KEEP_SAFE_DIST_ITEM, 0 } }; const int Num_player_orders = sizeof(Player_orders)/sizeof(flag_def_list); // Use the last parameter here to tell the parser whether to stuff the flag into flags or flags2 flag_def_list_new 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); flag_def_list_new 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 }, // 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); /* ++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 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); flag_def_list_new 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); flag_def_list_new 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 }, { "stream", Weapon::Info_Flags::Stream, 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 }, }; const int num_ai_tgt_weapon_info_flags = sizeof(ai_tgt_weapon_flags) / sizeof(flag_def_list_new); SCP_vector 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"}, }; 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_type_counts; SCP_vector Wing_formations; const std::shared_ptr 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; static int ship_get_subobj_model_num(ship_info* sip, char* subobj_name); SCP_vector 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; iwingnum == Starting_wings[i]) return 1; } for (i=0; iwingnum == 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 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 { for ( int i = 0; i < n_subsystems; i++ ) { for ( int j = 0; j < subsystems[i].n_triggers; j++ ) { if ( i < other.n_subsystems ) { subsystems[i].triggers = (queued_animation*)vm_realloc(subsystems[i].triggers, sizeof(queued_animation) * (other.subsystems[i].n_triggers)); } else { vm_free(subsystems[i].triggers); } } } delete[] subsystems; subsystems = new model_subsystem[other.n_subsystems]; } Assert(subsystems != nullptr); for ( int i = n_subsystems; i < other.n_subsystems; i++ ) { subsystems[i].triggers = (queued_animation*) vm_malloc(sizeof(queued_animation) * (other.subsystems[i].n_triggers)); } for ( int i = 0; i < other.n_subsystems; i++ ) { queued_animation* triggers = subsystems[i].triggers; subsystems[i] = other.subsystems[i]; subsystems[i].triggers = triggers; memcpy(subsystems[i].triggers, other.subsystems[i].triggers, sizeof(queued_animation) * (subsystems[i].n_triggers)); } } 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; 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; 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; 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; memcpy(ship_iff_info, other.ship_iff_info, sizeof(int) * MAX_IFFS * MAX_IFFS); 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; 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; 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(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; 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; memcpy(ship_iff_info, other.ship_iff_info, sizeof(int) * MAX_IFFS * MAX_IFFS); 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(glowpoint_bank_override_map, other.glowpoint_bank_override_map); } #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,j; 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; 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; 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; 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; for (i=0;i &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(index)); } static SCP_vector 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 && !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); } } 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); } 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, ¶ms.snd_start); sprintf(str, "%s End Sound:", prefix); parse_game_sound(str, ¶ms.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); 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::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(°rees); sip->collision_physics.landing_max_angle = cosf(fl_radians(90.0f - degrees)); } if(optional_string("+Landing Min Angle:")) { float degrees; stuff_float(°rees); sip->collision_physics.landing_min_angle = cosf(fl_radians(90.0f - degrees)); } if(optional_string("+Landing Max Rotate Angle:")) { float degrees; stuff_float(°rees); 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(°rees); sip->collision_physics.reorient_max_angle = cosf(fl_radians(90.0f - degrees)); } if(optional_string("+Reorient Min Angle:")) { float degrees; stuff_float(°rees); sip->collision_physics.reorient_min_angle = cosf(fl_radians(90.0f - degrees)); } if(optional_string("+Reorient Max Rotate Angle:")) { float degrees; stuff_float(°rees); 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(°rees); 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 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; // 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("$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 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;; tex_id = bm_load_animation(name_tmp, &tex_nframes, &tex_fps, nullptr, nullptr, true); 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; } } 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 tokens; tokens.clear(); stuff_string_list(tokens); for(SCP_vector::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::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 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 rvec and store on the path_metadata object 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; } 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; in_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; iprimary_banks[i] = -1; sp->primary_bank_capacity[i] = 0; } for (auto i=0; isecondary_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->n_triggers = 0; sp->triggers = NULL; 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_y_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 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 errors; flagset 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_alt_math); 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")) { queued_animation *current_trigger; sp->triggers = (queued_animation*)vm_realloc(sp->triggers, sizeof(queued_animation) * (sp->n_triggers + 1)); Verify(sp->triggers != NULL); //add a new trigger current_trigger = &sp->triggers[sp->n_triggers]; queued_animation_init(current_trigger); sp->n_triggers++; required_string("$type:"); char atype[NAME_LENGTH]; stuff_string(atype, F_NAME, NAME_LENGTH); current_trigger->type = model_anim_match_type(atype); if(optional_string("+sub_type:")){ stuff_int(¤t_trigger->subtype); }else{ current_trigger->subtype = ANIMATION_SUBTYPE_ALL; } if(optional_string("+sub_name:")) { stuff_string(current_trigger->sub_name, F_NAME, NAME_LENGTH); } else { strcpy_s(current_trigger->sub_name, ""); } if(current_trigger->type == AnimationTriggerType::Initial){ //the only thing initial animation type needs is the angle, //so to save space lets just make everything optional in this case if(optional_string("+delay:")) stuff_int(¤t_trigger->start); else current_trigger->start = 0; if ( optional_string("+reverse_delay:") ) stuff_int(¤t_trigger->reverse_start); else current_trigger->reverse_start = 0; if(optional_string("+absolute_angle:")){ current_trigger->absolute = true; stuff_vec3d(¤t_trigger->angle ); current_trigger->angle.xyz.x = fl_radians(current_trigger->angle.xyz.x); current_trigger->angle.xyz.y = fl_radians(current_trigger->angle.xyz.y); current_trigger->angle.xyz.z = fl_radians(current_trigger->angle.xyz.z); }else{ current_trigger->absolute = false; if(!optional_string("+relative_angle:")) required_string("+relative_angle:"); stuff_vec3d(¤t_trigger->angle ); current_trigger->angle.xyz.x = fl_radians(current_trigger->angle.xyz.x); current_trigger->angle.xyz.y = fl_radians(current_trigger->angle.xyz.y); current_trigger->angle.xyz.z = fl_radians(current_trigger->angle.xyz.z); } if(optional_string("+velocity:")){ stuff_vec3d(¤t_trigger->vel ); current_trigger->vel.xyz.x = fl_radians(current_trigger->vel.xyz.x); current_trigger->vel.xyz.y = fl_radians(current_trigger->vel.xyz.y); current_trigger->vel.xyz.z = fl_radians(current_trigger->vel.xyz.z); } if(optional_string("+acceleration:")){ stuff_vec3d(¤t_trigger->accel ); current_trigger->accel.xyz.x = fl_radians(current_trigger->accel.xyz.x); current_trigger->accel.xyz.y = fl_radians(current_trigger->accel.xyz.y); current_trigger->accel.xyz.z = fl_radians(current_trigger->accel.xyz.z); } if(optional_string("+time:")) stuff_int(¤t_trigger->end ); else current_trigger->end = 0; }else{ if(optional_string("+delay:")) stuff_int(¤t_trigger->start); else current_trigger->start = 0; if ( optional_string("+reverse_delay:") ) stuff_int(¤t_trigger->reverse_start); else current_trigger->reverse_start = -1; //have some code figure this out for us if(optional_string("+absolute_angle:")){ current_trigger->absolute = true; stuff_vec3d(¤t_trigger->angle ); current_trigger->angle.xyz.x = fl_radians(current_trigger->angle.xyz.x); current_trigger->angle.xyz.y = fl_radians(current_trigger->angle.xyz.y); current_trigger->angle.xyz.z = fl_radians(current_trigger->angle.xyz.z); }else{ current_trigger->absolute = false; required_string("+relative_angle:"); stuff_vec3d(¤t_trigger->angle ); current_trigger->angle.xyz.x = fl_radians(current_trigger->angle.xyz.x); current_trigger->angle.xyz.y = fl_radians(current_trigger->angle.xyz.y); current_trigger->angle.xyz.z = fl_radians(current_trigger->angle.xyz.z); } required_string("+velocity:"); stuff_vec3d(¤t_trigger->vel ); current_trigger->vel.xyz.x = fl_radians(current_trigger->vel.xyz.x); current_trigger->vel.xyz.y = fl_radians(current_trigger->vel.xyz.y); current_trigger->vel.xyz.z = fl_radians(current_trigger->vel.xyz.z); if (optional_string("+acceleration:")){ stuff_vec3d(¤t_trigger->accel ); current_trigger->accel.xyz.x = fl_radians(current_trigger->accel.xyz.x); current_trigger->accel.xyz.y = fl_radians(current_trigger->accel.xyz.y); current_trigger->accel.xyz.z = fl_radians(current_trigger->accel.xyz.z); } else { current_trigger->accel.xyz.x = 0.0f; current_trigger->accel.xyz.y = 0.0f; current_trigger->accel.xyz.z = 0.0f; } if(optional_string("+time:")) stuff_int(¤t_trigger->end ); else current_trigger->end = 0; if(optional_string("$Sound:")){ parse_game_sound("+Start:", ¤t_trigger->start_sound); parse_game_sound("+Loop:", ¤t_trigger->loop_sound); parse_game_sound("+End:", ¤t_trigger->end_sound); required_string("+Radius:"); stuff_float(¤t_trigger->snd_rad ); }else{ current_trigger->start_sound = gamesnd_id(); current_trigger->loop_sound = gamesnd_id(); current_trigger->end_sound = gamesnd_id(); current_trigger->snd_rad = 0; } } //make sure that the amount of time it takes to accelerate up and down doesn't make it go farther than the angle queued_animation_correct(current_trigger); } 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 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()); 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]; } model_anim_fix_reverse_times(sip); } 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; } 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]; } 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); } 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); } //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 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:")) { parse_string_flag_list(&stp->ai_player_orders, Player_orders, Num_player_orders); } 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); } } 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) { if (sip->allowed_bank_restricted_weapons.empty()) return false; for (int i = 0; i < MAX_SHIP_PRIMARY_BANKS; i++) { 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 specified for non-ballistic-primary-enabled ship %s.\nResetting capacities to 0.\nTo fix this, add a ballistic primary to the list of allowed primaries.\n", sip->name); for (j = 0; j < MAX_SHIP_PRIMARY_BANKS; j++) { sip->primary_bank_ammo_capacity[j] = 0; } } } else { 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; Triggered_rotations.clear(); } 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; iship_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(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. */ int ship_get_default_orders_accepted( ship_info *sip ) { if(sip->class_type >= 0) { return Ship_types[sip->class_type].ai_player_orders; } else { return 0; } } 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, j; 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 = 0; 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; } for (i = 0; i < MAX_SHIP_SECONDARY_BANKS; i++) { for (j = 0; j < MAX_SLOTS; j++) secondary_point_reload_pct[i][j] = 1.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; 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(); for(i=0;i &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] = rand32(); 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; } 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 (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)); } 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); } 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++ ) { 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); } 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; } /** * 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, 0, 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, 0, 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, 0, OS_TURRET_GUN_ROTATION, ship_system); ship_system->subsys_snd_flags.set(Ship::Subsys_Sound_Flags::Turret_rotation); } } if ((ship_system->flags[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, 0, 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, 0, 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; turn_rate = 0.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); 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 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); ship_system->turn_rate = model_system->turn_rate; // 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; kprimary_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; ksecondary_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; kweapons.num_secondary_banks; k++) { 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; kweapons.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 float turn_accel = 0.5f; if (ship_system->submodel_instance_1 != nullptr) model_set_submodel_turn_info(ship_system->submodel_instance_1, model_system->turn_rate, turn_accel); // Allocate a triggered rotation instance if we need it if (model_system->flags[Model::Subsystem_Flags::Triggered]) { ship_system->triggered_rotation_index = (int)Triggered_rotations.size(); triggered_rotation tr; Triggered_rotations.push_back(tr); } } if ( !ignore_subsys_info ) { ship_recalc_subsys_strength( shipp ); } // Fix up animation code references for (i = 0; i < sinfo->n_subsystems; i++) { for (j = 0; j < sinfo->subsystems[i].n_triggers; j++) { if (stricmp(sinfo->subsystems[i].triggers[j].sub_name, "") != 0) { int idx = ship_get_subobj_model_num(sinfo, sinfo->subsystems[i].triggers[j].sub_name); if (idx != -1) { sinfo->subsystems[i].triggers[j].subtype = idx; } else { WarningEx(LOCATION, "Could not find subobject %s in ship class %s. Animation triggers will not work correctly.\n", sinfo->subsystems[i].triggers[j].sub_name, sinfo->name); } } } } 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); gr_set_proj_matrix(Proj_fov, gr_screen.clip_aspect, 0.02f, 10.0f*pm->rad); gr_set_view_matrix(&leaning_position, &Eye_matrix); //Deal with the model model_clear_instance(sip->cockpit_model_num); 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); } 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; 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) { Assert(ship_objp->type == OBJ_SHIP); Assert(!(ship_objp == Player_obj)); // undocking and death preparation ship_stop_fire_primary(ship_objp); ai_deathroll_start(ship_objp); mission_log_add_entry(LOG_SELF_DESTRUCTED, Ships[ship_objp->instance].ship_name, NULL ); // scripting stuff 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); 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"}); } if (Ship_info[shipp->ship_info_index].is_big_or_huge()) { float mission_time = f2fl(Missiontime); int minutes = (int)(mission_time / 60); int seconds = (int)mission_time % 60; switch (cleanup_mode) { case SHIP_DESTROYED: mprintf(("%s destroyed at %02d:%02d\n", shipp->ship_name, minutes, seconds)); break; case SHIP_DEPARTED: case SHIP_DEPARTED_WARP: case SHIP_DEPARTED_BAY: mprintf(("%s departed at %02d:%02d\n", shipp->ship_name, minutes, seconds)); break; case SHIP_VANISHED: mprintf(("%s vanished at %02d:%02d\n", shipp->ship_name, minutes, seconds)); break; default: break; } } // 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 ); 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 model_anim_start_type(docked_shipp, AnimationTriggerType::Docked, dockee_index, -1); model_anim_start_type(docked_shipp, AnimationTriggerType::Docking_Stage3, dockee_index, -1); model_anim_start_type(docked_shipp, AnimationTriggerType::Docking_Stage2, dockee_index, -1); model_anim_start_type(docked_shipp, AnimationTriggerType::Docking_Stage1, dockee_index, -1); // 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); // 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 + rand()%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 + rand()%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; zzdeath_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.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 + rand()%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; } } // 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); } } // 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; idxobjnum].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<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<system_info->type); } ss = GET_NEXT( ss ); } if ( engines_disabled ) { if ( !(sp->subsys_disrupted_flags & (1<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<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"); Assertion(Ships[num].objnum == OBJ_INDEX(obj), "Ship objnum does not match its num in OBJ_INDEX in ship_evaluate_ai"); ship* shipp = &Ships[num]; //rotate player subobjects since its processed by the ai functions // AL 2-19-98: Fire turret for player if it exists //WMC - changed this to call process_subobjects if ((obj->flags[Object::Object_Flags::Player_ship]) && !Player_use_ai) { ai_info* aip = &Ai_info[Ships[obj->instance].ai_index]; if (aip->ai_flags[AI::AI_Flags::Being_repaired, AI::AI_Flags::Awaiting_repair]) { if (aip->support_ship_objnum >= 0) { if (vm_vec_dist_quick(&obj->pos, &Objects[aip->support_ship_objnum].pos) < (obj->radius + Objects[aip->support_ship_objnum].radius) * 1.25f) return; } } if (!shipp->flags[Ship::Ship_Flags::Rotators_locked]) process_subobjects(OBJ_INDEX(obj)); } // update ship lethality if (Ships[num].ai_index >= 0 ){ if (!physics_paused && !ai_paused) { lethality_decay(&Ai_info[Ships[num].ai_index]); } } // if the ship is an observer ship don't need to do AI if ( obj->type == OBJ_OBSERVER) { return; } // Goober5000 - player may want to use AI if ( (Ships[num].ai_index >= 0) && (!(obj->flags[Object::Object_Flags::Player_ship]) || Player_use_ai) ){ if (!physics_paused && !ai_paused){ ai_process( obj, Ships[num].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; iship_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; iship_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_damaged_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 ) { model_anim_handle_multiplayer( &Ships[num] ); 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 (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] = rand32(); 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] = rand32(); } //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= 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 shipp->model_instance_num = model_create_instance(true, sip->model_num); 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; in_detail_levels; i++ ) pm->detail_depth[i] = (i < sip->num_detail_levels) ? i2fl(sip->detail_distance[i]) : 0.0f; flagset 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->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); model_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(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]; // 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; in_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 target_matches; // tells us to check this Ai_info index during subsystem traversal SCP_list 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 homing_matches; // any missiles locked on to a subsystem from this ship SCP_list> homing_subsystem_matches; // When homing_matches find matches, they put them here SCP_list weapon_turret_matches; // projectiles that were fired from turrets on this ship SCP_list> weapon_turret_subsystem_matches; SCP_list last_targeted_matches; SCP_list> 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(true, 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->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->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; iprimary_animation_position[i] = MA_POS_NOT_SET; } for( i = 0; isecondary_animation_position[i] = MA_POS_NOT_SET; } model_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) { int old_defaults = ship_get_default_orders_accepted(sip_orig); int 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 != NULL) { // clear them out because the new positions may be different for (i = 0; i < MAX_REPLACEMENT_TEXTURES; i++) sp->ship_replacement_textures[i] = -1; // now fill them in according to texture name for (SCP_vector::iterator tr = p_objp->replacement_textures.begin(); tr != p_objp->replacement_textures.end(); ++tr) { 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 = myrand(); } 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_play_primary_fail_sound() { ship_weapon *swp = &Player_ship->weapons; int stampval; hud_start_flash_weapon(swp->current_primary_bank); 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_play_secondary_fail_sound(weapon_info *wip) { hud_start_flash_weapon(Player_ship->weapons.num_primary_banks + Player_ship->weapons.current_secondary_bank); 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; inum_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 stream_weapons, 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 we're firing stream weapons, but the trigger is not down, do nothing if(stream_weapons && !(shipp->flags[Ship_Flags::Trigger_down])){ return 0; } if(num_primary_banks == 1) for(i = 0; inum_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); } 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 == BEAM_TYPE_C) ){ ship_start_targeting_laser(shipp); continue; } // Cyborg17 - In rollback mode we don't need to worry about stream weapons or timestamps, because we are recreating an exact shot, anywway. if (!rollback_shot) { // if we're firing stream weapons and this is a non stream weapon, skip it if (stream_weapons && !(winfo_p->wi_flags[Weapon::Info_Flags::Stream])) { continue; } // if we're firing non stream weapons and this is a stream weapon, skip it if (!stream_weapons && (winfo_p->wi_flags[Weapon::Info_Flags::Stream])) { continue; } // 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; } // 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; } } if (winfo_p->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] = rand32(); } 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; } } polymodel *pm = model_get( sip->model_num ); // 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_play_primary_fail_sound(); } ship_stop_fire_primary_bank(obj, bank_to_fire); continue; } if ( pm->n_guns > 0 ) { int num_slots = pm->gun_banks[bank_to_fire].num_slots; 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{ points = num_slots; } if ( shipp->weapon_energy < points*winfo_p->energy_consumed*flFrametime) { swp->next_primary_fire_stamp[bank_to_fire] = timestamp((int)(next_fire_delay)); if ( obj == Player_obj ) { ship_maybe_play_primary_fail_sound(); } 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; for ( v = 0; v < points; v++ ){ if(winfo_p->b_info.beam_shots){ j = (shipp->last_fired_point[bank_to_fire]+1)%num_slots; shipp->last_fired_point[bank_to_fire] = j; }else{ j=v; } fbfire_info.targeting_laser_offset = 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) if ( (shipp->weapon_energy < points*numtimes * winfo_p->energy_consumed) //was num_slots && !force ) { swp->next_primary_fire_stamp[bank_to_fire] = timestamp((int)(next_fire_delay)); if ( obj == Player_obj ) { ship_maybe_play_primary_fail_sound(); } 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_play_primary_fail_sound(); } 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; sgun_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_play_primary_fail_sound(); } 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<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]; 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 == BEAM_TYPE_C)){ 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 == BEAM_TYPE_C)){ 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.targeting_laser_offset = 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()) { 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 && (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 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 && (obj == Player_obj || (MULTIPLAYER_MASTER && obj->flags[Object::Object_Flags::Player_ship] ))) { shipp->num_corkscrew_to_fire = (ubyte)shipp->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] = rand32(); } 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_play_secondary_fail_sound(wip) ) { 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 { 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; } if ( check_ammo && ( swp->secondary_bank_ammo[bank] <= 0) ) { if ( shipp->objnum == OBJ_INDEX(Player_obj) ) { if ( ship_maybe_play_secondary_fail_sound(wip) ) { // 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; 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) ) { 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[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_play_secondary_fail_sound(wip); } 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 ){ swp->secondary_bank_ammo[bank]--; } } } } 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 (swp->secondary_bank_ammo[bank] <= 0) { // 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]; 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]) && (swp->secondary_bank_ammo[bank] <= 0) && (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]; 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 ( swp->secondary_bank_ammo[new_bank] <= 0 ) 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]; 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; iweapons.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; Assert(objp->type == OBJ_SHIP); Assert((objp->instance >= 0) && (objp->instance < MAX_SHIPS)); shipp = &Ships[objp->instance]; for (i=0; iweapons.num_secondary_banks; i++) if (shipp->weapons.secondary_bank_ammo[i]) { 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; inum_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;idxname)) 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= 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; } /** * Like ship_model_start but fills submodel instances instead of the submodels themselves */ void ship_model_update_instance(object *objp) { model_subsystem *psub; ship *shipp; ship_subsys *pss; 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); // Handle subsystem rotations for this ship 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_RADAR: case SUBSYSTEM_NAVIGATION: case SUBSYSTEM_COMMUNICATION: case SUBSYSTEM_UNKNOWN: case SUBSYSTEM_ENGINE: case SUBSYSTEM_SENSORS: case SUBSYSTEM_WEAPONS: case SUBSYSTEM_SOLAR: case SUBSYSTEM_GAS_COLLECT: case SUBSYSTEM_ACTIVATION: case SUBSYSTEM_TURRET: break; default: Error(LOCATION, "Illegal subsystem type.\n"); } if ( psub->subobj_num >= 0 ) { model_update_instance(pm, pmi, psub->subobj_num, pss->flags ); } if ( (psub->subobj_num != psub->turret_gun_sobj) && (psub->turret_gun_sobj >= 0) ) { model_update_instance(pm, pmi, psub->turret_gun_sobj, pss->flags ); } } // Handle intrinsic rotations for this ship model_do_intrinsic_rotations(shipp->model_instance_num); } /** * 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].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]]; //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_valid(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_valid(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_valid(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; iship_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; vec3d engine_pos; ship_subsys *moveup; Assert( sp->objnum >= 0 ); if(sp->objnum < 0){ return; } objp = &Objects[sp->objnum]; sip = &Ship_info[sp->ship_info_index]; if ( sip->engine_snd.isValid() ) { vm_vec_copy_scale(&engine_pos, &objp->orient.vec.fvec, -objp->radius/2.0f); obj_snd_assign(sp->objnum, sip->engine_snd, &engine_pos, 1); } // Do subsystem sounds moveup = GET_FIRST(&sp->subsys_list); while(moveup != END_OF_LIST(&sp->subsys_list)){ // 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, 0); } else if(strstr(moveup->system_info->name, "enginehuge")){ obj_snd_assign(sp->objnum, GameSounds::ENGINE_LOOP_HUGE, &moveup->system_info->pnt, 0); } //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, 0, 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, 0, 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, 0, 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, 0, 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, 0, OS_SUBSYS_DEAD, moveup); moveup->subsys_snd_flags.set(Ship::Subsys_Sound_Flags::Dead); } } // next moveup = GET_NEXT(moveup); } } /** * 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; idxobjsnd_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 [--status] \n"); dc_printf(" 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(" 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(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 = (rand() % 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 = (rand() % 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 = (rand() % 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; if ( swp->current_secondary_bank == -1 ) return 0; Assert(swp->current_secondary_bank >= 0 && swp->current_secondary_bank < MAX_SHIP_SECONDARY_BANKS ); if ( swp->secondary_bank_ammo[swp->current_secondary_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) { ai_info *aip; int time, seconds, minutes; 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 ) { 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::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) { return NULL; } if ( (Objects[sp->objnum].phys_info.speed <= 0) || (max_speed <= 0.0f) ) { time = -1; } else { 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) ) { time = hud_get_dock_time( objp ); } else { // don't return anytime for time to except for waypoints and actual docking. return NULL; } if ( time >= 0 ) { minutes = time/60; seconds = time%60; if ( minutes > 99 ) { minutes = 99; seconds = 99; } sprintf(outbuf, NOX("%02d:%02d"), minutes, seconds); } else { strcpy( outbuf, XSTR( "Unknown", 497) ); } return outbuf; } /* 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 ( myrand()&1 ) { 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 (rand() % 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) { 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 (rand() & 1) 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 ( model_anim_start_type(sp, AnimationTriggerType::PrimaryBank, i, 1) ) { swp->primary_animation_done_time[i] = model_anim_get_time_type(sp, AnimationTriggerType::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 ( model_anim_start_type(sp, AnimationTriggerType::PrimaryBank, i, 1) ) { swp->primary_animation_done_time[i] = model_anim_get_time_type(sp, AnimationTriggerType::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) { model_anim_start_type(sp, AnimationTriggerType::PrimaryBank, i, -1); 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 ( model_anim_start_type(sp, AnimationTriggerType::SecondaryBank, i, 1) ) { swp->secondary_animation_done_time[i] = model_anim_get_time_type(sp, AnimationTriggerType::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) { model_anim_start_type(sp, AnimationTriggerType::SecondaryBank, i, -1); 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_get_SIF(ship *shipp) { return Ship_info[shipp->ship_info_index].flags; } flagset 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 ) { 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]++; // check if we are going to use a Knossos device and make sure the special warp ani gets pre-loaded if ( Ship_info[Ships[i].ship_info_index].flags[Ship::Info_Flags::Knossos_device] ) Knossos_warp_ani_used = 1; // 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; iinsignia_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 -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; } } int texture = bm_load(new_name); if (shipp->ship_replacement_textures == NULL) { 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; } // 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 -- Cyborg17 Has an incomplete implementation for now. Enemy teams can basically "steal" the opponent's SS and break multi. // A fix is on the todo list. // 3) dogfight -- never if(!MULTI_COOP) { 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 = (rand() % 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 model_anim_start_type(shipp, AnimationTriggerType::Docked, docker_index, -1); model_anim_start_type(shipp, AnimationTriggerType::Docking_Stage3, docker_index, -1); model_anim_start_type(shipp, AnimationTriggerType::Docking_Stage2, docker_index, -1); model_anim_start_type(shipp, AnimationTriggerType::Docking_Stage1, docker_index, -1); model_anim_start_type(cargo_shipp, AnimationTriggerType::Docked, dockee_index, -1); model_anim_start_type(cargo_shipp, AnimationTriggerType::Docking_Stage3, dockee_index, -1); model_anim_start_type(cargo_shipp, AnimationTriggerType::Docking_Stage2, dockee_index, -1); model_anim_start_type(cargo_shipp, AnimationTriggerType::Docking_Stage1, dockee_index, -1); // 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_rotating_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_rotating_submodel = find_parent_rotating_submodel(pm, docker_index); matrix dock_orient; find_adjusted_dockpoint_info(&pos, &dock_orient, objp, pm, docker_rotating_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= 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 == BEAM_TYPE_C)); 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 != BEAM_TYPE_C)){ 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); if ( !strnicmp(NOX("fighter"), ss->system_info->name, 7) ) { return true; } return false; } // 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] && pss->triggered_rotation_index >= 0) { Triggered_rotations[pss->triggered_rotation_index].process_queue(); model_anim_submodel_trigger_rotate(psub, pss ); 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; inum_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 ); } } // 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 { for (int i = 0; i < MAX_SQUADRON_WINGS; i++) { if (!stricmp(Squadron_wing_names[i], wing_name)) 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 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 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 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()); } } } } } 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); metadata.arrive_speed_mult = FLT_MIN; metadata.depart_speed_mult = FLT_MIN; } gamesnd_id ship_get_sound(object *objp, GameSounds id) { Assert( objp != NULL ); 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::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 != NULL ); 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, 1); } } //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_INDEX(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; 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].gun_rotation) { swp->primary_bank_external_model_instance[i] = model_create_instance(false, wip->external_model_num); break; } } swp->primary_bank_model_instance_check[i] = true; } auto bank = &model_get(sip->model_num)->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].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++ ) { model_render_queue(ship_render_info, scene, wip->external_model_num, swp->primary_bank_external_model_instance[i], &vmd_identity_matrix, &bank->pnt[k]); } } //secondary weapons int num_secondaries_rendered = 0; vec3d secondary_weapon_pos; w_bank* bank; for (i = 0; i < swp->num_secondary_banks; i++) { if ( Weapon_info[swp->secondary_bank_weapons[i]].external_model_num == -1 || !sip->draw_secondary_models[i] ) { continue; } bank = &(model_get(sip->model_num))->missile_banks[i]; if (Weapon_info[swp->secondary_bank_weapons[i]].wi_flags[Weapon::Info_Flags::External_weapon_lnch]) { for(k = 0; k < bank->num_slots; k++) { model_render_queue(ship_render_info, scene, Weapon_info[swp->secondary_bank_weapons[i]].external_model_num, &vmd_identity_matrix, &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[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[i][k]) * model_get(Weapon_info[swp->secondary_bank_weapons[i]].external_model_num)->rad); model_render_queue(ship_render_info, scene, Weapon_info[swp->secondary_bank_weapons[i]].external_model_num, &vmd_identity_matrix, &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; } if ( obj == Viewer_obj && !Rendering_to_shadow_map ) { if (!(Viewer_mode & VM_TOPDOWN)) { 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); model_render_params render_info; 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; } // 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; } 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_intensity(obj); if ( fog_val >= 0.6f ) { 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 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; }