/* * 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 "bmpman/bmpman.h" #include "debugconsole/console.h" #include "freespace.h" #include "gamesnd/gamesnd.h" #include "globalincs/linklist.h" #include "graphics/font.h" #include "iff_defs/iff_defs.h" #include "io/timer.h" #include "jumpnode/jumpnode.h" #include "localization/localize.h" #include "network/multi.h" #include "object/object.h" #include "options/Option.h" #include "playerman/player.h" #include "radar/radar.h" #include "radar/radarorb.h" #include "radar/radarsetup.h" #include "ship/awacs.h" #include "ship/ship.h" #include "ship/subsysdamage.h" #include "weapon/emp.h" #include "weapon/weapon.h" #include "mod_table/mod_table.h" sound_handle Radar_static_looping = sound_handle::invalid(); rcol Radar_color_rgb[MAX_RADAR_COLORS][MAX_RADAR_LEVELS] = { // homing missile (yellow) { { 0x40, 0x40, 0x00 }, // dim { 0x7f, 0x7f, 0x00 }, // bright }, // navbuoy or cargo (gray) { { 0x40, 0x40, 0x40 }, // dim { 0x7f, 0x7f, 0x7f }, // bright }, // warping ship (blue) { { 0x00, 0x00, 0x7f }, // dim { 0x00, 0x00, 0xff }, // bright }, // jump node (gray) { { 0x40, 0x40, 0x40 }, // dim { 0x7f, 0x7f, 0x7f }, // bright }, // tagged (yellow) { { 0x7f, 0x7f, 0x00 }, // dim { 0xff, 0xff, 0x00 }, // bright }, }; int radar_target_id_flags = 0; color Radar_colors[MAX_RADAR_COLORS][MAX_RADAR_LEVELS]; blip Blip_bright_list[MAX_BLIP_TYPES]; // linked list of bright blips blip Blip_dim_list[MAX_BLIP_TYPES]; // linked list of dim blips blip Blips[MAX_BLIPS]; // blips pool int N_blips; // next blip index to take from pool SCP_map Blip_last_update; // map of objnums to timestamps float Radar_bright_range; // range at which we start dimming the radar blips TIMESTAMP Radar_calc_bright_dist_timer; // timestamp at which we recalc Radar_bright_range extern int radar_iff_color[5][2][4]; int See_all = 0; DCF_BOOL(see_all, See_all); RadarIconMode Radar_2d_icon_mode = RadarIconMode::On; static auto RadarIconModeOption __UNUSED = options::OptionBuilder("HUD.Radar2dIconMode", std::pair{"Radar 2D Icons", 1915}, std::pair{"Controls how custom 2D ship icons are displayed on the radar", 1916}) .category(std::make_pair("Game", 1824)) .values({{RadarIconMode::Off, {"Off", 1286}}, {RadarIconMode::On, {"On", 1285}}, {RadarIconMode::TargetOnly, {"Target Only", 1917}}}) .default_val(RadarIconMode::On) .bind_to(&Radar_2d_icon_mode) .importance(56) .finish(); void radar_check_2d_icon_options() { bool has_icons = std::any_of(Ship_info.begin(), Ship_info.end(), [](const ship_info& sip) { return sip.radar_image_2d_idx >= 0 || sip.radar_color_image_2d_idx >= 0; }); if (!has_icons) { options::OptionsManager::instance()->removeOption(RadarIconModeOption); } } void radar_stuff_blip_info(object *objp, int is_bright, color **blip_color, int *blip_type) { ship *shipp = NULL; switch(objp->type) { case OBJ_SHIP: shipp = &Ships[objp->instance]; if (shipp->is_arriving(ship::warpstage::STAGE1, false)) { *blip_color = &Radar_colors[RCOL_WARPING_SHIP][is_bright]; *blip_type = BLIP_TYPE_WARPING_SHIP; } else if (ship_is_tagged(objp)) { *blip_color = &Radar_colors[RCOL_TAGGED][is_bright]; *blip_type = BLIP_TYPE_TAGGED_SHIP; } else if (Ship_info[shipp->ship_info_index].flags[Ship::Info_Flags::Navbuoy] || Ship_info[shipp->ship_info_index].flags[Ship::Info_Flags::Cargo]) { *blip_color = &Radar_colors[RCOL_NAVBUOY_CARGO][is_bright]; *blip_type = BLIP_TYPE_NAVBUOY_CARGO; } else { *blip_color = iff_get_color_by_team_and_object(shipp->team, Player_ship->team, is_bright, objp); *blip_type = BLIP_TYPE_NORMAL_SHIP; } break; case OBJ_WEAPON: if ((Weapons[objp->instance].lssm_stage == 2) || (Weapons[objp->instance].lssm_stage == 4)) { *blip_color = &Radar_colors[RCOL_WARPING_SHIP][is_bright]; *blip_type = BLIP_TYPE_WARPING_SHIP; } else { *blip_color = &Radar_colors[RCOL_BOMB][is_bright]; *blip_type = BLIP_TYPE_BOMB; } break; case OBJ_JUMP_NODE: *blip_color = &Radar_colors[RCOL_JUMP_NODE][is_bright]; *blip_type = BLIP_TYPE_JUMP_NODE; break; default: Error(LOCATION, "Illegal blip type in radar."); break; } } void radar_plot_object( object *objp ) { vec3d pos, tempv; float awacs_level, dist, max_radar_dist; vec3d world_pos = objp->pos; bool mine_in_targetable_range = true; // for mines, computed below after distance check; non-mines unaffected // don't process anything here. Somehow, a jumpnode object caused this function // to get entered on server side. if( Game_mode & GM_STANDALONE_SERVER ){ return; } // if we are in the lab, do nothing here if (Game_mode & GM_LAB) { return; } // multiplayer clients ingame joining should skip this function if ( MULTIPLAYER_CLIENT && (Net_player->flags & NETINFO_FLAG_INGAME_JOIN) ){ return; } // get team-wide awacs level for the object if not ship int ship_is_visible = 0; if (objp->type == OBJ_SHIP) { if (Player_ship != NULL) { if (ship_is_visible_by_team(objp, Player_ship)) { ship_is_visible = 1; } } } // only check awacs level if ship is not visible by team awacs_level = 1.5f; if (Player_ship != NULL && !ship_is_visible) { awacs_level = awacs_get_level(objp, Player_ship); } // if the awacs level is unviewable - bail if(awacs_level < 0.0f && !See_all){ return; } // Apply object type filters switch (objp->type) { case OBJ_SHIP: // Place to cull ships, such as NavBuoys break; case OBJ_JUMP_NODE: { auto jnp = jumpnode_get_by_objp(objp); // don't plot missing or hidden jump nodes if ( !jnp || jnp->IsHidden() ) return; // filter jump nodes here if required break; } case OBJ_WEAPON: { weapon_info *wip = &Weapon_info[Weapons[objp->instance].weapon_info_index]; if (wip->is_mine()) { // if explicitly hidden, return if (wip->wi_flags[Weapon::Info_Flags::Dont_show_on_radar]) return; // if we don't attack the mine, return if ( !wip->wi_flags[Weapon::Info_Flags::Show_friendly] && !iff_x_attacks_y(Player_ship->team, obj_team(objp)) ) return; // Mine range-based detection... visibility determined after distance is calculated below break; } // if not a bomb, return if ( !(wip->wi_flags[Weapon::Info_Flags::Shown_on_radar]) ) if ( !(wip->wi_flags[Weapon::Info_Flags::Bomb]) ) return; // if explicitly hidden, return if (wip->wi_flags[Weapon::Info_Flags::Dont_show_on_radar]) return; // if we don't attack the bomb, return if ( !wip->wi_flags[Weapon::Info_Flags::Show_friendly] && !iff_x_attacks_y(Player_ship->team, obj_team(objp)) ) return; // if a local ssm is in subspace, return if (Weapons[objp->instance].lssm_stage == 3) return; // if corkscrew missile use last frame pos for pos if (wip->wi_flags[Weapon::Info_Flags::Corkscrew]) world_pos = objp->last_pos; break; } // if any other kind of object, don't show it on radar default: return; } // Retrieve the eye orientation so we can position the blips relative to it matrix eye_orient; object_get_eye(&tempv, &eye_orient, Player_obj, false); // JAS -- new way of getting the rotated point that doesn't require this to be // in a g3_start_frame/end_frame block. vm_vec_sub(&tempv, &world_pos, &Player_obj->pos); vm_vec_rotate(&pos, &tempv, &eye_orient); // Apply range filter (squared-distance cull first so the sqrt only runs for blips that survive) max_radar_dist = Radar_ranges[HUD_config.rp_dist]; float dist_sq = vm_vec_mag_squared(&tempv); if (dist_sq > max_radar_dist * max_radar_dist) return; dist = sqrtf(dist_sq); // Mine range-based visibility: sensors_range is the outer detection envelope. The parser // guarantees sensors_range >= targetable_range, so anything within targetable range is // necessarily within sensors range; targetable range only governs distorted-vs-clear below. if (objp->type == OBJ_WEAPON) { weapon_info *wip = &Weapon_info[Weapons[objp->instance].weapon_info_index]; if (wip->is_mine()) { if (dist > wip->mine_sensors_range) return; // beyond detection mine_in_targetable_range = (dist <= wip->mine_targetable_range); } } // determine the range within which the radar blip is bright if (timestamp_elapsed(Radar_calc_bright_dist_timer)) { Radar_calc_bright_dist_timer = _timestamp(1000); Radar_bright_range = player_farthest_weapon_range(); if (Radar_bright_range <= 0) Radar_bright_range = 1500.0f; } blip *b; int blip_bright = 0; int blip_type = 0; if (N_blips >= MAX_BLIPS) { return; } int objnum = OBJ_INDEX(objp); b = &Blips[N_blips]; b->rad = 0; b->flags = 0; // bright if within range blip_bright = (dist <= Radar_bright_range); // flag the blip as a current target if it is if (objnum == Player_ai->target_objnum) { b->flags |= BLIP_CURRENT_TARGET; blip_bright = 1; } radar_stuff_blip_info(objp, blip_bright, &b->blip_color, &blip_type); if (blip_bright) list_append(&Blip_bright_list[blip_type], b); else list_append(&Blip_dim_list[blip_type], b); b->position = pos; b->radar_image_2d = -1; b->radar_color_image_2d = -1; b->radar_image_size = -1; b->radar_projection_size = 1.0f; b->dist = dist; b->objnum = objnum; auto it = Blip_last_update.find(objnum); if (it == Blip_last_update.end()) Blip_last_update.emplace(objnum, TIMESTAMP::never()); // see if blip should be drawn distorted // also determine if alternate image was defined for this ship // Mines outside their targetable range (but inside sensors range, by the earlier gate) get a distorted blip if (!mine_in_targetable_range) b->flags |= BLIP_DRAW_DISTORTED; if (objp->type == OBJ_SHIP) { // ships specifically hidden from sensors if (Ships[objp->instance].flags[Ship::Ship_Flags::Hidden_from_sensors]) b->flags |= BLIP_DRAW_DISTORTED; // determine if its AWACS distorted if (awacs_level < 1.0f) b->flags |= BLIP_DRAW_DISTORTED; ship_info *Iff_ship_info = &Ship_info[Ships[objp->instance].ship_info_index]; if (Iff_ship_info->radar_image_2d_idx >= 0 || Iff_ship_info->radar_color_image_2d_idx >= 0) { b->radar_image_2d = Iff_ship_info->radar_image_2d_idx; b->radar_color_image_2d = Iff_ship_info->radar_color_image_2d_idx; b->radar_image_size = Iff_ship_info->radar_image_size; b->radar_projection_size = Iff_ship_info->radar_projection_size_mult; } } // don't distort the sensor blips if the player has primitive sensors and the nebula effect // is not active if (Player_ship->flags[Ship::Ship_Flags::Primitive_sensors]) { if (!(The_mission.flags[Mission::Mission_Flags::Fullneb])) b->flags &= ~BLIP_DRAW_DISTORTED; } N_blips++; } void radar_mission_init() { Blip_last_update.clear(); for (int i=0; i= 0) { gr_init_alphacolor( &Radar_colors[i][j], radar_iff_color[i][j][0], radar_iff_color[i][j][1], radar_iff_color[i][j][2], radar_iff_color[i][j][3] ); } else { gr_init_alphacolor( &Radar_colors[i][j], Radar_color_rgb[i][j].r, Radar_color_rgb[i][j].g, Radar_color_rgb[i][j].b, 255 ); } } } Radar_calc_bright_dist_timer = TIMESTAMP::immediate(); } void radar_null_nblips() { int i; N_blips=0; for (i=0; iflags[Object::Object_Flags::Should_be_dead]) { return NOT_VISIBLE; } if (Player_obj == nullptr) return NOT_VISIBLE; vec3d pos, tempv; float awacs_level; vec3d world_pos = objp->pos; // get team-wide awacs level for the object if not ship int ship_is_visible = 0; if (objp->type == OBJ_SHIP) { if (Player_ship != nullptr) { if (ship_is_visible_by_team(objp, Player_ship)) { ship_is_visible = 1; } } } // only check awacs level if ship is not visible by team awacs_level = 1.5f; if (Player_ship != nullptr && !ship_is_visible) { awacs_level = awacs_get_level(objp, Player_ship); } // if the awacs level is unviewable - bail if(awacs_level < 0.0f && !See_all){ return NOT_VISIBLE; } // Apply object type filters switch (objp->type) { case OBJ_SHIP: if (Ships[objp->instance].flags[Ship::Ship_Flags::Stealth]) return NOT_VISIBLE; // Ships that are warp in in are not visible on the radar if (Ships[objp->instance].is_arriving(ship::warpstage::STAGE1, false)) return NOT_VISIBLE; break; case OBJ_JUMP_NODE: { auto jnp = jumpnode_get_by_objp(objp); // don't plot missing or hidden jump nodes if ( !jnp || jnp->IsHidden() ) return NOT_VISIBLE; // filter jump nodes here if required break; } case OBJ_WEAPON: { // if not a bomb, return if ( !(Weapon_info[Weapons[objp->instance].weapon_info_index].wi_flags[Weapon::Info_Flags::Shown_on_radar]) ) if ( !(Weapon_info[Weapons[objp->instance].weapon_info_index].wi_flags[Weapon::Info_Flags::Bomb]) ) return NOT_VISIBLE; // if explicitly hidden, return if (Weapon_info[Weapons[objp->instance].weapon_info_index].wi_flags[Weapon::Info_Flags::Dont_show_on_radar]) return NOT_VISIBLE; // if we don't attack the bomb, return if ( (!(Weapon_info[Weapons[objp->instance].weapon_info_index].wi_flags[Weapon::Info_Flags::Show_friendly])) && (!iff_x_attacks_y(Player_ship->team, obj_team(objp)))) return NOT_VISIBLE; // if a local ssm is in subspace, return if (Weapons[objp->instance].lssm_stage == 3) return NOT_VISIBLE; break; } // if any other kind of object, don't show it on radar default: return NOT_VISIBLE; } vm_vec_sub(&tempv, &world_pos, &Player_obj->pos); vm_vec_rotate(&pos, &tempv, &Player_obj->orient); // Apply range filter (squared-distance cull first so the sqrt only runs for blips that survive) float max_radar_dist = Radar_ranges[HUD_config.rp_dist]; float dist_sq = vm_vec_mag_squared(&tempv); if (dist_sq > max_radar_dist * max_radar_dist) return NOT_VISIBLE; if (objp->type == OBJ_SHIP) { // ships specifically hidden from sensors if (Ships[objp->instance].flags[Ship::Ship_Flags::Hidden_from_sensors]) return DISTORTED; // determine if its AWACS distorted if (awacs_level < 1.0f) return DISTORTED; } return VISIBLE; }