/* Starshatter: The Open Source Project Copyright (c) 2021-2024, Starshatter: The Open Source Project Contributors Copyright (c) 2011-2012, Starshatter OpenSource Distribution Contributors Copyright (c) 1997-2006, Destroyer Studios LLC. AUTHOR: John DiCamillo OVERVIEW ======== Class for all Multi Function Displays */ #include "Mfd.h" #include "HUDView.h" #include "Ship.h" #include "NavSystem.h" #include "Power.h" #include "Shield.h" #include "Sensor.h" #include "Contact.h" #include "ShipDesign.h" #include "Shot.h" #include "Weapon.h" #include "WeaponGroup.h" #include "Sim.h" #include "StarSystem.h" #include "Drive.h" #include "QuantumDrive.h" #include "Power.h" #include "Instruction.h" #include "NetGame.h" #include "CameraView.h" #include "Color.h" #include "Font.h" #include "FontMgr.h" #include "Window.h" #include "Video.h" #include "Screen.h" #include "Scene.h" #include "Graphic.h" #include "Sprite.h" #include "Keyboard.h" #include "Mouse.h" #include "Game.h" #include "Clock.h" #include "ContentBundle.h" static Bitmap sensor_fov; static Bitmap sensor_fwd; static Bitmap sensor_hsd; static Bitmap sensor_3d; static BYTE* sensor_fov_shade; static BYTE* sensor_fwd_shade; static BYTE* sensor_hsd_shade; static BYTE* sensor_3d_shade; static Color hud_color = Color::Black; static Color txt_color = Color::Black; // +--------------------------------------------------------------------+ MFD::MFD(Window* c, int n) : window(c), rect(0,0,0,0), index(n), mode(MFD_MODE_OFF), sprite(0), ship(0), hidden(true), camview(0), lines(0), mouse_latch(0), mouse_in(false), cockpit_hud_texture(0) { sprite = new Sprite(&sensor_fov); sprite->SetBlendMode(2); sprite->SetFilter(0); sprite->Hide(); Font* font = FontMgr::Find("HUD"); for (int i = 0; i < TXT_LAST; i++) { mfd_text[i].font = font; mfd_text[i].color = Color::White; mfd_text[i].hidden = true; } } MFD::~MFD() { GRAPHIC_DESTROY(sprite); } // +--------------------------------------------------------------------+ void MFD::Initialize() { static int initialized = 0; if (initialized) return; HUDView::PrepareBitmap("sensor_fov.pcx", sensor_fov, sensor_fov_shade); HUDView::PrepareBitmap("sensor_fwd.pcx", sensor_fwd, sensor_fwd_shade); HUDView::PrepareBitmap("sensor_hsd.pcx", sensor_hsd, sensor_hsd_shade); HUDView::PrepareBitmap("sensor_3d.pcx", sensor_3d, sensor_3d_shade); sensor_fov.SetType(Bitmap::BMP_TRANSLUCENT); sensor_fwd.SetType(Bitmap::BMP_TRANSLUCENT); sensor_hsd.SetType(Bitmap::BMP_TRANSLUCENT); sensor_3d.SetType(Bitmap::BMP_TRANSLUCENT); initialized = 1; } void MFD::Close() { sensor_fov.ClearImage(); sensor_fwd.ClearImage(); sensor_hsd.ClearImage(); sensor_3d.ClearImage(); delete [] sensor_fov_shade; delete [] sensor_fwd_shade; delete [] sensor_hsd_shade; delete [] sensor_3d_shade; } // +--------------------------------------------------------------------+ void MFD::UseCameraView(CameraView* v) { if (v && !camview) { camview = v; } } void MFD::SetColor(Color c) { HUDView* hud = HUDView::GetInstance(); if (hud) { hud_color = hud->GetHUDColor(); txt_color = hud->GetTextColor(); } else { hud_color = c; txt_color = c; } HUDView::ColorizeBitmap(sensor_fov, sensor_fov_shade, c); HUDView::ColorizeBitmap(sensor_fwd, sensor_fwd_shade, c); HUDView::ColorizeBitmap(sensor_hsd, sensor_hsd_shade, c); HUDView::ColorizeBitmap(sensor_3d, sensor_3d_shade, c); } void MFD::SetText3DColor(Color c) { for (int i = 0; i < TXT_LAST; i++) mfd_text[i].color = c; } // +--------------------------------------------------------------------+ void MFD::Show() { switch (mode) { case MFD_MODE_FOV: case MFD_MODE_HSD: case MFD_MODE_3D: if (sprite) sprite->Show(); break; } hidden = false; } void MFD::Hide() { if (sprite) sprite->Hide(); for (int i = 0; i < TXT_LAST; i++) HideMFDText(i); hidden = true; } // +--------------------------------------------------------------------+ void MFD::SetRect(const Rect& r) { rect = r; if (sprite) sprite->MoveTo(Point(rect.x + sprite->Width()/2, rect.y + sprite->Height()/2, 1)); } // +--------------------------------------------------------------------+ void MFD::SetMode(int m) { if (m < MFD_MODE_OFF || m > MFD_MODE_3D) mode = MFD_MODE_OFF; else mode = m; sprite->Hide(); for (int i = 0; i < TXT_LAST; i++) HideMFDText(i); switch (mode) { case MFD_MODE_GAME: case MFD_MODE_SHIP: lines = 0; break; case MFD_MODE_FOV: sprite->SetAnimation(&sensor_fov); sprite->Show(); sprite->Reshape(sensor_fov.Width()-8, 16); break; case MFD_MODE_HSD: sprite->SetAnimation(&sensor_hsd); sprite->Show(); sprite->Reshape(sensor_hsd.Width()-8, 16); break; case MFD_MODE_3D: sprite->SetAnimation(&sensor_3d); sprite->Show(); sprite->Reshape(sensor_3d.Width()-8, 16); break; } } // +--------------------------------------------------------------------+ void MFD::Draw() { mouse_in = false; if (Mouse::LButton() == 0) mouse_latch = 0; if (rect.Contains(Mouse::X(), Mouse::Y())) mouse_in = true; // click to turn on MFD when off: if (mode < MFD_MODE_FOV && Mouse::LButton() && !mouse_latch) { mouse_latch = 1; if (mouse_in) { HUDView* hud = HUDView::GetInstance(); if (hud) hud->CycleMFDMode(index); } } for (int i = 0; i < TXT_LAST; i++) HideMFDText(i); if (hidden || mode < MFD_MODE_FOV) { if (cockpit_hud_texture) { int x1 = index*128; int y1 = 256; int x2 = x1 + 128; int y2 = y1 + 128; cockpit_hud_texture->FillRect(x1, y1, x2, y2, Color::Black); } if (hidden) return; } if (sprite && !sprite->Hidden()) { if (cockpit_hud_texture) { int x1 = index*128; int y1 = 256; int w = sprite->Width(); int h = sprite->Height(); cockpit_hud_texture->BitBlt(x1, y1, *sprite->Frame(), 0,0,w,h); } else { int cx = rect.x + rect.w/2; int cy = rect.y + rect.h/2; int w2 = sprite->Width()/2; int h2 = sprite->Height()/2; window->DrawBitmap(cx-w2, cy-h2, cx+w2, cy+h2, sprite->Frame(), Video::BLEND_ALPHA); } } switch (mode) { default: case MFD_MODE_OFF: break; case MFD_MODE_GAME: DrawGameMFD(); break; case MFD_MODE_SHIP: DrawStatusMFD(); break; // sensor sub-modes: case MFD_MODE_FOV: DrawSensorMFD(); break; case MFD_MODE_HSD: DrawHSD(); break; case MFD_MODE_3D: Draw3D(); break; } } // +--------------------------------------------------------------------+ void MFD::DrawSensorLabels(const char* mfd_mode) { Sensor* sensor = ship->GetSensor(); char mode_buf[8] = " "; int scan_r = rect.w; int scan_x = rect.x; int scan_y = rect.y; switch (sensor->GetMode()) { case Sensor::PAS: strcpy_s(mode_buf, ContentBundle::GetInstance()->GetText("MFD.mode.passive").data()); break; case Sensor::STD: strcpy_s(mode_buf, ContentBundle::GetInstance()->GetText("MFD.mode.standard").data()); break; case Sensor::ACM: strcpy_s(mode_buf, ContentBundle::GetInstance()->GetText("MFD.mode.auto-combat").data()); break; case Sensor::GM: strcpy_s(mode_buf, ContentBundle::GetInstance()->GetText("MFD.mode.ground").data()); break; case Sensor::PST: strcpy_s(mode_buf, ContentBundle::GetInstance()->GetText("MFD.mode.passive").data()); break; case Sensor::CST: strcpy_s(mode_buf, ContentBundle::GetInstance()->GetText("MFD.mode.combined").data()); break; default: break; } Rect mode_rect(scan_x+2, scan_y+2, 40, 12); DrawMFDText(0, mode_buf, mode_rect, DT_LEFT); char range_txt[12]; double beam_range = sensor->GetBeamRange() + 1; if (beam_range >= 1e6) sprintf_s(range_txt, "-%dM+", (int) (beam_range / 1e6)); else sprintf_s(range_txt, "-%3d+", (int) (beam_range / 1e3)); Rect range_rect(scan_x+2, scan_y+scan_r-12, 40, 12); DrawMFDText(1, range_txt, range_rect, DT_LEFT); Rect disp_rect(scan_x+scan_r-41, scan_y+2, 40, 12); DrawMFDText(2, mfd_mode, disp_rect, DT_RIGHT); Rect probe_rect(scan_x+scan_r-41, scan_y+scan_r-12, 40, 12); if (ship->GetProbeLauncher()) { char probes[32]; sprintf_s(probes, "%s %02d", ContentBundle::GetInstance()->GetText("MFD.probe").data(), ship->GetProbeLauncher()->Ammo()); DrawMFDText(3, probes, probe_rect, DT_RIGHT); } else { HideMFDText(3); } if (Mouse::LButton() && !mouse_latch) { mouse_latch = 1; if (mode_rect.Contains(Mouse::X(), Mouse::Y())) { if (sensor->GetMode() < Sensor::PST) { int sensor_mode = sensor->GetMode() + 1; if (sensor_mode > Sensor::GM) sensor_mode = Sensor::PAS; sensor->SetMode((Sensor::Mode) sensor_mode); } } else if (range_rect.Contains(Mouse::X(), Mouse::Y())) { if (Mouse::X() > range_rect.x+range_rect.w/2) sensor->IncreaseRange(); else sensor->DecreaseRange(); } else if (disp_rect.Contains(Mouse::X(), Mouse::Y())) { HUDView* hud = HUDView::GetInstance(); if (hud) hud->CycleMFDMode(index); } else if (probe_rect.Contains(Mouse::X(), Mouse::Y())) { ship->LaunchProbe(); } } } // +--------------------------------------------------------------------+ // AZIMUTH-ELEVATION ANGULAR SCANNER void MFD::DrawSensorMFD() { int scan_r = rect.w; int scan_x = cockpit_hud_texture ? (index*128) : rect.x; int scan_y = cockpit_hud_texture ? 256 : rect.y; int r = scan_r / 2; double xctr = (scan_r / 2.0) - 0.5; double yctr = (scan_r / 2.0) + 0.5; Sensor* sensor = ship->GetSensor(); if (!sensor) { DrawMFDText(0, ContentBundle::GetInstance()->GetText("MFD.inactive").data(), rect, DT_CENTER); return; } int w = sprite->Width(); int h = sprite->Height(); if (w < sprite->Frame()->Width()) w += 2; if (h < sprite->Frame()->Height()) h += 16; sprite->Reshape(w, h); sprite->Show(); if (h < sprite->Frame()->Height()) return; double sweep_scale = r / (PI/2); if (sensor->GetBeamLimit() > 90*DEGREES) sweep_scale = (double) r / (90*DEGREES); int az = (int) (sensor->GetBeamLimit() * sweep_scale); int el = az; int xc = (int) (scan_x + xctr); int yc = (int) (scan_y + yctr); if (mode == MFD_MODE_FOV) { if (sensor->GetMode() < Sensor::GM) { if (cockpit_hud_texture) cockpit_hud_texture->DrawEllipse(xc-az, yc-el, xc+az, yc+el, hud_color); else window->DrawEllipse(xc-az, yc-el, xc+az, yc+el, hud_color); } } else { char az_txt[8]; sprintf_s(az_txt, "%d", (int) (sensor->GetBeamLimit() / DEGREES)); Rect az_rect(scan_x+2, scan_y+scan_r-12, 32, 12); DrawMFDText(1, az_txt, az_rect, DT_LEFT); az_rect.x = scan_x + (scan_r/2) - (az_rect.w/2); DrawMFDText(2, "0", az_rect, DT_CENTER); az_rect.x = scan_x + scan_r - az_rect.w - 2; DrawMFDText(3, az_txt, az_rect, DT_RIGHT); } // draw next nav point: Instruction* navpt = ship->GetNextNavPoint(); if (navpt && navpt->Region() == ship->GetRegion()) { const Camera* cam = &ship->Cam(); // translate: Point pt = navpt->Location().OtherHand() - ship->Location(); // rotate: double tx = pt * cam->vrt(); double ty = pt * cam->vup(); double tz = pt * cam->vpn(); if (tz > 1.0) { // convert to spherical coords: double rng = pt.length(); double az = asin(fabs(tx) / rng); double el = asin(fabs(ty) / rng); if (tx < 0) az = -az; if (ty < 0) el = -el; if (fabs(az) < 90*DEGREES) { az *= sweep_scale; el *= sweep_scale; int x = (int) (r + az); int y = (int) (r - el); // clip again: if (x > 0 && x < scan_r && y > 0 && y < scan_r) { // draw: int xc = scan_x + x; int yc = scan_y + y; if (cockpit_hud_texture) { cockpit_hud_texture->DrawLine(xc-2, yc-2, xc+2, yc+2, Color::White); cockpit_hud_texture->DrawLine(xc-2, yc+2, xc+2, yc-2, Color::White); } else { window->DrawLine(xc-2, yc-2, xc+2, yc+2, Color::White); window->DrawLine(xc-2, yc+2, xc+2, yc-2, Color::White); } } } } } int num_contacts = ship->NumContacts(); ListIter iter = ship->ContactList(); while (++iter) { Contact* contact = iter.value(); Ship* c_ship = contact->GetShip(); double az, el, rng; bool aft = false; if (c_ship == ship) continue; contact->GetBearing(ship, az, el, rng); // clip (is in-front): if (fabs(az) < 90*DEGREES) { az *= sweep_scale; el *= sweep_scale; } // rear anulus: else { double len = sqrt(az*az + el*el); if (len > 1e-6) { az = r * az/len; el = r * el/len; } else { az = -r; el = 0; } aft = true; } int x = (int) (r + az); int y = (int) (r - el); // clip again: if (x < 0 || x > scan_r) continue; if (y < 0 || y > scan_r) continue; // draw: Color mark = HUDView::MarkerColor(contact); if (aft) mark = mark * 0.75; int xc = scan_x + x; int yc = scan_y + y; int size = 1; if (c_ship && c_ship == ship->GetTarget()) size = 2; if (cockpit_hud_texture) cockpit_hud_texture->FillRect(xc-size, yc-size, xc+size, yc+size, mark); else window->FillRect(xc-size, yc-size, xc+size, yc+size, mark); if (contact->Threat(ship)) { if (c_ship) { if (cockpit_hud_texture) cockpit_hud_texture->DrawEllipse(xc-4, yc-4, xc+3, yc+3, mark); else window->DrawEllipse(xc-4, yc-4, xc+3, yc+3, mark); } else { if (cockpit_hud_texture) { cockpit_hud_texture->DrawLine(xc, yc-5, xc+5, yc, mark); cockpit_hud_texture->DrawLine(xc+5, yc, xc, yc+5, mark); cockpit_hud_texture->DrawLine(xc, yc+5, xc-5, yc, mark); cockpit_hud_texture->DrawLine(xc-5, yc, xc, yc-5, mark); } else { window->DrawLine(xc, yc-5, xc+5, yc, mark); window->DrawLine(xc+5, yc, xc, yc+5, mark); window->DrawLine(xc, yc+5, xc-5, yc, mark); window->DrawLine(xc-5, yc, xc, yc-5, mark); } } } } DrawSensorLabels(ContentBundle::GetInstance()->GetText("MFD.mode.field-of-view").data()); } // +--------------------------------------------------------------------+ // HORIZONTAL SITUATION DISPLAY void MFD::DrawHSD() { int scan_r = rect.w; int scan_x = cockpit_hud_texture ? (index*128) : rect.x; int scan_y = cockpit_hud_texture ? 256 : rect.y; int r = scan_r / 2 - 4; double xctr = (scan_r / 2.0) - 0.5; double yctr = (scan_r / 2.0) + 0.5; int xc = (int) xctr + scan_x; int yc = (int) yctr + scan_y; Sensor* sensor = ship->GetSensor(); if (!sensor) { DrawMFDText(0, ContentBundle::GetInstance()->GetText("MFD.inactive").data(), rect, DT_CENTER); return; } int w = sprite->Width(); int h = sprite->Height(); if (w < sprite->Frame()->Width()) w += 2; if (h < sprite->Frame()->Height()) h += 16; sprite->Reshape(w, h); sprite->Show(); if (h < sprite->Frame()->Height()) return; if (sensor->GetMode() < Sensor::PST) { double s = sin(sensor->GetBeamLimit()); double c = cos(sensor->GetBeamLimit()); int x0 = (int) (0.1*r*s); int y0 = (int) (0.1*r*c); int x1 = (int) (1.0*r*s); int y1 = (int) (1.0*r*c); if (cockpit_hud_texture) { cockpit_hud_texture->DrawLine(xc-x0, yc-y0, xc-x1, yc-y1, hud_color); cockpit_hud_texture->DrawLine(xc+x0, yc-y0, xc+x1, yc-y1, hud_color); } else { window->DrawLine(xc-x0, yc-y0, xc-x1, yc-y1, hud_color); window->DrawLine(xc+x0, yc-y0, xc+x1, yc-y1, hud_color); } } double rscale = (double) r/(sensor->GetBeamRange()); Camera hsd_cam = ship->Cam(); Point look = ship->Location() + ship->Heading() * 1000; look.y = ship->Location().y; hsd_cam.LookAt(look); // draw tick marks on range rings: for (int dir = 0; dir < 4; dir++) { Point tick; switch (dir) { case 0: tick = Point( 0, 0, 1000); break; case 1: tick = Point( 1000, 0, 0); break; case 2: tick = Point( 0, 0, -1000); break; case 3: tick = Point(-1000, 0, 0); break; } double tx = tick * hsd_cam.vrt(); double tz = tick * hsd_cam.vpn(); double az = asin(fabs(tx) / 1000); if (tx < 0) az = -az; if (tz < 0) if (az < 0) az = -PI - az; else az = PI - az; for (double range = 0.3; range < 1; range += 0.3) { int x0 = (int) (sin(az) * r * range); int y0 = (int) (cos(az) * r * range); int x1 = (int) (sin(az) * r * (range + 0.1)); int y1 = (int) (cos(az) * r * (range + 0.1)); if (cockpit_hud_texture) { cockpit_hud_texture->DrawLine(xc+x0, yc-y0, xc+x1, yc-y1, hud_color); } else { window->DrawLine(xc+x0, yc-y0, xc+x1, yc-y1, hud_color); } } } // draw next nav point: Instruction* navpt = ship->GetNextNavPoint(); if (navpt && navpt->Region() == ship->GetRegion()) { const Camera* cam = &hsd_cam; // translate: Point pt = navpt->Location().OtherHand() - ship->Location(); // rotate: double tx = pt * cam->vrt(); double ty = pt * cam->vup(); double tz = pt * cam->vpn(); // convert to spherical coords: double rng = pt.length(); double az = asin(fabs(tx) / rng); if (rng > sensor->GetBeamRange()) rng = sensor->GetBeamRange(); if (tx < 0) az = -az; if (tz < 0) if (az < 0) az = -PI - az; else az = PI - az; // draw: int x = (int) (xc + sin(az) * rng * rscale); int y = (int) (yc - cos(az) * rng * rscale); if (cockpit_hud_texture) { cockpit_hud_texture->DrawLine(x-2, y-2, x+2, y+2, Color::White); cockpit_hud_texture->DrawLine(x-2, y+2, x+2, y-2, Color::White); } else { window->DrawLine(x-2, y-2, x+2, y+2, Color::White); window->DrawLine(x-2, y+2, x+2, y-2, Color::White); } } // draw contact markers: double limit = sensor->GetBeamRange(); ListIter contact = ship->ContactList(); while (++contact) { Ship* c_ship = contact->GetShip(); if (c_ship == ship) continue; // translate: Point targ_pt = contact->Location() - hsd_cam.Pos(); // rotate: double tx = targ_pt * hsd_cam.vrt(); double rg = contact->Range(ship, limit); double true_range = targ_pt.length(); double az = asin(fabs(tx) / true_range); // clip: if (rg > limit || rg <= 0) continue; if (tx < 0) az = -az; if (!contact->InFront(ship)) if (az < 0) az = -PI - az; else az = PI - az; // draw: int x = (int) (xc + sin(az) * rg * rscale); int y = (int) (yc - cos(az) * rg * rscale); int size = 2; // clip again: if (x < scan_x || y < scan_y) continue; if (c_ship && c_ship == ship->GetTarget()) size = 3; Color mark = HUDView::MarkerColor(contact.value()); if (cockpit_hud_texture) { cockpit_hud_texture->FillRect(x-size, y-size, x+size, y+size, mark); } else { window->FillRect(x-size, y-size, x+size, y+size, mark); } if (contact->Threat(ship)) { if (c_ship) { if (cockpit_hud_texture) { cockpit_hud_texture->DrawEllipse(x-4, y-4, x+3, y+3, mark); } else { window->DrawEllipse(x-4, y-4, x+3, y+3, mark); } } else { if (cockpit_hud_texture) { cockpit_hud_texture->DrawLine(x, y-5, x+5, y, mark); cockpit_hud_texture->DrawLine(x+5, y, x, y+5, mark); cockpit_hud_texture->DrawLine(x, y+5, x-5, y, mark); cockpit_hud_texture->DrawLine(x-5, y, x, y-5, mark); } else { window->DrawLine(x, y-5, x+5, y, mark); window->DrawLine(x+5, y, x, y+5, mark); window->DrawLine(x, y+5, x-5, y, mark); window->DrawLine(x-5, y, x, y-5, mark); } } } } DrawSensorLabels(ContentBundle::GetInstance()->GetText("MFD.mode.horizontal").data()); } // +--------------------------------------------------------------------+ // ELITE-STYLE 3D RADAR void MFD::Draw3D() { int scan_r = rect.w; int scan_x = cockpit_hud_texture ? (index*128) : rect.x; int scan_y = cockpit_hud_texture ? 256 : rect.y; int r = scan_r / 2 - 4; double xctr = (scan_r / 2.0) - 0.5; double yctr = (scan_r / 2.0) + 0.5; int xc = (int) xctr + scan_x; int yc = (int) yctr + scan_y; Sensor* sensor = ship->GetSensor(); if (!sensor) { DrawMFDText(0, ContentBundle::GetInstance()->GetText("MFD.inactive").data(), rect, DT_CENTER); return; } int w = sprite->Width(); int h = sprite->Height(); if (w < sprite->Frame()->Width()) w += 2; if (h < sprite->Frame()->Height()) h += 16; sprite->Reshape(w, h); sprite->Show(); if (h < sprite->Frame()->Height()) return; double rscale = (double) r/(sensor->GetBeamRange()); Camera hsd_cam = ship->Cam(); if (ship->IsStarship()) { Point look = ship->Location() + ship->Heading() * 1000; look.y = ship->Location().y; hsd_cam.LookAt(look); } // draw next nav point: Instruction* navpt = ship->GetNextNavPoint(); if (navpt && navpt->Region() == ship->GetRegion()) { const Camera* cam = &hsd_cam; // translate: Point pt = navpt->Location().OtherHand() - ship->Location(); // rotate: double tx = pt * cam->vrt(); double ty = pt * cam->vup(); double tz = pt * cam->vpn(); // convert to cylindrical coords: double rng = pt.length(); double az = asin(fabs(tx) / rng); if (rng > sensor->GetBeamRange()) rng = sensor->GetBeamRange(); if (tx < 0) az = -az; if (tz < 0) { if (az < 0) az = -PI - az; else az = PI - az; } // accentuate vertical: if (ty > 10) ty = log10(ty-9) * r/8; else if (ty < -10) ty = -log10(9-ty) * r/8; else ty = 0; // draw: int x = (int) (sin(az) * rng * rscale); int y = (int) (cos(az) * rng * rscale/2); int z = (int) (ty); int x0 = xc+x; int y0 = yc-y-z; if (cockpit_hud_texture) { cockpit_hud_texture->DrawLine(x0-2, y0-2, x0+2, y0+2, Color::White); cockpit_hud_texture->DrawLine(x0-2, y0+2, x0+2, y0-2, Color::White); } else { window->DrawLine(x0-2, y0-2, x0+2, y0+2, Color::White); window->DrawLine(x0-2, y0+2, x0+2, y0-2, Color::White); } if (cockpit_hud_texture) { if (z > 0) cockpit_hud_texture->DrawLine(x0, y0+1, x0, y0+z, Color::White); else if (z < 0) cockpit_hud_texture->DrawLine(x0, y0+z, x0, y0-1, Color::White); } else { if (z > 0) window->DrawLine(x0, y0+1, x0, y0+z, Color::White); else if (z < 0) window->DrawLine(x0, y0+z, x0, y0-1, Color::White); } } // draw contact markers: double limit = sensor->GetBeamRange(); ListIter contact = ship->ContactList(); while (++contact) { Ship* c_ship = contact->GetShip(); if (c_ship == ship) continue; // translate: Point targ_pt = contact->Location() - hsd_cam.Pos(); // rotate: double tx = targ_pt * hsd_cam.vrt(); double ty = targ_pt * hsd_cam.vup(); double rg = contact->Range(ship, limit); double true_range = targ_pt.length(); double az = asin(fabs(tx) / true_range); // clip: if (rg > limit || rg <= 0) continue; if (tx < 0) az = -az; if (!contact->InFront(ship)) if (az < 0) az = -PI - az; else az = PI - az; // accentuate vertical: ty *= 4; // draw: int x = (int) (sin(az) * rg * rscale); int y = (int) (cos(az) * rg * rscale/2); int z = (int) (ty * rscale/2); int size = 1; int x0 = xc+x; int y0 = yc-y-z; if (c_ship && c_ship == ship->GetTarget()) size = 2; Color mark = HUDView::MarkerColor(contact.value()); if (cockpit_hud_texture) { cockpit_hud_texture->FillRect(x0-size, y0-size, x0+size, y0+size, mark); if (contact->Threat(ship)) { if (c_ship) { cockpit_hud_texture->DrawEllipse(x0-4, y0-4, x0+3, y0+3, mark); } else { cockpit_hud_texture->DrawLine(x0, y0-5, x0+5, y0, mark); cockpit_hud_texture->DrawLine(x0+5, y0, x0, y0+5, mark); cockpit_hud_texture->DrawLine(x0, y0+5, x0-5, y0, mark); cockpit_hud_texture->DrawLine(x0-5, y0, x0, y0-5, mark); } } if (z > 0) cockpit_hud_texture->FillRect(x0-1, y0+size, x0, y0+z, mark); else if (z < 0) cockpit_hud_texture->FillRect(x0-1, y0+z, x0, y0-size, mark); } else { window->FillRect(x0-size, y0-size, x0+size, y0+size, mark); if (contact->Threat(ship)) { if (c_ship) { window->DrawEllipse(x0-4, y0-4, x0+3, y0+3, mark); } else { window->DrawLine(x0, y0-5, x0+5, y0, mark); window->DrawLine(x0+5, y0, x0, y0+5, mark); window->DrawLine(x0, y0+5, x0-5, y0, mark); window->DrawLine(x0-5, y0, x0, y0-5, mark); } } if (z > 0) window->FillRect(x0-1, y0+size, x0, y0+z, mark); else if (z < 0) window->FillRect(x0-1, y0+z, x0, y0-size, mark); } } DrawSensorLabels(ContentBundle::GetInstance()->GetText("MFD.mode.3D").data()); } // +--------------------------------------------------------------------+ // GROUND MAP void MFD::DrawMap() { Rect text_rect(rect.x, rect.y, rect.w, 12); DrawMFDText(0, ContentBundle::GetInstance()->GetText("MFD.mode.ground").data(), text_rect, DT_CENTER); } // +--------------------------------------------------------------------+ void MFD::DrawGauge(int x, int y, int percent) { if (cockpit_hud_texture) { x += this->index * 128 - this->rect.x; y += 256 - this->rect.y; cockpit_hud_texture->DrawRect(x, y, x+53, y+8, Color::DarkGray); } else { window->DrawRect(x, y, x+53, y+8, Color::DarkGray); } if (percent < 3) return; if (percent > 100) percent = 100; percent /= 2; if (cockpit_hud_texture) cockpit_hud_texture->FillRect(x+2, y+2, x+2+percent, y+7, Color::Gray); else window->FillRect(x+2, y+2, x+2+percent, y+7, Color::Gray); } void MFD::DrawGameMFD() { if (lines < 10) lines++; char txt[64]; Rect txt_rect(rect.x, rect.y, rect.w, 12); int t = 0; if (!HUDView::IsArcade() && HUDView::ShowFPS()) { sprintf_s(txt, "FPS: %6.2f", Clock::GetInstance()->Rate()); DrawMFDText(t++, txt, txt_rect, DT_LEFT); txt_rect.y += 10; if (lines <= 1) return; sprintf_s(txt, "Polys: %d", Video::GetInstance()->GetStats().npolys); DrawMFDText(t++, txt, txt_rect, DT_LEFT); txt_rect.y += 10; } if (ship) { DrawMFDText(t++, ship->Name(), txt_rect, DT_LEFT); txt_rect.y += 10; } if (lines <= 2) return; int hours = (Clock::GetInstance()->GameTime() / 3600000) ; int minutes = (Clock::GetInstance()->GameTime() / 60000) % 60; int seconds = (Clock::GetInstance()->GameTime() / 1000) % 60; if (ship) { DWORD clock = ship->MissionClock(); hours = (clock / 3600000) ; minutes = (clock / 60000) % 60; seconds = (clock / 1000) % 60; } if (static_cast(Clock::GetInstance()->TimeCompression()) != 1) sprintf_s(txt, "%02d:%02d:%02d x%.1f", hours, minutes, seconds, Clock::GetInstance()->TimeCompression()); //-V576 else sprintf_s(txt, "%02d:%02d:%02d", hours, minutes, seconds); DrawMFDText(t++, txt, txt_rect, DT_LEFT); txt_rect.y += 10; if (HUDView::IsArcade() || lines <= 3) return; DrawMFDText(t++, ship->GetRegion()->Name(), txt_rect, DT_LEFT); txt_rect.y += 10; if (lines <= 4) return; if (ship) { switch (ship->GetFlightPhase()) { case Ship::DOCKED: DrawMFDText(t++, ContentBundle::GetInstance()->GetText("MFD.phase.DOCKED").data(), txt_rect, DT_LEFT); break; case Ship::ALERT: DrawMFDText(t++, ContentBundle::GetInstance()->GetText("MFD.phase.ALERT").data(), txt_rect, DT_LEFT); break; case Ship::LOCKED: DrawMFDText(t++, ContentBundle::GetInstance()->GetText("MFD.phase.LOCKED").data(), txt_rect, DT_LEFT); break; case Ship::LAUNCH: DrawMFDText(t++, ContentBundle::GetInstance()->GetText("MFD.phase.LAUNCH").data(), txt_rect, DT_LEFT); break; case Ship::TAKEOFF: DrawMFDText(t++, ContentBundle::GetInstance()->GetText("MFD.phase.TAKEOFF").data(), txt_rect, DT_LEFT); break; case Ship::ACTIVE: DrawMFDText(t++, ContentBundle::GetInstance()->GetText("MFD.phase.ACTIVE").data(), txt_rect, DT_LEFT); break; case Ship::APPROACH: DrawMFDText(t++, ContentBundle::GetInstance()->GetText("MFD.phase.APPROACH").data(), txt_rect, DT_LEFT); break; case Ship::RECOVERY: DrawMFDText(t++, ContentBundle::GetInstance()->GetText("MFD.phase.RECOVERY").data(), txt_rect, DT_LEFT); break; case Ship::DOCKING: DrawMFDText(t++, ContentBundle::GetInstance()->GetText("MFD.phase.DOCKING").data(), txt_rect, DT_LEFT); break; } } } void MFD::DrawStatusMFD() { if (lines < 10) lines++; Rect status_rect(rect.x, rect.y, rect.w, 12); int row = 0; char txt[32]; if (ship) { if (status_rect.y > 320 && !ship->IsStarship()) status_rect.y += 32; Drive* drive = ship->GetDrive(); if (drive) { DrawMFDText(row++, ContentBundle::GetInstance()->GetText("MFD.status.THRUST").data(), status_rect, DT_LEFT); DrawGauge(status_rect.x+70, status_rect.y, (int) ship->Throttle()); status_rect.y += 10; } if (lines <= 1) return; if (ship->Reactors().size() > 0) { PowerSource* reactor = ship->Reactors()[0]; if (reactor) { DrawMFDText(row++, ContentBundle::GetInstance()->GetText("MFD.status.FUEL").data(), status_rect, DT_LEFT); DrawGauge(status_rect.x+70, status_rect.y, reactor->Charge()); status_rect.y += 10; } } if (lines <= 2) return; QuantumDrive* quantum_drive = ship->GetQuantumDrive(); if (quantum_drive) { DrawMFDText(row++, ContentBundle::GetInstance()->GetText("MFD.status.QUANTUM").data(), status_rect, DT_LEFT); DrawGauge(status_rect.x+70, status_rect.y, (int) quantum_drive->Charge()); status_rect.y += 10; } if (lines <= 3) return; double hull = ship->Integrity() / ship->Design()->integrity * 100; int hull_status = System::CRITICAL; if (hull > 66) hull_status = System::NOMINAL; else if (hull > 33) hull_status = System::DEGRADED; DrawMFDText(row++, ContentBundle::GetInstance()->GetText("MFD.status.HULL").data(), status_rect, DT_LEFT); DrawGauge(status_rect.x+70, status_rect.y, (int) hull); status_rect.y += 10; if (lines <= 4) return; Shield* shield = ship->GetShield(); if (shield) { DrawMFDText(row++, ContentBundle::GetInstance()->GetText("MFD.status.SHIELD").data(), status_rect, DT_LEFT); DrawGauge(status_rect.x+70, status_rect.y, ship->ShieldStrength()); status_rect.y += 10; } if (lines <= 5) return; Weapon* primary = ship->GetPrimary(); if (primary) { DrawMFDText(row++, ContentBundle::GetInstance()->GetText("MFD.status.GUNS").data(), status_rect, DT_LEFT); DrawGauge(status_rect.x+70, status_rect.y, primary->Charge()); status_rect.y += 10; } if (lines <= 6) return; if (HUDView::IsArcade()) { for (int i = 0; i < ship->Weapons().size() && i < 4; i++) { WeaponGroup* w = ship->Weapons().at(i); if (w->IsMissile()) { char ammo[8]; if (ship->GetSecondaryGroup() == w) sprintf_s(ammo, "%d *", w->Ammo()); else sprintf_s(ammo, "%d", w->Ammo()); DrawMFDText(row++, (const char*) w->GetDesign()->name, status_rect, DT_LEFT); status_rect.x += 70; DrawMFDText(row++, ammo, status_rect, DT_LEFT); status_rect.x -= 70; status_rect.y += 10; } } if (ship->GetDecoy()) { char ammo[8]; sprintf_s(ammo, "%d", ship->GetDecoy()->Ammo()); DrawMFDText(row++, ContentBundle::GetInstance()->GetText("MFD.status.DECOY").data(), status_rect, DT_LEFT); status_rect.x += 70; DrawMFDText(row++, ammo, status_rect, DT_LEFT); status_rect.x -= 70; status_rect.y += 10; } if (NetGame::GetInstance()) { char lives[8]; sprintf_s(lives, "%d", ship->RespawnCount() + 1); DrawMFDText(row++, ContentBundle::GetInstance()->GetText("MFD.status.LIVES").data(), status_rect, DT_LEFT); status_rect.x += 70; DrawMFDText(row++, lives, status_rect, DT_LEFT); status_rect.x -= 70; status_rect.y += 10; } return; } Sensor* sensor = ship->GetSensor(); if (sensor) { if (ship->GetFlightPhase() != Ship::ACTIVE) { DrawMFDText(row++, ContentBundle::GetInstance()->GetText("MFD.status.SENSOR").data(), status_rect, DT_LEFT); status_rect.x += 70; DrawMFDText(row++, ContentBundle::GetInstance()->GetText("MFD.status.OFFLINE").data(), status_rect, DT_LEFT); status_rect.x -= 70; status_rect.y += 10; } else { DrawMFDText(row++, ContentBundle::GetInstance()->GetText("MFD.status.EMCON").data(), status_rect, DT_LEFT); status_rect.x += 70; sprintf_s(txt, "%s %d", ContentBundle::GetInstance()->GetText("MFD.status.MODE").data(), ship->GetEMCON()); if (!sensor->IsPowerOn() || sensor->GetEnergy() == 0) { if (!Game::GetInstance()->Paused() && (Clock::GetInstance()->RealTime()/1000) & 2) strcpy_s(txt, ContentBundle::GetInstance()->GetText("MFD.status.SENSOR-OFF").data()); } DrawMFDText(row++, txt, status_rect, DT_LEFT); status_rect.x -= 70; status_rect.y += 10; } } if (lines <= 7) return; DrawMFDText(row++, ContentBundle::GetInstance()->GetText("MFD.status.SYSTEMS").data(), status_rect, DT_LEFT); status_rect.x += 70; DrawMFDText(row++, ship->GetDirectorInfo(), status_rect, DT_LEFT); if (NetGame::GetInstance()) { char lives[8]; sprintf_s(lives, "%d", ship->RespawnCount() + 1); status_rect.x -= 70; status_rect.y += 10; DrawMFDText(row++, ContentBundle::GetInstance()->GetText("MFD.status.LIVES").data(), status_rect, DT_LEFT); status_rect.x += 70; DrawMFDText(row++, lives, status_rect, DT_LEFT); } } } // +--------------------------------------------------------------------+ void MFD::SetStatusColor(int status) { Color status_color; switch (status) { default: case System::NOMINAL: status_color = txt_color; break; case System::DEGRADED: status_color = Color(255,255, 0); break; case System::CRITICAL: status_color = Color(255, 0, 0); break; case System::DESTROYED: status_color = Color( 0, 0, 0); break; } } // +--------------------------------------------------------------------+ bool MFD::IsMouseLatched() const { return mouse_in; } // +--------------------------------------------------------------------+ void MFD::DrawMFDText(int index, const char* txt, Rect& txt_rect, int align, int status) { if (index >= MFD::TXT_LAST) { Print("MFD DrawMFDText() invalid mfd_text index %d '%s'\n", index, txt); } else { HUDText& mt = mfd_text[index]; Color mc = mt.color; switch (status) { default: case System::NOMINAL: mc = txt_color; break; case System::DEGRADED: mc = Color(255,255, 0); break; case System::CRITICAL: mc = Color(255, 0, 0); break; case System::DESTROYED: mc = Color( 0, 0, 0); break; } char txt_buf[256]; int n = strlen(txt); if (n > 250) n = 250; int i; for (i = 0; i < n; i++) { if (islower(txt[i])) txt_buf[i] = toupper(txt[i]); else txt_buf[i] = txt[i]; } txt_buf[i] = 0; if (cockpit_hud_texture) { Rect hud_rect(txt_rect); hud_rect.x = txt_rect.x + this->index * 128 - this->rect.x; hud_rect.y = txt_rect.y + 256 - this->rect.y; mt.font->SetColor(mc); mt.font->DrawText(txt_buf, 0, hud_rect, align | DT_SINGLELINE, cockpit_hud_texture); mt.rect = rect; mt.hidden = false; } else { if (txt_rect.Contains(Mouse::X(), Mouse::Y())) mc = Color::White; mt.font->SetColor(mc); mt.font->DrawText(txt_buf, 0, txt_rect, align | DT_SINGLELINE); mt.rect = rect; mt.hidden = false; } } } void MFD::HideMFDText(int index) { if (index >= MFD::TXT_LAST) Print("MFD HideMFDText() invalid mfd_text index %d\n", index); else mfd_text[index].hidden = true; }