1 /* 2 Copyright (c) 2013 yvt 3 based on code of pysnip (c) Mathias Kaerlev 2011-2012. 4 5 This file is part of OpenSpades. 6 7 OpenSpades is free software: you can redistribute it and/or modify 8 it under the terms of the GNU General Public License as published by 9 the Free Software Foundation, either version 3 of the License, or 10 (at your option) any later version. 11 12 OpenSpades is distributed in the hope that it will be useful, 13 but WITHOUT ANY WARRANTY; without even the implied warranty of 14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 GNU General Public License for more details. 16 17 You should have received a copy of the GNU General Public License 18 along with OpenSpades. If not, see <http://www.gnu.org/licenses/>. 19 20 */ 21 22 #include <cstdlib> 23 24 #include "Client.h" 25 26 #include <Core/Bitmap.h> 27 #include <Core/ConcurrentDispatch.h> 28 #include <Core/FileManager.h> 29 #include <Core/Settings.h> 30 #include <Core/Strings.h> 31 32 #include "IAudioChunk.h" 33 #include "IAudioDevice.h" 34 35 #include "CenterMessageView.h" 36 #include "ChatWindow.h" 37 #include "ClientPlayer.h" 38 #include "ClientUI.h" 39 #include "Corpse.h" 40 #include "FallingBlock.h" 41 #include "Fonts.h" 42 #include "HurtRingView.h" 43 #include "IFont.h" 44 #include "ILocalEntity.h" 45 #include "LimboView.h" 46 #include "MapView.h" 47 #include "PaletteView.h" 48 #include "ParticleSpriteEntity.h" 49 #include "ScoreboardView.h" 50 #include "SmokeSpriteEntity.h" 51 #include "TCProgressView.h" 52 #include "Tracer.h" 53 #include "IGameMode.h" 54 #include "CTFGameMode.h" 55 56 #include "GameMap.h" 57 #include "Grenade.h" 58 #include "Weapon.h" 59 #include "World.h" 60 61 #include "NetClient.h" 62 63 DEFINE_SPADES_SETTING(cg_hitIndicator, "1"); 64 DEFINE_SPADES_SETTING(cg_debugAim, "0"); 65 SPADES_SETTING(cg_keyReloadWeapon); 66 SPADES_SETTING(cg_keyJump); 67 SPADES_SETTING(cg_keyAttack); 68 SPADES_SETTING(cg_keyAltAttack); 69 SPADES_SETTING(cg_keyCrouch); 70 DEFINE_SPADES_SETTING(cg_screenshotFormat, "jpeg"); 71 DEFINE_SPADES_SETTING(cg_stats, "0"); 72 DEFINE_SPADES_SETTING(cg_hideHud, "0"); 73 DEFINE_SPADES_SETTING(cg_playerNames, "2"); 74 DEFINE_SPADES_SETTING(cg_playerNameX, "0"); 75 DEFINE_SPADES_SETTING(cg_playerNameY, "0"); 76 77 namespace spades { 78 namespace client { 79 80 enum class ScreenshotFormat { Jpeg, Targa, Png }; 81 82 namespace { GetScreenshotFormat()83 ScreenshotFormat GetScreenshotFormat() { 84 if (EqualsIgnoringCase(cg_screenshotFormat, "jpeg")) { 85 return ScreenshotFormat::Jpeg; 86 } else if (EqualsIgnoringCase(cg_screenshotFormat, "targa")) { 87 return ScreenshotFormat::Targa; 88 } else if (EqualsIgnoringCase(cg_screenshotFormat, "png")) { 89 return ScreenshotFormat::Png; 90 } else { 91 SPRaise("Invalid screenshot format: %s", cg_screenshotFormat.CString()); 92 } 93 } 94 TranslateKeyName(const std::string & name)95 std::string TranslateKeyName(const std::string &name) { 96 if (name == "LeftMouseButton") { 97 return "LMB"; 98 } else if (name == "RightMouseButton") { 99 return "RMB"; 100 } else if (name.empty()) { 101 return _Tr("Client", "Unbound"); 102 } else { 103 return name; 104 } 105 } 106 } 107 TakeScreenShot(bool sceneOnly)108 void Client::TakeScreenShot(bool sceneOnly) { 109 SceneDefinition sceneDef = CreateSceneDefinition(); 110 lastSceneDef = sceneDef; 111 112 // render scene 113 flashDlights = flashDlightsOld; 114 DrawScene(); 115 116 // draw 2d 117 if (!sceneOnly) 118 Draw2D(); 119 120 // Well done! 121 renderer->FrameDone(); 122 123 Handle<Bitmap> bmp(renderer->ReadBitmap(), false); 124 // force 100% opacity 125 126 uint32_t *pixels = bmp->GetPixels(); 127 for (size_t i = bmp->GetWidth() * bmp->GetHeight(); i > 0; i--) { 128 *(pixels++) |= 0xff000000UL; 129 } 130 131 try { 132 std::string name = ScreenShotPath(); 133 bmp->Save(name); 134 135 std::string msg; 136 if (sceneOnly) 137 msg = _Tr("Client", "Sceneshot saved: {0}", name); 138 else 139 msg = _Tr("Client", "Screenshot saved: {0}", name); 140 ShowAlert(msg, AlertType::Notice); 141 } catch (const Exception &ex) { 142 std::string msg; 143 msg = _Tr("Client", "Screenshot failed: "); 144 msg += ex.GetShortMessage(); 145 ShowAlert(msg, AlertType::Error); 146 SPLog("Screenshot failed: %s", ex.what()); 147 } catch (const std::exception &ex) { 148 std::string msg; 149 msg = _Tr("Client", "Screenshot failed: "); 150 msg += ex.what(); 151 ShowAlert(msg, AlertType::Error); 152 SPLog("Screenshot failed: %s", ex.what()); 153 } 154 } 155 ScreenShotPath()156 std::string Client::ScreenShotPath() { 157 char bufJpeg[256]; 158 char bufTarga[256]; 159 char bufPng[256]; 160 for (int i = 0; i < 10000; i++) { 161 sprintf(bufJpeg, "Screenshots/shot%04d.jpg", nextScreenShotIndex); 162 sprintf(bufTarga, "Screenshots/shot%04d.tga", nextScreenShotIndex); 163 sprintf(bufPng, "Screenshots/shot%04d.png", nextScreenShotIndex); 164 if (FileManager::FileExists(bufJpeg) || FileManager::FileExists(bufTarga) || 165 FileManager::FileExists(bufPng)) { 166 nextScreenShotIndex++; 167 if (nextScreenShotIndex >= 10000) 168 nextScreenShotIndex = 0; 169 continue; 170 } 171 172 switch (GetScreenshotFormat()) { 173 case ScreenshotFormat::Jpeg: return bufJpeg; 174 case ScreenshotFormat::Targa: return bufTarga; 175 case ScreenshotFormat::Png: return bufPng; 176 } 177 SPAssert(false); 178 } 179 180 SPRaise("No free file name"); 181 } 182 183 #pragma mark - HUD Drawings 184 DrawSplash()185 void Client::DrawSplash() { 186 Handle<IImage> img; 187 Vector2 siz; 188 Vector2 scrSize = {renderer->ScreenWidth(), renderer->ScreenHeight()}; 189 190 renderer->SetColorAlphaPremultiplied(MakeVector4(0, 0, 0, 1)); 191 img = renderer->RegisterImage("Gfx/White.tga"); 192 renderer->DrawImage(img, AABB2(0, 0, scrSize.x, scrSize.y)); 193 194 renderer->SetColorAlphaPremultiplied(MakeVector4(1, 1, 1, 1.)); 195 img = renderer->RegisterImage("Gfx/Title/Logo.png"); 196 197 siz = MakeVector2(img->GetWidth(), img->GetHeight()); 198 siz *= std::min(1.f, scrSize.x / siz.x * 0.5f); 199 siz *= std::min(1.f, scrSize.y / siz.y); 200 201 renderer->DrawImage( 202 img, AABB2((scrSize.x - siz.x) * .5f, (scrSize.y - siz.y) * .5f, siz.x, siz.y)); 203 } 204 DrawStartupScreen()205 void Client::DrawStartupScreen() { 206 Handle<IImage> img; 207 Vector2 scrSize = {renderer->ScreenWidth(), renderer->ScreenHeight()}; 208 209 renderer->SetColorAlphaPremultiplied(MakeVector4(0, 0, 0, 1.)); 210 img = renderer->RegisterImage("Gfx/White.tga"); 211 renderer->DrawImage(img, AABB2(0, 0, scrSize.x, scrSize.y)); 212 213 DrawSplash(); 214 215 IFont *font = fontManager->GetGuiFont(); 216 std::string str = _Tr("Client", "NOW LOADING"); 217 Vector2 size = font->Measure(str); 218 Vector2 pos = MakeVector2(scrSize.x - 16.f, scrSize.y - 16.f); 219 pos -= size; 220 font->DrawShadow(str, pos, 1.f, MakeVector4(1, 1, 1, 1), MakeVector4(0, 0, 0, 0.5)); 221 222 renderer->FrameDone(); 223 renderer->Flip(); 224 } 225 DrawDisconnectScreen()226 void Client::DrawDisconnectScreen() {} 227 DrawHurtSprites()228 void Client::DrawHurtSprites() { 229 float per = (world->GetTime() - lastHurtTime) / 1.5f; 230 if (per > 1.f) 231 return; 232 if (per < 0.f) 233 return; 234 Handle<IImage> img = renderer->RegisterImage("Gfx/HurtSprite.png"); 235 236 Vector2 scrSize = {renderer->ScreenWidth(), renderer->ScreenHeight()}; 237 Vector2 scrCenter = scrSize * .5f; 238 float radius = scrSize.GetLength() * .5f; 239 240 for (size_t i = 0; i < hurtSprites.size(); i++) { 241 HurtSprite &spr = hurtSprites[i]; 242 float alpha = spr.strength - per; 243 if (alpha < 0.f) 244 continue; 245 if (alpha > 1.f) 246 alpha = 1.f; 247 248 Vector2 radDir = {cosf(spr.angle), sinf(spr.angle)}; 249 Vector2 angDir = {-sinf(spr.angle), cosf(spr.angle)}; 250 float siz = spr.scale * radius; 251 Vector2 base = radDir * radius + scrCenter; 252 Vector2 centVect = radDir * (-siz); 253 Vector2 sideVect1 = angDir * (siz * 4.f * (spr.horzShift)); 254 Vector2 sideVect2 = angDir * (siz * 4.f * (spr.horzShift - 1.f)); 255 256 Vector2 v1 = base + centVect + sideVect1; 257 Vector2 v2 = base + centVect + sideVect2; 258 Vector2 v3 = base + sideVect1; 259 260 renderer->SetColorAlphaPremultiplied(MakeVector4(0.f, 0.f, 0.f, alpha)); 261 renderer->DrawImage(img, v1, v2, v3, 262 AABB2(0, 8.f, img->GetWidth(), img->GetHeight())); 263 } 264 } 265 DrawHurtScreenEffect()266 void Client::DrawHurtScreenEffect() { 267 SPADES_MARK_FUNCTION(); 268 269 float scrWidth = renderer->ScreenWidth(); 270 float scrHeight = renderer->ScreenHeight(); 271 float wTime = world->GetTime(); 272 Player *p = GetWorld()->GetLocalPlayer(); 273 if (wTime < lastHurtTime + .35f && wTime >= lastHurtTime) { 274 float per = (wTime - lastHurtTime) / .35f; 275 per = 1.f - per; 276 per *= .3f + (1.f - p->GetHealth() / 100.f) * .7f; 277 per = std::min(per, 0.9f); 278 per = 1.f - per; 279 Vector3 color = {1.f, per, per}; 280 renderer->MultiplyScreenColor(color); 281 renderer->SetColorAlphaPremultiplied( 282 MakeVector4((1.f - per) * .1f, 0, 0, (1.f - per) * .1f)); 283 renderer->DrawImage(renderer->RegisterImage("Gfx/White.tga"), 284 AABB2(0, 0, scrWidth, scrHeight)); 285 } 286 } 287 DrawHottrackedPlayerName()288 void Client::DrawHottrackedPlayerName() { 289 SPADES_MARK_FUNCTION(); 290 291 if ((int)cg_playerNames == 0) 292 return; 293 294 Player *p = GetWorld()->GetLocalPlayer(); 295 296 hitTag_t tag = hit_None; 297 Player *hottracked = HotTrackedPlayer(&tag); 298 if (hottracked) { 299 Vector3 posxyz = Project(hottracked->GetEye()); 300 Vector2 pos = {posxyz.x, posxyz.y}; 301 char buf[64]; 302 if ((int)cg_playerNames == 1) { 303 float dist = (hottracked->GetEye() - p->GetEye()).GetLength(); 304 int idist = (int)floorf(dist + .5f); 305 sprintf(buf, "%s [%d%s]", hottracked->GetName().c_str(), idist, 306 (idist == 1) ? "block" : "blocks"); 307 } else 308 sprintf(buf, "%s", hottracked->GetName().c_str()); 309 310 pos.y += (int)cg_playerNameY; 311 pos.x += (int)cg_playerNameX; 312 313 IFont *font = fontManager->GetGuiFont(); 314 Vector2 size = font->Measure(buf); 315 pos.x -= size.x * .5f; 316 pos.y -= size.y; 317 font->DrawShadow(buf, pos, 1.f, MakeVector4(1, 1, 1, 1), MakeVector4(0, 0, 0, 0.5)); 318 } 319 } 320 DrawDebugAim()321 void Client::DrawDebugAim() { 322 SPADES_MARK_FUNCTION(); 323 324 // float scrWidth = renderer->ScreenWidth(); 325 // float scrHeight = renderer->ScreenHeight(); 326 // float wTime = world->GetTime(); 327 Player &p = GetCameraTargetPlayer(); 328 // IFont *font; 329 330 Weapon &w = *p.GetWeapon(); 331 float spread = w.GetSpread(); 332 333 AABB2 boundary(0, 0, 0, 0); 334 for (int i = 0; i < 8; i++) { 335 Vector3 vec = p.GetFront(); 336 if (i & 1) 337 vec.x += spread; 338 else 339 vec.x -= spread; 340 if (i & 2) 341 vec.y += spread; 342 else 343 vec.y -= spread; 344 if (i & 4) 345 vec.z += spread; 346 else 347 vec.z -= spread; 348 349 Vector3 viewPos; 350 viewPos.x = Vector3::Dot(vec, p.GetRight()); 351 viewPos.y = Vector3::Dot(vec, p.GetUp()); 352 viewPos.z = Vector3::Dot(vec, p.GetFront()); 353 354 Vector2 p; 355 p.x = viewPos.x / viewPos.z; 356 p.y = viewPos.y / viewPos.z; 357 boundary.min.x = std::min(boundary.min.x, p.x); 358 boundary.min.y = std::min(boundary.min.y, p.y); 359 boundary.max.x = std::max(boundary.max.x, p.x); 360 boundary.max.y = std::max(boundary.max.y, p.y); 361 } 362 363 Handle<IImage> img = renderer->RegisterImage("Gfx/White.tga"); 364 boundary.min *= renderer->ScreenHeight() * .5f; 365 boundary.max *= renderer->ScreenHeight() * .5f; 366 boundary.min /= tanf(lastSceneDef.fovY * .5f); 367 boundary.max /= tanf(lastSceneDef.fovY * .5f); 368 IntVector3 cent; 369 cent.x = (int)(renderer->ScreenWidth() * .5f); 370 cent.y = (int)(renderer->ScreenHeight() * .5f); 371 372 IntVector3 p1 = cent; 373 IntVector3 p2 = cent; 374 375 p1.x += (int)floorf(boundary.min.x); 376 p1.y += (int)floorf(boundary.min.y); 377 p2.x += (int)ceilf(boundary.max.x); 378 p2.y += (int)ceilf(boundary.max.y); 379 380 renderer->SetColorAlphaPremultiplied(MakeVector4(0, 0, 0, 1)); 381 renderer->DrawImage(img, AABB2(p1.x - 2, p1.y - 2, p2.x - p1.x + 4, 1)); 382 renderer->DrawImage(img, AABB2(p1.x - 2, p1.y - 2, 1, p2.y - p1.y + 4)); 383 renderer->DrawImage(img, AABB2(p1.x - 2, p2.y + 1, p2.x - p1.x + 4, 1)); 384 renderer->DrawImage(img, AABB2(p2.x + 1, p1.y - 2, 1, p2.y - p1.y + 4)); 385 386 renderer->SetColorAlphaPremultiplied(MakeVector4(1, 1, 1, 1)); 387 renderer->DrawImage(img, AABB2(p1.x - 1, p1.y - 1, p2.x - p1.x + 2, 1)); 388 renderer->DrawImage(img, AABB2(p1.x - 1, p1.y - 1, 1, p2.y - p1.y + 2)); 389 renderer->DrawImage(img, AABB2(p1.x - 1, p2.y, p2.x - p1.x + 2, 1)); 390 renderer->DrawImage(img, AABB2(p2.x, p1.y - 1, 1, p2.y - p1.y + 2)); 391 } 392 DrawFirstPersonHUD()393 void Client::DrawFirstPersonHUD() { 394 SPADES_MARK_FUNCTION(); 395 396 float scrWidth = renderer->ScreenWidth(); 397 float scrHeight = renderer->ScreenHeight(); 398 399 Player &player = GetCameraTargetPlayer(); 400 int playerId = GetCameraTargetPlayerId(); 401 402 clientPlayers[playerId]->Draw2D(); 403 404 if (cg_hitIndicator && hitFeedbackIconState > 0.f && !cg_hideHud) { 405 Handle<IImage> img(renderer->RegisterImage("Gfx/HitFeedback.png"), false); 406 Vector2 pos = {scrWidth * .5f, scrHeight * .5f}; 407 pos.x -= img->GetWidth() * .5f; 408 pos.y -= img->GetHeight() * .5f; 409 410 float op = hitFeedbackIconState; 411 Vector4 color; 412 if (hitFeedbackFriendly) { 413 color = MakeVector4(0.02f, 1.f, 0.02f, 1.f); 414 } else { 415 color = MakeVector4(1.f, 0.02f, 0.04f, 1.f); 416 } 417 color *= op; 418 419 renderer->SetColorAlphaPremultiplied(color); 420 421 renderer->DrawImage(img, pos); 422 } 423 424 // If the player has the intel, display an intel icon 425 IGameMode &mode = *world->GetMode(); 426 if (mode.ModeType() == IGameMode::m_CTF) { 427 auto &ctfMode = static_cast<CTFGameMode &>(mode); 428 if (ctfMode.PlayerHasIntel(*world, player)) { 429 Handle<IImage> img(renderer->RegisterImage("Gfx/Intel.png"), false); 430 431 // Strobe 432 Vector4 color {1.0f, 1.0f, 1.0f, 1.0f}; 433 color *= std::fabs(std::sin(world->GetTime() * 2.0f)); 434 435 renderer->SetColorAlphaPremultiplied(color); 436 437 renderer->DrawImage(img, Vector2{scrWidth - 260.f, scrHeight - 64.0f}); 438 } 439 } 440 441 if (cg_debugAim && player.GetTool() == Player::ToolWeapon && player.IsAlive()) { 442 DrawDebugAim(); 443 } 444 } 445 DrawJoinedAlivePlayerHUD()446 void Client::DrawJoinedAlivePlayerHUD() { 447 SPADES_MARK_FUNCTION(); 448 449 float scrWidth = renderer->ScreenWidth(); 450 float scrHeight = renderer->ScreenHeight(); 451 Player *p = GetWorld()->GetLocalPlayer(); 452 IFont *font; 453 454 // Draw damage rings 455 if (!cg_hideHud) 456 hurtRingView->Draw(); 457 458 if (!cg_hideHud) { 459 // Draw ammo amount 460 // (Note: this cannot be displayed for a spectated player --- the server 461 // does not submit sufficient information) 462 Weapon *weap = p->GetWeapon(); 463 Handle<IImage> ammoIcon; 464 float iconWidth, iconHeight; 465 float spacing = 2.f; 466 int stockNum; 467 int warnLevel; 468 469 if (p->IsToolWeapon()) { 470 switch (weap->GetWeaponType()) { 471 case RIFLE_WEAPON: 472 ammoIcon = renderer->RegisterImage("Gfx/Bullet/7.62mm.png"); 473 iconWidth = 6.f; 474 iconHeight = iconWidth * 4.f; 475 break; 476 case SMG_WEAPON: 477 ammoIcon = renderer->RegisterImage("Gfx/Bullet/9mm.png"); 478 iconWidth = 4.f; 479 iconHeight = iconWidth * 4.f; 480 break; 481 case SHOTGUN_WEAPON: 482 ammoIcon = renderer->RegisterImage("Gfx/Bullet/12gauge.png"); 483 iconWidth = 30.f; 484 iconHeight = iconWidth / 4.f; 485 spacing = -6.f; 486 break; 487 default: SPInvalidEnum("weap->GetWeaponType()", weap->GetWeaponType()); 488 } 489 490 int clipSize = weap->GetClipSize(); 491 int clip = weap->GetAmmo(); 492 493 clipSize = std::max(clipSize, clip); 494 495 for (int i = 0; i < clipSize; i++) { 496 float x = scrWidth - 16.f - (float)(i + 1) * (iconWidth + spacing); 497 float y = scrHeight - 16.f - iconHeight; 498 499 if (clip >= i + 1) { 500 renderer->SetColorAlphaPremultiplied(MakeVector4(1, 1, 1, 1)); 501 } else { 502 renderer->SetColorAlphaPremultiplied(MakeVector4(0.4, 0.4, 0.4, 1)); 503 } 504 505 renderer->DrawImage(ammoIcon, AABB2(x, y, iconWidth, iconHeight)); 506 } 507 508 stockNum = weap->GetStock(); 509 warnLevel = weap->GetMaxStock() / 3; 510 } else { 511 iconHeight = 0.f; 512 warnLevel = 0; 513 514 switch (p->GetTool()) { 515 case Player::ToolSpade: 516 case Player::ToolBlock: stockNum = p->GetNumBlocks(); break; 517 case Player::ToolGrenade: stockNum = p->GetNumGrenades(); break; 518 default: SPInvalidEnum("p->GetTool()", p->GetTool()); 519 } 520 } 521 522 Vector4 numberColor = {1, 1, 1, 1}; 523 524 if (stockNum == 0) { 525 numberColor.y = 0.3f; 526 numberColor.z = 0.3f; 527 } else if (stockNum <= warnLevel) { 528 numberColor.z = 0.3f; 529 } 530 531 char buf[64]; 532 sprintf(buf, "%d", stockNum); 533 font = fontManager->GetSquareDesignFont(); 534 std::string stockStr = buf; 535 Vector2 size = font->Measure(stockStr); 536 Vector2 pos = MakeVector2(scrWidth - 16.f, scrHeight - 16.f - iconHeight); 537 pos -= size; 538 font->DrawShadow(stockStr, pos, 1.f, numberColor, MakeVector4(0, 0, 0, 0.5)); 539 540 // draw "press ... to reload" 541 { 542 std::string msg = ""; 543 544 switch (p->GetTool()) { 545 case Player::ToolBlock: 546 if (p->GetNumBlocks() == 0) { 547 msg = _Tr("Client", "Out of Block"); 548 } 549 break; 550 case Player::ToolGrenade: 551 if (p->GetNumGrenades() == 0) { 552 msg = _Tr("Client", "Out of Grenade"); 553 } 554 break; 555 case Player::ToolWeapon: { 556 Weapon *weap = p->GetWeapon(); 557 if (weap->IsReloading() || p->IsAwaitingReloadCompletion()) { 558 msg = _Tr("Client", "Reloading"); 559 } else if (weap->GetAmmo() == 0 && weap->GetStock() == 0) { 560 msg = _Tr("Client", "Out of Ammo"); 561 } else if (weap->GetStock() > 0 && 562 weap->GetAmmo() < weap->GetClipSize() / 4) { 563 msg = _Tr("Client", "Press [{0}] to Reload", 564 TranslateKeyName(cg_keyReloadWeapon)); 565 } 566 } break; 567 default:; 568 // no message 569 } 570 571 if (!msg.empty()) { 572 font = fontManager->GetGuiFont(); 573 Vector2 size = font->Measure(msg); 574 Vector2 pos = MakeVector2((scrWidth - size.x) * .5f, scrHeight * 2.f / 3.f); 575 font->DrawShadow(msg, pos, 1.f, MakeVector4(1, 1, 1, 1), 576 MakeVector4(0, 0, 0, 0.5)); 577 } 578 } 579 580 if (p->GetTool() == Player::ToolBlock) { 581 paletteView->Draw(); 582 } 583 584 // draw map 585 mapView->Draw(); 586 587 DrawHealth(); 588 } 589 } 590 DrawDeadPlayerHUD()591 void Client::DrawDeadPlayerHUD() { 592 SPADES_MARK_FUNCTION(); 593 594 Player *p = GetWorld()->GetLocalPlayer(); 595 IFont *font; 596 float scrWidth = renderer->ScreenWidth(); 597 float scrHeight = renderer->ScreenHeight(); 598 599 if (!cg_hideHud) { 600 // draw respawn tme 601 if (!p->IsAlive()) { 602 std::string msg; 603 604 float secs = p->GetRespawnTime() - world->GetTime(); 605 606 if (secs > 0.f) 607 msg = _Tr("Client", "You will respawn in: {0}", (int)ceilf(secs)); 608 else 609 msg = _Tr("Client", "Waiting for respawn"); 610 611 if (!msg.empty()) { 612 font = fontManager->GetGuiFont(); 613 Vector2 size = font->Measure(msg); 614 Vector2 pos = MakeVector2((scrWidth - size.x) * .5f, scrHeight / 3.f); 615 616 font->DrawShadow(msg, pos, 1.f, MakeVector4(1, 1, 1, 1), 617 MakeVector4(0, 0, 0, 0.5)); 618 } 619 } 620 } 621 } 622 DrawSpectateHUD()623 void Client::DrawSpectateHUD() { 624 SPADES_MARK_FUNCTION(); 625 626 if (cg_hideHud) { 627 return; 628 } 629 630 IFont &font = *fontManager->GetGuiFont(); 631 float scrWidth = renderer->ScreenWidth(); 632 633 float textX = scrWidth - 8.0f; 634 float textY = 256.0f + 32.0f; 635 636 auto addLine = [&](const std::string &text) { 637 Vector2 size = font.Measure(text); 638 Vector2 pos = MakeVector2(textX, textY); 639 pos.x -= size.x; 640 textY += 20.0f; 641 font.DrawShadow(text, pos, 1.f, MakeVector4(1, 1, 1, 1), MakeVector4(0, 0, 0, 0.5)); 642 }; 643 644 if (HasTargetPlayer(GetCameraMode())) { 645 addLine(_Tr("Client", "Following {0}", 646 world->GetPlayerPersistent(GetCameraTargetPlayerId()).name)); 647 } 648 649 textY += 10.0f; 650 651 // Help messages (make sure to synchronize these with the keyboard input handler) 652 if (FollowsNonLocalPlayer(GetCameraMode())) { 653 if (GetCameraTargetPlayer().IsAlive()) { 654 addLine(_Tr("Client", "[{0}] Cycle camera mode", TranslateKeyName(cg_keyJump))); 655 } 656 addLine(_Tr("Client", "[{0}/{1}] Next/previous player", 657 TranslateKeyName(cg_keyAttack), TranslateKeyName(cg_keyAltAttack))); 658 659 if (GetWorld()->GetLocalPlayer()->IsSpectator()) { 660 addLine(_Tr("Client", "[{0}] Unfollow", TranslateKeyName(cg_keyReloadWeapon))); 661 } 662 } else { 663 addLine(_Tr("Client", "[{0}/{1}] Follow a player", TranslateKeyName(cg_keyAttack), 664 TranslateKeyName(cg_keyAltAttack))); 665 } 666 667 if (GetCameraMode() == ClientCameraMode::Free) { 668 addLine(_Tr("Client", "[{0}/{1}] Go up/down", TranslateKeyName(cg_keyJump), 669 TranslateKeyName(cg_keyCrouch))); 670 } 671 672 mapView->Draw(); 673 } 674 DrawAlert()675 void Client::DrawAlert() { 676 SPADES_MARK_FUNCTION(); 677 678 IFont *font = fontManager->GetGuiFont(); 679 float scrWidth = renderer->ScreenWidth(); 680 float scrHeight = renderer->ScreenHeight(); 681 auto &r = renderer; 682 683 const float fadeOutTime = 1.f; 684 685 float fade = 1.f - (time - alertDisappearTime) / fadeOutTime; 686 fade = std::min(fade, 1.f); 687 if (fade <= 0.f) { 688 return; 689 } 690 691 float borderFade = 1.f - (time - alertAppearTime) * 1.5f; 692 borderFade = std::max(std::min(borderFade, 1.f), 0.f); 693 borderFade *= fade; 694 695 Handle<IImage> alertIcon(renderer->RegisterImage("Gfx/AlertIcon.png"), false); 696 697 Vector2 textSize = font->Measure(alertContents); 698 Vector2 contentsSize = textSize; 699 contentsSize.y = std::max(contentsSize.y, 16.f); 700 if (alertType != AlertType::Notice) { 701 contentsSize.x += 22.f; 702 } 703 704 // add margin 705 const float margin = 8.f; 706 contentsSize.x += margin * 2.f; 707 contentsSize.y += margin * 2.f; 708 709 contentsSize.x = floorf(contentsSize.x); 710 contentsSize.y = floorf(contentsSize.y); 711 712 Vector2 pos = (Vector2(scrWidth, scrHeight) - contentsSize) * Vector2(0.5f, 0.7f); 713 pos.y += 40.f; 714 715 pos.x = floorf(pos.x); 716 pos.y = floorf(pos.y); 717 718 Vector4 color; 719 720 // draw border 721 switch (alertType) { 722 case AlertType::Notice: color = Vector4(0.f, 0.f, 0.f, 0.f); break; 723 case AlertType::Warning: color = Vector4(1.f, 1.f, 0.f, .7f); break; 724 case AlertType::Error: color = Vector4(1.f, 0.f, 0.f, .7f); break; 725 } 726 color *= borderFade; 727 r->SetColorAlphaPremultiplied(color); 728 729 const float border = 1.f; 730 r->DrawImage(nullptr, AABB2(pos.x - border, pos.y - border, 731 contentsSize.x + border * 2.f, border)); 732 r->DrawImage(nullptr, AABB2(pos.x - border, pos.y + contentsSize.y, 733 contentsSize.x + border * 2.f, border)); 734 735 r->DrawImage(nullptr, AABB2(pos.x - border, pos.y, border, contentsSize.y)); 736 r->DrawImage(nullptr, AABB2(pos.x + contentsSize.x, pos.y, border, contentsSize.y)); 737 738 // fill background 739 color = Vector4(0.f, 0.f, 0.f, fade * 0.5f); 740 r->SetColorAlphaPremultiplied(color); 741 r->DrawImage(nullptr, AABB2(pos.x, pos.y, contentsSize.x, contentsSize.y)); 742 743 // draw icon 744 switch (alertType) { 745 case AlertType::Notice: color = Vector4(0.f, 0.f, 0.f, 0.f); break; 746 case AlertType::Warning: color = Vector4(1.f, 1.f, 0.f, 1.f); break; 747 case AlertType::Error: color = Vector4(1.f, 0.f, 0.f, 1.f); break; 748 } 749 color *= fade; 750 r->SetColorAlphaPremultiplied(color); 751 752 r->DrawImage(alertIcon, 753 Vector2(pos.x + margin, pos.y + (contentsSize.y - 16.f) * 0.5f)); 754 755 // draw text 756 color = Vector4(1.f, 1.f, 1.f, 1.f); 757 color *= fade; 758 759 font->DrawShadow(alertContents, Vector2(pos.x + contentsSize.x - textSize.x - margin, 760 pos.y + (contentsSize.y - textSize.y) * 0.5f), 761 1.f, color, Vector4(0.f, 0.f, 0.f, fade * 0.5f)); 762 } 763 DrawHealth()764 void Client::DrawHealth() { 765 SPADES_MARK_FUNCTION(); 766 767 Player *p = GetWorld()->GetLocalPlayer(); 768 IFont *font; 769 // float scrWidth = renderer->ScreenWidth(); 770 float scrHeight = renderer->ScreenHeight(); 771 772 std::string str = std::to_string(p->GetHealth()); 773 774 Vector4 numberColor = {1, 1, 1, 1}; 775 776 if (p->GetHealth() == 0) { 777 numberColor.y = 0.3f; 778 numberColor.z = 0.3f; 779 } else if (p->GetHealth() <= 50) { 780 numberColor.z = 0.3f; 781 } 782 783 font = fontManager->GetSquareDesignFont(); 784 Vector2 size = font->Measure(str); 785 Vector2 pos = MakeVector2(16.f, scrHeight - 16.f); 786 pos.y -= size.y; 787 font->DrawShadow(str, pos, 1.f, numberColor, MakeVector4(0, 0, 0, 0.5)); 788 } 789 Draw2DWithWorld()790 void Client::Draw2DWithWorld() { 791 SPADES_MARK_FUNCTION(); 792 793 for (auto &ent : localEntities) { 794 ent->Render2D(); 795 } 796 797 Player *p = GetWorld()->GetLocalPlayer(); 798 if (p) { 799 DrawHurtSprites(); 800 DrawHurtScreenEffect(); 801 DrawHottrackedPlayerName(); 802 803 if (!cg_hideHud) { 804 tcView->Draw(); 805 806 if (IsFirstPerson(GetCameraMode())) { 807 DrawFirstPersonHUD(); 808 } 809 } 810 811 if (p->GetTeamId() < 2) { 812 // player is not spectator 813 if (p->IsAlive()) { 814 DrawJoinedAlivePlayerHUD(); 815 } else { 816 DrawDeadPlayerHUD(); 817 DrawSpectateHUD(); 818 } 819 } else { 820 DrawSpectateHUD(); 821 } 822 823 if (!cg_hideHud) { 824 DrawAlert(); 825 826 chatWindow->Draw(); 827 killfeedWindow->Draw(); 828 } 829 830 // large map view should come in front 831 largeMapView->Draw(); 832 833 // --- end "player is there" render 834 } else { 835 // world exists, but no local player: not joined 836 837 scoreboard->Draw(); 838 839 DrawAlert(); 840 } 841 842 if (!cg_hideHud) 843 centerMessageView->Draw(); 844 845 if (scoreboardVisible || !p) 846 scoreboard->Draw(); 847 848 if (IsLimboViewActive()) 849 limbo->Draw(); 850 } 851 Draw2DWithoutWorld()852 void Client::Draw2DWithoutWorld() { 853 SPADES_MARK_FUNCTION(); 854 // no world; loading? 855 float scrWidth = renderer->ScreenWidth(); 856 float scrHeight = renderer->ScreenHeight(); 857 IFont *font; 858 859 DrawSplash(); 860 861 Handle<IImage> img; 862 863 std::string msg = net->GetStatusString(); 864 font = fontManager->GetGuiFont(); 865 Vector2 textSize = font->Measure(msg); 866 font->Draw(msg, MakeVector2(scrWidth - 16.f, scrHeight - 24.f) - textSize, 1.f, 867 MakeVector4(1, 1, 1, 0.95f)); 868 869 img = renderer->RegisterImage("Gfx/White.tga"); 870 float pos = timeSinceInit / 3.6f; 871 pos -= floorf(pos); 872 pos = 1.f - pos * 2.0f; 873 for (float v = 0; v < 0.6f; v += 0.14f) { 874 float p = pos + v; 875 if (p < 0.01f || p > .99f) 876 continue; 877 p = asin(p * 2.f - 1.f); 878 p = p / (float)M_PI + 0.5f; 879 880 float op = p * (1.f - p) * 4.f; 881 renderer->SetColorAlphaPremultiplied(MakeVector4(op, op, op, op)); 882 renderer->DrawImage( 883 img, AABB2(scrWidth - 236.f + p * 234.f, scrHeight - 18.f, 4.f, 4.f)); 884 } 885 886 DrawAlert(); 887 } 888 DrawStats()889 void Client::DrawStats() { 890 SPADES_MARK_FUNCTION(); 891 892 if (!cg_stats) 893 return; 894 895 char buf[256]; 896 std::string str; 897 898 { 899 auto fps = fpsCounter.GetFps(); 900 if (fps == 0.0) 901 str += "--.-- fps"; 902 else { 903 sprintf(buf, "%.02f fps", fps); 904 str += buf; 905 } 906 } 907 { 908 // Display world updates per second 909 auto ups = upsCounter.GetFps(); 910 if (ups == 0.0) 911 str += ", --.-- ups"; 912 else { 913 sprintf(buf, ", %.02f ups", ups); 914 str += buf; 915 } 916 } 917 918 if (net) { 919 auto ping = net->GetPing(); 920 auto upbps = net->GetUplinkBps(); 921 auto downbps = net->GetDownlinkBps(); 922 sprintf(buf, ", ping: %dms, up/down: %.02f/%.02fkbps", ping, upbps / 1000.0, 923 downbps / 1000.0); 924 str += buf; 925 } 926 927 float scrWidth = renderer->ScreenWidth(); 928 float scrHeight = renderer->ScreenHeight(); 929 IFont *font = fontManager->GetGuiFont(); 930 float margin = 5.f; 931 932 IRenderer *r = renderer; 933 auto size = font->Measure(str); 934 size += Vector2(margin * 2.f, margin * 2.f); 935 936 auto pos = (Vector2(scrWidth, scrHeight) - size) * Vector2(0.5f, 1.f); 937 938 r->SetColorAlphaPremultiplied(Vector4(0.f, 0.f, 0.f, 0.5f)); 939 r->DrawImage(nullptr, AABB2(pos.x, pos.y, size.x, size.y)); 940 font->DrawShadow(str, pos + Vector2(margin, margin), 1.f, Vector4(1.f, 1.f, 1.f, 1.f), 941 Vector4(0.f, 0.f, 0.f, 0.5f)); 942 } 943 Draw2D()944 void Client::Draw2D() { 945 SPADES_MARK_FUNCTION(); 946 947 if (GetWorld()) { 948 Draw2DWithWorld(); 949 } else { 950 Draw2DWithoutWorld(); 951 } 952 953 DrawStats(); 954 } 955 } 956 } 957