mirror of
https://github.com/ddnet/ddnet.git
synced 2024-11-11 10:38:20 +00:00
49bed71932
5696: Add option for parallax-aware zoom r=def- a=Fireball-Teeworlds ## How this works Currently we have parallax X and Y parameters that control how far away a layer feels. Currently it's only taken into account for camera moving sideways. This pull request makes zoom behave as if the camera was moving closer or further away, taking into account the distance to the layer. In order to provide flexibility, this behaviour is using a separate parallax value, similar to how we have separate values for X and Y. Para Zoom will default to the minimum of parallax X, Y, clamped to be between 0 and 100. This seems to look well on most maps. This pull request also introduces two new features to the Editor: * Controls for configuring per-group Para Zoom value. If Custom Zoom is set to No, Para Zoom will automatically keep following to the default value described above, so that people new to the feature don't have to figure out how it works. * Zoom button that previews the zoom behavior (next to Proof at the top). ## Editor Screenshots ### Para Zoom controls ![screenshot_2022-08-22_21-38-04](https://user-images.githubusercontent.com/68162181/186014490-f7b91245-460f-405f-8d5c-3f91db2a1b9a.png) ![screenshot_2022-08-22_21-37-58](https://user-images.githubusercontent.com/68162181/186014522-03b6e814-4dd9-4d07-9af9-7db5fb434a56.png) ### Zoom Button ![screenshot_2022-08-22_21-40-46](https://user-images.githubusercontent.com/68162181/186014856-2d1d499d-9011-439c-b217-536957e0c1e3.png) ![screenshot_2022-08-22_21-40-50](https://user-images.githubusercontent.com/68162181/186014874-6d2939d3-00ff-4327-a790-414b5151ba31.png) ## In-Game Screenshots Video: https://youtu.be/W7eXQN0gRFI This is an older version of the pull request that had an option to disable the new behaviour. The current version can only be disabled in map editor for a particular map. ### Springlobe 3 With new feature: ![screenshot_2022-08-02_04-28-19](https://user-images.githubusercontent.com/68162181/182286371-b67cee1c-73d8-4a24-a9c3-1b28340a3b42.png) Without: ![screenshot_2022-08-02_04-28-25](https://user-images.githubusercontent.com/68162181/182286367-24555381-1700-4ff1-80c7-39e5dce63820.png) ### Beyond Dreams With new feature: ![screenshot_2022-08-02_04-28-59](https://user-images.githubusercontent.com/68162181/182286322-114ecb90-0860-462c-9012-bb2f3ff848fb.png) Without: ![screenshot_2022-08-02_04-28-55](https://user-images.githubusercontent.com/68162181/182286654-f34da72b-7991-4f42-89ad-679279fcb83e.png) ## Checklist - [X] Tested the change ingame - [X] Provided screenshots if it is a visual change - [X] Tested in combination with possibly related configuration options - [ ] Written a unit test (especially base/) or added coverage to integration test - [X] Considered possible null pointers and out of bounds array indexing - [ ] Changed no physics that affect existing maps - [X] Tested the change with [ASan+UBSan or valgrind's memcheck](https://github.com/ddnet/ddnet/#using-addresssanitizer--undefinedbehavioursanitizer-or-valgrinds-memcheck) (optional) Co-authored-by: Fireball <fireball.teeworlds@gmail.com>
1715 lines
67 KiB
C++
1715 lines
67 KiB
C++
/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
|
||
/* If you are missing that file, acquire a complete release at teeworlds.com. */
|
||
#include <engine/graphics.h>
|
||
#include <engine/shared/config.h>
|
||
#include <engine/textrender.h>
|
||
|
||
#include <game/client/animstate.h>
|
||
#include <game/client/components/scoreboard.h>
|
||
#include <game/client/gameclient.h>
|
||
#include <game/client/render.h>
|
||
#include <game/generated/client_data.h>
|
||
#include <game/generated/protocol.h>
|
||
|
||
#include <game/layers.h>
|
||
#include <game/localization.h>
|
||
|
||
#include <cmath>
|
||
|
||
#include "binds.h"
|
||
#include "camera.h"
|
||
#include "controls.h"
|
||
#include "hud.h"
|
||
#include "voting.h"
|
||
|
||
CHud::CHud()
|
||
{
|
||
// won't work if zero
|
||
m_FrameTimeAvg = 0.0f;
|
||
m_FPSTextContainerIndex = -1;
|
||
}
|
||
|
||
void CHud::ResetHudContainers()
|
||
{
|
||
for(auto &ScoreInfo : m_aScoreInfo)
|
||
{
|
||
TextRender()->DeleteTextContainer(ScoreInfo.m_OptionalNameTextContainerIndex);
|
||
TextRender()->DeleteTextContainer(ScoreInfo.m_TextRankContainerIndex);
|
||
TextRender()->DeleteTextContainer(ScoreInfo.m_TextScoreContainerIndex);
|
||
if(ScoreInfo.m_RoundRectQuadContainerIndex != -1)
|
||
Graphics()->DeleteQuadContainer(ScoreInfo.m_RoundRectQuadContainerIndex);
|
||
|
||
ScoreInfo.Reset();
|
||
}
|
||
|
||
TextRender()->DeleteTextContainer(m_FPSTextContainerIndex);
|
||
}
|
||
|
||
void CHud::OnWindowResize()
|
||
{
|
||
ResetHudContainers();
|
||
}
|
||
|
||
void CHud::OnReset()
|
||
{
|
||
m_TimeCpDiff = 0.0f;
|
||
m_DDRaceTime = 0;
|
||
m_FinishTimeLastReceivedTick = 0;
|
||
m_TimeCpLastReceivedTick = 0;
|
||
m_ShowFinishTime = false;
|
||
m_ServerRecord = -1.0f;
|
||
m_aPlayerRecord[0] = -1.0f;
|
||
m_aPlayerRecord[1] = -1.0f;
|
||
|
||
ResetHudContainers();
|
||
}
|
||
|
||
void CHud::OnInit()
|
||
{
|
||
OnReset();
|
||
|
||
m_HudQuadContainerIndex = Graphics()->CreateQuadContainer(false);
|
||
Graphics()->QuadsSetSubset(0, 0, 1, 1);
|
||
PrepareAmmoHealthAndArmorQuads();
|
||
|
||
// all cursors for the different weapons
|
||
for(int i = 0; i < NUM_WEAPONS; ++i)
|
||
{
|
||
float ScaleX, ScaleY;
|
||
RenderTools()->GetSpriteScale(g_pData->m_Weapons.m_aId[i].m_pSpriteCursor, ScaleX, ScaleY);
|
||
m_aCursorOffset[i] = RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, 64.f * ScaleX, 64.f * ScaleY);
|
||
}
|
||
|
||
// the flags
|
||
m_FlagOffset = RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, 0.f, 0.f, 8.f, 16.f);
|
||
|
||
PreparePlayerStateQuads();
|
||
|
||
Graphics()->QuadContainerUpload(m_HudQuadContainerIndex);
|
||
}
|
||
|
||
void CHud::RenderGameTimer()
|
||
{
|
||
float Half = m_Width / 2.0f;
|
||
|
||
if(!(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_SUDDENDEATH))
|
||
{
|
||
char aBuf[32];
|
||
int Time = 0;
|
||
if(m_pClient->m_Snap.m_pGameInfoObj->m_TimeLimit && (m_pClient->m_Snap.m_pGameInfoObj->m_WarmupTimer <= 0))
|
||
{
|
||
Time = m_pClient->m_Snap.m_pGameInfoObj->m_TimeLimit * 60 - ((Client()->GameTick(g_Config.m_ClDummy) - m_pClient->m_Snap.m_pGameInfoObj->m_RoundStartTick) / Client()->GameTickSpeed());
|
||
|
||
if(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_GAMEOVER)
|
||
Time = 0;
|
||
}
|
||
else if(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_RACETIME)
|
||
{
|
||
//The Warmup timer is negative in this case to make sure that incompatible clients will not see a warmup timer
|
||
Time = (Client()->GameTick(g_Config.m_ClDummy) + m_pClient->m_Snap.m_pGameInfoObj->m_WarmupTimer) / Client()->GameTickSpeed();
|
||
}
|
||
else
|
||
Time = (Client()->GameTick(g_Config.m_ClDummy) - m_pClient->m_Snap.m_pGameInfoObj->m_RoundStartTick) / Client()->GameTickSpeed();
|
||
|
||
str_time((int64_t)Time * 100, TIME_DAYS, aBuf, sizeof(aBuf));
|
||
float FontSize = 10.0f;
|
||
static float s_TextWidthM = TextRender()->TextWidth(0, FontSize, "00:00", -1, -1.0f);
|
||
static float s_TextWidthH = TextRender()->TextWidth(0, FontSize, "00:00:00", -1, -1.0f);
|
||
static float s_TextWidth0D = TextRender()->TextWidth(0, FontSize, "0d 00:00:00", -1, -1.0f);
|
||
static float s_TextWidth00D = TextRender()->TextWidth(0, FontSize, "00d 00:00:00", -1, -1.0f);
|
||
static float s_TextWidth000D = TextRender()->TextWidth(0, FontSize, "000d 00:00:00", -1, -1.0f);
|
||
float w = Time >= 3600 * 24 * 100 ? s_TextWidth000D : Time >= 3600 * 24 * 10 ? s_TextWidth00D : Time >= 3600 * 24 ? s_TextWidth0D : Time >= 3600 ? s_TextWidthH : s_TextWidthM;
|
||
// last 60 sec red, last 10 sec blink
|
||
if(m_pClient->m_Snap.m_pGameInfoObj->m_TimeLimit && Time <= 60 && (m_pClient->m_Snap.m_pGameInfoObj->m_WarmupTimer <= 0))
|
||
{
|
||
float Alpha = Time <= 10 && (2 * time() / time_freq()) % 2 ? 0.5f : 1.0f;
|
||
TextRender()->TextColor(1.0f, 0.25f, 0.25f, Alpha);
|
||
}
|
||
TextRender()->Text(0, Half - w / 2, 2, FontSize, aBuf, -1.0f);
|
||
TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f);
|
||
}
|
||
}
|
||
|
||
void CHud::RenderPauseNotification()
|
||
{
|
||
if(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_PAUSED &&
|
||
!(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_GAMEOVER))
|
||
{
|
||
const char *pText = Localize("Game paused");
|
||
float FontSize = 20.0f;
|
||
float w = TextRender()->TextWidth(0, FontSize, pText, -1, -1.0f);
|
||
TextRender()->Text(0, 150.0f * Graphics()->ScreenAspect() + -w / 2.0f, 50.0f, FontSize, pText, -1.0f);
|
||
}
|
||
}
|
||
|
||
void CHud::RenderSuddenDeath()
|
||
{
|
||
if(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_SUDDENDEATH)
|
||
{
|
||
float Half = m_Width / 2.0f;
|
||
const char *pText = Localize("Sudden Death");
|
||
float FontSize = 12.0f;
|
||
float w = TextRender()->TextWidth(0, FontSize, pText, -1, -1.0f);
|
||
TextRender()->Text(0, Half - w / 2, 2, FontSize, pText, -1.0f);
|
||
}
|
||
}
|
||
|
||
void CHud::RenderScoreHud()
|
||
{
|
||
// render small score hud
|
||
if(!(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_GAMEOVER))
|
||
{
|
||
int GameFlags = m_pClient->m_Snap.m_pGameInfoObj->m_GameFlags;
|
||
float StartY = 229.0f; // the height of this display is 56, so EndY is 285
|
||
|
||
const float ScoreSingleBoxHeight = 18.0f;
|
||
|
||
bool ForceScoreInfoInit = !m_aScoreInfo[0].m_Initialized || !m_aScoreInfo[1].m_Initialized;
|
||
m_aScoreInfo[0].m_Initialized = m_aScoreInfo[1].m_Initialized = true;
|
||
|
||
if(GameFlags & GAMEFLAG_TEAMS && m_pClient->m_Snap.m_pGameDataObj)
|
||
{
|
||
char aScoreTeam[2][16];
|
||
str_format(aScoreTeam[TEAM_RED], sizeof(aScoreTeam), "%d", m_pClient->m_Snap.m_pGameDataObj->m_TeamscoreRed);
|
||
str_format(aScoreTeam[TEAM_BLUE], sizeof(aScoreTeam), "%d", m_pClient->m_Snap.m_pGameDataObj->m_TeamscoreBlue);
|
||
|
||
bool aRecreateTeamScore[2] = {str_comp(aScoreTeam[0], m_aScoreInfo[0].m_aScoreText) != 0, str_comp(aScoreTeam[1], m_aScoreInfo[1].m_aScoreText) != 0};
|
||
|
||
int aFlagCarrier[2] = {
|
||
m_pClient->m_Snap.m_pGameDataObj->m_FlagCarrierRed,
|
||
m_pClient->m_Snap.m_pGameDataObj->m_FlagCarrierBlue};
|
||
|
||
bool RecreateRect = ForceScoreInfoInit;
|
||
for(int t = 0; t < 2; t++)
|
||
{
|
||
if(aRecreateTeamScore[t])
|
||
{
|
||
m_aScoreInfo[t].m_ScoreTextWidth = TextRender()->TextWidth(0, 14.0f, aScoreTeam[t == 0 ? TEAM_RED : TEAM_BLUE], -1, -1.0f);
|
||
mem_copy(m_aScoreInfo[t].m_aScoreText, aScoreTeam[t == 0 ? TEAM_RED : TEAM_BLUE], sizeof(m_aScoreInfo[t].m_aScoreText));
|
||
RecreateRect = true;
|
||
}
|
||
}
|
||
|
||
static float s_TextWidth100 = TextRender()->TextWidth(0, 14.0f, "100", -1, -1.0f);
|
||
float ScoreWidthMax = maximum(maximum(m_aScoreInfo[0].m_ScoreTextWidth, m_aScoreInfo[1].m_ScoreTextWidth), s_TextWidth100);
|
||
float Split = 3.0f;
|
||
float ImageSize = GameFlags & GAMEFLAG_FLAGS ? 16.0f : Split;
|
||
for(int t = 0; t < 2; t++)
|
||
{
|
||
// draw box
|
||
if(RecreateRect)
|
||
{
|
||
if(m_aScoreInfo[t].m_RoundRectQuadContainerIndex != -1)
|
||
Graphics()->DeleteQuadContainer(m_aScoreInfo[t].m_RoundRectQuadContainerIndex);
|
||
|
||
if(t == 0)
|
||
Graphics()->SetColor(1.0f, 0.0f, 0.0f, 0.25f);
|
||
else
|
||
Graphics()->SetColor(0.0f, 0.0f, 1.0f, 0.25f);
|
||
m_aScoreInfo[t].m_RoundRectQuadContainerIndex = Graphics()->CreateRectQuadContainer(m_Width - ScoreWidthMax - ImageSize - 2 * Split, StartY + t * 20, ScoreWidthMax + ImageSize + 2 * Split, ScoreSingleBoxHeight, 5.0f, IGraphics::CORNER_L);
|
||
}
|
||
Graphics()->TextureClear();
|
||
Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f);
|
||
if(m_aScoreInfo[t].m_RoundRectQuadContainerIndex != -1)
|
||
Graphics()->RenderQuadContainer(m_aScoreInfo[t].m_RoundRectQuadContainerIndex, -1);
|
||
|
||
// draw score
|
||
if(aRecreateTeamScore[t])
|
||
{
|
||
TextRender()->DeleteTextContainer(m_aScoreInfo[t].m_TextScoreContainerIndex);
|
||
|
||
CTextCursor Cursor;
|
||
TextRender()->SetCursor(&Cursor, m_Width - ScoreWidthMax + (ScoreWidthMax - m_aScoreInfo[t].m_ScoreTextWidth) / 2 - Split, StartY + t * 20 + (18.f - 14.f) / 2.f, 14.0f, TEXTFLAG_RENDER);
|
||
Cursor.m_LineWidth = -1;
|
||
TextRender()->CreateTextContainer(m_aScoreInfo[t].m_TextScoreContainerIndex, &Cursor, aScoreTeam[t]);
|
||
}
|
||
if(m_aScoreInfo[t].m_TextScoreContainerIndex != -1)
|
||
{
|
||
ColorRGBA TColor(1.f, 1.f, 1.f, 1.f);
|
||
ColorRGBA TOutlineColor(0.f, 0.f, 0.f, 0.3f);
|
||
TextRender()->RenderTextContainer(m_aScoreInfo[t].m_TextScoreContainerIndex, TColor, TOutlineColor);
|
||
}
|
||
|
||
if(GameFlags & GAMEFLAG_FLAGS)
|
||
{
|
||
int BlinkTimer = (m_pClient->m_aFlagDropTick[t] != 0 &&
|
||
(Client()->GameTick(g_Config.m_ClDummy) - m_pClient->m_aFlagDropTick[t]) / Client()->GameTickSpeed() >= 25) ?
|
||
10 :
|
||
20;
|
||
if(aFlagCarrier[t] == FLAG_ATSTAND || (aFlagCarrier[t] == FLAG_TAKEN && ((Client()->GameTick(g_Config.m_ClDummy) / BlinkTimer) & 1)))
|
||
{
|
||
// draw flag
|
||
Graphics()->TextureSet(t == 0 ? m_pClient->m_GameSkin.m_SpriteFlagRed : m_pClient->m_GameSkin.m_SpriteFlagBlue);
|
||
Graphics()->SetColor(1.f, 1.f, 1.f, 1.f);
|
||
Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_FlagOffset, m_Width - ScoreWidthMax - ImageSize, StartY + 1.0f + t * 20);
|
||
}
|
||
else if(aFlagCarrier[t] >= 0)
|
||
{
|
||
// draw name of the flag holder
|
||
int ID = aFlagCarrier[t] % MAX_CLIENTS;
|
||
const char *pName = m_pClient->m_aClients[ID].m_aName;
|
||
if(str_comp(pName, m_aScoreInfo[t].m_aPlayerNameText) != 0 || RecreateRect)
|
||
{
|
||
mem_copy(m_aScoreInfo[t].m_aPlayerNameText, pName, sizeof(m_aScoreInfo[t].m_aPlayerNameText));
|
||
|
||
TextRender()->DeleteTextContainer(m_aScoreInfo[t].m_OptionalNameTextContainerIndex);
|
||
|
||
float w = TextRender()->TextWidth(0, 8.0f, pName, -1, -1.0f);
|
||
|
||
CTextCursor Cursor;
|
||
TextRender()->SetCursor(&Cursor, minimum(m_Width - w - 1.0f, m_Width - ScoreWidthMax - ImageSize - 2 * Split), StartY + (t + 1) * 20.0f - 2.0f, 8.0f, TEXTFLAG_RENDER);
|
||
Cursor.m_LineWidth = -1;
|
||
TextRender()->CreateTextContainer(m_aScoreInfo[t].m_OptionalNameTextContainerIndex, &Cursor, pName);
|
||
}
|
||
|
||
if(m_aScoreInfo[t].m_OptionalNameTextContainerIndex != -1)
|
||
{
|
||
ColorRGBA TColor(1.f, 1.f, 1.f, 1.f);
|
||
ColorRGBA TOutlineColor(0.f, 0.f, 0.f, 0.3f);
|
||
TextRender()->RenderTextContainer(m_aScoreInfo[t].m_OptionalNameTextContainerIndex, TColor, TOutlineColor);
|
||
}
|
||
|
||
// draw tee of the flag holder
|
||
CTeeRenderInfo TeeInfo = m_pClient->m_aClients[ID].m_RenderInfo;
|
||
TeeInfo.m_Size = ScoreSingleBoxHeight;
|
||
|
||
CAnimState *pIdleState = CAnimState::GetIdle();
|
||
vec2 OffsetToMid;
|
||
RenderTools()->GetRenderTeeOffsetToRenderedTee(pIdleState, &TeeInfo, OffsetToMid);
|
||
vec2 TeeRenderPos(m_Width - ScoreWidthMax - TeeInfo.m_Size / 2 - Split, StartY + (t * 20) + ScoreSingleBoxHeight / 2.0f + OffsetToMid.y);
|
||
|
||
RenderTools()->RenderTee(pIdleState, &TeeInfo, EMOTE_NORMAL, vec2(1.0f, 0.0f), TeeRenderPos);
|
||
}
|
||
}
|
||
StartY += 8.0f;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
int Local = -1;
|
||
int aPos[2] = {1, 2};
|
||
const CNetObj_PlayerInfo *apPlayerInfo[2] = {0, 0};
|
||
int i = 0;
|
||
for(int t = 0; t < 2 && i < MAX_CLIENTS && m_pClient->m_Snap.m_apInfoByScore[i]; ++i)
|
||
{
|
||
if(m_pClient->m_Snap.m_apInfoByScore[i]->m_Team != TEAM_SPECTATORS)
|
||
{
|
||
apPlayerInfo[t] = m_pClient->m_Snap.m_apInfoByScore[i];
|
||
if(apPlayerInfo[t]->m_ClientID == m_pClient->m_Snap.m_LocalClientID)
|
||
Local = t;
|
||
++t;
|
||
}
|
||
}
|
||
// search local player info if not a spectator, nor within top2 scores
|
||
if(Local == -1 && m_pClient->m_Snap.m_pLocalInfo && m_pClient->m_Snap.m_pLocalInfo->m_Team != TEAM_SPECTATORS)
|
||
{
|
||
for(; i < MAX_CLIENTS && m_pClient->m_Snap.m_apInfoByScore[i]; ++i)
|
||
{
|
||
if(m_pClient->m_Snap.m_apInfoByScore[i]->m_Team != TEAM_SPECTATORS)
|
||
++aPos[1];
|
||
if(m_pClient->m_Snap.m_apInfoByScore[i]->m_ClientID == m_pClient->m_Snap.m_LocalClientID)
|
||
{
|
||
apPlayerInfo[1] = m_pClient->m_Snap.m_apInfoByScore[i];
|
||
Local = 1;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
char aScore[2][16];
|
||
for(int t = 0; t < 2; ++t)
|
||
{
|
||
if(apPlayerInfo[t])
|
||
{
|
||
if(m_pClient->m_GameInfo.m_TimeScore && g_Config.m_ClDDRaceScoreBoard)
|
||
{
|
||
if(apPlayerInfo[t]->m_Score != -9999)
|
||
str_time((int64_t)abs(apPlayerInfo[t]->m_Score) * 100, TIME_HOURS, aScore[t], sizeof(aScore[t]));
|
||
else
|
||
aScore[t][0] = 0;
|
||
}
|
||
else
|
||
str_format(aScore[t], sizeof(aScore) / 2, "%d", apPlayerInfo[t]->m_Score);
|
||
}
|
||
else
|
||
aScore[t][0] = 0;
|
||
}
|
||
|
||
static int LocalClientID = -1;
|
||
bool RecreateScores = str_comp(aScore[0], m_aScoreInfo[0].m_aScoreText) != 0 || str_comp(aScore[1], m_aScoreInfo[1].m_aScoreText) != 0 || LocalClientID != m_pClient->m_Snap.m_LocalClientID;
|
||
LocalClientID = m_pClient->m_Snap.m_LocalClientID;
|
||
|
||
bool RecreateRect = ForceScoreInfoInit;
|
||
for(int t = 0; t < 2; t++)
|
||
{
|
||
if(RecreateScores)
|
||
{
|
||
m_aScoreInfo[t].m_ScoreTextWidth = TextRender()->TextWidth(0, 14.0f, aScore[t], -1, -1.0f);
|
||
mem_copy(m_aScoreInfo[t].m_aScoreText, aScore[t], sizeof(m_aScoreInfo[t].m_aScoreText));
|
||
RecreateRect = true;
|
||
}
|
||
|
||
if(apPlayerInfo[t])
|
||
{
|
||
int ID = apPlayerInfo[t]->m_ClientID;
|
||
if(ID >= 0 && ID < MAX_CLIENTS)
|
||
{
|
||
const char *pName = m_pClient->m_aClients[ID].m_aName;
|
||
if(str_comp(pName, m_aScoreInfo[t].m_aPlayerNameText) != 0)
|
||
RecreateRect = true;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if(m_aScoreInfo[t].m_aPlayerNameText[0] != 0)
|
||
RecreateRect = true;
|
||
}
|
||
|
||
char aBuf[16];
|
||
str_format(aBuf, sizeof(aBuf), "%d.", aPos[t]);
|
||
if(str_comp(aBuf, m_aScoreInfo[t].m_aRankText) != 0)
|
||
RecreateRect = true;
|
||
}
|
||
|
||
static float s_TextWidth10 = TextRender()->TextWidth(0, 14.0f, "10", -1, -1.0f);
|
||
float ScoreWidthMax = maximum(maximum(m_aScoreInfo[0].m_ScoreTextWidth, m_aScoreInfo[1].m_ScoreTextWidth), s_TextWidth10);
|
||
float Split = 3.0f, ImageSize = 16.0f, PosSize = 16.0f;
|
||
|
||
for(int t = 0; t < 2; t++)
|
||
{
|
||
// draw box
|
||
if(RecreateRect)
|
||
{
|
||
if(m_aScoreInfo[t].m_RoundRectQuadContainerIndex != -1)
|
||
Graphics()->DeleteQuadContainer(m_aScoreInfo[t].m_RoundRectQuadContainerIndex);
|
||
|
||
if(t == Local)
|
||
Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.25f);
|
||
else
|
||
Graphics()->SetColor(0.0f, 0.0f, 0.0f, 0.25f);
|
||
m_aScoreInfo[t].m_RoundRectQuadContainerIndex = Graphics()->CreateRectQuadContainer(m_Width - ScoreWidthMax - ImageSize - 2 * Split - PosSize, StartY + t * 20, ScoreWidthMax + ImageSize + 2 * Split + PosSize, ScoreSingleBoxHeight, 5.0f, IGraphics::CORNER_L);
|
||
}
|
||
Graphics()->TextureClear();
|
||
Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f);
|
||
if(m_aScoreInfo[t].m_RoundRectQuadContainerIndex != -1)
|
||
Graphics()->RenderQuadContainer(m_aScoreInfo[t].m_RoundRectQuadContainerIndex, -1);
|
||
|
||
if(RecreateScores)
|
||
{
|
||
TextRender()->DeleteTextContainer(m_aScoreInfo[t].m_TextScoreContainerIndex);
|
||
|
||
CTextCursor Cursor;
|
||
TextRender()->SetCursor(&Cursor, m_Width - ScoreWidthMax + (ScoreWidthMax - m_aScoreInfo[t].m_ScoreTextWidth) - Split, StartY + t * 20 + (18.f - 14.f) / 2.f, 14.0f, TEXTFLAG_RENDER);
|
||
Cursor.m_LineWidth = -1;
|
||
TextRender()->CreateTextContainer(m_aScoreInfo[t].m_TextScoreContainerIndex, &Cursor, aScore[t]);
|
||
}
|
||
// draw score
|
||
if(m_aScoreInfo[t].m_TextScoreContainerIndex != -1)
|
||
{
|
||
ColorRGBA TColor(1.f, 1.f, 1.f, 1.f);
|
||
ColorRGBA TOutlineColor(0.f, 0.f, 0.f, 0.3f);
|
||
TextRender()->RenderTextContainer(m_aScoreInfo[t].m_TextScoreContainerIndex, TColor, TOutlineColor);
|
||
}
|
||
|
||
if(apPlayerInfo[t])
|
||
{
|
||
// draw name
|
||
int ID = apPlayerInfo[t]->m_ClientID;
|
||
if(ID >= 0 && ID < MAX_CLIENTS)
|
||
{
|
||
const char *pName = m_pClient->m_aClients[ID].m_aName;
|
||
if(RecreateRect)
|
||
{
|
||
mem_copy(m_aScoreInfo[t].m_aPlayerNameText, pName, sizeof(m_aScoreInfo[t].m_aPlayerNameText));
|
||
|
||
TextRender()->DeleteTextContainer(m_aScoreInfo[t].m_OptionalNameTextContainerIndex);
|
||
|
||
CTextCursor Cursor;
|
||
float w = TextRender()->TextWidth(0, 8.0f, pName, -1, -1.0f);
|
||
TextRender()->SetCursor(&Cursor, minimum(m_Width - w - 1.0f, m_Width - ScoreWidthMax - ImageSize - 2 * Split - PosSize), StartY + (t + 1) * 20.0f - 2.0f, 8.0f, TEXTFLAG_RENDER);
|
||
Cursor.m_LineWidth = -1;
|
||
TextRender()->CreateTextContainer(m_aScoreInfo[t].m_OptionalNameTextContainerIndex, &Cursor, pName);
|
||
}
|
||
|
||
if(m_aScoreInfo[t].m_OptionalNameTextContainerIndex != -1)
|
||
{
|
||
ColorRGBA TColor(1.f, 1.f, 1.f, 1.f);
|
||
ColorRGBA TOutlineColor(0.f, 0.f, 0.f, 0.3f);
|
||
TextRender()->RenderTextContainer(m_aScoreInfo[t].m_OptionalNameTextContainerIndex, TColor, TOutlineColor);
|
||
}
|
||
|
||
// draw tee
|
||
CTeeRenderInfo TeeInfo = m_pClient->m_aClients[ID].m_RenderInfo;
|
||
TeeInfo.m_Size = ScoreSingleBoxHeight;
|
||
|
||
CAnimState *pIdleState = CAnimState::GetIdle();
|
||
vec2 OffsetToMid;
|
||
RenderTools()->GetRenderTeeOffsetToRenderedTee(pIdleState, &TeeInfo, OffsetToMid);
|
||
vec2 TeeRenderPos(m_Width - ScoreWidthMax - TeeInfo.m_Size / 2 - Split, StartY + (t * 20) + ScoreSingleBoxHeight / 2.0f + OffsetToMid.y);
|
||
|
||
RenderTools()->RenderTee(pIdleState, &TeeInfo, EMOTE_NORMAL, vec2(1.0f, 0.0f), TeeRenderPos);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
m_aScoreInfo[t].m_aPlayerNameText[0] = 0;
|
||
}
|
||
|
||
// draw position
|
||
char aBuf[16];
|
||
str_format(aBuf, sizeof(aBuf), "%d.", aPos[t]);
|
||
if(RecreateRect)
|
||
{
|
||
mem_copy(m_aScoreInfo[t].m_aRankText, aBuf, sizeof(m_aScoreInfo[t].m_aRankText));
|
||
|
||
TextRender()->DeleteTextContainer(m_aScoreInfo[t].m_TextRankContainerIndex);
|
||
|
||
CTextCursor Cursor;
|
||
TextRender()->SetCursor(&Cursor, m_Width - ScoreWidthMax - ImageSize - Split - PosSize, StartY + t * 20 + (18.f - 10.f) / 2.f, 10.0f, TEXTFLAG_RENDER);
|
||
Cursor.m_LineWidth = -1;
|
||
TextRender()->CreateTextContainer(m_aScoreInfo[t].m_TextRankContainerIndex, &Cursor, aBuf);
|
||
}
|
||
if(m_aScoreInfo[t].m_TextRankContainerIndex != -1)
|
||
{
|
||
ColorRGBA TColor(1.f, 1.f, 1.f, 1.f);
|
||
ColorRGBA TOutlineColor(0.f, 0.f, 0.f, 0.3f);
|
||
TextRender()->RenderTextContainer(m_aScoreInfo[t].m_TextRankContainerIndex, TColor, TOutlineColor);
|
||
}
|
||
|
||
StartY += 8.0f;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
void CHud::RenderWarmupTimer()
|
||
{
|
||
// render warmup timer
|
||
if(m_pClient->m_Snap.m_pGameInfoObj->m_WarmupTimer > 0 && !(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_RACETIME))
|
||
{
|
||
char aBuf[256];
|
||
float FontSize = 20.0f;
|
||
float w = TextRender()->TextWidth(0, FontSize, Localize("Warmup"), -1, -1.0f);
|
||
TextRender()->Text(0, 150 * Graphics()->ScreenAspect() + -w / 2, 50, FontSize, Localize("Warmup"), -1.0f);
|
||
|
||
int Seconds = m_pClient->m_Snap.m_pGameInfoObj->m_WarmupTimer / SERVER_TICK_SPEED;
|
||
if(Seconds < 5)
|
||
str_format(aBuf, sizeof(aBuf), "%d.%d", Seconds, (m_pClient->m_Snap.m_pGameInfoObj->m_WarmupTimer * 10 / SERVER_TICK_SPEED) % 10);
|
||
else
|
||
str_format(aBuf, sizeof(aBuf), "%d", Seconds);
|
||
w = TextRender()->TextWidth(0, FontSize, aBuf, -1, -1.0f);
|
||
TextRender()->Text(0, 150 * Graphics()->ScreenAspect() + -w / 2, 75, FontSize, aBuf, -1.0f);
|
||
}
|
||
}
|
||
|
||
void CHud::RenderTextInfo()
|
||
{
|
||
if(g_Config.m_ClShowfps)
|
||
{
|
||
// calculate avg. fps
|
||
m_FrameTimeAvg = m_FrameTimeAvg * 0.9f + Client()->RenderFrameTime() * 0.1f;
|
||
char aBuf[64];
|
||
int FrameTime = (int)(1.0f / m_FrameTimeAvg + 0.5f);
|
||
str_format(aBuf, sizeof(aBuf), "%d", FrameTime);
|
||
|
||
static float s_TextWidth0 = TextRender()->TextWidth(0, 12.f, "0", -1, -1.0f);
|
||
static float s_TextWidth00 = TextRender()->TextWidth(0, 12.f, "00", -1, -1.0f);
|
||
static float s_TextWidth000 = TextRender()->TextWidth(0, 12.f, "000", -1, -1.0f);
|
||
static float s_TextWidth0000 = TextRender()->TextWidth(0, 12.f, "0000", -1, -1.0f);
|
||
static float s_TextWidth00000 = TextRender()->TextWidth(0, 12.f, "00000", -1, -1.0f);
|
||
static float s_aTextWidth[5] = {s_TextWidth0, s_TextWidth00, s_TextWidth000, s_TextWidth0000, s_TextWidth00000};
|
||
|
||
int DigitIndex = GetDigitsIndex(FrameTime, 4);
|
||
//TextRender()->Text(0, m_Width-10-TextRender()->TextWidth(0,12,Buf,-1,-1.0f), 5, 12, Buf, -1.0f);
|
||
|
||
CTextCursor Cursor;
|
||
TextRender()->SetCursor(&Cursor, m_Width - 10 - s_aTextWidth[DigitIndex], 5, 12, TEXTFLAG_RENDER);
|
||
Cursor.m_LineWidth = -1;
|
||
auto OldFlags = TextRender()->GetRenderFlags();
|
||
TextRender()->SetRenderFlags(OldFlags | TEXT_RENDER_FLAG_ONE_TIME_USE);
|
||
if(m_FPSTextContainerIndex == -1)
|
||
TextRender()->CreateTextContainer(m_FPSTextContainerIndex, &Cursor, "0");
|
||
else
|
||
TextRender()->RecreateTextContainerSoft(&Cursor, m_FPSTextContainerIndex, aBuf);
|
||
TextRender()->SetRenderFlags(OldFlags);
|
||
ColorRGBA TColor(1, 1, 1, 1);
|
||
ColorRGBA TOutColor(0, 0, 0, 0.3f);
|
||
TextRender()->RenderTextContainer(m_FPSTextContainerIndex, TColor, TOutColor);
|
||
}
|
||
if(g_Config.m_ClShowpred)
|
||
{
|
||
char aBuf[64];
|
||
str_format(aBuf, sizeof(aBuf), "%d", Client()->GetPredictionTime());
|
||
TextRender()->Text(0, m_Width - 10 - TextRender()->TextWidth(0, 12, aBuf, -1, -1.0f), g_Config.m_ClShowfps ? 20 : 5, 12, aBuf, -1.0f);
|
||
}
|
||
}
|
||
|
||
void CHud::RenderConnectionWarning()
|
||
{
|
||
if(Client()->ConnectionProblems())
|
||
{
|
||
const char *pText = Localize("Connection Problems...");
|
||
float w = TextRender()->TextWidth(0, 24, pText, -1, -1.0f);
|
||
TextRender()->Text(0, 150 * Graphics()->ScreenAspect() - w / 2, 50, 24, pText, -1.0f);
|
||
}
|
||
}
|
||
|
||
void CHud::RenderTeambalanceWarning()
|
||
{
|
||
// render prompt about team-balance
|
||
bool Flash = time() / (time_freq() / 2) % 2 == 0;
|
||
if(m_pClient->m_Snap.m_pGameInfoObj->m_GameFlags & GAMEFLAG_TEAMS)
|
||
{
|
||
int TeamDiff = m_pClient->m_Snap.m_aTeamSize[TEAM_RED] - m_pClient->m_Snap.m_aTeamSize[TEAM_BLUE];
|
||
if(g_Config.m_ClWarningTeambalance && (TeamDiff >= 2 || TeamDiff <= -2))
|
||
{
|
||
const char *pText = Localize("Please balance teams!");
|
||
if(Flash)
|
||
TextRender()->TextColor(1, 1, 0.5f, 1);
|
||
else
|
||
TextRender()->TextColor(0.7f, 0.7f, 0.2f, 1.0f);
|
||
TextRender()->Text(0x0, 5, 50, 6, pText, -1.0f);
|
||
TextRender()->TextColor(1, 1, 1, 1);
|
||
}
|
||
}
|
||
}
|
||
|
||
void CHud::RenderVoting()
|
||
{
|
||
if((!g_Config.m_ClShowVotesAfterVoting && !m_pClient->m_Scoreboard.Active() && m_pClient->m_Voting.TakenChoice()) || !m_pClient->m_Voting.IsVoting() || Client()->State() == IClient::STATE_DEMOPLAYBACK)
|
||
return;
|
||
|
||
Graphics()->DrawRect(-10, 60 - 2, 100 + 10 + 4 + 5, 46, ColorRGBA(0.0f, 0.0f, 0.0f, 0.4f), IGraphics::CORNER_ALL, 5.0f);
|
||
|
||
TextRender()->TextColor(1, 1, 1, 1);
|
||
|
||
CTextCursor Cursor;
|
||
char aBuf[512];
|
||
str_format(aBuf, sizeof(aBuf), Localize("%ds left"), m_pClient->m_Voting.SecondsLeft());
|
||
float tw = TextRender()->TextWidth(0x0, 6, aBuf, -1, -1.0f);
|
||
TextRender()->SetCursor(&Cursor, 5.0f + 100.0f - tw, 60.0f, 6.0f, TEXTFLAG_RENDER);
|
||
TextRender()->TextEx(&Cursor, aBuf, -1);
|
||
|
||
TextRender()->SetCursor(&Cursor, 5.0f, 60.0f, 6.0f, TEXTFLAG_RENDER);
|
||
Cursor.m_LineWidth = 100.0f - tw;
|
||
Cursor.m_MaxLines = 3;
|
||
TextRender()->TextEx(&Cursor, m_pClient->m_Voting.VoteDescription(), -1);
|
||
|
||
// reason
|
||
str_format(aBuf, sizeof(aBuf), "%s %s", Localize("Reason:"), m_pClient->m_Voting.VoteReason());
|
||
TextRender()->SetCursor(&Cursor, 5.0f, 79.0f, 6.0f, TEXTFLAG_RENDER | TEXTFLAG_STOP_AT_END);
|
||
Cursor.m_LineWidth = 100.0f;
|
||
TextRender()->TextEx(&Cursor, aBuf, -1);
|
||
|
||
CUIRect Base = {5, 88, 100, 4};
|
||
m_pClient->m_Voting.RenderBars(Base, false);
|
||
|
||
char aKey[64];
|
||
m_pClient->m_Binds.GetKey("vote yes", aKey, sizeof(aKey));
|
||
|
||
str_format(aBuf, sizeof(aBuf), "%s - %s", aKey, Localize("Vote yes"));
|
||
Base.y += Base.h;
|
||
Base.h = 11.f;
|
||
UI()->DoLabel(&Base, aBuf, 6.0f, TEXTALIGN_LEFT);
|
||
|
||
m_pClient->m_Binds.GetKey("vote no", aKey, sizeof(aKey));
|
||
str_format(aBuf, sizeof(aBuf), "%s - %s", Localize("Vote no"), aKey);
|
||
UI()->DoLabel(&Base, aBuf, 6.0f, TEXTALIGN_RIGHT);
|
||
}
|
||
|
||
void CHud::RenderCursor()
|
||
{
|
||
if(!m_pClient->m_Snap.m_pLocalCharacter || Client()->State() == IClient::STATE_DEMOPLAYBACK)
|
||
return;
|
||
|
||
RenderTools()->MapScreenToInterface(m_pClient->m_Camera.m_Center.x, m_pClient->m_Camera.m_Center.y);
|
||
|
||
// render cursor
|
||
int CurWeapon = m_pClient->m_Snap.m_pLocalCharacter->m_Weapon % NUM_WEAPONS;
|
||
Graphics()->SetColor(1.f, 1.f, 1.f, 1.f);
|
||
Graphics()->TextureSet(m_pClient->m_GameSkin.m_aSpriteWeaponCursors[CurWeapon]);
|
||
Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_aCursorOffset[CurWeapon], m_pClient->m_Controls.m_aTargetPos[g_Config.m_ClDummy].x, m_pClient->m_Controls.m_aTargetPos[g_Config.m_ClDummy].y);
|
||
}
|
||
|
||
void CHud::PrepareAmmoHealthAndArmorQuads()
|
||
{
|
||
float x = 5;
|
||
float y = 5;
|
||
IGraphics::CQuadItem Array[10];
|
||
|
||
// ammo of the different weapons
|
||
for(int i = 0; i < NUM_WEAPONS; ++i)
|
||
{
|
||
// 0.6
|
||
for(int n = 0; n < 10; n++)
|
||
Array[n] = IGraphics::CQuadItem(x + n * 12, y, 10, 10);
|
||
|
||
m_aAmmoOffset[i] = Graphics()->QuadContainerAddQuads(m_HudQuadContainerIndex, Array, 10);
|
||
|
||
// 0.7
|
||
if(i == WEAPON_GRENADE)
|
||
{
|
||
// special case for 0.7 grenade
|
||
for(int n = 0; n < 10; n++)
|
||
Array[n] = IGraphics::CQuadItem(1 + x + n * 12, y, 10, 10);
|
||
}
|
||
else
|
||
{
|
||
for(int n = 0; n < 10; n++)
|
||
Array[n] = IGraphics::CQuadItem(x + n * 12, y, 12, 12);
|
||
}
|
||
|
||
Graphics()->QuadContainerAddQuads(m_HudQuadContainerIndex, Array, 10);
|
||
}
|
||
|
||
// health
|
||
for(int i = 0; i < 10; ++i)
|
||
Array[i] = IGraphics::CQuadItem(x + i * 12, y, 10, 10);
|
||
m_HealthOffset = Graphics()->QuadContainerAddQuads(m_HudQuadContainerIndex, Array, 10);
|
||
|
||
// 0.7
|
||
for(int i = 0; i < 10; ++i)
|
||
Array[i] = IGraphics::CQuadItem(x + i * 12, y, 12, 12);
|
||
Graphics()->QuadContainerAddQuads(m_HudQuadContainerIndex, Array, 10);
|
||
|
||
// empty health
|
||
for(int i = 0; i < 10; ++i)
|
||
Array[i] = IGraphics::CQuadItem(x + i * 12, y, 10, 10);
|
||
m_EmptyHealthOffset = Graphics()->QuadContainerAddQuads(m_HudQuadContainerIndex, Array, 10);
|
||
|
||
// 0.7
|
||
for(int i = 0; i < 10; ++i)
|
||
Array[i] = IGraphics::CQuadItem(x + i * 12, y, 12, 12);
|
||
Graphics()->QuadContainerAddQuads(m_HudQuadContainerIndex, Array, 10);
|
||
|
||
// armor meter
|
||
for(int i = 0; i < 10; ++i)
|
||
Array[i] = IGraphics::CQuadItem(x + i * 12, y + 12, 10, 10);
|
||
m_ArmorOffset = Graphics()->QuadContainerAddQuads(m_HudQuadContainerIndex, Array, 10);
|
||
|
||
// 0.7
|
||
for(int i = 0; i < 10; ++i)
|
||
Array[i] = IGraphics::CQuadItem(x + i * 12, y + 12, 12, 12);
|
||
Graphics()->QuadContainerAddQuads(m_HudQuadContainerIndex, Array, 10);
|
||
|
||
// empty armor meter
|
||
for(int i = 0; i < 10; ++i)
|
||
Array[i] = IGraphics::CQuadItem(x + i * 12, y + 12, 10, 10);
|
||
m_EmptyArmorOffset = Graphics()->QuadContainerAddQuads(m_HudQuadContainerIndex, Array, 10);
|
||
|
||
// 0.7
|
||
for(int i = 0; i < 10; ++i)
|
||
Array[i] = IGraphics::CQuadItem(x + i * 12, y + 12, 12, 12);
|
||
Graphics()->QuadContainerAddQuads(m_HudQuadContainerIndex, Array, 10);
|
||
}
|
||
|
||
void CHud::RenderAmmoHealthAndArmor(const CNetObj_Character *pCharacter)
|
||
{
|
||
if(!pCharacter)
|
||
return;
|
||
|
||
bool IsSixupGameSkin = m_pClient->m_GameSkin.IsSixup();
|
||
int QuadOffsetSixup = (IsSixupGameSkin ? 10 : 0);
|
||
|
||
if(GameClient()->m_GameInfo.m_HudAmmo)
|
||
{
|
||
// ammo display
|
||
float AmmoOffsetY = GameClient()->m_GameInfo.m_HudHealthArmor ? 24 : 0;
|
||
int CurWeapon = pCharacter->m_Weapon % NUM_WEAPONS;
|
||
if(m_pClient->m_GameSkin.m_aSpriteWeaponProjectiles[CurWeapon].IsValid())
|
||
{
|
||
Graphics()->TextureSet(m_pClient->m_GameSkin.m_aSpriteWeaponProjectiles[CurWeapon]);
|
||
if(AmmoOffsetY > 0)
|
||
{
|
||
Graphics()->RenderQuadContainerEx(m_HudQuadContainerIndex, m_aAmmoOffset[CurWeapon] + QuadOffsetSixup, minimum(pCharacter->m_AmmoCount, 10), 0, AmmoOffsetY);
|
||
}
|
||
else
|
||
{
|
||
Graphics()->RenderQuadContainer(m_HudQuadContainerIndex, m_aAmmoOffset[CurWeapon] + QuadOffsetSixup, minimum(pCharacter->m_AmmoCount, 10));
|
||
}
|
||
}
|
||
}
|
||
|
||
if(GameClient()->m_GameInfo.m_HudHealthArmor)
|
||
{
|
||
// health display
|
||
Graphics()->TextureSet(m_pClient->m_GameSkin.m_SpriteHealthFull);
|
||
Graphics()->RenderQuadContainer(m_HudQuadContainerIndex, m_HealthOffset + QuadOffsetSixup, minimum(pCharacter->m_Health, 10));
|
||
Graphics()->TextureSet(m_pClient->m_GameSkin.m_SpriteHealthEmpty);
|
||
Graphics()->RenderQuadContainer(m_HudQuadContainerIndex, m_EmptyHealthOffset + QuadOffsetSixup + minimum(pCharacter->m_Health, 10), 10 - minimum(pCharacter->m_Health, 10));
|
||
|
||
// armor display
|
||
Graphics()->TextureSet(m_pClient->m_GameSkin.m_SpriteArmorFull);
|
||
Graphics()->RenderQuadContainer(m_HudQuadContainerIndex, m_ArmorOffset + QuadOffsetSixup, minimum(pCharacter->m_Armor, 10));
|
||
Graphics()->TextureSet(m_pClient->m_GameSkin.m_SpriteArmorEmpty);
|
||
Graphics()->RenderQuadContainer(m_HudQuadContainerIndex, m_ArmorOffset + QuadOffsetSixup + minimum(pCharacter->m_Armor, 10), 10 - minimum(pCharacter->m_Armor, 10));
|
||
}
|
||
}
|
||
|
||
void CHud::PreparePlayerStateQuads()
|
||
{
|
||
float x = 5;
|
||
float y = 5 + 24;
|
||
IGraphics::CQuadItem Array[10];
|
||
|
||
// Quads for displaying the available and used jumps
|
||
for(int i = 0; i < 10; ++i)
|
||
Array[i] = IGraphics::CQuadItem(x + i * 12, y, 12, 12);
|
||
m_AirjumpOffset = Graphics()->QuadContainerAddQuads(m_HudQuadContainerIndex, Array, 10);
|
||
|
||
for(int i = 0; i < 10; ++i)
|
||
Array[i] = IGraphics::CQuadItem(x + i * 12, y, 12, 12);
|
||
m_AirjumpEmptyOffset = Graphics()->QuadContainerAddQuads(m_HudQuadContainerIndex, Array, 10);
|
||
|
||
// Quads for displaying weapons
|
||
float ScaleX, ScaleY;
|
||
const float HudWeaponScale = 0.25f;
|
||
RenderTools()->GetSpriteScale(g_pData->m_Weapons.m_aId[WEAPON_HAMMER].m_pSpriteBody, ScaleX, ScaleY);
|
||
m_WeaponHammerOffset = RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, g_pData->m_Weapons.m_aId[WEAPON_HAMMER].m_VisualSize * ScaleX * HudWeaponScale, g_pData->m_Weapons.m_aId[WEAPON_HAMMER].m_VisualSize * ScaleY * HudWeaponScale);
|
||
RenderTools()->GetSpriteScale(g_pData->m_Weapons.m_aId[WEAPON_GUN].m_pSpriteBody, ScaleX, ScaleY);
|
||
m_WeaponGunOffset = RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, g_pData->m_Weapons.m_aId[WEAPON_GUN].m_VisualSize * ScaleX * HudWeaponScale, g_pData->m_Weapons.m_aId[WEAPON_GUN].m_VisualSize * ScaleY * HudWeaponScale);
|
||
RenderTools()->GetSpriteScale(g_pData->m_Weapons.m_aId[WEAPON_SHOTGUN].m_pSpriteBody, ScaleX, ScaleY);
|
||
m_WeaponShotgunOffset = RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, g_pData->m_Weapons.m_aId[WEAPON_SHOTGUN].m_VisualSize * ScaleX * HudWeaponScale, g_pData->m_Weapons.m_aId[WEAPON_SHOTGUN].m_VisualSize * ScaleY * HudWeaponScale);
|
||
RenderTools()->GetSpriteScale(g_pData->m_Weapons.m_aId[WEAPON_GRENADE].m_pSpriteBody, ScaleX, ScaleY);
|
||
m_WeaponGrenadeOffset = RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, g_pData->m_Weapons.m_aId[WEAPON_GRENADE].m_VisualSize * ScaleX * HudWeaponScale, g_pData->m_Weapons.m_aId[WEAPON_GRENADE].m_VisualSize * ScaleY * HudWeaponScale);
|
||
RenderTools()->GetSpriteScale(g_pData->m_Weapons.m_aId[WEAPON_LASER].m_pSpriteBody, ScaleX, ScaleY);
|
||
m_WeaponLaserOffset = RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, g_pData->m_Weapons.m_aId[WEAPON_LASER].m_VisualSize * ScaleX * HudWeaponScale, g_pData->m_Weapons.m_aId[WEAPON_LASER].m_VisualSize * ScaleY * HudWeaponScale);
|
||
RenderTools()->GetSpriteScale(g_pData->m_Weapons.m_aId[WEAPON_NINJA].m_pSpriteBody, ScaleX, ScaleY);
|
||
m_WeaponNinjaOffset = RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, g_pData->m_Weapons.m_aId[WEAPON_NINJA].m_VisualSize * ScaleX * HudWeaponScale, g_pData->m_Weapons.m_aId[WEAPON_NINJA].m_VisualSize * ScaleY * HudWeaponScale);
|
||
|
||
// Quads for displaying capabilities
|
||
m_EndlessJumpOffset = RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, 0.f, 0.f, 12.f, 12.f);
|
||
m_EndlessHookOffset = RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, 0.f, 0.f, 12.f, 12.f);
|
||
m_JetpackOffset = RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, 0.f, 0.f, 12.f, 12.f);
|
||
m_TeleportGrenadeOffset = RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, 0.f, 0.f, 12.f, 12.f);
|
||
m_TeleportGunOffset = RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, 0.f, 0.f, 12.f, 12.f);
|
||
m_TeleportLaserOffset = RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, 0.f, 0.f, 12.f, 12.f);
|
||
|
||
// Quads for displaying prohibited capabilities
|
||
m_SoloOffset = RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, 0.f, 0.f, 12.f, 12.f);
|
||
m_CollisionDisabledOffset = RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, 0.f, 0.f, 12.f, 12.f);
|
||
m_HookHitDisabledOffset = RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, 0.f, 0.f, 12.f, 12.f);
|
||
m_HammerHitDisabledOffset = RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, 0.f, 0.f, 12.f, 12.f);
|
||
m_GunHitDisabledOffset = RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, 0.f, 0.f, 12.f, 12.f);
|
||
m_ShotgunHitDisabledOffset = RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, 0.f, 0.f, 12.f, 12.f);
|
||
m_GrenadeHitDisabledOffset = RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, 0.f, 0.f, 12.f, 12.f);
|
||
m_LaserHitDisabledOffset = RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, 0.f, 0.f, 12.f, 12.f);
|
||
|
||
// Quads for displaying freeze status
|
||
m_DeepFrozenOffset = RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, 0.f, 0.f, 12.f, 12.f);
|
||
m_LiveFrozenOffset = RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, 0.f, 0.f, 12.f, 12.f);
|
||
|
||
// Quads for displaying dummy actions
|
||
m_DummyHammerOffset = RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, 0.f, 0.f, 12.f, 12.f);
|
||
m_DummyCopyOffset = RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, 0.f, 0.f, 12.f, 12.f);
|
||
|
||
// Quad for displaying practice mode
|
||
m_PracticeModeOffset = RenderTools()->QuadContainerAddSprite(m_HudQuadContainerIndex, 0.f, 0.f, 12.f, 12.f);
|
||
}
|
||
|
||
void CHud::RenderPlayerState(const int ClientID)
|
||
{
|
||
Graphics()->SetColor(1.f, 1.f, 1.f, 1.f);
|
||
|
||
// pCharacter contains the predicted character for local players or the last snap for players who are spectated
|
||
CCharacterCore *pCharacter = &m_pClient->m_aClients[ClientID].m_Predicted;
|
||
CNetObj_Character *pPlayer = &m_pClient->m_aClients[ClientID].m_RenderCur;
|
||
int TotalJumpsToDisplay = 0, AvailableJumpsToDisplay = 0;
|
||
if(g_Config.m_ClShowhudJumpsIndicator)
|
||
{
|
||
if(m_pClient->m_Snap.m_aCharacters[ClientID].m_HasExtendedDisplayInfo)
|
||
{
|
||
bool Grounded = false;
|
||
if(Collision()->CheckPoint(pPlayer->m_X + CCharacterCore::PhysicalSize() / 2,
|
||
pPlayer->m_Y + CCharacterCore::PhysicalSize() / 2 + 5))
|
||
{
|
||
Grounded = true;
|
||
}
|
||
if(Collision()->CheckPoint(pPlayer->m_X - CCharacterCore::PhysicalSize() / 2,
|
||
pPlayer->m_Y + CCharacterCore::PhysicalSize() / 2 + 5))
|
||
{
|
||
Grounded = true;
|
||
}
|
||
|
||
int UsedJumps = pCharacter->m_JumpedTotal;
|
||
if(pCharacter->m_Jumps > 1)
|
||
{
|
||
UsedJumps += !Grounded;
|
||
}
|
||
else if(pCharacter->m_Jumps == 1)
|
||
{
|
||
// If the player has only one jump, each jump is the last one
|
||
UsedJumps = pPlayer->m_Jumped & 2;
|
||
}
|
||
else if(pCharacter->m_Jumps == -1)
|
||
{
|
||
// The player has only one ground jump
|
||
UsedJumps = !Grounded;
|
||
}
|
||
|
||
if(pCharacter->m_EndlessJump && UsedJumps >= abs(pCharacter->m_Jumps))
|
||
{
|
||
UsedJumps = abs(pCharacter->m_Jumps) - 1;
|
||
}
|
||
|
||
int UnusedJumps = abs(pCharacter->m_Jumps) - UsedJumps;
|
||
if(!(pPlayer->m_Jumped & 2) && UnusedJumps <= 0)
|
||
{
|
||
// In some edge cases when the player just got another number of jumps, UnusedJumps is not correct
|
||
UnusedJumps = 1;
|
||
}
|
||
TotalJumpsToDisplay = maximum(minimum(abs(pCharacter->m_Jumps), 10), 0);
|
||
AvailableJumpsToDisplay = maximum(minimum(UnusedJumps, TotalJumpsToDisplay), 0);
|
||
}
|
||
else
|
||
{
|
||
TotalJumpsToDisplay = AvailableJumpsToDisplay = abs(m_pClient->m_Snap.m_aCharacters[ClientID].m_ExtendedData.m_Jumps);
|
||
}
|
||
|
||
// render available and used jumps
|
||
int JumpsOffsetY = ((GameClient()->m_GameInfo.m_HudHealthArmor && g_Config.m_ClShowhudHealthAmmo ? 24 : 0) +
|
||
(GameClient()->m_GameInfo.m_HudAmmo && g_Config.m_ClShowhudHealthAmmo ? 12 : 0));
|
||
if(JumpsOffsetY > 0)
|
||
{
|
||
Graphics()->TextureSet(m_pClient->m_HudSkin.m_SpriteHudAirjump);
|
||
Graphics()->RenderQuadContainerEx(m_HudQuadContainerIndex, m_AirjumpOffset, AvailableJumpsToDisplay, 0, JumpsOffsetY);
|
||
Graphics()->TextureSet(m_pClient->m_HudSkin.m_SpriteHudAirjumpEmpty);
|
||
Graphics()->RenderQuadContainerEx(m_HudQuadContainerIndex, m_AirjumpEmptyOffset + AvailableJumpsToDisplay, TotalJumpsToDisplay - AvailableJumpsToDisplay, 0, JumpsOffsetY);
|
||
}
|
||
else
|
||
{
|
||
Graphics()->TextureSet(m_pClient->m_HudSkin.m_SpriteHudAirjump);
|
||
Graphics()->RenderQuadContainer(m_HudQuadContainerIndex, m_AirjumpOffset, AvailableJumpsToDisplay);
|
||
Graphics()->TextureSet(m_pClient->m_HudSkin.m_SpriteHudAirjumpEmpty);
|
||
Graphics()->RenderQuadContainer(m_HudQuadContainerIndex, m_AirjumpEmptyOffset + AvailableJumpsToDisplay, TotalJumpsToDisplay - AvailableJumpsToDisplay);
|
||
}
|
||
}
|
||
|
||
float x = 5 + 12;
|
||
float y = (5 + 12 + (GameClient()->m_GameInfo.m_HudHealthArmor && g_Config.m_ClShowhudHealthAmmo ? 24 : 0) +
|
||
(GameClient()->m_GameInfo.m_HudAmmo && g_Config.m_ClShowhudHealthAmmo ? 12 : 0));
|
||
|
||
// render weapons
|
||
if(pCharacter->m_aWeapons[WEAPON_HAMMER].m_Got)
|
||
{
|
||
if(pPlayer->m_Weapon != WEAPON_HAMMER)
|
||
{
|
||
Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.4f);
|
||
}
|
||
x -= 3;
|
||
Graphics()->QuadsSetRotation(pi * 7 / 4);
|
||
Graphics()->TextureSet(m_pClient->m_GameSkin.m_SpritePickupHammer);
|
||
Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_WeaponHammerOffset, x, y);
|
||
Graphics()->QuadsSetRotation(0);
|
||
Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f);
|
||
x += 16;
|
||
}
|
||
if(pCharacter->m_aWeapons[WEAPON_GUN].m_Got)
|
||
{
|
||
if(pPlayer->m_Weapon != WEAPON_GUN)
|
||
{
|
||
Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.4f);
|
||
}
|
||
Graphics()->QuadsSetRotation(pi * 7 / 4);
|
||
Graphics()->TextureSet(m_pClient->m_GameSkin.m_SpritePickupGun);
|
||
Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_WeaponGunOffset, x, y);
|
||
Graphics()->QuadsSetRotation(0);
|
||
Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f);
|
||
x += 12;
|
||
}
|
||
if(pCharacter->m_aWeapons[WEAPON_SHOTGUN].m_Got)
|
||
{
|
||
if(pPlayer->m_Weapon != WEAPON_SHOTGUN)
|
||
{
|
||
Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.4f);
|
||
}
|
||
Graphics()->QuadsSetRotation(pi * 7 / 4);
|
||
Graphics()->TextureSet(m_pClient->m_GameSkin.m_SpritePickupShotgun);
|
||
Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_WeaponShotgunOffset, x, y);
|
||
Graphics()->QuadsSetRotation(0);
|
||
Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f);
|
||
x += 12;
|
||
}
|
||
if(pCharacter->m_aWeapons[WEAPON_GRENADE].m_Got)
|
||
{
|
||
if(pPlayer->m_Weapon != WEAPON_GRENADE)
|
||
{
|
||
Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.4f);
|
||
}
|
||
Graphics()->QuadsSetRotation(pi * 7 / 4);
|
||
Graphics()->TextureSet(m_pClient->m_GameSkin.m_SpritePickupGrenade);
|
||
Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_WeaponGrenadeOffset, x, y);
|
||
Graphics()->QuadsSetRotation(0);
|
||
Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f);
|
||
x += 12;
|
||
}
|
||
if(pCharacter->m_aWeapons[WEAPON_LASER].m_Got)
|
||
{
|
||
if(pPlayer->m_Weapon != WEAPON_LASER)
|
||
{
|
||
Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.4f);
|
||
}
|
||
Graphics()->QuadsSetRotation(pi * 7 / 4);
|
||
Graphics()->TextureSet(m_pClient->m_GameSkin.m_SpritePickupLaser);
|
||
Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_WeaponLaserOffset, x, y);
|
||
Graphics()->QuadsSetRotation(0);
|
||
Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f);
|
||
x += 12;
|
||
}
|
||
if(pCharacter->m_aWeapons[WEAPON_NINJA].m_Got)
|
||
{
|
||
if(pPlayer->m_Weapon != WEAPON_NINJA)
|
||
{
|
||
Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.4f);
|
||
}
|
||
Graphics()->QuadsSetRotation(pi * 7 / 4);
|
||
Graphics()->TextureSet(m_pClient->m_GameSkin.m_SpritePickupNinja);
|
||
Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_WeaponNinjaOffset, x, y);
|
||
Graphics()->QuadsSetRotation(0);
|
||
Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f);
|
||
|
||
const int Max = g_pData->m_Weapons.m_Ninja.m_Duration * Client()->GameTickSpeed() / 1000;
|
||
float NinjaProgress = clamp(pCharacter->m_Ninja.m_ActivationTick + g_pData->m_Weapons.m_Ninja.m_Duration * Client()->GameTickSpeed() / 1000 - Client()->GameTick(g_Config.m_ClDummy), 0, Max) / (float)Max;
|
||
if(NinjaProgress > 0.0f && m_pClient->m_Snap.m_aCharacters[ClientID].m_HasExtendedDisplayInfo)
|
||
{
|
||
x += 12;
|
||
RenderNinjaBarPos(x, y - 12, 6.f, 24.f, NinjaProgress);
|
||
}
|
||
}
|
||
|
||
// render capabilities
|
||
x = 5;
|
||
y += 12;
|
||
if(TotalJumpsToDisplay > 0)
|
||
{
|
||
y += 12;
|
||
}
|
||
bool HasCapabilities = false;
|
||
if(pCharacter->m_EndlessJump)
|
||
{
|
||
HasCapabilities = true;
|
||
Graphics()->TextureSet(m_pClient->m_HudSkin.m_SpriteHudEndlessJump);
|
||
Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_EndlessJumpOffset, x, y);
|
||
x += 12;
|
||
}
|
||
if(pCharacter->m_EndlessHook)
|
||
{
|
||
HasCapabilities = true;
|
||
Graphics()->TextureSet(m_pClient->m_HudSkin.m_SpriteHudEndlessHook);
|
||
Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_EndlessHookOffset, x, y);
|
||
x += 12;
|
||
}
|
||
if(pCharacter->m_Jetpack)
|
||
{
|
||
HasCapabilities = true;
|
||
Graphics()->TextureSet(m_pClient->m_HudSkin.m_SpriteHudJetpack);
|
||
Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_JetpackOffset, x, y);
|
||
x += 12;
|
||
}
|
||
if(pCharacter->m_HasTelegunGun && pCharacter->m_aWeapons[WEAPON_GUN].m_Got)
|
||
{
|
||
HasCapabilities = true;
|
||
Graphics()->TextureSet(m_pClient->m_HudSkin.m_SpriteHudTeleportGun);
|
||
Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_TeleportGunOffset, x, y);
|
||
x += 12;
|
||
}
|
||
if(pCharacter->m_HasTelegunGrenade && pCharacter->m_aWeapons[WEAPON_GRENADE].m_Got)
|
||
{
|
||
HasCapabilities = true;
|
||
Graphics()->TextureSet(m_pClient->m_HudSkin.m_SpriteHudTeleportGrenade);
|
||
Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_TeleportGrenadeOffset, x, y);
|
||
x += 12;
|
||
}
|
||
if(pCharacter->m_HasTelegunLaser && pCharacter->m_aWeapons[WEAPON_LASER].m_Got)
|
||
{
|
||
HasCapabilities = true;
|
||
Graphics()->TextureSet(m_pClient->m_HudSkin.m_SpriteHudTeleportLaser);
|
||
Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_TeleportLaserOffset, x, y);
|
||
}
|
||
|
||
// render prohibited capabilities
|
||
x = 5;
|
||
if(HasCapabilities)
|
||
{
|
||
y += 12;
|
||
}
|
||
bool HasProhibitedCapabilities = false;
|
||
if(pCharacter->m_Solo)
|
||
{
|
||
HasProhibitedCapabilities = true;
|
||
Graphics()->TextureSet(m_pClient->m_HudSkin.m_SpriteHudSolo);
|
||
Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_SoloOffset, x, y);
|
||
x += 12;
|
||
}
|
||
if(pCharacter->m_CollisionDisabled)
|
||
{
|
||
HasProhibitedCapabilities = true;
|
||
Graphics()->TextureSet(m_pClient->m_HudSkin.m_SpriteHudCollisionDisabled);
|
||
Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_CollisionDisabledOffset, x, y);
|
||
x += 12;
|
||
}
|
||
if(pCharacter->m_HookHitDisabled)
|
||
{
|
||
HasProhibitedCapabilities = true;
|
||
Graphics()->TextureSet(m_pClient->m_HudSkin.m_SpriteHudHookHitDisabled);
|
||
Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_HookHitDisabledOffset, x, y);
|
||
x += 12;
|
||
}
|
||
if(pCharacter->m_HammerHitDisabled)
|
||
{
|
||
HasProhibitedCapabilities = true;
|
||
Graphics()->TextureSet(m_pClient->m_HudSkin.m_SpriteHudHammerHitDisabled);
|
||
Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_HammerHitDisabledOffset, x, y);
|
||
x += 12;
|
||
}
|
||
if((pCharacter->m_GrenadeHitDisabled && pCharacter->m_HasTelegunGun && pCharacter->m_aWeapons[WEAPON_GUN].m_Got))
|
||
{
|
||
HasProhibitedCapabilities = true;
|
||
Graphics()->TextureSet(m_pClient->m_HudSkin.m_SpriteHudGunHitDisabled);
|
||
Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_LaserHitDisabledOffset, x, y);
|
||
x += 12;
|
||
}
|
||
if((pCharacter->m_ShotgunHitDisabled && pCharacter->m_aWeapons[WEAPON_SHOTGUN].m_Got))
|
||
{
|
||
HasProhibitedCapabilities = true;
|
||
Graphics()->TextureSet(m_pClient->m_HudSkin.m_SpriteHudShotgunHitDisabled);
|
||
Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_ShotgunHitDisabledOffset, x, y);
|
||
x += 12;
|
||
}
|
||
if((pCharacter->m_GrenadeHitDisabled && pCharacter->m_aWeapons[WEAPON_GRENADE].m_Got))
|
||
{
|
||
HasProhibitedCapabilities = true;
|
||
Graphics()->TextureSet(m_pClient->m_HudSkin.m_SpriteHudGrenadeHitDisabled);
|
||
Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_GrenadeHitDisabledOffset, x, y);
|
||
x += 12;
|
||
}
|
||
if((pCharacter->m_LaserHitDisabled && pCharacter->m_aWeapons[WEAPON_LASER].m_Got))
|
||
{
|
||
HasProhibitedCapabilities = true;
|
||
Graphics()->TextureSet(m_pClient->m_HudSkin.m_SpriteHudLaserHitDisabled);
|
||
Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_LaserHitDisabledOffset, x, y);
|
||
}
|
||
|
||
// render dummy actions and freeze state
|
||
x = 5;
|
||
if(HasProhibitedCapabilities)
|
||
{
|
||
y += 12;
|
||
}
|
||
if(m_pClient->m_Snap.m_aCharacters[ClientID].m_HasExtendedDisplayInfo && m_pClient->m_Snap.m_aCharacters[ClientID].m_ExtendedData.m_Flags & CHARACTERFLAG_PRACTICE_MODE)
|
||
{
|
||
Graphics()->TextureSet(m_pClient->m_HudSkin.m_SpriteHudPracticeMode);
|
||
Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_PracticeModeOffset, x, y);
|
||
x += 12;
|
||
}
|
||
if(pCharacter->m_DeepFrozen)
|
||
{
|
||
Graphics()->TextureSet(m_pClient->m_HudSkin.m_SpriteHudDeepFrozen);
|
||
Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_DeepFrozenOffset, x, y);
|
||
x += 12;
|
||
}
|
||
if(pCharacter->m_LiveFrozen)
|
||
{
|
||
Graphics()->TextureSet(m_pClient->m_HudSkin.m_SpriteHudLiveFrozen);
|
||
Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_LiveFrozenOffset, x, y);
|
||
}
|
||
}
|
||
|
||
void CHud::RenderNinjaBarPos(const float x, float y, const float width, const float height, float Progress, const float Alpha)
|
||
{
|
||
Progress = clamp(Progress, 0.0f, 1.0f);
|
||
|
||
// what percentage of the end pieces is used for the progress indicator and how much is the rest
|
||
// half of the ends are used for the progress display
|
||
const float RestPct = 0.5f;
|
||
const float ProgPct = 0.5f;
|
||
|
||
const float EndHeight = width; // to keep the correct scale - the width of the sprite is as long as the height
|
||
const float BarWidth = width;
|
||
const float WholeBarHeight = height;
|
||
const float MiddleBarHeight = WholeBarHeight - (EndHeight * 2.0f);
|
||
const float EndProgressHeight = EndHeight * ProgPct;
|
||
const float EndRestHeight = EndHeight * RestPct;
|
||
const float ProgressBarHeight = WholeBarHeight - (EndProgressHeight * 2.0f);
|
||
const float EndProgressProportion = EndProgressHeight / ProgressBarHeight;
|
||
const float MiddleProgressProportion = MiddleBarHeight / ProgressBarHeight;
|
||
|
||
// beginning piece
|
||
float BeginningPieceProgress = 1;
|
||
if(Progress <= 1)
|
||
{
|
||
if(Progress <= (EndProgressProportion + MiddleProgressProportion))
|
||
{
|
||
BeginningPieceProgress = 0;
|
||
}
|
||
else
|
||
{
|
||
BeginningPieceProgress = (Progress - EndProgressProportion - MiddleProgressProportion) / EndProgressProportion;
|
||
}
|
||
}
|
||
// empty
|
||
Graphics()->WrapClamp();
|
||
Graphics()->TextureSet(m_pClient->m_HudSkin.m_SpriteHudNinjaBarEmptyRight);
|
||
Graphics()->QuadsBegin();
|
||
Graphics()->SetColor(1.f, 1.f, 1.f, Alpha);
|
||
// Subset: btm_r, top_r, top_m, btm_m | it is mirrored on the horizontal axe and rotated 90 degrees counterclockwise
|
||
Graphics()->QuadsSetSubsetFree(1, 1, 1, 0, ProgPct - ProgPct * (1.0f - BeginningPieceProgress), 0, ProgPct - ProgPct * (1.0f - BeginningPieceProgress), 1);
|
||
IGraphics::CQuadItem QuadEmptyBeginning(x, y, BarWidth, EndRestHeight + EndProgressHeight * (1.0f - BeginningPieceProgress));
|
||
Graphics()->QuadsDrawTL(&QuadEmptyBeginning, 1);
|
||
Graphics()->QuadsEnd();
|
||
// full
|
||
if(BeginningPieceProgress > 0.0f)
|
||
{
|
||
Graphics()->TextureSet(m_pClient->m_HudSkin.m_SpriteHudNinjaBarFullLeft);
|
||
Graphics()->QuadsBegin();
|
||
Graphics()->SetColor(1.f, 1.f, 1.f, Alpha);
|
||
// Subset: btm_m, top_m, top_r, btm_r | it is rotated 90 degrees clockwise
|
||
Graphics()->QuadsSetSubsetFree(RestPct + ProgPct * (1.0f - BeginningPieceProgress), 1, RestPct + ProgPct * (1.0f - BeginningPieceProgress), 0, 1, 0, 1, 1);
|
||
IGraphics::CQuadItem QuadFullBeginning(x, y + (EndRestHeight + EndProgressHeight * (1.0f - BeginningPieceProgress)), BarWidth, EndProgressHeight * BeginningPieceProgress);
|
||
Graphics()->QuadsDrawTL(&QuadFullBeginning, 1);
|
||
Graphics()->QuadsEnd();
|
||
}
|
||
|
||
// middle piece
|
||
y += EndHeight;
|
||
|
||
float MiddlePieceProgress = 1;
|
||
if(Progress <= EndProgressProportion + MiddleProgressProportion)
|
||
{
|
||
if(Progress <= EndProgressProportion)
|
||
{
|
||
MiddlePieceProgress = 0;
|
||
}
|
||
else
|
||
{
|
||
MiddlePieceProgress = (Progress - EndProgressProportion) / MiddleProgressProportion;
|
||
}
|
||
}
|
||
|
||
const float FullMiddleBarHeight = MiddleBarHeight * MiddlePieceProgress;
|
||
const float EmptyMiddleBarHeight = MiddleBarHeight - FullMiddleBarHeight;
|
||
|
||
// empty ninja bar
|
||
if(EmptyMiddleBarHeight > 0.0f)
|
||
{
|
||
Graphics()->TextureSet(m_pClient->m_HudSkin.m_SpriteHudNinjaBarEmpty);
|
||
Graphics()->QuadsBegin();
|
||
Graphics()->SetColor(1.f, 1.f, 1.f, Alpha);
|
||
// select the middle portion of the sprite so we don't get edge bleeding
|
||
if(EmptyMiddleBarHeight <= EndHeight)
|
||
{
|
||
// prevent pixel puree, select only a small slice
|
||
// Subset: btm_r, top_r, top_m, btm_m | it is mirrored on the horizontal axe and rotated 90 degrees counterclockwise
|
||
Graphics()->QuadsSetSubsetFree(1, 1, 1, 0, 1.0f - (EmptyMiddleBarHeight / EndHeight), 0, 1.0f - (EmptyMiddleBarHeight / EndHeight), 1);
|
||
}
|
||
else
|
||
{
|
||
// Subset: btm_r, top_r, top_l, btm_l | it is mirrored on the horizontal axe and rotated 90 degrees counterclockwise
|
||
Graphics()->QuadsSetSubsetFree(1, 1, 1, 0, 0, 0, 0, 1);
|
||
}
|
||
IGraphics::CQuadItem QuadEmpty(x, y, BarWidth, EmptyMiddleBarHeight);
|
||
Graphics()->QuadsDrawTL(&QuadEmpty, 1);
|
||
Graphics()->QuadsEnd();
|
||
}
|
||
|
||
// full ninja bar
|
||
Graphics()->TextureSet(m_pClient->m_HudSkin.m_SpriteHudNinjaBarFull);
|
||
Graphics()->QuadsBegin();
|
||
Graphics()->SetColor(1.f, 1.f, 1.f, Alpha);
|
||
// select the middle portion of the sprite so we don't get edge bleeding
|
||
if(FullMiddleBarHeight <= EndHeight)
|
||
{
|
||
// prevent pixel puree, select only a small slice
|
||
// Subset: btm_m, top_m, top_r, btm_r | it is rotated 90 degrees clockwise
|
||
Graphics()->QuadsSetSubsetFree(1.0f - (FullMiddleBarHeight / EndHeight), 1, 1.0f - (FullMiddleBarHeight / EndHeight), 0, 1, 0, 1, 1);
|
||
}
|
||
else
|
||
{
|
||
// Subset: btm_l, top_l, top_r, btm_r | it is rotated 90 degrees clockwise
|
||
Graphics()->QuadsSetSubsetFree(0, 1, 0, 0, 1, 0, 1, 1);
|
||
}
|
||
IGraphics::CQuadItem QuadFull(x, y + EmptyMiddleBarHeight, BarWidth, FullMiddleBarHeight);
|
||
Graphics()->QuadsDrawTL(&QuadFull, 1);
|
||
Graphics()->QuadsEnd();
|
||
|
||
// ending piece
|
||
y += MiddleBarHeight;
|
||
float EndingPieceProgress = 1;
|
||
if(Progress <= EndProgressProportion)
|
||
{
|
||
EndingPieceProgress = Progress / EndProgressProportion;
|
||
}
|
||
// empty
|
||
if(EndingPieceProgress < 1.0f)
|
||
{
|
||
Graphics()->TextureSet(m_pClient->m_HudSkin.m_SpriteHudNinjaBarEmptyRight);
|
||
Graphics()->QuadsBegin();
|
||
Graphics()->SetColor(1.f, 1.f, 1.f, Alpha);
|
||
// Subset: btm_l, top_l, top_m, btm_m | it is rotated 90 degrees clockwise
|
||
Graphics()->QuadsSetSubsetFree(0, 1, 0, 0, ProgPct - ProgPct * EndingPieceProgress, 0, ProgPct - ProgPct * EndingPieceProgress, 1);
|
||
IGraphics::CQuadItem QuadEmptyEnding(x, y, BarWidth, EndProgressHeight * (1.0f - EndingPieceProgress));
|
||
Graphics()->QuadsDrawTL(&QuadEmptyEnding, 1);
|
||
Graphics()->QuadsEnd();
|
||
}
|
||
// full
|
||
Graphics()->TextureSet(m_pClient->m_HudSkin.m_SpriteHudNinjaBarFullLeft);
|
||
Graphics()->QuadsBegin();
|
||
Graphics()->SetColor(1.f, 1.f, 1.f, Alpha);
|
||
// Subset: btm_m, top_m, top_l, btm_l | it is mirrored on the horizontal axe and rotated 90 degrees counterclockwise
|
||
Graphics()->QuadsSetSubsetFree(RestPct + ProgPct * EndingPieceProgress, 1, RestPct + ProgPct * EndingPieceProgress, 0, 0, 0, 0, 1);
|
||
IGraphics::CQuadItem QuadFullEnding(x, y + (EndProgressHeight * (1.0f - EndingPieceProgress)), BarWidth, EndRestHeight + EndProgressHeight * EndingPieceProgress);
|
||
Graphics()->QuadsDrawTL(&QuadFullEnding, 1);
|
||
Graphics()->QuadsEnd();
|
||
|
||
Graphics()->QuadsSetSubset(0, 0, 1, 1);
|
||
Graphics()->SetColor(1.f, 1.f, 1.f, 1.f);
|
||
Graphics()->WrapNormal();
|
||
}
|
||
|
||
void CHud::RenderDummyActions()
|
||
{
|
||
if(!g_Config.m_ClShowhudDummyActions || (m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_GAMEOVER) || !Client()->DummyConnected())
|
||
{
|
||
return;
|
||
}
|
||
// render small dummy actions hud
|
||
const float BoxHeight = 29.0f;
|
||
const float BoxWidth = 16.0f;
|
||
|
||
float StartX = m_Width - BoxWidth;
|
||
float StartY = 285.0f - BoxHeight - 4; // 4 units distance to the next display;
|
||
if(g_Config.m_ClShowhudPlayerPosition || g_Config.m_ClShowhudPlayerSpeed || g_Config.m_ClShowhudPlayerAngle)
|
||
{
|
||
StartY -= 4;
|
||
}
|
||
StartY -= GetMovementInformationBoxHeight();
|
||
|
||
if(g_Config.m_ClShowhudScore)
|
||
{
|
||
StartY -= 56;
|
||
}
|
||
|
||
Graphics()->DrawRect(StartX, StartY, BoxWidth, BoxHeight, ColorRGBA(0.0f, 0.0f, 0.0f, 0.4f), IGraphics::CORNER_L, 5.0f);
|
||
|
||
float y = StartY + 2;
|
||
float x = StartX + 2;
|
||
Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.4f);
|
||
if(g_Config.m_ClDummyHammer)
|
||
{
|
||
Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f);
|
||
}
|
||
Graphics()->TextureSet(m_pClient->m_HudSkin.m_SpriteHudDummyHammer);
|
||
Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_DummyHammerOffset, x, y);
|
||
y += 13;
|
||
Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.4f);
|
||
if(g_Config.m_ClDummyCopyMoves)
|
||
{
|
||
Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f);
|
||
}
|
||
Graphics()->TextureSet(m_pClient->m_HudSkin.m_SpriteHudDummyCopy);
|
||
Graphics()->RenderQuadContainerAsSprite(m_HudQuadContainerIndex, m_DummyCopyOffset, x, y);
|
||
}
|
||
|
||
inline int CHud::GetDigitsIndex(int Value, int Max)
|
||
{
|
||
if(Value < 0)
|
||
{
|
||
Value *= -1;
|
||
}
|
||
int DigitsIndex = (int)log10((Value ? Value : 1));
|
||
if(DigitsIndex > Max)
|
||
{
|
||
DigitsIndex = Max;
|
||
}
|
||
if(DigitsIndex < 0)
|
||
{
|
||
DigitsIndex = 0;
|
||
}
|
||
return DigitsIndex;
|
||
}
|
||
|
||
inline float CHud::GetMovementInformationBoxHeight()
|
||
{
|
||
float BoxHeight = 3 * MOVEMENT_INFORMATION_LINE_HEIGHT * (g_Config.m_ClShowhudPlayerPosition + g_Config.m_ClShowhudPlayerSpeed) + 2 * MOVEMENT_INFORMATION_LINE_HEIGHT * g_Config.m_ClShowhudPlayerAngle;
|
||
if(g_Config.m_ClShowhudPlayerPosition || g_Config.m_ClShowhudPlayerSpeed || g_Config.m_ClShowhudPlayerAngle)
|
||
{
|
||
BoxHeight += 2;
|
||
}
|
||
return BoxHeight;
|
||
}
|
||
|
||
void CHud::RenderMovementInformation(const int ClientID)
|
||
{
|
||
// Draw the infomations depending on settings: Position, speed and target angle
|
||
// This display is only to present the available information from the last snapshot, not to interpolate or predict
|
||
if(!g_Config.m_ClShowhudPlayerPosition && !g_Config.m_ClShowhudPlayerSpeed && !g_Config.m_ClShowhudPlayerAngle)
|
||
{
|
||
return;
|
||
}
|
||
const float LineSpacer = 1.0f; // above and below each entry
|
||
const float Fontsize = 6.0f;
|
||
|
||
float BoxHeight = GetMovementInformationBoxHeight();
|
||
const float BoxWidth = 62.0f;
|
||
|
||
float StartX = m_Width - BoxWidth;
|
||
float StartY = 285.0f - BoxHeight - 4; // 4 units distance to the next display;
|
||
if(g_Config.m_ClShowhudScore)
|
||
{
|
||
StartY -= 56;
|
||
}
|
||
|
||
Graphics()->DrawRect(StartX, StartY, BoxWidth, BoxHeight, ColorRGBA(0.0f, 0.0f, 0.0f, 0.4f), IGraphics::CORNER_L, 5.0f);
|
||
|
||
CNetObj_Character *pCharacter = &m_pClient->m_Snap.m_aCharacters[ClientID].m_Cur;
|
||
const float TicksPerSecond = 50.0f;
|
||
|
||
// To make the player position relative to blocks we need to divide by the block size
|
||
float PosX = pCharacter->m_X / 32.0f;
|
||
float PosY = pCharacter->m_Y / 32.0f;
|
||
|
||
float VelspeedX = pCharacter->m_VelX / 256.0f * TicksPerSecond;
|
||
if(pCharacter->m_VelX >= -1 && pCharacter->m_VelX <= 1)
|
||
{
|
||
VelspeedX = 0;
|
||
}
|
||
float VelspeedY = pCharacter->m_VelY / 256.0f * TicksPerSecond;
|
||
if(pCharacter->m_VelY >= -128 && pCharacter->m_VelY <= 128)
|
||
{
|
||
VelspeedY = 0;
|
||
}
|
||
// We show the speed in Blocks per Second (Bps) and therefore have to divide by the block size
|
||
float DisplaySpeedX = VelspeedX / 32;
|
||
float VelspeedLength = length(vec2(pCharacter->m_VelX / 256.0f, pCharacter->m_VelY / 256.0f)) * TicksPerSecond;
|
||
// Todo: Use Velramp tuning of each individual player
|
||
// Since these tuning parameters are almost never changed, the default values are sufficient in most cases
|
||
float Ramp = VelocityRamp(VelspeedLength, m_pClient->m_aTuning[g_Config.m_ClDummy].m_VelrampStart, m_pClient->m_aTuning[g_Config.m_ClDummy].m_VelrampRange, m_pClient->m_aTuning[g_Config.m_ClDummy].m_VelrampCurvature);
|
||
DisplaySpeedX *= Ramp;
|
||
float DisplaySpeedY = VelspeedY / 32;
|
||
|
||
float Angle = 0.0f;
|
||
if(m_pClient->m_Snap.m_aCharacters[ClientID].m_HasExtendedDisplayInfo)
|
||
{
|
||
// On DDNet servers the more accurate angle is displayed, calculated from the target coordinates
|
||
CNetObj_DDNetCharacter *pExtendedData = &m_pClient->m_Snap.m_aCharacters[ClientID].m_ExtendedData;
|
||
Angle = atan2f(pExtendedData->m_TargetY, pExtendedData->m_TargetX);
|
||
}
|
||
else
|
||
{
|
||
Angle = pCharacter->m_Angle / 256.0f;
|
||
}
|
||
if(Angle < 0)
|
||
{
|
||
Angle += 2.0f * pi;
|
||
}
|
||
float DisplayAngle = Angle * 180.0f / pi;
|
||
|
||
char aBuf[128];
|
||
float w;
|
||
|
||
float y = StartY + LineSpacer * 2;
|
||
float xl = StartX + 2;
|
||
float xr = m_Width - 2;
|
||
int DigitsIndex = 0;
|
||
|
||
static float s_TextWidth0 = TextRender()->TextWidth(0, Fontsize, "0.00", -1, -1.0f);
|
||
static float s_TextWidth00 = TextRender()->TextWidth(0, Fontsize, "00.00", -1, -1.0f);
|
||
static float s_TextWidth000 = TextRender()->TextWidth(0, Fontsize, "000.00", -1, -1.0f);
|
||
static float s_TextWidth0000 = TextRender()->TextWidth(0, Fontsize, "0000.00", -1, -1.0f);
|
||
static float s_TextWidth00000 = TextRender()->TextWidth(0, Fontsize, "00000.00", -1, -1.0f);
|
||
static float s_TextWidth000000 = TextRender()->TextWidth(0, Fontsize, "000000.00", -1, -1.0f);
|
||
static float s_aTextWidth[6] = {s_TextWidth0, s_TextWidth00, s_TextWidth000, s_TextWidth0000, s_TextWidth00000, s_TextWidth000000};
|
||
static float s_TextWidthMinus0 = TextRender()->TextWidth(0, Fontsize, "-0.00", -1, -1.0f);
|
||
static float s_TextWidthMinus00 = TextRender()->TextWidth(0, Fontsize, "-00.00", -1, -1.0f);
|
||
static float s_TextWidthMinus000 = TextRender()->TextWidth(0, Fontsize, "-000.00", -1, -1.0f);
|
||
static float s_TextWidthMinus0000 = TextRender()->TextWidth(0, Fontsize, "-0000.00", -1, -1.0f);
|
||
static float s_TextWidthMinus00000 = TextRender()->TextWidth(0, Fontsize, "-00000.00", -1, -1.0f);
|
||
static float s_TextWidthMinus000000 = TextRender()->TextWidth(0, Fontsize, "-000000.00", -1, -1.0f);
|
||
static float s_aTextWidthMinus[6] = {s_TextWidthMinus0, s_TextWidthMinus00, s_TextWidthMinus000, s_TextWidthMinus0000, s_TextWidthMinus00000, s_TextWidthMinus000000};
|
||
|
||
if(g_Config.m_ClShowhudPlayerPosition)
|
||
{
|
||
TextRender()->Text(0, xl, y, Fontsize, Localize("Position:"), -1.0f);
|
||
y += MOVEMENT_INFORMATION_LINE_HEIGHT;
|
||
|
||
TextRender()->Text(0, xl, y, Fontsize, "X:", -1.0f);
|
||
str_format(aBuf, sizeof(aBuf), "%.2f", PosX);
|
||
DigitsIndex = GetDigitsIndex(PosX, 5);
|
||
w = (PosX < 0) ? s_aTextWidthMinus[DigitsIndex] : s_aTextWidth[DigitsIndex];
|
||
TextRender()->Text(0, xr - w, y, Fontsize, aBuf, -1.0f);
|
||
y += MOVEMENT_INFORMATION_LINE_HEIGHT;
|
||
|
||
TextRender()->Text(0, xl, y, Fontsize, "Y:", -1.0f);
|
||
str_format(aBuf, sizeof(aBuf), "%.2f", PosY);
|
||
DigitsIndex = GetDigitsIndex(PosY, 5);
|
||
w = (PosY < 0) ? s_aTextWidthMinus[DigitsIndex] : s_aTextWidth[DigitsIndex];
|
||
TextRender()->Text(0, xr - w, y, Fontsize, aBuf, -1.0f);
|
||
y += MOVEMENT_INFORMATION_LINE_HEIGHT;
|
||
}
|
||
|
||
if(g_Config.m_ClShowhudPlayerSpeed)
|
||
{
|
||
TextRender()->Text(0, xl, y, Fontsize, Localize("Speed:"), -1.0f);
|
||
y += MOVEMENT_INFORMATION_LINE_HEIGHT;
|
||
|
||
TextRender()->Text(0, xl, y, Fontsize, "X:", -1.0f);
|
||
str_format(aBuf, sizeof(aBuf), "%.2f", DisplaySpeedX);
|
||
DigitsIndex = GetDigitsIndex(DisplaySpeedX, 5);
|
||
w = (DisplaySpeedX < 0) ? s_aTextWidthMinus[DigitsIndex] : s_aTextWidth[DigitsIndex];
|
||
TextRender()->Text(0, xr - w, y, Fontsize, aBuf, -1.0f);
|
||
y += MOVEMENT_INFORMATION_LINE_HEIGHT;
|
||
|
||
TextRender()->Text(0, xl, y, Fontsize, "Y:", -1.0f);
|
||
str_format(aBuf, sizeof(aBuf), "%.2f", DisplaySpeedY);
|
||
DigitsIndex = GetDigitsIndex(DisplaySpeedY, 5);
|
||
w = (DisplaySpeedY < 0) ? s_aTextWidthMinus[DigitsIndex] : s_aTextWidth[DigitsIndex];
|
||
TextRender()->Text(0, xr - w, y, Fontsize, aBuf, -1.0f);
|
||
y += MOVEMENT_INFORMATION_LINE_HEIGHT;
|
||
}
|
||
|
||
if(g_Config.m_ClShowhudPlayerAngle)
|
||
{
|
||
TextRender()->Text(0, xl, y, Fontsize, Localize("Angle:"), -1.0f);
|
||
y += MOVEMENT_INFORMATION_LINE_HEIGHT;
|
||
str_format(aBuf, sizeof(aBuf), "%.2f", DisplayAngle);
|
||
DigitsIndex = GetDigitsIndex(DisplayAngle, 5);
|
||
w = (DisplayAngle < 0) ? s_aTextWidthMinus[DigitsIndex] : s_aTextWidth[DigitsIndex];
|
||
TextRender()->Text(0, xr - w, y, Fontsize, aBuf, -1.0f);
|
||
}
|
||
}
|
||
|
||
void CHud::RenderSpectatorHud()
|
||
{
|
||
// draw the box
|
||
Graphics()->DrawRect(m_Width - 180.0f, m_Height - 15.0f, 180.0f, 15.0f, ColorRGBA(0.0f, 0.0f, 0.0f, 0.4f), IGraphics::CORNER_TL, 5.0f);
|
||
|
||
// draw the text
|
||
char aBuf[128];
|
||
str_format(aBuf, sizeof(aBuf), "%s: %s", Localize("Spectate"), m_pClient->m_Snap.m_SpecInfo.m_SpectatorID != SPEC_FREEVIEW ? m_pClient->m_aClients[m_pClient->m_Snap.m_SpecInfo.m_SpectatorID].m_aName : Localize("Free-View"));
|
||
TextRender()->Text(0, m_Width - 174.0f, m_Height - 15.0f + (15.f - 8.f) / 2.f, 8.0f, aBuf, -1.0f);
|
||
}
|
||
|
||
void CHud::RenderLocalTime(float x)
|
||
{
|
||
if(!g_Config.m_ClShowLocalTimeAlways && !m_pClient->m_Scoreboard.Active())
|
||
return;
|
||
|
||
// draw the box
|
||
Graphics()->DrawRect(x - 30.0f, 0.0f, 25.0f, 12.5f, ColorRGBA(0.0f, 0.0f, 0.0f, 0.4f), IGraphics::CORNER_B, 3.75f);
|
||
|
||
// draw the text
|
||
char aTimeStr[6];
|
||
str_timestamp_format(aTimeStr, sizeof(aTimeStr), "%H:%M");
|
||
TextRender()->Text(0, x - 25.0f, (12.5f - 5.f) / 2.f, 5.0f, aTimeStr, -1.0f);
|
||
}
|
||
|
||
void CHud::OnRender()
|
||
{
|
||
if(!m_pClient->m_Snap.m_pGameInfoObj)
|
||
return;
|
||
|
||
m_Width = 300.0f * Graphics()->ScreenAspect();
|
||
m_Height = 300.0f;
|
||
Graphics()->MapScreen(0.0f, 0.0f, m_Width, m_Height);
|
||
|
||
#if defined(CONF_VIDEORECORDER)
|
||
if((IVideo::Current() && g_Config.m_ClVideoShowhud) || (!IVideo::Current() && g_Config.m_ClShowhud))
|
||
#else
|
||
if(g_Config.m_ClShowhud)
|
||
#endif
|
||
{
|
||
if(m_pClient->m_Snap.m_pLocalCharacter && !m_pClient->m_Snap.m_SpecInfo.m_Active && !(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags & GAMESTATEFLAG_GAMEOVER))
|
||
{
|
||
if(g_Config.m_ClShowhudHealthAmmo)
|
||
{
|
||
RenderAmmoHealthAndArmor(m_pClient->m_Snap.m_pLocalCharacter);
|
||
}
|
||
if(m_pClient->m_Snap.m_aCharacters[m_pClient->m_Snap.m_LocalClientID].m_HasExtendedData && g_Config.m_ClShowhudDDRace && GameClient()->m_GameInfo.m_HudDDRace)
|
||
{
|
||
RenderPlayerState(m_pClient->m_Snap.m_LocalClientID);
|
||
}
|
||
RenderMovementInformation(m_pClient->m_Snap.m_LocalClientID);
|
||
RenderDDRaceEffects();
|
||
}
|
||
else if(m_pClient->m_Snap.m_SpecInfo.m_Active)
|
||
{
|
||
int SpectatorID = m_pClient->m_Snap.m_SpecInfo.m_SpectatorID;
|
||
if(SpectatorID != SPEC_FREEVIEW && g_Config.m_ClShowhudHealthAmmo)
|
||
{
|
||
RenderAmmoHealthAndArmor(&m_pClient->m_Snap.m_aCharacters[SpectatorID].m_Cur);
|
||
}
|
||
if(SpectatorID != SPEC_FREEVIEW && m_pClient->m_Snap.m_aCharacters[SpectatorID].m_HasExtendedData && g_Config.m_ClShowhudDDRace && GameClient()->m_GameInfo.m_HudDDRace)
|
||
{
|
||
RenderPlayerState(SpectatorID);
|
||
}
|
||
if(SpectatorID != SPEC_FREEVIEW)
|
||
{
|
||
RenderMovementInformation(SpectatorID);
|
||
}
|
||
RenderSpectatorHud();
|
||
}
|
||
|
||
if(g_Config.m_ClShowhudTimer)
|
||
RenderGameTimer();
|
||
RenderPauseNotification();
|
||
RenderSuddenDeath();
|
||
if(g_Config.m_ClShowhudScore)
|
||
RenderScoreHud();
|
||
RenderDummyActions();
|
||
RenderWarmupTimer();
|
||
RenderTextInfo();
|
||
RenderLocalTime((m_Width / 7) * 3);
|
||
if(Client()->State() != IClient::STATE_DEMOPLAYBACK)
|
||
RenderConnectionWarning();
|
||
RenderTeambalanceWarning();
|
||
RenderVoting();
|
||
if(g_Config.m_ClShowRecord)
|
||
RenderRecord();
|
||
}
|
||
RenderCursor();
|
||
}
|
||
|
||
void CHud::OnMessage(int MsgType, void *pRawMsg)
|
||
{
|
||
if(MsgType == NETMSGTYPE_SV_DDRACETIME || MsgType == NETMSGTYPE_SV_DDRACETIMELEGACY)
|
||
{
|
||
CNetMsg_Sv_DDRaceTime *pMsg = (CNetMsg_Sv_DDRaceTime *)pRawMsg;
|
||
|
||
m_DDRaceTime = pMsg->m_Time;
|
||
|
||
m_ShowFinishTime = pMsg->m_Finish != 0;
|
||
|
||
if(!m_ShowFinishTime)
|
||
{
|
||
m_TimeCpDiff = (float)pMsg->m_Check / 100;
|
||
m_TimeCpLastReceivedTick = Client()->GameTick(g_Config.m_ClDummy);
|
||
}
|
||
else
|
||
{
|
||
m_FinishTimeDiff = (float)pMsg->m_Check / 100;
|
||
m_FinishTimeLastReceivedTick = Client()->GameTick(g_Config.m_ClDummy);
|
||
}
|
||
}
|
||
else if(MsgType == NETMSGTYPE_SV_RECORD || MsgType == NETMSGTYPE_SV_RECORDLEGACY)
|
||
{
|
||
CNetMsg_Sv_Record *pMsg = (CNetMsg_Sv_Record *)pRawMsg;
|
||
|
||
// NETMSGTYPE_SV_RACETIME on old race servers
|
||
if(MsgType == NETMSGTYPE_SV_RECORDLEGACY && m_pClient->m_GameInfo.m_DDRaceRecordMessage)
|
||
{
|
||
m_DDRaceTime = pMsg->m_ServerTimeBest; // First value: m_Time
|
||
|
||
m_FinishTimeLastReceivedTick = Client()->GameTick(g_Config.m_ClDummy);
|
||
|
||
if(pMsg->m_PlayerTimeBest) // Second value: m_Check
|
||
{
|
||
m_TimeCpDiff = (float)pMsg->m_PlayerTimeBest / 100;
|
||
m_TimeCpLastReceivedTick = Client()->GameTick(g_Config.m_ClDummy);
|
||
}
|
||
}
|
||
else if(MsgType == NETMSGTYPE_SV_RECORD || m_pClient->m_GameInfo.m_RaceRecordMessage)
|
||
{
|
||
m_ServerRecord = (float)pMsg->m_ServerTimeBest / 100;
|
||
m_aPlayerRecord[g_Config.m_ClDummy] = (float)pMsg->m_PlayerTimeBest / 100;
|
||
}
|
||
}
|
||
}
|
||
|
||
void CHud::RenderDDRaceEffects()
|
||
{
|
||
if(m_DDRaceTime)
|
||
{
|
||
char aBuf[64];
|
||
char aTime[32];
|
||
if(m_ShowFinishTime && m_FinishTimeLastReceivedTick + Client()->GameTickSpeed() * 6 > Client()->GameTick(g_Config.m_ClDummy))
|
||
{
|
||
str_time(m_DDRaceTime, TIME_HOURS_CENTISECS, aTime, sizeof(aTime));
|
||
str_format(aBuf, sizeof(aBuf), "Finish time: %s", aTime);
|
||
|
||
// calculate alpha (4 sec 1 than get lower the next 2 sec)
|
||
float alpha = 1.0f;
|
||
if(m_FinishTimeLastReceivedTick + Client()->GameTickSpeed() * 4 < Client()->GameTick(g_Config.m_ClDummy) && m_FinishTimeLastReceivedTick + Client()->GameTickSpeed() * 6 > Client()->GameTick(g_Config.m_ClDummy))
|
||
{
|
||
// lower the alpha slowly to blend text out
|
||
alpha = ((float)(m_FinishTimeLastReceivedTick + Client()->GameTickSpeed() * 6) - (float)Client()->GameTick(g_Config.m_ClDummy)) / (float)(Client()->GameTickSpeed() * 2);
|
||
}
|
||
|
||
TextRender()->TextColor(1, 1, 1, alpha);
|
||
TextRender()->Text(0, 150 * Graphics()->ScreenAspect() - TextRender()->TextWidth(0, 12, aBuf, -1, -1.0f) / 2, 20, 12, aBuf, -1.0f);
|
||
if(m_FinishTimeDiff != 0.0f)
|
||
{
|
||
if(m_FinishTimeDiff < 0)
|
||
{
|
||
str_time_float(-m_FinishTimeDiff, TIME_HOURS_CENTISECS, aTime, sizeof(aTime));
|
||
str_format(aBuf, sizeof(aBuf), "-%s", aTime);
|
||
TextRender()->TextColor(0.5f, 1.0f, 0.5f, alpha); // green
|
||
}
|
||
else
|
||
{
|
||
str_time_float(m_FinishTimeDiff, TIME_HOURS_CENTISECS, aTime, sizeof(aTime));
|
||
str_format(aBuf, sizeof(aBuf), "+%s", aTime);
|
||
TextRender()->TextColor(1.0f, 0.5f, 0.5f, alpha); // red
|
||
}
|
||
TextRender()->Text(0, 150 * Graphics()->ScreenAspect() - TextRender()->TextWidth(0, 10, aBuf, -1, -1.0f) / 2, 34, 10, aBuf, -1.0f);
|
||
}
|
||
TextRender()->TextColor(1, 1, 1, 1);
|
||
}
|
||
else if(!m_ShowFinishTime && m_TimeCpLastReceivedTick + Client()->GameTickSpeed() * 6 > Client()->GameTick(g_Config.m_ClDummy))
|
||
{
|
||
if(m_TimeCpDiff < 0)
|
||
{
|
||
str_time_float(-m_TimeCpDiff, TIME_HOURS_CENTISECS, aTime, sizeof(aTime));
|
||
str_format(aBuf, sizeof(aBuf), "-%s", aTime);
|
||
}
|
||
else
|
||
{
|
||
str_time_float(m_TimeCpDiff, TIME_HOURS_CENTISECS, aTime, sizeof(aTime));
|
||
str_format(aBuf, sizeof(aBuf), "+%s", aTime);
|
||
}
|
||
|
||
// calculate alpha (4 sec 1 than get lower the next 2 sec)
|
||
float alpha = 1.0f;
|
||
if(m_TimeCpLastReceivedTick + Client()->GameTickSpeed() * 4 < Client()->GameTick(g_Config.m_ClDummy) && m_TimeCpLastReceivedTick + Client()->GameTickSpeed() * 6 > Client()->GameTick(g_Config.m_ClDummy))
|
||
{
|
||
// lower the alpha slowly to blend text out
|
||
alpha = ((float)(m_TimeCpLastReceivedTick + Client()->GameTickSpeed() * 6) - (float)Client()->GameTick(g_Config.m_ClDummy)) / (float)(Client()->GameTickSpeed() * 2);
|
||
}
|
||
|
||
if(m_TimeCpDiff > 0)
|
||
TextRender()->TextColor(1.0f, 0.5f, 0.5f, alpha); // red
|
||
else if(m_TimeCpDiff < 0)
|
||
TextRender()->TextColor(0.5f, 1.0f, 0.5f, alpha); // green
|
||
else if(!m_TimeCpDiff)
|
||
TextRender()->TextColor(1, 1, 1, alpha); // white
|
||
TextRender()->Text(0, 150 * Graphics()->ScreenAspect() - TextRender()->TextWidth(0, 10, aBuf, -1, -1.0f) / 2, 20, 10, aBuf, -1.0f);
|
||
|
||
TextRender()->TextColor(1, 1, 1, 1);
|
||
}
|
||
}
|
||
}
|
||
|
||
void CHud::RenderRecord()
|
||
{
|
||
if(m_ServerRecord > 0.0f)
|
||
{
|
||
char aBuf[64];
|
||
str_format(aBuf, sizeof(aBuf), Localize("Server best:"));
|
||
TextRender()->Text(0, 5, 75, 6, aBuf, -1.0f);
|
||
char aTime[32];
|
||
str_time_float(m_ServerRecord, TIME_HOURS_CENTISECS, aTime, sizeof(aTime));
|
||
str_format(aBuf, sizeof(aBuf), "%s%s", m_ServerRecord > 3600 ? "" : " ", aTime);
|
||
TextRender()->Text(0, 53, 75, 6, aBuf, -1.0f);
|
||
}
|
||
|
||
const float PlayerRecord = m_aPlayerRecord[g_Config.m_ClDummy];
|
||
if(PlayerRecord > 0.0f)
|
||
{
|
||
char aBuf[64];
|
||
str_format(aBuf, sizeof(aBuf), Localize("Personal best:"));
|
||
TextRender()->Text(0, 5, 82, 6, aBuf, -1.0f);
|
||
char aTime[32];
|
||
str_time_float(PlayerRecord, TIME_HOURS_CENTISECS, aTime, sizeof(aTime));
|
||
str_format(aBuf, sizeof(aBuf), "%s%s", PlayerRecord > 3600 ? "" : " ", aTime);
|
||
TextRender()->Text(0, 53, 82, 6, aBuf, -1.0f);
|
||
}
|
||
}
|