/* Copyright (c) 2013 yvt based on code of pysnip (c) Mathias Kaerlev 2011-2012. This file is part of OpenSpades. OpenSpades is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. OpenSpades is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenSpades. If not, see . */ #include #include "Client.h" #include #include #include #include #include #include "IAudioChunk.h" #include "IAudioDevice.h" #include "CenterMessageView.h" #include "ChatWindow.h" #include "ClientPlayer.h" #include "ClientUI.h" #include "Corpse.h" #include "FallingBlock.h" #include "Fonts.h" #include "HurtRingView.h" #include "IFont.h" #include "ILocalEntity.h" #include "LimboView.h" #include "MapView.h" #include "PaletteView.h" #include "ParticleSpriteEntity.h" #include "ScoreboardView.h" #include "SmokeSpriteEntity.h" #include "TCProgressView.h" #include "Tracer.h" #include "GameMap.h" #include "Grenade.h" #include "Weapon.h" #include "World.h" #include "NetClient.h" DEFINE_SPADES_SETTING(cg_hitIndicator, "1"); DEFINE_SPADES_SETTING(cg_debugAim, "0"); SPADES_SETTING(cg_keyReloadWeapon); DEFINE_SPADES_SETTING(cg_screenshotFormat, "jpeg"); DEFINE_SPADES_SETTING(cg_stats, "0"); DEFINE_SPADES_SETTING(cg_hideHud, "0"); DEFINE_SPADES_SETTING(cg_playerNames, "2"); DEFINE_SPADES_SETTING(cg_playerNameX, "0"); DEFINE_SPADES_SETTING(cg_playerNameY, "0"); namespace spades { namespace client { enum class ScreenshotFormat { Jpeg, Targa, Png }; namespace { ScreenshotFormat GetScreenshotFormat() { if (EqualsIgnoringCase(cg_screenshotFormat, "jpeg")) { return ScreenshotFormat::Jpeg; } else if (EqualsIgnoringCase(cg_screenshotFormat, "targa")) { return ScreenshotFormat::Targa; } else if (EqualsIgnoringCase(cg_screenshotFormat, "png")) { return ScreenshotFormat::Png; } else { SPRaise("Invalid screenshot format: %s", cg_screenshotFormat.CString()); } } } void Client::TakeScreenShot(bool sceneOnly) { SceneDefinition sceneDef = CreateSceneDefinition(); lastSceneDef = sceneDef; // render scene flashDlights = flashDlightsOld; DrawScene(); // draw 2d if (!sceneOnly) Draw2D(); // Well done! renderer->FrameDone(); Handle bmp(renderer->ReadBitmap(), false); // force 100% opacity uint32_t *pixels = bmp->GetPixels(); for (size_t i = bmp->GetWidth() * bmp->GetHeight(); i > 0; i--) { *(pixels++) |= 0xff000000UL; } try { std::string name = ScreenShotPath(); bmp->Save(name); std::string msg; if (sceneOnly) msg = _Tr("Client", "Sceneshot saved: {0}", name); else msg = _Tr("Client", "Screenshot saved: {0}", name); ShowAlert(msg, AlertType::Notice); } catch (const Exception &ex) { std::string msg; msg = _Tr("Client", "Screenshot failed: "); msg += ex.GetShortMessage(); ShowAlert(msg, AlertType::Error); SPLog("Screenshot failed: %s", ex.what()); } catch (const std::exception &ex) { std::string msg; msg = _Tr("Client", "Screenshot failed: "); msg += ex.what(); ShowAlert(msg, AlertType::Error); SPLog("Screenshot failed: %s", ex.what()); } } std::string Client::ScreenShotPath() { char bufJpeg[256]; char bufTarga[256]; char bufPng[256]; for (int i = 0; i < 10000; i++) { sprintf(bufJpeg, "Screenshots/shot%04d.jpg", nextScreenShotIndex); sprintf(bufTarga, "Screenshots/shot%04d.tga", nextScreenShotIndex); sprintf(bufPng, "Screenshots/shot%04d.png", nextScreenShotIndex); if (FileManager::FileExists(bufJpeg) || FileManager::FileExists(bufTarga) || FileManager::FileExists(bufPng)) { nextScreenShotIndex++; if (nextScreenShotIndex >= 10000) nextScreenShotIndex = 0; continue; } switch (GetScreenshotFormat()) { case ScreenshotFormat::Jpeg: return bufJpeg; case ScreenshotFormat::Targa: return bufTarga; case ScreenshotFormat::Png: return bufPng; } SPAssert(false); } SPRaise("No free file name"); } #pragma mark - HUD Drawings void Client::DrawSplash() { Handle img; Vector2 siz; Vector2 scrSize = {renderer->ScreenWidth(), renderer->ScreenHeight()}; renderer->SetColorAlphaPremultiplied(MakeVector4(0, 0, 0, 1)); img = renderer->RegisterImage("Gfx/White.tga"); renderer->DrawImage(img, AABB2(0, 0, scrSize.x, scrSize.y)); renderer->SetColorAlphaPremultiplied(MakeVector4(1, 1, 1, 1.)); img = renderer->RegisterImage("Gfx/Title/Logo.png"); siz = MakeVector2(img->GetWidth(), img->GetHeight()); siz *= std::min(1.f, scrSize.x / siz.x * 0.5f); siz *= std::min(1.f, scrSize.y / siz.y); renderer->DrawImage( img, AABB2((scrSize.x - siz.x) * .5f, (scrSize.y - siz.y) * .5f, siz.x, siz.y)); } void Client::DrawStartupScreen() { Handle img; Vector2 scrSize = {renderer->ScreenWidth(), renderer->ScreenHeight()}; renderer->SetColorAlphaPremultiplied(MakeVector4(0, 0, 0, 1.)); img = renderer->RegisterImage("Gfx/White.tga"); renderer->DrawImage(img, AABB2(0, 0, scrSize.x, scrSize.y)); DrawSplash(); IFont *font = fontManager->GetGuiFont(); std::string str = _Tr("Client", "NOW LOADING"); Vector2 size = font->Measure(str); Vector2 pos = MakeVector2(scrSize.x - 16.f, scrSize.y - 16.f); pos -= size; font->DrawShadow(str, pos, 1.f, MakeVector4(1, 1, 1, 1), MakeVector4(0, 0, 0, 0.5)); renderer->FrameDone(); renderer->Flip(); } void Client::DrawDisconnectScreen() { return; Handle img; Vector2 scrSize = {renderer->ScreenWidth(), renderer->ScreenHeight()}; renderer->SetColorAlphaPremultiplied(MakeVector4(0, 0, 0, 1.)); img = renderer->RegisterImage("Gfx/White.tga"); renderer->DrawImage(img, AABB2(0, 0, scrSize.x, scrSize.y)); DrawSplash(); IFont *font = fontManager->GetGuiFont(); std::string str = _Tr("Client", "Disconnecting..."); Vector2 size = font->Measure(str); Vector2 pos = MakeVector2(scrSize.x - 16.f, scrSize.y - 16.f); pos -= size; font->DrawShadow(str, pos, 1.f, MakeVector4(1, 1, 1, 1), MakeVector4(0, 0, 0, 0.5)); renderer->FrameDone(); renderer->Flip(); } void Client::DrawHurtSprites() { float per = (world->GetTime() - lastHurtTime) / 1.5f; if (per > 1.f) return; if (per < 0.f) return; Handle img = renderer->RegisterImage("Gfx/HurtSprite.png"); Vector2 scrSize = {renderer->ScreenWidth(), renderer->ScreenHeight()}; Vector2 scrCenter = scrSize * .5f; float radius = scrSize.GetLength() * .5f; for (size_t i = 0; i < hurtSprites.size(); i++) { HurtSprite &spr = hurtSprites[i]; float alpha = spr.strength - per; if (alpha < 0.f) continue; if (alpha > 1.f) alpha = 1.f; Vector2 radDir = {cosf(spr.angle), sinf(spr.angle)}; Vector2 angDir = {-sinf(spr.angle), cosf(spr.angle)}; float siz = spr.scale * radius; Vector2 base = radDir * radius + scrCenter; Vector2 centVect = radDir * (-siz); Vector2 sideVect1 = angDir * (siz * 4.f * (spr.horzShift)); Vector2 sideVect2 = angDir * (siz * 4.f * (spr.horzShift - 1.f)); Vector2 v1 = base + centVect + sideVect1; Vector2 v2 = base + centVect + sideVect2; Vector2 v3 = base + sideVect1; renderer->SetColorAlphaPremultiplied(MakeVector4(0.f, 0.f, 0.f, alpha)); renderer->DrawImage(img, v1, v2, v3, AABB2(0, 8.f, img->GetWidth(), img->GetHeight())); } } void Client::DrawHurtScreenEffect() { SPADES_MARK_FUNCTION(); float scrWidth = renderer->ScreenWidth(); float scrHeight = renderer->ScreenHeight(); float wTime = world->GetTime(); Player *p = GetWorld()->GetLocalPlayer(); if (wTime < lastHurtTime + .35f && wTime >= lastHurtTime) { float per = (wTime - lastHurtTime) / .35f; per = 1.f - per; per *= .3f + (1.f - p->GetHealth() / 100.f) * .7f; per = std::min(per, 0.9f); per = 1.f - per; Vector3 color = {1.f, per, per}; renderer->MultiplyScreenColor(color); renderer->SetColorAlphaPremultiplied( MakeVector4((1.f - per) * .1f, 0, 0, (1.f - per) * .1f)); renderer->DrawImage(renderer->RegisterImage("Gfx/White.tga"), AABB2(0, 0, scrWidth, scrHeight)); } } void Client::DrawHottrackedPlayerName() { SPADES_MARK_FUNCTION(); if ((int)cg_playerNames == 0) return; Player *p = GetWorld()->GetLocalPlayer(); hitTag_t tag = hit_None; Player *hottracked = HotTrackedPlayer(&tag); if (hottracked) { Vector3 posxyz = Project(hottracked->GetEye()); Vector2 pos = {posxyz.x, posxyz.y}; char buf[64]; if ((int)cg_playerNames == 1) { float dist = (hottracked->GetEye() - p->GetEye()).GetLength(); int idist = (int)floorf(dist + .5f); sprintf(buf, "%s [%d%s]", hottracked->GetName().c_str(), idist, (idist == 1) ? "block" : "blocks"); } else sprintf(buf, "%s", hottracked->GetName().c_str()); pos.y += (int)cg_playerNameY; pos.x += (int)cg_playerNameX; IFont *font = fontManager->GetGuiFont(); Vector2 size = font->Measure(buf); pos.x -= size.x * .5f; pos.y -= size.y; font->DrawShadow(buf, pos, 1.f, MakeVector4(1, 1, 1, 1), MakeVector4(0, 0, 0, 0.5)); } } void Client::DrawDebugAim() { SPADES_MARK_FUNCTION(); // float scrWidth = renderer->ScreenWidth(); // float scrHeight = renderer->ScreenHeight(); // float wTime = world->GetTime(); Player *p = GetWorld()->GetLocalPlayer(); // IFont *font; Weapon *w = p->GetWeapon(); float spread = w->GetSpread(); AABB2 boundary(0, 0, 0, 0); for (int i = 0; i < 8; i++) { Vector3 vec = p->GetFront(); if (i & 1) vec.x += spread; else vec.x -= spread; if (i & 2) vec.y += spread; else vec.y -= spread; if (i & 4) vec.z += spread; else vec.z -= spread; Vector3 viewPos; viewPos.x = Vector3::Dot(vec, p->GetRight()); viewPos.y = Vector3::Dot(vec, p->GetUp()); viewPos.z = Vector3::Dot(vec, p->GetFront()); Vector2 p; p.x = viewPos.x / viewPos.z; p.y = viewPos.y / viewPos.z; boundary.min.x = std::min(boundary.min.x, p.x); boundary.min.y = std::min(boundary.min.y, p.y); boundary.max.x = std::max(boundary.max.x, p.x); boundary.max.y = std::max(boundary.max.y, p.y); } Handle img = renderer->RegisterImage("Gfx/White.tga"); boundary.min *= renderer->ScreenHeight() * .5f; boundary.max *= renderer->ScreenHeight() * .5f; boundary.min /= tanf(lastSceneDef.fovY * .5f); boundary.max /= tanf(lastSceneDef.fovY * .5f); IntVector3 cent; cent.x = (int)(renderer->ScreenWidth() * .5f); cent.y = (int)(renderer->ScreenHeight() * .5f); IntVector3 p1 = cent; IntVector3 p2 = cent; p1.x += (int)floorf(boundary.min.x); p1.y += (int)floorf(boundary.min.y); p2.x += (int)ceilf(boundary.max.x); p2.y += (int)ceilf(boundary.max.y); renderer->SetColorAlphaPremultiplied(MakeVector4(0, 0, 0, 1)); renderer->DrawImage(img, AABB2(p1.x - 2, p1.y - 2, p2.x - p1.x + 4, 1)); renderer->DrawImage(img, AABB2(p1.x - 2, p1.y - 2, 1, p2.y - p1.y + 4)); renderer->DrawImage(img, AABB2(p1.x - 2, p2.y + 1, p2.x - p1.x + 4, 1)); renderer->DrawImage(img, AABB2(p2.x + 1, p1.y - 2, 1, p2.y - p1.y + 4)); renderer->SetColorAlphaPremultiplied(MakeVector4(1, 1, 1, 1)); renderer->DrawImage(img, AABB2(p1.x - 1, p1.y - 1, p2.x - p1.x + 2, 1)); renderer->DrawImage(img, AABB2(p1.x - 1, p1.y - 1, 1, p2.y - p1.y + 2)); renderer->DrawImage(img, AABB2(p1.x - 1, p2.y, p2.x - p1.x + 2, 1)); renderer->DrawImage(img, AABB2(p2.x, p1.y - 1, 1, p2.y - p1.y + 2)); } void Client::DrawJoinedAlivePlayerHUD() { SPADES_MARK_FUNCTION(); float scrWidth = renderer->ScreenWidth(); float scrHeight = renderer->ScreenHeight(); // float wTime = world->GetTime(); Player *p = GetWorld()->GetLocalPlayer(); IFont *font; // draw local weapon's 2d things clientPlayers[p->GetId()]->Draw2D(); // draw damage ring if (!cg_hideHud) hurtRingView->Draw(); if (cg_hitIndicator && hitFeedbackIconState > 0.f && !cg_hideHud) { Handle img(renderer->RegisterImage("Gfx/HitFeedback.png"), false); Vector2 pos = {scrWidth * .5f, scrHeight * .5f}; pos.x -= img->GetWidth() * .5f; pos.y -= img->GetHeight() * .5f; float op = hitFeedbackIconState; Vector4 color; if (hitFeedbackFriendly) { color = MakeVector4(0.02f, 1.f, 0.02f, 1.f); } else { color = MakeVector4(1.f, 0.02f, 0.04f, 1.f); } color *= op; renderer->SetColorAlphaPremultiplied(color); renderer->DrawImage(img, pos); } if (cg_debugAim && p->GetTool() == Player::ToolWeapon) { DrawDebugAim(); } if (!cg_hideHud) { // draw ammo Weapon *weap = p->GetWeapon(); Handle ammoIcon; float iconWidth, iconHeight; float spacing = 2.f; int stockNum; int warnLevel; if (p->IsToolWeapon()) { switch (weap->GetWeaponType()) { case RIFLE_WEAPON: ammoIcon = renderer->RegisterImage("Gfx/Bullet/7.62mm.png"); iconWidth = 6.f; iconHeight = iconWidth * 4.f; break; case SMG_WEAPON: ammoIcon = renderer->RegisterImage("Gfx/Bullet/9mm.png"); iconWidth = 4.f; iconHeight = iconWidth * 4.f; break; case SHOTGUN_WEAPON: ammoIcon = renderer->RegisterImage("Gfx/Bullet/12gauge.png"); iconWidth = 30.f; iconHeight = iconWidth / 4.f; spacing = -6.f; break; default: SPInvalidEnum("weap->GetWeaponType()", weap->GetWeaponType()); } int clipSize = weap->GetClipSize(); int clip = weap->GetAmmo(); clipSize = std::max(clipSize, clip); for (int i = 0; i < clipSize; i++) { float x = scrWidth - 16.f - (float)(i + 1) * (iconWidth + spacing); float y = scrHeight - 16.f - iconHeight; if (clip >= i + 1) { renderer->SetColorAlphaPremultiplied(MakeVector4(1, 1, 1, 1)); } else { renderer->SetColorAlphaPremultiplied(MakeVector4(0.4, 0.4, 0.4, 1)); } renderer->DrawImage(ammoIcon, AABB2(x, y, iconWidth, iconHeight)); } stockNum = weap->GetStock(); warnLevel = weap->GetMaxStock() / 3; } else { iconHeight = 0.f; warnLevel = 0; switch (p->GetTool()) { case Player::ToolSpade: case Player::ToolBlock: stockNum = p->GetNumBlocks(); break; case Player::ToolGrenade: stockNum = p->GetNumGrenades(); break; default: SPInvalidEnum("p->GetTool()", p->GetTool()); } } Vector4 numberColor = {1, 1, 1, 1}; if (stockNum == 0) { numberColor.y = 0.3f; numberColor.z = 0.3f; } else if (stockNum <= warnLevel) { numberColor.z = 0.3f; } char buf[64]; sprintf(buf, "%d", stockNum); font = fontManager->GetGuiFont(); std::string stockStr = buf; Vector2 size = font->Measure(stockStr); Vector2 pos = MakeVector2(scrWidth - 16.f, scrHeight - 16.f - iconHeight); pos -= size; font->DrawShadow(stockStr, pos, 1.f, numberColor, MakeVector4(0, 0, 0, 0.5)); // draw "press ... to reload" { std::string msg = ""; switch (p->GetTool()) { case Player::ToolBlock: if (p->GetNumBlocks() == 0) { msg = _Tr("Client", "Out of Block"); } break; case Player::ToolGrenade: if (p->GetNumGrenades() == 0) { msg = _Tr("Client", "Out of Grenade"); } break; case Player::ToolWeapon: { Weapon *weap = p->GetWeapon(); if (weap->IsReloading() || p->IsAwaitingReloadCompletion()) { msg = _Tr("Client", "Reloading"); } else if (weap->GetAmmo() == 0 && weap->GetStock() == 0) { msg = _Tr("Client", "Out of Ammo"); } else if (weap->GetStock() > 0 && weap->GetAmmo() < weap->GetClipSize() / 4) { msg = _Tr("Client", "Press [{0}] to Reload", (std::string)cg_keyReloadWeapon); } } break; default:; // no message } if (!msg.empty()) { font = fontManager->GetGuiFont(); Vector2 size = font->Measure(msg); Vector2 pos = MakeVector2((scrWidth - size.x) * .5f, scrHeight * 2.f / 3.f); font->DrawShadow(msg, pos, 1.f, MakeVector4(1, 1, 1, 1), MakeVector4(0, 0, 0, 0.5)); } } if (p->GetTool() == Player::ToolBlock) { paletteView->Draw(); } // draw map mapView->Draw(); DrawHealth(); } } void Client::DrawDeadPlayerHUD() { SPADES_MARK_FUNCTION(); Player *p = GetWorld()->GetLocalPlayer(); IFont *font; float scrWidth = renderer->ScreenWidth(); float scrHeight = renderer->ScreenHeight(); if (!cg_hideHud) { // draw respawn tme if (!p->IsAlive()) { std::string msg; float secs = p->GetRespawnTime() - world->GetTime(); if (secs > 0.f) msg = _Tr("Client", "You will respawn in: {0}", (int)ceilf(secs)); else msg = _Tr("Client", "Waiting for respawn"); if (!msg.empty()) { font = fontManager->GetGuiFont(); Vector2 size = font->Measure(msg); Vector2 pos = MakeVector2((scrWidth - size.x) * .5f, scrHeight / 3.f); font->DrawShadow(msg, pos, 1.f, MakeVector4(1, 1, 1, 1), MakeVector4(0, 0, 0, 0.5)); } } // draw map mapView->Draw(); } } void Client::DrawAlert() { SPADES_MARK_FUNCTION(); IFont *font = fontManager->GetGuiFont(); float scrWidth = renderer->ScreenWidth(); float scrHeight = renderer->ScreenHeight(); auto &r = renderer; const float fadeOutTime = 1.f; float fade = 1.f - (time - alertDisappearTime) / fadeOutTime; fade = std::min(fade, 1.f); if (fade <= 0.f) { return; } float borderFade = 1.f - (time - alertAppearTime) * 1.5f; borderFade = std::max(std::min(borderFade, 1.f), 0.f); borderFade *= fade; Handle alertIcon(renderer->RegisterImage("Gfx/AlertIcon.png"), false); Vector2 textSize = font->Measure(alertContents); Vector2 contentsSize = textSize; contentsSize.y = std::max(contentsSize.y, 16.f); if (alertType != AlertType::Notice) { contentsSize.x += 22.f; } // add margin const float margin = 8.f; contentsSize.x += margin * 2.f; contentsSize.y += margin * 2.f; contentsSize.x = floorf(contentsSize.x); contentsSize.y = floorf(contentsSize.y); Vector2 pos = (Vector2(scrWidth, scrHeight) - contentsSize) * Vector2(0.5f, 0.7f); pos.y += 40.f; pos.x = floorf(pos.x); pos.y = floorf(pos.y); Vector4 color; // draw border switch (alertType) { case AlertType::Notice: color = Vector4(0.f, 0.f, 0.f, 0.f); break; case AlertType::Warning: color = Vector4(1.f, 1.f, 0.f, .7f); break; case AlertType::Error: color = Vector4(1.f, 0.f, 0.f, .7f); break; } color *= borderFade; r->SetColorAlphaPremultiplied(color); const float border = 1.f; r->DrawImage(nullptr, AABB2(pos.x - border, pos.y - border, contentsSize.x + border * 2.f, border)); r->DrawImage(nullptr, AABB2(pos.x - border, pos.y + contentsSize.y, contentsSize.x + border * 2.f, border)); r->DrawImage(nullptr, AABB2(pos.x - border, pos.y, border, contentsSize.y)); r->DrawImage(nullptr, AABB2(pos.x + contentsSize.x, pos.y, border, contentsSize.y)); // fill background color = Vector4(0.f, 0.f, 0.f, fade * 0.5f); r->SetColorAlphaPremultiplied(color); r->DrawImage(nullptr, AABB2(pos.x, pos.y, contentsSize.x, contentsSize.y)); // draw icon switch (alertType) { case AlertType::Notice: color = Vector4(0.f, 0.f, 0.f, 0.f); break; case AlertType::Warning: color = Vector4(1.f, 1.f, 0.f, 1.f); break; case AlertType::Error: color = Vector4(1.f, 0.f, 0.f, 1.f); break; } color *= fade; r->SetColorAlphaPremultiplied(color); r->DrawImage(alertIcon, Vector2(pos.x + margin, pos.y + (contentsSize.y - 16.f) * 0.5f)); // draw text color = Vector4(1.f, 1.f, 1.f, 1.f); color *= fade; font->DrawShadow(alertContents, Vector2(pos.x + contentsSize.x - textSize.x - margin, pos.y + (contentsSize.y - textSize.y) * 0.5f), 1.f, color, Vector4(0.f, 0.f, 0.f, fade * 0.5f)); } void Client::DrawHealth() { SPADES_MARK_FUNCTION(); Player *p = GetWorld()->GetLocalPlayer(); IFont *font; // float scrWidth = renderer->ScreenWidth(); float scrHeight = renderer->ScreenHeight(); std::string str = std::to_string(p->GetHealth()); Vector4 numberColor = {1, 1, 1, 1}; if (p->GetHealth() == 0) { numberColor.y = 0.3f; numberColor.z = 0.3f; } else if (p->GetHealth() <= 50) { numberColor.z = 0.3f; } font = fontManager->GetSquareDesignFont(); Vector2 size = font->Measure(str); Vector2 pos = MakeVector2(16.f, scrHeight - 16.f); pos.y -= size.y; font->DrawShadow(str, pos, 1.f, numberColor, MakeVector4(0, 0, 0, 0.5)); } void Client::Draw2DWithWorld() { SPADES_MARK_FUNCTION(); float scrWidth = renderer->ScreenWidth(); // float scrHeight = renderer->ScreenHeight(); IFont *font; // float wTime = world->GetTime(); for (auto &ent : localEntities) { ent->Render2D(); } Player *p = GetWorld()->GetLocalPlayer(); if (p) { DrawHurtSprites(); DrawHurtScreenEffect(); DrawHottrackedPlayerName(); if (!cg_hideHud) tcView->Draw(); if (p->GetTeamId() < 2) { // player is not spectator if (p->IsAlive()) { DrawJoinedAlivePlayerHUD(); } else { DrawDeadPlayerHUD(); } } if (IsFollowing() && !cg_hideHud) { if (followingPlayerId == p->GetId()) { // just spectating } else { font = fontManager->GetGuiFont(); std::string msg = _Tr("Client", "Following {0}", world->GetPlayerPersistent(followingPlayerId).name); Vector2 size = font->Measure(msg); Vector2 pos = MakeVector2(scrWidth - 8.f, 256.f + 32.f); pos.x -= size.x; font->DrawShadow(msg, pos, 1.f, MakeVector4(1, 1, 1, 1), MakeVector4(0, 0, 0, 0.5)); } } if (!cg_hideHud) { DrawAlert(); chatWindow->Draw(); killfeedWindow->Draw(); } // large map view should come in front largeMapView->Draw(); if (scoreboardVisible) scoreboard->Draw(); // --- end "player is there" render } else { // world exists, but no local player: not joined scoreboard->Draw(); DrawAlert(); } if (IsLimboViewActive()) limbo->Draw(); } void Client::Draw2DWithoutWorld() { SPADES_MARK_FUNCTION(); // no world; loading? float scrWidth = renderer->ScreenWidth(); float scrHeight = renderer->ScreenHeight(); IFont *font; DrawSplash(); Handle img; std::string msg = net->GetStatusString(); font = fontManager->GetGuiFont(); Vector2 textSize = font->Measure(msg); font->Draw(msg, MakeVector2(scrWidth - 16.f, scrHeight - 24.f) - textSize, 1.f, MakeVector4(1, 1, 1, 0.95f)); img = renderer->RegisterImage("Gfx/White.tga"); float pos = timeSinceInit / 3.6f; pos -= floorf(pos); pos = 1.f - pos * 2.0f; for (float v = 0; v < 0.6f; v += 0.14f) { float p = pos + v; if (p < 0.01f || p > .99f) continue; p = asin(p * 2.f - 1.f); p = p / (float)M_PI + 0.5f; float op = p * (1.f - p) * 4.f; renderer->SetColorAlphaPremultiplied(MakeVector4(op, op, op, op)); renderer->DrawImage( img, AABB2(scrWidth - 236.f + p * 234.f, scrHeight - 18.f, 4.f, 4.f)); } DrawAlert(); } void Client::DrawStats() { SPADES_MARK_FUNCTION(); if (!cg_stats) return; char buf[256]; std::string str; { auto fps = fpsCounter.GetFps(); if (fps == 0.0) str += "--.-- fps"; else { sprintf(buf, "%.02f fps", fps); str += buf; } } if (net) { auto ping = net->GetPing(); auto upbps = net->GetUplinkBps(); auto downbps = net->GetDownlinkBps(); sprintf(buf, ", ping: %dms, up/down: %.02f/%.02fkbps", ping, upbps / 1000.0, downbps / 1000.0); str += buf; } float scrWidth = renderer->ScreenWidth(); float scrHeight = renderer->ScreenHeight(); IFont *font = fontManager->GetGuiFont(); float margin = 5.f; IRenderer *r = renderer; auto size = font->Measure(str); size += Vector2(margin * 2.f, margin * 2.f); auto pos = (Vector2(scrWidth, scrHeight) - size) * Vector2(0.5f, 1.f); r->SetColorAlphaPremultiplied(Vector4(0.f, 0.f, 0.f, 0.5f)); r->DrawImage(nullptr, AABB2(pos.x, pos.y, size.x, size.y)); font->DrawShadow(str, pos + Vector2(margin, margin), 1.f, Vector4(1.f, 1.f, 1.f, 1.f), Vector4(0.f, 0.f, 0.f, 0.5f)); } void Client::Draw2D() { SPADES_MARK_FUNCTION(); if (GetWorld()) { Draw2DWithWorld(); } else { Draw2DWithoutWorld(); } if (!cg_hideHud) centerMessageView->Draw(); DrawStats(); } } }