/* * 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 "ai/aigoals.h" #include "asteroid/asteroid.h" #include "bmpman/bmpman.h" #include "cmdline/cmdline.h" #include "freespace.h" #include "gamesnd/eventmusic.h" #include "gamesnd/gamesnd.h" #include "graphics/openxr.h" #include "globalincs/alphacolors.h" #include "globalincs/linklist.h" #include "hud/hud.h" #include "hud/hudconfig.h" #include "hud/hudescort.h" #include "hud/hudets.h" #include "hud/hudlock.h" #include "hud/hudmessage.h" #include "hud/hudnavigation.h" //kazan #include "hud/hudobserver.h" #include "hud/hudreticle.h" #include "hud/hudscripting.h" #include "hud/hudshield.h" #include "hud/hudsquadmsg.h" #include "hud/hudtarget.h" #include "hud/hudtargetbox.h" #include "hud/hudwingmanstatus.h" #include "iff_defs/iff_defs.h" #include "io/timer.h" #include "localization/localize.h" #include "mission/missiongoals.h" #include "mission/missionmessage.h" #include "mission/missionparse.h" #include "mission/missiontraining.h" #include "missionui/redalert.h" #include "model/model.h" #include "network/multi_pmsg.h" #include "network/multi_voice.h" #include "network/multiutil.h" #include "tracing/tracing.h" #include "object/object.h" #include "object/objectdock.h" #include "playerman/player.h" #include "radar/radar.h" #include "radar/radarsetup.h" #include "render/3d.h" #include "scripting/scripting.h" #include "ship/ship.h" #include "starfield/supernova.h" #include "weapon/emp.h" #include "weapon/weapon.h" // This contains not only the retail HUD gauges but also any custom HUD gauges that are not specific to a ship. SCP_vector> default_hud_gauges; // new values for HUD alpha #define HUD_NEW_ALPHA_DIM 80 #define HUD_NEW_ALPHA_NORMAL 120 #define HUD_NEW_ALPHA_BRIGHT 220 // high contrast #define HUD_NEW_ALPHA_DIM_HI 130 #define HUD_NEW_ALPHA_NORMAL_HI 190 #define HUD_NEW_ALPHA_BRIGHT_HI 255 // Externals not related to the HUD code itself extern fov_t View_zoom; // globals that will control the color of the HUD gauges int HUD_color_red = 0; int HUD_color_green = 255; int HUD_color_blue = 0; int HUD_color_alpha = HUD_COLOR_ALPHA_DEFAULT; // 1 -> HUD_COLOR_ALPHA_USER_MAX int HUD_draw = 1; int HUD_contrast = 0; // high or lo contrast (for nebula, etc) bool HUD_shadows = false; // Goober5000 int HUD_disable_except_messages = 0; color HUD_color_defaults[HUD_NUM_COLOR_LEVELS]; // array of colors with different alpha blending color HUD_color_debug; // grey debug text shown on HUD static sound_handle Player_engine_snd_loop = sound_handle::invalid(); // the offset of the player's view vector and the ship forward vector in pixels (Swifty) int HUD_nose_x; int HUD_nose_y; // Global: integrity of player's target float Pl_target_integrity; int Hud_max_targeting_range; static int Hud_last_can_target; // whether Player is able to target in the last frame static int Hud_can_target_timer; // timestamp to allow target gauge to draw static once targeting functions are not allowed // centered text message gauges (collision, emp, etc) char Hud_text_flash[512] = ""; int Hud_text_flash_timer = 0; int Hud_text_flash_interval = 0; void hud_init_text_flash_gauge(); void hud_start_text_flash(char *txt, int t, int interval); // multiplayer messaging text int Multi_msg_coords[GR_NUM_RESOLUTIONS][2] = { { // GR_640 5, 150 }, { // GR_1024 8, 240 } }; // multiplayer voice stuff int Voice_coords[GR_NUM_RESOLUTIONS][2] = { { // GR_640 5, 165 }, { // GR_1024 8, 255 } }; // ping text coords int Ping_coords[GR_NUM_RESOLUTIONS][2] = { { // GR_640 560, 3 }, { // GR_1024 896, 5 } }; // supernova coords int Supernova_coords[GR_NUM_RESOLUTIONS][2] = { { // GR_640 100, 100 }, { // GR_1024 170, 170 } }; // used to draw the hud support view static int Hud_support_view_active; static int Hud_support_view_abort; // active when we need to display abort message static int Hud_support_view_fade; // timer static int Hud_support_obj_sig, Hud_support_objnum, Hud_support_target_sig; // flashing gauges #define HUD_GAUGE_FLASH_DURATION 5000 #define HUD_GAUGE_FLASH_INTERVAL 200 int HUD_gauge_flash_duration[NUM_HUD_GAUGES]; int HUD_gauge_flash_next[NUM_HUD_GAUGES]; int HUD_gauge_bright; static objective_display_info Objective_display; // Subspace notify display static int Subspace_notify_active; static int Objective_notify_active; static int HUD_abort_subspace_timer = 1; static hud_subsys_info Pl_hud_subsys_info[SUBSYSTEM_MAX]; static int Pl_hud_next_flash_timestamp; static int Pl_hud_is_bright; #define SUBSYS_DAMAGE_FLASH_DURATION 1800 #define SUBSYS_DAMAGE_FLASH_INTERVAL 100 float Player_rearm_eta = 0; bool Extra_target_info = false; // forward declarations void update_throttle_sound(); void hud_damage_popup_init(); void hud_support_view_init(); void hud_gauge_flash_init(); void hud_objective_message_init(); void hud_stop_subspace_notify(); void hud_start_subspace_notify(); void hud_stop_objective_notify(); void hud_start_objective_notify(); int hud_subspace_notify_active(); int hud_objective_notify_active(); void hud_subspace_notify_abort(); void hud_maybe_display_subspace_notify(); int hud_maybe_render_emp_icon(); void hud_init_emp_icon(); // Saturate a value in minv..maxv. void saturate(int *i, int minv, int maxv) { if (*i < minv) *i = minv; else if (*i > maxv) *i = maxv; } // init the colors used for the different shades of the HUD void HUD_init_hud_color_array() { int i; for ( i = 0; i < HUD_NUM_COLOR_LEVELS; i++ ) { gr_init_alphacolor( &HUD_color_defaults[i], HUD_color_red, HUD_color_green, HUD_color_blue, (i+1)*16 ); } } // HUD_init will call all the various HUD gauge init functions. This function is called at the // start of each mission (level) void HUD_init_colors() { saturate(&HUD_color_red, 0, 255); saturate(&HUD_color_green, 0, 255); saturate(&HUD_color_blue, 0, 255); saturate(&HUD_color_alpha, 0, HUD_COLOR_ALPHA_USER_MAX); gr_init_alphacolor( &HUD_color_debug, 128, 255, 128, HUD_color_alpha*16 ); HUD_init_hud_color_array(); hud_init_targeting_colors(); hud_gauge_flash_init(); } // The following global data is used to determine if we should change the engine sound. // We only check if the throttle has changed every THROTTLE_SOUND_CHECK_INTERVAL ms, and // then we make sure that the throttle has actually changed. If it has changed, we start // a new sound and/or adjust the volume. This occurs in update_throttle_sound() // static float last_percent_throttle; #define THROTTLE_SOUND_CHECK_INTERVAL 50 // in ms static int throttle_sound_check_id; #define DAMAGE_FLASH_TIME 150 static int Damage_flash_bright; static int Damage_flash_timer; HudGauge::HudGauge(): base_w(0), base_h(0), gauge_config(-1), font_num(font::FONT1), lock_color(false), sexp_lock_color(false), reticle_follow(false), active(false), off_by_default(false), sexp_override(false), pop_up(false), disabled_views(0), only_render_in_chase_view(false), render_for_cockpit_toggle(0), custom_gauge(false), texture_target(-1), canvas_w(-1), canvas_h(-1), target_w(-1), target_h(-1) { position[0] = 0; position[1] = 0; gr_init_alphacolor(&gauge_color, 255, 255, 255, (HUD_color_alpha+1)*16); flash_duration = timestamp(1); flash_next = timestamp(1); flash_status = false; popup_timer = timestamp(1); texture_target_fname[0] = '\0'; custom_name[0] = '\0'; custom_text = ""; custom_frame.first_frame = -1; custom_frame.num_frames = 0; custom_frame_offset = 0; } HudGauge::HudGauge(int _gauge_object, int _gauge_config, bool _slew, bool _message, int _disabled_views, int r, int g, int b): base_w(0), base_h(0), gauge_config(_gauge_config), gauge_object(_gauge_object), font_num(font::FONT1), lock_color(false), sexp_lock_color(false), reticle_follow(_slew), active(false), off_by_default(false), sexp_override(false), pop_up(false), message_gauge(_message), disabled_views(_disabled_views), only_render_in_chase_view(false), render_for_cockpit_toggle(0), custom_gauge(false), textoffset_x(0), textoffset_y(0), texture_target(-1), canvas_w(-1), canvas_h(-1), target_w(-1), target_h(-1) { Assert(gauge_config <= NUM_HUD_GAUGES && gauge_config >= 0); position[0] = 0; position[1] = 0; if(r >= 0 && r <= 255 && g >= 0 && g <= 255 && b >= 0 && b <= 255) { gr_init_alphacolor(&gauge_color, r, g, b, (HUD_color_alpha+1)*16); } else { gr_init_alphacolor(&gauge_color, 255, 255, 255, (HUD_color_alpha+1)*16); } flash_duration = timestamp(1); flash_next = timestamp(1); flash_status = false; popup_timer = timestamp(1); texture_target_fname[0] = '\0'; custom_name[0] = '\0'; custom_text = ""; default_text = ""; custom_frame.first_frame = -1; custom_frame.num_frames = 0; custom_frame_offset = 0; } // constructor for custom gauges HudGauge::HudGauge(int _gauge_config, bool _slew, int r, int g, int b, char* _custom_name, char* _custom_text, char* frame_fname, int txtoffset_x, int txtoffset_y): base_w(0), base_h(0), gauge_config(_gauge_config), gauge_object(HUD_OBJECT_CUSTOM), font_num(font::FONT1), lock_color(false), sexp_lock_color(false), reticle_follow(_slew), active(false), off_by_default(false), sexp_override(false), pop_up(false), message_gauge(false), disabled_views(VM_EXTERNAL | VM_DEAD_VIEW | VM_WARP_CHASE | VM_PADLOCK_ANY), only_render_in_chase_view(false), render_for_cockpit_toggle(0), custom_gauge(true), textoffset_x(txtoffset_x), textoffset_y(txtoffset_y), texture_target(-1), canvas_w(-1), canvas_h(-1), target_w(-1), target_h(-1) { position[0] = 0; position[1] = 0; if(r >= 0 && r <= 255 && g >= 0 && g <= 255 && b >= 0 && b <= 255) { gr_init_alphacolor(&gauge_color, r, g, b, (HUD_color_alpha+1)*16); } else { gr_init_alphacolor(&gauge_color, 255, 255, 255, (HUD_color_alpha+1)*16); } flash_duration = timestamp(1); flash_next = timestamp(1); flash_status = false; popup_timer = timestamp(1); texture_target_fname[0] = '\0'; if(_custom_name) { strcpy_s(custom_name, _custom_name); } else { custom_name[0] = '\0'; } if(_custom_text) { custom_text = _custom_text; default_text = _custom_text; } else { custom_text = ""; default_text = ""; } custom_frame.first_frame = -1; custom_frame.num_frames = 0; custom_frame_offset = 0; if(frame_fname) { custom_frame.first_frame = bm_load_animation(frame_fname, &custom_frame.num_frames); if (custom_frame.first_frame < 0 ) { Warning(LOCATION,"Cannot load hud ani: %s\n", frame_fname); } } } HudGauge::~HudGauge() {}; void HudGauge::initPosition(int x, int y) { position[0] = x; position[1] = y; } void HudGauge::getPosition(int *x, int *y) { *x = position[0]; *y = position[1]; } void HudGauge::initBaseResolution(int w, int h, float aq) { Assert(w >= 640 && h >= 480); base_w = w; base_h = h; aspect_quotient = aq; } void HudGauge::initSlew(bool slew) { reticle_follow = slew; } void HudGauge::initFont(int input_font_num) { if (input_font_num >= 0 && input_font_num < font::FontManager::numberOfFonts()) { font_num = input_font_num; } } int HudGauge::getFont() { return font_num; } void HudGauge::initOriginAndOffset(float originX, float originY, int offsetX, int offsetY) { tabled_origin[0] = originX; tabled_origin[1] = originY; tabled_offset[0] = offsetX; tabled_offset[1] = offsetY; } void HudGauge::getOriginAndOffset(float *originX, float *originY, int *offsetX, int *offsetY) { *originX = tabled_origin[0]; *originY = tabled_origin[1]; *offsetX = tabled_offset[0]; *offsetY = tabled_offset[1]; } void HudGauge::initCoords(bool use_coords, int coordsX, int coordsY) { tabled_use_coords = use_coords; tabled_coords[0] = coordsX; tabled_coords[1] = coordsY; } void HudGauge::getCoords(bool *use_coords, int *coordsX, int *coordsY) { *use_coords = tabled_use_coords; *coordsX = tabled_coords[0]; *coordsY = tabled_coords[1]; } const char* HudGauge::getCustomGaugeName() { return custom_name; } const char* HudGauge::getCustomGaugeText() { return custom_text.c_str(); } void HudGauge::setGaugeCoords(int _x, int _y) { position[0] = _x; position[1] = _y; } void HudGauge::setGaugeFrame(int frame_offset) { if (frame_offset < 0 ||frame_offset > custom_frame.num_frames) { return; } custom_frame_offset = frame_offset; } void HudGauge::updateCustomGaugeText(const char* txt) { if(!custom_gauge) { return; } custom_text = txt; } void HudGauge::updateCustomGaugeText(const SCP_string& txt) { if(!custom_gauge) { return; } custom_text = txt; } void HudGauge::setFont() { font::set_font(font_num); } void HudGauge::setGaugeColor(int bright_index) { int alpha; // if we're drawing it as bright if(bright_index != HUD_C_NONE){ switch(bright_index){ case HUD_C_DIM: alpha = HUD_contrast ? HUD_NEW_ALPHA_DIM_HI : HUD_NEW_ALPHA_DIM; gr_init_alphacolor(&gauge_color, gauge_color.red, gauge_color.green, gauge_color.blue, alpha); break; case HUD_C_NORMAL: alpha = HUD_contrast ? HUD_NEW_ALPHA_NORMAL_HI : HUD_NEW_ALPHA_NORMAL; gr_init_alphacolor(&gauge_color, gauge_color.red, gauge_color.green, gauge_color.blue, alpha); break; case HUD_C_BRIGHT: alpha = HUD_contrast ? HUD_NEW_ALPHA_BRIGHT_HI : HUD_NEW_ALPHA_BRIGHT; gr_init_alphacolor(&gauge_color, gauge_color.red, gauge_color.green, gauge_color.blue, alpha); break; // intensity default: Assert((bright_index >= 0) && (bright_index < HUD_NUM_COLOR_LEVELS)); if(bright_index < 0){ bright_index = 0; } if(bright_index >= HUD_NUM_COLOR_LEVELS){ bright_index = HUD_NUM_COLOR_LEVELS - 1; } // alpha = 255 - (255 / (bright_index + 1)); // alpha = (int)((float)alpha * 1.5f); int level = 255 / (HUD_NUM_COLOR_LEVELS); alpha = level * bright_index; if(alpha > 255){ alpha = 255; } if(alpha < 0){ alpha = 0; } gr_init_alphacolor(&gauge_color, gauge_color.red, gauge_color.green, gauge_color.blue, alpha); break; } } else { switch(maybeFlashSexp()) { case 0: alpha = HUD_contrast ? HUD_NEW_ALPHA_DIM_HI : HUD_NEW_ALPHA_DIM; gr_init_alphacolor(&gauge_color, gauge_color.red, gauge_color.green, gauge_color.blue, alpha); break; case 1: alpha = HUD_contrast ? HUD_NEW_ALPHA_BRIGHT_HI : HUD_NEW_ALPHA_BRIGHT; gr_init_alphacolor(&gauge_color, gauge_color.red, gauge_color.green, gauge_color.blue, alpha); break; default: alpha = HUD_contrast ? HUD_NEW_ALPHA_NORMAL_HI : HUD_NEW_ALPHA_NORMAL; gr_init_alphacolor(&gauge_color, gauge_color.red, gauge_color.green, gauge_color.blue, alpha); } } gr_set_color_fast(&gauge_color); } bool HudGauge::isCustom() { return custom_gauge; } int HudGauge::getBaseWidth() { return base_w; } int HudGauge::getBaseHeight() { return base_h; } float HudGauge::getAspectQuotient() { return aspect_quotient; } int HudGauge::getConfigType() { //return gauge_type; return gauge_config; } int HudGauge::getObjectType() { return gauge_object; } void HudGauge::lockConfigColor(bool lock) { lock_color = lock; } void HudGauge::sexpLockConfigColor(bool lock) { sexp_lock_color = lock; } void HudGauge::updateColor(int r, int g, int b, int a) { if(sexp_lock_color || lock_color) return; gr_init_alphacolor(&gauge_color, r, g, b, a); } const color& HudGauge::getColor() { return gauge_color; } void HudGauge::updateActive(bool show) { active = show; } void HudGauge::initRenderStatus(bool do_render) { off_by_default = !do_render; } void HudGauge::initChase_view_only(bool chase_view_only) { only_render_in_chase_view = chase_view_only; } void HudGauge::initCockpit_view_choice(int cockpit_view_choice) { render_for_cockpit_toggle = cockpit_view_choice; } bool HudGauge::isOffbyDefault() { return off_by_default; } bool HudGauge::isActive() { return active && !sexp_override; } void HudGauge::updateSexpOverride(bool sexp) { sexp_override = sexp; } void HudGauge::updatePopUp(bool pop_up_flag) { pop_up = pop_up_flag; } void HudGauge::startPopUp(int time) { //Assert(gauge_index >=0 && gauge_index < NUM_HUD_GAUGES); if ( !pop_up ) { return; } popup_timer = timestamp(time); } int HudGauge::popUpActive() { //Assert(gauge_index >=0 && gauge_index < NUM_HUD_GAUGES); if ( !pop_up ) { return 0; } if ( !timestamp_elapsed(popup_timer) ) { return 1; } else { return 0; } } void HudGauge::resetTimers() { flash_duration = timestamp(1); flash_next = timestamp(1); flash_status = false; popup_timer = timestamp(1); } void HudGauge::startFlashSexp() { flash_duration = timestamp(HUD_GAUGE_FLASH_DURATION); flash_next = timestamp(1); flash_status = false; } bool HudGauge::flashExpiredSexp() { if(timestamp_elapsed(flash_duration)) { return true; } return false; } int HudGauge::maybeFlashSexp() { if ( !timestamp_elapsed(flash_duration) ) { if ( timestamp_elapsed(flash_next) ) { flash_next = timestamp(HUD_GAUGE_FLASH_INTERVAL); // toggle between default and bright frames flash_status = !flash_status; } return (int)flash_status; } return -1; } void HudGauge::preprocess() { } void HudGauge::onFrame(float /*frametime*/) { } void HudGauge::render(float /*frametime*/) { if(!custom_gauge) { return; } setGaugeColor(); if( !custom_text.empty() ) { char *text = new char[custom_text.size()+1]; strcpy(text, custom_text.c_str()); hud_num_make_mono(text, font_num); renderString(position[0] + textoffset_x, position[1] + textoffset_y, text); delete[] text; } if(custom_frame.first_frame > -1) { renderBitmap(custom_frame.first_frame + custom_frame_offset, position[0], position[1]); } } void HudGauge::renderString(int x, int y, const char *str) { int nx = 0, ny = 0; if ( gr_screen.rendering_to_texture != -1 ) { gr_set_screen_scale(canvas_w, canvas_h, -1, -1, target_w, target_h, target_w, target_h, true); } else { if ( reticle_follow ) { nx = HUD_nose_x; ny = HUD_nose_y; gr_resize_screen_pos(&nx, &ny); gr_set_screen_scale(base_w, base_h); gr_unsize_screen_pos(&nx, &ny); } else { gr_set_screen_scale(base_w, base_h); } } if (HUD_shadows) { gr_set_color_fast(&Color_black); gr_string(x + nx + 1, y + ny + 1, str); gr_set_color_fast(&gauge_color); } gr_string(x + nx, y + ny, str); gr_reset_screen_scale(); } void HudGauge::renderString(int x, int y, int gauge_id, const char *str) { int nx = 0, ny = 0; if ( gr_screen.rendering_to_texture != -1 ) { gr_set_screen_scale(canvas_w, canvas_h, -1, -1, target_w, target_h, target_w, target_h, true); } else { if ( reticle_follow ) { nx = HUD_nose_x; ny = HUD_nose_y; gr_resize_screen_pos(&nx, &ny); gr_set_screen_scale(base_w, base_h); gr_unsize_screen_pos(&nx, &ny); } else { gr_set_screen_scale(base_w, base_h); } } if ( gauge_id > -2 ) { if (HUD_shadows) { gr_set_color_fast(&Color_black); emp_hud_string(x + nx + 1, y + ny + 1, gauge_id, str, GR_RESIZE_FULL); gr_set_color_fast(&gauge_color); } emp_hud_string(x + nx, y + ny, gauge_id, str, GR_RESIZE_FULL); } else { if (HUD_shadows) { gr_set_color_fast(&Color_black); gr_string(x + nx + 1, y + ny + 1, str); gr_set_color_fast(&gauge_color); } gr_string(x + nx, y + ny, str); } gr_reset_screen_scale(); } void HudGauge::renderStringAlignCenter(int x, int y, int area_width, const char *s) { int w, h; gr_get_string_size(&w, &h, s); renderString(x + ((area_width - w) / 2), y, s); } void HudGauge::renderPrintf(int x, int y, const char* format, ...) { char tmp[256] = ""; va_list args; // format the text va_start(args, format); vsnprintf(tmp, sizeof(tmp)-1, format, args); va_end(args); tmp[sizeof(tmp)-1] = '\0'; renderString(x, y, tmp); } void HudGauge::renderPrintf(int x, int y, int gauge_id, const char* format, ...) { char tmp[256] = ""; va_list args; // format the text va_start(args, format); vsnprintf(tmp, sizeof(tmp)-1, format, args); va_end(args); tmp[sizeof(tmp)-1] = '\0'; renderString(x, y, gauge_id, tmp); } void HudGauge::renderBitmapColor(int frame, int x, int y) { int nx = 0, ny = 0; if( !emp_should_blit_gauge() ) { return; } emp_hud_jitter(&x, &y); if ( gr_screen.rendering_to_texture != -1 ) { gr_set_screen_scale(canvas_w, canvas_h, -1, -1, target_w, target_h, target_w, target_h, true); } else { if ( reticle_follow ) { nx = HUD_nose_x; ny = HUD_nose_y; gr_resize_screen_pos(&nx, &ny); gr_set_screen_scale(base_w, base_h); gr_unsize_screen_pos(&nx, &ny); } else { gr_set_screen_scale(base_w, base_h); } } gr_set_bitmap(frame); gr_bitmap(x + nx, y + ny); gr_reset_screen_scale(); } void HudGauge::renderBitmap(int x, int y) { int nx = 0, ny = 0; if( !emp_should_blit_gauge() ) { return; } emp_hud_jitter(&x, &y); if ( gr_screen.rendering_to_texture != -1 ) { gr_set_screen_scale(canvas_w, canvas_h, -1, -1, target_w, target_h, target_w, target_h, true); } else { if ( reticle_follow ) { nx = HUD_nose_x; ny = HUD_nose_y; gr_resize_screen_pos(&nx, &ny); gr_set_screen_scale(base_w, base_h); gr_unsize_screen_pos(&nx, &ny); } else { gr_set_screen_scale(base_w, base_h); } } gr_aabitmap(x + nx, y + ny); gr_reset_screen_scale(); } void HudGauge::renderBitmap(int frame, int x, int y) { gr_set_bitmap(frame); renderBitmap(x, y); } void HudGauge::renderBitmapEx(int frame, int x, int y, int w, int h, int sx, int sy) { int nx = 0, ny = 0; if( !emp_should_blit_gauge() ) { return; } emp_hud_jitter(&x, &y); gr_set_bitmap(frame); if( gr_screen.rendering_to_texture != -1 ) { gr_set_screen_scale(canvas_w, canvas_h, -1, -1, target_w, target_h, target_w, target_h, true); } else { if ( reticle_follow ) { nx = HUD_nose_x; ny = HUD_nose_y; gr_resize_screen_pos(&nx, &ny); gr_set_screen_scale(base_w, base_h); gr_unsize_screen_pos(&nx, &ny); } else { gr_set_screen_scale(base_w, base_h); } } gr_aabitmap_ex(x + nx, y + ny, w, h, sx, sy); gr_reset_screen_scale(); } void HudGauge::renderLine(int x1, int y1, int x2, int y2) { int nx = 0, ny = 0; if ( gr_screen.rendering_to_texture != -1 ) { gr_set_screen_scale(canvas_w, canvas_h, -1, -1, target_w, target_h, target_w, target_h, true); } else { if ( reticle_follow ) { nx = HUD_nose_x; ny = HUD_nose_y; gr_resize_screen_pos(&nx, &ny); gr_set_screen_scale(base_w, base_h); gr_unsize_screen_pos(&nx, &ny); } else { gr_set_screen_scale(base_w, base_h); } } gr_line(x1+nx, y1+ny, x2+nx, y2+ny); gr_reset_screen_scale(); } void HudGauge::renderGradientLine(int x1, int y1, int x2, int y2) { int nx = 0, ny = 0; if ( gr_screen.rendering_to_texture != -1 ) { gr_set_screen_scale(canvas_w, canvas_h, -1, -1, target_w, target_h, target_w, target_h, true); } else { if(reticle_follow) { nx = HUD_nose_x; ny = HUD_nose_y; gr_resize_screen_pos(&nx, &ny); gr_set_screen_scale(base_w, base_h); gr_unsize_screen_pos(&nx, &ny); } else { gr_set_screen_scale(base_w, base_h); } } gr_gradient(x1+nx, y1+ny, x2+nx, y2+ny); gr_reset_screen_scale(); } void HudGauge::renderRect(int x, int y, int w, int h) { int nx = 0, ny = 0; if ( gr_screen.rendering_to_texture != -1 ) { gr_set_screen_scale(canvas_w, canvas_h, -1, -1, target_w, target_h, target_w, target_h, true); } else { if ( reticle_follow ) { nx = HUD_nose_x; ny = HUD_nose_y; gr_resize_screen_pos(&nx, &ny); gr_set_screen_scale(base_w, base_h); gr_unsize_screen_pos(&nx, &ny); } else { gr_set_screen_scale(base_w, base_h); } } gr_rect(x+nx, y+ny, w, h); gr_reset_screen_scale(); } void HudGauge::renderCircle(int x, int y, int diameter, bool filled) { int nx = 0, ny = 0; if ( gr_screen.rendering_to_texture != -1 ) { gr_set_screen_scale(canvas_w, canvas_h, -1, -1, target_w, target_h, target_w, target_h, true); } else { if ( reticle_follow ) { nx = HUD_nose_x; ny = HUD_nose_y; gr_resize_screen_pos(&nx, &ny); gr_set_screen_scale(base_w, base_h); gr_unsize_screen_pos(&nx, &ny); } else { gr_set_screen_scale(base_w, base_h); } } if (filled) { gr_circle(x + nx, y + ny, diameter); } else { gr_unfilled_circle(x + nx, y + ny, diameter); } gr_reset_screen_scale(); } void HudGauge::setClip(int x, int y, int w, int h) { int hx = 0; int hy = 0; if ( gr_screen.rendering_to_texture != -1 ) { gr_set_screen_scale(canvas_w, canvas_h, -1, -1, target_w, target_h, target_w, target_h, true); hx = display_offset_x; hy = display_offset_y; gr_resize_screen_pos(&x, &y); hx += x; hy += y; gr_unsize_screen_pos(&hx, &hy); gr_set_clip(hx, hy, w, h); } else { if (reticle_follow) { hx += HUD_nose_x; hy += HUD_nose_y; gr_resize_screen_pos(&hx, &hy); gr_set_screen_scale(base_w, base_h); gr_unsize_screen_pos(&hx, &hy); } else { gr_set_screen_scale(base_w, base_h); } x += hx; y += hy; gr_resize_screen_pos(&x, &y, &w, &h); gr_set_clip(x, y, w, h, GR_RESIZE_NONE); } gr_reset_screen_scale(); } void HudGauge::resetClip() { int hx = 0, hy = 0; int w, h; if ( gr_screen.rendering_to_texture != -1 ) { gr_set_screen_scale(canvas_w, canvas_h, -1, -1, target_w, target_h, target_w, target_h, true); hx = display_offset_x; hy = display_offset_y; gr_unsize_screen_pos(&hx, &hy); w = canvas_w; h = canvas_h; gr_set_clip(hx, hy, w, h); } else { gr_resize_screen_pos(&hx, &hy); gr_set_screen_scale(base_w, base_h); w = gr_screen.max_w; h = gr_screen.max_h; // clip the screen based on the actual resolution gr_set_clip(hx, hy, w, h, GR_RESIZE_NONE); } gr_reset_screen_scale(); } void HudGauge::unsize(int *x, int *y) { gr_set_screen_scale(base_w, base_h); gr_unsize_screen_pos(x, y); gr_reset_screen_scale(); } void HudGauge::unsize(float *x, float *y) { gr_set_screen_scale(base_w, base_h); gr_unsize_screen_posf(x, y); gr_reset_screen_scale(); } void HudGauge::resize(int *x, int *y) { gr_set_screen_scale(base_w, base_h); gr_resize_screen_pos(x, y); gr_reset_screen_scale(); } void HudGauge::resize(float *x, float *y) { gr_set_screen_scale(base_w, base_h); gr_resize_screen_posf(x, y); gr_reset_screen_scale(); } void HudGauge::pageIn() { if(custom_gauge) { if(custom_frame.first_frame > -1 && custom_frame.num_frames > 0) { bm_page_in_aabitmap( custom_frame.first_frame, custom_frame.num_frames ); } } } void HudGauge::initialize() { //Reset text to default custom_text = default_text; custom_frame_offset = 0; sexp_lock_color = false; } bool HudGauge::canRender() { if (sexp_override) { return false; } if(hud_disabled_except_messages() && !message_gauge) { return false; } if (hud_disabled() && !hud_disabled_except_messages()) { return false; } if(!active) return false; if ( !(Game_detail_flags & DETAIL_FLAG_HUD) ) { return false; } if ((Viewer_mode & disabled_views)) { return false; } if ((Viewer_mode & (VM_CHASE)) == 0 && only_render_in_chase_view) { return false; } if (render_for_cockpit_toggle > 0) { if ((Viewer_mode & VM_CHASE) && (render_for_cockpit_toggle == 2)) { return true; } else if (Cockpit_active && (render_for_cockpit_toggle == 2)) { return false; } else if (!Cockpit_active && (render_for_cockpit_toggle == 1)) { return false; } } if(pop_up) { if(!popUpActive()) { return false; } } if (gauge_config == HUD_ETS_GAUGE) { if (Ships[Player_obj->instance].flags[Ship::Ship_Flags::No_ets]) { return false; } } return true; } void HudGauge::initCockpitTarget(const char* display_name, int _target_x, int _target_y, int _target_w, int _target_h, int _canvas_w, int _canvas_h) { if ( strlen(display_name) <= 0 ) { return; } target_x = _target_x; target_y = _target_y; strcpy_s(texture_target_fname, display_name); target_w = _target_w; target_h = _target_h; if ( _canvas_w > 0 && _canvas_h > 0 ) { canvas_w = _canvas_w; canvas_h = _canvas_h; } else { canvas_w = _target_w; canvas_h = _target_h; } } bool HudGauge::setupRenderCanvas(int render_target) { if (texture_target_fname[0] != '\0') { if ( render_target >= 0 && render_target == texture_target ) { return true; } } else { if ( render_target < 0 ) { return true; } } return false; } void HudGauge::setCockpitTarget(const cockpit_display *display) { if ( display == NULL ) { return; } if ( strcmp(texture_target_fname, display->name) != 0 ) { return; } if ( display->target < 0 ) { return; } texture_target = display->target; display_offset_x = display->offset[0] + target_x; display_offset_y = display->offset[1] + target_y; } void HudGauge::resetCockpitTarget() { texture_target = -1; } // ---------------------------------------------------------------------- /** * @brief Called each level to initialize HUD systems */ void HUD_init() { HUD_init_colors(); hud_init_msg_window(); hud_init_targeting(); hud_init_reticle(); hud_shield_level_init(); hud_targetbox_init_flash(); hud_escort_init(); hud_damage_popup_init(); hud_support_view_init(); hud_init_squadmsg(); // initialize the vars needed for squadmate messaging hud_objective_message_init(); hud_init_wingman_status_gauge(); hud_init_target_static(); hud_init_text_flash_gauge(); hud_stop_subspace_notify(); hud_stop_objective_notify(); hud_target_last_transmit_level_init(); throttle_sound_check_id = timestamp(THROTTLE_SOUND_CHECK_INTERVAL); HUD_abort_subspace_timer = 1; Hud_last_can_target = 1; Hud_can_target_timer = 1; last_percent_throttle = 0.0f; // default to high contrast in the nebula HUD_contrast = 0; HUD_draw = 1; HUD_disable_except_messages = 0; if(The_mission.flags[Mission::Mission_Flags::Fullneb]){ HUD_contrast = 1; } // reset to infinite Hud_max_targeting_range = 0; size_t j, num_gauges; // go through all HUD gauges and call their initialization functions for (auto it = Ship_info.cbegin(); it != Ship_info.cend(); ++it) { if(it->hud_enabled) { num_gauges = it->hud_gauges.size(); for(j = 0; j < num_gauges; j++) { it->hud_gauges[j]->initialize(); it->hud_gauges[j]->resetTimers(); it->hud_gauges[j]->updateSexpOverride(false); } } } num_gauges = default_hud_gauges.size(); for(j = 0; j < num_gauges; j++) { default_hud_gauges[j]->initialize(); default_hud_gauges[j]->resetTimers(); default_hud_gauges[j]->updateSexpOverride(false); } Script_system.OnStateDestroy.add(hud_scripting_close); } void hud_scripting_close(lua_State*) { // Clean up Lua references so that we don't have dangling references on to the lua state in the HUD gauges for (auto it = Ship_info.begin(); it != Ship_info.end(); ++it) { for (const auto& gauge : it->hud_gauges) { if (gauge->getObjectType() == HUD_OBJECT_SCRIPTING) { static_cast(gauge.get())->setRenderFunction(luacpp::LuaFunction()); } } } for (const auto& gauge : default_hud_gauges) { if (gauge->getObjectType() == HUD_OBJECT_SCRIPTING) { static_cast(gauge.get())->setRenderFunction(luacpp::LuaFunction()); } } } /** * @brief Do post mission cleanup of HUD */ void hud_level_close() { size_t j, num_gauges; for ( auto it = Ship_info.cbegin(); it != Ship_info.cend(); ++it ) { if(it->hud_enabled) { num_gauges = it->hud_gauges.size(); for(j = 0; j < num_gauges; j++) { it->hud_gauges[j]->resetCockpitTarget(); } } } } /** * @brief Delete all HUD gauge objects, for all ships */ void hud_close() { for (auto it = Ship_info.begin(); it != Ship_info.end(); ++it) { it->hud_gauges.clear(); } default_hud_gauges.clear(); } void hud_toggle_draw() { HUD_draw = !HUD_draw; } // Goober5000 void hud_set_draw(int draw) { HUD_draw = draw; } //WMC int hud_get_draw() { return HUD_draw; } // Goober5000 void hud_disable_except_messages(int disable) { HUD_disable_except_messages = disable; } // Goober5000 /** * @brief Like ::hud_disabled(), except messages are still drawn */ int hud_disabled_except_messages() { return HUD_disable_except_messages; } /** * @brief Checks if HUD disabled * @return !0 if HUD is disabled (ie no gauges are shown/usable), otherwise return 0 */ int hud_disabled() { return !HUD_draw; } /** * @brief Determine if we should popup the weapons gauge on the HUD. */ void hud_maybe_popup_weapons_gauge() { if ( hud_gauge_is_popup(HUD_WEAPONS_GAUGE) ) { ship_weapon *swp = &Player_ship->weapons; int i; for ( i = 0; i < swp->num_secondary_banks; i++ ) { if (ship_secondary_has_ammo(swp, i)) { int ms_till_fire = timestamp_until(swp->next_secondary_fire_stamp[i]); if ( ms_till_fire >= 1000 ) { hud_gauge_popup_start(HUD_WEAPONS_GAUGE, 2500); } } } } } /** * @brief Updates HUD systems each frame * * @details This function updates those parts of the HUD that are not dependant on the rendering of the HUD. */ void hud_update_frame(float /*frametime*/) { object *targetp; int can_target; update_throttle_sound(); hud_check_reticle_list(); hud_wingman_status_update(); // Check hotkey selections to see if any ships need to be removed hud_prune_hotkeys(); // Remove dead/departed ships from the escort list hud_escort_cull_list(); hud_escort_update_list(); hud_update_reticle( Player ); hud_shield_hit_update(); hud_update_weapon_flash(); hud_maybe_popup_weapons_gauge(); hud_update_objective_message(); hud_support_view_update(); message_training_update_frame(); // if emp is active we have to allow targeting by the "random emp" system // we will intercept player targeting requests in hud_sensors_ok() when checking key commands // DB - 8/24/98 can_target = hud_sensors_ok(Player_ship, 0); if(emp_active_local()){ can_target = 1; } if ( !can_target && Hud_last_can_target ) { Hud_can_target_timer = timestamp(1200); } Hud_last_can_target = can_target; if ( timestamp_elapsed(Hud_can_target_timer) ) { if ( (Player_ai->target_objnum != -1) && !can_target ){ Player_ai->target_objnum = -1; } } // if there is no target, check if auto-targeting is enabled, and select new target int retarget = 0; int retarget_turret = 0; if (Player_ai->target_objnum == -1){ retarget = 1; } else if (Objects[Player_ai->target_objnum].type == OBJ_SHIP) { if (Ships[Objects[Player_ai->target_objnum].instance].flags[Ship::Ship_Flags::Dying]){ if (timestamp_elapsed(Ships[Objects[Player_ai->target_objnum].instance].final_death_time)) { retarget = 1; } } } // check if big ship and currently selected subsys is turret and turret is dead // only do this is not retargeting if ((!retarget) && (Player_ai->target_objnum != -1)) { if (Objects[Player_ai->target_objnum].type == OBJ_SHIP) { if ( !(Ships[Objects[Player_ai->target_objnum].instance].flags[Ship::Ship_Flags::Dying]) ) { if ( Ship_info[Ships[Objects[Player_ai->target_objnum].instance].ship_info_index].is_big_or_huge() ) { ship_subsys *ss = Player_ai->targeted_subsys; if (ss != NULL) { if ((ss->system_info->type == SUBSYSTEM_TURRET) && (ss->current_hits == 0)) { retarget_turret = 1; } } } } } } if ( retarget && can_target ) { Player_ai->current_target_is_locked = 0; if ( Players[Player_num].flags & PLAYER_FLAGS_AUTO_TARGETING ) { Player_ai->target_objnum = -1; hud_target_auto_target_next(); } } if (retarget_turret && can_target) { Assert(!retarget); void hud_update_closest_turret(); hud_update_closest_turret(); } // check to see if we are in messaging mode. If so, send the key to the code // to deal with the message. hud_sqaudmsg_do_frame will return 0 if the key // wasn't used in messaging mode, otherwise 1. In the event the key was used, // return immediately out of this function. if ( Players[Player_num].flags & PLAYER_FLAGS_MSG_MODE ) { hud_squadmsg_do_frame(); } if (Player_ai->target_objnum == -1) { Player->target_is_dying = -1; // according to comments elsewhere in the code, set to -1 when no target hud_target_change_check(); return; } targetp = &Objects[Player_ai->target_objnum]; if ( Player_ai->targeted_subsys != NULL ) { vec3d target_pos; get_subsystem_world_pos(targetp, Player_ai->targeted_subsys, &target_pos); // get new distance of current target Player_ai->current_target_distance = vm_vec_dist_quick(&target_pos,&Player_obj->pos); float shield_strength = Player_ai->targeted_subsys->current_hits/Player_ai->targeted_subsys->max_hits * 100.0f; int screen_integrity = (int)std::lround(shield_strength); if ( screen_integrity < 0 ) { screen_integrity = 0; } if ( screen_integrity == 0 ) { if ( shield_strength > 0 ) { screen_integrity = 1; } } // Goober5000 - don't flash if this subsystem can't be destroyed if ( ship_subsys_takes_damage(Player_ai->targeted_subsys) ) { if ( screen_integrity <= 0 ){ hud_targetbox_start_flash(TBOX_FLASH_SUBSYS); // need to flash 0% continuously } } } else { Player_ai->current_target_distance = hud_find_target_distance(targetp,Player_obj); } // moved past the if() block so that the target tracker has the current frame's target distance hud_target_change_check(); bool stop_targeting_this_thing = false; // check to see if the target is still alive if ( targetp->flags[Object::Object_Flags::Should_be_dead] ) { stop_targeting_this_thing = true; } Player->target_is_dying = FALSE; ship *target_shipp = NULL; if ( targetp->type == OBJ_SHIP ) { Assert ( targetp->instance >=0 && targetp->instance < MAX_SHIPS ); target_shipp = &Ships[targetp->instance]; Player->target_is_dying = target_shipp->flags[Ship::Ship_Flags::Dying]; // If it is warping out (or exploded), turn off targeting if ( target_shipp->flags[Ship::Ship_Flags::Depart_warp] || target_shipp->flags[Ship::Ship_Flags::Exploded] ) { stop_targeting_this_thing = true; } } // Check if can still be seen in Nebula if ( hud_target_invalid_awacs(targetp) ) { stop_targeting_this_thing = true; } // If this was found to be something we shouldn't // target anymore, just remove it if ( stop_targeting_this_thing ) { Player_ai->target_objnum = -1; Player_ai->targeted_subsys = NULL; hud_stop_looped_locking_sounds(); } if (Player->target_is_dying) { hud_stop_looped_locking_sounds(); if ( Players[Player_num].flags & PLAYER_FLAGS_AUTO_TARGETING ) { Player_ai->target_objnum = -1; hud_target_auto_target_next(); } } // Switch to battle track when a targeted ship is hostile (it attacks you) and within BATTLE_START_MIN_TARGET_DIST if (targetp->type == OBJ_SHIP && Event_Music_battle_started == 0 ) { Assert( target_shipp != NULL ); // Goober5000 if (iff_x_attacks_y(target_shipp->team, Player_ship->team)) { float dist_to_target; dist_to_target = vm_vec_dist_quick(&targetp->pos, &Player_obj->pos); if (dist_to_target < BATTLE_START_MIN_TARGET_DIST) { // If the target has an AI class of none, it is a Cargo, NavBuoy or other non-aggressive // ship, so don't start the battle music if (stricmp(Ai_class_names[Ai_info[target_shipp->ai_index].ai_class], NOX("none")) != 0) event_music_battle_start(); } } } // Make sure that the player isn't targeting a 3rd stage local ssm if (Player_ai->target_objnum >= 0 && Objects[Player_ai->target_objnum].type == OBJ_WEAPON) { if (Weapons[Objects[Player_ai->target_objnum].instance].lssm_stage==3) { set_target_objnum( Player_ai, -1 ); hud_lock_reset(); } } // Since we need to reference the player's target integrity in several places this upcoming // frame, only calculate once here if ( target_shipp ) { Pl_target_integrity = get_hull_pct(targetp); } // Update cargo scanning hud_update_cargo_scan_sound(); if ( Viewer_mode & ( VM_EXTERNAL | VM_WARP_CHASE ) ) { Player->cargo_inspect_time = 0; player_stop_cargo_scan_sound(); } hud_update_target_static(); if ( (targetp->instance >=0) && (targetp->instance < MAX_SHIPS) ) { hud_update_ship_status(targetp); } } /** * @brief Draw white brackets around asteroids which has the AF_DRAW_BRACKETS flag set */ void hud_show_asteroid_brackets() { if ( hud_sensors_ok(Player_ship, 0) ) { asteroid_show_brackets(); } } /** * @brief Render gauges that need to be between a ::g3_start_frame() and a ::g3_end_frame() */ void hud_render_preprocess(float frametime) { Player->subsys_in_view = -1; hud_target_clear_display_list(); if ( (Game_detail_flags & DETAIL_FLAG_HUD) && (supernova_stage() >= SUPERNOVA_STAGE::HIT) ) { return; } if ( hud_disabled() ) { // if the hud is disabled, we still need to make sure that the indicators are properly handled hud_do_lock_indicators(flFrametime); return; } if ( Viewer_mode & ( VM_DEAD_VIEW ) ) { return; } if ( Viewer_mode & (VM_EXTERNAL | VM_WARP_CHASE | VM_PADLOCK_ANY ) ) { // If the player is warping out, don't draw the targeting gauges Assert(Player != NULL); if ( Player->control_mode != PCM_NORMAL ) { return; } } // process targeting data around any message sender hud_show_message_sender(); // if messages are disabled then skip everything else if ( hud_disabled_except_messages() ) { return; } // process navigation stuff hud_draw_navigation(); // process current selection set, if any hud_show_selection_set(); // process asteroid brackets if necessary hud_show_asteroid_brackets(); // process targeting data around the current target hud_show_targeting_gauges(frametime); // process brackets and distance to remote detonate missile hud_process_remote_detonate_missile(); // process any incoming missiles hud_process_homing_missiles(); } HudGaugeMissionTime::HudGaugeMissionTime(): HudGauge(HUD_OBJECT_MISSION_TIME, HUD_MISSION_TIME, false, false, (VM_EXTERNAL | VM_DEAD_VIEW | VM_WARP_CHASE | VM_PADLOCK_ANY | VM_OTHER_SHIP), 255, 255, 255) { } void HudGaugeMissionTime::initTextOffsets(int x, int y) { time_text_offsets[0] = x; time_text_offsets[1] = y; } void HudGaugeMissionTime::initValueOffsets(int x, int y) { time_val_offsets[0] = x; time_val_offsets[1] = y; } void HudGaugeMissionTime::initBitmaps(const char *fname) { time_gauge.first_frame = bm_load_animation(fname, &time_gauge.num_frames); if ( time_gauge.first_frame == -1 ) { Warning(LOCATION, "Could not load in ani: %s\n", fname); } } void HudGaugeMissionTime::pageIn() { bm_page_in_aabitmap(time_gauge.first_frame, time_gauge.num_frames ); } void HudGaugeMissionTime::render(float /*frametime*/) { float mission_time, time_comp; int minutes=0; int seconds=0; mission_time = f2fl(Missiontime) + (float)The_mission.HUD_timer_padding; // convert to seconds minutes=(int)(mission_time/60); seconds=(int)mission_time%60; setGaugeColor(); // blit background frame if ( time_gauge.first_frame >= 0 ) { renderBitmap(time_gauge.first_frame, position[0], position[1]); } // print out mission time in MM:SS format renderPrintf(position[0] + time_text_offsets[0], position[1] + time_text_offsets[1], NOX("%02d:%02d"), minutes, seconds); // display time compression as xN time_comp = f2fl(Game_time_compression); if ( time_comp < 1 ) { renderPrintf(position[0] + time_val_offsets[0], position[1] + time_val_offsets[1], /*XSTR( "x%.1f", 215), time_comp)*/ NOX("%.2f"), time_comp); } else { renderPrintf(position[0] + time_val_offsets[0], position[1] + time_val_offsets[1], XSTR( "x%.0f", 216), time_comp); } } /** * @brief Show supernova warning if there's a supernova coming */ void hud_maybe_display_supernova() { if (!supernova_active()) { return; } auto time_left = supernova_hud_time_left(); gr_set_color_fast(&Color_bright_red); if (Lcl_pl) { gr_printf(Supernova_coords[gr_screen.res][0], Supernova_coords[gr_screen.res][1], "Wybuch supernowej: %.2f s", time_left); } else { gr_printf(Supernova_coords[gr_screen.res][0], Supernova_coords[gr_screen.res][1], "Supernova Warning: %.2f s", time_left); } } /** * @brief Undertakes main HUD render. */ void hud_render_all() { int i; hud_render_gauges(); // start rendering cockpit dependent gauges if possible for ( i = 0; i < (int)Player_displays.size(); ++i ) { hud_render_gauges(i); } hud_clear_msg_buffer(); // set font back the way it was font::set_font(font::FONT1); } void hud_render_gauges(int cockpit_display_num) { size_t j, num_gauges; ship_info* sip = &Ship_info[Player_ship->ship_info_index]; int render_target = -1; if ( cockpit_display_num >= 0 ) { if ( sip->cockpit_model_num < 0 ) { return; } if ( !sip->hud_enabled ) { return; } render_target = ship_start_render_cockpit_display(cockpit_display_num); if ( render_target <= 0 ) { return; } } else { if( supernova_stage() >= SUPERNOVA_STAGE::HIT) { return; } } //Since we render things twice in VR mode, frametime needs to be halved for HUD, as the HUD uses the frametime to advance ANI's and crucially, the missile lock... float frametime = openxr_enabled() ? flFrametime * 0.5f : flFrametime; // Check if this ship has its own HUD gauges. if ( sip->hud_enabled ) { num_gauges = sip->hud_gauges.size(); for(j = 0; j < num_gauges; j++) { GR_DEBUG_SCOPE("Render HUD gauge"); // only preprocess gauges if we're not rendering to cockpit if ( cockpit_display_num < 0 ) { sip->hud_gauges[j]->preprocess(); } sip->hud_gauges[j]->onFrame(frametime); if ( !sip->hud_gauges[j]->setupRenderCanvas(render_target) ) { continue; } if ( !sip->hud_gauges[j]->canRender() ) { continue; } TRACE_SCOPE(tracing::RenderHUDGauge); sip->hud_gauges[j]->resetClip(); sip->hud_gauges[j]->setFont(); sip->hud_gauges[j]->render(frametime); } } else { num_gauges = default_hud_gauges.size(); for(j = 0; j < num_gauges; j++) { GR_DEBUG_SCOPE("Render HUD gauge"); default_hud_gauges[j]->preprocess(); default_hud_gauges[j]->onFrame(frametime); if ( !default_hud_gauges[j]->canRender() ) { continue; } TRACE_SCOPE(tracing::RenderHUDGauge); default_hud_gauges[j]->resetClip(); default_hud_gauges[j]->setFont(); default_hud_gauges[j]->render(frametime); } } if ( cockpit_display_num >= 0 ) { ship_end_render_cockpit_display(cockpit_display_num); if ( gr_screen.rendering_to_texture != -1 ) { // are we still are rendering to a texture at this point? uh oh. bm_set_render_target(-1); } } } /** * @brief Called when the game decides to stop all looping sounds * * @details This function will set the loop id's for the engine noises to -1, this will force any looping engine sounds to stop. */ void hud_stop_looped_engine_sounds() { if (Player_engine_snd_loop.isValid()) { snd_stop(Player_engine_snd_loop); Player_engine_snd_loop = sound_handle::invalid(); } } #define ZERO_PERCENT 0.01f #define ENGINE_MAX_VOL 1.0f #define ENGINE_MAX_PITCH 44100 /** * @brief If the throttle has changed, modify the sound * * @details Determine what engine sound to play, based upon the percentage throttle set. * If we're a multiplayer observer, stop any engine sounds from playing and return. */ void update_throttle_sound() { float percent_throttle; if((Game_mode & GM_MULTIPLAYER) && (Net_player->flags & NETINFO_FLAG_OBSERVER)){ if (Player_engine_snd_loop.isValid()) { snd_stop(Player_engine_snd_loop); Player_engine_snd_loop = sound_handle::invalid(); } return; } if ( timestamp_elapsed(throttle_sound_check_id) ) { auto enginesnd_id = ship_get_sound(Player_obj, GameSounds::ENGINE); if (!enginesnd_id.isValid()) { throttle_sound_check_id = timestamp(-1); // never time out return; } throttle_sound_check_id = timestamp(THROTTLE_SOUND_CHECK_INTERVAL); if ( object_get_gliding(Player_obj) ) { // Backslash percent_throttle = Player_obj->phys_info.linear_thrust.xyz.z; } else { percent_throttle = Player_obj->phys_info.fspeed / Ship_info[Ships[Player_obj->instance].ship_info_index].max_speed; } if (percent_throttle != last_percent_throttle || !Player_engine_snd_loop.isValid()) { if ( percent_throttle < ZERO_PERCENT ) { if (Player_engine_snd_loop.isValid()) { snd_stop(Player_engine_snd_loop); // Backslash - otherwise, long engine loops keep playing Player_engine_snd_loop = sound_handle::invalid(); } } else { if (!Player_engine_snd_loop.isValid()) { Player_engine_snd_loop = snd_play_looping( gamesnd_get_game_sound(enginesnd_id), 0.0f , -1, -1, percent_throttle * ENGINE_MAX_VOL, FALSE); } else { // The sound may have been trashed at the low-level if sound channel overflow. // TODO: implement system where certain sounds cannot be interrupted (priority?) if ( snd_is_playing(Player_engine_snd_loop) ) { snd_set_volume(Player_engine_snd_loop, percent_throttle * ENGINE_MAX_VOL); } else { Player_engine_snd_loop = sound_handle::invalid(); } } } } // end if (percent_throttle != last_percent_throttle) last_percent_throttle = percent_throttle; } // end if ( timestamp_elapsed(throttle_sound_check_id) ) } /** * @brief Called at the beginning of each level. Loads frame data in once, and initializes any damage * gauge specific data. */ void hud_damage_popup_init() { int i; Damage_flash_bright = 0; Damage_flash_timer = 1; for ( i = 0; i < SUBSYSTEM_MAX; i++ ) { Pl_hud_subsys_info[i].last_str = 1.0f; Pl_hud_subsys_info[i].flash_duration_timestamp = 1; Pl_hud_next_flash_timestamp = 1; Pl_hud_is_bright = 0; } } HudGaugeDamage::HudGaugeDamage(): HudGauge(HUD_OBJECT_DAMAGE, HUD_DAMAGE_GAUGE, false, false, (VM_EXTERNAL | VM_DEAD_VIEW | VM_WARP_CHASE | VM_PADLOCK_ANY | VM_OTHER_SHIP), 255, 255, 255) { } void HudGaugeDamage::initHeaderOffsets(int x, int y) { header_offsets[0] = x; header_offsets[1] = y; } void HudGaugeDamage::initHullIntegOffsets(int x, int y) { hull_integ_offsets[0] = x; hull_integ_offsets[1] = y; } void HudGaugeDamage::initHullIntegValueOffsetX(int x) { hull_integ_val_offset_x = x; } void HudGaugeDamage::initMiddleFrameStartOffsetY(int y) { middle_frame_start_offset_y = y; } void HudGaugeDamage::initSubsysIntegStartOffsets(int x, int y) { subsys_integ_start_offsets[0] = x; subsys_integ_start_offsets[1] = y; } void HudGaugeDamage::initSubsysIntegValueOffsetX(int x) { subsys_integ_val_offset_x = x; } void HudGaugeDamage::initBottomBgOffset(int offset) { bottom_bg_offset = offset; } void HudGaugeDamage::initLineHeight(int h) { line_h = h; } void HudGaugeDamage::initDisplayValue(bool value) { always_display = value; } void HudGaugeDamage::initBitmaps(const char *fname_top, const char *fname_middle, const char *fname_bottom) { damage_top.first_frame = bm_load_animation(fname_top, &damage_top.num_frames); if ( damage_top.first_frame == -1 ) { Warning(LOCATION, "Could not load in the ani: %s\n", fname_top); } damage_middle.first_frame = bm_load_animation(fname_middle, &damage_middle.num_frames); if ( damage_middle.first_frame == -1 ) { Warning(LOCATION, "Could not load in the ani: %s\n", fname_middle); } damage_bottom.first_frame = bm_load_animation(fname_bottom, &damage_bottom.num_frames); if ( damage_bottom.first_frame == -1 ) { Warning(LOCATION, "Could not load in the ani: %s\n", fname_bottom); } } void HudGaugeDamage::initialize() { Damage_flash_bright = false; Damage_flash_timer = timestamp(1); next_flash = timestamp(1); flash_status = false; HudGauge::initialize(); } void HudGaugeDamage::pageIn() { bm_page_in_aabitmap(damage_top.first_frame, damage_top.num_frames); bm_page_in_aabitmap(damage_middle.first_frame, damage_middle.num_frames); bm_page_in_aabitmap(damage_bottom.first_frame, damage_bottom.num_frames); } void HudGaugeDamage::render(float /*frametime*/) { model_subsystem *psub; ship_subsys *pss; int screen_integrity, num, best_str, best_index; float strength, shield, integrity; hud_subsys_damage hud_subsys_list[SUBSYSTEM_MAX]; if ( damage_top.first_frame == -1 ) { return; } if ( (The_mission.game_type & MISSION_TYPE_TRAINING) && Training_message_visible ){ return; } if ( timestamp_elapsed(Damage_flash_timer) ) { Damage_flash_timer = timestamp(DAMAGE_FLASH_TIME); Damage_flash_bright = !Damage_flash_bright; } num = 0; for ( pss = GET_FIRST(&Player_ship->subsys_list); pss !=END_OF_LIST(&Player_ship->subsys_list); pss = GET_NEXT(pss) ) { psub = pss->system_info; strength = ship_get_subsystem_strength(Player_ship, psub->type); if ( always_display || strength < 1 ) { // display the actual health of this specific subsystem --wookieejedi if (pss->max_hits > 0) { // get percentage if this subsystem has hitpoints to begin with screen_integrity = fl2i((pss->current_hits / pss->max_hits) * 100); } else { // subsystems with no starting hitpoints can never be damaged, so they are always full health screen_integrity = 1; } if ( screen_integrity == 0 ) { if ( strength > 0 ) { screen_integrity = 1; } } if (strlen(psub->alt_dmg_sub_name)) hud_subsys_list[num].name = psub->alt_dmg_sub_name; else { hud_subsys_list[num].name = ship_subsys_get_name(pss); } hud_subsys_list[num].str = screen_integrity; hud_subsys_list[num].type = psub->type; num++; if ( strength < Pl_hud_subsys_info[psub->type].last_str ) { Pl_hud_subsys_info[psub->type].flash_duration_timestamp = timestamp(SUBSYS_DAMAGE_FLASH_DURATION); } Pl_hud_subsys_info[psub->type].last_str = strength; // Don't display more than the max number of damaged subsystems. if (num >= SUBSYSTEM_MAX) { break; } } } // Build a list of damage values to display and then actually display them in a second pass // This allows to hide the gauge when there is no damage SCP_vector info_lines; auto sx = position[0] + subsys_integ_start_offsets[0]; auto sy = position[1] + subsys_integ_start_offsets[1]; auto bx = position[0]; auto by = position[1] + middle_frame_start_offset_y; int type; for ( int i = 0; i < num; i++ ) { best_str = 1000; best_index = -1; for ( int j = 0; j < num-i; j++ ) { if ( hud_subsys_list[j].str < best_str ) { best_str = hud_subsys_list[j].str; best_index = j; } } Assert(best_index >= 0); Assert(best_str >= 0); DamageInfo info; info.draw_background = true; info.background_x = bx; info.background_y = by; type = hud_subsys_list[best_index].type; if ( !timestamp_elapsed( Pl_hud_subsys_info[type].flash_duration_timestamp ) ) { if ( timestamp_elapsed(next_flash) ) { flash_status = !flash_status; next_flash = timestamp(SUBSYS_DAMAGE_FLASH_INTERVAL); } if ( flash_status ) { int alpha_color = MIN(HUD_COLOR_ALPHA_MAX,HUD_color_alpha+HUD_BRIGHT_DELTA); info.bright_index = alpha_color; } } // Draw the text if ( best_str < 30 ) { if ( best_str <= 0 ) { if ( Damage_flash_bright ) { info.color_override = &Color_bright_red; } else { info.color_override = &Color_red; } } else { info.color_override = &Color_red; } } const char *n_firstline; n_firstline = strrchr(hud_subsys_list[best_index].name, '|'); if (n_firstline) { // Print only the last line n_firstline++; info.name = n_firstline; } else { char temp_name[NAME_LENGTH]; strcpy_s(temp_name, hud_subsys_list[best_index].name); hud_targetbox_truncate_subsys_name(temp_name); info.name = temp_name; } char buf[128]; sprintf(buf, XSTR( "%d%%", 219), best_str); hud_num_make_mono(buf, font_num); int w, h; gr_get_string_size(&w, &h, buf); info.value_x = position[0] + subsys_integ_val_offset_x - w; info.value_y = sy; info.strength = best_str; info.name_x = sx; info.name_y = sy; by += line_h; sy += line_h; info_lines.push_back(info); // Remove it from hud_subsys_list if ( best_index < (num-i-1) ) { hud_subsys_list[best_index] = hud_subsys_list[num-i-1]; } } hud_get_target_strength(Player_obj, &shield, &integrity); screen_integrity = fl2i(integrity*100); // Show hull integrity if it's below 100% or if a subsystem is damaged // The second case is just to make the display look complete // The third case is there to make the gauge appear only if needed if the right option is set if ( always_display || screen_integrity < 100 || !info_lines.empty() ) { DamageInfo info; info.name = XSTR( "Hull Integrity", 220); if ( screen_integrity == 0 ) { screen_integrity = 1; } info.strength = screen_integrity; char buf[128]; sprintf(buf, XSTR( "%d%%", 219), screen_integrity); hud_num_make_mono(buf, font_num); int w, h; gr_get_string_size(&w, &h, buf); if ( screen_integrity < 30 ) { info.color_override = &Color_red; } info.name_x = position[0] + hull_integ_offsets[0]; info.name_y = position[1] + hull_integ_offsets[1]; info.value_x = position[0] + hull_integ_val_offset_x - w; info.value_y = position[1] + hull_integ_offsets[1]; // Insert at the top since hull is always first info_lines.insert(info_lines.begin(), info); } if (info_lines.empty()) { // Nothing to display, return before dawing anything return; } setGaugeColor(); // Draw the top of the damage pop-up renderBitmap(damage_top.first_frame, position[0], position[1]); renderString(position[0] + header_offsets[0], position[1] + header_offsets[1], XSTR( "damage", 218)); // These variables keep track of where the background was drawn last so we can draw the bottom correctly int last_bx = position[0]; int last_by = position[1] + middle_frame_start_offset_y; for (auto& line : info_lines) { if (line.draw_background) { renderBitmap(damage_middle.first_frame, line.background_x, line.background_y); last_bx = line.background_x; last_by = line.background_y + line_h; // Add line_h here so that the footer is properly aligned } char buf[128]; sprintf(buf, XSTR( "%d%%", 219), line.strength); hud_num_make_mono(buf, font_num); if (line.color_override != nullptr) { gr_set_color_fast(line.color_override); } else { setGaugeColor(line.bright_index); } renderString(line.name_x, line.name_y, line.name.c_str()); renderString(line.value_x, line.value_y, buf); setGaugeColor(); } setGaugeColor(); renderBitmap(damage_bottom.first_frame, last_bx, last_by + bottom_bg_offset); } /** * @brief Initialise the members of the ::hud_anim struct to default values */ void hud_anim_init(hud_anim *ha, int sx, int sy, const char *filename) { ha->first_frame = -1; ha->num_frames = 0; ha->total_time = 0.0f; ha->time_elapsed = 0.0f; ha->sx = sx; ha->sy = sy; strcpy_s(ha->filename, filename); } /** * @brief Initialise the members of the ::hud_frames struct to default values */ void hud_frames_init(hud_frames *hf) { hf->first_frame = -1; hf->num_frames = 0; } /** * @brief Load a ::hud_anim * @return If successful return 0, otherwise return -1 */ int hud_anim_load(hud_anim *ha) { int fps; ha->first_frame = bm_load_animation(ha->filename, &ha->num_frames, &fps); // Goober5000 - try to bypass the Volition bug if ( (ha->first_frame == -1) && !stricmp(ha->filename, "FadeIconS-FreighterCW") ) { ha->first_frame = bm_load_animation("FadeIconS-FreighterWC", &ha->num_frames, &fps); } if ( ha->first_frame == -1 ) { Warning(LOCATION, "Couldn't load hud animation for file '%s'", ha->filename); return -1; } Assert(fps != 0); ha->total_time = i2fl(ha->num_frames)/fps; return 0; } /** * @brief Render out a frame of a hud or briefing animation, based on how much time has elapsed * @note targetbox static was not implemented by :v:, also used for briefing icons & hud lock icons * * @param ha Pointer to ::hud_anim info * @param frametime Seconds elapsed since last frame * @param draw_alpha Draw bitmap as alpha-bitmap (default 0) * @param loop Anim should loop (default 1) * @param hold_last Should last frame be held (default 0) * @param reverse Play animation in reverse (default 0) * @param resize_mode Resize for non-standard resolutions * @param mirror Mirror along y-axis so icon points left instead of right * @param scale_factor Multiplier for the width and height * * @returns 1 on success, 0 on failure */ int hud_anim_render(hud_anim *ha, float frametime, int draw_alpha, int loop, int hold_last, int reverse, int resize_mode, bool mirror, float scale_factor) { int framenum; if ( ha->num_frames <= 0 ) { if ( hud_anim_load(ha) == -1 ) return 0; } ha->time_elapsed += frametime; if ( ha->time_elapsed > ha->total_time && loop == 0 && hold_last == 0) { return 0; } // Note; total_time for hud_anim is derived only from the fps, no need to pass it in framenum = bm_get_anim_frame(ha->first_frame, ha->time_elapsed, 0.0f, loop != 0); if (reverse) { framenum = (ha->num_frames-1) - framenum; } // Blit the bitmap for this frame if(emp_should_blit_gauge()){ gr_set_bitmap(ha->first_frame + framenum); if ( draw_alpha ){ gr_aabitmap(ha->sx, ha->sy, resize_mode, mirror, scale_factor); } else { gr_bitmap(ha->sx, ha->sy, resize_mode); } } return 1; } /** * @brief Convert a number string to use mono-spaced 1 character */ void hud_num_make_mono(char *num_str, int font_num) { font::FSFont* fsFont = font::get_font(font_num); if (fsFont->getType() != font::VFNT_FONT) { // Only old volition fonts need this return; } ubyte sc; sc = lcl_get_font_index(font_num); if (sc == 0) { // specified font has no mono-spaced 1, make do with non-mono-spaced 1 return; } size_t len = strlen(num_str); for (size_t i = 0; i < len; i++ ) { if ( num_str[i] == '1' ) { num_str[i] = (char)(sc + 1); } } } /** * @brief Flashing text gauge */ void hud_init_text_flash_gauge() { strcpy_s(Hud_text_flash, ""); Hud_text_flash_timer = timestamp(0); Hud_text_flash_interval = 0; } void hud_start_text_flash(const char *txt, int t, int interval) { // bogus if(txt == NULL){ strcpy_s(Hud_text_flash, ""); return; } // HACK. don't override EMP if its still going :) // An additional hack: don't interrupt other warnings if this is a missile launch alert (Swifty) if(!timestamp_elapsed(Hud_text_flash_timer)) if(!stricmp(Hud_text_flash, XSTR("Emp", 1670)) || !stricmp(txt, XSTR("Launch", 1507))) return; strncpy(Hud_text_flash, txt, 500); Hud_text_flash_timer = timestamp(t); Hud_text_flash_interval = interval; } HudGaugeTextWarnings::HudGaugeTextWarnings(): HudGauge(HUD_OBJECT_TEXT_WARNINGS, HUD_TEXT_FLASH, true, false, (VM_EXTERNAL | VM_DEAD_VIEW | VM_WARP_CHASE | VM_PADLOCK_ANY | VM_OTHER_SHIP), 255, 255, 255) { } void HudGaugeTextWarnings::initialize() { next_flash = timestamp(0); flash_flags = false; HudGauge::initialize(); } int HudGaugeTextWarnings::maybeTextFlash() { if ( !timestamp_elapsed(Hud_text_flash_timer) ) { if ( timestamp_elapsed(next_flash) ) { next_flash = timestamp(Hud_text_flash_interval); // toggle between default and bright frames flash_flags = !flash_flags; } } return flash_flags; } void HudGaugeTextWarnings::render(float /*frametime*/) { // note: Hud_text_flash globally allocated, address can't be NULL if ( timestamp_elapsed(Hud_text_flash_timer) || Hud_text_flash[0] == '\0' ) { return; } int w, h; // string size gr_get_string_size(&w, &h, Hud_text_flash); // set color if(maybeTextFlash()){ setGaugeColor(HUD_C_DIM); // draw the box renderRect( (int)( (float)position[0] - (float)w / 2.0f - 1.0f), (int)((float)position[1] - 1.0f), w + 2, h + 1); } // string setGaugeColor(HUD_C_BRIGHT); renderString(fl2i((float)position[0] - ((float)w / 2.0f)), position[1], Hud_text_flash); } HudGaugeKills::HudGaugeKills(): HudGauge(HUD_OBJECT_KILLS, HUD_KILLS_GAUGE, false, false, (VM_EXTERNAL | VM_DEAD_VIEW | VM_WARP_CHASE | VM_PADLOCK_ANY | VM_OTHER_SHIP), 255, 255, 255) { } void HudGaugeKills::initBitmaps(const char *fname) { Kills_gauge.first_frame = bm_load_animation(fname, &Kills_gauge.num_frames); if ( Kills_gauge.first_frame == -1 ) { Warning(LOCATION, "Could not load in the ani: %s\n", fname); } } void HudGaugeKills::initTextOffsets(int x, int y) { text_offsets[0] = x; text_offsets[1] = y; } void HudGaugeKills::initTextValueOffsets(int x, int y) { text_value_offsets[0] = x; text_value_offsets[1] = y; } void HudGaugeKills::pageIn() { bm_page_in_aabitmap(Kills_gauge.first_frame, Kills_gauge.num_frames); } /** * @brief Display the kills gauge on the HUD */ void HudGaugeKills::render(float /*frametime*/) { if ( Kills_gauge.first_frame < 0 ) { return; } setGaugeColor(); // Draw background renderBitmap(Kills_gauge.first_frame, position[0], position[1]); renderString(position[0] + text_offsets[0], position[1] + text_offsets[1], XSTR( "kills:", 223)); // Display how many kills the player has so far char num_kills_string[32]; int w,h; if ( !Player ) { Int3(); return; } sprintf(num_kills_string, "%d", Player->stats.m_kill_count_ok); gr_get_string_size(&w, &h, num_kills_string); renderString(position[0]+text_value_offsets[0]-w, position[1]+text_value_offsets[1], num_kills_string); } HudGaugeLag::HudGaugeLag(): HudGauge(HUD_OBJECT_LAG, HUD_LAG_GAUGE, false, false, (VM_EXTERNAL | VM_DEAD_VIEW | VM_WARP_CHASE | VM_PADLOCK_ANY | VM_OTHER_SHIP), 255, 255, 255) { } void HudGaugeLag::initBitmaps(const char *fname) { Netlag_icon.first_frame = bm_load_animation(fname, &Netlag_icon.num_frames); if ( Netlag_icon.first_frame == -1 ) { Warning(LOCATION, "Could not load in the netlag ani: %s\n", fname); } } void HudGaugeLag::pageIn() { bm_page_in_aabitmap(Netlag_icon.first_frame, Netlag_icon.num_frames); } void HudGaugeLag::startFlashLag(int duration) { flash_timer[0] = timestamp(duration); } bool HudGaugeLag::maybeFlashLag(bool flash_fast) { bool draw_bright = false; if(!timestamp_elapsed(flash_timer[0])) { if(timestamp_elapsed(flash_timer[1])) { if(flash_fast) { flash_timer[1] = timestamp(fl2i(TBOX_FLASH_INTERVAL/2.0f)); } else { flash_timer[1] = timestamp(TBOX_FLASH_INTERVAL); } flash_flag = !flash_flag; } if(flash_flag) { draw_bright = true; } } return draw_bright; } void HudGaugeLag::render(float /*frametime*/) { int lag_status; if( !(Game_mode & GM_MULTIPLAYER) ){ return; } if ( Netlag_icon.first_frame == -1 ) { Int3(); return; } lag_status = multi_query_lag_status(); switch(lag_status) { case 0: // Draw the net lag icon flashing startFlashLag(); if(maybeFlashLag()){ setGaugeColor(HUD_C_BRIGHT); } else { setGaugeColor(); } renderBitmap(Netlag_icon.first_frame, position[0], position[1]); break; case 1: // Draw the disconnected icon flashing fast if(maybeFlashLag(true)){ setGaugeColor(HUD_C_BRIGHT); } else { setGaugeColor(); } renderBitmap(Netlag_icon.first_frame+1, position[0], position[1]); break; default: // Nothing to draw return; } } /** * @brief Called at mission start to init data, and load support view bitmap if required */ void hud_support_view_init() { Hud_support_view_fade = 1; Hud_support_obj_sig = -1; Hud_support_target_sig = -1; Hud_support_objnum = -1; Hud_support_view_active = 0; Hud_support_view_abort = 0; } /** * @brief Start displaying the support view pop-up. This will remain up until ::hud_support_view_stop() is called. */ void hud_support_view_start() { Hud_support_view_active = 1; Hud_support_view_fade = 1; } /** * @brief Stop displaying the support view pop-up. * * @param stop_now If set, stop now, otherwise in 2 seconds. */ void hud_support_view_stop(int stop_now) { if ( stop_now ) { Hud_support_view_active = 0; Hud_support_view_fade = 1; Hud_support_view_abort = 0; } else { Hud_support_view_fade = timestamp(2000); } Hud_support_obj_sig = -1; Hud_support_target_sig = -1; Hud_support_objnum = -1; } void hud_support_view_abort() { hud_support_view_stop(0); Hud_support_view_abort = 1; } /** * @brief Get the number of seconds until repair ship will dock with ther player * @details mwa made this function more general purpose. Goober5000 made clearer * * @param docker_objp The object which is attempting to dock with the player * @return The number of seconds, 0 if already docked, -1 if error * * @todo This function is pretty stupid now. It just assumes the dockee is sitting still, and * the docker is moving directly to the dockee. */ int hud_get_dock_time( object *docker_objp ) { ai_info *aip; object *dockee_objp; float dist, rel_speed, docker_speed; vec3d rel_vel; aip = &Ai_info[Ships[docker_objp->instance].ai_index]; // Get the dockee object pointer if (aip->goal_objnum == -1) { // This can happen when you target a support ship as it warps in // just give a debug warning instead of a fault - taylor mprintf(("'aip->goal_objnum == -1' in hud_get_dock_time(), line %i\n", __LINE__)); return 0; } dockee_objp = &Objects[aip->goal_objnum]; // If the ship is docked, return 0 if ( dock_check_find_direct_docked_object(docker_objp, dockee_objp) ) return 0; vm_vec_sub(&rel_vel, &docker_objp->phys_info.vel, &dockee_objp->phys_info.vel); rel_speed = vm_vec_mag_quick(&rel_vel); dist = vm_vec_dist_quick(&dockee_objp->pos, &docker_objp->pos); docker_speed = docker_objp->phys_info.speed; if ( rel_speed <= docker_speed/2.0f) { // This means the player is moving away fast from the support ship. return (int) (dist/docker_speed); } else { float d1; float d = dist; float time = 0.0f; if (rel_speed < 20.0f) rel_speed = 20.0f; // When far away, use max speed, not current speed. Might not have sped up yet. if (d > 100.0f) { time += (d - 100.0f)/docker_objp->phys_info.max_vel.xyz.z; } // For mid-range, use current speed. if (d > 60.0f) { d1 = MIN(d, 100.0f); time += (d1 - 60.0f)/rel_speed; } // For nearby, ship will have to slow down a bit for docking maneuver. if (d > 30.0f) { d1 = MIN(d, 60.0f); time += (d1 - 30.0f)/5.0f; } // For very nearby, ship moves quite slowly. d1 = MIN(d, 30.0f); time += d1/7.5f; return fl2i(time); } } /** * @brief Locate the closest support ship which is trying to dock with player * * @param objnum Object number of player * @return Number of support ship, -1 if there is no support ship currently trying to dock */ int hud_support_find_closest( int objnum ) { ai_info *aip; object *objp; int i; objp = &Objects[objnum]; for (auto sop: list_range(&Ship_obj_list)) { if (Objects[sop->objnum].flags[Object::Object_Flags::Should_be_dead]) continue; if ( Ship_info[Ships[Objects[sop->objnum].instance].ship_info_index].flags[Ship::Info_Flags::Support] ) { int pship_index, sindex; // make sure support ship is not dying auto shipp = &Ships[Objects[sop->objnum].instance]; if ( !(shipp->flags[Ship::Ship_Flags::Dying] || shipp->flags[Ship::Ship_Flags::Exploded]) ) { Assert( objp->type == OBJ_SHIP ); aip = &Ai_info[Ships[Objects[sop->objnum].instance].ai_index]; pship_index = objp->instance; // we must check all goals for this support ship -- not just the first one for ( i = 0; i < MAX_AI_GOALS; i++ ) { // we can use == in the next statement (and should) since a ship will only ever be // following one order at a time. if ( aip->goals[i].ai_mode == AI_GOAL_REARM_REPAIR ) { Assert( aip->goals[i].target_name ); sindex = ship_name_lookup( aip->goals[i].target_name ); if ( sindex == pship_index ) return sop->objnum; } } } } } return -1; } void hud_support_view_update() { if ( !Hud_support_view_active ) { return; } if((Game_mode & GM_MULTIPLAYER) && ((Net_player->flags & NETINFO_FLAG_OBSERVER) || (Player_obj->type == OBJ_OBSERVER))){ return; } // If we haven't determined yet who the rearm ship is, try to! if (Hud_support_objnum == -1) { Hud_support_objnum = hud_support_find_closest( OBJ_INDEX(Player_obj) ); if ( Hud_support_objnum >= 0 ) { Hud_support_obj_sig = Objects[Hud_support_objnum].signature; Hud_support_target_sig = Player_obj->signature; } } else { // Check to see if support ship is still alive if ( (Objects[Hud_support_objnum].signature != Hud_support_obj_sig) || (Hud_support_target_sig != Player_obj->signature) ) { hud_support_view_stop(1); return; } } if ( Hud_support_view_fade > 1 ) { if ( timestamp_elapsed(Hud_support_view_fade) ) { Hud_support_view_abort = 0; Hud_support_view_active = 0; Hud_support_view_fade = 1; Hud_support_objnum = -1; } } } HudGaugeSupport::HudGaugeSupport(): HudGauge(HUD_OBJECT_SUPPORT, HUD_SUPPORT_GAUGE, false, false, (VM_EXTERNAL | VM_DEAD_VIEW | VM_WARP_CHASE | VM_PADLOCK_ANY | VM_OTHER_SHIP), 255, 255, 255) { } void HudGaugeSupport::initHeaderOffsets(int x, int y) { Header_offsets[0] = x; Header_offsets[1] = y; } void HudGaugeSupport::initTextValueOffsetY(int y) { text_val_offset_y = y; } void HudGaugeSupport::initTextDockOffsetX(int x) { text_dock_offset_x = x; } void HudGaugeSupport::initTextDockValueOffsetX(int x) { text_dock_val_offset_x = x; } void HudGaugeSupport::initRearmTimer(bool choice) { enable_rearm_timer = choice; } void HudGaugeSupport::initBitmaps(const char *fname) { background.first_frame = bm_load_animation(fname, &background.num_frames); if ( background.first_frame == -1 ) { Warning(LOCATION, "Could not load in ani: %s\n", fname); } } void HudGaugeSupport::pageIn() { bm_page_in_aabitmap( background.first_frame, background.num_frames); } void HudGaugeSupport::render(float /*frametime*/) { int show_time, w, h; char outstr[64]; if (background.first_frame < 0) return; if ( !Hud_support_view_active ) { return; } // Don't render this gauge for multiplayer observers if((Game_mode & GM_MULTIPLAYER) && ((Net_player->flags & NETINFO_FLAG_OBSERVER) || (Player_obj->type == OBJ_OBSERVER))){ return; } if ( Hud_support_objnum >= 0 ) { // Check to see if support ship is still alive if ( (Objects[Hud_support_objnum].signature != Hud_support_obj_sig) || (Hud_support_target_sig != Player_obj->signature) ) { return; } } bm_get_info(background.first_frame, &w, &h); // Set hud color setGaugeColor(); renderBitmap(background.first_frame, position[0], position[1]); renderString(position[0] + Header_offsets[0], position[1] + Header_offsets[1], XSTR( "support", 224)); if ( Hud_support_view_fade > 1 ) { if ( !timestamp_elapsed(Hud_support_view_fade) ) { if ( Hud_support_view_abort){ renderStringAlignCenter(position[0], position[1] + text_val_offset_y, w, XSTR( "aborted", 225)); } else { renderStringAlignCenter(position[0], position[1] + text_val_offset_y, w, XSTR( "complete", 1407)); } } return; } show_time = 0; if (Player_ai->ai_flags[AI::AI_Flags::Being_repaired]) { Assert(Player_ship->ship_max_hull_strength > 0); if (!enable_rearm_timer) { int i; bool repairing = false; for (i = 0; i < SUBSYSTEM_MAX; i++) { if (Player_ship->subsys_info[i].type_count > 0) { if (ship_get_subsystem_strength(Player_ship, i) < 1.0f) { repairing = true; break; } } } if (repairing) strcpy_s(outstr, XSTR("repairing", 227)); else strcpy_s(outstr, XSTR("rearming", 228)); } else { if (Player_rearm_eta > 0) { int min, sec, hund; min = (int)Player_rearm_eta / 60; sec = (int)Player_rearm_eta % 60; hund = (int)(Player_rearm_eta * 100) % 100; sprintf(outstr, "%02d:%02d.%02d", min, sec, hund); } else { strcpy_s(outstr, XSTR("Waiting...", 1603)); } } renderStringAlignCenter(position[0], position[1] + text_val_offset_y, w, outstr); } else if (Player_ai->ai_flags[AI::AI_Flags::Repair_obstructed]) { strcpy_s(outstr, XSTR( "obstructed", 229)); renderStringAlignCenter(position[0], position[1] + text_val_offset_y, w, outstr); } else { if ( Hud_support_objnum == -1 ) { if (The_mission.support_ships.arrival_location == ARRIVE_FROM_DOCK_BAY) { strcpy_s(outstr, XSTR( "exiting hangar", 1622)); } else { strcpy_s(outstr, XSTR( "warping in", 230)); } renderStringAlignCenter(position[0], position[1] + text_val_offset_y, w, outstr); } else { ai_info *aip; // Display "busy" when support ship isn't actually enroute to me aip = &Ai_info[Ships[Objects[Hud_support_objnum].instance].ai_index]; if ( aip->goal_objnum != OBJ_INDEX(Player_obj) ) { strcpy_s(outstr, XSTR( "busy", 231)); show_time = 0; } else { strcpy_s(outstr, XSTR( "dock in:", 232)); show_time = 1; } renderString(position[0] + text_dock_offset_x, position[1] + text_val_offset_y, outstr); } } if ( show_time ) { int seconds, minutes; Assert( Hud_support_objnum != -1 ); // Ensure support ship is still alive if ( (Objects[Hud_support_objnum].signature != Hud_support_obj_sig) || (Hud_support_target_sig != Player_obj->signature) ) { seconds = 0; } else { seconds = hud_get_dock_time( &Objects[Hud_support_objnum] ); } if ( seconds >= 0 ) { minutes = seconds/60; seconds = seconds%60; if ( minutes > 99 ) { minutes = 99; seconds = 99; } } else { minutes = 99; seconds = 99; } renderPrintf(position[0] + text_dock_val_offset_x, position[1] + text_val_offset_y, NOX("%02d:%02d"), minutes, seconds); } } /** * @brief Set the current color to the default HUD color (with default alpha) */ void hud_set_default_color() { Assert(HUD_color_alpha >= 0 && HUD_color_alpha < HUD_NUM_COLOR_LEVELS); gr_set_color_fast(&HUD_color_defaults[HUD_color_alpha]); } /** * @brief Set the current color to a bright HUD color (ie high alpha) */ void hud_set_bright_color() { int alpha_color; alpha_color = MIN(HUD_COLOR_ALPHA_MAX,HUD_color_alpha+HUD_BRIGHT_DELTA); gr_set_color_fast(&HUD_color_defaults[alpha_color]); } /** * @brief Set the current color to a dim HUD color (ie low alpha) */ void hud_set_dim_color() { if ( HUD_color_alpha > 2 ) { gr_set_color_fast(&HUD_color_defaults[2]); } } /** * @brief Will set the color to the IFF color based on the team * * @param objp Pointer to object whose team will be queried to set HUD gauge color * @param is_bright Default parameter (value 0) which uses bright version of IFF color */ void hud_set_iff_color(object* objp, int is_bright) { gr_set_color_fast(hud_get_iff_color(objp, is_bright)); } /** * @brief Will get the color to the IFF color based on the team * * @param objp Pointer to object whose team will be queried to get HUD gauge color * @param is_bright Default parameter (value 0) which uses bright version of IFF color */ color* hud_get_iff_color(object* objp, int is_bright) { color *use_color; if (ship_is_tagged(objp)) { use_color = iff_get_color(IFF_COLOR_TAGGED, is_bright); } else { if (objp->type == OBJ_ASTEROID) { if (OBJ_INDEX(objp) == Player_ai->target_objnum) { use_color = iff_get_color_by_team(Iff_traitor, Player_ship->team, is_bright); } else { use_color = iff_get_color(IFF_COLOR_SELECTION, is_bright); } } else { use_color = iff_get_color_by_team_and_object(obj_team(objp), Player_ship->team, is_bright, objp); } } return use_color; } /** * @brief Reset gauge flashing data */ void hud_gauge_flash_init() { int i; for ( i=0; i=0 && gauge_index < NUM_HUD_GAUGES); // AL: Special code: Only show two gauges when not viewing from own ship if ( Viewer_mode & VM_OTHER_SHIP ) { for ( int i = 0; i < NUM_VM_OTHER_SHIP_GAUGES; i++ ) { if ( gauge_index == Vm_other_ship_gauges[i] ) { return 1; } } return 0; } return hud_config_show_flag_is_set(gauge_index); } /** * @brief Determine if gauge is in pop-up mode or not */ int hud_gauge_is_popup(int gauge_index) { Assert(gauge_index >=0 && gauge_index < NUM_HUD_GAUGES); return hud_config_popup_flag_is_set(gauge_index); } /** * @brief Start a gauge to pop-up * @details Load gauge properties defined in the HUD config if gauge is not customized. */ void hud_gauge_popup_start(int gauge_index, int time) { Assert(gauge_index >=0 && gauge_index < NUM_HUD_GAUGES); if ( !hud_gauge_is_popup(gauge_index) ) { return; } size_t num_gauges, i; if(!Ship_info[Player_ship->ship_info_index].hud_gauges.empty()) { num_gauges = Ship_info[Player_ship->ship_info_index].hud_gauges.size(); for(i = 0; i < num_gauges; i++) { if(Ship_info[Player_ship->ship_info_index].hud_gauges[i]->getConfigType() == gauge_index) Ship_info[Player_ship->ship_info_index].hud_gauges[i]->startPopUp(time); } } else { num_gauges = default_hud_gauges.size(); for(i = 0; i < num_gauges; i++) { if(default_hud_gauges[i]->getConfigType() == gauge_index) default_hud_gauges[i]->startPopUp(time); } } } /** * @brief Call HUD function to flash gauge * @details Load gauge properties defined in the HUD config if gauge is not customized. */ void hud_gauge_start_flash(int gauge_index) { Assert(gauge_index >=0 && gauge_index < NUM_HUD_GAUGES); size_t num_gauges, i; HUD_gauge_flash_duration[gauge_index] = timestamp(HUD_GAUGE_FLASH_DURATION); HUD_gauge_flash_next[gauge_index] = 1; if(!Ship_info[Player_ship->ship_info_index].hud_gauges.empty()) { num_gauges = Ship_info[Player_ship->ship_info_index].hud_gauges.size(); for(i = 0; i < num_gauges; i++) { if(Ship_info[Player_ship->ship_info_index].hud_gauges[i]->getConfigType() == gauge_index) Ship_info[Player_ship->ship_info_index].hud_gauges[i]->startFlashSexp(); } } else { num_gauges = default_hud_gauges.size(); for(i = 0; i < num_gauges; i++) { if(default_hud_gauges[i]->getConfigType() == gauge_index) default_hud_gauges[i]->startFlashSexp(); } } } /** * @brief Set the HUD color for the gauge, based on whether it is flashing or not */ void hud_set_gauge_color(int gauge_index, int bright_index) { int flash_status = hud_gauge_maybe_flash(gauge_index); color *use_color = &HUD_config.clr[gauge_index]; int alpha; // If we're drawing it as bright if(bright_index != HUD_C_NONE){ switch(bright_index){ case HUD_C_DIM: alpha = HUD_contrast ? HUD_NEW_ALPHA_DIM_HI : HUD_NEW_ALPHA_DIM; gr_init_alphacolor(use_color, use_color->red, use_color->green, use_color->blue, alpha); break; case HUD_C_NORMAL: alpha = HUD_contrast ? HUD_NEW_ALPHA_NORMAL_HI : HUD_NEW_ALPHA_NORMAL; gr_init_alphacolor(use_color, use_color->red, use_color->green, use_color->blue, alpha); break; case HUD_C_BRIGHT: alpha = HUD_contrast ? HUD_NEW_ALPHA_BRIGHT_HI : HUD_NEW_ALPHA_BRIGHT; gr_init_alphacolor(use_color, use_color->red, use_color->green, use_color->blue, alpha); break; // Intensity default: Assert((bright_index >= 0) && (bright_index < HUD_NUM_COLOR_LEVELS)); if(bright_index < 0){ bright_index = 0; } if(bright_index >= HUD_NUM_COLOR_LEVELS){ bright_index = HUD_NUM_COLOR_LEVELS - 1; } // alpha = 255 - (255 / (bright_index + 1)); // alpha = (int)((float)alpha * 1.5f); int level = 255 / (HUD_NUM_COLOR_LEVELS); alpha = level * bright_index; if(alpha > 255){ alpha = 255; } if(alpha < 0){ alpha = 0; } gr_init_alphacolor(use_color, use_color->red, use_color->green, use_color->blue, alpha); break; } } else { switch(flash_status) { case 0: alpha = HUD_contrast ? HUD_NEW_ALPHA_DIM_HI : HUD_NEW_ALPHA_DIM; gr_init_alphacolor(use_color, use_color->red, use_color->green, use_color->blue, alpha); break; case 1: alpha = HUD_contrast ? HUD_NEW_ALPHA_BRIGHT_HI : HUD_NEW_ALPHA_BRIGHT; gr_init_alphacolor(use_color, use_color->red, use_color->green, use_color->blue, alpha); break; default: alpha = HUD_contrast ? HUD_NEW_ALPHA_NORMAL_HI : HUD_NEW_ALPHA_NORMAL; gr_init_alphacolor(use_color, use_color->red, use_color->green, use_color->blue, alpha); break; } } gr_set_color_fast(use_color); } /** * @brief Set the color for a gauge that may be flashing * @param gauge_index Gauge to test * @return Gauge is not flashing -1; gauge is flashing, draw dim 0; gauge is flashing, draw bright 1 */ int hud_gauge_maybe_flash(int gauge_index) { Assert(gauge_index >=0 && gauge_index < NUM_HUD_GAUGES); int flash_status=-1; if ( !timestamp_elapsed(HUD_gauge_flash_duration[gauge_index]) ) { if ( timestamp_elapsed(HUD_gauge_flash_next[gauge_index]) ) { HUD_gauge_flash_next[gauge_index] = timestamp(HUD_GAUGE_FLASH_INTERVAL); HUD_gauge_bright ^= (1<control_mode == PCM_WARPOUT_STAGE1) || (Player->control_mode == PCM_WARPOUT_STAGE2) || (Player->control_mode == PCM_WARPOUT_STAGE3) || (Sexp_hud_display_warpout > 0) ) { if (!hud_subspace_notify_active()) { // Keep sound from being played 1e06 times hud_start_subspace_notify(); } } else { if ( timestamp_elapsed(HUD_abort_subspace_timer) ) { hud_stop_subspace_notify(); } } if ( (Sexp_hud_display_warpout > 1) && hud_subspace_notify_active() ) { if ( Sexp_hud_display_warpout < timestamp()) { Sexp_hud_display_warpout = 0; } } // Find out if we should display the objective status notification if ( timestamp_elapsed(Objective_display.display_timer) ) { hud_stop_objective_notify(); } else if (!hud_objective_notify_active() && !hud_subspace_notify_active()) { hud_start_objective_notify(); } } /** * @brief Add objective status on the HUD * * @param type Type of goal, one of: ::PRIMARY_GOAL, ::SECONDARY_GOAL, ::BONUS_GOAL * @param status Status of goal, one of: ::GOAL_FAILED, ::GOAL_COMPLETE, ::GOAL_INCOMPLETE * @todo Play a sound? */ void hud_add_objective_messsage(int type, int status) { Objective_display.display_timer=timestamp(7000); Objective_display.goal_type=type; Objective_display.goal_status=status; // if this is a multiplayer tvt game if(MULTI_TEAM && (Net_player != NULL)) { mission_goal_fetch_num_resolved(type, &Objective_display.goal_nresolved, &Objective_display.goal_ntotal, Net_player->p_info.team); } else { mission_goal_fetch_num_resolved(type, &Objective_display.goal_nresolved, &Objective_display.goal_ntotal); } // TODO: play a sound? } HudGaugeObjectiveNotify::HudGaugeObjectiveNotify(): HudGauge(HUD_OBJECT_OBJ_NOTIFY, HUD_OBJECTIVES_NOTIFY_GAUGE, false, false, VM_EXTERNAL | VM_DEAD_VIEW | VM_WARP_CHASE | VM_PADLOCK_ANY | VM_OTHER_SHIP, 255, 255, 255) { } void HudGaugeObjectiveNotify::initObjTextOffsetY(int y) { Objective_text_offset_y = y; } void HudGaugeObjectiveNotify::initObjValueOffsetY(int y) { Objective_text_val_offset_y = y; } void HudGaugeObjectiveNotify::initSubspaceTextOffsetY(int y) { Subspace_text_offset_y = y; } void HudGaugeObjectiveNotify::initSubspaceValueOffsetY(int y) { Subspace_text_val_offset_y = y; } void HudGaugeObjectiveNotify::initRedAlertTextOffsetY(int y) { Red_text_offset_y = y; } void HudGaugeObjectiveNotify::initRedAlertValueOffsetY(int y) { Red_text_val_offset_y = y; } void HudGaugeObjectiveNotify::initBitmaps(const char *fname) { Objective_display_gauge.first_frame = bm_load_animation(fname, &Objective_display_gauge.num_frames); if ( Objective_display_gauge.first_frame == -1 ) { Warning(LOCATION, "Could not load in ani: %s\n", fname); } } void HudGaugeObjectiveNotify::initialize() { flash_timer[0] = timestamp(1); flash_timer[1] = timestamp(1); flash_flag = false; HudGauge::initialize(); } void HudGaugeObjectiveNotify::pageIn() { bm_page_in_aabitmap( Objective_display_gauge.first_frame, Objective_display_gauge.num_frames); } void HudGaugeObjectiveNotify::startFlashNotify(int duration) { flash_timer[0] = timestamp(duration); } bool HudGaugeObjectiveNotify::maybeFlashNotify(bool flash_fast) { bool draw_bright = false; if(!timestamp_elapsed(flash_timer[0])) { if(timestamp_elapsed(flash_timer[1])) { if(flash_fast) { flash_timer[1] = timestamp(fl2i(TBOX_FLASH_INTERVAL/2.0f)); } else { flash_timer[1] = timestamp(TBOX_FLASH_INTERVAL); } flash_flag = !flash_flag; } if(flash_flag) { draw_bright = true; } } return draw_bright; } void HudGaugeObjectiveNotify::render(float /*frametime*/) { renderSubspace(); renderRedAlert(); renderObjective(); } void HudGaugeObjectiveNotify::renderSubspace() { int w, h; int warp_aborted = 0; if ( (Player->control_mode != PCM_WARPOUT_STAGE1) && (Player->control_mode != PCM_WARPOUT_STAGE2) && (Player->control_mode != PCM_WARPOUT_STAGE3) && (Sexp_hud_display_warpout <= 0) ) { if ( !timestamp_elapsed(HUD_abort_subspace_timer) ) { warp_aborted = 1; } } if ( !hud_subspace_notify_active() ) { return; } if ( Objective_display_gauge.first_frame < 0 ) { return; } // Blit the background setGaugeColor(); renderBitmap(Objective_display_gauge.first_frame, position[0], position[1]); startFlashNotify(); maybeFlashNotify() ? setGaugeColor(HUD_C_BRIGHT) : setGaugeColor(); bm_get_info(Objective_display_gauge.first_frame, &w, &h); renderStringAlignCenter(position[0], position[1] + Subspace_text_offset_y, w, XSTR( "subspace drive", 233)); if ( warp_aborted ) { renderStringAlignCenter(position[0], position[1] + Subspace_text_val_offset_y, w, XSTR( "aborted", 225)); } else { renderStringAlignCenter(position[0], position[1] + Subspace_text_val_offset_y, w, XSTR( "engaged", 234)); } } /** * @todo Play a sound? */ void HudGaugeObjectiveNotify::renderRedAlert() { int w, h; if ( !red_alert_in_progress() ) { return; } if ( Objective_display_gauge.first_frame < 0 ) { return; } if ( hud_subspace_notify_active() ) { return; } if ( hud_objective_notify_active() ) { return; } // Blit the background gr_set_color_fast(&Color_red); // Color box red, because it's an emergency renderBitmap(Objective_display_gauge.first_frame, position[0], position[1]); startFlashNotify(); if(maybeFlashNotify()) { gr_set_color_fast(&Color_red); } else { gr_set_color_fast(&Color_bright_red); } bm_get_info(Objective_display_gauge.first_frame, &w, &h); renderStringAlignCenter(position[0], position[1] + Red_text_offset_y, w, XSTR( "downloading new", 235)); renderStringAlignCenter(position[0], position[1] + Red_text_val_offset_y, w, XSTR( "orders...", 236)); // TODO: play a sound? } void HudGaugeObjectiveNotify::renderObjective() { int w, h; char buf[128]; if ( timestamp_elapsed(Objective_display.display_timer) ) { return; } if ( Objective_display_gauge.first_frame < 0 ) { return; } if ( hud_subspace_notify_active() ) { return; } // Blit the background setGaugeColor(); renderBitmap(Objective_display_gauge.first_frame, position[0], position[1]); startFlashNotify(); if(maybeFlashNotify()){ setGaugeColor(HUD_C_BRIGHT); } else { setGaugeColor(); } bm_get_info(Objective_display_gauge.first_frame, &w, &h); // Draw the correct goal type switch(Objective_display.goal_type) { case PRIMARY_GOAL: renderStringAlignCenter(position[0], position[1] + Objective_text_offset_y, w, XSTR( "primary objective", 237)); break; case SECONDARY_GOAL: renderStringAlignCenter(position[0], position[1] + Objective_text_offset_y, w, XSTR( "secondary objective", 238)); break; case BONUS_GOAL: renderStringAlignCenter(position[0], position[1] + Objective_text_offset_y, w, XSTR( "bonus objective", 239)); break; } // Show the status switch(Objective_display.goal_type) { case PRIMARY_GOAL: case SECONDARY_GOAL: switch(Objective_display.goal_status) { case GOAL_FAILED: sprintf(buf, XSTR( "failed (%d/%d)", 240), Objective_display.goal_nresolved, Objective_display.goal_ntotal); renderStringAlignCenter(position[0], position[1] + Objective_text_val_offset_y, w, buf); break; default: sprintf(buf, XSTR( "complete (%d/%d)", 241), Objective_display.goal_nresolved, Objective_display.goal_ntotal); renderStringAlignCenter(position[0], position[1] + Objective_text_val_offset_y, w, buf); break; } break; case BONUS_GOAL: switch(Objective_display.goal_status) { case GOAL_FAILED: renderStringAlignCenter(position[0], position[1] + Objective_text_val_offset_y, w, XSTR( "failed", 242)); break; default: renderStringAlignCenter(position[0], position[1] + Objective_text_val_offset_y, w, XSTR( "complete", 226)); break; } break; } } void hud_subspace_notify_abort() { HUD_abort_subspace_timer = timestamp(1500); } void hud_stop_subspace_notify() { Subspace_notify_active=0; } void hud_start_subspace_notify() { Subspace_notify_active=1; } int hud_subspace_notify_active() { return Subspace_notify_active; } void hud_stop_objective_notify() { Objective_notify_active = 0; } void hud_start_objective_notify() { snd_play(gamesnd_get_game_sound(GameSounds::DIRECTIVE_COMPLETE)); Objective_notify_active = 1; } int hud_objective_notify_active() { return Objective_notify_active; } /** * @brief Set the offset values for this render frame * @details Since the player's view vector may be different from the ship's forward vector, * we calculate the offset of those two in pixels and store the x and y offsets in * variables HUD_nose_x and HUD_nose_y (Swifty) * * @param viewer_obj Object, likely to be player * @param wiggedy_wack * @param eye_orient */ void HUD_set_offsets() { if ( Viewer_mode & ( VM_TOPDOWN | VM_CHASE ) ) { HUD_nose_x = 0; HUD_nose_y = 0; } else { HUD_get_nose_coordinates(&HUD_nose_x, &HUD_nose_y); } } /** * @brief Returns the offset between the player's view vector and the forward vector of the ship in pixels (Swifty) */ void HUD_get_nose_coordinates(int *x, int *y) { vertex v0; vec3d p0; float x_nose; float y_nose; float x_center = gr_screen.clip_center_x; float y_center = gr_screen.clip_center_y; *x = 0; *y = 0; vm_vec_scale_add(&p0, &Player_obj->pos, &Player_obj->orient.vec.fvec, 1000.0f); g3_rotate_vertex(&v0, &p0); if (v0.codes == 0) { g3_project_vertex(&v0); if ( !(v0.codes & PF_OVERFLOW) ) { x_nose = v0.screen.xyw.x; y_nose = v0.screen.xyw.y; } else { // Means that the ship forward vector is not going through the frame buffer. // We're assigning a high negative value so that the the bitmaps will be drawn offscreen so that // we can give the illusion that the player is looking away from the slewable HUD reticle. *x = -100000; *y = -100000; return; } } else { // Means that the ship forward vector is not going through the frame buffer. // We're assigning a high negative value so that the the bitmaps will be drawn offscreen so that // we can give the illusion that the player is looking away from the slewable HUD reticle. *x = -100000; *y = -100000; return; } gr_unsize_screen_posf(&x_nose, &y_nose); gr_unsize_screen_posf(&x_center, &y_center); *x = fl2i(x_nose - x_center); *y = fl2i(y_nose - y_center); return; } /** * @brief Like ::gr_reset_clip() only it accounts for HUD jittering */ void HUD_reset_clip() { gr_set_clip(0, 0, gr_screen.max_w_unscaled, gr_screen.max_h_unscaled); } /** * @brief Like ::gr_set_clip() only it accounts for HUD jittering */ void HUD_set_clip(int x, int y, int w, int h) { gr_set_clip(x, y, w, h); } /** * @brief Called to save and restore the 3D camera settings. * @param save Save global view variables if 1, restore them if not 1 */ void hud_save_restore_camera_data(int save) { static vec3d save_view_position; static fov_t save_view_zoom; static fov_t save_proj_fov; static matrix save_view_matrix; static matrix save_eye_matrix; static vec3d save_eye_position; if ( save ) { save_view_position = View_position; save_view_zoom = View_zoom; save_proj_fov = Proj_fov; save_view_matrix = View_matrix; save_eye_matrix = Eye_matrix; save_eye_position = Eye_position; } else { // restore global view variables View_position = save_view_position; View_zoom = save_view_zoom; Proj_fov = save_proj_fov; View_matrix = save_view_matrix; Eye_matrix = save_eye_matrix; Eye_position = save_eye_position; } } void hud_toggle_contrast() { HUD_contrast = !HUD_contrast; } void hud_set_contrast(int high) { HUD_contrast = high; } void hud_toggle_shadows() { HUD_shadows = !HUD_shadows; } // Paging functions for the rest of the HUD code extern void hudtarget_page_in(); /** * @brief Page in all HUD bitmaps */ void hud_page_in() { // Go through all hud gauges to page them in size_t j, num_gauges = 0; for (auto it = Ship_info.cbegin(); it != Ship_info.cend(); ++it) { if(it->hud_enabled) { if(!it->hud_gauges.empty()) { num_gauges = it->hud_gauges.size(); for(j = 0; j < num_gauges; j++) { it->hud_gauges[j]->pageIn(); } } } } num_gauges = default_hud_gauges.size(); for(j = 0; j < num_gauges; j++) { default_hud_gauges[j]->pageIn(); } } HudGauge* hud_get_custom_gauge(const char* name, bool check_all_gauges) { auto player_sip = Player_ship->ship_info_index < 0 ? nullptr : &Ship_info[Player_ship->ship_info_index]; // go through all gauges for all ships and defaults if (check_all_gauges) { for (auto &si : Ship_info) { for (auto &gauge : si.hud_gauges) { if (!stricmp(name, gauge->getCustomGaugeName())) { return gauge.get(); } } } } // if the player is flying a ship with a custom set of gauges, check those else if (player_sip && !player_sip->hud_gauges.empty()) { for (auto &gauge : player_sip->hud_gauges) { if (!stricmp(name, gauge->getCustomGaugeName())) { return gauge.get(); } } } // fall back to checking the custom gauges in the default list for (auto &gauge : default_hud_gauges) { if (gauge->isCustom() && !stricmp(name, gauge->getCustomGaugeName())) { return gauge.get(); } } return nullptr; } int hud_get_default_gauge_index(const char *name) { int config_type = -1, object_type = -1; // default gauges had two different lists for (int i = 0; i < NUM_HUD_GAUGES; i++) { if (!strcmp(Legacy_HUD_gauges[i].hud_gauge_text, name)) { config_type = i; break; } } if (config_type < 0) { for (int i = 0; i < Num_hud_gauge_types; i++) { if (!stricmp(name, Hud_gauge_types[i].name)) { object_type = Hud_gauge_types[i].def; break; } } } if (config_type >= 0 || object_type >= 0) { for (int i = 0; i < (int)default_hud_gauges.size(); i++) { if (default_hud_gauges[i]->getConfigType() == config_type || default_hud_gauges[i]->getObjectType() == object_type) { return i; } } } return -1; } HudGauge *hud_get_gauge(const char *name) { auto gauge = hud_get_custom_gauge(name); if (gauge == nullptr) { int idx = hud_get_default_gauge_index(name); if (idx >= 0 && idx < (int)default_hud_gauges.size()) gauge = default_hud_gauges[idx].get(); } return gauge; } HudGaugeMultiMsg::HudGaugeMultiMsg(): HudGauge(HUD_OBJECT_MULTI_MSG, HUD_MESSAGE_LINES, false, true, 0, 255, 255, 255) { } bool HudGaugeMultiMsg::canRender() { return true; } /** * @brief Render multiplayer text message currently being entered, if any */ void HudGaugeMultiMsg::render(float /*frametime*/) { char txt[MULTI_MSG_MAX_TEXT_LEN+20]; // clear the text memset(txt,0,MULTI_MSG_MAX_TEXT_LEN+20); // if there is valid multiplayer message text to be displayed if(multi_msg_message_text(txt)){ gr_set_color_fast(&Color_normal); renderString(position[0], position[1], txt); } } HudGaugeVoiceStatus::HudGaugeVoiceStatus(): HudGauge(HUD_OBJECT_VOICE_STATUS, HUD_MESSAGE_LINES, false, true, VM_EXTERNAL | VM_DEAD_VIEW | VM_WARP_CHASE | VM_PADLOCK_ANY, 255, 255, 255) { } void HudGaugeVoiceStatus::render(float /*frametime*/) { if(!(Game_mode & GM_MULTIPLAYER)){ return; } // if we are currently playing a rtvoice sound stream from another player back switch(multi_voice_status()){ // the player has been denied the voice token case MULTI_VOICE_STATUS_DENIED: // show a red indicator or something renderString(position[0], position[1], XSTR( "[voice denied]", 243)); break; // the player is currently recording case MULTI_VOICE_STATUS_RECORDING: renderString(position[0], position[1], XSTR( "[recording voice]", 244)); break; // the player is current playing back voice from someone case MULTI_VOICE_STATUS_PLAYING: renderString(position[0], position[1], XSTR( "[playing voice]", 245)); break; // nothing voice related is happening on my machine case MULTI_VOICE_STATUS_IDLE: // probably shouldn't be displaying anything break; } } HudGaugePing::HudGaugePing(): HudGauge(HUD_OBJECT_PING, HUD_LAG_GAUGE, false, false, 0, 255, 255, 255) { } /** * @brief Render multiplayer ping time to the server, if appropriate */ void HudGaugePing::render(float /*frametime*/) { // If we shouldn't be displaying a ping time, return here if(!multi_show_ingame_ping()){ return; } // If we're in multiplayer mode, display our ping time to the server if(MULTIPLAYER_CLIENT && (Net_player != NULL)){ char ping_str[50]; memset(ping_str,0,50); // If our ping is positive, display it if((Netgame.server != NULL) && (Netgame.server->s_info.ping.ping_avg > 0)){ // Get the string if(Netgame.server->s_info.ping.ping_avg >= 1000){ strcpy_s(ping_str,XSTR("> 1 sec",628)); } else { sprintf(ping_str,XSTR("%d ms",629),Netgame.server->s_info.ping.ping_avg); } // Blit the string out hud_set_default_color(); renderString(position[0], position[1], ping_str); } } } HudGaugeSupernova::HudGaugeSupernova(): HudGauge(HUD_OBJECT_SUPERNOVA, HUD_DIRECTIVES_VIEW, false, false, 0, 255, 255, 255) { } void HudGaugeSupernova::render(float /*frametime*/) { if (!supernova_active()) { return; } auto time_left = supernova_hud_time_left(); gr_set_color_fast(&Color_bright_red); if (Lcl_pl) { renderPrintf(position[0], position[1], "Wybuch supernowej: %.2f s", time_left); } else { renderPrintf(position[0], position[1], XSTR( "Supernova Warning: %.2f s", 1639), time_left); } } HudGaugeFlightPath::HudGaugeFlightPath(): HudGauge3DAnchor(HUD_OBJECT_FLIGHT_PATH, HUD_CENTER_RETICLE, false, false, VM_EXTERNAL | VM_DEAD_VIEW | VM_WARP_CHASE | VM_PADLOCK_ANY, 255, 255, 255) { } void HudGaugeFlightPath::initBitmap(const char *fname) { Marker.first_frame = bm_load_animation(fname, &Marker.num_frames); if ( Marker.first_frame < 0 ) { Warning(LOCATION,"Cannot load hud ani: %s\n", fname); } } void HudGaugeFlightPath::initHalfSize(int w, int h) { Marker_half[0] = w; Marker_half[1] = h; } void HudGaugeFlightPath::render(float /*frametime*/) { object *obj; vec3d p0,v; vertex v0; int sx, sy; bool in_frame = g3_in_frame() > 0; if(!in_frame) { g3_start_frame(0); } obj = Player_obj; vm_vec_scale_add( &v, &obj->phys_info.vel, &obj->orient.vec.fvec, 1.0f ); vm_vec_normalize( &v ); vm_vec_scale_add( &p0, &obj->pos, &v, 1000000.0f ); g3_rotate_vertex( &v0, &p0 ); if (v0.codes == 0) { // on screen g3_project_vertex(&v0); if (!(v0.flags & PF_OVERFLOW)) { if ( Marker.first_frame >= 0 ) { sx = fl2i(v0.screen.xyw.x); sy = fl2i(v0.screen.xyw.y); unsize(&sx, &sy); renderBitmap(Marker.first_frame, sx - Marker_half[0], sy - Marker_half[1]); } } } if(!in_frame) { g3_end_frame(); } }