/* 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 "CTFGameMode.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 "IGameMode.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); SPADES_SETTING(cg_keyJump); SPADES_SETTING(cg_keyAttack); SPADES_SETTING(cg_keyAltAttack); SPADES_SETTING(cg_keyCrouch); 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()); } } std::string TranslateKeyName(const std::string &name) { if (name == "LeftMouseButton") { return "LMB"; } else if (name == "RightMouseButton") { return "RMB"; } else if (name.empty()) { return _Tr("Client", "Unbound"); } else { return name; } } } // namespace 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(); // 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() {} 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().value(); 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().value(); auto hottracked = HotTrackedPlayer(); if (hottracked) { Player &hottrackedPlayer = std::get<0>(*hottracked); Vector3 posxyz = Project(hottrackedPlayer.GetEye()); Vector2 pos = {posxyz.x, posxyz.y}; char buf[64]; if ((int)cg_playerNames == 1) { float dist = (hottrackedPlayer.GetEye() - p.GetEye()).GetLength(); int idist = (int)floorf(dist + .5f); sprintf(buf, "%s [%d%s]", hottrackedPlayer.GetName().c_str(), idist, (idist == 1) ? "block" : "blocks"); } else sprintf(buf, "%s", hottrackedPlayer.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 = GetCameraTargetPlayer(); // 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::DrawFirstPersonHUD() { SPADES_MARK_FUNCTION(); float scrWidth = renderer->ScreenWidth(); float scrHeight = renderer->ScreenHeight(); Player &player = GetCameraTargetPlayer(); int playerId = GetCameraTargetPlayerId(); clientPlayers[playerId]->Draw2D(); if (cg_hitIndicator && hitFeedbackIconState > 0.f && !cg_hideHud) { Handle img = renderer->RegisterImage("Gfx/HitFeedback.png"); 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 the player has the intel, display an intel icon IGameMode &mode = *world->GetMode(); if (mode.ModeType() == IGameMode::m_CTF) { auto &ctfMode = static_cast(mode); if (ctfMode.PlayerHasIntel(*world, player)) { Handle img = renderer->RegisterImage("Gfx/Intel.png"); // Strobe Vector4 color{1.0f, 1.0f, 1.0f, 1.0f}; color *= std::fabs(std::sin(world->GetTime() * 2.0f)); renderer->SetColorAlphaPremultiplied(color); renderer->DrawImage(img, Vector2{scrWidth - 260.f, scrHeight - 64.0f}); } } if (cg_debugAim && player.GetTool() == Player::ToolWeapon && player.IsAlive()) { DrawDebugAim(); } } void Client::DrawJoinedAlivePlayerHUD() { SPADES_MARK_FUNCTION(); float scrWidth = renderer->ScreenWidth(); float scrHeight = renderer->ScreenHeight(); Player &p = GetWorld()->GetLocalPlayer().value(); // Draw damage rings if (!cg_hideHud) hurtRingView->Draw(); if (!cg_hideHud) { // Draw ammo amount // (Note: this cannot be displayed for a spectated player --- the server // does not submit sufficient information) 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); IFont &font = fontManager->GetSquareDesignFont(); 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", TranslateKeyName(cg_keyReloadWeapon)); } } break; default:; // no message } if (!msg.empty()) { IFont &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().value(); 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()) { IFont &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)); } } } } void Client::DrawSpectateHUD() { SPADES_MARK_FUNCTION(); if (cg_hideHud) { return; } IFont &font = fontManager->GetGuiFont(); float scrWidth = renderer->ScreenWidth(); float textX = scrWidth - 8.0f; float textY = 256.0f + 32.0f; auto addLine = [&](const std::string &text) { Vector2 size = font.Measure(text); Vector2 pos = MakeVector2(textX, textY); pos.x -= size.x; textY += 20.0f; font.DrawShadow(text, pos, 1.f, MakeVector4(1, 1, 1, 1), MakeVector4(0, 0, 0, 0.5)); }; if (HasTargetPlayer(GetCameraMode())) { addLine(_Tr("Client", "Following {0}", world->GetPlayerPersistent(GetCameraTargetPlayerId()).name)); } textY += 10.0f; // Help messages (make sure to synchronize these with the keyboard input handler) if (FollowsNonLocalPlayer(GetCameraMode())) { if (GetCameraTargetPlayer().IsAlive()) { addLine(_Tr("Client", "[{0}] Cycle camera mode", TranslateKeyName(cg_keyJump))); } addLine(_Tr("Client", "[{0}/{1}] Next/previous player", TranslateKeyName(cg_keyAttack), TranslateKeyName(cg_keyAltAttack))); if (GetWorld()->GetLocalPlayer()->IsSpectator()) { addLine(_Tr("Client", "[{0}] Unfollow", TranslateKeyName(cg_keyReloadWeapon))); } } else { addLine(_Tr("Client", "[{0}/{1}] Follow a player", TranslateKeyName(cg_keyAttack), TranslateKeyName(cg_keyAltAttack))); } if (GetCameraMode() == ClientCameraMode::Free) { addLine(_Tr("Client", "[{0}/{1}] Go up/down", TranslateKeyName(cg_keyJump), TranslateKeyName(cg_keyCrouch))); } 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"); 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().value(); // 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; } IFont &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(); for (auto &ent : localEntities) { ent->Render2D(); } stmp::optional p = GetWorld()->GetLocalPlayer(); if (p) { DrawHurtSprites(); DrawHurtScreenEffect(); DrawHottrackedPlayerName(); if (!cg_hideHud) { tcView->Draw(); if (IsFirstPerson(GetCameraMode())) { DrawFirstPersonHUD(); } } if (p->GetTeamId() < 2) { // player is not spectator if (p->IsAlive()) { DrawJoinedAlivePlayerHUD(); } else { DrawDeadPlayerHUD(); DrawSpectateHUD(); } } else { DrawSpectateHUD(); } if (!cg_hideHud) { DrawAlert(); chatWindow->Draw(); killfeedWindow->Draw(); } // large map view should come in front largeMapView->Draw(); // --- end "player is there" render } else { // world exists, but no local player: not joined scoreboard->Draw(); DrawAlert(); } if (!cg_hideHud) centerMessageView->Draw(); if (scoreboardVisible || !p) scoreboard->Draw(); if (IsLimboViewActive()) limbo->Draw(); } void Client::Draw2DWithoutWorld() { SPADES_MARK_FUNCTION(); // no world; loading? float scrWidth = renderer->ScreenWidth(); float scrHeight = renderer->ScreenHeight(); DrawSplash(); Handle img; std::string msg = net->GetStatusString(); IFont &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"); if (net->GetStatus() == NetClientStatusReceivingMap) { // Normal progress bar float progress = mapReceivingProgressSmoothed; renderer->SetColorAlphaPremultiplied(MakeVector4(0.2f, 0.2f, 0.2f, 0.2f)); renderer->DrawImage(img, AABB2(scrWidth - 236.f, scrHeight - 18.f, 222.f, 4.f)); renderer->SetColorAlphaPremultiplied(MakeVector4(1.0f, 1.0f, 1.0f, 1.0f)); renderer->DrawImage( img, AABB2(scrWidth - 236.f, scrHeight - 18.f, 222.f * progress, 4.f)); } else { // Indeterminate progress bar 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; } } { // Display world updates per second auto ups = upsCounter.GetFps(); if (ups == 0.0) str += ", --.-- ups"; else { sprintf(buf, ", %.02f ups", ups); 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; auto size = font.Measure(str); size += Vector2(margin * 2.f, margin * 2.f); auto pos = (Vector2(scrWidth, scrHeight) - size) * Vector2(0.5f, 1.f); renderer->SetColorAlphaPremultiplied(Vector4(0.f, 0.f, 0.f, 0.5f)); renderer->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(); } DrawStats(); } } // namespace client } // namespace spades