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/client_data.h> |
8 | #include <game/generated/protocol.h> |
9 | |
10 | #include <game/client/gameclient.h> |
11 | #include <game/client/prediction/entities/character.h> |
12 | |
13 | #include "camera.h" |
14 | #include "controls.h" |
15 | #include "nameplates.h" |
16 | |
17 | void CNamePlates::RenderNameplate( |
18 | const CNetObj_Character *pPrevChar, |
19 | const CNetObj_Character *pPlayerChar, |
20 | const CNetObj_PlayerInfo *pPlayerInfo) |
21 | { |
22 | int ClientId = pPlayerInfo->m_ClientId; |
23 | |
24 | vec2 Position; |
25 | if(ClientId >= 0 && ClientId < MAX_CLIENTS) |
26 | Position = m_pClient->m_aClients[ClientId].m_RenderPos; |
27 | else |
28 | Position = mix(a: vec2(pPrevChar->m_X, pPrevChar->m_Y), b: vec2(pPlayerChar->m_X, pPlayerChar->m_Y), amount: Client()->IntraGameTick(Conn: g_Config.m_ClDummy)); |
29 | |
30 | RenderNameplatePos(Position, pPlayerInfo, Alpha: 1.0f); |
31 | } |
32 | |
33 | void CNamePlates::RenderNameplatePos(vec2 Position, const CNetObj_PlayerInfo *pPlayerInfo, float Alpha, bool ForceAlpha) |
34 | { |
35 | int ClientId = pPlayerInfo->m_ClientId; |
36 | |
37 | bool OtherTeam = m_pClient->IsOtherTeam(ClientId); |
38 | |
39 | float FontSize = 18.0f + 20.0f * g_Config.m_ClNameplatesSize / 100.0f; |
40 | float FontSizeClan = 18.0f + 20.0f * g_Config.m_ClNameplatesClanSize / 100.0f; |
41 | |
42 | TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_NO_FIRST_CHARACTER_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_LAST_CHARACTER_ADVANCE); |
43 | float YOffset = Position.y - 38; |
44 | ColorRGBA rgb = ColorRGBA(1.0f, 1.0f, 1.0f); |
45 | |
46 | // render players' key presses |
47 | int ShowDirection = g_Config.m_ClShowDirection; |
48 | #if defined(CONF_VIDEORECORDER) |
49 | if(IVideo::Current()) |
50 | ShowDirection = g_Config.m_ClVideoShowDirection; |
51 | #endif |
52 | if((ShowDirection && ShowDirection != 3 && !pPlayerInfo->m_Local) || (ShowDirection >= 2 && pPlayerInfo->m_Local) || (ShowDirection == 3 && Client()->DummyConnected() && Client()->State() != IClient::STATE_DEMOPLAYBACK && ClientId == m_pClient->m_aLocalIds[!g_Config.m_ClDummy])) |
53 | { |
54 | Graphics()->SetColor(r: 1.0f, g: 1.0f, b: 1.0f, a: 1.0f); |
55 | Graphics()->QuadsSetRotation(Angle: 0); |
56 | |
57 | const float ShowDirectionImgSize = 22.0f; |
58 | YOffset -= ShowDirectionImgSize; |
59 | vec2 ShowDirectionPos = vec2(Position.x - 11.0f, YOffset); |
60 | |
61 | bool DirLeft = m_pClient->m_Snap.m_aCharacters[pPlayerInfo->m_ClientId].m_Cur.m_Direction == -1; |
62 | bool DirRight = m_pClient->m_Snap.m_aCharacters[pPlayerInfo->m_ClientId].m_Cur.m_Direction == 1; |
63 | bool Jump = m_pClient->m_Snap.m_aCharacters[pPlayerInfo->m_ClientId].m_Cur.m_Jumped & 1; |
64 | |
65 | if(pPlayerInfo->m_Local && Client()->State() != IClient::STATE_DEMOPLAYBACK) |
66 | { |
67 | DirLeft = m_pClient->m_Controls.m_aInputData[g_Config.m_ClDummy].m_Direction == -1; |
68 | DirRight = m_pClient->m_Controls.m_aInputData[g_Config.m_ClDummy].m_Direction == 1; |
69 | Jump = m_pClient->m_Controls.m_aInputData[g_Config.m_ClDummy].m_Jump == 1; |
70 | } |
71 | if(Client()->DummyConnected() && Client()->State() != IClient::STATE_DEMOPLAYBACK && pPlayerInfo->m_ClientId == m_pClient->m_aLocalIds[!g_Config.m_ClDummy]) |
72 | { |
73 | DirLeft = m_pClient->m_Controls.m_aInputData[!g_Config.m_ClDummy].m_Direction == -1; |
74 | DirRight = m_pClient->m_Controls.m_aInputData[!g_Config.m_ClDummy].m_Direction == 1; |
75 | Jump = m_pClient->m_Controls.m_aInputData[!g_Config.m_ClDummy].m_Jump == 1; |
76 | } |
77 | if(DirLeft) |
78 | { |
79 | Graphics()->TextureSet(Texture: g_pData->m_aImages[IMAGE_ARROW].m_Id); |
80 | Graphics()->QuadsSetRotation(Angle: pi); |
81 | Graphics()->RenderQuadContainerAsSprite(ContainerIndex: m_DirectionQuadContainerIndex, QuadOffset: 0, X: ShowDirectionPos.x - 30.f, Y: ShowDirectionPos.y); |
82 | } |
83 | else if(DirRight) |
84 | { |
85 | Graphics()->TextureSet(Texture: g_pData->m_aImages[IMAGE_ARROW].m_Id); |
86 | Graphics()->RenderQuadContainerAsSprite(ContainerIndex: m_DirectionQuadContainerIndex, QuadOffset: 0, X: ShowDirectionPos.x + 30.f, Y: ShowDirectionPos.y); |
87 | } |
88 | if(Jump) |
89 | { |
90 | Graphics()->TextureSet(Texture: g_pData->m_aImages[IMAGE_ARROW].m_Id); |
91 | Graphics()->QuadsSetRotation(Angle: pi * 3 / 2); |
92 | Graphics()->RenderQuadContainerAsSprite(ContainerIndex: m_DirectionQuadContainerIndex, QuadOffset: 0, X: ShowDirectionPos.x, Y: ShowDirectionPos.y); |
93 | } |
94 | Graphics()->SetColor(r: 1.0f, g: 1.0f, b: 1.0f, a: 1.0f); |
95 | Graphics()->QuadsSetRotation(Angle: 0); |
96 | } |
97 | |
98 | // render name plate |
99 | if((!pPlayerInfo->m_Local || g_Config.m_ClNameplatesOwn) && g_Config.m_ClNameplates) |
100 | { |
101 | float a = 1; |
102 | if(g_Config.m_ClNameplatesAlways == 0) |
103 | a = clamp(val: 1 - std::pow(x: distance(a: m_pClient->m_Controls.m_aTargetPos[g_Config.m_ClDummy], b: Position) / 200.0f, y: 16.0f), lo: 0.0f, hi: 1.0f); |
104 | |
105 | const char *pName = m_pClient->m_aClients[pPlayerInfo->m_ClientId].m_aName; |
106 | if(str_comp(a: pName, b: m_aNamePlates[ClientId].m_aName) != 0 || FontSize != m_aNamePlates[ClientId].m_NameTextFontSize) |
107 | { |
108 | str_copy(dst&: m_aNamePlates[ClientId].m_aName, src: pName); |
109 | m_aNamePlates[ClientId].m_NameTextFontSize = FontSize; |
110 | |
111 | CTextCursor Cursor; |
112 | TextRender()->SetCursor(pCursor: &Cursor, x: 0, y: 0, FontSize, Flags: TEXTFLAG_RENDER); |
113 | Cursor.m_LineWidth = -1; |
114 | |
115 | // create nameplates at standard zoom |
116 | float ScreenX0, ScreenY0, ScreenX1, ScreenY1; |
117 | Graphics()->GetScreen(pTopLeftX: &ScreenX0, pTopLeftY: &ScreenY0, pBottomRightX: &ScreenX1, pBottomRightY: &ScreenY1); |
118 | RenderTools()->MapScreenToInterface(CenterX: m_pClient->m_Camera.m_Center.x, CenterY: m_pClient->m_Camera.m_Center.y); |
119 | |
120 | m_aNamePlates[ClientId].m_NameTextWidth = TextRender()->TextWidth(Size: FontSize, pText: pName, StrLength: -1, LineWidth: -1.0f); |
121 | |
122 | TextRender()->RecreateTextContainer(TextContainerIndex&: m_aNamePlates[ClientId].m_NameTextContainerIndex, pCursor: &Cursor, pText: pName); |
123 | Graphics()->MapScreen(TopLeftX: ScreenX0, TopLeftY: ScreenY0, BottomRightX: ScreenX1, BottomRightY: ScreenY1); |
124 | } |
125 | |
126 | if(g_Config.m_ClNameplatesClan) |
127 | { |
128 | const char *pClan = m_pClient->m_aClients[ClientId].m_aClan; |
129 | if(str_comp(a: pClan, b: m_aNamePlates[ClientId].m_aClanName) != 0 || FontSizeClan != m_aNamePlates[ClientId].m_ClanNameTextFontSize) |
130 | { |
131 | str_copy(dst&: m_aNamePlates[ClientId].m_aClanName, src: pClan); |
132 | m_aNamePlates[ClientId].m_ClanNameTextFontSize = FontSizeClan; |
133 | |
134 | CTextCursor Cursor; |
135 | TextRender()->SetCursor(pCursor: &Cursor, x: 0, y: 0, FontSize: FontSizeClan, Flags: TEXTFLAG_RENDER); |
136 | Cursor.m_LineWidth = -1; |
137 | |
138 | // create nameplates at standard zoom |
139 | float ScreenX0, ScreenY0, ScreenX1, ScreenY1; |
140 | Graphics()->GetScreen(pTopLeftX: &ScreenX0, pTopLeftY: &ScreenY0, pBottomRightX: &ScreenX1, pBottomRightY: &ScreenY1); |
141 | RenderTools()->MapScreenToInterface(CenterX: m_pClient->m_Camera.m_Center.x, CenterY: m_pClient->m_Camera.m_Center.y); |
142 | |
143 | m_aNamePlates[ClientId].m_ClanNameTextWidth = TextRender()->TextWidth(Size: FontSizeClan, pText: pClan, StrLength: -1, LineWidth: -1.0f); |
144 | |
145 | TextRender()->RecreateTextContainer(TextContainerIndex&: m_aNamePlates[ClientId].m_ClanNameTextContainerIndex, pCursor: &Cursor, pText: pClan); |
146 | Graphics()->MapScreen(TopLeftX: ScreenX0, TopLeftY: ScreenY0, BottomRightX: ScreenX1, BottomRightY: ScreenY1); |
147 | } |
148 | } |
149 | |
150 | float tw = m_aNamePlates[ClientId].m_NameTextWidth; |
151 | if(g_Config.m_ClNameplatesTeamcolors && m_pClient->m_Teams.Team(ClientId)) |
152 | rgb = m_pClient->GetDDTeamColor(DDTeam: m_pClient->m_Teams.Team(ClientId), Lightness: 0.75f); |
153 | |
154 | ColorRGBA TColor; |
155 | ColorRGBA TOutlineColor; |
156 | |
157 | if(OtherTeam && !ForceAlpha) |
158 | { |
159 | TOutlineColor = ColorRGBA(0.0f, 0.0f, 0.0f, 0.2f * g_Config.m_ClShowOthersAlpha / 100.0f); |
160 | TColor = ColorRGBA(rgb.r, rgb.g, rgb.b, g_Config.m_ClShowOthersAlpha / 100.0f); |
161 | } |
162 | else |
163 | { |
164 | TOutlineColor = ColorRGBA(0.0f, 0.0f, 0.0f, 0.5f * a); |
165 | TColor = ColorRGBA(rgb.r, rgb.g, rgb.b, a); |
166 | } |
167 | if(g_Config.m_ClNameplatesTeamcolors && m_pClient->m_Snap.m_pGameInfoObj && m_pClient->m_Snap.m_pGameInfoObj->m_GameFlags & GAMEFLAG_TEAMS) |
168 | { |
169 | if(m_pClient->m_aClients[ClientId].m_Team == TEAM_RED) |
170 | TColor = ColorRGBA(1.0f, 0.5f, 0.5f, a); |
171 | else if(m_pClient->m_aClients[ClientId].m_Team == TEAM_BLUE) |
172 | TColor = ColorRGBA(0.7f, 0.7f, 1.0f, a); |
173 | } |
174 | |
175 | TOutlineColor.a *= Alpha; |
176 | TColor.a *= Alpha; |
177 | |
178 | if(m_aNamePlates[ClientId].m_NameTextContainerIndex.Valid()) |
179 | { |
180 | YOffset -= FontSize; |
181 | TextRender()->RenderTextContainer(TextContainerIndex: m_aNamePlates[ClientId].m_NameTextContainerIndex, TextColor: TColor, TextOutlineColor: TOutlineColor, X: Position.x - tw / 2.0f, Y: YOffset); |
182 | } |
183 | |
184 | if(g_Config.m_ClNameplatesClan) |
185 | { |
186 | YOffset -= FontSizeClan; |
187 | if(m_aNamePlates[ClientId].m_ClanNameTextContainerIndex.Valid()) |
188 | TextRender()->RenderTextContainer(TextContainerIndex: m_aNamePlates[ClientId].m_ClanNameTextContainerIndex, TextColor: TColor, TextOutlineColor: TOutlineColor, X: Position.x - m_aNamePlates[ClientId].m_ClanNameTextWidth / 2.0f, Y: YOffset); |
189 | } |
190 | |
191 | if(g_Config.m_ClNameplatesFriendMark && m_pClient->m_aClients[ClientId].m_Friend) |
192 | { |
193 | YOffset -= FontSize; |
194 | char aFriendMark[] = "♥" ; |
195 | |
196 | ColorRGBA Color; |
197 | |
198 | if(OtherTeam && !ForceAlpha) |
199 | Color = ColorRGBA(1.0f, 0.0f, 0.0f, g_Config.m_ClShowOthersAlpha / 100.0f); |
200 | else |
201 | Color = ColorRGBA(1.0f, 0.0f, 0.0f, a); |
202 | |
203 | Color.a *= Alpha; |
204 | |
205 | TextRender()->TextColor(rgb: Color); |
206 | float XOffSet = TextRender()->TextWidth(Size: FontSize, pText: aFriendMark, StrLength: -1, LineWidth: -1.0f) / 2.0f; |
207 | TextRender()->Text(x: Position.x - XOffSet, y: YOffset, Size: FontSize, pText: aFriendMark, LineWidth: -1.0f); |
208 | } |
209 | |
210 | if(g_Config.m_Debug || g_Config.m_ClNameplatesIds) // render client id when in debug as well |
211 | { |
212 | YOffset -= FontSize; |
213 | char aBuf[128]; |
214 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%d" , pPlayerInfo->m_ClientId); |
215 | float XOffset = TextRender()->TextWidth(Size: FontSize, pText: aBuf, StrLength: -1, LineWidth: -1.0f) / 2.0f; |
216 | TextRender()->TextColor(rgb); |
217 | TextRender()->Text(x: Position.x - XOffset, y: YOffset, Size: FontSize, pText: aBuf, LineWidth: -1.0f); |
218 | } |
219 | } |
220 | |
221 | if((g_Config.m_Debug || g_Config.m_ClNameplatesStrong) && g_Config.m_ClNameplates) |
222 | { |
223 | bool Following = (m_pClient->m_Snap.m_SpecInfo.m_Active && !GameClient()->m_MultiViewActivated && m_pClient->m_Snap.m_SpecInfo.m_SpectatorId != SPEC_FREEVIEW); |
224 | if(m_pClient->m_Snap.m_LocalClientId != -1 || Following) |
225 | { |
226 | int SelectedId = Following ? m_pClient->m_Snap.m_SpecInfo.m_SpectatorId : m_pClient->m_Snap.m_LocalClientId; |
227 | const CGameClient::CSnapState::CCharacterInfo &Selected = m_pClient->m_Snap.m_aCharacters[SelectedId]; |
228 | const CGameClient::CSnapState::CCharacterInfo &Other = m_pClient->m_Snap.m_aCharacters[ClientId]; |
229 | if(Selected.m_HasExtendedData && Other.m_HasExtendedData) |
230 | { |
231 | if(SelectedId == ClientId) |
232 | TextRender()->TextColor(rgb); |
233 | else |
234 | { |
235 | float ScaleX, ScaleY; |
236 | const float StrongWeakImgSize = 40.0f; |
237 | Graphics()->TextureClear(); |
238 | Graphics()->TextureSet(Texture: g_pData->m_aImages[IMAGE_STRONGWEAK].m_Id); |
239 | Graphics()->QuadsBegin(); |
240 | ColorRGBA StrongWeakStatusColor; |
241 | int StrongWeakSpriteId; |
242 | if(Selected.m_ExtendedData.m_StrongWeakId > Other.m_ExtendedData.m_StrongWeakId) |
243 | { |
244 | StrongWeakStatusColor = color_cast<ColorRGBA>(hsl: ColorHSLA(6401973)); |
245 | StrongWeakSpriteId = SPRITE_HOOK_STRONG; |
246 | } |
247 | else |
248 | { |
249 | StrongWeakStatusColor = color_cast<ColorRGBA>(hsl: ColorHSLA(41131)); |
250 | StrongWeakSpriteId = SPRITE_HOOK_WEAK; |
251 | } |
252 | |
253 | float ClampedAlpha = 1; |
254 | if(g_Config.m_ClNameplatesAlways == 0) |
255 | ClampedAlpha = clamp(val: 1 - std::pow(x: distance(a: m_pClient->m_Controls.m_aTargetPos[g_Config.m_ClDummy], b: Position) / 200.0f, y: 16.0f), lo: 0.0f, hi: 1.0f); |
256 | |
257 | if(OtherTeam && !ForceAlpha) |
258 | StrongWeakStatusColor.a = g_Config.m_ClShowOthersAlpha / 100.0f; |
259 | else |
260 | StrongWeakStatusColor.a = ClampedAlpha; |
261 | |
262 | StrongWeakStatusColor.a *= Alpha; |
263 | Graphics()->SetColor(StrongWeakStatusColor); |
264 | RenderTools()->SelectSprite(Id: StrongWeakSpriteId); |
265 | RenderTools()->GetSpriteScale(Id: StrongWeakSpriteId, ScaleX, ScaleY); |
266 | TextRender()->TextColor(rgb: StrongWeakStatusColor); |
267 | |
268 | YOffset -= StrongWeakImgSize * ScaleY; |
269 | RenderTools()->DrawSprite(x: Position.x, y: YOffset + (StrongWeakImgSize / 2.0f) * ScaleY, Size: StrongWeakImgSize); |
270 | Graphics()->QuadsEnd(); |
271 | } |
272 | if(g_Config.m_Debug || g_Config.m_ClNameplatesStrong == 2) |
273 | { |
274 | YOffset -= FontSize; |
275 | char aBuf[12]; |
276 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%d" , Other.m_ExtendedData.m_StrongWeakId); |
277 | float XOffset = TextRender()->TextWidth(Size: FontSize, pText: aBuf, StrLength: -1, LineWidth: -1.0f) / 2.0f; |
278 | TextRender()->Text(x: Position.x - XOffset, y: YOffset, Size: FontSize, pText: aBuf, LineWidth: -1.0f); |
279 | } |
280 | } |
281 | } |
282 | } |
283 | |
284 | TextRender()->TextColor(rgb: TextRender()->DefaultTextColor()); |
285 | TextRender()->TextOutlineColor(rgb: TextRender()->DefaultTextOutlineColor()); |
286 | |
287 | TextRender()->SetRenderFlags(0); |
288 | } |
289 | |
290 | void CNamePlates::OnRender() |
291 | { |
292 | if(Client()->State() != IClient::STATE_ONLINE && Client()->State() != IClient::STATE_DEMOPLAYBACK) |
293 | return; |
294 | |
295 | int ShowDirection = g_Config.m_ClShowDirection; |
296 | #if defined(CONF_VIDEORECORDER) |
297 | if(IVideo::Current()) |
298 | ShowDirection = g_Config.m_ClVideoShowDirection; |
299 | #endif |
300 | if(!g_Config.m_ClNameplates && ShowDirection == 0) |
301 | return; |
302 | |
303 | // get screen edges to avoid rendering offscreen |
304 | float ScreenX0, ScreenY0, ScreenX1, ScreenY1; |
305 | Graphics()->GetScreen(pTopLeftX: &ScreenX0, pTopLeftY: &ScreenY0, pBottomRightX: &ScreenX1, pBottomRightY: &ScreenY1); |
306 | // expand the edges to prevent popping in/out onscreen |
307 | // |
308 | // it is assumed that the nameplate and all its components fit into a 800x800 box placed directly above the tee |
309 | // this may need to be changed or calculated differently in the future |
310 | ScreenX0 -= 400; |
311 | ScreenX1 += 400; |
312 | //ScreenY0 -= 0; |
313 | ScreenY1 += 800; |
314 | |
315 | for(int i = 0; i < MAX_CLIENTS; i++) |
316 | { |
317 | const CNetObj_PlayerInfo *pInfo = m_pClient->m_Snap.m_apPlayerInfos[i]; |
318 | if(!pInfo) |
319 | { |
320 | continue; |
321 | } |
322 | |
323 | vec2 *pRenderPos; |
324 | if(m_pClient->m_aClients[i].m_SpecCharPresent) |
325 | { |
326 | // Each player can also have a spec char whose nameplate is displayed independently |
327 | pRenderPos = &m_pClient->m_aClients[i].m_SpecChar; |
328 | // don't render offscreen |
329 | if(!(pRenderPos->x < ScreenX0) && !(pRenderPos->x > ScreenX1) && !(pRenderPos->y < ScreenY0) && !(pRenderPos->y > ScreenY1)) |
330 | { |
331 | RenderNameplatePos(Position: m_pClient->m_aClients[i].m_SpecChar, pPlayerInfo: pInfo, Alpha: 0.4f, ForceAlpha: true); |
332 | } |
333 | } |
334 | if(m_pClient->m_Snap.m_aCharacters[i].m_Active) |
335 | { |
336 | // Only render nameplates for active characters |
337 | pRenderPos = &m_pClient->m_aClients[i].m_RenderPos; |
338 | // don't render offscreen |
339 | if(!(pRenderPos->x < ScreenX0) && !(pRenderPos->x > ScreenX1) && !(pRenderPos->y < ScreenY0) && !(pRenderPos->y > ScreenY1)) |
340 | { |
341 | RenderNameplate( |
342 | pPrevChar: &m_pClient->m_Snap.m_aCharacters[i].m_Prev, |
343 | pPlayerChar: &m_pClient->m_Snap.m_aCharacters[i].m_Cur, |
344 | pPlayerInfo: pInfo); |
345 | } |
346 | } |
347 | } |
348 | } |
349 | |
350 | void CNamePlates::ResetNamePlates() |
351 | { |
352 | for(auto &NamePlate : m_aNamePlates) |
353 | { |
354 | TextRender()->DeleteTextContainer(TextContainerIndex&: NamePlate.m_NameTextContainerIndex); |
355 | TextRender()->DeleteTextContainer(TextContainerIndex&: NamePlate.m_ClanNameTextContainerIndex); |
356 | |
357 | NamePlate.Reset(); |
358 | } |
359 | } |
360 | |
361 | void CNamePlates::OnWindowResize() |
362 | { |
363 | ResetNamePlates(); |
364 | } |
365 | |
366 | void CNamePlates::OnInit() |
367 | { |
368 | ResetNamePlates(); |
369 | |
370 | // Quad for the direction arrows above the player |
371 | m_DirectionQuadContainerIndex = Graphics()->CreateQuadContainer(AutomaticUpload: false); |
372 | RenderTools()->QuadContainerAddSprite(QuadContainerIndex: m_DirectionQuadContainerIndex, x: 0.f, y: 0.f, Size: 22.f); |
373 | Graphics()->QuadContainerUpload(ContainerIndex: m_DirectionQuadContainerIndex); |
374 | } |
375 | |