/* * 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 "asteroid/asteroid.h" #include "cmdline/cmdline.h" #include "debris/debris.h" #include "freespace.h" // for flFrametime #include "gamesnd/gamesnd.h" #include "globalincs/alphacolors.h" #include "globalincs/linklist.h" #include "graphics/matrix.h" #include "hud/hudartillery.h" #include "hud/hudbrackets.h" #include "hud/hudlock.h" #include "hud/hudmessage.h" #include "hud/hudparse.h" #include "hud/hudreticle.h" #include "hud/hudshield.h" #include "hud/hudtarget.h" #include "hud/hudtargetbox.h" #include "iff_defs/iff_defs.h" #include "io/timer.h" #include "jumpnode/jumpnode.h" #include "localization/localize.h" #include "mission/missionhotkey.h" #include "mission/missionmessage.h" #include "model/model.h" #include "network/multi.h" #include "network/multiutil.h" #include "object/object.h" #include "libs/renderdoc/renderdoc.h" #include "parse/parselo.h" #include "playerman/player.h" #include "render/3dinternal.h" #include "ship/awacs.h" #include "ship/ship.h" #include "ship/subsysdamage.h" #include "weapon/emp.h" #include "weapon/weapon.h" // Global values for the target bracket width and height, used for debugging int Hud_target_w, Hud_target_h; // offscreen triangle that point the the off-screen target float Offscreen_tri_base[GR_NUM_RESOLUTIONS] = { 6.0f, 9.5f }; float Offscreen_tri_height[GR_NUM_RESOLUTIONS] = { 7.0f, 11.0f }; float Max_offscreen_tri_seperation[GR_NUM_RESOLUTIONS] = { 10.0f, 16.0f }; float Max_front_seperation[GR_NUM_RESOLUTIONS] = { 10.0f, 16.0f }; SCP_vector target_display_list; // The following variables are global to this file, and do not need to be persistent from frame-to-frame // This means the variables are not player-specific object* hostile_obj = NULL; static int ballistic_hud_index = 0; // Goober5000 extern object obj_used_list; // dummy node in linked list of active objects extern char *Cargo_names[]; // shader is used to shade the target box shader Training_msg_glass; // the target triangle (that orbits the reticle) dimensions float Target_triangle_base[GR_NUM_RESOLUTIONS] = { 6.0f, 9.5f }; float Target_triangle_height[GR_NUM_RESOLUTIONS] = { 7.0f, 11.0f }; // stuff for hotkey targeting lists htarget_list htarget_items[MAX_HOTKEY_TARGET_ITEMS]; htarget_list htarget_free_list; /* // coordinates and widths used to render the HUD afterburner energy gauge int current_hud->Aburn_coords[GR_NUM_RESOLUTIONS][4] = { { // GR_640 171, 265, 60, 60 }, { // GR_1024 274, 424, 86, 96 } }; // coordinates and widths used to render the HUD weapons energy gauge int current_hud->Wenergy_coords[GR_NUM_RESOLUTIONS][4] = { { // GR_640 416, 265, 60, 60 }, { // GR_1024 666, 424, 86, 96 } };*/ #define MIN_DISTANCE_TO_CONSIDER_THREAT 1500 // min distance to show hostile warning triangle ////////////////////////////////////////////////////////////////////////// // lists for target in reticle cycling ////////////////////////////////////////////////////////////////////////// #define RL_USED (1<<0) #define RL_USE_DOT (1<<1) // use dot product result, not distance typedef struct _reticle_list { _reticle_list *next, *prev; object *objp; float dist, dot; int flags; } reticle_list; #define RESET_TARGET_IN_RETICLE 750 int Reticle_save_timestamp; reticle_list Reticle_cur_list; reticle_list Reticle_save_list; #define MAX_RETICLE_TARGETS 50 reticle_list Reticle_list[MAX_RETICLE_TARGETS]; ////////////////////////////////////////////////////////////////////////// // used for closest target cycling ////////////////////////////////////////////////////////////////////////// #define TL_RESET 1500 #define TURRET_RESET 1000 static int Tl_hostile_reset_timestamp; static int Tl_friendly_reset_timestamp; static int Target_next_uninspected_object_timestamp; static int Target_newest_ship_timestamp; static int Target_next_turret_timestamp; // animation frames for the hud targeting gauges // frames: 0 => out of range lead // 1 => in range lead float Lead_indicator_half[NUM_HUD_RETICLE_STYLES][GR_NUM_RESOLUTIONS][2] = { { { // GR_640 12.5f, // half-width 12.5f // half-height }, { // GR_1024 20.0f, // half-width 20.0f // half-height } }, { { // GR_640 8.0f, // half-width 8.0f // half-height }, { // GR_1024 13.0f, // half-width 13.0f // half-height } } }; hud_frames Lead_indicator_gauge; int Lead_indicator_gauge_loaded = 0; char Lead_fname[NUM_HUD_RETICLE_STYLES][GR_NUM_RESOLUTIONS][MAX_FILENAME_LEN] = { { "lead1_fs1", "2_lead1_fs1" }, { "lead1", "2_lead1" } }; // animation frames for the countermeasures gauge // frames: 0 => background hud_frames Cmeasure_gauge; int Cmeasure_gauge_loaded = 0; int Cm_coords[GR_NUM_RESOLUTIONS][2] = { { // GR_640 497, 343 }, { // GR_1024 880, 602 } }; int Cm_text_coords[GR_NUM_RESOLUTIONS][2] = { { // GR_640 533, 347 }, { // GR_1024 916, 606 } }; int Cm_text_val_coords[GR_NUM_RESOLUTIONS][2] = { { // GR_640 506, 347 }, { // GR_1024 889, 606 } }; char Cm_fname[GR_NUM_RESOLUTIONS][MAX_FILENAME_LEN] = { "countermeasure1", "countermeasure1" }; #define TOGGLE_TEXT_AUTOT 0 #define TOGGLE_TEXT_TARGET 1 #define TOGGLE_TEXT_AUTOS 2 #define TOGGLE_TEXT_SPEED 3 int Toggle_text_alpha = 255; // animation files for the weapons gauge #define NUM_WEAPON_GAUGES 5 hud_frames Weapon_gauges[NUM_HUD_SETTINGS][NUM_WEAPON_GAUGES]; hud_frames New_weapon; int Weapon_gauges_loaded = 0; // for primaries int Weapon_gauge_primary_coords[NUM_HUD_SETTINGS][GR_NUM_RESOLUTIONS][3][2] = { { // normal HUD { // GR_640 // based on the # of primaries {509, 273}, // top of weapon gauge, first frame, always {497, 293}, // for the first primary {497, 305} // for the second primary }, { // GR_1024 // based on the # of primaries {892, 525}, // top of weapon gauge, first frame, always {880, 545}, // for the first primary {880, 557} // for the second primary } }, { // ballistic HUD - slightly different alignment { // GR_640 // based on the # of primaries {485, 273}, // top of weapon gauge, first frame, always {485, 293}, // for the first primary {485, 305} // for the second primary }, { // GR_1024 // based on the # of primaries {868, 525}, // top of weapon gauge, first frame, always {868, 545}, // for the first primary {868, 557} // for the second primary } } }; int Weapon_gauge_secondary_coords[NUM_HUD_SETTINGS][GR_NUM_RESOLUTIONS][5][2] = { { // normal HUD { // GR_640 // based on the # of secondaries {497, 318}, // bottom of gauge, 0 secondaries {497, 318}, // bottom of gauge, 1 secondaries {497, 317}, // middle of gauge, 2 secondaries AND middle of gauge, 3 secondaries {497, 326}, // bottom of gauge, 2 secondaries AND middle of gauge, 3 secondaries {497, 335} // bottom of gauge, 3 secondaries }, { // GR_1024 // based on the # of secondaries {880, 570}, // bottom of gauge, 0 secondaries {880, 570}, // bottom of gauge, 1 secondaries {880, 569}, // middle of gauge, 2 secondaries AND middle of gauge, 3 secondaries {880, 578}, // bottom of gauge, 2 secondaries AND middle of gauge, 3 secondaries {880, 587} // bottom of gauge, 3 secondaries } }, { // ballistic HUD - slightly different alignment { // GR_640 // based on the # of secondaries {485, 318}, // bottom of gauge, 0 secondaries {485, 318}, // bottom of gauge, 1 secondaries {485, 317}, // middle of gauge, 2 secondaries AND middle of gauge, 3 secondaries {485, 326}, // bottom of gauge, 2 secondaries AND middle of gauge, 3 secondaries {485, 335} // bottom of gauge, 3 secondaries }, { // GR_1024 // based on the # of secondaries {868, 570}, // bottom of gauge, 0 secondaries {868, 570}, // bottom of gauge, 1 secondaries {868, 569}, // middle of gauge, 2 secondaries AND middle of gauge, 3 secondaries {868, 578}, // bottom of gauge, 2 secondaries AND middle of gauge, 3 secondaries {868, 587} // bottom of gauge, 3 secondaries } } }; int Weapon_title_coords[NUM_HUD_SETTINGS][GR_NUM_RESOLUTIONS][2] = { { // normal HUD { // GR_640 518, 274 }, { // GR_1024 901, 527 } }, { // ballistic HUD - slightly different alignment { // GR_640 487, 274 }, { // GR_1024 870, 527 } } }; int Weapon_plink_coords[GR_NUM_RESOLUTIONS][2][2] = { { // GR_640 {530, 285}, // fire-linked thingie, for the first primary {530, 295} // fire-linked thingie, for the second primary }, { // GR_1024 {913, 537}, // fire-linked thingie, for the first primary {913, 547} // fire-linked thingie, for the second primary } }; int Weapon_pname_coords[GR_NUM_RESOLUTIONS][2][2] = { { // GR_640 {536, 285}, // weapon name, first primary {536, 295} // weapon name, second primary }, { // GR_1024 {919, 537}, // weapon name, first primary {919, 547} // weapon name, second primary } }; int Weapon_slinked_x[GR_NUM_RESOLUTIONS] = { 525, // where to draw the second thingie if this weapon is fire-linked 908 }; int Weapon_sunlinked_x[GR_NUM_RESOLUTIONS] = { 530, // where to draw the first thingie if this weapon is selected at all (fire-linked or not) 913 }; int Weapon_secondary_y[GR_NUM_RESOLUTIONS][3] = { { // GR_640 309, // y location of where to draw text for the first secondary 318, // y location of where to draw text for the second secondary 327 // y location of where to draw text for the third secondary }, { // GR_1024 561, // y location of where to draw text for the third secondary 570, // y location of where to draw text for the third secondary 579 // y location of where to draw text for the third secondary } }; int Weapon_primary_y[GR_NUM_RESOLUTIONS][2] = { { // GR_640 285, // y location of where to draw text for the first primary 295 // y location of where to draw text for the second primary }, { // GR_1024 537, // y location of where to draw text for the first primary 547 // y location of where to draw text for the second primary } }; int Weapon_secondary_name_x[GR_NUM_RESOLUTIONS] = { 536, // x location of where to draw weapon name 919 }; int Weapon_secondary_ammo_x[GR_NUM_RESOLUTIONS] = { 525, // x location of where to draw weapon ammo count 908 }; int Weapon_primary_ammo_x[GR_NUM_RESOLUTIONS] = { 525, // x location of where to draw primary weapon ammo count 908 }; int Weapon_secondary_reload_x[GR_NUM_RESOLUTIONS] = { 615, // x location of where to draw the weapon reload time 998 }; const char *Weapon_gauge_fnames[NUM_HUD_SETTINGS][GR_NUM_RESOLUTIONS][NUM_WEAPON_GAUGES] = { //XSTR:OFF { // normal HUD { // GR_640 "weapons1", "weapons2", "weapons3", "weapons4", "weapons5" }, { // GR_1024 "weapons1", "weapons2", "weapons3", "weapons4", "weapons5" } }, { // ballistic HUD - slightly different alignment { // GR_640 "weapons1_b", "weapons2_b", "weapons3_b", "weapons4_b", "weapons5_b" }, { // GR_1024 "weapons1_b", "weapons2_b", "weapons3_b", "weapons4_b", "weapons5_b" } } //XSTR:ON }; // Flash the line for a weapon. This normally occurs when the player tries to fire that // weapon, but the firing fails (due to lack of energy or damaged weapons subsystem). #define MAX_WEAPON_FLASH_LINES 7 // 3 primary and 4 secondary typedef struct weapon_flash { int flash_duration[MAX_WEAPON_FLASH_LINES]; int flash_next[MAX_WEAPON_FLASH_LINES]; int is_bright; } weapon_flash; weapon_flash Weapon_flash_info; // Data used for the proximity warning typedef struct homing_beep_info { sound_handle snd_handle; // sound handle for last played beep fix last_time_played; // time beep was last played int min_cycle_time; // time (in ms) for fastest cycling of the sound int max_cycle_time; // time (in ms) for slowest cycling of the sound float min_cycle_dist; // distance at which fastest cycling occurs float max_cycle_dist; // distance at which slowest cycling occurs float precalced_interp; // a precalculated value used in a linear interpretation } homing_beep_info; homing_beep_info Homing_beep = {sound_handle::invalid(), 0, 150, 1000, 30.0f, 1500.0f, 1.729412f}; // Set at the start of a mission, used to decide how to draw the separation for the warning missile indicators float Min_warning_missile_dist; float Max_warning_missile_dist; void hud_maybe_flash_weapon(int index); // if a given object should be ignored because of AWACS effects int hud_target_invalid_awacs(object *objp) { // if objp is ship object, first check if can be targeted with team info if (objp->type == OBJ_SHIP) { if (Player_ship != NULL) { if (ship_is_visible_by_team(objp, Player_ship)) { return 0; } } } // check for invalid status if((Player_ship != NULL) && (awacs_get_level(objp, Player_ship) < 1.0f)){ return 1; } // valid return 0; } ship_subsys *advance_subsys(ship_subsys *cur, int next_flag) { if (next_flag) { return GET_NEXT(cur); } else { return GET_LAST(cur); } } // select a sorted turret subsystem on a ship if no other subsys has been selected void hud_maybe_set_sorted_turret_subsys(ship *shipp) { Assert((Player_ai->target_objnum >= 0) && (Player_ai->target_objnum < MAX_OBJECTS)); if (!((Player_ai->target_objnum >= 0) && (Player_ai->target_objnum < MAX_OBJECTS))) { return; } Assert(Objects[Player_ai->target_objnum].type == OBJ_SHIP); if (Objects[Player_ai->target_objnum].type != OBJ_SHIP) { return; } if (Ship_info[shipp->ship_info_index].is_big_or_huge()) { if (shipp->last_targeted_subobject[Player_num] == NULL) { hud_target_live_turret(1, 1); } } } // ----------------------------------------------------------------------- // clear out the linked list of targets in the reticle void hud_reticle_clear_list(reticle_list *rlist) { reticle_list *cur; for ( cur = GET_FIRST(rlist); cur != END_OF_LIST(rlist); cur = GET_NEXT(cur) ) { cur->flags = 0; } list_init(rlist); } // -------------------------------------------------------------------------------------- // hud_reticle_list_init() void hud_reticle_list_init() { int i; for ( i = 0; i < MAX_RETICLE_TARGETS; i++ ) { Reticle_list[i].flags = 0; } Reticle_save_timestamp = 1; list_init(&Reticle_save_list); list_init(&Reticle_cur_list); } // -------------------------------------------------------------------------------------- // hud_check_reticle_list() // // void hud_check_reticle_list() { reticle_list *rl, *temp; // cull dying objects from reticle list rl = GET_FIRST(&Reticle_cur_list); while( rl !=END_OF_LIST(&Reticle_cur_list) ) { temp = GET_NEXT(rl); if ( rl->objp->flags[Object::Object_Flags::Should_be_dead] ) { list_remove( &Reticle_cur_list, rl ); rl->flags = 0; } rl = temp; } if ( timestamp_elapsed(Reticle_save_timestamp) ) { hud_reticle_clear_list(&Reticle_save_list); Reticle_save_timestamp = timestamp(RESET_TARGET_IN_RETICLE); } } // -------------------------------------------------------------------------------------- // hud_reticle_list_find_free() // // int hud_reticle_list_find_free() { int i; // find a free reticle_list element for ( i = 0; i < MAX_RETICLE_TARGETS; i++ ) { if ( !(Reticle_list[i].flags & RL_USED) ) { break; } } if ( i == MAX_RETICLE_TARGETS ) { // nprintf(("Warning","Warning ==> Ran out of reticle target elements...\n")); return -1; } return i; } // -------------------------------------------------------------------------------------- // hud_stuff_reticle_list() // // #define RETICLE_DEFAULT_DIST 100000.0f #define RETICLE_DEFAULT_DOT 1.0f void hud_stuff_reticle_list(reticle_list *rl, object *objp, float measure, int dot_flag) { if ( dot_flag ) { rl->dot = measure; rl->dist = RETICLE_DEFAULT_DIST; rl->flags |= RL_USE_DOT; } else { rl->dist = measure; rl->dot = RETICLE_DEFAULT_DOT; } rl->objp = objp; } // -------------------------------------------------------------------------------------- // hud_reticle_list_update() // // Update Reticle_cur_list with an object that lies in the reticle // // parmeters: objp => object pointer to target // measure => distance or dot product, depending on dot_flag // dot_flag => if 0, measure is distance, if 1 measure is dot // void hud_reticle_list_update(object *objp, float measure, int dot_flag) { reticle_list *rl, *new_rl; int i; SCP_list::iterator jnp; if (objp->type == OBJ_JUMP_NODE) { for (jnp = Jump_nodes.begin(); jnp != Jump_nodes.end(); ++jnp) { if( jnp->GetSCPObject() != objp ) continue; if( jnp->IsHidden() ) return; } } for ( rl = GET_FIRST(&Reticle_cur_list); rl != END_OF_LIST(&Reticle_cur_list); rl = GET_NEXT(rl) ) { if ( rl->objp == objp ) return; } i = hud_reticle_list_find_free(); if ( i == -1 ) return; new_rl = &Reticle_list[i]; new_rl->flags |= RL_USED; hud_stuff_reticle_list(new_rl, objp, measure, dot_flag); int was_inserted = 0; if ( EMPTY(&Reticle_cur_list) ) { list_insert(&Reticle_cur_list, new_rl); was_inserted = 1; } else { for ( rl = GET_FIRST(&Reticle_cur_list); rl != END_OF_LIST(&Reticle_cur_list); rl = GET_NEXT(rl) ) { if ( !dot_flag ) { // compare based on distance if ( measure < rl->dist ) { list_insert_before(rl, new_rl); was_inserted = 1; break; } } else { // compare based on dot if ( measure > rl->dot ) { list_insert_before(rl, new_rl); was_inserted = 1; break; } } } // end for } if ( !was_inserted ) { list_append(&Reticle_cur_list, new_rl); } } // -------------------------------------------------------------------------------------- // hud_reticle_pick_target() // // Pick a target from Reticle_cur_list, based on what is in Reticle_save_list // // object *hud_reticle_pick_target() { reticle_list *cur_rl, *save_rl, *new_rl; object *return_objp; int in_save_list, i; return_objp = NULL; // As a first step, see if both ships and debris are in the list. If so, cull the debris. int debris_in_list = 0; int ship_in_list = 0; for ( cur_rl = GET_FIRST(&Reticle_cur_list); cur_rl != END_OF_LIST(&Reticle_cur_list); cur_rl = GET_NEXT(cur_rl) ) { if ( (cur_rl->objp->type == OBJ_SHIP) || (cur_rl->objp->type == OBJ_JUMP_NODE) ) { ship_in_list = 1; continue; } if ( cur_rl->objp->type == OBJ_WEAPON ) { if ( Weapon_info[Weapons[cur_rl->objp->instance].weapon_info_index].subtype == WP_MISSILE ) { ship_in_list = 1; continue; } } if ( (cur_rl->objp->type == OBJ_DEBRIS) || (cur_rl->objp->type == OBJ_ASTEROID) ) { debris_in_list = 1; continue; } } if ( ship_in_list && debris_in_list ) { // cull debris reticle_list *rl, *next; rl = GET_FIRST(&Reticle_cur_list); while ( rl != &Reticle_cur_list ) { next = rl->next; if ( (rl->objp->type == OBJ_DEBRIS) || (rl->objp->type == OBJ_ASTEROID) ){ list_remove(&Reticle_cur_list,rl); rl->flags = 0; } rl = next; } } for ( cur_rl = GET_FIRST(&Reticle_cur_list); cur_rl != END_OF_LIST(&Reticle_cur_list); cur_rl = GET_NEXT(cur_rl) ) { in_save_list = 0; for ( save_rl = GET_FIRST(&Reticle_save_list); save_rl != END_OF_LIST(&Reticle_save_list); save_rl = GET_NEXT(save_rl) ) { if ( cur_rl->objp == save_rl->objp ) { in_save_list = 1; break; } } if ( !in_save_list ) { return_objp = cur_rl->objp; i = hud_reticle_list_find_free(); if ( i == -1 ) break; new_rl = &Reticle_list[i]; new_rl->flags |= RL_USED; if ( cur_rl->flags & RL_USE_DOT ) { hud_stuff_reticle_list(new_rl, cur_rl->objp, cur_rl->dot, 1); } else { hud_stuff_reticle_list(new_rl, cur_rl->objp, cur_rl->dist, 0); } list_append(&Reticle_save_list, new_rl); break; } } // end for if ( return_objp == NULL && !EMPTY(&Reticle_cur_list) ) { i = hud_reticle_list_find_free(); if ( i == -1 ) return NULL; new_rl = &Reticle_list[i]; cur_rl = GET_FIRST(&Reticle_cur_list); *new_rl = *cur_rl; return_objp = cur_rl->objp; hud_reticle_clear_list(&Reticle_save_list); list_append(&Reticle_save_list, new_rl); } return return_objp; } // hud_target_hotkey_add_remove takes as its parameter which hotkey (1-0) to add/remove the current // target from. This function behaves like the Shift- does in Windows -- using shift # will toggle // the current target in and out of the selection set. void hud_target_hotkey_add_remove( int k, object *ctarget, int how_to_add ) { htarget_list *hitem, *plist; // don't do anything if a standalone multiplayer server if ( MULTIPLAYER_STANDALONE ) return; if ( (k < 0) || (k >= MAX_KEYED_TARGETS) ) { nprintf(("Warning", "Bogus hotkey %d sent to hud_target_hotkey_add_remove\n", k)); return; } plist = &(Players[Player_num].keyed_targets[k]); // we must operate only on ships if ( ctarget->type != OBJ_SHIP ) return; // don't allow player into hotkey set if ( ctarget == Player_obj ) return; // don't put dying or departing if ( Ships[ctarget->instance].is_dying_or_departing() ) return; // don't add mission file added hotkey assignments if there are player added assignments // already in the list if ( (how_to_add == HOTKEY_MISSION_FILE_ADDED) && NOT_EMPTY(plist) ) { for ( hitem = GET_FIRST(plist); hitem != END_OF_LIST(plist); hitem = GET_NEXT(hitem) ) { if ( hitem->how_added == HOTKEY_USER_ADDED ) return; } } // determine if the current target is currently in the set or not for ( hitem = GET_FIRST(plist); hitem != END_OF_LIST(plist); hitem = GET_NEXT(hitem) ) { if ( hitem->objp == ctarget ) break; } // if hitem == end of the list, then the target should be added, else it should be removed if ( hitem == END_OF_LIST(plist) ) { if ( EMPTY(&htarget_free_list) ) { Int3(); // get Allender -- no more free hotkey target items return; } nprintf(("network", "Hotkey: Adding %s\n", Ships[ctarget->instance].ship_name)); hitem = GET_FIRST( &htarget_free_list ); list_remove( &htarget_free_list, hitem ); list_append( plist, hitem ); hitem->objp = ctarget; hitem->how_added = how_to_add; } else { nprintf(("network", "Hotkey: Removing %s\n", Ships[ctarget->instance].ship_name)); list_remove( plist, hitem ); list_append( &htarget_free_list, hitem ); hitem->objp = NULL; // for safety } } // the following function clears the hotkey set given by parameter passed in void hud_target_hotkey_clear( int k ) { htarget_list *hitem, *plist, *temp; plist = &(Players[Player_num].keyed_targets[k]); hitem = GET_FIRST(plist); while ( hitem != END_OF_LIST(plist) ) { temp = GET_NEXT(hitem); list_remove( plist, hitem ); list_append( &htarget_free_list, hitem ); hitem->objp = NULL; hitem = temp; } if ( Players[Player_num].current_hotkey_set == k ) // clear this variable if we removed the bindings Players[Player_num].current_hotkey_set = -1; } // the next function sets the current selected set to be N. If there is just one ship in the selection // set, this ship will become the new target. If there is more than one ship in the selection set, // then the current_target will remain what it was. void hud_target_hotkey_select( int k ) { int visible_count = 0; htarget_list *hitem, *plist, *target, *next_target, *first_target; int target_objnum; plist = &(Players[Player_num].keyed_targets[k]); if ( EMPTY( plist ) ) // no items in list, then do nothing return; // a simple walk of the list to get the count for ( hitem = GET_FIRST(plist); hitem != END_OF_LIST(plist); hitem = GET_NEXT(hitem) ){ if (awacs_get_level(hitem->objp, Player_ship, 1) > 1) { visible_count++; } } // no visible ships in list if (visible_count == 0) { return; } // set the current target to be the "next" ship in the list. Scan the list to see if our // current target is in the set. If so, target the next ship in the list, otherwise target // the first // set first_target - first visible item in list // target - item in list that is the player's currently selected target // next_target - next visible item in list following target target_objnum = Player_ai->target_objnum; target = NULL; next_target = NULL; first_target = NULL; for ( hitem = GET_FIRST(plist); hitem != END_OF_LIST(plist); hitem = GET_NEXT(hitem) ) { if (awacs_get_level(hitem->objp, Player_ship, 1) > 1) { // get the first valid target if (first_target == NULL) { first_target = hitem; } // get the next target in the list following the player currently selected target if (target != NULL) { next_target = hitem; break; } } // mark the player currently selected target if ( OBJ_INDEX(hitem->objp) == target_objnum ) { target = hitem; } } // if current target is not in list, then target and next_target will be NULL // so we use the first found target if (target == NULL) { Assert(first_target != NULL); if (first_target != NULL) { target = first_target; next_target = first_target; } else { // this should not happen return; } // if current target is in the list but this is not our current selection set, // then we don't want to change target. } else if (Players[Player_num].current_hotkey_set != k) { next_target = target; } // update target if more than 1 is visible if (visible_count > 1) { // next already found (after current target in list) if (next_target != NULL) { target = next_target; } else { // next is before current target, so search from start of list for ( hitem = GET_FIRST(plist); hitem != END_OF_LIST(plist); hitem = GET_NEXT(hitem) ) { if (awacs_get_level(hitem->objp, Player_ship, 1) > 1) { target = hitem; break; } } } } Assert( target != END_OF_LIST(plist) ); if ( Player_obj != target->objp ){ set_target_objnum( Player_ai, OBJ_INDEX(target->objp) ); hud_shield_hit_reset(target->objp); } Players[Player_num].current_hotkey_set = k; } // hud_init_targeting_colors() will initialize the shader and gradient objects used // on the HUD // color HUD_color_homing_indicator; void hud_make_shader(shader *sh, ubyte r, ubyte /*g*/, ubyte /*b*/, float dimmer = 1000.0f) { // The m matrix converts all colors to shades of green //float tmp = 16.0f*(0.0015625f * i2fl(HUD_color_alpha+1.0f)); float tmp = 0.025f * i2fl(HUD_color_alpha+1.0f); ubyte R = ubyte(r * tmp); ubyte G = ubyte(r * tmp); ubyte B = ubyte(r * tmp); ubyte A = ubyte((float(r) / dimmer)*(i2fl(HUD_color_alpha) / 15.0f) * 255.0f); gr_create_shader( sh, R, G, B, A ); } void hud_init_targeting_colors() { gr_init_color( &HUD_color_homing_indicator, 0x7f, 0x7f, 0 ); // yellow hud_make_shader(&Training_msg_glass, 61, 61, 85, 500.0f); hud_init_brackets(); } void hud_keyed_targets_clear() { int i; // clear out the keyed target list for (i = 0; i < MAX_KEYED_TARGETS; i++ ) list_init( &(Players[Player_num].keyed_targets[i]) ); Players[Player_num].current_hotkey_set = -1; // place all of the hoykey target items back onto the free list list_init( &htarget_free_list ); for ( i = 0; i < MAX_HOTKEY_TARGET_ITEMS; i++ ) list_append( &htarget_free_list, &htarget_items[i] ); } // Init data used for the weapons display on the HUD void hud_weapons_init() { Weapon_flash_info.is_bright = 0; for ( int i = 0; i < MAX_WEAPON_FLASH_LINES; i++ ) { Weapon_flash_info.flash_duration[i] = 1; Weapon_flash_info.flash_next[i] = 1; } // The E: There used to be a number of checks here. They are no longer needed, as the new HUD code handles it on its own. Weapon_gauges_loaded = 1; } // init data used to play the homing "proximity warning" sound void hud_init_homing_beep() { Homing_beep.snd_handle = sound_handle::invalid(); Homing_beep.last_time_played = 0; Homing_beep.precalced_interp = (Homing_beep.max_cycle_dist-Homing_beep.min_cycle_dist) / (Homing_beep.max_cycle_time - Homing_beep.min_cycle_time ); } void hud_init_ballistic_index() { int i; // decide whether to realign HUD for ballistic primaries ballistic_hud_index = 0; for (i = 0; i < Player_ship->weapons.num_primary_banks; i++) { if (Weapon_info[Player_ship->weapons.primary_bank_weapons[i]].wi_flags[Weapon::Info_Flags::Ballistic]) { ballistic_hud_index = 1; break; } } } // hud_init_targeting() will set the current target to point to the dummy node // in the object used list // void hud_init_targeting() { Assert(Player_ai != NULL); // make sure there is no current target set_target_objnum( Player_ai, -1 ); Player_ai->last_target = -1; Player_ai->last_subsys_target = NULL; Player_ai->last_dist = 0.0f; Player_ai->last_speed = 0.0f; hud_keyed_targets_clear(); hud_init_missile_lock(); hud_init_artillery(); // Init the lists that hold targets in reticle (to allow cycling of targets in reticle) hud_reticle_list_init(); hud_init_homing_beep(); hud_weapons_init(); Min_warning_missile_dist = 2.5f*Player_obj->radius; Max_warning_missile_dist = 1500.0f; Tl_hostile_reset_timestamp = timestamp(0); Tl_friendly_reset_timestamp = timestamp(0); Target_next_uninspected_object_timestamp = timestamp(0); Target_newest_ship_timestamp = timestamp(0); Target_next_turret_timestamp = timestamp(0); if(The_mission.flags[Mission::Mission_Flags::Fullneb]) { Toggle_text_alpha = TOGGLE_TEXT_NEBULA_ALPHA; } else { Toggle_text_alpha = TOGGLE_TEXT_NORMAL_ALPHA; } } // Target the next or previous subobject on the currently selected ship, based on next_flag. void hud_target_subobject_common(int next_flag) { if (Player_ai->target_objnum == -1) { HUD_sourced_printf(HUD_SOURCE_HIDDEN, "%s", XSTR( "No target selected.", 322)); snd_play( gamesnd_get_game_sound(GameSounds::TARGET_FAIL) ); return; } if (Objects[Player_ai->target_objnum].type != OBJ_SHIP) { snd_play( gamesnd_get_game_sound(GameSounds::TARGET_FAIL)); return; } ship_subsys *start, *start2, *A; ship_subsys *subsys_to_target=NULL; ship *target_shipp; target_shipp = &Ships[Objects[Player_ai->target_objnum].instance]; if (!Player_ai->targeted_subsys) { start = END_OF_LIST(&target_shipp->subsys_list); } else { start = Player_ai->targeted_subsys; } start2 = advance_subsys(start, next_flag); for ( A = start2; A != start; A = advance_subsys(A, next_flag) ) { if ( A == &target_shipp->subsys_list ) { continue; } // ignore turrets if ( A->system_info->type == SUBSYSTEM_TURRET ) { continue; } if ( A->flags[Ship::Subsystem_Flags::Untargetable] ) { continue; } subsys_to_target = A; break; } // end for if ( subsys_to_target == NULL ) { snd_play( gamesnd_get_game_sound(GameSounds::TARGET_FAIL)); } else { set_targeted_subsys(Player_ai, subsys_to_target, Player_ai->target_objnum); target_shipp->last_targeted_subobject[Player_num] = Player_ai->targeted_subsys; } } object *advance_fb(object *objp, int next_flag) { if (next_flag) return GET_NEXT(objp); else return GET_LAST(objp); } // Target the previous subobject on the currently selected ship. // void hud_target_prev_subobject() { hud_target_subobject_common(0); } void hud_target_next_subobject() { hud_target_subobject_common(1); } // hud_target_next() will set the Players[Player_num].current_target to the next target in the object // used list whose team matches the team parameter. The player is NOT included in the target list. // // parameters: team_mask => team(s) of ship to target next // void hud_target_common(int team_mask, int next_flag) { object *A, *start, *start2; ship *shipp; int is_ship, target_found = FALSE; SCP_list::iterator jnp; if (Player_ai->target_objnum == -1) start = &obj_used_list; else start = &Objects[Player_ai->target_objnum]; start2 = advance_fb(start, next_flag); for ( A = start2; A != start; A = advance_fb(A, next_flag) ) { is_ship = 0; if (A == &obj_used_list) continue; if ( (A == Player_obj) || ((A->type != OBJ_SHIP) && (A->type != OBJ_WEAPON) && (A->type != OBJ_JUMP_NODE)) ) continue; if ( hud_target_invalid_awacs(A) ) continue; if (A->type == OBJ_WEAPON) { if ( !(Weapon_info[Weapons[A->instance].weapon_info_index].wi_flags[Weapon::Info_Flags::Can_be_targeted]) ) if ( !(Weapon_info[Weapons[A->instance].weapon_info_index].wi_flags[Weapon::Info_Flags::Bomb]) ) continue; if (Weapons[A->instance].lssm_stage == 3) continue; } if (A->type == OBJ_SHIP) { if (should_be_ignored(&Ships[A->instance])) continue; is_ship = 1; } if (A->type == OBJ_JUMP_NODE) { for (jnp = Jump_nodes.begin(); jnp != Jump_nodes.end(); ++jnp) { if( jnp->GetSCPObject() == A ) break; } Assertion(jnp != Jump_nodes.end(), "Failed to find jump node with object index %d; trace out and fix!\n", OBJ_INDEX(A)); if( jnp->IsHidden() ) continue; } if ( vm_vec_same(&A->pos, &Eye_position) ) continue; if ( is_ship ) { shipp = &Ships[A->instance]; // get a pointer to the ship information if (!iff_matches_mask(shipp->team, team_mask)) { continue; } if ( A == Player_obj ) { continue; } // if we've reached here, it is a valid next target if ( Player_ai->target_objnum != OBJ_INDEX(A) ) { target_found = TRUE; set_target_objnum( Player_ai, OBJ_INDEX(A) ); hud_shield_hit_reset(A); // if ship is BIG|HUGE and last subsys is NULL, get turret hud_maybe_set_sorted_turret_subsys(shipp); hud_restore_subsystem_target(shipp); } } else { target_found = TRUE; set_target_objnum( Player_ai, OBJ_INDEX(A) ); hud_shield_hit_reset(A); } break; } if ( target_found == FALSE ) { snd_play( gamesnd_get_game_sound(GameSounds::TARGET_FAIL) ); } } void hud_target_next(int team_mask) { hud_target_common(team_mask, 1); } void hud_target_prev(int team_mask) { hud_target_common(team_mask, 0); } // ------------------------------------------------------------------- // advance_missile_obj() // missile_obj *advance_missile_obj(missile_obj *mo, int next_flag) { if (next_flag){ return GET_NEXT(mo); } return GET_LAST(mo); } ship_obj *advance_ship(ship_obj *so, int next_flag) { if (next_flag){ return GET_NEXT(so); } return GET_LAST(so); } /// \brief Iterates down to and selects the next target in a linked list /// fashion ordered from closest to farthest from the /// attacked_object_number, returning the next valid target. /// /// /// \param targeting_from_closest_to_farthest[in] targets the closest object /// if true. Targets the farthest /// away object if false. /// \param valid_team_mask[in] A bit mask that defines the desired /// victim team. /// \param attacked_object_number[in] The objectid that is under attack. /// Defaults to -1. /// \param target_filters Applies a bit filter to exclude certain /// classes of objects from being targeted. /// Defaults to (SIF_CARGO | SIF_NAVBUOY) /// /// \returns The next object to target if targeting was successful. /// Returns NULL if targeting was unsuccessful. static object* select_next_target_by_distance(const bool targeting_from_closest_to_farthest, const int valid_team_mask, const int attacked_object_number = -1, flagset* target_filters = NULL) { object *minimum_object_ptr, *maximum_object_ptr, *nearest_object_ptr; minimum_object_ptr = maximum_object_ptr = nearest_object_ptr = NULL; float current_distance = hud_find_target_distance(&Objects[Player_ai->target_objnum], Player_obj); float minimum_distance = 1e20f; float maximum_distance = 0.0f; int player_object_index = OBJ_INDEX(Player_obj); flagset filter; if (target_filters != NULL) { filter = *target_filters; } else { filter.set(Ship::Info_Flags::Cargo); filter.set(Ship::Info_Flags::Navbuoy); } float nearest_distance; if (targeting_from_closest_to_farthest) { nearest_distance = 1e20f; } else { nearest_distance = 0.0f; } ship_obj *ship_object_ptr; object *prospective_victim_ptr; ship *prospective_victim_ship_ptr; for (ship_object_ptr = GET_FIRST(&Ship_obj_list); ship_object_ptr != END_OF_LIST(&Ship_obj_list); ship_object_ptr = GET_NEXT(ship_object_ptr)) { prospective_victim_ptr = &Objects[ship_object_ptr->objnum]; // get a pointer to the ship information prospective_victim_ship_ptr = &Ships[prospective_victim_ptr->instance]; float new_distance; if ((prospective_victim_ptr == Player_obj) || (should_be_ignored(prospective_victim_ship_ptr))) { continue; } // choose from the correct team if (!iff_matches_mask(prospective_victim_ship_ptr->team, valid_team_mask)) { continue; } // don't use object if it is already a target if (OBJ_INDEX(prospective_victim_ptr) == Player_ai->target_objnum) { continue; } if (attacked_object_number == -1) { // always ignore navbuoys and cargo if ((Ship_info[prospective_victim_ship_ptr->ship_info_index].flags & filter).any_set()) { continue; } if (hud_target_invalid_awacs(prospective_victim_ptr)) { continue; } new_distance = hud_find_target_distance(prospective_victim_ptr, Player_obj); } else { // Filter out any target that is not targeting the player --Mastadon if ((attacked_object_number == player_object_index) && (Ai_info[prospective_victim_ship_ptr->ai_index].target_objnum != player_object_index)) { continue; } esct eval_ship_as_closest_target_args; eval_ship_as_closest_target_args.attacked_objnum = attacked_object_number; eval_ship_as_closest_target_args.check_all_turrets = (attacked_object_number == player_object_index); eval_ship_as_closest_target_args.check_nearest_turret = FALSE; // We don't ever filter our target selection to just bombers or fighters // because the select next attacker logic doesn't. --Mastadon eval_ship_as_closest_target_args.filter = 0; eval_ship_as_closest_target_args.team_mask = valid_team_mask; // We always get the turret attacking, since that's how the select next // attacker logic does it. --Mastadon eval_ship_as_closest_target_args.turret_attacking_target = 1; eval_ship_as_closest_target_args.shipp = prospective_victim_ship_ptr; evaluate_ship_as_closest_target(&eval_ship_as_closest_target_args); new_distance = eval_ship_as_closest_target_args.min_distance; } if (new_distance <= minimum_distance) { minimum_distance = new_distance; minimum_object_ptr = prospective_victim_ptr; } if (new_distance >= maximum_distance) { maximum_distance = new_distance; maximum_object_ptr = prospective_victim_ptr; } float diff = 0.0f; if (targeting_from_closest_to_farthest) { diff = new_distance - current_distance; if (diff > 0.0f) { if (diff < (nearest_distance - current_distance)) { nearest_distance = new_distance; nearest_object_ptr = prospective_victim_ptr; } } } else { diff = current_distance - new_distance; if (diff > 0.0f) { if (diff < (current_distance - nearest_distance)) { nearest_distance = new_distance; nearest_object_ptr = prospective_victim_ptr; } } } } if (nearest_object_ptr == NULL) { if (targeting_from_closest_to_farthest) { if (minimum_object_ptr != NULL) { nearest_object_ptr = minimum_object_ptr; } } else { if (maximum_object_ptr != NULL) { nearest_object_ptr = maximum_object_ptr; } } } return nearest_object_ptr; } ship_obj *get_ship_obj_ptr_from_index(int index); // ------------------------------------------------------------------- // hud_target_missile() // // Target the closest locked missile that is locked on locked_obj // // input: source_obj => pointer to object that fired weapon // next_flag => 0 -> previous 1 -> next // // NOTE: this function is only allows targeting bombs void hud_target_missile(object *source_obj, int next_flag) { missile_obj *end, *start, *mo; object *A, *target_objp; ai_info *aip; weapon *wp; weapon_info *wip; int target_found = 0; if ( source_obj->type != OBJ_SHIP ) return; Assert( Ships[source_obj->instance].ai_index != -1 ); aip = &Ai_info[Ships[source_obj->instance].ai_index]; end = &Missile_obj_list; if (aip->target_objnum != -1) { target_objp = &Objects[aip->target_objnum]; if ( target_objp->type == OBJ_WEAPON && Weapon_info[Weapons[target_objp->instance].weapon_info_index].subtype == WP_MISSILE ) { // must be a missile end = missile_obj_return_address(Weapons[target_objp->instance].missile_list_index); } } start = advance_missile_obj(end, next_flag); for ( mo = start; mo != end; mo = advance_missile_obj(mo, next_flag) ) { if ( mo == &Missile_obj_list ){ continue; } Assert(mo->objnum >= 0 && mo->objnum < MAX_OBJECTS); A = &Objects[mo->objnum]; Assert(A->type == OBJ_WEAPON); Assert((A->instance >= 0) && (A->instance < MAX_WEAPONS)); wp = &Weapons[A->instance]; wip = &Weapon_info[wp->weapon_info_index]; // only allow targeting of bombs if ( !(wip->wi_flags[Weapon::Info_Flags::Can_be_targeted]) ) { if ( !(wip->wi_flags[Weapon::Info_Flags::Bomb]) ) { continue; } } if (wp->lssm_stage==3){ continue; } // only allow targeting of hostile bombs if (!iff_x_attacks_y(Player_ship->team, obj_team(A))) { continue; } if(hud_target_invalid_awacs(A)){ continue; } // if we've reached here, got a new target target_found = TRUE; set_target_objnum( aip, OBJ_INDEX(A) ); hud_shield_hit_reset(A); break; } // end for if ( !target_found ) { // if no bomb is found, search for bombers ship_obj *startShip, *so; if ( (aip->target_objnum != -1) && (Objects[aip->target_objnum].type == OBJ_SHIP) && ((Ship_info[Ships[Objects[aip->target_objnum].instance].ship_info_index].flags[Ship::Info_Flags::Bomber]) || (Objects[aip->target_objnum].flags[Object::Object_Flags::Targetable_as_bomb]))) { int index = Ships[Objects[aip->target_objnum].instance].ship_list_index; startShip = get_ship_obj_ptr_from_index(index); } else { startShip = GET_FIRST(&Ship_obj_list); } for (so=advance_ship(startShip, next_flag); so!=startShip; so=advance_ship(so, next_flag)) { A = &Objects[so->objnum]; // don't look at header if (so == &Ship_obj_list) { continue; } // only allow targeting of hostile bombs if (!iff_x_attacks_y(Player_ship->team, obj_team(A))) { continue; } if(hud_target_invalid_awacs(A)){ continue; } // check if ship type is bomber if ( !(Ship_info[Ships[A->instance].ship_info_index].flags[Ship::Info_Flags::Bomber]) && !(A->flags[Object::Object_Flags::Targetable_as_bomb]) ) { continue; } // check if ignore if ( should_be_ignored(&Ships[A->instance]) ){ continue; } // found a good one target_found = TRUE; set_target_objnum( aip, OBJ_INDEX(A) ); hud_shield_hit_reset(A); break; } } if ( !target_found ) { snd_play( gamesnd_get_game_sound(GameSounds::TARGET_FAIL), 0.0f ); } } // Return !0 if shipp can be scanned, otherwise return 0 int hud_target_ship_can_be_scanned(ship *shipp) { ship_info *sip; sip = &Ship_info[shipp->ship_info_index]; // ignore cargo that has already been scanned if (shipp->flags[Ship::Ship_Flags::Cargo_revealed]) return 0; // allow ships with scannable flag set if (shipp->flags[Ship::Ship_Flags::Scannable]) return 1; // ignore ships that don't carry cargo if ((sip->class_type < 0) || !(Ship_types[sip->class_type].flags[Ship::Type_Info_Flags::Scannable])) return 0; return 1; } // target the next/prev uninspected cargo container void hud_target_uninspected_cargo(int next_flag) { object *A, *start, *start2; ship *shipp; int target_found = 0; if (Player_ai->target_objnum == -1) { start = &obj_used_list; } else { start = &Objects[Player_ai->target_objnum]; } start2 = advance_fb(start, next_flag); for ( A = start2; A != start; A = advance_fb(A, next_flag) ) { if ( A == &obj_used_list ) { continue; } if (A == Player_obj || (A->type != OBJ_SHIP) ) { continue; } shipp = &Ships[A->instance]; // get a pointer to the ship information if ( should_be_ignored(shipp) ) { continue; } // ignore all non-cargo carrying craft if ( !hud_target_ship_can_be_scanned(shipp) ) { continue; } if(hud_target_invalid_awacs(A)){ continue; } // if we've reached here, it is a valid next target if ( Player_ai->target_objnum != OBJ_INDEX(A) ) { target_found = TRUE; set_target_objnum( Player_ai, OBJ_INDEX(A) ); hud_shield_hit_reset(A); } } if ( target_found == FALSE ) { snd_play( gamesnd_get_game_sound(GameSounds::TARGET_FAIL)); } } // target the newest ship in the area void hud_target_newest_ship() { object *A, *player_target_objp; object *newest_obj=NULL; ship *shipp; ship_obj *so; uint current_target_arrived_time = 0xffffffff, newest_time = 0; if ( Player_ai->target_objnum >= 0 ) { player_target_objp = &Objects[Player_ai->target_objnum]; if ( player_target_objp->type == OBJ_SHIP ) { current_target_arrived_time = Ships[player_target_objp->instance].create_time; } } else { player_target_objp = NULL; } // If no target is selected, then simply target the closest uninspected cargo if ( Player_ai->target_objnum == -1 || timestamp_elapsed(Target_newest_ship_timestamp) ) { current_target_arrived_time = 0xffffffff; } Target_newest_ship_timestamp = timestamp(TL_RESET); for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) { A = &Objects[so->objnum]; shipp = &Ships[A->instance]; // get a pointer to the ship information if ( (A == Player_obj) || (should_be_ignored(shipp)) ) continue; // ignore navbuoys if ( Ship_info[shipp->ship_info_index].flags[Ship::Info_Flags::Navbuoy] ) { continue; } if ( A == player_target_objp ) { continue; } if(hud_target_invalid_awacs(A)){ continue; } if ( (shipp->create_time >= newest_time) && (shipp->create_time <= current_target_arrived_time) ) { newest_time = shipp->create_time; newest_obj = A; } } if (newest_obj) { set_target_objnum( Player_ai, OBJ_INDEX(newest_obj) ); hud_shield_hit_reset(newest_obj); // if BIG|HUGE and no selected subsystem, get sorted turret hud_maybe_set_sorted_turret_subsys(&Ships[newest_obj->instance]); hud_restore_subsystem_target(&Ships[newest_obj->instance]); } else { snd_play( gamesnd_get_game_sound(GameSounds::TARGET_FAIL)); } } #define TYPE_NONE 0 #define TYPE_FACING_BEAM 1 #define TYPE_FACING_FLAK 2 #define TYPE_FACING_MISSILE 3 #define TYPE_FACING_LASER 4 #define TYPE_NONFACING_BEAM 5 #define TYPE_NONFACING_FLAK 6 #define TYPE_NONFACING_MISSILE 7 #define TYPE_NONFACING_LASER 8 #define TYPE_NONFACING_INC 4 typedef struct eval_next_turret { ship_subsys *ss; int type; float dist; } eval_next_turret; int turret_compare_func(const void *e1, const void *e2) { eval_next_turret *p1 = (eval_next_turret*)e1; eval_next_turret *p2 = (eval_next_turret*)e2; Assert(p1->type != TYPE_NONE); Assert(p2->type != TYPE_NONE); if (p1->type != p2->type) { return (p1->type - p2->type); } else { float delta_dist = p1->dist - p2->dist; if (delta_dist < 0) { return -1; } else if (delta_dist > 0) { return 1; } else { return 0; } } } extern bool turret_weapon_has_flags(ship_weapon *swp, Weapon::Info_Flags flags); extern bool turret_weapon_has_subtype(ship_weapon *swp, int subtype); // target the next/prev live turret on the current target // auto_advance from hud_update_closest_turret void hud_target_live_turret(int next_flag, int auto_advance, int only_player_target) { ship_subsys *A; ship_subsys *live_turret=NULL; ship *target_shipp; object *objp; eval_next_turret ent[MAX_MODEL_SUBSYSTEMS]; int num_live_turrets = 0; // make sure we're targeting a ship if (Player_ai->target_objnum == -1 && !auto_advance) { snd_play(gamesnd_get_game_sound(GameSounds::TARGET_FAIL)); return; } // only targeting subsystems on ship if ((Objects[Player_ai->target_objnum].type != OBJ_SHIP) && (!auto_advance)) { snd_play( gamesnd_get_game_sound(GameSounds::TARGET_FAIL)); return; } // set some pointers objp = &Objects[Player_ai->target_objnum]; target_shipp = &Ships[objp->instance]; // set timestamp int timestamp_val = 0; if (!auto_advance) { timestamp_val = Target_next_turret_timestamp; Target_next_turret_timestamp = timestamp(TURRET_RESET); } // If no target is selected, then simply target the closest (or facing) turret int last_subsys_turret = FALSE; if (Player_ai->targeted_subsys != NULL) { if (Player_ai->targeted_subsys->system_info->type == SUBSYSTEM_TURRET) { if (Player_ai->targeted_subsys->weapons.num_primary_banks > 0 || Player_ai->targeted_subsys->weapons.num_secondary_banks > 0) { last_subsys_turret = TRUE; } } } // do we want the closest turret (or the one our ship is pointing at) int get_closest_turret = (auto_advance || !last_subsys_turret || timestamp_elapsed(timestamp_val)); // initialize eval struct memset(ent,0, sizeof(ent)); int use_straight_ahead_turret = FALSE; // go through list of turrets for (A=GET_FIRST(&target_shipp->subsys_list); A!=END_OF_LIST(&target_shipp->subsys_list); A=GET_NEXT(A)) { // get a turret if (A->system_info->type == SUBSYSTEM_TURRET) { // niffiwan: ignore untargetable turrets if ( A->flags[Ship::Subsystem_Flags::Untargetable] ) { continue; } // check turret has hit points and has a weapon if ( (A->current_hits > 0) && (A->weapons.num_primary_banks > 0 || A->weapons.num_secondary_banks > 0) ) { if ( !only_player_target || (A->turret_enemy_objnum == OBJ_INDEX(Player_obj)) ) { vec3d gsubpos, vec_to_subsys; float distance, dot; // get world pos of subsystem and its distance get_subsystem_world_pos(objp, A, &gsubpos); distance = vm_vec_normalized_dir(&vec_to_subsys, &gsubpos, &View_position); // check if facing and in view int facing = ship_subsystem_in_sight(objp, A, &View_position, &gsubpos, 0); if (!auto_advance && get_closest_turret && !only_player_target) { // if within 3 degrees and not previous subsys, use subsys in front dot = vm_vec_dot(&vec_to_subsys, &Player_obj->orient.vec.fvec); if ((dot > 0.9986) && facing) { use_straight_ahead_turret = TRUE; break; } } // set weapon_type to allow sort of ent on type if (turret_weapon_has_flags(&A->weapons, Weapon::Info_Flags::Beam)) { ent[num_live_turrets].type = TYPE_FACING_BEAM; } else if (turret_weapon_has_flags(&A->weapons, Weapon::Info_Flags::Flak)) { ent[num_live_turrets].type = TYPE_FACING_FLAK; } else { if (turret_weapon_has_subtype(&A->weapons, WP_MISSILE)) { ent[num_live_turrets].type = TYPE_FACING_MISSILE; } else if (turret_weapon_has_subtype(&A->weapons, WP_LASER)) { ent[num_live_turrets].type = TYPE_FACING_LASER; } else { //Turret not live, bail continue; } } // fill out ent struct ent[num_live_turrets].ss = A; ent[num_live_turrets].dist = distance; if (!facing) { ent[num_live_turrets].type += TYPE_NONFACING_INC; } num_live_turrets++; } } } } // sort the list if we're not using turret straight ahead of us if (!use_straight_ahead_turret) { insertion_sort(ent, num_live_turrets, sizeof(eval_next_turret), turret_compare_func); } if (use_straight_ahead_turret) { // use the straight ahead turret live_turret = A; } else { // check if we have a currently targeted turret and find its position after the sort int i, start_index, next_index; if (get_closest_turret) { start_index = 0; } else { start_index = -1; for (i=0; itargeted_subsys) { start_index = i; break; } } // check that we started with a turret if (start_index == -1) { start_index = 0; } } // set next live turret if (num_live_turrets == 0) { // no live turrets live_turret = NULL; } else if (num_live_turrets == 1 || get_closest_turret) { // only 1 live turret, so set it live_turret = ent[0].ss; } else { if (next_flag) { // advance to next closest turret next_index = start_index + 1; if (next_index == num_live_turrets) { next_index = 0; } } else { // go to next farther turret next_index = start_index - 1; if (next_index == -1) { next_index = num_live_turrets - 1; } } // set the next turret to be targeted based on next_index live_turret = ent[next_index].ss; } //if (live_turret) { // debug info // mprintf(("name %s, index: %d, type: %d\n", live_turret->system_info->subobj_name, next_index, ent[next_index].type)); //} } if ( live_turret != NULL ) { set_targeted_subsys(Player_ai, live_turret, Player_ai->target_objnum); target_shipp->last_targeted_subobject[Player_num] = Player_ai->targeted_subsys; } else { if (!auto_advance) { snd_play( gamesnd_get_game_sound(GameSounds::TARGET_FAIL)); } } } // select a new target, by auto-targeting void hud_target_auto_target_next() { if ( Framecount < 2 ) { return; } // No auto-targeting after dead. if (Game_mode & (GM_DEAD | GM_DEAD_BLEW_UP)) return; // try target next ship in hotkey set, if any -- Backslash if ( Player->current_hotkey_set != -1 ) { hud_target_hotkey_select(Player->current_hotkey_set); } int valid_team_mask = iff_get_attackee_mask(Player_ship->team); // if none, try targeting closest hostile fighter/bomber if ( Player_ai->target_objnum == -1 ) { //-V581 hud_target_closest(valid_team_mask, -1, FALSE, TRUE); } // No fighter/bombers exists, so go ahead an target the closest hostile if ( Player_ai->target_objnum == -1 ) { //-V581 hud_target_closest(valid_team_mask, -1, FALSE); } // um, ok. Try targeting asteroids that are on a collision course for an escort ship if ( Player_ai->target_objnum == -1 ) { //-V581 asteroid_target_closest_danger(); } } // Given that object 'targeter' is targeting object 'targetee', // how far are they? This uses the point closest to the targeter // object on the targetee's bounding box. So if targeter is inside // targtee's bounding box, the distance is 0. float hud_find_target_distance( object *targetee, const vec3d *targeter_pos ) { vec3d tmp_pnt; int model_num = -1; // Which model is it? switch( targetee->type ) { case OBJ_SHIP: model_num = Ship_info[Ships[targetee->instance].ship_info_index].model_num; break; case OBJ_DEBRIS: // model_num = Debris[targetee->instance].model_num; break; case OBJ_WEAPON: // Don't find model_num since circles would work better //model_num = Weapon_info[Weapons[targetee->instance].weapon_info_index].model_num; break; case OBJ_ASTEROID: // Don't find model_num since circles would work better //model_num = Asteroid_info[Asteroids[targetee->instance].type].model_num; break; case OBJ_JUMP_NODE: // Don't find model_num since circles would work better //model_num = Jump_nodes[targetee->instance].modelnum; break; } float dist = 0.0f; // New way, that uses bounding box. if ( model_num > -1 ) { dist = model_find_closest_point( &tmp_pnt, model_num, -1, &targetee->orient, &targetee->pos, targeter_pos ); } else { // Old way, that uses radius. dist = vm_vec_dist_quick(&targetee->pos, targeter_pos) - targetee->radius; if ( dist < 0.0f ) { dist = 0.0f; } } return dist; } float hud_find_target_distance( object *targetee, object *targeter ) { return hud_find_target_distance(targetee, &targeter->pos); } // /// \brief evaluate a ship (and maybe turrets) as a potential target /// /// Check if shipp (or its turrets) is attacking attacked_objnum /// Provides a special case for player trying to select target (don't check if /// turrets are aimed at player) /// /// \param[in, out] *esct The Evaluate Ship as Closest Target (esct) that will /// be used to determine if a target is a valid, harmful /// target and, if so, sets the min_distance attribute /// to the distance of either the attacker or the /// closest attacker's turret. Otherwise, min_distance /// is set to FLT_MAX /// /// \return true if either the ship or one of it's turrets are attacking the /// player. Otherwise, returns false. bool evaluate_ship_as_closest_target(esct *esct_p) { int targeting_player, turret_is_attacking; ship_subsys *ss; float new_distance; // initialize esct_p->min_distance = FLT_MAX; esct_p->check_nearest_turret = FALSE; turret_is_attacking = FALSE; object *objp = &Objects[esct_p->shipp->objnum]; Assert(objp->type == OBJ_SHIP); if (objp->type != OBJ_SHIP) { return false; } // player being targeted, so we will want closest distance from player targeting_player = (esct_p->attacked_objnum == OBJ_INDEX(Player_obj)); // filter on team if ( !iff_matches_mask(esct_p->shipp->team, esct_p->team_mask) ) { return false; } // check if player or ignore ship if ( (esct_p->shipp->objnum == OBJ_INDEX(Player_obj)) || (should_be_ignored(esct_p->shipp)) ) { return false; } // bail if harmless if ( Ship_info[esct_p->shipp->ship_info_index].class_type > -1 && !(Ship_types[Ship_info[esct_p->shipp->ship_info_index].class_type].flags[Ship::Type_Info_Flags::Target_as_threat])) { return false; } // only look at targets that are AWACS valid if (hud_target_invalid_awacs(&Objects[esct_p->shipp->objnum])) { return false; } // If filter is set, only target fighters and bombers if ( esct_p->filter ) { if ( !(Ship_info[esct_p->shipp->ship_info_index].is_fighter_bomber()) ) { return false; } } // find closest turret to player if BIG or HUGE ship if (Ship_info[esct_p->shipp->ship_info_index].is_big_or_huge()) { for (ss=GET_FIRST(&esct_p->shipp->subsys_list); ss!=END_OF_LIST(&esct_p->shipp->subsys_list); ss=GET_NEXT(ss)) { if (ss->flags[Ship::Subsystem_Flags::Untargetable]) continue; if ( (ss->system_info->type == SUBSYSTEM_TURRET) && (ss->current_hits > 0) ) { if (esct_p->check_all_turrets || (ss->turret_enemy_objnum == esct_p->attacked_objnum)) { turret_is_attacking = 1; esct_p->check_nearest_turret = TRUE; if ( !esct_p->turret_attacking_target || (esct_p->turret_attacking_target && (ss->turret_enemy_objnum == esct_p->attacked_objnum)) ) { vec3d gsubpos; // get world pos of subsystem vm_vec_unrotate(&gsubpos, &ss->system_info->pnt, &objp->orient); vm_vec_add2(&gsubpos, &objp->pos); new_distance = vm_vec_dist_quick(&gsubpos, &Player_obj->pos); /* // GET TURRET TYPE, FAVOR BEAM, FLAK, OTHER int turret_type = ss->system_info->turret_weapon_type; if (Weapon_info[turret_type].wi_flags[Weapon::Info_Flags::Beam]) { new_distance *= 0.3f; } else if (Weapon_info[turret_type].wi_flags[Weapon::Info_Flags::Flak]) { new_distance *= 0.6f; } */ // get the closest distance if (new_distance <= esct_p->min_distance) { esct_p->min_distance = new_distance; } } } } } } // If no turret is attacking, check if objp is actually targeting attacked_objnum // don't bail if targeting is for player if ( !targeting_player && !turret_is_attacking ) { ai_info *aip = &Ai_info[esct_p->shipp->ai_index]; if (aip->target_objnum != esct_p->attacked_objnum) { return false; } if ( (Game_mode & GM_NORMAL) && ( aip->mode != AIM_CHASE ) && (aip->mode != AIM_STRAFE) && (aip->mode != AIM_EVADE) && (aip->mode != AIM_EVADE_WEAPON) && (aip->mode != AIM_AVOID)) { return false; } } // consider the ship alone if there are no attacking turrets if ( !turret_is_attacking ) { //new_distance = hud_find_target_distance(objp, Player_obj); new_distance = vm_vec_dist_quick(&objp->pos, &Player_obj->pos); if (new_distance <= esct_p->min_distance) { esct_p->min_distance = new_distance; esct_p->check_nearest_turret = FALSE; } } return true; } /// \brief Sets the Players[Player_num].current_target to the closest ship to /// the player that matches the team passed as a paramater. /// /// The current algorithm is to simply iterate through the objects and /// calculate the magnitude of the vector that connects the player to the /// target. The smallest magnitude is tracked, and then used to locate the /// closest hostile ship. Note only the square of the magnitude is required, /// since we are only comparing magnitudes. /// /// \param[in] team_mask team of closest ship that should be targeted. /// Default value is -1, if team doesn't matter. /// /// \param[in] attacked_objnum object number of ship that is being attacked /// \param[in] play_fail_snd boolean, whether to play SND_TARGET_FAIL /// (needed, since function called repeatedly when /// auto-targeting is enabled, and we don't want a /// string of fail sounds playing). This is a /// default parameter with a value of TRUE. /// \param[in] filter OPTIONAL parameter (default value 0): when set /// to TRUE, only fighters and bombers are /// considered for new targets. /// \param[in] get_closest_turret_attacking_player Finds the closest turret /// attacking the player if true. Otherwise, only /// finds the closest attacking ship, targeting the /// turret closest to the player. /// /// \return: true (non-zero) if a target was acquired. Returns false (zero) if /// no target was acquired. int hud_target_closest(int team_mask, int attacked_objnum, int play_fail_snd, int filter, int get_closest_turret_attacking_player) { object *A; object *nearest_obj = &obj_used_list; ship *shipp; ship_obj *so; int check_nearest_turret = FALSE; // evaluate ship closest target struct esct eval_ship_as_closest_target_args; float min_distance = FLT_MAX; int target_found = FALSE; int player_obj_index = OBJ_INDEX(Player_obj); ship_subsys *ss; int initial_attacked_objnum = attacked_objnum; if ( (attacked_objnum >= 0) && (attacked_objnum != player_obj_index) ) { // bail if player does not have target if ( Player_ai->target_objnum == -1 ) { goto Target_closest_done; } if ( Objects[attacked_objnum].type != OBJ_SHIP ) { goto Target_closest_done; } // bail if ship is to be ignored if (should_be_ignored(&Ships[Objects[attacked_objnum].instance])) { goto Target_closest_done; } } if (attacked_objnum == -1) { attacked_objnum = player_obj_index; } // check all turrets if for player. eval_ship_as_closest_target_args.check_all_turrets = (attacked_objnum == player_obj_index); eval_ship_as_closest_target_args.filter = filter; eval_ship_as_closest_target_args.team_mask = team_mask; eval_ship_as_closest_target_args.attacked_objnum = attacked_objnum; eval_ship_as_closest_target_args.turret_attacking_target = get_closest_turret_attacking_player; for ( so=GET_FIRST(&Ship_obj_list); so!=END_OF_LIST(&Ship_obj_list); so=GET_NEXT(so) ) { A = &Objects[so->objnum]; shipp = &Ships[A->instance]; // get a pointer to the ship information // fill in rest of eval_ship_as_closest_target_args eval_ship_as_closest_target_args.shipp = shipp; // Filter out any target that is not targeting the player --Mastadon if ( (initial_attacked_objnum == player_obj_index) && (Ai_info[shipp->ai_index].target_objnum != player_obj_index) ) { continue; } // check each shipp on list and update nearest obj and subsys evaluate_ship_as_closest_target(&eval_ship_as_closest_target_args); if (eval_ship_as_closest_target_args.min_distance < min_distance) { target_found = TRUE; min_distance = eval_ship_as_closest_target_args.min_distance; nearest_obj = A; check_nearest_turret = eval_ship_as_closest_target_args.check_nearest_turret; } } Target_closest_done: // maybe ignore target if too far away // DKA 9/8/99 Remove distance check /* if (target_found) { // get distance to nearest attacker float dist = vm_vec_dist_quick(&Objects[attacked_objnum].pos, &nearest_obj->pos); // no distance limit for player obj if ((attacked_objnum != player_obj_index) && (dist > MIN_DISTANCE_TO_CONSIDER_THREAT)) { target_found = FALSE; } } */ if (target_found) { set_target_objnum(Player_ai, OBJ_INDEX(nearest_obj)); hud_shield_hit_reset(nearest_obj); if ( check_nearest_turret ) { // if former subobject was not a turret do, not change subsystem ss = Ships[nearest_obj->instance].last_targeted_subobject[Player_num]; if (ss == NULL || get_closest_turret_attacking_player) { // update nearest turret with later func hud_target_live_turret(1, 1, get_closest_turret_attacking_player); Ships[nearest_obj->instance].last_targeted_subobject[Player_num] = Player_ai->targeted_subsys; } } else { hud_restore_subsystem_target(&Ships[nearest_obj->instance]); } } else { // no target found, maybe play fail sound if (play_fail_snd == TRUE) { snd_play(gamesnd_get_game_sound(GameSounds::TARGET_FAIL)); } } return target_found; } // auto update closest turret to attack on big or huge ships void hud_update_closest_turret() { hud_target_live_turret(1, 1); /* float nearest_distance, new_distance; ship_subsys *ss, *closest_subsys; ship *shipp; object *objp; nearest_distance = FLT_MAX; objp = &Objects[Player_ai->target_objnum]; shipp = &Ships[objp->instance]; closest_subsys = NULL; Assert(Ship_info[shipp->ship_info_index].flags & (SIF_BIG_SHIP|SIF_HUGE_SHIP)); for (ss=GET_FIRST(&shipp->subsys_list); ss!=END_OF_LIST(&shipp->subsys_list); ss=GET_NEXT(ss)) { if ( (ss->system_info->type == SUBSYSTEM_TURRET) && (ss->current_hits > 0) ) { // make sure turret is not "unused" if (ss->system_info->turret_weapon_type >= 0) { vec3d gsubpos; // get world pos of subsystem vm_vec_unrotate(&gsubpos, &ss->system_info->pnt, &objp->orient); vm_vec_add2(&gsubpos, &objp->pos); new_distance = vm_vec_dist_quick(&gsubpos, &Player_obj->pos); // GET TURRET TYPE, FAVOR BEAM, FLAK, OTHER int turret_type = ss->system_info->turret_weapon_type; if (Weapon_info[turret_type].wi_flags[Weapon::Info_Flags::Beam]) { new_distance *= 0.3f; } else if (Weapon_info[turret_type].wi_flags[Weapon::Info_Flags::Flak]) { new_distance *= 0.6f; } // check if facing and in view int facing = ship_subsystem_in_sight(objp, ss, &View_position, &gsubpos, 0); if (facing) { new_distance *= 0.5f; } // get the closest distance if (new_distance <= nearest_distance) { nearest_distance = new_distance; closest_subsys = ss; } } } } // check if new subsys to target if (Player_ai->targeted_subsys != NULL) { set_targeted_subsys(Player_ai, closest_subsys, Player_ai->target_objnum); shipp->last_targeted_subobject[Player_num] = Player_ai->targeted_subsys; } */ } // -------------------------------------------------------------------- // hud_target_targets_target() // // Target your target's target. Your target is specified by objnum passed // as a parameter. // void hud_target_targets_target() { object *objp = NULL; object *tt_objp = NULL; int tt_objnum; if ( Player_ai->target_objnum < 0 || Player_ai->target_objnum >= MAX_OBJECTS ) { goto ttt_fail; } objp = &Objects[Player_ai->target_objnum]; if ( objp->type != OBJ_SHIP ) { goto ttt_fail; } tt_objnum = Ai_info[Ships[objp->instance].ai_index].target_objnum; if ( tt_objnum < 0 || tt_objnum >= MAX_OBJECTS ) { goto ttt_fail; } if ( tt_objnum == OBJ_INDEX(Player_obj) ) { goto ttt_fail; } tt_objp = &Objects[tt_objnum]; if (hud_target_invalid_awacs(tt_objp)) { goto ttt_fail; } if ( tt_objp->type != OBJ_SHIP ) { goto ttt_fail; } if ( should_be_ignored(&Ships[tt_objp->instance]) ) { goto ttt_fail; } // if we've reached here, found player target's target set_target_objnum( Player_ai, tt_objnum ); hud_shield_hit_reset(&Objects[tt_objnum]); if (Objects[tt_objnum].type == OBJ_SHIP) { hud_maybe_set_sorted_turret_subsys(&Ships[Objects[tt_objnum].instance]); } hud_restore_subsystem_target(&Ships[Objects[tt_objnum].instance]); return; ttt_fail: snd_play( gamesnd_get_game_sound(GameSounds::TARGET_FAIL), 0.0f ); } // Return !0 if target_objp is a valid object type for targeting in reticle, otherwise return 0 int object_targetable_in_reticle(object *target_objp) { int obj_type; SCP_list::iterator jnp; if (target_objp == Player_obj ) { return 0; } obj_type = target_objp->type; if ( (obj_type == OBJ_SHIP) || (obj_type == OBJ_DEBRIS) || (obj_type == OBJ_WEAPON) || (obj_type == OBJ_ASTEROID) ) { return 1; } else if ( obj_type == OBJ_JUMP_NODE ) { for (jnp = Jump_nodes.begin(); jnp != Jump_nodes.end(); ++jnp) { if(jnp->GetSCPObject() == target_objp) break; } if (!jnp->IsHidden()) return 1; } return 0; } // hud_target_in_reticle_new() will target the object that is closest to the player, and who is // intersected by a ray passed from the center of the reticle out along the forward vector of the // player. // // targeting of objects of type OBJ_SHIP and OBJ_DEBRIS are supported // // Method: A ray is cast from the center of the reticle, and we keep track of any eligible object // the ray intersects. We take the ship closest to us that intersects an object. // // Since this method may work poorly with objects that are far away, hud_target_in_reticle_old() // is called if no intersections are found. // // #define TARGET_IN_RETICLE_DISTANCE 10000.0f void hud_target_in_reticle_new() { vec3d terminus; object *A; mc_info mc; float dist; SCP_list::iterator jnp; hud_reticle_clear_list(&Reticle_cur_list); Reticle_save_timestamp = timestamp(RESET_TARGET_IN_RETICLE); // Get 3d vector through center of reticle vm_vec_scale_add(&terminus, &Eye_position, &Player_obj->orient.vec.fvec, TARGET_IN_RETICLE_DISTANCE); mc_info_init(&mc); mc.model_instance_num = -1; mc.model_num = 0; for ( A = GET_FIRST(&obj_used_list); A !=END_OF_LIST(&obj_used_list); A = GET_NEXT(A) ) { if ( !object_targetable_in_reticle(A) ) { continue; } if ( A->type == OBJ_WEAPON ) { if ( !(Weapon_info[Weapons[A->instance].weapon_info_index].wi_flags[Weapon::Info_Flags::Can_be_targeted]) ) { if ( !(Weapon_info[Weapons[A->instance].weapon_info_index].wi_flags[Weapon::Info_Flags::Bomb]) ){ continue; } } if (Weapons[A->instance].lssm_stage==3){ continue; } } if ( A->type == OBJ_SHIP ) { if ( should_be_ignored(&Ships[A->instance]) ){ continue; } } if(hud_target_invalid_awacs(A)){ continue; } switch (A->type) { case OBJ_SHIP: mc.model_num = Ship_info[Ships[A->instance].ship_info_index].model_num; break; case OBJ_DEBRIS: mc.model_num = Debris[A->instance].model_num; break; case OBJ_WEAPON: mc.model_num = Weapon_info[Weapons[A->instance].weapon_info_index].model_num; break; case OBJ_ASTEROID: { int pof = 0; pof = Asteroids[A->instance].asteroid_subtype; mc.model_num = Asteroid_info[Asteroids[A->instance].asteroid_type].model_num[pof]; } break; case OBJ_JUMP_NODE: for (jnp = Jump_nodes.begin(); jnp != Jump_nodes.end(); ++jnp) { if(jnp->GetSCPObject() == A) break; } mc.model_num = jnp->GetModelNumber(); break; default: Int3(); // Illegal object type. } if (mc.model_num == -1) { // so just check distance of a point vec3d temp_v; float angle; vm_vec_sub(&temp_v, &A->pos, &Eye_position); vm_vec_normalize(&temp_v); angle = vm_vec_dot(&Player_obj->orient.vec.fvec, &temp_v); if (angle > 0.99f) { dist = vm_vec_mag_squared(&temp_v); hud_reticle_list_update(A, dist, 0); } } else { mc.orient = &A->orient; // The object's orient mc.pos = &A->pos; // The object's position mc.p0 = &Eye_position; // Point 1 of ray to check mc.p1 = &terminus; // Point 2 of ray to check mc.flags = MC_CHECK_MODEL; // | MC_ONLY_BOUND_BOX; // check the model, but only its bounding box model_collide(&mc); if ( mc.num_hits ) { dist = vm_vec_dist_squared(&mc.hit_point_world, &Eye_position); hud_reticle_list_update(A, dist, 0); } } } // end for (go to next object) hud_target_in_reticle_old(); // try the old method (works well with ships far away) } // hud_target_in_reticle_old() will target the object that is closest to the reticle center and inside // the reticle // // targeting of objects of type OBJ_SHIP and OBJ_DEBRIS are supported // // // Method: take the dot product of the foward vector and the vector to target. Take // the one that is closest to 1 and at least MIN_DOT_FOR_TARGET // // IMPORTANT: The MIN_DOT_FOR_TARGET value was arrived at by trial and error and // is only valid for the HUD reticle in use at that time. #define MIN_DOT_FOR_TARGET 0.9726// fov for targeting in reticle void hud_target_in_reticle_old() { object *A, *target_obj; float dot; vec3d vec_to_target; for ( A = GET_FIRST(&obj_used_list); A !=END_OF_LIST(&obj_used_list); A = GET_NEXT(A) ) { if ( !object_targetable_in_reticle(A) ) { continue; } if ( A->type == OBJ_WEAPON ) { if ( !(Weapon_info[Weapons[A->instance].weapon_info_index].wi_flags[Weapon::Info_Flags::Can_be_targeted]) ){ if ( !(Weapon_info[Weapons[A->instance].weapon_info_index].wi_flags[Weapon::Info_Flags::Bomb]) ){ continue; } } if (Weapons[A->instance].lssm_stage==3){ continue; } } if ( A->type == OBJ_SHIP ) { if ( should_be_ignored(&Ships[A->instance]) ){ continue; } } if(hud_target_invalid_awacs(A)){ continue; } if ( vm_vec_same( &A->pos, &Eye_position ) ) { continue; } vm_vec_normalized_dir(&vec_to_target, &A->pos, &Eye_position); dot = vm_vec_dot(&Player_obj->orient.vec.fvec, &vec_to_target); if ( dot > MIN_DOT_FOR_TARGET ) { hud_reticle_list_update(A, dot, 1); } } target_obj = hud_reticle_pick_target(); if ( target_obj != NULL ) { set_target_objnum( Player_ai, OBJ_INDEX(target_obj) ); hud_shield_hit_reset(target_obj); if ( target_obj->type == OBJ_SHIP ) { // if BIG|HUGE, maybe set subsys to turret hud_maybe_set_sorted_turret_subsys(&Ships[target_obj->instance]); hud_restore_subsystem_target(&Ships[target_obj->instance]); } } else { snd_play( gamesnd_get_game_sound(GameSounds::TARGET_FAIL), 0.0f ); } } // hud_target_subsystem_in_reticle() will target the subsystem that is within the reticle and // is closest to the reticle center. The current target is the only object that is searched for // subsystems // // Method: take the dot product of the foward vector and the vector to target. Take // the one that is closest to 1 and at least MIN_DOT_FOR_TARGET // // IMPORTANT: The MIN_DOT_FOR_TARGET value was arrived at by trial and error and // is only valid for the HUD reticle in use at that time. // void hud_target_subsystem_in_reticle() { object* targetp; ship_subsys *subsys; ship_subsys *nearest_subsys = NULL; vec3d subobj_pos; float dot, best_dot; vec3d vec_to_target; best_dot = -1.0f; if ( Player_ai->target_objnum == -1){ hud_target_in_reticle_old(); } if ( Player_ai->target_objnum == -1) { //-V581 snd_play( gamesnd_get_game_sound(GameSounds::TARGET_FAIL)); return; } targetp = &Objects[Player_ai->target_objnum]; if ( targetp->type != OBJ_SHIP ){ // only targeting subsystems on ship return; } int shipnum = targetp->instance; if ( targetp->type == OBJ_SHIP ) { if ( should_be_ignored(&Ships[shipnum]) ) { return; } } for (subsys = GET_FIRST(&Ships[shipnum].subsys_list); subsys != END_OF_LIST(&Ships[shipnum].subsys_list) ; subsys = GET_NEXT( subsys ) ) { //if the subsystem isn't targetable, skip it if (subsys->flags[Ship::Subsystem_Flags::Untargetable]) continue; get_subsystem_world_pos(targetp, subsys, &subobj_pos); vm_vec_normalized_dir(&vec_to_target, &subobj_pos, &Eye_position); dot = vm_vec_dot(&Player_obj->orient.vec.fvec, &vec_to_target); if ( dot > best_dot ) { best_dot = dot; if ( best_dot > MIN_DOT_FOR_TARGET ) nearest_subsys = subsys; } } // end for if ( nearest_subsys != NULL ) { set_targeted_subsys(Player_ai, nearest_subsys, Player_ai->target_objnum); char r_name[NAME_LENGTH]; int i; strcpy_s(r_name, ship_subsys_get_name(Player_ai->targeted_subsys)); for (i = 0; r_name[i] > 0; i++) { if (r_name[i] == '|') r_name[i] = ' '; } HUD_sourced_printf(HUD_SOURCE_HIDDEN, XSTR( "Targeting subsystem %s.", 323), r_name); Ships[shipnum].last_targeted_subobject[Player_num] = Player_ai->targeted_subsys; } else { snd_play( gamesnd_get_game_sound(GameSounds::TARGET_FAIL)); } } #define T_LENGTH 8 #define T_OFFSET_FROM_CIRCLE -13 #define T_BASE_LENGTH 4 // On entry: // color set void HudGaugeOrientationTee::renderOrientation(object *from_objp, object *to_objp, matrix *from_orientp) { float dot_product; vec3d target_to_obj; float x1,y1,x2,y2,x3,y3,x4,y4; vm_vec_sub(&target_to_obj, &from_objp->pos, &to_objp->pos); vm_vec_normalize(&target_to_obj); // calculate the dot product between the target_to_player vector and the targets forward vector // // 0 - vectors are perpendicular // 1 - vectors are collinear and in the same direction (target is facing player) // -1 - vectors are collinear and in the opposite direction (target is facing away from player) dot_product = vm_vec_dot(&from_orientp->vec.fvec, &target_to_obj); if (vm_vec_dot(&from_orientp->vec.rvec, &target_to_obj) >= 0) { if (dot_product >= 0){ dot_product = -PI_2*dot_product + PI; } else { dot_product = -PI_2*dot_product - PI; } } else { dot_product *= PI_2; //(range is now -PI/2 => PI/2) } y1 = sinf(dot_product) * (Radius - T_OFFSET_FROM_CIRCLE); x1 = cosf(dot_product) * (Radius - T_OFFSET_FROM_CIRCLE); y1 += position[1]; x1 += position[0]; x1 += HUD_offset_x; y1 += HUD_offset_y; y2 = sinf(dot_product) * (Radius - T_OFFSET_FROM_CIRCLE - T_LENGTH); x2 = cosf(dot_product) * (Radius - T_OFFSET_FROM_CIRCLE - T_LENGTH); y2 += position[1]; x2 += position[0]; x2 += HUD_offset_x; y2 += HUD_offset_y; x3 = x1 - T_BASE_LENGTH * sinf(dot_product); y3 = y1 + T_BASE_LENGTH * cosf(dot_product); x4 = x1 + T_BASE_LENGTH * sinf(dot_product); y4 = y1 - T_BASE_LENGTH * cosf(dot_product); // HACK! Should be antialiased! renderLine(fl2i(x3),fl2i(y3),fl2i(x4),fl2i(y4)); // bottom of T renderLine(fl2i(x1),fl2i(y1),fl2i(x2),fl2i(y2)); // part of T pointing towards center } void hud_tri(float x1,float y1,float x2,float y2,float x3,float y3) { int i; // Make the triangle always be the correct handiness so // the tmapper won't think its back-facing and throw it out. float det = (y2-y1)*(x3-x1) - (x2-x1)*(y3-y1); if ( det >= 0.0f ) { float tmp; // swap y1 & y3 tmp = y1; y1 = y3; y3 = tmp; // swap x1 & x3 tmp = x1; x1 = x3; x3 = tmp; } vertex verts[3]; // zero verts[] out, this is a faster way (nods to Kazan) to make sure that // the specular colors are set to 0 to avoid rendering problems - taylor memset(verts, 0, sizeof(verts)); verts[0].screen.xyw.x = x1; verts[0].screen.xyw.y = y1; verts[0].screen.xyw.w = 0.0f; verts[0].texture_position.u = 0.0f; verts[0].texture_position.v = 0.0f; verts[0].flags = PF_PROJECTED; verts[0].codes = 0; verts[0].r = (ubyte)gr_screen.current_color.red; verts[0].g = (ubyte)gr_screen.current_color.green; verts[0].b = (ubyte)gr_screen.current_color.blue; verts[0].a = (ubyte)gr_screen.current_color.alpha; verts[1].screen.xyw.x = x2; verts[1].screen.xyw.y = y2; verts[1].screen.xyw.w = 0.0f; verts[1].texture_position.u = 0.0f; verts[1].texture_position.v = 0.0f; verts[1].flags = PF_PROJECTED; verts[1].codes = 0; verts[1].r = (ubyte)gr_screen.current_color.red; verts[1].g = (ubyte)gr_screen.current_color.green; verts[1].b = (ubyte)gr_screen.current_color.blue; verts[1].a = (ubyte)gr_screen.current_color.alpha; verts[2].screen.xyw.x = x3; verts[2].screen.xyw.y = y3; verts[2].screen.xyw.w = 0.0f; verts[2].texture_position.u = 0.0f; verts[2].texture_position.v = 0.0f; verts[2].flags = PF_PROJECTED; verts[2].codes = 0; verts[2].r = (ubyte)gr_screen.current_color.red; verts[2].g = (ubyte)gr_screen.current_color.green; verts[2].b = (ubyte)gr_screen.current_color.blue; verts[2].a = (ubyte)gr_screen.current_color.alpha; for (i=0; i<3; i++) gr_resize_screen_posf(&verts[i].screen.xyw.x, &verts[i].screen.xyw.y); //uint saved_mode = gr_zbuffer_get(); //int cull = gr_set_cull(0); //gr_zbuffer_set( GR_ZBUFF_NONE ); material material_def; material_def.set_blend_mode(ALPHA_BLEND_NONE); material_def.set_depth_mode(ZBUFFER_TYPE_NONE); material_def.set_cull_mode(false); g3_render_primitives_colored(&material_def, verts, 3, PRIM_TYPE_TRIFAN, true); //g3_draw_poly_constant_sw(3, vertlist, TMAP_FLAG_GOURAUD | TMAP_FLAG_RGB | TMAP_FLAG_ALPHA, 0.1f); //gr_zbuffer_set( saved_mode ); //gr_set_cull(cull); } void hud_tri_empty(float x1,float y1,float x2,float y2,float x3,float y3) { gr_line(fl2i(x1),fl2i(y1),fl2i(x2),fl2i(y2)); gr_line(fl2i(x2),fl2i(y2),fl2i(x3),fl2i(y3)); gr_line(fl2i(x3),fl2i(y3),fl2i(x1),fl2i(y1)); } HudGaugeReticleTriangle::HudGaugeReticleTriangle(): HudGauge(HUD_OBJECT_HOSTILE_TRI, HUD_HOSTILE_TRIANGLE, false, false, VM_EXTERNAL | VM_DEAD_VIEW | VM_WARP_CHASE | VM_PADLOCK_ANY | VM_OTHER_SHIP, 255, 255, 255) { } HudGaugeReticleTriangle::HudGaugeReticleTriangle(int _gauge_object, int _gauge_config): HudGauge(_gauge_object, _gauge_config, true, false, VM_EXTERNAL | VM_DEAD_VIEW | VM_WARP_CHASE | VM_PADLOCK_ANY | VM_OTHER_SHIP, 255, 255, 255) { } void HudGaugeReticleTriangle::initRadius(int length) { Radius = length; } void HudGaugeReticleTriangle::initTriBase(float length) { Target_triangle_base = length; } void HudGaugeReticleTriangle::initTriHeight(float h) { Target_triangle_height = h; } void HudGaugeReticleTriangle::render(float /*frametime*/) { } // Render a missile warning triangle that has a tail on it to indicate distance void HudGaugeReticleTriangle::renderTriangleMissileTail(float ang, float xpos, float ypos, float cur_dist, int draw_solid, int draw_inside) { float x1=0.0f; float x2=0.0f; float y1=0.0f; float y2=0.0f; float xtail=0.0f; float ytail=0.0f; float sin_ang, cos_ang, tail_len; float max_tail_len=20.0f; sin_ang=sinf(ang); cos_ang=cosf(ang); if ( cur_dist < Min_warning_missile_dist ) { tail_len = 0.0f; } else if ( cur_dist > Max_warning_missile_dist ) { tail_len = max_tail_len; } else { tail_len = cur_dist/Max_warning_missile_dist * max_tail_len; } if ( draw_inside ) { x1 = xpos - Target_triangle_base * -sin_ang; y1 = ypos + Target_triangle_base * cos_ang; x2 = xpos + Target_triangle_base * -sin_ang; y2 = ypos - Target_triangle_base * cos_ang; xpos -= Target_triangle_height * cos_ang; ypos += Target_triangle_height * sin_ang; if ( tail_len > 0 ) { xtail = xpos - tail_len * cos_ang; ytail = ypos + tail_len * sin_ang; } } else { x1 = xpos - Target_triangle_base * -sin_ang; y1 = ypos + Target_triangle_base * cos_ang; x2 = xpos + Target_triangle_base * -sin_ang; y2 = ypos - Target_triangle_base * cos_ang; xpos += Target_triangle_height * cos_ang; ypos -= Target_triangle_height * sin_ang; if ( tail_len > 0 ) { xtail = xpos + tail_len * cos_ang; ytail = ypos - tail_len * sin_ang; } } gr_set_screen_scale(base_w, base_h); if (draw_solid) { hud_tri(xpos,ypos,x1,y1,x2,y2); } else { hud_tri_empty(xpos,ypos,x1,y1,x2,y2); } // draw the tail indicating length if ( tail_len > 0 ) { gr_line(fl2i(xpos), fl2i(ypos), fl2i(xtail), fl2i(ytail)); } gr_reset_screen_scale(); } // Render a triangle on the outside of the targeting circle. // Must be inside a g3_start_frame(). // If aspect_flag !0, then render filled, indicating aspect lock. // If show_interior !0, then point inwards to positions inside reticle void HudGaugeReticleTriangle::renderTriangle(vec3d *hostile_pos, int aspect_flag, int show_interior, int split_tri) { vertex hostile_vertex; float ang; float xpos,ypos,cur_dist,sin_ang,cos_ang; int draw_inside=0; // determine if the given object is within the targeting reticle // (which means the triangle is not drawn) cur_dist = vm_vec_dist_quick(&Player_obj->pos, hostile_pos); g3_rotate_vertex(&hostile_vertex, hostile_pos); g3_project_vertex(&hostile_vertex); if (hostile_vertex.codes == 0) { // on screen int projected_x, projected_y; if (!(hostile_vertex.flags & PF_OVERFLOW)) { // make sure point projected int mag_squared; projected_x = fl2i(hostile_vertex.screen.xyw.x); projected_y = fl2i(hostile_vertex.screen.xyw.y); unsize(&projected_x, &projected_y); mag_squared = (projected_x - position[0]) * (projected_x - position[0]) + (projected_y - position[1]) * (projected_y - position[1]); if ( mag_squared < Radius*Radius ) { if ( show_interior ) { draw_inside=1; } else { return; } } } } int HUD_nose_scaled_x = HUD_nose_x; int HUD_nose_scaled_y = HUD_nose_y; gr_resize_screen_pos(&HUD_nose_scaled_x, &HUD_nose_scaled_y); unsize( &hostile_vertex.world.xyz.x, &hostile_vertex.world.xyz.y ); ang = atan2_safe(hostile_vertex.world.xyz.y,hostile_vertex.world.xyz.x); sin_ang=sinf(ang); cos_ang=cosf(ang); if ( draw_inside ) { xpos = position[0] + cos_ang*(Radius-7); ypos = position[1] - sin_ang*(Radius-7); } else { xpos = position[0] + cos_ang*(Radius+4); ypos = position[1] - sin_ang*(Radius+4); } xpos += HUD_offset_x + HUD_nose_x; ypos += HUD_offset_y + HUD_nose_y; if ( split_tri ) { // renderTriangleMissileSplit(ang, xpos, ypos, cur_dist, aspect_flag, draw_inside); renderTriangleMissileTail(ang, xpos, ypos, cur_dist, aspect_flag, draw_inside); } else { float x1=0.0f; float x2=0.0f; float y1=0.0f; float y2=0.0f; if ( draw_inside ) { x1 = xpos - Target_triangle_base * -sin_ang; y1 = ypos + Target_triangle_base * cos_ang; x2 = xpos + Target_triangle_base * -sin_ang; y2 = ypos - Target_triangle_base * cos_ang; xpos -= Target_triangle_height * cos_ang; ypos += Target_triangle_height * sin_ang; } else { x1 = xpos - Target_triangle_base * -sin_ang; y1 = ypos + Target_triangle_base * cos_ang; x2 = xpos + Target_triangle_base * -sin_ang; y2 = ypos - Target_triangle_base * cos_ang; xpos += Target_triangle_height * cos_ang; ypos -= Target_triangle_height * sin_ang; } //renderPrintf(position[0], position[1], "%d", fl2i((360*ang)/(2*PI))); gr_set_screen_scale(base_w, base_h); if (aspect_flag) { hud_tri(xpos,ypos,x1,y1,x2,y2); } else { hud_tri_empty(xpos,ypos,x1,y1,x2,y2); } gr_reset_screen_scale(); } } // Show all homing missiles locked onto the player. // Also, play the beep! void hud_process_homing_missiles() { object *A; missile_obj *mo; weapon *wp; float dist, nearest_dist; int closest_is_aspect=0; nearest_dist = Homing_beep.max_cycle_dist; for ( mo = GET_NEXT(&Missile_obj_list); mo != END_OF_LIST(&Missile_obj_list); mo = GET_NEXT(mo) ) { A = &Objects[mo->objnum]; Assert((A->instance >= 0) && (A->instance < MAX_WEAPONS)); wp = &Weapons[A->instance]; if (wp->homing_object == Player_obj) { dist = vm_vec_dist_quick(&A->pos, &Player_obj->pos); if (dist < nearest_dist) { nearest_dist = dist; if ( Weapon_info[wp->weapon_info_index].is_locked_homing() ) { closest_is_aspect=1; } else { closest_is_aspect=0; } } } } // See if need to play warning beep. if (nearest_dist < Homing_beep.max_cycle_dist ) { float delta_time; float cycle_time; delta_time = f2fl(Missiontime - Homing_beep.last_time_played); // figure out the cycle time by doing a linear interpretation cycle_time = Homing_beep.min_cycle_time + (nearest_dist-Homing_beep.min_cycle_dist) * Homing_beep.precalced_interp; // play a new 'beep' if cycle time has elapsed if ( (delta_time*1000) > cycle_time ) { Homing_beep.last_time_played = Missiontime; if ( snd_is_playing(Homing_beep.snd_handle) ) { snd_stop(Homing_beep.snd_handle); } if ( closest_is_aspect ) { Homing_beep.snd_handle = snd_play(gamesnd_get_game_sound(ship_get_sound(Player_obj, GameSounds::PROXIMITY_ASPECT_WARNING))); } else { Homing_beep.snd_handle = snd_play(gamesnd_get_game_sound(ship_get_sound(Player_obj, GameSounds::PROXIMITY_WARNING))); } } } } HudGaugeMissileTriangles::HudGaugeMissileTriangles(): HudGaugeReticleTriangle(HUD_OBJECT_MISSILE_TRI, HUD_MISSILE_WARNING_ARROW) { } void HudGaugeMissileTriangles::render(float /*frametime*/) { object *A; missile_obj *mo; weapon *wp; bool in_frame = g3_in_frame() > 0; if(!in_frame) g3_start_frame(0); gr_set_screen_scale(base_w, base_h); gr_set_color_fast(&HUD_color_homing_indicator); for ( mo = GET_NEXT(&Missile_obj_list); mo != END_OF_LIST(&Missile_obj_list); mo = GET_NEXT(mo) ) { A = &Objects[mo->objnum]; Assert((A->instance >= 0) && (A->instance < MAX_WEAPONS)); wp = &Weapons[A->instance]; if (wp->homing_object == Player_obj) { renderTriangle(&A->pos, Weapon_info[wp->weapon_info_index].is_locked_homing(), 1, 1); } } gr_reset_screen_scale(); if(!in_frame) g3_end_frame(); } HudGaugeOrientationTee::HudGaugeOrientationTee(): HudGauge(HUD_OBJECT_ORIENTATION_TEE, HUD_ORIENTATION_TEE, true, false, VM_EXTERNAL | VM_DEAD_VIEW | VM_WARP_CHASE | VM_PADLOCK_ANY | VM_OTHER_SHIP, 255, 255, 255) { } void HudGaugeOrientationTee::initRadius(int length) { Radius = length; } void HudGaugeOrientationTee::pageIn() { } // hud_show_orientation_tee() will draw the orientation gauge that orbits the inside of the // outer reticle ring. If the T is at 12 o'clock, the target is facing the player, if the T // is at 6 o'clock the target is facing away from the player. If the T is at 3 or 9 o'clock // the target is facing 90 away from the player. void HudGaugeOrientationTee::render(float /*frametime*/) { object* targetp; if (Player_ai->target_objnum == -1 || Player->target_is_dying) return; targetp = &Objects[Player_ai->target_objnum]; if ( maybeFlashSexp() == 1 ) { hud_set_iff_color( targetp ); } else { hud_set_iff_color( targetp, 1); } renderOrientation(targetp, Player_obj, &targetp->orient); } // routine to draw a bounding box around a remote detonate missile and distance to void hud_process_remote_detonate_missile() { missile_obj *mo; object *mobjp; vertex target_point; memset(&target_point, 0, sizeof(target_point)); // 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) ) { Assert(mo->objnum >= 0 && mo->objnum < MAX_OBJECTS); mobjp = &Objects[mo->objnum]; if ((Player_obj != NULL) && (mobjp->parent_sig == Player_obj->parent_sig)) { if (Weapon_info[Weapons[mobjp->instance].weapon_info_index].wi_flags[Weapon::Info_Flags::Remote]) { // get box center point g3_rotate_vertex(&target_point,&mobjp->pos); // project vertex g3_project_vertex(&target_point); if (!(target_point.flags & PF_OVERFLOW)) { // make sure point projected switch ( mobjp->type ) { case OBJ_WEAPON: hud_target_add_display_list(mobjp, &target_point, &mobjp->pos, 0, iff_get_color(IFF_COLOR_MESSAGE, 1), NULL, TARGET_DISPLAY_DIST); break; default: Int3(); // should never happen return; } } } } } } // routine to possibly draw a bounding box around a ship sending a message to the player void hud_show_message_sender() { object *targetp; vertex target_point; // temp vertex used to find screen position for 3-D object; // don't draw brackets if no ship sending a message if ( Message_shipnum == -1 ) return; targetp = &Objects[Ships[Message_shipnum].objnum]; Assert ( targetp != NULL ); if (targetp->type != OBJ_SHIP) { // if it's not a ship (maybe it got ship-vanished in the middle of talking) then clear Message_shipnum Message_shipnum = -1; return; } // Don't do this for the ship you're flying! if ( targetp == Player_obj ) { return; } // Goober5000 - don't draw if primitive sensors if ( Ships[Player_obj->instance].flags[Ship::Ship_Flags::Primitive_sensors] ) { return; } // Karajorma - If we've gone to all the trouble to make our friendly ships stealthed they shouldn't then give away // their position cause they're feeling chatty // MageKing17 - Make the check see if they're actually stealthed at the time, and may as well include a check for // being hidden from sensors, too; logic copied from a similar check in hudescort.cpp if ( (Ships[Message_shipnum].flags[Ship::Ship_Flags::Hidden_from_sensors]) || ((Ships[Message_shipnum].flags[Ship::Ship_Flags::Stealth]) && ((Ships[Message_shipnum].team != Player_ship->team) || (Ships[Message_shipnum].flags[Ship::Ship_Flags::Friendly_stealth_invis]))) ) { return; } Assert ( targetp->instance >=0 && targetp->instance < MAX_SHIPS ); // check the object flags to see if this ship is gone. If so, then don't do this stuff anymore if ( targetp->flags[Object::Object_Flags::Should_be_dead] ) { Message_shipnum = -1; return; } memset(&target_point, 0, sizeof(target_point)); // find the current target vertex g3_rotate_vertex(&target_point, &targetp->pos); g3_project_vertex(&target_point); if (!(target_point.flags & PF_OVERFLOW)) { // make sure point projected hud_target_add_display_list(targetp, &target_point, &targetp->pos, 10, iff_get_color(IFF_COLOR_MESSAGE, 1), NULL, 0); } else if (target_point.codes != 0) { // target center is not on screen // draw the offscreen indicator at the edge of the screen where the target is closest to // AL 11-19-97: only show offscreen indicator if player sensors are functioning if ( (OBJ_INDEX(targetp) != Player_ai->target_objnum) || (Message_shipnum == Objects[Player_ai->target_objnum].instance) ) { if ( hud_sensors_ok(Player_ship, 0) ) { hud_target_add_display_list(targetp, &target_point, &targetp->pos, 0, iff_get_color(IFF_COLOR_MESSAGE, 1), NULL, TARGET_DISPLAY_DIST); } } } } // hud_prune_hotkeys() // // Check for ships that are dying, departed or dead. These should be removed from the player's // hotkey lists. void hud_prune_hotkeys() { int i; htarget_list *hitem, *plist; object *objp; ship *sp; for ( i = 0; i < MAX_KEYED_TARGETS; i++ ) { plist = &(Players[Player_num].keyed_targets[i]); if ( EMPTY( plist ) ) // no items in list, then do nothing continue; hitem = GET_FIRST(plist); while ( hitem != END_OF_LIST(plist) ) { int remove_item; remove_item = 0; objp = hitem->objp; Assert ( objp != NULL ); if ( objp->type == OBJ_SHIP ) { Assert ( objp->instance >=0 && objp->instance < MAX_SHIPS ); sp = &Ships[objp->instance]; } else { // if the object isn't a ship, it shouldn't be on the list, so remove it without question remove_item = 1; sp = NULL; } // check to see if the object is dying -- if so, remove it from the list // check to see if the ship is departing -- if so, remove it from the list if ( remove_item || (objp->flags[Object::Object_Flags::Should_be_dead]) || (sp->is_dying_or_departing()) ) { if ( sp != NULL ) { nprintf(("Network", "Hotkey: Pruning %s\n", sp->ship_name)); } htarget_list *temp; temp = GET_NEXT(hitem); list_remove( plist, hitem ); list_append( &htarget_free_list, hitem ); hitem->objp = NULL; hitem = temp; continue; } hitem = GET_NEXT( hitem ); } // end while } // end for // save the hotkey sets with mission time reaches a certain point. Code was put here because this // function always called for both single/multiplayer. Maybe not the best location, but whatever. mission_hotkey_maybe_save_sets(); } int HUD_drew_selection_bracket_on_target; // hud_show_selection_set draws some indicator around all the ships in the current selection set. No // indicators will be drawn if there is only 1 ship in the set. void hud_show_selection_set() { htarget_list *hitem, *plist; object *targetp; int set, count; vertex target_point; // temp vertex used to find screen position for 3-D object; HUD_drew_selection_bracket_on_target = 0; set = Players[Player_num].current_hotkey_set; if ( set == -1 ) return; Assert ( (set >= 0) && (set < MAX_KEYED_TARGETS) ); plist = &(Players[Player_num].keyed_targets[set]); count = 0; for ( hitem = GET_FIRST(plist); hitem != END_OF_LIST(plist); hitem = GET_NEXT(hitem) ) count++; if ( count == 0 ) { // only one ship, do nothing Players[Player_num].current_hotkey_set = -1; return; } memset(&target_point, 0, sizeof(target_point)); for ( hitem = GET_FIRST(plist); hitem != END_OF_LIST(plist); hitem = GET_NEXT(hitem) ) { targetp = hitem->objp; Assert ( targetp != NULL ); ship *target_shipp = NULL; Assert ( targetp->type == OBJ_SHIP ); Assert ( targetp->instance >=0 && targetp->instance < MAX_SHIPS ); target_shipp = &Ships[targetp->instance]; if ( (Game_mode & GM_MULTIPLAYER) && (target_shipp == Player_ship) ) { continue; } // Goober5000 - don't draw indicators for non-visible ships, per Mantis #1972 // (the only way we could get here is if the hotkey set contained a mix of visible // and invisible ships) if (awacs_get_level(targetp, Player_ship, 1) < 1) { continue; } // find the current target vertex // g3_rotate_vertex(&target_point,&targetp->pos); g3_project_vertex(&target_point); if (!(target_point.flags & PF_OVERFLOW)) { // make sure point projected switch ( targetp->type ) { case OBJ_SHIP: break; default: Int3(); // should never happen return; } if ( OBJ_INDEX(targetp) == Player_ai->target_objnum ) { hud_target_add_display_list(targetp, &target_point, &targetp->pos, 5, iff_get_color(IFF_COLOR_SELECTION, 1), NULL, 0); HUD_drew_selection_bracket_on_target = 1; } else if ( Cmdline_targetinfo ) { //Backslash -- show the distance and a lead indicator hud_target_add_display_list(targetp, &target_point, &targetp->pos, 5, iff_get_color(IFF_COLOR_SELECTION, 1), NULL, TARGET_DISPLAY_DIST | TARGET_DISPLAY_LEAD); } else { hud_target_add_display_list(targetp, &target_point, &targetp->pos, 5, iff_get_color(IFF_COLOR_SELECTION, 1), NULL, 0); } } if (target_point.codes != 0) { // target center is not on screen // draw the offscreen indicator at the edge of the screen where the target is closest to // AL 11-19-97: only show offscreen indicator if player sensors are functioning if ( OBJ_INDEX(targetp) != Player_ai->target_objnum ) { if ( hud_sensors_ok(Player_ship, 0) ) { hud_target_add_display_list(targetp, &target_point, &targetp->pos, 5, iff_get_color(IFF_COLOR_SELECTION, 1), NULL, 0); } } } } } // hud_show_targeting_gauges() will display the targeting information on the HUD. Called once per frame. // // Must be inside a g3_start_frame() // input: frametime => time in seconds since last update // in_cockpit => flag (default value 1) indicating whether viewpoint is from cockpit or external void hud_show_targeting_gauges(float frametime) { vertex target_point; // temp vertex used to find screen position for 3-D object; vec3d target_pos; hud_show_hostile_triangle(); hud_do_lock_indicators(frametime); if (Player_ai->target_objnum == -1) return; object * targetp = &Objects[Player_ai->target_objnum]; Players[Player_num].lead_indicator_active = 0; // check to see if there is even a current target if ( targetp == &obj_used_list ) { return; } // AL 1/20/97: Point to targted subsystem if one exists if ( Player_ai->targeted_subsys != NULL ) { get_subsystem_world_pos(targetp, Player_ai->targeted_subsys, &target_pos); } else { target_pos = targetp->pos; } // find the current target vertex // // The 2D screen pos depends on the current viewer position and orientation. g3_rotate_vertex(&target_point,&target_pos); hud_set_iff_color( targetp, 1 ); g3_project_vertex(&target_point); if (!(target_point.flags & PF_OVERFLOW)) { // make sure point projected if (target_point.codes == 0) { // target center is not on screen int target_display_flags; if(Cmdline_targetinfo) { target_display_flags = TARGET_DISPLAY_DIST | TARGET_DISPLAY_DOTS | TARGET_DISPLAY_SUBSYS | TARGET_DISPLAY_NAME | TARGET_DISPLAY_CLASS; } else { target_display_flags = TARGET_DISPLAY_DIST | TARGET_DISPLAY_DOTS | TARGET_DISPLAY_SUBSYS; } hud_target_add_display_list(targetp, &target_point, &targetp->pos, 0, NULL, NULL, target_display_flags); } } else { Hud_target_w = 0; Hud_target_h = 0; } // update cargo scanning hud_cargo_scan_update(targetp, frametime); // display the lock indicator if (!Player->target_is_dying) { // update and render artillery hud_artillery_update(); hud_artillery_render(); } if (target_point.codes != 0) { // draw the offscreen indicator at the edge of the screen where the target is closest to Assert(Player_ai->target_objnum >= 0); // AL 11-11-97: don't draw the indicator if the ship is messaging, the indicator is drawn // in the message sending color in hud_show_message_sender() if ( Message_shipnum != Objects[Player_ai->target_objnum].instance ) { hud_target_add_display_list(targetp, &target_point, &targetp->pos, 0, NULL, NULL, 0); } } } // hud_show_hostile_triangle() will draw an empty triangle that oribits around the outer // circle of the reticle. It will point to the closest enemy that is firing on the player. // Currently, it points to the closest enemy that has the player as its target_objnum and has // SM_ATTACK or SM_SUPER_ATTACK as its ai submode. void hud_show_hostile_triangle() { object* A; float min_distance=1e20f; float new_distance=0.0f; object* nearest_obj = &obj_used_list; ai_info *aip; ship_obj *so; ship *sp; ship_subsys *ss; int player_obj_index = OBJ_INDEX(Player_obj); int turret_is_attacking = 0; hostile_obj = NULL; for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) { A = &Objects[so->objnum]; sp = &Ships[A->instance]; // only look at ships who attack us if ( (A == Player_obj) || !(iff_x_attacks_y(Ships[A->instance].team, Player_ship->team)) ) { continue; } aip = &Ai_info[Ships[A->instance].ai_index]; // don't look at ignore ships if ( should_be_ignored(sp) ) { continue; } // always ignore cargo containers and navbuoys if ( Ship_info[sp->ship_info_index].class_type > -1 && !(Ship_types[Ship_info[sp->ship_info_index].class_type].flags[Ship::Type_Info_Flags::Show_attack_direction]) ) { continue; } // check if ship is stealthy if (awacs_get_level(&Objects[sp->objnum], Player_ship, 1) < 1) { continue; } turret_is_attacking = 0; // check if any turrets on ship are firing at the player (only on non fighter-bombers) if ( !(Ship_info[sp->ship_info_index].is_fighter_bomber()) ) { for (ss = GET_FIRST(&sp->subsys_list); ss != END_OF_LIST(&sp->subsys_list); ss = GET_NEXT(ss) ) { if (ss->flags[Ship::Subsystem_Flags::Untargetable]) continue; if ( (ss->system_info->type == SUBSYSTEM_TURRET) && (ss->current_hits > 0) ) { if ( ss->turret_enemy_objnum == player_obj_index ) { turret_is_attacking = 1; vec3d gsubpos; // get world pos of subsystem vm_vec_unrotate(&gsubpos, &ss->system_info->pnt, &A->orient); vm_vec_add2(&gsubpos, &A->pos); new_distance = vm_vec_dist_quick(&gsubpos, &Player_obj->pos); if (new_distance <= min_distance) { min_distance=new_distance; nearest_obj = A; } } } } } if ( !turret_is_attacking ) { // check for ships attacking the player if ( aip->target_objnum != Player_ship->objnum ) { continue; } // ignore enemy if not in chase mode if ( (Game_mode & GM_NORMAL) && (aip->mode != AIM_CHASE) ) { continue; } new_distance = vm_vec_dist_quick(&A->pos, &Player_obj->pos); if (new_distance <= min_distance) { min_distance=new_distance; nearest_obj = A; } } } if ( nearest_obj == &obj_used_list ) { return; } if ( min_distance > MIN_DISTANCE_TO_CONSIDER_THREAT ) { return; } hostile_obj = nearest_obj; // hook to maybe warn player about this attacking ship ship_maybe_warn_player(&Ships[nearest_obj->instance], min_distance); } HudGaugeHostileTriangle::HudGaugeHostileTriangle(): HudGaugeReticleTriangle(HUD_OBJECT_HOSTILE_TRI, HUD_HOSTILE_TRIANGLE) { } void HudGaugeHostileTriangle::render(float /*frametime*/) { if (hostile_obj && maybeFlashSexp() != 1) { bool in_frame = g3_in_frame() > 0; if(!in_frame) g3_start_frame(0); // hud_set_iff_color( TEAM_HOSTILE, 1 ); // Note: This should really be TEAM_HOSTILE, not opposite of Player_ship->team. hud_set_iff_color( hostile_obj, 1 ); renderTriangle(&hostile_obj->pos, 0, 1, 0); if(!in_frame) g3_end_frame(); } } void hud_calculate_lead_pos(vec3d *lead_target_pos, vec3d *target_pos, object *targetp, weapon_info *wip, float dist_to_target, vec3d *rel_pos) { vec3d target_moving_direction; vec3d last_delta_vector; float time_to_target, target_moved_dist; if(wip->max_speed != 0) { time_to_target = dist_to_target / wip->max_speed; } else { time_to_target = 0; } target_moved_dist = targetp->phys_info.speed * time_to_target; target_moving_direction = targetp->phys_info.vel; if(The_mission.ai_profile->flags[AI::Profile_Flags::Use_additive_weapon_velocity]) vm_vec_scale_sub2(&target_moving_direction, &Player_obj->phys_info.vel, wip->vel_inherit_amount); // test if the target is moving at all if ( vm_vec_mag_quick(&target_moving_direction) < 0.1f ) { // Find distance! *lead_target_pos = *target_pos; } else { vm_vec_normalize(&target_moving_direction); vm_vec_scale(&target_moving_direction, target_moved_dist); vm_vec_add(lead_target_pos, target_pos, &target_moving_direction ); polish_predicted_target_pos(wip, targetp, target_pos, lead_target_pos, dist_to_target, &last_delta_vector, 1); // Not used:, float time_to_enemy) if(rel_pos) { // needed for quick lead indicators, not needed for normal lead indicators. vm_vec_add2(lead_target_pos, rel_pos); } } } // Return the bank number for the primary weapon that can fire the farthest, from // the number of active primary weapons // input: range => output parameter... it is the range of the selected bank int hud_get_best_primary_bank(float *range) { int i, best_bank, bank_to_fire, num_to_test; float weapon_range, farthest_weapon_range; ship_weapon *swp; weapon_info *wip; swp = &Player_ship->weapons; farthest_weapon_range = 0.0f; best_bank = -1; if ( Player_ship->flags[Ship::Ship_Flags::Primary_linked] ) { num_to_test = swp->num_primary_banks; } else { num_to_test = MIN(1, swp->num_primary_banks); } for ( i = 0; i < num_to_test; i++ ) { bank_to_fire = (swp->current_primary_bank + i) % swp->num_primary_banks; // calculate the range of the weapon, and only display the lead target indicator // if the weapon can actually hit the target Assert(bank_to_fire >= 0 && bank_to_fire < swp->num_primary_banks); Assert(swp->primary_bank_weapons[bank_to_fire] < weapon_info_size()); if (swp->primary_bank_weapons[bank_to_fire] < 0) continue; wip = &Weapon_info[swp->primary_bank_weapons[bank_to_fire]]; weapon_range = MIN((wip->max_speed * wip->lifetime), wip->weapon_range); // don't consider this primary if it's a ballistic that's out of ammo - Goober5000 if ( wip->wi_flags[Weapon::Info_Flags::Ballistic] ) { if ( swp->primary_bank_ammo[bank_to_fire] <= 0) { continue; } } if ( weapon_range > farthest_weapon_range ) { best_bank = bank_to_fire; farthest_weapon_range = weapon_range; } } *range = farthest_weapon_range; return best_bank; } // ----------------------------------------------------------------------------- // polish_predicted_target_pos() // // Called by the draw lead indicator code to predict where the enemy is going to be // void polish_predicted_target_pos(weapon_info *wip, object *targetp, vec3d *enemy_pos, vec3d *predicted_enemy_pos, float dist_to_enemy, vec3d *last_delta_vec, int num_polish_steps, object *reference_object) { Assertion(reference_object != nullptr, "polish_predicted_target_pos received a nullptr for its reference ship, this is a coder mistake, please report!"); int iteration; vec3d reference_pos = reference_object->pos; float time_to_enemy; vec3d last_predicted_enemy_pos = *predicted_enemy_pos; float weapon_speed = wip->max_speed; vm_vec_zero(last_delta_vec); vec3d enemy_vel = targetp->phys_info.vel; vec3d enemy_acc; if (The_mission.ai_profile->second_order_lead_predict_factor > 0) { vec3d world_rotvel; vm_vec_unrotate(&world_rotvel, &targetp->phys_info.rotvel, &targetp->orient); vm_vec_cross(&enemy_acc, &world_rotvel, &enemy_vel); } // additive velocity stuff // not just the player's main target if (The_mission.ai_profile->flags[AI::Profile_Flags::Use_additive_weapon_velocity]) { vm_vec_scale_sub2( &enemy_vel, &reference_object->phys_info.vel, wip->vel_inherit_amount); } for (iteration=0; iteration < num_polish_steps; iteration++) { dist_to_enemy = vm_vec_dist_quick(predicted_enemy_pos, &reference_pos); time_to_enemy = dist_to_enemy/weapon_speed; vm_vec_scale_add(predicted_enemy_pos, enemy_pos, &enemy_vel, time_to_enemy); if (The_mission.ai_profile->second_order_lead_predict_factor > 0) { vm_vec_scale_add(predicted_enemy_pos, predicted_enemy_pos, &enemy_acc, (time_to_enemy * time_to_enemy / 2) * The_mission.ai_profile->second_order_lead_predict_factor); } vm_vec_sub(last_delta_vec, predicted_enemy_pos, &last_predicted_enemy_pos); last_predicted_enemy_pos= *predicted_enemy_pos; } } HudGaugeLeadIndicator::HudGaugeLeadIndicator(): HudGauge(HUD_OBJECT_LEAD, HUD_LEAD_INDICATOR, false, false, VM_EXTERNAL | VM_DEAD_VIEW | VM_WARP_CHASE | VM_PADLOCK_ANY | VM_OTHER_SHIP, 255, 255, 255) { } void HudGaugeLeadIndicator::initHalfSize(float w, float h) { Lead_indicator_half[0] = w; Lead_indicator_half[1] = h; } void HudGaugeLeadIndicator::initBitmaps(char *fname) { Lead_indicator_gauge.first_frame = bm_load_animation(fname, &Lead_indicator_gauge.num_frames); if ( Lead_indicator_gauge.first_frame < 0 ) { Warning(LOCATION,"Cannot load hud ani: %s\n", fname); } } void HudGaugeLeadIndicator::pageIn() { bm_page_in_aabitmap(Lead_indicator_gauge.first_frame, Lead_indicator_gauge.num_frames); } // determine the correct frame to draw for the lead indicator // 0 -> center only (in secondary range only) // 1 -> full (in secondary and primary range) // 2 -> oustide only (in primary range only) // // input: prange => range of current primary weapon // srange => range of current secondary weapon // dist_to_target => current dist to target // // exit: 0-2 => frame offset // -1 => don't draw anything int HudGaugeLeadIndicator::pickFrame(float prange, float srange, float dist_to_target) { int frame_offset=-1; int in_prange=0, in_srange=0; if ( dist_to_target < prange ) { in_prange=1; } if ( dist_to_target < srange ) { in_srange=1; } if ( in_prange && in_srange ) { frame_offset=1; } else if ( in_prange && !in_srange ) { frame_offset=2; } else if ( !in_prange && in_srange ) { frame_offset=0; } else { frame_offset=-1; } return frame_offset; } void HudGaugeLeadIndicator::render(float /*frametime*/) { if(Player->target_is_dying) { return; } bool in_frame = g3_in_frame() > 0; if(!in_frame) g3_start_frame(0); // first render the current target the player has selected. renderLeadCurrentTarget(); // if extra targeting info is enabled, render lead indicators for objects in the target display list. for(size_t i = 0; i < target_display_list.size(); i++) { if ( (target_display_list[i].flags & TARGET_DISPLAY_LEAD) && target_display_list[i].objp ) { // set the color if( target_display_list[i].bracket_clr.red && target_display_list[i].bracket_clr.green && target_display_list[i].bracket_clr.blue ) { gr_set_color_fast(&target_display_list[i].bracket_clr); } else { // use IFF colors if none defined. hud_set_iff_color(target_display_list[i].objp, 1); } renderLeadQuick(&target_display_list[i].target_pos, target_display_list[i].objp); } } if(!in_frame) g3_end_frame(); } void HudGaugeLeadIndicator::renderIndicator(int frame_offset, object *targetp, vec3d *lead_target_pos) { vertex lead_target_vertex; int sx, sy; g3_rotate_vertex(&lead_target_vertex, lead_target_pos); if (lead_target_vertex.codes == 0) { // on screen g3_project_vertex(&lead_target_vertex); if (!(lead_target_vertex.flags & PF_OVERFLOW)) { if ( maybeFlashSexp() == 1 ) { hud_set_iff_color(targetp, 0); } else { hud_set_iff_color(targetp, 1); } if ( Lead_indicator_gauge.first_frame + frame_offset >= 0 ) { sx = fl2i(lead_target_vertex.screen.xyw.x); sy = fl2i(lead_target_vertex.screen.xyw.y); unsize(&sx, &sy); renderBitmap(Lead_indicator_gauge.first_frame + frame_offset, fl2i(sx - Lead_indicator_half[0]), fl2i(sy - Lead_indicator_half[1])); } } } } // HudGaugeLeadIndicator::renderTargetLead() determine where to draw the lead target box and display it void HudGaugeLeadIndicator::renderLeadCurrentTarget() { vec3d target_pos; vec3d source_pos; vec3d *rel_pos; vec3d lead_target_pos; object *targetp; polymodel *pm; ship_weapon *swp; weapon_info *wip; weapon_info *tmp=NULL; float dist_to_target, prange, srange; int bank_to_fire, frame_offset; if (Player_ai->target_objnum == -1) return; targetp = &Objects[Player_ai->target_objnum]; if ( (targetp->type != OBJ_SHIP) && (targetp->type != OBJ_WEAPON) && (targetp->type != OBJ_ASTEROID) ) { return; } // only allow bombs to have lead indicator displayed if ( targetp->type == OBJ_WEAPON ) { if ( !(Weapon_info[Weapons[targetp->instance].weapon_info_index].wi_flags[Weapon::Info_Flags::Can_be_targeted]) ) { if ( !(Weapon_info[Weapons[targetp->instance].weapon_info_index].wi_flags[Weapon::Info_Flags::Bomb]) ) { return; } } } // If the target is out of range, then draw the correct frame for the lead indicator if ( Lead_indicator_gauge.first_frame == -1 ) { Int3(); return; } // AL 1/20/97: Point to targted subsystem if one exists if ( Player_ai->targeted_subsys != NULL ) { get_subsystem_world_pos(targetp, Player_ai->targeted_subsys, &target_pos); } else { target_pos = targetp->pos; } pm = model_get(Ship_info[Player_ship->ship_info_index].model_num); swp = &Player_ship->weapons; // Added to take care of situation where there are no primary banks on the player ship // (this may not be possible, depending on what we decide for the weapons loadout rules) if ( swp->num_primary_banks == 0 ) return; bank_to_fire = hud_get_best_primary_bank(&prange); if ( bank_to_fire < 0 ) return; wip = &Weapon_info[swp->primary_bank_weapons[bank_to_fire]]; if (pm->n_guns && bank_to_fire != -1 ) { rel_pos = &pm->gun_banks[bank_to_fire].pnt[0]; } else { rel_pos = NULL; } // source_pos will contain the world coordinate of where to base the lead indicator prediction // from. Normally, this will be the world pos of the gun turret of the currently selected primary // weapon. source_pos = Player_obj->pos; if (rel_pos != NULL) { vec3d gun_point; vm_vec_unrotate(&gun_point, rel_pos, &Player_obj->orient); vm_vec_add2(&source_pos, &gun_point); } // Determine "accurate" distance to target. // This is the distance from the player ship to: // (if targeting a subsystem) the distance to the subsystem centre // (playing it safe, will usually be in range at slightly further away due to subsys radius) // (otherwise) the closest point on the bounding box of the target if ( Player_ai->targeted_subsys != NULL ) { dist_to_target = vm_vec_dist(&target_pos, &Player_obj->pos); } else { dist_to_target = hud_find_target_distance(targetp, Player_obj); } srange = ship_get_secondary_weapon_range(Player_ship); if ( (swp->current_secondary_bank >= 0) && (swp->secondary_bank_weapons[swp->current_secondary_bank] >= 0) ) { int bank = swp->current_secondary_bank; tmp = &Weapon_info[swp->secondary_bank_weapons[bank]]; if ( !(tmp->is_homing()) && !(tmp->is_locked_homing() && Player->target_in_lock_cone) ) { //The secondary lead indicator is handled farther below if it is a non-locking type srange = -1.0f; } } frame_offset = pickFrame(prange, srange, dist_to_target); if ( frame_offset < 0 && srange != -1.0f ) { return; } if (frame_offset >= 0) { hud_calculate_lead_pos(&lead_target_pos, &target_pos, targetp, wip, dist_to_target); renderIndicator(frame_offset, targetp, &lead_target_pos); } //do dumbfire lead indicator - color is orange (255,128,0) - bright, (192,96,0) - dim //phreak changed 9/01/02 if((swp->current_secondary_bank>=0) && (swp->secondary_bank_weapons[swp->current_secondary_bank] >= 0)) { int bank=swp->current_secondary_bank; wip=&Weapon_info[swp->secondary_bank_weapons[bank]]; //get out of here if the secondary weapon is a homer or if its out of range if ( wip->is_homing() ) return; double max_dist = MIN((wip->lifetime * wip->max_speed), wip->weapon_range); if (dist_to_target > max_dist) return; } hud_calculate_lead_pos(&lead_target_pos, &target_pos, targetp, wip, dist_to_target); renderIndicator(0, targetp, &lead_target_pos); } //Backslash // A stripped-down version of the lead indicator, only shows primary weapons // and works for a specified target (not just the current selected target). // Ideally I'd like to later turn this into something (or make a new function) that would actually WORK with gun convergence/normals // instead of the existing code (copied from above) that does some calculations and then is ignored ;-) // (Go look, what's it actually DO with source_pos?) // And also, something that could be called for multiple weapons, ITTS style. void HudGaugeLeadIndicator::renderLeadQuick(vec3d *target_world_pos, object *targetp) { vec3d source_pos; vec3d *rel_pos; vec3d lead_target_pos; polymodel *pm; ship_weapon *swp; weapon_info *wip; float dist_to_target, prange; int bank_to_fire, frame_offset; if ( (targetp->type != OBJ_SHIP) && (targetp->type != OBJ_WEAPON) && (targetp->type != OBJ_ASTEROID) ) { return; } // only allow bombs to have lead indicator displayed if ( targetp->type == OBJ_WEAPON ) { if ( !(Weapon_info[Weapons[targetp->instance].weapon_info_index].wi_flags[Weapon::Info_Flags::Can_be_targeted]) ) { if ( !(Weapon_info[Weapons[targetp->instance].weapon_info_index].wi_flags[Weapon::Info_Flags::Bomb]) ) { return; } } } // If the target is out of range, then draw the correct frame for the lead indicator if ( Lead_indicator_gauge.first_frame == -1 ) { Int3(); return; } pm = model_get(Ship_info[Player_ship->ship_info_index].model_num); swp = &Player_ship->weapons; // Added to take care of situation where there are no primary banks on the player ship // (this may not be possible, depending on what we decide for the weapons loadout rules) if ( swp->num_primary_banks == 0 ) return; bank_to_fire = hud_get_best_primary_bank(&prange); //Backslash note: this! if ( bank_to_fire < 0 ) return; wip = &Weapon_info[swp->primary_bank_weapons[bank_to_fire]]; if (pm->n_guns && bank_to_fire != -1 ) { rel_pos = &pm->gun_banks[bank_to_fire].pnt[0]; } else { rel_pos = NULL; } // vec3d firing_vec; // vm_vec_unrotate(&firing_vec, &po->gun_banks[bank_to_fire].norm[pt], &obj->orient); // vm_vector_2_matrix(&firing_orient, &firing_vec, NULL, NULL); // source_pos will contain the world coordinate of where to base the lead indicator prediction // from. Normally, this will be the world pos of the gun turret of the currently selected primary // weapon. source_pos = Player_obj->pos; if (rel_pos != NULL) { vec3d gun_point; vm_vec_unrotate(&gun_point, rel_pos, &Player_obj->orient); vm_vec_add2(&source_pos, &gun_point); } // Determine "accurate" distance to target. This is the distance from the player ship // to the closest point on the bounding box of the target dist_to_target = hud_find_target_distance(targetp, Player_obj); frame_offset = pickFrame(prange, -1.0f, dist_to_target); if ( frame_offset < 0 ) { return; } hud_calculate_lead_pos(&lead_target_pos, target_world_pos, targetp, wip, dist_to_target, rel_pos); renderIndicator(frame_offset, targetp, &lead_target_pos); } HudGaugeLeadSight::HudGaugeLeadSight(): HudGauge(HUD_OBJECT_LEAD_SIGHT, HUD_LEAD_INDICATOR, true, false, VM_EXTERNAL | VM_DEAD_VIEW | VM_WARP_CHASE | VM_PADLOCK_ANY | VM_OTHER_SHIP, 255, 255, 255) { } void HudGaugeLeadSight::initBitmaps(char *fname) { Lead_sight.first_frame = bm_load_animation(fname, &Lead_sight.num_frames); if ( Lead_sight.first_frame < 0 ) { Warning(LOCATION,"Cannot load hud ani: %s\n", fname); } else { int w, h; bm_get_info(Lead_sight.first_frame, &w, &h); Lead_sight_half[0] = fl2i(w * 0.5f); Lead_sight_half[1] = fl2i(h * 0.5f); } } void HudGaugeLeadSight::renderSight(int frame_offset, vec3d *target_pos, vec3d *lead_target_pos) { vertex target_vertex; float target_sx; float target_sy; vertex lead_target_vertex; float target_lead_sx; float target_lead_sy; // first see if the lead is on screen g3_rotate_vertex(&lead_target_vertex, lead_target_pos); if (lead_target_vertex.codes != 0) return; g3_project_vertex(&lead_target_vertex); if (lead_target_vertex.flags & PF_OVERFLOW) return; target_lead_sx = lead_target_vertex.screen.xyw.x; target_lead_sy = lead_target_vertex.screen.xyw.y; // now see if the target is on screen g3_rotate_vertex(&target_vertex, target_pos); if (target_vertex.codes != 0) return; g3_project_vertex(&target_vertex); if (target_vertex.flags & PF_OVERFLOW) return; target_sx = target_vertex.screen.xyw.x; target_sy = target_vertex.screen.xyw.y; // render the lead sight if ( Lead_sight.first_frame >= 0 ) { unsize(&target_lead_sx, &target_lead_sy); unsize(&target_sx, &target_sy); float reticle_target_sx = target_sx - Lead_sight_half[0] - target_lead_sx; float reticle_target_sy = target_sy - Lead_sight_half[1] - target_lead_sy; reticle_target_sx += position[0] + 0.5f; reticle_target_sy += position[1] + 0.5f; setGaugeColor(); renderBitmap(Lead_sight.first_frame + frame_offset, fl2i(reticle_target_sx) + fl2i(HUD_offset_x), fl2i(reticle_target_sy) + fl2i(HUD_offset_y)); } } void HudGaugeLeadSight::pageIn() { bm_page_in_aabitmap(Lead_sight.first_frame, Lead_sight.num_frames); } void HudGaugeLeadSight::render(float /*frametime*/) { vec3d target_pos; vec3d source_pos; vec3d *rel_pos; vec3d lead_target_pos; object *targetp; polymodel *pm; ship_weapon *swp; weapon_info *wip; float dist_to_target, prange; int bank_to_fire; if (Player_ai->target_objnum == -1) return; targetp = &Objects[Player_ai->target_objnum]; if ( (targetp->type != OBJ_SHIP) && (targetp->type != OBJ_WEAPON) && (targetp->type != OBJ_ASTEROID) ) { return; } // only allow bombs to have lead indicator displayed if ( targetp->type == OBJ_WEAPON ) { if ( !(Weapon_info[Weapons[targetp->instance].weapon_info_index].wi_flags[Weapon::Info_Flags::Bomb]) ) { return; } } // If the target is out of range, then draw the correct frame for the lead indicator if ( Lead_sight.first_frame == -1 ) { Int3(); return; } // AL 1/20/97: Point to targeted subsystem if one exists if ( Player_ai->targeted_subsys != NULL ) { get_subsystem_world_pos(targetp, Player_ai->targeted_subsys, &target_pos); } else { target_pos = targetp->pos; } pm = model_get(Ship_info[Player_ship->ship_info_index].model_num); swp = &Player_ship->weapons; // Added to take care of situation where there are no primary banks on the player ship // (this may not be possible, depending on what we decide for the weapons loadout rules) if ( swp->num_primary_banks == 0 ) return; bank_to_fire = hud_get_best_primary_bank(&prange); if ( bank_to_fire < 0 ) return; wip = &Weapon_info[swp->primary_bank_weapons[bank_to_fire]]; if (pm->n_guns && bank_to_fire != -1 ) { rel_pos = &pm->gun_banks[bank_to_fire].pnt[0]; } else { rel_pos = NULL; } // source_pos will contain the world coordinate of where to base the lead indicator prediction // from. Normally, this will be the world pos of the gun turret of the currently selected primary // weapon. source_pos = Player_obj->pos; if (rel_pos != NULL) { vec3d gun_point; vm_vec_unrotate(&gun_point, rel_pos, &Player_obj->orient); vm_vec_add2(&source_pos, &gun_point); } // Determine "accurate" distance to target. This is the distance from the player ship // to the closest point on the bounding box of the target dist_to_target = hud_find_target_distance(targetp, Player_obj); bool in_frame; if ( dist_to_target < prange ) { // fire it up in_frame = g3_in_frame() > 0; if(!in_frame) { g3_start_frame(0); } hud_calculate_lead_pos(&lead_target_pos, &target_pos, targetp, wip, dist_to_target); renderSight(1, &target_pos, &lead_target_pos); // render the primary weapon lead sight if(!in_frame) { g3_end_frame(); } } //do dumbfire lead indicator - color is orange (255,128,0) - bright, (192,96,0) - dim //phreak changed 9/01/02 if((swp->current_secondary_bank>=0) && (swp->secondary_bank_weapons[swp->current_secondary_bank] >= 0)) { int bank=swp->current_secondary_bank; wip=&Weapon_info[swp->secondary_bank_weapons[bank]]; //get out of here if the secondary weapon is a homer or if its out of range if ( wip->is_homing() ) { return; } double max_dist = MIN((wip->lifetime * wip->max_speed), wip->weapon_range); if (dist_to_target > max_dist) { return; } } else { return; } // fire it up in_frame = g3_in_frame() > 0; if(!in_frame) { g3_start_frame(0); } //give it the "in secondary range frame hud_calculate_lead_pos(&lead_target_pos, &target_pos, targetp, wip, dist_to_target); renderSight(0, &target_pos, &lead_target_pos); // now render the secondary weapon lead sight if(!in_frame) { g3_end_frame(); } } void hud_cease_subsystem_targeting(bool print_message) { int ship_index; Assertion(Player_ai->target_objnum < MAX_OBJECTS, "Invalid player target objnum"); if (Player_ai->target_objnum < 0) { // Nothing selected if (print_message) { HUD_sourced_printf(HUD_SOURCE_HIDDEN, "%s", XSTR("No target selected.", 322)); snd_play(gamesnd_get_game_sound(GameSounds::TARGET_FAIL)); } return; } if (Objects[Player_ai->target_objnum].type != OBJ_SHIP) { // Object isn't a ship, so it doesn't have any subsystems if (print_message) { snd_play(gamesnd_get_game_sound(GameSounds::TARGET_FAIL)); } return; } ship_index = Objects[Player_ai->target_objnum].instance; Assertion(ship_index >= 0 && ship_index < MAX_SHIPS, "Invalid ship index"); Assertion(Player_num >= 0 && Player_num < MAX_PLAYERS, "Invalid player number"); Ships[ship_index].last_targeted_subobject[Player_num] = NULL; Player_ai->targeted_subsys = NULL; Player_ai->targeted_subsys_parent = -1; if ( print_message ) { HUD_sourced_printf(HUD_SOURCE_HIDDEN, "%s", XSTR( "Deactivating sub-system targeting", 324)); } hud_stop_looped_locking_sounds(); hud_lock_reset(); } void hud_cease_targeting(bool deliberate) { // Turn off subsys targeting before we invalidate the player target_objnum... hud_cease_subsystem_targeting(false); set_target_objnum(Player_ai, -1); if (deliberate) { // Player wants to stop targeting, so turn off auto-target so we don't immediately aquire another target Players[Player_num].flags &= ~PLAYER_FLAGS_AUTO_TARGETING; HUD_sourced_printf(HUD_SOURCE_HIDDEN, "%s", XSTR("Deactivating targeting system", 325)); } } // hud_restore_subsystem_target() will remember the last targeted subsystem // on a target. // void hud_restore_subsystem_target(ship* shipp) { // check if there was a previously targeted sub-system for this target if ( shipp->last_targeted_subobject[Player_num] != NULL ) { Player_ai->targeted_subsys = shipp->last_targeted_subobject[Player_num]; Player_ai->targeted_subsys_parent = Player_ai->target_objnum; } else { Player_ai->targeted_subsys = NULL; Player_ai->targeted_subsys_parent = -1; } } // -------------------------------------------------------------------------------- // get_subsystem_world_pos() returns the world position for a given subsystem on a ship // vec3d* get_subsystem_world_pos(object* parent_obj, ship_subsys* subsys, vec3d* world_pos) { get_subsystem_pos(world_pos, parent_obj, subsys); return world_pos; } // ---------------------------------------------------------------------------- // hud_target_change_check() // // called once per frame to account for when the target changes // void hud_target_change_check() { float current_speed=0.0f; // Check if player subsystem target has changed, and reset necessary player flag if ( Player_ai->targeted_subsys != Player_ai->last_subsys_target ) { Player->subsys_in_view=-1; } // check if the main target has changed if (Player_ai->last_target != Player_ai->target_objnum) { if ( Player_ai->target_objnum != -1){ snd_play( gamesnd_get_game_sound(ship_get_sound(Player_obj, GameSounds::TARGET_ACQUIRE)), 0.0f ); } // if we have a hotkey set active, see if new target is in set. If not in // set, deselect the current hotkey set. if ( Player->current_hotkey_set != -1 ) { htarget_list *hitem, *plist; plist = &(Player->keyed_targets[Player->current_hotkey_set]); for ( hitem = GET_FIRST(plist); hitem != END_OF_LIST(plist); hitem = GET_NEXT(hitem) ) { if ( OBJ_INDEX(hitem->objp) == Player_ai->target_objnum ){ break; } } if ( hitem == END_OF_LIST(plist) ){ Player->current_hotkey_set = -1; } } player_stop_cargo_scan_sound(); if ( (Player_ai->target_objnum >= 0) && (Player_ai->target_objnum < MAX_OBJECTS) ) { hud_shield_hit_reset(&Objects[Player_ai->target_objnum]); } hud_targetbox_init_flash(); hud_targetbox_start_flash(TBOX_FLASH_NAME); hud_gauge_popup_start(HUD_TARGET_MINI_ICON); Player->cargo_inspect_time=0; Player->locking_subsys=NULL; Player->locking_on_center=0; Player->locking_subsys_parent=-1; Player_ai->current_target_dist_trend = NO_CHANGE; Player_ai->current_target_speed_trend = NO_CHANGE; if ( Players[Player_num].flags & PLAYER_FLAGS_AUTO_MATCH_SPEED ) { Players[Player_num].flags &= ~PLAYER_FLAGS_MATCH_TARGET; player_match_target_speed(); } else { if ( Players[Player_num].flags & PLAYER_FLAGS_MATCH_TARGET ) Players[Player_num].flags &= ~PLAYER_FLAGS_MATCH_TARGET; // no more target matching. } hud_lock_reset(); if ( (Player_ai->target_objnum >= 0) && (Player_ai->target_objnum < MAX_OBJECTS) ) { if ( Objects[Player_ai->target_objnum].type == OBJ_SHIP ) { hud_restore_subsystem_target(&Ships[Objects[Player_ai->target_objnum].instance]); } } // reset external cam distance if (Viewer_mode & VM_EXTERNAL) { if (Viewer_mode & VM_OTHER_SHIP) Viewer_external_info.preferred_distance = 2 * Objects[Player_ai->target_objnum].radius; } } else { if (Player_ai->current_target_distance < Player_ai->last_dist-0.01){ Player_ai->current_target_dist_trend = DECREASING; } else if (Player_ai->current_target_distance > Player_ai->last_dist+0.01){ Player_ai->current_target_dist_trend = INCREASING; } else { Player_ai->current_target_dist_trend = NO_CHANGE; } if ( (Player_ai->target_objnum >= 0) && (Player_ai->target_objnum < MAX_OBJECTS) ) { current_speed = Objects[Player_ai->target_objnum].phys_info.speed; } if (current_speed < Player_ai->last_speed-0.01){ Player_ai->current_target_speed_trend = DECREASING; } else if (current_speed > Player_ai->last_speed+0.01) { Player_ai->current_target_speed_trend = INCREASING; } else { Player_ai->current_target_speed_trend = NO_CHANGE; } if ( Players[Player_num].flags & PLAYER_FLAGS_AUTO_MATCH_SPEED ) { if ( !(Players[Player_num].flags & PLAYER_FLAGS_MATCH_TARGET) ) { player_match_target_speed(); } } } Player_ai->last_dist = Player_ai->current_target_distance; Player_ai->last_speed = current_speed; Player_ai->last_target = Player_ai->target_objnum; Player_ai->last_subsys_target = Player_ai->targeted_subsys; } HudGaugeTargetTriangle::HudGaugeTargetTriangle(): HudGaugeReticleTriangle(HUD_OBJECT_TARGET_TRI, HUD_TARGET_TRIANGLE) { } void HudGaugeTargetTriangle::render(float /*frametime*/) { if ( Player_ai->target_objnum == -1) return; bool in_frame = g3_in_frame() > 0; if(!in_frame) g3_start_frame(0); object *targetp = &Objects[Player_ai->target_objnum]; // draw the targeting triangle that orbits the outside of the outer circle of the reticle if (!Player->target_is_dying && maybeFlashSexp() != 1) { hud_set_iff_color(targetp, 1); renderTriangle(&targetp->pos, 1, 0, 0); } if(!in_frame) g3_end_frame(); } // start the weapon line (on the HUD) flashing void hud_start_flash_weapon(int index) { if ( index >= MAX_WEAPON_FLASH_LINES ) { Int3(); // Get Alan return; } if ( timestamp_elapsed(Weapon_flash_info.flash_duration[index]) ) { Weapon_flash_info.flash_next[index] = timestamp(TBOX_FLASH_INTERVAL); Weapon_flash_info.is_bright &= ~(1<= MAX_WEAPON_FLASH_LINES ) { Int3(); // Get Alan return; } // hud_set_default_color(); hud_set_gauge_color(HUD_WEAPONS_GAUGE); if ( !timestamp_elapsed(Weapon_flash_info.flash_duration[index]) ) { if ( timestamp_elapsed(Weapon_flash_info.flash_next[index]) ) { Weapon_flash_info.flash_next[index] = timestamp(TBOX_FLASH_INTERVAL); Weapon_flash_info.is_bright ^= (1<flags & NETINFO_FLAG_OBSERVER)) ) { return 1; } // if the ship is currently being affected by EMP if(emp_active_local()){ return 0; } // ensure targeting functions are not disabled through damage sensors_str = ship_get_subsystem_strength( sp, SUBSYSTEM_SENSORS ); if ( (sensors_str < MIN_SENSOR_STR_TO_TARGET) || (ship_subsys_disrupted(sp, SUBSYSTEM_SENSORS)) ) { if ( show_msg ) { HUD_sourced_printf(HUD_SOURCE_HIDDEN, "%s", XSTR( "Targeting is disabled due to sensors damage", 330)); snd_play(gamesnd_get_game_sound(GameSounds::TARGET_FAIL)); } return 0; } else { return 1; } } int hud_communications_state(ship *sp, bool for_death_scream) { float str; // If playing on the lowest skill level, communications always ok // If dead, still allow player to communicate, despite any subsystem damage if ( Game_skill_level == 0 || (Game_mode & GM_DEAD) ) { return COMM_OK; } // Goober5000 - if the ship is the player, and he's dying, return OK (so laments can be played) if ((sp == Player_ship) && (sp->flags[Ship::Ship_Flags::Dying])) return COMM_OK; str = ship_get_subsystem_strength( sp, SUBSYSTEM_COMMUNICATION, for_death_scream ); if ( (str <= 0.01) || ship_subsys_disrupted(sp, SUBSYSTEM_COMMUNICATION) ) { return COMM_DESTROYED; } else if ( str < MIN_COMM_STR_TO_MESSAGE ) { return COMM_DAMAGED; } // Goober5000 - check for scrambled communications if (emp_active_local() || sp->flags[Ship::Ship_Flags::Scramble_messages]) return COMM_SCRAMBLED; return COMM_OK; } // target the next or previous hostile/friendly ship void hud_target_next_list(int hostile, int next_flag, int team_mask, int attacked_objnum, int play_fail_snd, int filter, int get_closest_turret_attacking_player) { int timestamp_val, valid_team_mask; if ( hostile ) { timestamp_val = Tl_hostile_reset_timestamp; Tl_hostile_reset_timestamp = timestamp(TL_RESET); if ( team_mask == -1 ) { valid_team_mask = iff_get_attackee_mask(Player_ship->team); } else { valid_team_mask = team_mask; } } else { // everyone hates a traitor including other traitors so the friendly target option shouldn't work for them if (Player_ship->team == Iff_traitor) { snd_play( gamesnd_get_game_sound(GameSounds::TARGET_FAIL), 0.0f ); return; } timestamp_val = Tl_friendly_reset_timestamp; Tl_friendly_reset_timestamp = timestamp(TL_RESET); valid_team_mask = iff_get_mask(Player_ship->team); } // If no target is selected, then simply target the closest ship if ( Player_ai->target_objnum == -1 || timestamp_elapsed(timestamp_val) ) { hud_target_closest(valid_team_mask, attacked_objnum, play_fail_snd, filter, get_closest_turret_attacking_player); return; } object *nearest_object = select_next_target_by_distance((next_flag != 0), valid_team_mask, attacked_objnum); if (nearest_object != NULL) { // set new target set_target_objnum( Player_ai, OBJ_INDEX(nearest_object) ); hud_shield_hit_reset(nearest_object); // maybe set new turret subsystem hud_maybe_set_sorted_turret_subsys(&Ships[nearest_object->instance]); hud_restore_subsystem_target(&Ships[nearest_object->instance]); } else { snd_play( gamesnd_get_game_sound(GameSounds::TARGET_FAIL), 0.0f ); } } HudGaugeAutoTarget::HudGaugeAutoTarget(): HudGauge(HUD_OBJECT_AUTO_TARGET, HUD_AUTO_TARGET, false, false, (VM_EXTERNAL | VM_DEAD_VIEW | VM_WARP_CHASE | VM_PADLOCK_ANY | VM_OTHER_SHIP), 255, 255, 255) { } void HudGaugeAutoTarget::initAutoTextOffsets(int x, int y) { Auto_text_offsets[0] = x; Auto_text_offsets[1] = y; } void HudGaugeAutoTarget::initTargetTextOffsets(int x, int y) { Target_text_offsets[0] = x; Target_text_offsets[1] = y; } void HudGaugeAutoTarget::initBitmaps(char *fname) { Toggle_frame.first_frame = bm_load_animation(fname, &Toggle_frame.num_frames); if ( Toggle_frame.first_frame < 0 ) { Warning(LOCATION,"Cannot load hud ani: %s\n", fname); } } void HudGaugeAutoTarget::initOnColor(int r, int g, int b, int a) { if ( r == -1 || g == -1 || b == -1 || a == -1 ) { Use_on_color = false; gr_init_alphacolor(&On_color, 0, 0, 0, 0); return; } Use_on_color = true; gr_init_alphacolor(&On_color, r, g, b, a); } void HudGaugeAutoTarget::initOffColor(int r, int g, int b, int a) { if ( r == -1 || g == -1 || b == -1 || a == -1 ) { Use_off_color = false; gr_init_alphacolor(&Off_color, 0, 0, 0, 0); return; } Use_off_color = true; gr_init_alphacolor(&Off_color, r, g, b, a); } void HudGaugeAutoTarget::render(float /*frametime*/) { if (Player_ship->flags[Ship::Ship_Flags::Primitive_sensors]) return; int frame_offset; if ( Players[Player_num].flags & PLAYER_FLAGS_AUTO_TARGETING ) { frame_offset = 1; } else { frame_offset = 0; } // draw the box background setGaugeColor(); renderBitmap(Toggle_frame.first_frame+frame_offset, position[0], position[1]); // draw the text on top if (frame_offset == 1) { //static color text_color; //gr_init_alphacolor(&text_color, 0, 0, 0, Toggle_text_alpha); if ( Use_on_color ) { gr_set_color_fast(&On_color); } } else if ( Use_off_color ) { gr_set_color_fast(&Off_color); } renderString(position[0] + Auto_text_offsets[0], position[1] + Auto_text_offsets[1], XSTR("auto", 1463)); renderString(position[0] + Target_text_offsets[0], position[1] + Target_text_offsets[1], XSTR("target", 1465)); } void HudGaugeAutoTarget::pageIn() { bm_page_in_aabitmap(Toggle_frame.first_frame, Toggle_frame.num_frames); } HudGaugeAutoSpeed::HudGaugeAutoSpeed(): HudGauge(HUD_OBJECT_AUTO_SPEED, HUD_AUTO_SPEED, false, false, (VM_EXTERNAL | VM_DEAD_VIEW | VM_WARP_CHASE | VM_PADLOCK_ANY | VM_OTHER_SHIP), 255, 255, 255) { } void HudGaugeAutoSpeed::initAutoTextOffsets(int x, int y) { Auto_text_offsets[0] = x; Auto_text_offsets[1] = y; } void HudGaugeAutoSpeed::initSpeedTextOffsets(int x, int y) { Speed_text_offsets[0] = x; Speed_text_offsets[1] = y; } void HudGaugeAutoSpeed::initBitmaps(char *fname) { Toggle_frame.first_frame = bm_load_animation(fname, &Toggle_frame.num_frames); if ( Toggle_frame.first_frame < 0 ) { Warning(LOCATION,"Cannot load hud ani: %s\n", fname); } } void HudGaugeAutoSpeed::initOnColor(int r, int g, int b, int a) { if ( r == -1 || g == -1 || b == -1 || a == -1 ) { Use_on_color = false; gr_init_alphacolor(&On_color, 0, 0, 0, 0); return; } Use_on_color = true; gr_init_alphacolor(&On_color, r, g, b, a); } void HudGaugeAutoSpeed::initOffColor(int r, int g, int b, int a) { if ( r == -1 || g == -1 || b == -1 || a == -1 ) { Use_off_color = false; gr_init_alphacolor(&Off_color, 0, 0, 0, 0); return; } Use_off_color = true; gr_init_alphacolor(&Off_color, r, g, b, a); } void HudGaugeAutoSpeed::render(float /*frametime*/) { if (Player_ship->flags[Ship::Ship_Flags::Primitive_sensors]) return; int frame_offset; if ( Players[Player_num].flags & PLAYER_FLAGS_AUTO_MATCH_SPEED ) { frame_offset = 3; } else { frame_offset = 2; } setGaugeColor(); renderBitmap(Toggle_frame.first_frame+frame_offset, position[0], position[1]); // draw the text on top if (frame_offset == 3) { //static color text_color; //gr_init_alphacolor(&text_color, 0, 0, 0, Toggle_text_alpha); if ( Use_on_color ) { gr_set_color_fast(&On_color); } } else if ( Use_off_color ) { gr_set_color_fast(&Off_color); } renderString(position[0] + Auto_text_offsets[0], position[1] + Auto_text_offsets[1], XSTR("auto", 1463)); renderString(position[0] + Speed_text_offsets[0], position[1] + Speed_text_offsets[1], XSTR("speed", 1464)); } void HudGaugeAutoSpeed::pageIn() { bm_page_in_aabitmap(Toggle_frame.first_frame, Toggle_frame.num_frames); } // Set the player target to the closest friendly repair ship // input: goal_objnum => Try to find repair ship where aip->goal_objnum matches this // output: 1 => A repair ship was targeted // 0 => No targeting change int hud_target_closest_repair_ship(int goal_objnum) { object *A; object *nearest_obj=&obj_used_list; ship *shipp; ship_obj *so; float min_distance=1e20f; float new_distance=0.0f; int rval=0; for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) { A = &Objects[so->objnum]; shipp = &Ships[A->instance]; // get a pointer to the ship information // ignore all ships that aren't repair ships if ( !(Ship_info[shipp->ship_info_index].flags[Ship::Info_Flags::Support]) ) { continue; } if ( (A == Player_obj) || (should_be_ignored(shipp)) ) continue; // only consider friendly ships if ( !(Player_ship->team == shipp->team)) { continue; } if(hud_target_invalid_awacs(A)){ continue; } if ( goal_objnum >= 0 ) { if ( Ai_info[shipp->ai_index].goal_objnum != goal_objnum ) { continue; } } new_distance = hud_find_target_distance(A,Player_obj); if (new_distance <= min_distance) { min_distance=new_distance; nearest_obj = A; } } if (nearest_obj != &obj_used_list) { set_target_objnum( Player_ai, OBJ_INDEX(nearest_obj) ); hud_shield_hit_reset(nearest_obj); hud_restore_subsystem_target(&Ships[nearest_obj->instance]); rval=1; } else { // inform player how to get a support ship if ( goal_objnum == -1 ) { HUD_sourced_printf(HUD_SOURCE_HIDDEN, "%s", XSTR( "No support ships in area. Use messaging to call one in.", 332)); } rval=0; } return rval; } void hud_target_toggle_hidden_from_sensors() { if ( Ignore_List[Ship::Ship_Flags::Hidden_from_sensors] ) { Ignore_List.remove(Ship::Ship_Flags::Hidden_from_sensors); HUD_sourced_printf(HUD_SOURCE_HIDDEN, NOX("Target hiding from sensors disabled")); } else { Ignore_List.set(Ship::Ship_Flags::Hidden_from_sensors); HUD_sourced_printf(HUD_SOURCE_HIDDEN, NOX("Target hiding from sensors enabled")); } } // target the closest uninspected object void hud_target_closest_uninspected_object() { object *A, *nearest_obj = NULL; ship *shipp; ship_obj *so; float min_distance = 1e20f; float new_distance = 0.0f; for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) { A = &Objects[so->objnum]; shipp = &Ships[A->instance]; // get a pointer to the ship information if ( (A == Player_obj) || (should_be_ignored(shipp)) ){ continue; } if(hud_target_invalid_awacs(A)){ continue; } // ignore all non-cargo carrying craft if ( !hud_target_ship_can_be_scanned(shipp) ) { continue; } new_distance = hud_find_target_distance(A,Player_obj); if (new_distance <= min_distance) { min_distance=new_distance; nearest_obj = A; } } if (nearest_obj != NULL) { set_target_objnum( Player_ai, OBJ_INDEX(nearest_obj) ); hud_shield_hit_reset(nearest_obj); hud_restore_subsystem_target(&Ships[nearest_obj->instance]); } else { snd_play( gamesnd_get_game_sound(GameSounds::TARGET_FAIL) ); } } // target the next or previous uninspected/unscanned object void hud_target_uninspected_object(int next_flag) { object *A, *min_obj, *max_obj, *nearest_obj; ship *shipp; ship_obj *so; float cur_dist, min_dist, max_dist, new_dist, nearest_dist, diff; // If no target is selected, then simply target the closest uninspected cargo if ( Player_ai->target_objnum == -1 || timestamp_elapsed(Target_next_uninspected_object_timestamp) ) { Target_next_uninspected_object_timestamp = timestamp(TL_RESET); hud_target_closest_uninspected_object(); return; } Target_next_uninspected_object_timestamp = timestamp(TL_RESET); cur_dist = hud_find_target_distance(&Objects[Player_ai->target_objnum], Player_obj); min_obj = max_obj = nearest_obj = NULL; min_dist = 1e20f; max_dist = 0.0f; if ( next_flag ) { nearest_dist = 1e20f; } else { nearest_dist = 0.0f; } for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) { A = &Objects[so->objnum]; shipp = &Ships[A->instance]; // get a pointer to the ship information if ( (A == Player_obj) || (should_be_ignored(shipp)) ) continue; // ignore all non-cargo carrying craft if ( !hud_target_ship_can_be_scanned(shipp) ) { continue; } // don't use object if it is already a target if ( OBJ_INDEX(A) == Player_ai->target_objnum ) { continue; } if(hud_target_invalid_awacs(A)){ continue; } new_dist = hud_find_target_distance(A, Player_obj); if (new_dist <= min_dist) { min_dist = new_dist; min_obj = A; } if (new_dist >= max_dist) { max_dist = new_dist; max_obj = A; } if ( next_flag ) { diff = new_dist - cur_dist; if ( diff > 0 ) { if ( diff < ( nearest_dist - cur_dist ) ) { nearest_dist = new_dist; nearest_obj = A; } } } else { diff = cur_dist - new_dist; if ( diff > 0 ) { if ( diff < ( cur_dist - nearest_dist ) ) { nearest_dist = new_dist; nearest_obj = A; } } } } if ( nearest_obj == NULL ) { if ( next_flag ) { if ( min_obj != NULL ) { nearest_obj = min_obj; } } else { if ( max_obj != NULL ) { nearest_obj = max_obj; } } } if (nearest_obj != NULL) { set_target_objnum( Player_ai, OBJ_INDEX(nearest_obj) ); hud_shield_hit_reset(nearest_obj); hud_restore_subsystem_target(&Ships[nearest_obj->instance]); } else { snd_play( gamesnd_get_game_sound(GameSounds::TARGET_FAIL) ); } } // ---------------------------------------------------------------- // // Target Last Transmission Sender code START // // ---------------------------------------------------------------- typedef struct transmit_target { int objnum; int objsig; } transmit_target; static int Transmit_target_next_slot = 0; static int Transmit_target_current_slot = -1; static int Transmit_target_reset_timer = timestamp(0); #define MAX_TRANSMIT_TARGETS 10 static transmit_target Transmit_target_list[MAX_TRANSMIT_TARGETS]; // called once per level to initialize the target last transmission sender list void hud_target_last_transmit_level_init() { int i; for ( i = 0; i < MAX_TRANSMIT_TARGETS; i++ ) { Transmit_target_list[i].objnum = -1; Transmit_target_list[i].objsig = -1; } Transmit_target_next_slot = 0; Transmit_target_current_slot = 0; Transmit_target_reset_timer = timestamp(0); } // internal function only.. used to find index for last recorded ship transmission int hud_target_last_transmit_newest() { int latest_slot; latest_slot = Transmit_target_next_slot - 1; if ( latest_slot < 0 ) { latest_slot = MAX_TRANSMIT_TARGETS - 1; } return latest_slot; } // called externally to set the player target to the last ship which sent a transmission to the player void hud_target_last_transmit() { int i; if ( Transmit_target_current_slot < 0 ) { Transmit_target_current_slot = hud_target_last_transmit_newest(); } // If timed out, then simply target the last ship to transmit if ( timestamp_elapsed(Transmit_target_reset_timer) ) { Transmit_target_current_slot = hud_target_last_transmit_newest(); } Transmit_target_reset_timer = timestamp(TL_RESET); int play_fail_sound = 1; int transmit_index = Transmit_target_current_slot; Assert(transmit_index >= 0); for ( i = 0; i < MAX_TRANSMIT_TARGETS; i++ ) { if ( Transmit_target_list[transmit_index].objnum >= 0 ) { int transmit_objnum = Transmit_target_list[transmit_index].objnum; if ( Player_ai->target_objnum == transmit_objnum ) { play_fail_sound = 0; } else { if ( Transmit_target_list[transmit_index].objsig == Objects[Transmit_target_list[transmit_index].objnum].signature ) { if ( !(should_be_ignored(&Ships[Objects[transmit_objnum].instance])) ) { Transmit_target_current_slot = transmit_index-1; if ( Transmit_target_current_slot < 0 ) { Transmit_target_current_slot = MAX_TRANSMIT_TARGETS - 1; } break; } } } } transmit_index--; if ( transmit_index < 0 ) { transmit_index = MAX_TRANSMIT_TARGETS - 1; } } if ( i == MAX_TRANSMIT_TARGETS ) { if ( play_fail_sound ) { snd_play( gamesnd_get_game_sound(GameSounds::TARGET_FAIL) ); } Transmit_target_current_slot = -1; return; } if(hud_target_invalid_awacs(&Objects[Transmit_target_list[transmit_index].objnum])){ return; } // target new ship! // Fix bug in targeting due to Alt-Y (target last ship sending transmission). // Was just bogus code in the call to hud_restore_subsystem_target(). -- MK, 9/15/99, 1:59 pm. int targeted_objnum; targeted_objnum = Transmit_target_list[transmit_index].objnum; Assert((targeted_objnum >= 0) && (targeted_objnum < MAX_OBJECTS)); if ((targeted_objnum >= 0) && (targeted_objnum < MAX_OBJECTS)) { set_target_objnum( Player_ai, Transmit_target_list[transmit_index].objnum ); hud_shield_hit_reset(&Objects[Transmit_target_list[transmit_index].objnum]); hud_restore_subsystem_target(&Ships[Objects[Transmit_target_list[transmit_index].objnum].instance]); } } // called externally to add a message sender to the list void hud_target_last_transmit_add(int ship_num) { object *ship_objp; int ship_objnum; ship_objnum = Ships[ship_num].objnum; Assert(ship_objnum >= 0 && ship_objnum < MAX_OBJECTS); ship_objp = &Objects[ship_objnum]; Assert(ship_objp->type == OBJ_SHIP); // don't add ourselves to the list if (Player_obj == ship_objp) { return; } Transmit_target_list[Transmit_target_next_slot].objnum = ship_objnum; Transmit_target_list[Transmit_target_next_slot].objsig = ship_objp->signature; Transmit_target_next_slot++; if ( Transmit_target_next_slot >= MAX_TRANSMIT_TARGETS ) { Transmit_target_next_slot = 0; } } // target a random ship (useful for EMP stuff) void hud_target_random_ship() { int shipnum; int objnum; shipnum = ship_get_random_targetable_ship(); if((shipnum < 0) || (Ships[shipnum].objnum < 0)){ return; } objnum = Ships[shipnum].objnum; if((objnum >= 0) && (Player_ai != NULL) && !hud_target_invalid_awacs(&Objects[objnum])){ // never target yourself if(objnum == OBJ_INDEX(Player_obj)){ set_target_objnum(Player_ai, -1); } else { set_target_objnum(Player_ai, objnum); hud_shield_hit_reset(&Objects[objnum]); } } } // ---------------------------------------------------------------- // // Target Last Transmission Sender code END // // ---------------------------------------------------------------- void hudtarget_page_in() { int i; for ( i = 0; i < NUM_WEAPON_GAUGES; i++ ) { bm_page_in_aabitmap( Weapon_gauges[ballistic_hud_index][i].first_frame, Weapon_gauges[ballistic_hud_index][i].num_frames); } bm_page_in_aabitmap( New_weapon.first_frame, New_weapon.num_frames ); for (auto &wi : Weapon_info) { if (strlen(wi.hud_filename)) { wi.hud_image_index = bm_load(wi.hud_filename); } } } void hud_stuff_ship_name(char *ship_name_text, const ship *shipp) { strcpy(ship_name_text, hud_get_ship_name(shipp).c_str()); } SCP_string hud_get_ship_name(const ship *shipp) { // print ship name if ( ((Iff_info[shipp->team].flags & IFFF_WING_NAME_HIDDEN) && (shipp->wingnum != -1)) || (shipp->flags[Ship::Ship_Flags::Hide_ship_name]) ) { return ""; } else if (!Disable_built_in_translations) { // handle translation if (Lcl_gr) { char buf[128]; strcpy(buf, shipp->get_display_name()); lcl_translate_targetbox_name_gr(buf); return buf; } else if (Lcl_pl) { char buf[128]; strcpy(buf, shipp->get_display_name()); lcl_translate_targetbox_name_pl(buf); return buf; } } return shipp->get_display_name(); } void hud_stuff_ship_callsign(char *ship_callsign_text, const ship *shipp) { strcpy(ship_callsign_text, hud_get_ship_callsign(shipp).c_str()); } extern char Fred_callsigns[MAX_SHIPS][NAME_LENGTH+1]; SCP_string hud_get_ship_callsign(const ship *shipp) { // handle multiplayer callsign if (Game_mode & GM_MULTIPLAYER) { // get a player num from the object, then get a callsign from the player structure. int pn = multi_find_player_by_object( &Objects[shipp->objnum] ); if (pn >= 0) { return Net_players[pn].m_player->short_callsign; } } SCP_string ship_callsign_text; // try to get callsign if (Fred_running) { ship_callsign_text = Fred_callsigns[shipp-Ships]; } else { if (shipp->callsign_index >= 0) { ship_callsign_text = mission_parse_lookup_callsign_index(shipp->callsign_index); } } if (!Disable_built_in_translations) { // handle translation if (Lcl_gr) { char buf[128]; strcpy(buf, ship_callsign_text.c_str()); lcl_translate_targetbox_name_gr(buf); return buf; } else if (Lcl_pl) { char buf[128]; strcpy(buf, ship_callsign_text.c_str()); lcl_translate_targetbox_name_pl(buf); return buf; } } return ship_callsign_text; } void hud_stuff_ship_class(char *ship_class_text, const ship *shipp) { strcpy(ship_class_text, hud_get_ship_class(shipp).c_str()); } extern char Fred_alt_names[MAX_SHIPS][NAME_LENGTH + 1]; SCP_string hud_get_ship_class(const ship *shipp) { SCP_string ship_class_text; // try to get alt name if (Fred_running) { ship_class_text = Fred_alt_names[shipp-Ships]; if (ship_class_text.empty()) { ship_class_text = Ship_info[shipp->ship_info_index].get_display_name(); } } else { if (shipp->alt_type_index >= 0) { ship_class_text = mission_parse_lookup_alt_index(shipp->alt_type_index); } else { ship_class_text = Ship_info[shipp->ship_info_index].get_display_name(); } } if (!Disable_built_in_translations) { // handle translation if (Lcl_gr) { char buf[128]; strcpy(buf, ship_class_text.c_str()); lcl_translate_targetbox_name_gr(buf); return buf; } else if (Lcl_pl) { char buf[128]; strcpy(buf, ship_class_text.c_str()); lcl_translate_targetbox_name_pl(buf); return buf; } } return ship_class_text; } HudGaugeCmeasures::HudGaugeCmeasures(): HudGauge(HUD_OBJECT_CMEASURES, HUD_CMEASURE_GAUGE, false, false, VM_EXTERNAL | VM_DEAD_VIEW | VM_WARP_CHASE | VM_PADLOCK_ANY | VM_OTHER_SHIP, 255, 255, 255) { } void HudGaugeCmeasures::initCountTextOffsets(int x, int y) { Cm_text_offsets[0] = x; Cm_text_offsets[1] = y; } void HudGaugeCmeasures::initCountValueOffsets(int x, int y) { Cm_text_val_offsets[0] = x; Cm_text_val_offsets[1] = y; } void HudGaugeCmeasures::initBitmaps(char *fname) { Cmeasure_gauge.first_frame = bm_load_animation(fname, &Cmeasure_gauge.num_frames); if ( Cmeasure_gauge.first_frame < 0 ) { Warning(LOCATION,"Cannot load hud ani: %s\n", fname); } } void HudGaugeCmeasures::pageIn() { bm_page_in_aabitmap(Cmeasure_gauge.first_frame, Cmeasure_gauge.num_frames); } void HudGaugeCmeasures::render(float /*frametime*/) { if ( Cmeasure_gauge.first_frame == -1) { Int3(); // failed to load coutermeasure gauge background return; } ship_info *sip = &Ship_info[Player_ship->ship_info_index]; if(sip->cmeasure_max < 0 || sip->cmeasure_type < 0){ return; } // hud_set_default_color(); setGaugeColor(); // blit the background renderBitmap(Cmeasure_gauge.first_frame, position[0], position[1]); // blit text renderString(position[0] + Cm_text_offsets[0], position[1] + Cm_text_offsets[1], XSTR( "cm.", 327)); if ( !Player_ship ) { Int3(); // player ship doesn't exist? return; } renderPrintf(position[0] + Cm_text_val_offsets[0], position[1] + Cm_text_val_offsets[1], NOX("%02d"), Player_ship->cmeasure_count); } HudGaugeAfterburner::HudGaugeAfterburner(): HudGauge(HUD_OBJECT_AFTERBURNER, HUD_AFTERBURNER_ENERGY, true, false, (VM_EXTERNAL | VM_DEAD_VIEW | VM_WARP_CHASE | VM_PADLOCK_ANY | VM_OTHER_SHIP), 255, 255, 255) { } void HudGaugeAfterburner::initEnergyHeight(int h) { Energy_h = h; } void HudGaugeAfterburner::initBitmaps(char *fname) { Energy_bar.first_frame = bm_load_animation(fname, &Energy_bar.num_frames); if ( Energy_bar.first_frame < 0 ) { Warning(LOCATION,"Cannot load hud ani: %s\n", fname); } } // Render the HUD afterburner energy gauge void HudGaugeAfterburner::render(float /*frametime*/) { float percent_left; int clip_h,w,h; if ( Energy_bar.first_frame == -1 ){ return; } Assert(Player_ship); if ( !(Ship_info[Player_ship->ship_info_index].flags[Ship::Info_Flags::Afterburner]) ) { // Goober5000 - instead of drawing an empty burner gauge, don't draw the gauge at all return; } else { percent_left = Player_ship->afterburner_fuel/Ship_info[Player_ship->ship_info_index].afterburner_fuel_capacity; } if ( percent_left > 1 ) { percent_left = 1.0f; } clip_h = (int)std::lround((1.0f - percent_left) * Energy_h); bm_get_info(Energy_bar.first_frame,&w,&h); setGaugeColor(); if ( clip_h > 0) { renderBitmapEx(Energy_bar.first_frame, position[0], position[1],w,clip_h,0,0); } if ( clip_h <= Energy_h ) { renderBitmapEx(Energy_bar.first_frame+1, position[0], position[1] + clip_h,w,h-clip_h,0,clip_h); } } void HudGaugeAfterburner::pageIn() { bm_page_in_aabitmap( Energy_bar.first_frame, Energy_bar.num_frames); } HudGaugeWeaponEnergy::HudGaugeWeaponEnergy(): HudGauge(HUD_OBJECT_WEAPON_ENERGY, HUD_WEAPONS_ENERGY, true, false, (VM_EXTERNAL | VM_DEAD_VIEW | VM_WARP_CHASE | VM_PADLOCK_ANY | VM_OTHER_SHIP), 255, 255, 255) { } void HudGaugeWeaponEnergy::initTextOffsets(int x, int y) { Wenergy_text_offsets[0] = x; Wenergy_text_offsets[1] = y; } void HudGaugeWeaponEnergy::initEnergyHeight(int h) { Wenergy_h = h; } void HudGaugeWeaponEnergy::initAlignments(HudAlignment text_align, HudAlignment armed_align) { Text_alignment = text_align; Armed_alignment = armed_align; } void HudGaugeWeaponEnergy::initArmedOffsets(int x, int y, int h, bool show) { Armed_name_offsets[0] = x; Armed_name_offsets[1] = y; Show_armed = show; Armed_name_h = h; } void HudGaugeWeaponEnergy::initAlwaysShowText(bool show_text) { Always_show_text = show_text; } void HudGaugeWeaponEnergy::initMoveText(bool move_text) { Moving_text = move_text; } void HudGaugeWeaponEnergy::initShowBallistics(bool show_ballistics) { Show_ballistic = show_ballistics; } void HudGaugeWeaponEnergy::initBitmaps(char *fname) { Energy_bar.first_frame = bm_load_animation(fname, &Energy_bar.num_frames); if ( Energy_bar.first_frame < 0 ) { Warning(LOCATION,"Cannot load hud ani: %s\n", fname); } else { if (Energy_bar.num_frames != 4) { error_display(0, "The animation '%s' has %d frames but the energy bar requires 4 frames in the animation! Ignoring animation...", fname, Energy_bar.num_frames); // Free the bitmap slot and set the frame handle to -1 so that it's ignored when rendering this gauge bm_release(Energy_bar.first_frame); Energy_bar.first_frame = -1; Energy_bar.num_frames = 0; } } } void HudGaugeWeaponEnergy::pageIn() { bm_page_in_aabitmap( Energy_bar.first_frame, Energy_bar.num_frames); } void HudGaugeWeaponEnergy::render(float /*frametime*/) { int x; bool use_new_gauge = false; // Goober5000 - only check for the new gauge in case of command line + a ballistic-capable ship if (Cmdline_ballistic_gauge) { for (x = 0; x < Player_ship->weapons.num_primary_banks; ++x) { if (Weapon_info[Player_ship->weapons.primary_bank_weapons[x]].wi_flags[Weapon::Info_Flags::Ballistic]) { use_new_gauge = true; break; } } } if(use_new_gauge) { int currentx, currenty, line_height; int y; int max_w = 100; float remaining; currentx = position[0] + 10; currenty = position[1]; line_height = gr_get_font_height() + 1; if(gr_screen.max_w_unscaled == 640) { max_w = 60; } //*****ENERGY GAUGE if(Weapon_info[Player_ship->weapons.current_primary_bank].energy_consumed > 0.0f) setGaugeColor(HUD_C_BRIGHT); //Draw name renderString(currentx, currenty, XSTR("Energy", 1616)); currenty += line_height; //Draw background setGaugeColor(HUD_C_DIM); renderRect(currentx, currenty, max_w, 10); //Draw gauge bar setGaugeColor(HUD_C_NORMAL); remaining = max_w * ((float)Player_ship->weapon_energy/(float)Ship_info[Player_ship->ship_info_index].max_weapon_reserve); if(remaining > 0) { setGaugeColor(HUD_C_BRIGHT); for(y = 0; y < 10; y++) { renderGradientLine(currentx, currenty + y, currentx + fl2i(remaining), currenty + y); } } currenty += 12; char shortened_name[NAME_LENGTH]; //*****BALLISTIC GAUGES for(x = 0; x < Player_ship->weapons.num_primary_banks; x++) { //Skip all pure-energy weapons if(!(Weapon_info[Player_ship->weapons.primary_bank_weapons[x]].wi_flags[Weapon::Info_Flags::Ballistic])) continue; //Draw the weapon bright or normal depending if it's active or not. if(x == Player_ship->weapons.current_primary_bank || (Player_ship->flags[Ship::Ship_Flags::Primary_linked])) { setGaugeColor(HUD_C_BRIGHT); } else { setGaugeColor(HUD_C_NORMAL); } if(gr_screen.max_w_unscaled == 640) { strcpy_s(shortened_name, Weapon_info[Player_ship->weapons.primary_bank_weapons[x]].get_display_name()); font::force_fit_string(shortened_name, NAME_LENGTH, 55); renderString(currentx, currenty, shortened_name); } else { renderString(currentx, currenty, Weapon_info[Player_ship->weapons.primary_bank_weapons[x]].get_display_name()); } //Next 'line' currenty += line_height; //Draw the background for the gauge setGaugeColor(HUD_C_DIM); renderRect(currentx, currenty, max_w, 10); //Reset to normal brightness setGaugeColor(HUD_C_NORMAL); //Draw the bar graph remaining = (max_w - 4) * ((float) Player_ship->weapons.primary_bank_ammo[x] / (float) Player_ship->weapons.primary_bank_start_ammo[x]); if(remaining > 0) { renderRect(currentx + 2, currenty + 2, fl2i(remaining), 6); } //Increment for next 'line' currenty += 12; } } else { float percent_left; int ballistic_ammo = 0; int max_ballistic_ammo = 0; int clip_h, w, h, i; weapon_info *wip; ship_weapon *sw; if ( Energy_bar.first_frame == -1 ) { return; } if ( Player_ship->weapons.num_primary_banks <= 0 ) { return; } sw = &Player_ship->weapons; // show ballistic ammunition in energy gauge if need be if ( Show_ballistic ) { if ( Player_ship->flags[Ship::Ship_Flags::Primary_linked] ) { // go through all ballistic primaries and add up their ammunition totals and max capacities for ( i = 0; i < sw->num_primary_banks; i++ ) { // skip all pure-energy weapons if( ! ( Weapon_info[sw->primary_bank_weapons[i]].wi_flags[Weapon::Info_Flags::Ballistic] ) ) { continue; } ballistic_ammo += sw->primary_bank_ammo[i]; max_ballistic_ammo += sw->primary_bank_start_ammo[i]; } } else { ballistic_ammo = sw->primary_bank_ammo[sw->current_primary_bank]; max_ballistic_ammo = sw->primary_bank_start_ammo[sw->current_primary_bank]; } if (max_ballistic_ammo > 0) { percent_left = i2fl(ballistic_ammo) / i2fl(max_ballistic_ammo); } else { percent_left = 1.0f; } } else { // also leave if no energy can be stored for weapons - Goober5000 if (!ship_has_energy_weapons(Player_ship)) return; percent_left = Player_ship->weapon_energy/Ship_info[Player_ship->ship_info_index].max_weapon_reserve; if ( percent_left > 1 ) { percent_left = 1.0f; } } clip_h = (int)std::lround((1.0f - percent_left) * Wenergy_h); if ( percent_left <= 0.3 || Show_ballistic || Always_show_text ) { char buf[10]; int delta_y = 0, delta_x = 0; if ( percent_left < 0.1 ) { gr_set_color_fast(&Color_bright_red); } else { setGaugeColor(); } if ( Show_ballistic ) { sprintf(buf, "%d", ballistic_ammo); } else { sprintf(buf, XSTR( "%d%%", 326), (int)std::lround(percent_left*100)); } if ( Moving_text ) { delta_y = clip_h; } hud_num_make_mono(buf, font_num); if ( Text_alignment == HudAlignment::RIGHT ) { gr_get_string_size(&w, &h, buf); delta_x = -w; } renderString(position[0] + Wenergy_text_offsets[0] + delta_x, position[1] + Wenergy_text_offsets[1] + delta_y, buf); } setGaugeColor(); // list currently armed primary banks if we have to if ( Show_armed ) { if ( Player_ship->flags[Ship::Ship_Flags::Primary_linked] ) { // show all primary banks for ( i = 0; i < Player_ship->weapons.num_primary_banks; i++ ) { wip = &Weapon_info[sw->primary_bank_weapons[i]]; auto weapon_name = wip->get_display_name(); if ( Armed_alignment == HudAlignment::RIGHT ) { gr_get_string_size(&w, &h, weapon_name); } else { w = 0; } renderString(position[0] + Armed_name_offsets[0] - w, position[1] + Armed_name_offsets[1] + Armed_name_h * i, weapon_name); } } else { // just show the current armed bank i = Player_ship->weapons.current_primary_bank; wip = &Weapon_info[sw->primary_bank_weapons[i]]; auto weapon_name = wip->get_display_name(); if ( Armed_alignment == HudAlignment::RIGHT ) { gr_get_string_size(&w, &h, weapon_name); } else { w = 0; } renderString(position[0] + Armed_name_offsets[0] - w, position[1] + Armed_name_offsets[1], weapon_name); } } for ( i = 0; i < sw->num_primary_banks; i++ ) { if ( !timestamp_elapsed(Weapon_flash_info.flash_duration[i]) ) { if ( Weapon_flash_info.is_bright & (1< 0 ) { renderBitmapEx(Energy_bar.first_frame+2, position[0], position[1], w,clip_h,0,0); } if ( clip_h <= Wenergy_h ) { renderBitmapEx(Energy_bar.first_frame+3, position[0], position[1] + clip_h, w,h-clip_h,0,clip_h); } // hud_set_default_color(); } } HudGaugeWeapons::HudGaugeWeapons(): HudGauge(HUD_OBJECT_WEAPONS, HUD_WEAPONS_GAUGE, false, false, VM_EXTERNAL | VM_DEAD_VIEW | VM_WARP_CHASE | VM_PADLOCK_ANY | VM_OTHER_SHIP, 255, 255, 255) { } void HudGaugeWeapons::initLinkIcon() { ubyte sc = lcl_get_font_index(font_num); // default to a '>' if the font has no special chars // seems about the closest normal char to the triangle if (sc == 0) { Weapon_link_icon = ubyte ('>'); } else { Weapon_link_icon = sc + 2; } } void HudGaugeWeapons::initTopOffsetX(int x, int x_b) { top_offset_x[0] = x; top_offset_x[1] = x_b; } void HudGaugeWeapons::initHeaderOffsets(int x, int y, int x_b, int y_b) { Weapon_header_offsets[0][0] = x; Weapon_header_offsets[0][1] = y; Weapon_header_offsets[1][0] = x_b; Weapon_header_offsets[1][1] = y_b; } void HudGaugeWeapons::initFrameOffsetX(int x, int x_b) { frame_offset_x[0] = x; frame_offset_x[1] = x_b; } void HudGaugeWeapons::initPrimaryWeaponOffsets(int link_x, int name_x, int ammo_x) { Weapon_plink_offset_x = link_x; Weapon_pname_offset_x = name_x; Weapon_pammo_offset_x = ammo_x; } void HudGaugeWeapons::initSecondaryWeaponOffsets(int ammo_x, int name_x, int reload_x, int linked_x, int unlinked_x) { Weapon_sammo_offset_x = ammo_x; Weapon_sname_offset_x = name_x; Weapon_sreload_offset_x = reload_x; Weapon_slinked_offset_x = linked_x; Weapon_sunlinked_offset_x = unlinked_x; } void HudGaugeWeapons::initStartNameOffsetsY(int p_y, int s_y) { pname_start_offset_y = p_y; sname_start_offset_y = s_y; } void HudGaugeWeapons::initPrimaryHeights(int top_h, int text_h) { top_primary_h = top_h; primary_text_h = text_h; } void HudGaugeWeapons::initSecondaryHeights(int top_h, int text_h) { top_secondary_h = top_h; secondary_text_h = text_h; } void HudGaugeWeapons::initBitmapsPrimaryTop(char *fname, char *fname_ballistic) { // load the graphics for the top portion of the weapons gauge primary_top[0].first_frame = bm_load_animation(fname, &primary_top[0].num_frames); if(primary_top[0].first_frame < 0) { Warning(LOCATION,"Cannot load hud ani: %s\n", fname); } primary_top[1].first_frame = bm_load_animation(fname_ballistic, &primary_top[1].num_frames); if(primary_top[1].first_frame < 0) { primary_top[1].first_frame = primary_top[0].first_frame; primary_top[1].num_frames = primary_top[0].num_frames; } } void HudGaugeWeapons::initBitmapsPrimaryMiddle(char *fname, char *fname_ballistic) { // load the graphics for the middle portion of the primary weapons listing primary_middle[0].first_frame = bm_load_animation(fname, &primary_middle[0].num_frames); if(primary_middle[0].first_frame < 0) { Warning(LOCATION,"Cannot load hud ani: %s\n", fname); } primary_middle[1].first_frame = bm_load_animation(fname_ballistic, &primary_middle[1].num_frames); if(primary_middle[1].first_frame < 0) { primary_middle[1].first_frame = primary_middle[0].first_frame; primary_middle[1].num_frames = primary_middle[0].num_frames; } } void HudGaugeWeapons::initBitmapsPrimaryLast(char *fname, char *fname_ballistic) { // load the graphics for the bottom portion of the primary weapons listing if there is one. // Don't bother the user if there isn't one since retail doesn't use this. primary_last[0].first_frame = bm_load_animation(fname, &primary_last[0].num_frames); primary_last[1].first_frame = bm_load_animation(fname_ballistic, &primary_last[1].num_frames); if(primary_last[1].first_frame < 0) { primary_last[1].first_frame = primary_last[0].first_frame; primary_last[1].num_frames = primary_last[0].num_frames; } } void HudGaugeWeapons::initBitmapsSecondaryTop(char *fname, char *fname_ballistic) { // top portion of the secondary weapons gauge secondary_top[0].first_frame = bm_load_animation(fname, &secondary_top[0].num_frames); if(secondary_top[0].first_frame < 0) { Warning(LOCATION,"Cannot load hud ani: %s\n", fname); } secondary_top[1].first_frame = bm_load_animation(fname_ballistic, &secondary_top[1].num_frames); if(secondary_top[1].first_frame < 0) { secondary_top[1].first_frame = secondary_top[0].first_frame; secondary_top[1].num_frames = secondary_top[0].num_frames; } } void HudGaugeWeapons::initBitmapsSecondaryMiddle(char *fname, char *fname_ballistic) { // middle portion of the secondary weapons gauge secondary_middle[0].first_frame = bm_load_animation(fname, &secondary_middle[0].num_frames); if(secondary_middle[0].first_frame < 0) { Warning(LOCATION,"Cannot load hud ani: %s\n", fname); } secondary_middle[1].first_frame = bm_load_animation(fname_ballistic, &secondary_middle[1].num_frames); if(secondary_middle[1].first_frame < 0) { secondary_middle[1].first_frame = secondary_middle[0].first_frame; secondary_middle[1].num_frames = secondary_middle[0].num_frames; } } void HudGaugeWeapons::initBitmapsSecondaryBottom(char *fname, char *fname_ballistic) { // bottom portion of the entire weapons gauge secondary_bottom[0].first_frame = bm_load_animation(fname, &secondary_bottom[0].num_frames); if(secondary_bottom[0].first_frame < 0) { Warning(LOCATION,"Cannot load hud ani: %s\n", fname); } secondary_bottom[1].first_frame = bm_load_animation(fname_ballistic, &secondary_bottom[1].num_frames); if(secondary_bottom[1].first_frame < 0) { secondary_bottom[1].first_frame = secondary_bottom[0].first_frame; secondary_bottom[1].num_frames = secondary_bottom[0].num_frames; } } void HudGaugeWeapons::pageIn() { if(primary_top[0].first_frame > -1) { bm_page_in_aabitmap(primary_top[0].first_frame,primary_top[0].num_frames); } if(primary_top[1].first_frame > -1) { bm_page_in_aabitmap(primary_top[1].first_frame,primary_top[1].num_frames); } if(primary_middle[0].first_frame > -1) { bm_page_in_aabitmap(primary_middle[0].first_frame,primary_middle[0].num_frames); } if(primary_middle[1].first_frame > -1) { bm_page_in_aabitmap(primary_middle[1].first_frame, primary_middle[1].num_frames); } if(primary_last[1].first_frame > -1) { bm_page_in_aabitmap(primary_last[1].first_frame, primary_last[1].num_frames); } if(secondary_top[0].first_frame > -1) { bm_page_in_aabitmap(secondary_top[0].first_frame, secondary_top[0].num_frames); } if(secondary_top[1].first_frame > -1) { bm_page_in_aabitmap(secondary_top[1].first_frame, secondary_top[1].num_frames); } if(secondary_middle[0].first_frame > -1) { bm_page_in_aabitmap(secondary_middle[0].first_frame, secondary_middle[0].num_frames); } if(secondary_middle[1].first_frame >-1) { bm_page_in_aabitmap(secondary_middle[0].first_frame, secondary_middle[0].num_frames); } if(secondary_bottom[0].first_frame > -1) { bm_page_in_aabitmap(secondary_bottom[0].first_frame, secondary_bottom[0].num_frames); } if(secondary_bottom[1].first_frame > -1) { bm_page_in_aabitmap(secondary_bottom[0].first_frame, secondary_bottom[0].num_frames); } } void HudGaugeWeapons::render(float /*frametime*/) { ship_weapon *sw; int np, ns; // np == num primary, ns == num secondary if(Player_obj->type == OBJ_OBSERVER) return; Assert(Player_obj->type == OBJ_SHIP); Assert(Player_obj->instance >= 0 && Player_obj->instance < MAX_SHIPS); sw = &Ships[Player_obj->instance].weapons; np = sw->num_primary_banks; ns = sw->num_secondary_banks; setGaugeColor(); // draw top of primary display renderBitmap(primary_top[ballistic_hud_index].first_frame, position[0] + top_offset_x[ballistic_hud_index], position[1]); // render the header of this gauge renderString(position[0] + Weapon_header_offsets[ballistic_hud_index][0], position[1] + Weapon_header_offsets[ballistic_hud_index][1], EG_WEAPON_TITLE, XSTR( "weapons", 328)); const char *weapon_name; char ammo_str[32]; int i, w, h; int y = position[1] + top_primary_h; int name_y = position[1] + pname_start_offset_y; // render primaries for(i = 0; i < np; i++) { setGaugeColor(); // choose which background to draw for additional primaries. // Note, we don't draw a background for the first primary. // It is assumed that the top primary wep frame already has this rendered. if(i == 1) { // used to draw the second primary weapon background renderBitmap(primary_middle[ballistic_hud_index].first_frame, position[0] + frame_offset_x[ballistic_hud_index], y); } else if(i != 0) { // used to draw the the third, fourth, fifth, etc... if(primary_last[ballistic_hud_index].first_frame != -1) renderBitmap(primary_last[ballistic_hud_index].first_frame, position[0] + frame_offset_x[ballistic_hud_index], y); } weapon_name = Weapon_info[sw->primary_bank_weapons[i]].get_display_name(); // maybe modify name here to fit // do we need to flash the text? if (HudGauge::maybeFlashSexp() == i ) { setGaugeColor(HUD_C_BRIGHT); } else { maybeFlashWeapon(i); } // indicate if this is linked or currently armed if ( ((sw->current_primary_bank == i) && !(Player_ship->flags[Ship::Ship_Flags::Primary_linked])) || ((Player_ship->flags[Ship::Ship_Flags::Primary_linked]) && !(Weapon_info[sw->primary_bank_weapons[i]].wi_flags[Weapon::Info_Flags::Nolink]))) { renderPrintf(position[0] + Weapon_plink_offset_x, name_y, EG_NULL, "%c", Weapon_link_icon); } // either render this primary's image or its name if(Weapon_info[sw->primary_bank_weapons[0]].hud_image_index != -1) { renderBitmap(Weapon_info[sw->primary_bank_weapons[i]].hud_image_index, position[0] + Weapon_pname_offset_x, name_y); } else { renderPrintf(position[0] + Weapon_pname_offset_x, name_y, EG_WEAPON_P2, "%s", weapon_name); } // if this is a ballistic primary with ammo, render the ammo count if (Weapon_info[sw->primary_bank_weapons[i]].wi_flags[Weapon::Info_Flags::Ballistic]) { // print out the ammo right justified sprintf(ammo_str, "%d", sw->primary_bank_ammo[i]); hud_num_make_mono(ammo_str, font_num); gr_get_string_size(&w, &h, ammo_str); renderString(position[0] + Weapon_pammo_offset_x - w, name_y, EG_NULL, ammo_str); } if(i != 0) { y += primary_text_h; } name_y += primary_text_h; } weapon_info *wip; if ( HudGauge::maybeFlashSexp() == i ) { setGaugeColor(HUD_C_BRIGHT); } renderBitmap(secondary_top[ballistic_hud_index].first_frame, position[0] + frame_offset_x[ballistic_hud_index], y); name_y = y + sname_start_offset_y; y += top_secondary_h; for(i = 0; i < ns; i++) { setGaugeColor(); wip = &Weapon_info[sw->secondary_bank_weapons[i]]; if(i!=0) { renderBitmap(secondary_middle[ballistic_hud_index].first_frame, position[0] + frame_offset_x[ballistic_hud_index], y); } maybeFlashWeapon(np+i); if (wip->has_display_name()) { // Do not apply the cluster bomb hack if we have an alternate name to make translating that name possible weapon_name = wip->get_display_name(); } else { // HACK - make Cluster Bomb fit on the HUD. if(!stricmp(wip->name,"cluster bomb")){ weapon_name = NOX("Cluster"); } else { weapon_name = wip->get_display_name(); } } if ( sw->current_secondary_bank == i ) { // show that this is the current secondary armed renderPrintf(position[0] + Weapon_sunlinked_offset_x, name_y, EG_NULL, "%c", Weapon_link_icon); // indicate if this is linked // don't draw the link indicator if the fire can't be fired link. // the link flag is ignored rather than cleared so the player can cycle past a no-doublefire weapon without the setting being cleared if ( Player_ship->flags[Ship::Ship_Flags::Secondary_dual_fire] && !wip->wi_flags[Weapon::Info_Flags::No_doublefire] && !The_mission.ai_profile->flags[AI::Profile_Flags::Disable_player_secondary_doublefire] ) { renderPrintf(position[0] + Weapon_slinked_offset_x, name_y, EG_NULL, "%c", Weapon_link_icon); } // show secondary weapon's image or print its name if(wip->hud_image_index != -1) { renderBitmap(wip->hud_image_index, position[0] + Weapon_sname_offset_x, name_y); } else { renderString(position[0] + Weapon_sname_offset_x, name_y, i ? EG_WEAPON_S1 : EG_WEAPON_S2, weapon_name); } // show the cooldown time if ( (sw->secondary_bank_ammo[i] > 0) && (sw->current_secondary_bank >= 0) ) { int ms_till_fire = timestamp_until(sw->next_secondary_fire_stamp[sw->current_secondary_bank]); if ( (ms_till_fire >= 500) && ((wip->fire_wait >= 1 ) || (ms_till_fire > wip->fire_wait*1000)) ) { renderPrintf(position[0] + Weapon_sreload_offset_x, name_y, EG_NULL, "%d", (int)std::lround(ms_till_fire/1000.0f)); } } } else { // just print the weapon's name since this isn't armed renderString(position[0] + Weapon_sname_offset_x, name_y, i ? EG_WEAPON_S1 : EG_WEAPON_S2, weapon_name); } int ammo=sw->secondary_bank_ammo[i]; // print out the ammo right justified sprintf(ammo_str, "%d", ammo); hud_num_make_mono(ammo_str, font_num); gr_get_string_size(&w, &h, ammo_str); renderString(position[0] + Weapon_sammo_offset_x - w, name_y, EG_NULL, ammo_str); if(i != 0) { y += secondary_text_h; } name_y += secondary_text_h; } // a bit lonely here with no secondaries so just print "" if(ns==0) { renderString(position[0] + Weapon_pname_offset_x, name_y, EG_WEAPON_S1, XSTR( "", 329)); } y -= 0; // finish drawing the background renderBitmap(secondary_bottom[ballistic_hud_index].first_frame, position[0] + frame_offset_x[ballistic_hud_index], y); } void hud_update_weapon_flash() { ship_weapon *sw; int num_weapons; sw = &Ships[Player_obj->instance].weapons; num_weapons = sw->num_primary_banks + sw->num_secondary_banks; if ( num_weapons > MAX_WEAPON_FLASH_LINES ) { Int3(); // Get Alan return; } for(int i = 0; i < num_weapons; i++) { if ( !timestamp_elapsed(Weapon_flash_info.flash_duration[i]) ) { if ( timestamp_elapsed(Weapon_flash_info.flash_next[i]) ) { Weapon_flash_info.flash_next[i] = timestamp(TBOX_FLASH_INTERVAL); Weapon_flash_info.is_bright ^= (1<= MAX_WEAPON_FLASH_LINES ) { Int3(); // Get Alan return; } // hud_set_default_color(); setGaugeColor(); if ( !timestamp_elapsed(Weapon_flash_info.flash_duration[index]) ) { if ( Weapon_flash_info.is_bright & (1<control_mode != PCM_NORMAL ) { return; } for(size_t i = 0; i < target_display_list.size(); i++) { if(target_display_list[i].target_point.codes != 0) { float dist = 0.0f; vec2d coords; float half_triangle_sep; int dir; if(target_display_list[i].objp) { dist = hud_find_target_distance( target_display_list[i].objp, Player_obj ); } else { // if we don't have a corresponding object, use given position to figure out distance dist = vm_vec_dist_quick(&Player_obj->pos, &target_display_list[i].target_pos); } if( target_display_list[i].bracket_clr.red && target_display_list[i].bracket_clr.green && target_display_list[i].bracket_clr.blue ) { gr_set_color_fast(&target_display_list[i].bracket_clr); } else { // use IFF colors if none defined. if(target_display_list[i].objp) { hud_set_iff_color(target_display_list[i].objp, 1); } else { // no object so this must mean it's a nav point. but for some odd reason someone forgot to include a color. gr_set_color_fast(&target_display_list[i].bracket_clr); } } calculatePosition(&target_display_list[i].target_point, &target_display_list[i].target_pos, &coords, &dir, &half_triangle_sep); renderOffscreenIndicator(&coords, dir, dist, half_triangle_sep, true); } } } void HudGaugeOffscreen::calculatePosition(vertex* target_point, vec3d *tpos, vec2d *outcoords, int *dir, float *half_triangle_sep) { float xpos,ypos; vec3d targ_to_player; float dist_behind; float triangle_sep; float half_gauge_length; bool in_frame = g3_in_frame() > 0; if(!in_frame) g3_start_frame(0); gr_set_screen_scale(base_w, base_h); // calculate the dot product between the players forward vector and the vector connecting // the player to the target. Normalize targ_to_player since we want the dot product // to range between 0 -> 1. vm_vec_sub(&targ_to_player, &Player_obj->pos, tpos); vm_vec_normalize(&targ_to_player); dist_behind = vm_vec_dot(&Player_obj->orient.vec.fvec, &targ_to_player); if (dist_behind < 0) { // still in front of player, but not in view dist_behind = dist_behind + 1.0f; if (dist_behind > 0.2 ){ triangle_sep = ( dist_behind ) * Max_front_seperation; } else { triangle_sep = 0.0f; } } else { triangle_sep = dist_behind * Max_offscreen_tri_seperation + Max_offscreen_tri_seperation; } if ( triangle_sep > Max_offscreen_tri_seperation + Max_front_seperation){ triangle_sep = Max_offscreen_tri_seperation + Max_front_seperation; } // calculate these values only once, since it will be used in several places *half_triangle_sep = 0.5f * triangle_sep; half_gauge_length = *half_triangle_sep + Offscreen_tri_base; // We need to find the screen (x,y) for where to draw the offscreen indicator // // The best way I've found is to draw a line from the eye_pos to the target, and // then use clip_line() to find the screen (x,y) for where the line hits the edge // of the screen. // // The weird thing about clip_line() is that is flips around the two verticies, // so I use eye_vertex->sx and eye_vertex->sy for the off-screen indicator (x,y) // vertex *eye_vertex = NULL; vertex real_eye_vertex; eye_vertex = &real_eye_vertex; // this is needed since clip line takes a **vertex vec3d eye_pos; vm_vec_add( &eye_pos, &Eye_position, &View_matrix.vec.fvec); g3_rotate_vertex(eye_vertex, &eye_pos); ubyte codes_or; codes_or = (ubyte)(target_point->codes | eye_vertex->codes); clip_line(&target_point,&eye_vertex,codes_or,0); if (!(target_point->flags&PF_PROJECTED)) g3_project_vertex(target_point); if (!(eye_vertex->flags&PF_PROJECTED)) g3_project_vertex(eye_vertex); if (eye_vertex->flags&PF_OVERFLOW) { Int3(); // This is unlikely to happen, but can if a clip goes through the player's eye. Player_ai->target_objnum = -1; if (!in_frame) g3_end_frame(); return; } if (target_point->flags & PF_TEMP_POINT) free_temp_point(target_point); if (eye_vertex->flags & PF_TEMP_POINT) free_temp_point(eye_vertex); xpos = eye_vertex->screen.xyw.x; ypos = eye_vertex->screen.xyw.y; // we need it unsized here and it will be fixed when things are acutally drawn gr_unsize_screen_posf(&xpos, &ypos); xpos = (xpos<1) ? 0 : xpos; ypos = (ypos<1) ? 0 : ypos; if ( xpos >= gr_screen.clip_right_unscaled) { xpos = i2fl(gr_screen.clip_right_unscaled); *dir = 0; if ( ypos < (half_gauge_length - gr_screen.clip_top_unscaled) ) ypos = half_gauge_length; if ( ypos > (gr_screen.clip_bottom_unscaled - half_gauge_length) ) ypos = gr_screen.clip_bottom_unscaled - half_gauge_length; } else if ( xpos <= gr_screen.clip_left_unscaled ) { xpos = i2fl(gr_screen.clip_left_unscaled); *dir = 1; if ( ypos < (half_gauge_length - gr_screen.clip_top_unscaled) ) ypos = half_gauge_length; if ( ypos > (gr_screen.clip_bottom_unscaled - half_gauge_length) ) ypos = gr_screen.clip_bottom_unscaled - half_gauge_length; } else if ( ypos <= gr_screen.clip_top_unscaled ) { ypos = i2fl(gr_screen.clip_top_unscaled); *dir = 2; if ( xpos < ( half_gauge_length - gr_screen.clip_left_unscaled) ) xpos = half_gauge_length; if ( xpos > (gr_screen.clip_right_unscaled - half_gauge_length) ) xpos = gr_screen.clip_right_unscaled - half_gauge_length; } else if ( ypos >= gr_screen.clip_bottom_unscaled ) { ypos = i2fl(gr_screen.clip_bottom_unscaled); *dir = 3; if ( xpos < ( half_gauge_length - gr_screen.clip_left_unscaled) ) xpos = half_gauge_length; if ( xpos > (gr_screen.clip_right_unscaled - half_gauge_length) ) xpos = gr_screen.clip_right_unscaled - half_gauge_length; } else { Int3(); if (!in_frame) g3_end_frame(); return; } // The offscreen target triangles are drawn according the the diagram below // // // // x3 x3 // / | | \. // / | | \. // x1____x2 x2____x1 // | | // .......|................|............(xpos,ypos) // | | // x4____x5 x5____x4 // \ | | / // \ | | / // x6 x6 // // xpos = (float)floor(xpos); ypos = (float)floor(ypos); if (outcoords != NULL) { outcoords->x = xpos; outcoords->y = ypos; } gr_reset_screen_scale(); if(!in_frame) g3_end_frame(); } void HudGaugeOffscreen::renderOffscreenIndicator(vec2d *coords, int dir, float distance, float half_triangle_sep, bool draw_solid) { float xpos, ypos; float displayed_distance; char buf[32]; int w = 0, h = 0; // points to draw triangles float x1=0.0f; float y1=0.0f; float x2=0.0f; float y2=0.0f; float x3=0.0f; float y3=0.0f; float x4=0.0f; float y4=0.0f; float x5=0.0f; float y5=0.0f; float x6=0.0f; float y6=0.0f; // scale by distance modifier from hud_guages.tbl for display purposes displayed_distance = distance * Hud_unit_multiplier; bool in_frame = g3_in_frame() > 0; if(!in_frame) g3_start_frame(0); gr_set_screen_scale(base_w, base_h); if (displayed_distance > 0.0f) { sprintf(buf, "%d", (int)std::lround(displayed_distance)); hud_num_make_mono(buf, font_num); gr_get_string_size(&w, &h, buf); } else { buf[0] = 0; } xpos = coords->x; ypos = coords->y; if (dir == 0) { x1 = x4 = (xpos+2); x2 = x3 = x5 = x6 = x1 - Offscreen_tri_height; y1 = y2 = ypos - half_triangle_sep; y3 = y2 - Offscreen_tri_base; y4 = y5 = ypos + half_triangle_sep; y6 = y5 + Offscreen_tri_base; if ( buf[0] ) { gr_string( fl2i(xpos - w - 10), (int)std::lround(ypos - h/2.0f), buf); } } else if (dir == 1) { x1 = x4 = (xpos-1); x2 = x3 = x5 = x6 = x1 + Offscreen_tri_height; y1 = y2 = ypos - half_triangle_sep; y3 = y2 - Offscreen_tri_base; y4 = y5 = ypos + half_triangle_sep; y6 = y5 + Offscreen_tri_base; if ( buf[0] ) { gr_string(fl2i(xpos + 10), (int)std::lround(ypos - h/2.0f), buf); } } else if (dir == 2) { y1 = y4 = (ypos-1); y2 = y3 = y5 = y6 = y1 + Offscreen_tri_height; x1 = x2 = xpos - half_triangle_sep; x3 = x2 - Offscreen_tri_base; x4 = x5 = xpos + half_triangle_sep; x6 = x5 + Offscreen_tri_base; if ( buf[0] ) { gr_string((int)std::lround(xpos - w/2.0f), fl2i(ypos+10), buf); } } else if (dir == 3) { y1 = y4 = (ypos+2); y2 = y3 = y5 = y6 = y1 - Offscreen_tri_height; x1 = x2 = xpos - half_triangle_sep; x3 = x2 - Offscreen_tri_base; x4 = x5 = xpos + half_triangle_sep; x6 = x5 + Offscreen_tri_base; if ( buf[0] ) { gr_string((int)std::lround(xpos - w/2.0f), fl2i(ypos-h-10), buf); } } if (draw_solid) { hud_tri(x3,y3,x2,y2,x1,y1); hud_tri(x4,y4,x5,y5,x6,y6); } else { hud_tri_empty(x3,y3,x2,y2,x1,y1); hud_tri_empty(x4,y4,x5,y5,x6,y6); } if (dir == 0 || dir == 3){ gr_line(fl2i(x2),fl2i(y2),fl2i(x5),fl2i(y5)); } else if (dir == 1) { gr_line(fl2i(x2-1),fl2i(y2),fl2i(x5-1),fl2i(y5)); } else { gr_line(fl2i(x2),fl2i(y2-1),fl2i(x5),fl2i(y5-1)); } gr_reset_screen_scale(); if(!in_frame) g3_end_frame(); } HudGaugeWarheadCount::HudGaugeWarheadCount(): HudGauge(HUD_OBJECT_WARHEAD_COUNT, HUD_WEAPONS_GAUGE, false, false, VM_EXTERNAL | VM_DEAD_VIEW | VM_WARP_CHASE | VM_PADLOCK_ANY, 255, 255, 255) { } void HudGaugeWarheadCount::initBitmap(char *fname) { Warhead.first_frame = bm_load_animation(fname, &Warhead.num_frames); if ( Warhead.first_frame < 0 ) { Warning(LOCATION,"Cannot load hud ani: %s\n", fname); } } void HudGaugeWarheadCount::initNameOffsets(int x, int y) { Warhead_name_offsets[0] = x; Warhead_name_offsets[1] = y; } void HudGaugeWarheadCount::initCountOffsets(int x, int y) { Warhead_count_offsets[0] = x; Warhead_count_offsets[1] = y; } void HudGaugeWarheadCount::initCountSizes(int w, int h) { Warhead_count_size[0] = w; Warhead_count_size[1] = h; } void HudGaugeWarheadCount::initMaxSymbols(int count) { Max_symbols = count; } void HudGaugeWarheadCount::initMaxColumns(int count) { Max_columns = count; } void HudGaugeWarheadCount::initTextAlign(HudAlignment align) { Text_align = align; } void HudGaugeWarheadCount::pageIn() { bm_page_in_aabitmap(Warhead.first_frame, Warhead.num_frames); } void HudGaugeWarheadCount::render(float /*frametime*/) { if(Player_obj->type == OBJ_OBSERVER) { return; } Assert(Player_obj->type == OBJ_SHIP); Assert(Player_obj->instance >= 0 && Player_obj->instance < MAX_SHIPS); ship_weapon *sw = &Ships[Player_obj->instance].weapons; // don't bother displaying anything if we have no secondaries if ( sw->num_secondary_banks <= 0 ) { return; } int wep_num = sw->current_secondary_bank; weapon_info *wip = &Weapon_info[sw->secondary_bank_weapons[wep_num]]; int ammo = sw->secondary_bank_ammo[wep_num]; // don't bother displaying anything if we have no ammo. if ( ammo <= 0 ) { return; } auto weapon_name = wip->get_display_name(); setGaugeColor(); // display the weapon name if ( Text_align == HudAlignment::RIGHT ) { int w, h; gr_get_string_size(&w, &h, weapon_name); renderString(position[0] + Warhead_name_offsets[0] - w, position[1] + Warhead_name_offsets[1], weapon_name); } else if ( Text_align == HudAlignment::LEFT ) { renderString(position[0] + Warhead_name_offsets[0], position[1] + Warhead_name_offsets[1], weapon_name); } setGaugeColor(HUD_C_BRIGHT); // if ammo is greater than the icon display limit, just show a numeric if ( ammo > Max_symbols ) { char ammo_str[32]; sprintf(ammo_str, "%d", ammo); hud_num_make_mono(ammo_str, font_num); if ( Text_align == HudAlignment::RIGHT ) { int w, h; gr_get_string_size(&w, &h, ammo_str); renderString(position[0] + Warhead_count_offsets[0] - w, position[1] + Warhead_count_offsets[1], ammo_str); } else if ( Text_align == HudAlignment::LEFT ) { renderString(position[0] + Warhead_count_offsets[0], position[1] + Warhead_count_offsets[1], ammo_str); } return; } int delta_x = 0, delta_y = 0; if ( Text_align == HudAlignment::RIGHT ) { delta_x = -Warhead_count_size[0]; } else if ( Text_align == HudAlignment::LEFT ) { delta_x = Warhead_count_size[0]; } int i, column; for ( i = 0; i < ammo; i++ ) { if ( Max_columns > 0 ) { delta_y = Warhead_count_size[1] * (i / Max_columns); column = i % Max_columns; } else { column = i; } renderBitmap(Warhead.first_frame, position[0] + Warhead_count_offsets[0] + column * delta_x, position[1] + Warhead_count_offsets[1] + delta_y); } } HudGaugeWeaponList::HudGaugeWeaponList(int _gauge_object): HudGauge(_gauge_object, HUD_WEAPONS_GAUGE, false, false, VM_EXTERNAL | VM_DEAD_VIEW | VM_WARP_CHASE | VM_PADLOCK_ANY, 255, 255, 255) { } void HudGaugeWeaponList::initLinkIcon() { ubyte sc = lcl_get_font_index(font_num); // default to a '>' if the font has no special chars // seems about the closest normal char to the triangle if (sc == 0) { Weapon_link_icon = ubyte ('>'); } else { Weapon_link_icon = sc + 2; } } void HudGaugeWeaponList::initBitmaps(char *fname_first, char *fname_entry, char *fname_last) { _background_first.first_frame = bm_load_animation(fname_first, &_background_first.num_frames); if(_background_first.first_frame < 0) { Warning(LOCATION,"Cannot load hud ani: %s\n", fname_first); } _background_entry.first_frame = bm_load_animation(fname_entry, &_background_entry.num_frames); if(_background_entry.first_frame < 0) { Warning(LOCATION,"Cannot load hud ani: %s\n", fname_entry); } _background_last.first_frame = bm_load_animation(fname_last, &_background_last.num_frames); if(_background_last.first_frame < 0) { Warning(LOCATION,"Cannot load hud ani: %s\n", fname_last); } } void HudGaugeWeaponList::initBgFirstOffsetX(int x) { _bg_first_offset_x = x; } void HudGaugeWeaponList::initBgEntryOffsetX(int x) { _bg_entry_offset_x = x; } void HudGaugeWeaponList::initBgLastOffsetX(int x) { _bg_last_offset_x = x; } void HudGaugeWeaponList::initBgLastOffsetY(int y) { _bg_last_offset_y = y; } void HudGaugeWeaponList::initBgFirstHeight(int h) { _background_first_h = h; } void HudGaugeWeaponList::initBgEntryHeight(int h) { _background_entry_h = h; } void HudGaugeWeaponList::initHeaderText(char *text) { strcpy_s(header_text, text); } void HudGaugeWeaponList::initHeaderOffsets(int x, int y) { _header_offsets[0] = x; _header_offsets[1] = y; } void HudGaugeWeaponList::initEntryStartY(int y) { _entry_start_y = y; } void HudGaugeWeaponList::initEntryHeight(int h) { _entry_h = h; } void HudGaugeWeaponList::pageIn() { if ( _background_first.first_frame >= 0 ) { bm_page_in_aabitmap(_background_first.first_frame, _background_first.num_frames); } if ( _background_entry.first_frame >= 0 ) { bm_page_in_aabitmap(_background_entry.first_frame, _background_entry.num_frames); } if ( _background_last.first_frame >= 0 ) { bm_page_in_aabitmap(_background_last.first_frame, _background_last.num_frames); } } void HudGaugeWeaponList::maybeFlashWeapon(int index) { if ( index >= MAX_WEAPON_FLASH_LINES ) { Int3(); // Get Alan return; } // hud_set_default_color(); setGaugeColor(); if ( !timestamp_elapsed(Weapon_flash_info.flash_duration[index]) ) { if ( Weapon_flash_info.is_bright & (1<type == OBJ_OBSERVER) return; Assert(Player_obj->type == OBJ_SHIP); Assert(Player_obj->instance >= 0 && Player_obj->instance < MAX_SHIPS); sw = &Ships[Player_obj->instance].weapons; num_primaries = sw->num_primary_banks; setGaugeColor(); renderBitmap(_background_first.first_frame, position[0], position[1]); // render the header of this gauge renderString(position[0] + _header_offsets[0], position[1] + _header_offsets[1], EG_WEAPON_TITLE, header_text); char ammo_str[32]; int i, w, h; int bg_y_offset = _background_first_h; int text_y_offset = _entry_start_y; for ( i = 0; i < num_primaries; ++i ) { setGaugeColor(); renderBitmap(_background_entry.first_frame, position[0], position[1] + bg_y_offset); auto weapon_name = Weapon_info[sw->primary_bank_weapons[i]].get_display_name(); if (HudGauge::maybeFlashSexp() == i ) { setGaugeColor(HUD_C_BRIGHT); } else { maybeFlashWeapon(i); } // indicate if this is linked or currently armed if ( (sw->current_primary_bank == i) || (Player_ship->flags[Ship::Ship_Flags::Primary_linked]) ) { renderPrintf(position[0] + _plink_offset_x, position[1] + text_y_offset, EG_NULL, "%c", Weapon_link_icon); } // either render this primary's image or its name if(Weapon_info[sw->primary_bank_weapons[0]].hud_image_index != -1) { renderBitmap(Weapon_info[sw->primary_bank_weapons[i]].hud_image_index, position[0] + _pname_offset_x, text_y_offset); } else { renderPrintf(position[0] + _pname_offset_x, position[1] + text_y_offset, EG_WEAPON_P2, "%s", weapon_name); } // if this is a ballistic primary with ammo, render the ammo count if (Weapon_info[sw->primary_bank_weapons[i]].wi_flags[Weapon::Info_Flags::Ballistic]) { // print out the ammo right justified sprintf(ammo_str, "%d", sw->primary_bank_ammo[i]); hud_num_make_mono(ammo_str, font_num); gr_get_string_size(&w, &h, ammo_str); renderString(position[0] + _pammo_offset_x - w, position[1] + text_y_offset, EG_NULL, ammo_str); } text_y_offset += _entry_h; bg_y_offset += _background_entry_h; } if ( num_primaries == 0 ) { renderBitmap(_background_entry.first_frame, position[0], position[1] + bg_y_offset); renderString(position[0] + _pname_offset_x, position[1] + text_y_offset, EG_WEAPON_P1, XSTR( "", 329)); bg_y_offset += _background_entry_h; } renderBitmap(_background_last.first_frame, position[0], position[1] + bg_y_offset + _bg_last_offset_y); } HudGaugeSecondaryWeapons::HudGaugeSecondaryWeapons(): HudGaugeWeaponList(HUD_OBJECT_SECONDARY_WEAPONS) { } void HudGaugeSecondaryWeapons::initSecondaryAmmoOffsetX(int x) { _sammo_offset_x = x; } void HudGaugeSecondaryWeapons::initSecondaryNameOffsetX(int x) { _sname_offset_x = x; } void HudGaugeSecondaryWeapons::initSecondaryReloadOffsetX(int x) { _sreload_offset_x = x; } void HudGaugeSecondaryWeapons::initSecondaryLinkedOffsetX(int x) { _slinked_offset_x = x; } void HudGaugeSecondaryWeapons::initSecondaryUnlinkedOffsetX(int x) { _sunlinked_offset_x = x; } void HudGaugeSecondaryWeapons::render(float /*frametime*/) { ship_weapon *sw; int num_primaries, num_secondaries; Assert(Player_obj->type == OBJ_SHIP); Assert(Player_obj->instance >= 0 && Player_obj->instance < MAX_SHIPS); sw = &Ships[Player_obj->instance].weapons; num_primaries = sw->num_primary_banks; num_secondaries = sw->num_secondary_banks; setGaugeColor(); renderBitmap(_background_first.first_frame, position[0], position[1]); // render the header of this gauge renderString(position[0] + _header_offsets[0], position[1] + _header_offsets[1], EG_WEAPON_TITLE, header_text); weapon_info *wip; char ammo_str[32]; int i, w, h; int bg_y_offset = _background_first_h; int text_y_offset = _entry_start_y; for ( i = 0; i < num_secondaries; ++i ) { setGaugeColor(); wip = &Weapon_info[sw->secondary_bank_weapons[i]]; renderBitmap(_background_entry.first_frame, position[0], position[1] + bg_y_offset); maybeFlashWeapon(num_primaries+i); auto weapon_name = wip->get_display_name(); if ( sw->current_secondary_bank == i ) { // show that this is the current secondary armed renderPrintf(position[0] + _sunlinked_offset_x, position[1] + text_y_offset, EG_NULL, "%c", Weapon_link_icon); // indicate if this is linked // don't draw the link indicator if the fire can't be fired link. // the link flag is ignored rather than cleared so the player can cycle past a no-doublefire weapon without the setting being cleared if ( Player_ship->flags[Ship::Ship_Flags::Secondary_dual_fire] && !wip->wi_flags[Weapon::Info_Flags::No_doublefire] && !The_mission.ai_profile->flags[AI::Profile_Flags::Disable_player_secondary_doublefire] ) { renderPrintf(position[0] + _slinked_offset_x, position[1] + text_y_offset, EG_NULL, "%c", Weapon_link_icon); } // show secondary weapon's image or print its name if(wip->hud_image_index != -1) { renderBitmap(wip->hud_image_index, position[0] + _sname_offset_x, position[1] + text_y_offset); } else { renderString(position[0] + _sname_offset_x, position[1] + text_y_offset, i ? EG_WEAPON_S1 : EG_WEAPON_S2, weapon_name); } // show the cooldown time if ( (sw->secondary_bank_ammo[i] > 0) && (sw->current_secondary_bank >= 0) ) { int ms_till_fire = timestamp_until(sw->next_secondary_fire_stamp[sw->current_secondary_bank]); if ( (ms_till_fire >= 500) && ((wip->fire_wait >= 1 ) || (ms_till_fire > wip->fire_wait*1000)) ) { renderPrintf(position[0] + _sreload_offset_x, position[1] + text_y_offset, EG_NULL, "%d", (int)std::lround(ms_till_fire/1000.0f)); } } } else { renderString(position[0] + _sname_offset_x, position[1] + text_y_offset, i ? EG_WEAPON_S1 : EG_WEAPON_S2, weapon_name); } int ammo = sw->secondary_bank_ammo[i]; // print out the ammo right justified sprintf(ammo_str, "%d", ammo); hud_num_make_mono(ammo_str, font_num); gr_get_string_size(&w, &h, ammo_str); renderString(position[0] + _sammo_offset_x - w, position[1] + text_y_offset, EG_NULL, ammo_str); bg_y_offset += _background_entry_h; text_y_offset += _entry_h; } if ( num_secondaries == 0 ) { renderBitmap(_background_entry.first_frame, position[0], position[1] + bg_y_offset); renderString(position[0] + _sname_offset_x, position[1] + text_y_offset, EG_WEAPON_S1, XSTR( "", 329)); bg_y_offset += _background_entry_h; } // finish drawing the background renderBitmap(_background_last.first_frame, position[0], position[1] + bg_y_offset + _bg_last_offset_y); } HudGaugeHardpoints::HudGaugeHardpoints(): HudGauge(HUD_OBJECT_HARDPOINTS, HUD_WEAPONS_GAUGE, false, false, VM_EXTERNAL | VM_DEAD_VIEW | VM_WARP_CHASE | VM_PADLOCK_ANY, 255, 255, 255) { } void HudGaugeHardpoints::initSizes(int w, int h) { _size[0] = w; _size[1] = h; } void HudGaugeHardpoints::initLineWidth(float w) { _line_width = w; } void HudGaugeHardpoints::initViewDir(int dir) { _view_direction = dir; } void HudGaugeHardpoints::initDrawOptions(bool primary_models, bool secondary_models) { draw_primary_models = primary_models; draw_secondary_models = secondary_models; } void HudGaugeHardpoints::render(float /*frametime*/) { int sx, sy; ship *sp; ship_info *sip; object *objp = Player_obj; sp = &Ships[objp->instance]; sip = &Ship_info[sp->ship_info_index]; sx = position[0]; sy = position[1]; bool g3_yourself = !g3_in_frame(); angles top_view = {-PI_2,0.0f,0.0f}; angles front_view = {PI_2*2.0f,PI_2*2.0f,0.0f}; matrix object_orient; switch ( _view_direction ) { case TOP: vm_angles_2_matrix(&object_orient, &top_view); break; case FRONT: vm_angles_2_matrix(&object_orient, &front_view); break; } gr_screen.clip_width = _size[0]; gr_screen.clip_height = _size[1]; //Fire it up if(g3_yourself) g3_start_frame(1); hud_save_restore_camera_data(1); setClip(sx, sy, _size[0], _size[1]); g3_set_view_matrix( &sip->closeup_pos, &vmd_identity_matrix, sip->closeup_zoom*1.5f); gr_set_proj_matrix(Proj_fov, gr_screen.clip_aspect, Min_draw_distance, Max_draw_distance); gr_set_view_matrix(&Eye_position, &Eye_matrix); setGaugeColor(); //We're ready to show stuff model_render_params render_info; gr_stencil_clear(); const int detail_level_lock = 1; render_info.set_detail_level_lock(detail_level_lock); render_info.set_flags( MR_NO_LIGHTING | MR_AUTOCENTER | MR_NO_FOGGING | MR_NO_TEXTURING | MR_NO_CULL | MR_NO_ZBUFFER | MR_STENCIL_WRITE | MR_NO_COLOR_WRITES); render_info.set_object_number(OBJ_INDEX(objp)); model_render_immediate( &render_info, sip->model_num, &object_orient, &vmd_zero_vector); render_info.set_color(gauge_color); render_info.set_flags(MR_NO_LIGHTING | MR_AUTOCENTER | MR_NO_FOGGING | MR_NO_TEXTURING | MR_NO_ZBUFFER | MR_NO_CULL | MR_STENCIL_READ); // Factor was chosen pretty arbitrarily to make outline feature work with existing data render_info.set_outline_thickness(15.f * _line_width); model_render_immediate( &render_info, sip->model_num, &object_orient, &vmd_zero_vector ); gr_stencil_set(GR_STENCIL_NONE); // draw weapon models int i, k; ship_weapon *swp = &sp->weapons; vertex draw_point; vec3d subobj_pos; int render_flags = MR_NO_LIGHTING | MR_AUTOCENTER | MR_NO_FOGGING | MR_NO_TEXTURING | MR_NO_ZBUFFER; setGaugeColor(); float alpha = gr_screen.current_color.alpha / 255.0f; //secondary weapons int num_secondaries_rendered = 0; if ( draw_secondary_models ) { auto ship_pm = model_get(sip->model_num); for (i = 0; i < swp->num_secondary_banks; i++) { auto wip = &Weapon_info[swp->secondary_bank_weapons[i]]; if (wip->external_model_num == -1 || !sip->draw_secondary_models[i]) continue; auto bank = &ship_pm->missile_banks[i]; if (wip->wi_flags[Weapon::Info_Flags::External_weapon_lnch]) { for(k = 0; k < bank->num_slots; k++) { model_render_params weapon_render_info; weapon_render_info.set_detail_level_lock(detail_level_lock); weapon_render_info.set_flags(render_flags); // We need to transform the position local to the model to be in "world" space relative to the rendered outline vec3d world_position; vm_vec_unrotate(&world_position, &bank->pnt[k], &object_orient); // "Bank" the external model by the angle offset angles angs = { 0.0f, bank->external_model_angle_offset[k], 0.0f }; matrix model_orient = object_orient; vm_rotate_matrix_by_angles(&model_orient, &angs); model_render_immediate(&weapon_render_info, wip->external_model_num, &model_orient, &bank->pnt[k]); } } else { num_secondaries_rendered = 0; for(k = 0; k < bank->num_slots; k++) { auto secondary_weapon_pos = bank->pnt[k]; if (num_secondaries_rendered >= sp->weapons.secondary_bank_ammo[i]) break; if(sp->secondary_point_reload_pct.get(i, k) <= 0.0) continue; model_render_params weapon_render_info; if ( swp->current_secondary_bank == i && ( swp->secondary_next_slot[i] == k || ( swp->secondary_next_slot[i]+1 == k && sp->flags[Ship::Ship_Flags::Secondary_dual_fire] ) ) ) { weapon_render_info.set_color(Color_bright_blue); } else { weapon_render_info.set_color(Color_bright_white); } num_secondaries_rendered++; vm_vec_scale_add2(&secondary_weapon_pos, &vmd_z_vector, -(1.0f-sp->secondary_point_reload_pct.get(i, k)) * model_get(wip->external_model_num)->rad); weapon_render_info.set_detail_level_lock(detail_level_lock); weapon_render_info.set_flags(render_flags); // We need to transform the position local to the model to be in "world" space relative to the rendered outline vec3d world_position; vm_vec_unrotate(&world_position, &secondary_weapon_pos, &object_orient); // "Bank" the external model by the angle offset angles angs = { 0.0f, bank->external_model_angle_offset[k], 0.0f }; matrix model_orient = object_orient; vm_rotate_matrix_by_angles(&model_orient, &angs); model_render_immediate(&weapon_render_info, wip->external_model_num, &model_orient, &world_position); } } } } resetClip(); setGaugeColor(HUD_C_BRIGHT); //primary weapons if ( draw_primary_models ) { auto ship_pm = model_get(sip->model_num); for ( i = 0; i < swp->num_primary_banks; i++ ) { auto wip = &Weapon_info[swp->primary_bank_weapons[i]]; auto bank = &ship_pm->gun_banks[i]; for ( k = 0; k < bank->num_slots; k++ ) { if ( wip->external_model_num < 0 || !sip->draw_primary_models[i] ) { vm_vec_unrotate(&subobj_pos, &bank->pnt[k], &object_orient); //vm_vec_sub(&subobj_pos, &Eye_position, &subobj_pos); //g3_rotate_vertex(&draw_point, &bank->pnt[k]); g3_rotate_vertex(&draw_point, &subobj_pos); g3_project_vertex(&draw_point); //resize(&width, &height); //unsize(&xc, &yc); //unsize(&draw_point.screen.xyw.x, &draw_point.screen.xyw.y); renderCircle((int)draw_point.screen.xyw.x + position[0], (int)draw_point.screen.xyw.y + position[1], 10); //renderCircle(xc, yc, 25); } else { model_render_params weapon_render_info; weapon_render_info.set_detail_level_lock(detail_level_lock); weapon_render_info.set_flags(render_flags); weapon_render_info.set_alpha(alpha); // We need to transform the position local to the model to be in "world" space relative to the rendered outline vec3d world_position; vm_vec_unrotate(&world_position, &bank->pnt[k], &object_orient); // "Bank" the external model by the angle offset angles angs = { 0.0f, bank->external_model_angle_offset[k], 0.0f }; matrix model_orient = object_orient; vm_rotate_matrix_by_angles(&model_orient, &angs); model_render_immediate(&weapon_render_info, wip->external_model_num, swp->primary_bank_external_model_instance[i], &model_orient, &world_position); } } } } //We're done gr_end_view_matrix(); gr_end_proj_matrix(); if(g3_yourself) g3_end_frame(); hud_save_restore_camera_data(0); }