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 <engine/graphics.h>
4#include <engine/shared/config.h>
5#include <engine/textrender.h>
6
7#include <game/generated/protocol.h>
8
9#include <game/client/gameclient.h>
10#include <game/client/prediction/entities/character.h>
11#include <game/localization.h>
12
13#include "debughud.h"
14
15static constexpr int64_t GRAPH_MAX_VALUES = 128;
16
17CDebugHud::CDebugHud() :
18 m_RampGraph(GRAPH_MAX_VALUES),
19 m_ZoomedInGraph(GRAPH_MAX_VALUES)
20{
21}
22
23void CDebugHud::RenderNetCorrections()
24{
25 if(!g_Config.m_Debug || g_Config.m_DbgGraphs || !m_pClient->m_Snap.m_pLocalCharacter || !m_pClient->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(m_pClient->m_Snap.m_pLocalCharacter->m_VelX / 256.0f, m_pClient->m_Snap.m_pLocalCharacter->m_VelY / 256.0f)) * Client()->GameTickSpeed();
33 const float VelspeedX = m_pClient->m_Snap.m_pLocalCharacter->m_VelX / 256.0f * Client()->GameTickSpeed();
34 const float VelspeedY = m_pClient->m_Snap.m_pLocalCharacter->m_VelY / 256.0f * Client()->GameTickSpeed();
35 const float Ramp = VelocityRamp(Value: Velspeed, Start: m_pClient->m_aTuning[g_Config.m_ClDummy].m_VelrampStart, Range: m_pClient->m_aTuning[g_Config.m_ClDummy].m_VelrampRange, Curvature: m_pClient->m_aTuning[g_Config.m_ClDummy].m_VelrampCurvature);
36 const CCharacter *pCharacter = m_pClient->m_GameWorld.GetCharacterById(Id: m_pClient->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(rgb: 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", pCharacter == nullptr ? -1 : pCharacter->m_TuneZone);
67 RenderRow("Tune zone:", aBuf);
68
69 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%.2f", m_pClient->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", m_pClient->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", m_pClient->m_Snap.m_pLocalCharacter->m_Angle);
76 RenderRow("Angle:", aBuf);
77
78 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%d", m_pClient->NetobjNumCorrections());
79 RenderRow("Netobj corrections", aBuf);
80 RenderRow(" on:", m_pClient->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 = m_pClient->m_GameWorld.GetCharacterById(Id: m_pClient->m_Snap.m_LocalClientId);
96
97 const CTuningParams StandardTuning;
98 const CTuningParams *pGlobalTuning = m_pClient->GetTuning(i: 0);
99 const CTuningParams *pZoneTuning = !m_pClient->m_GameWorld.m_WorldConfig.m_UseTuneZones || pCharacter == nullptr ? nullptr : m_pClient->GetTuning(i: pCharacter->m_TuneZone);
100 const CTuningParams *pActiveTuning = pZoneTuning == nullptr ? pGlobalTuning : pZoneTuning;
101
102 const float Height = 300.0f;
103 const float Width = Height * Graphics()->ScreenAspect();
104 Graphics()->MapScreen(TopLeftX: 0.0f, TopLeftY: 0.0f, BottomRightX: Width, BottomRightY: Height);
105
106 const float FontSize = 5.0f;
107
108 const float StartY = 50.0f;
109 float y = StartY;
110 float StartX = 30.0f;
111 const auto &&RenderRow = [&](const char *pCol1, const char *pCol2, const char *pCol3) {
112 float x = StartX;
113 TextRender()->Text(x: x - TextRender()->TextWidth(Size: FontSize, pText: pCol1), y, Size: FontSize, pText: pCol1);
114
115 x += 30.0f;
116 TextRender()->Text(x: x - TextRender()->TextWidth(Size: FontSize, pText: pCol2), y, Size: FontSize, pText: pCol2);
117
118 x += 10.0f;
119 TextRender()->Text(x, y, Size: FontSize, pText: pCol3);
120
121 y += FontSize + 1.0f;
122
123 if(y >= Height - 80.0f)
124 {
125 y = StartY;
126 StartX += 130.0f;
127 }
128 };
129
130 for(int i = 0; i < CTuningParams::Num(); i++)
131 {
132 float CurrentGlobal, CurrentZone, Standard;
133 pGlobalTuning->Get(Index: i, pValue: &CurrentGlobal);
134 if(pZoneTuning == nullptr)
135 CurrentZone = 0.0f;
136 else
137 pZoneTuning->Get(Index: i, pValue: &CurrentZone);
138 StandardTuning.Get(Index: i, pValue: &Standard);
139
140 if(g_Config.m_DbgTuning == DBG_TUNING_SHOW_CHANGED && Standard == CurrentGlobal && (pZoneTuning == nullptr || Standard == CurrentZone))
141 continue; // skip unchanged params
142
143 if(y == StartY)
144 {
145 TextRender()->TextColor(rgb: TextRender()->DefaultTextColor());
146 RenderRow("Standard", "Current", "Tuning");
147 }
148
149 ColorRGBA TextColor;
150 if(g_Config.m_DbgTuning == DBG_TUNING_SHOW_ALL && Standard == CurrentGlobal && (pZoneTuning == nullptr || Standard == CurrentZone))
151 TextColor = ColorRGBA(0.75f, 0.75f, 0.75f, 1.0f); // grey: value unchanged globally and in current zone
152 else if(Standard == CurrentGlobal && pZoneTuning != nullptr && Standard != CurrentZone)
153 TextColor = ColorRGBA(0.6f, 0.6f, 1.0f, 1.0f); // blue: value changed only in current zone
154 else if(Standard != CurrentGlobal && pZoneTuning != nullptr && Standard == CurrentZone)
155 TextColor = ColorRGBA(0.4f, 1.0f, 0.4f, 1.0f); // green: value changed globally but reset to default by tune zone
156 else
157 TextColor = ColorRGBA(1.0f, 0.5f, 0.5f, 1.0f); // red: value changed globally
158 TextRender()->TextColor(rgb: TextColor);
159
160 char aBufStandard[32];
161 str_format(buffer: aBufStandard, buffer_size: sizeof(aBufStandard), format: "%.2f", Standard);
162 char aBufCurrent[32];
163 str_format(buffer: aBufCurrent, buffer_size: sizeof(aBufCurrent), format: "%.2f", pZoneTuning == nullptr ? CurrentGlobal : CurrentZone);
164 RenderRow(aBufStandard, aBufCurrent, CTuningParams::Name(Index: i));
165 }
166
167 TextRender()->TextColor(rgb: TextRender()->DefaultTextColor());
168 if(g_Config.m_DbgTuning == DBG_TUNING_SHOW_CHANGED)
169 return;
170
171 // Render Velspeed.X * Ramp Graphs
172 Graphics()->MapScreen(TopLeftX: 0.0f, TopLeftY: 0.0f, BottomRightX: Graphics()->ScreenWidth(), BottomRightY: Graphics()->ScreenHeight());
173 const float GraphSpacing = Graphics()->ScreenWidth() / 100.0f;
174 const float GraphW = Graphics()->ScreenWidth() / 4.0f;
175 const float GraphH = 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: m_pClient->m_aTuning[g_Config.m_ClDummy].m_VelrampStart, Range: m_pClient->m_aTuning[g_Config.m_ClDummy].m_VelrampRange, Curvature: m_pClient->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 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 = MiddleOfZoomedInGraph - 64 * StepSizeZoomedInGraph + i * StepSizeZoomedInGraph;
216 const float Ramp = VelocityRamp(Value: Speed, Start: m_pClient->m_aTuning[g_Config.m_ClDummy].m_VelrampStart, Range: m_pClient->m_aTuning[g_Config.m_ClDummy].m_VelrampRange, Curvature: m_pClient->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, y: GraphY - GraphFontSize, Size: GraphFontSize, pText: aBuf);
242 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "Zoomed in on turning point (Velspeed %d to %d)", ((int)MiddleOfZoomedInGraph - 64 * StepSizeZoomedInGraph) / 32, ((int)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(rgb: 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