/* 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 "Client.h" #include #include #include #include #include #include #include "IAudioChunk.h" #include "IAudioDevice.h" #include "ClientUI.h" #include "PaletteView.h" #include "LimboView.h" #include "MapView.h" #include "Corpse.h" #include "ClientPlayer.h" #include "ILocalEntity.h" #include "ChatWindow.h" #include "CenterMessageView.h" #include "Tracer.h" #include "FallingBlock.h" #include "HurtRingView.h" #include "ParticleSpriteEntity.h" #include "SmokeSpriteEntity.h" #include "IFont.h" #include "ScoreboardView.h" #include "TCProgressView.h" #include "World.h" #include "Weapon.h" #include "GameMap.h" #include "Grenade.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 = textFont; 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 = textFont; 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 = textFont; 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 = designFont; 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 = textFont; 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 = textFont; 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 = textFont; 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 = designFont; 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 = textFont; 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 = textFont; 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 = textFont; 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(); } } }