1/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
2/* If you are missing that file, acquire a complete release at teeworlds.com. */
3#include "debughud.h"
4
5#include <engine/graphics.h>
6#include <engine/shared/config.h>
7#include <engine/textrender.h>
8
9#include <generated/protocol.h>
10
11#include <game/client/gameclient.h>
12#include <game/client/prediction/entities/character.h>
13#include <game/localization.h>
14
15static constexpr int64_t GRAPH_MAX_VALUES = 128;
16
17CDebugHud::CDebugHud() :
18 m_RampGraph(GRAPH_MAX_VALUES, 2, false),
19 m_ZoomedInGraph(GRAPH_MAX_VALUES, 2, false)
20{
21}
22
23void CDebugHud::RenderNetCorrections()
24{
25 if(!g_Config.m_Debug || g_Config.m_DbgGraphs || !GameClient()->m_Snap.m_pLocalCharacter || !GameClient()->m_Snap.m_pLocalPrevCharacter)
26 return;
27
28 const float Height = 300.0f;
29 const float Width = Height * Graphics()->ScreenAspect();
30 Graphics()->MapScreen(TopLeftX: 0.0f, TopLeftY: 0.0f, BottomRightX: Width, BottomRightY: Height);
31
32 const float Velspeed = length(a: vec2(GameClient()->m_Snap.m_pLocalCharacter->m_VelX / 256.0f, GameClient()->m_Snap.m_pLocalCharacter->m_VelY / 256.0f)) * Client()->GameTickSpeed();
33 const float VelspeedX = GameClient()->m_Snap.m_pLocalCharacter->m_VelX / 256.0f * Client()->GameTickSpeed();
34 const float VelspeedY = GameClient()->m_Snap.m_pLocalCharacter->m_VelY / 256.0f * Client()->GameTickSpeed();
35 const float Ramp = VelocityRamp(Value: Velspeed, Start: GameClient()->m_aTuning[g_Config.m_ClDummy].m_VelrampStart, Range: GameClient()->m_aTuning[g_Config.m_ClDummy].m_VelrampRange, Curvature: GameClient()->m_aTuning[g_Config.m_ClDummy].m_VelrampCurvature);
36 const CCharacter *pCharacter = GameClient()->m_GameWorld.GetCharacterById(Id: GameClient()->m_Snap.m_LocalClientId);
37
38 const float FontSize = 5.0f;
39 const float LineHeight = FontSize + 1.0f;
40
41 float y = 50.0f;
42 char aBuf[128];
43 const auto &&RenderRow = [&](const char *pLabel, const char *pValue) {
44 TextRender()->Text(x: Width - 100.0f, y, Size: FontSize, pText: pLabel);
45 TextRender()->Text(x: Width - 10.0f - TextRender()->TextWidth(Size: FontSize, pText: pValue), y, Size: FontSize, pText: pValue);
46 y += LineHeight;
47 };
48
49 TextRender()->TextColor(Color: TextRender()->DefaultTextColor());
50
51 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%.0f Bps", Velspeed / 32);
52 RenderRow("Velspeed:", aBuf);
53
54 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%.0f Bps", VelspeedX / 32 * Ramp);
55 RenderRow("Velspeed.x * Ramp:", aBuf);
56
57 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%.0f Bps", VelspeedY / 32);
58 RenderRow("Velspeed.y:", aBuf);
59
60 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%.2f", Ramp);
61 RenderRow("Ramp:", aBuf);
62
63 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%d", pCharacter == nullptr ? -1 : pCharacter->m_TeleCheckpoint);
64 RenderRow("Checkpoint:", aBuf);
65
66 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%d / %d", pCharacter == nullptr ? -1 : pCharacter->GetPureTuneZone(), pCharacter == nullptr ? -1 : pCharacter->GetOverriddenTuneZone());
67 RenderRow("Tune zone (pure / override):", aBuf);
68
69 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%.2f", GameClient()->m_Snap.m_pLocalCharacter->m_X / 32.0f);
70 RenderRow("Pos.x:", aBuf);
71
72 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%.2f", GameClient()->m_Snap.m_pLocalCharacter->m_Y / 32.0f);
73 RenderRow("Pos.y:", aBuf);
74
75 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%d", GameClient()->m_Snap.m_pLocalCharacter->m_Angle);
76 RenderRow("Angle:", aBuf);
77
78 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%d", GameClient()->NetobjNumCorrections());
79 RenderRow("Netobj corrections", aBuf);
80 RenderRow(" on:", GameClient()->NetobjCorrectedOn());
81}
82
83void CDebugHud::RenderTuning()
84{
85 enum
86 {
87 DBG_TUNING_OFF = 0,
88 DBG_TUNING_SHOW_CHANGED,
89 DBG_TUNING_SHOW_ALL,
90 };
91
92 if(g_Config.m_DbgTuning == DBG_TUNING_OFF)
93 return;
94
95 const CCharacter *pCharacter = GameClient()->m_GameWorld.GetCharacterById(Id: GameClient()->m_Snap.m_LocalClientId);
96
97 const CTuningParams *pGlobalTuning = GameClient()->GetTuning(i: 0);
98 const CTuningParams *pZoneTuning = !GameClient()->m_GameWorld.m_WorldConfig.m_UseTuneZones || pCharacter == nullptr ? nullptr : GameClient()->GetTuning(i: pCharacter->GetOverriddenTuneZone());
99 const CTuningParams *pActiveTuning = pZoneTuning == nullptr ? pGlobalTuning : pZoneTuning;
100
101 const float Height = 300.0f;
102 const float Width = Height * Graphics()->ScreenAspect();
103 Graphics()->MapScreen(TopLeftX: 0.0f, TopLeftY: 0.0f, BottomRightX: Width, BottomRightY: Height);
104
105 const float FontSize = 5.0f;
106
107 const float StartY = 50.0f;
108 float y = StartY;
109 float StartX = 30.0f;
110 const auto &&RenderRow = [&](const char *pCol1, const char *pCol2, const char *pCol3) {
111 float x = StartX;
112 TextRender()->Text(x: x - TextRender()->TextWidth(Size: FontSize, pText: pCol1), y, Size: FontSize, pText: pCol1);
113
114 x += 30.0f;
115 TextRender()->Text(x: x - TextRender()->TextWidth(Size: FontSize, pText: pCol2), y, Size: FontSize, pText: pCol2);
116
117 x += 10.0f;
118 TextRender()->Text(x, y, Size: FontSize, pText: pCol3);
119
120 y += FontSize + 1.0f;
121
122 if(y >= Height - 80.0f)
123 {
124 y = StartY;
125 StartX += 130.0f;
126 }
127 };
128
129 for(int i = 0; i < CTuningParams::Num(); i++)
130 {
131 float CurrentGlobal, CurrentZone, Standard;
132 pGlobalTuning->Get(Index: i, pValue: &CurrentGlobal);
133 if(pZoneTuning == nullptr)
134 CurrentZone = 0.0f;
135 else
136 pZoneTuning->Get(Index: i, pValue: &CurrentZone);
137 CTuningParams::DEFAULT.Get(Index: i, pValue: &Standard);
138
139 if(g_Config.m_DbgTuning == DBG_TUNING_SHOW_CHANGED && Standard == CurrentGlobal && (pZoneTuning == nullptr || Standard == CurrentZone))
140 continue; // skip unchanged params
141
142 if(y == StartY)
143 {
144 TextRender()->TextColor(Color: TextRender()->DefaultTextColor());
145 RenderRow("Standard", "Current", "Tuning");
146 }
147
148 ColorRGBA TextColor;
149 if(g_Config.m_DbgTuning == DBG_TUNING_SHOW_ALL && Standard == CurrentGlobal && (pZoneTuning == nullptr || Standard == CurrentZone))
150 TextColor = ColorRGBA(0.75f, 0.75f, 0.75f, 1.0f); // grey: value unchanged globally and in current zone
151 else if(Standard == CurrentGlobal && pZoneTuning != nullptr && Standard != CurrentZone)
152 TextColor = ColorRGBA(0.6f, 0.6f, 1.0f, 1.0f); // blue: value changed only in current zone
153 else if(Standard != CurrentGlobal && pZoneTuning != nullptr && Standard == CurrentZone)
154 TextColor = ColorRGBA(0.4f, 1.0f, 0.4f, 1.0f); // green: value changed globally but reset to default by tune zone
155 else
156 TextColor = ColorRGBA(1.0f, 0.5f, 0.5f, 1.0f); // red: value changed globally
157 TextRender()->TextColor(Color: TextColor);
158
159 char aBufStandard[32];
160 str_format(buffer: aBufStandard, buffer_size: sizeof(aBufStandard), format: "%.2f", Standard);
161 char aBufCurrent[32];
162 str_format(buffer: aBufCurrent, buffer_size: sizeof(aBufCurrent), format: "%.2f", pZoneTuning == nullptr ? CurrentGlobal : CurrentZone);
163 RenderRow(aBufStandard, aBufCurrent, CTuningParams::Name(Index: i));
164 }
165
166 TextRender()->TextColor(Color: TextRender()->DefaultTextColor());
167 if(g_Config.m_DbgTuning == DBG_TUNING_SHOW_CHANGED)
168 return;
169
170 // Render Velspeed.X * Ramp Graphs
171 Graphics()->MapScreen(TopLeftX: 0.0f, TopLeftY: 0.0f, BottomRightX: Graphics()->ScreenWidth(), BottomRightY: Graphics()->ScreenHeight());
172 // Make sure graph positions and sizes are aligned with pixels to avoid lines overlapping graph edges
173 const float GraphSpacing = std::round(x: Graphics()->ScreenWidth() / 100.0f);
174 const float GraphW = std::round(x: Graphics()->ScreenWidth() / 4.0f);
175 const float GraphH = std::round(x: Graphics()->ScreenHeight() / 6.0f);
176 const float GraphX = GraphW;
177 const float GraphY = Graphics()->ScreenHeight() - GraphH - GraphSpacing;
178
179 const int StepSizeRampGraph = 270;
180 const int StepSizeZoomedInGraph = 14;
181 if(m_OldVelrampStart != pActiveTuning->m_VelrampStart || m_OldVelrampRange != pActiveTuning->m_VelrampRange || m_OldVelrampCurvature != pActiveTuning->m_VelrampCurvature)
182 {
183 m_OldVelrampStart = pActiveTuning->m_VelrampStart;
184 m_OldVelrampRange = pActiveTuning->m_VelrampRange;
185 m_OldVelrampCurvature = pActiveTuning->m_VelrampCurvature;
186
187 m_RampGraph.Init(Min: 0.0f, Max: 0.0f);
188 m_SpeedTurningPoint = 0;
189 float PreviousRampedSpeed = 1.0f;
190 for(int64_t i = 0; i < GRAPH_MAX_VALUES; i++)
191 {
192 // This is a calculation of the speed values per second on the X axis, from 270 to 34560 in steps of 270
193 const float Speed = (i + 1) * StepSizeRampGraph;
194 const float Ramp = VelocityRamp(Value: Speed, Start: GameClient()->m_aTuning[g_Config.m_ClDummy].m_VelrampStart, Range: GameClient()->m_aTuning[g_Config.m_ClDummy].m_VelrampRange, Curvature: GameClient()->m_aTuning[g_Config.m_ClDummy].m_VelrampCurvature);
195 const float RampedSpeed = Speed * Ramp;
196 if(RampedSpeed >= PreviousRampedSpeed)
197 {
198 m_RampGraph.InsertAt(Time: i, Value: RampedSpeed / 32, Color: ColorRGBA(0.0f, 1.0f, 0.0f, 0.75f));
199 m_SpeedTurningPoint = Speed;
200 }
201 else
202 {
203 m_RampGraph.InsertAt(Time: i, Value: RampedSpeed / 32, Color: ColorRGBA(1.0f, 0.0f, 0.0f, 0.75f));
204 }
205 PreviousRampedSpeed = RampedSpeed;
206 }
207 m_RampGraph.Scale(WantedTotalTime: GRAPH_MAX_VALUES - 1);
208
209 m_ZoomedInGraph.Init(Min: 0.0f, Max: 0.0f);
210 PreviousRampedSpeed = 1.0f;
211 m_MiddleOfZoomedInGraph = m_SpeedTurningPoint;
212 for(int64_t i = 0; i < GRAPH_MAX_VALUES; i++)
213 {
214 // This is a calculation of the speed values per second on the X axis, from (MiddleOfZoomedInGraph - 64 * StepSize) to (MiddleOfZoomedInGraph + 64 * StepSize)
215 const float Speed = m_MiddleOfZoomedInGraph - 64 * StepSizeZoomedInGraph + i * StepSizeZoomedInGraph;
216 const float Ramp = VelocityRamp(Value: Speed, Start: GameClient()->m_aTuning[g_Config.m_ClDummy].m_VelrampStart, Range: GameClient()->m_aTuning[g_Config.m_ClDummy].m_VelrampRange, Curvature: GameClient()->m_aTuning[g_Config.m_ClDummy].m_VelrampCurvature);
217 const float RampedSpeed = Speed * Ramp;
218 if(RampedSpeed >= PreviousRampedSpeed)
219 {
220 m_ZoomedInGraph.InsertAt(Time: i, Value: RampedSpeed / 32, Color: ColorRGBA(0.0f, 1.0f, 0.0f, 0.75f));
221 m_SpeedTurningPoint = Speed;
222 }
223 else
224 {
225 m_ZoomedInGraph.InsertAt(Time: i, Value: RampedSpeed / 32, Color: ColorRGBA(1.0f, 0.0f, 0.0f, 0.75f));
226 }
227 if(i == 0)
228 {
229 m_ZoomedInGraph.SetMin(RampedSpeed / 32);
230 }
231 PreviousRampedSpeed = RampedSpeed;
232 }
233 m_ZoomedInGraph.Scale(WantedTotalTime: GRAPH_MAX_VALUES - 1);
234 }
235
236 const float GraphFontSize = 12.0f;
237 char aBuf[128];
238 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Velspeed.X * Ramp in Bps (Velspeed %d to %d)", StepSizeRampGraph / 32, 128 * StepSizeRampGraph / 32);
239 m_RampGraph.Render(pGraphics: Graphics(), pTextRender: TextRender(), x: GraphX, y: GraphY, w: GraphW, h: GraphH, pDescription: aBuf);
240 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Max Velspeed before it ramps off: %.2f Bps", m_SpeedTurningPoint / 32);
241 TextRender()->Text(x: GraphX + 2.0f, y: GraphY + GraphH - GraphFontSize - 2.0f, Size: GraphFontSize, pText: aBuf);
242 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Zoomed in on turning point (Velspeed %d to %d)", ((int)m_MiddleOfZoomedInGraph - 64 * StepSizeZoomedInGraph) / 32, ((int)m_MiddleOfZoomedInGraph + 64 * StepSizeZoomedInGraph) / 32);
243 m_ZoomedInGraph.Render(pGraphics: Graphics(), pTextRender: TextRender(), x: GraphX + GraphW + GraphSpacing, y: GraphY, w: GraphW, h: GraphH, pDescription: aBuf);
244}
245
246void CDebugHud::RenderHint()
247{
248 if(!g_Config.m_Debug)
249 return;
250
251 const float Height = 300.0f;
252 const float Width = Height * Graphics()->ScreenAspect();
253 Graphics()->MapScreen(TopLeftX: 0.0f, TopLeftY: 0.0f, BottomRightX: Width, BottomRightY: Height);
254
255 const float FontSize = 5.0f;
256 const float Spacing = 5.0f;
257
258 TextRender()->TextColor(Color: TextRender()->DefaultTextColor());
259 TextRender()->Text(x: Spacing, y: Height - FontSize - Spacing, Size: FontSize, pText: Localize(pStr: "Debug mode enabled. Press Ctrl+Shift+D to disable debug mode."));
260}
261
262void CDebugHud::OnRender()
263{
264 if(Client()->State() != IClient::STATE_ONLINE && Client()->State() != IClient::STATE_DEMOPLAYBACK)
265 return;
266
267 RenderTuning();
268 RenderNetCorrections();
269 RenderHint();
270}
271