895 lines
24 KiB
C++
895 lines
24 KiB
C++
/*
|
|
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 <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
#include "Client.h"
|
|
#include <cstdlib>
|
|
|
|
#include <Core/ConcurrentDispatch.h>
|
|
#include <Core/Settings.h>
|
|
#include <Core/Strings.h>
|
|
#include <Core/Bitmap.h>
|
|
#include <Core/FileManager.h>
|
|
|
|
#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"
|
|
|
|
SPADES_SETTING(cg_hitIndicator, "1");
|
|
SPADES_SETTING(cg_debugAim, "0");
|
|
SPADES_SETTING(cg_keyReloadWeapon, "");
|
|
SPADES_SETTING(cg_screenshotFormat, "jpeg");
|
|
SPADES_SETTING(cg_stats, "0");
|
|
SPADES_SETTING(cg_hideHud, "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<Bitmap> 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<IImage> 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<IImage> 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::DrawHurtSprites() {
|
|
float per = (world->GetTime() - lastHurtTime) / 1.5f;
|
|
if(per > 1.f) return;
|
|
if(per < 0.f) return;
|
|
Handle<IImage> 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();
|
|
|
|
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};
|
|
float dist = (hottracked->GetEye() - p->GetEye()).GetLength();
|
|
int idist = (int)floorf(dist + .5f);
|
|
char buf[64];
|
|
sprintf(buf, "%s [%d%s]", hottracked->GetName().c_str(), idist, (idist == 1) ? "block":"blocks");
|
|
|
|
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<IImage> 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<IImage> 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<IImage> 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.tga");
|
|
iconWidth = 6.f;
|
|
iconHeight = iconWidth * 4.f;
|
|
break;
|
|
case SMG_WEAPON:
|
|
ammoIcon = renderer->RegisterImage("Gfx/Bullet/9mm.tga");
|
|
iconWidth = 4.f;
|
|
iconHeight = iconWidth * 4.f;
|
|
break;
|
|
case SHOTGUN_WEAPON:
|
|
ammoIcon = renderer->RegisterImage("Gfx/Bullet/12gauge.tga");
|
|
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<IImage> 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<IImage> 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();
|
|
}
|
|
|
|
centerMessageView->Draw();
|
|
|
|
DrawStats();
|
|
}
|
|
|
|
}
|
|
}
|